diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..fab7372d --- /dev/null +++ b/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/3rdparty/gmock/CHANGES b/3rdparty/gmock/CHANGES new file mode 100644 index 00000000..21ab4882 --- /dev/null +++ b/3rdparty/gmock/CHANGES @@ -0,0 +1,66 @@ +Changes for 1.5.0: + + * New feature: Google Mock can be safely used in multi-threaded tests + on platforms having pthreads. + * New feature: function for printing a value of arbitrary type. + * New feature: function ExplainMatchResult() for easy definition of + composite matchers. + * The new matcher API lets user-defined matchers generate custom + explanations more directly and efficiently. + * Better failure messages all around. + * NotNull() and IsNull() now work with smart pointers. + * Field() and Property() now work when the matcher argument is a pointer + passed by reference. + * Regular expression matchers on all platforms. + * Added GCC 4.0 support for Google Mock Doctor. + * Added gmock_all_test.cc for compiling most Google Mock tests + in a single file. + * Significantly cleaned up compiler warnings. + * Bug fixes, better test coverage, and implementation clean-ups. + + Potentially breaking changes: + + * Custom matchers defined using MatcherInterface or MakePolymorphicMatcher() + need to be updated after upgrading to Google Mock 1.5.0; matchers defined + using MATCHER or MATCHER_P* aren't affected. + * Dropped support for 'make install'. + +Changes for 1.4.0 (we skipped 1.2.* and 1.3.* to match the version of +Google Test): + + * Works in more environments: Symbian and minGW, Visual C++ 7.1. + * Lighter weight: comes with our own implementation of TR1 tuple (no + more dependency on Boost!). + * New feature: --gmock_catch_leaked_mocks for detecting leaked mocks. + * New feature: ACTION_TEMPLATE for defining templatized actions. + * New feature: the .After() clause for specifying expectation order. + * New feature: the .With() clause for for specifying inter-argument + constraints. + * New feature: actions ReturnArg(), ReturnNew(...), and + DeleteArg(). + * New feature: matchers Key(), Pair(), Args<...>(), AllArgs(), IsNull(), + and Contains(). + * New feature: utility class MockFunction, useful for checkpoints, etc. + * New feature: functions Value(x, m) and SafeMatcherCast(m). + * New feature: copying a mock object is rejected at compile time. + * New feature: a script for fusing all Google Mock and Google Test + source files for easy deployment. + * Improved the Google Mock doctor to diagnose more diseases. + * Improved the Google Mock generator script. + * Compatibility fixes for Mac OS X and gcc. + * Bug fixes and implementation clean-ups. + +Changes for 1.1.0: + + * New feature: ability to use Google Mock with any testing framework. + * New feature: macros for easily defining new matchers + * New feature: macros for easily defining new actions. + * New feature: more container matchers. + * New feature: actions for accessing function arguments and throwing + exceptions. + * Improved the Google Mock doctor script for diagnosing compiler errors. + * Bug fixes and implementation clean-ups. + +Changes for 1.0.0: + + * Initial Open Source release of Google Mock diff --git a/3rdparty/gmock/CONTRIBUTORS b/3rdparty/gmock/CONTRIBUTORS new file mode 100644 index 00000000..6e9ae362 --- /dev/null +++ b/3rdparty/gmock/CONTRIBUTORS @@ -0,0 +1,40 @@ +# This file contains a list of people who've made non-trivial +# contribution to the Google C++ Mocking Framework project. People +# who commit code to the project are encouraged to add their names +# here. Please keep the list sorted by first names. + +Benoit Sigoure +Bogdan Piloca +Chandler Carruth +Dave MacLachlan +David Anderson +Dean Sturtevant +Gene Volovich +Hal Burch +Jeffrey Yasskin +Jim Keller +Joe Walnes +Jon Wray +Keir Mierle +Keith Ray +Kostya Serebryany +Lev Makhlis +Manuel Klimek +Mario Tanev +Mark Paskin +Markus Heule +Matthew Simmons +Mike Bland +Neal Norwitz +Nermin Ozkiranartli +Owen Carlsen +Paneendra Ba +Paul Menage +Piotr Kaminski +Russ Rufer +Sverre Sundsdal +Takeshi Yoshino +Vadim Berman +Vlad Losev +Wolfgang Klier +Zhanyong Wan diff --git a/3rdparty/gmock/COPYING b/3rdparty/gmock/COPYING new file mode 100644 index 00000000..1941a11f --- /dev/null +++ b/3rdparty/gmock/COPYING @@ -0,0 +1,28 @@ +Copyright 2008, Google Inc. +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 name of Google Inc. nor the names of its +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 +OWNER 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. diff --git a/3rdparty/gmock/README b/3rdparty/gmock/README new file mode 100644 index 00000000..8e4150aa --- /dev/null +++ b/3rdparty/gmock/README @@ -0,0 +1,352 @@ +Google C++ Mocking Framework +============================ + +http://code.google.com/p/googlemock/ + +Overview +-------- + +Google's framework for writing and using C++ mock classes on a variety +of platforms (Linux, Mac OS X, Windows, Windows CE, Symbian, etc). +Inspired by jMock, EasyMock, and Hamcrest, and designed with C++'s +specifics in mind, it can help you derive better designs of your +system and write better tests. + +Google Mock: + +- provides a declarative syntax for defining mocks, +- can easily define partial (hybrid) mocks, which are a cross of real + and mock objects, +- handles functions of arbitrary types and overloaded functions, +- comes with a rich set of matchers for validating function arguments, +- uses an intuitive syntax for controlling the behavior of a mock, +- does automatic verification of expectations (no record-and-replay + needed), +- allows arbitrary (partial) ordering constraints on + function calls to be expressed, +- lets a user extend it by defining new matchers and actions. +- does not use exceptions, and +- is easy to learn and use. + +Please see the project page above for more information as well as the +mailing list for questions, discussions, and development. There is +also an IRC channel on OFTC (irc.oftc.net) #gtest available. Please +join us! + +Please note that code under scripts/generator/ is from the cppclean +project (http://code.google.com/p/cppclean/) and under the Apache +License, which is different from Google Mock's license. + +Requirements for End Users +-------------------------- + +Google Mock is implemented on top of the Google Test C++ testing +framework (http://code.google.com/p/googletest/), and includes the +latter as part of the SVN repositary and distribution package. You +must use the bundled version of Google Test when using Google Mock, or +you may get compiler/linker errors. + +You can also easily configure Google Mock to work with another testing +framework of your choice; although it will still need Google Test as +an internal dependency. Please read +http://code.google.com/p/googlemock/wiki/ForDummies#Using_Google_Mock_with_Any_Testing_Framework +for how to do it. + +Google Mock depends on advanced C++ features and thus requires a more +modern compiler. The following are needed to use Google Mock: + +### Linux Requirements ### + +These are the base requirements to build and use Google Mock from a source +package (as described below): + + * GNU-compatible Make or "gmake" + * POSIX-standard shell + * POSIX(-2) Regular Expressions (regex.h) + * C++98-standard-compliant compiler (e.g. GCC 3.4 or newer) + +### Windows Requirements ### + + * Microsoft Visual C++ 8.0 SP1 or newer + +### Mac OS X Requirements ### + + * Mac OS X 10.4 Tiger or newer + * Developer Tools Installed + +Requirements for Contributors +----------------------------- + +We welcome patches. If you plan to contribute a patch, you need to +build Google Mock and its own tests from an SVN checkout (described +below), which has further requirements: + + * Automake version 1.9 or newer + * Autoconf version 2.59 or newer + * Libtool / Libtoolize + * Python version 2.3 or newer (for running some of the tests and + re-generating certain source files from templates) + +Getting the Source +------------------ + +There are two primary ways of getting Google Mock's source code: you +can download a stable source release in your preferred archive format, +or directly check out the source from our Subversion (SVN) repositary. +The SVN checkout requires a few extra steps and some extra software +packages on your system, but lets you track development and make +patches much more easily, so we highly encourage it. + +### Source Package ### + +Google Mock is released in versioned source packages which can be +downloaded from the download page [1]. Several different archive +formats are provided, but the only difference is the tools needed to +extract their contents, and the size of the resulting file. Download +whichever you are most comfortable with. + + [1] http://code.google.com/p/googlemock/downloads/list + +Once downloaded expand the archive using whichever tools you prefer +for that type. This will always result in a new directory with the +name "gmock-X.Y.Z" which contains all of the source code. Here are +some examples on Linux: + + tar -xvzf gmock-X.Y.Z.tar.gz + tar -xvjf gmock-X.Y.Z.tar.bz2 + unzip gmock-X.Y.Z.zip + +### SVN Checkout ### + +To check out the main branch (also known as the "trunk") of Google +Mock, run the following Subversion command: + + svn checkout http://googlemock.googlecode.com/svn/trunk/ gmock-svn + +If you are using a *nix system and plan to use the GNU Autotools build +system to build Google Mock (described below), you'll need to +configure it now. Otherwise you are done with getting the source +files. + +To prepare the Autotools build system, enter the target directory of +the checkout command you used ('gmock-svn') and proceed with the +following command: + + autoreconf -fvi + +Once you have completed this step, you are ready to build the library. +Note that you should only need to complete this step once. The +subsequent 'make' invocations will automatically re-generate the bits +of the build system that need to be changed. + +If your system uses older versions of the autotools, the above command +will fail. You may need to explicitly specify a version to use. For +instance, if you have both GNU Automake 1.4 and 1.9 installed and +'automake' would invoke the 1.4, use instead: + + AUTOMAKE=automake-1.9 ACLOCAL=aclocal-1.9 autoreconf -fvi + +Make sure you're using the same version of automake and aclocal. + +Setting up the Build +-------------------- + +To build Google Mock and your tests that use it, you need to tell your +build system where to find its headers and source files. The exact +way to do it depends on which build system you use, and is usually +straightforward. + +### Generic Build Instructions ### + +This section shows how you can integrate Google Mock into your +existing build system. + +Suppose you put Google Mock in directory ${GMOCK_DIR} and Google Test +in ${GTEST_DIR} (the latter is ${GMOCK_DIR}/gtest by default). To +build Google Mock, create a library build target (or a project as +called by Visual Studio and Xcode) to compile + + ${GTEST_DIR}/src/gtest-all.cc and ${GMOCK_DIR}/src/gmock-all.cc + +with + + ${GTEST_DIR}/include, ${GTEST_DIR}, ${GMOCK_DIR}/include, and ${GMOCK_DIR} + +in the header search path. Assuming a Linux-like system and gcc, +something like the following will do: + + g++ -I${GTEST_DIR}/include -I${GTEST_DIR} -I${GMOCK_DIR}/include \ + -I${GMOCK_DIR} -c ${GTEST_DIR}/src/gtest-all.cc + g++ -I${GTEST_DIR}/include -I${GTEST_DIR} -I${GMOCK_DIR}/include \ + -I${GMOCK_DIR} -c ${GMOCK_DIR}/src/gmock-all.cc + ar -rv libgmock.a gtest-all.o gmock-all.o + +Next, you should compile your test source file with +${GTEST_DIR}/include and ${GMOCK_DIR}/include in the header search +path, and link it with gmock and any other necessary libraries: + + g++ -I${GTEST_DIR}/include -I${GMOCK_DIR}/include \ + path/to/your_test.cc libgmock.a -o your_test + +As an example, the make/ directory contains a Makefile that you can +use to build Google Mock on systems where GNU make is available +(e.g. Linux, Mac OS X, and Cygwin). It doesn't try to build Google +Mock's own tests. Instead, it just builds the Google Mock library and +a sample test. You can use it as a starting point for your own build +script. + +If the default settings are correct for your environment, the +following commands should succeed: + + cd ${GMOCK_DIR}/make + make + ./gmock_test + +If you see errors, try to tweak the contents of make/Makefile to make +them go away. There are instructions in make/Makefile on how to do +it. + +### Windows ### + +The msvc/ directory contains VC++ 2005 projects for building Google +Mock and selected tests. + +Open msvc/gmock.sln and build the library and tests. If you want to +create your own project to use with Google Mock, you'll have to +configure it to use the gmock_config propety sheet. For that: + + * Open the Property Manager window (View | Other Windows | Property Manager) + * Right-click on your project and select "Add Existing Property Sheet..." + * Navigate to gmock_config.vsprops and select it. + * In Project Properties | Configuration Properties | General | Additional + Include Directories, type /include. + +Tweaking Google Mock +-------------------- + +Google Mock can be used in diverse environments. The default +configuration may not work (or may not work well) out of the box in +some environments. However, you can easily tweak Google Mock by +defining control macros on the compiler command line. Generally, +these macros are named like GTEST_XYZ and you define them to either 1 +or 0 to enable or disable a certain feature. + +We list the most frequently used macros below. For a complete list, +see file ${GTEST_DIR}/include/gtest/internal/gtest-port.h. + +### Choosing a TR1 Tuple Library ### + +Google Mock uses the C++ Technical Report 1 (TR1) tuple library +heavily. Unfortunately TR1 tuple is not yet widely available with all +compilers. The good news is that Google Test 1.4.0+ implements a +subset of TR1 tuple that's enough for Google Mock's need. Google Mock +will automatically use that implementation when the compiler doesn't +provide TR1 tuple. + +Usually you don't need to care about which tuple library Google Test +and Google Mock use. However, if your project already uses TR1 tuple, +you need to tell Google Test and Google Mock to use the same TR1 tuple +library the rest of your project uses, or the two tuple +implementations will clash. To do that, add + + -DGTEST_USE_OWN_TR1_TUPLE=0 + +to the compiler flags while compiling Google Test, Google Mock, and +your tests. If you want to force Google Test and Google Mock to use +their own tuple library, just add + + -DGTEST_USE_OWN_TR1_TUPLE=1 + +to the compiler flags instead. + +If you want to use Boost's TR1 tuple library with Google Mock, please +refer to the Boost website (http://www.boost.org/) for how to obtain +it and set it up. + +### Tweaking Google Test ### + +Most of Google Test's control macros apply to Google Mock as well. +Please see file ${GTEST_DIR}/README for how to tweak them. + +Upgrading from an Earlier Version +--------------------------------- + +We strive to keep Google Mock releases backward compatible. +Sometimes, though, we have to make some breaking changes for the +users' long-term benefits. This section describes what you'll need to +do if you are upgrading from an earlier version of Google Mock. + +### Upgrading from 1.1.0 or Earlier ### + +You may need to explicitly enable or disable Google Test's own TR1 +tuple library. See the instructions in section "Choosing a TR1 Tuple +Library". + +### Upgrading from 1.4.0 or Earlier ### + +On platforms where the pthread library is available, Google Test and +Google Mock use it in order to be thread-safe. For this to work, you +may need to tweak your compiler and/or linker flags. Please see the +"Multi-threaded Tests" section in file ${GTEST_DIR}/README for what +you may need to do. + +If you have custom matchers defined using MatcherInterface or +MakePolymorphicMatcher(), you'll need to update their definitions to +use the new matcher API [2]. Matchers defined using MATCHER() or +MATCHER_P*() aren't affected. + + [2] http://code.google.com/p/googlemock/wiki/CookBook#Writing_New_Monomorphic_Matchers, + http://code.google.com/p/googlemock/wiki/CookBook#Writing_New_Polymorphic_Matchers + +Developing Google Mock +---------------------- + +This section discusses how to make your own changes to Google Mock. + +### Testing Google Mock Itself ### + +To make sure your changes work as intended and don't break existing +functionality, you'll want to compile and run Google Test's own tests. +For that you'll need Autotools. First, make sure you have followed +the instructions in section "SVN Checkout" to configure Google Mock. +Then, create a build output directory and enter it. Next, + + ${GMOCK_DIR}/configure # Standard GNU configure script, --help for more info + +Once you have successfully configured Google Mock, the build steps are +standard for GNU-style OSS packages. + + make # Standard makefile following GNU conventions + make check # Builds and runs all tests - all should pass. + +Note that when building your project against Google Mock, you are building +against Google Test as well. There is no need to configure Google Test +separately. + +### Regenerating Source Files ### + +Some of Google Mock's source files are generated from templates (not +in the C++ sense) using a script. A template file is named FOO.pump, +where FOO is the name of the file it will generate. For example, the +file include/gmock/gmock-generated-actions.h.pump is used to generate +gmock-generated-actions.h in the same directory. + +Normally you don't need to worry about regenerating the source files, +unless you need to modify them. In that case, you should modify the +corresponding .pump files instead and run the 'pump' script (for Pump +is Useful for Meta Programming) to regenerate them. You can find +pump.py in the ${GTEST_DIR}/scripts/ directory. Read the Pump manual +[3] for how to use it. + + [3] http://code.google.com/p/googletest/wiki/PumpManual. + +### Contributing a Patch ### + +We welcome patches. Please read the Google Mock developer's guide [4] +for how you can contribute. In particular, make sure you have signed +the Contributor License Agreement, or we won't be able to accept the +patch. + + [4] http://code.google.com/p/googlemock/wiki/DevGuide + +Happy testing! diff --git a/3rdparty/gmock/gtest/CHANGES b/3rdparty/gmock/gtest/CHANGES new file mode 100644 index 00000000..e574415e --- /dev/null +++ b/3rdparty/gmock/gtest/CHANGES @@ -0,0 +1,98 @@ +Changes for 1.5.0: + + * New feature: assertions can be safely called in multiple threads + where the pthreads library is available. + * New feature: predicates used inside EXPECT_TRUE() and friends + can now generate custom failure messages. + * New feature: Google Test can now be compiled as a DLL. + * New feature: fused source files are included. + * New feature: prints help when encountering unrecognized Google Test flags. + * Experimental feature: CMake build script (requires CMake 2.6.4+). + * Experimental feature: the Pump script for meta programming. + * double values streamed to an assertion are printed with enough precision + to differentiate any two different values. + * Google Test now works on Solaris and AIX. + * Build and test script improvements. + * Bug fixes and implementation clean-ups. + + Potentially breaking changes: + + * Stopped supporting VC++ 7.1 with exceptions disabled. + * Dropped support for 'make install'. + +Changes for 1.4.0: + + * New feature: the event listener API + * New feature: test shuffling + * New feature: the XML report format is closer to junitreport and can + be parsed by Hudson now. + * New feature: when a test runs under Visual Studio, its failures are + integrated in the IDE. + * New feature: /MD(d) versions of VC++ projects. + * New feature: elapsed time for the tests is printed by default. + * New feature: comes with a TR1 tuple implementation such that Boost + is no longer needed for Combine(). + * New feature: EXPECT_DEATH_IF_SUPPORTED macro and friends. + * New feature: the Xcode project can now produce static gtest + libraries in addition to a framework. + * Compatibility fixes for Solaris, Cygwin, minGW, Windows Mobile, + Symbian, gcc, and C++Builder. + * Bug fixes and implementation clean-ups. + +Changes for 1.3.0: + + * New feature: death tests on Windows, Cygwin, and Mac. + * New feature: ability to use Google Test assertions in other testing + frameworks. + * New feature: ability to run disabled test via + --gtest_also_run_disabled_tests. + * New feature: the --help flag for printing the usage. + * New feature: access to Google Test flag values in user code. + * New feature: a script that packs Google Test into one .h and one + .cc file for easy deployment. + * New feature: support for distributing test functions to multiple + machines (requires support from the test runner). + * Bug fixes and implementation clean-ups. + +Changes for 1.2.1: + + * Compatibility fixes for Linux IA-64 and IBM z/OS. + * Added support for using Boost and other TR1 implementations. + * Changes to the build scripts to support upcoming release of Google C++ + Mocking Framework. + * Added Makefile to the distribution package. + * Improved build instructions in README. + +Changes for 1.2.0: + + * New feature: value-parameterized tests. + * New feature: the ASSERT/EXPECT_(NON)FATAL_FAILURE(_ON_ALL_THREADS) + macros. + * Changed the XML report format to match JUnit/Ant's. + * Added tests to the Xcode project. + * Added scons/SConscript for building with SCons. + * Added src/gtest-all.cc for building Google Test from a single file. + * Fixed compatibility with Solaris and z/OS. + * Enabled running Python tests on systems with python 2.3 installed, + e.g. Mac OS X 10.4. + * Bug fixes. + +Changes for 1.1.0: + + * New feature: type-parameterized tests. + * New feature: exception assertions. + * New feature: printing elapsed time of tests. + * Improved the robustness of death tests. + * Added an Xcode project and samples. + * Adjusted the output format on Windows to be understandable by Visual Studio. + * Minor bug fixes. + +Changes for 1.0.1: + + * Added project files for Visual Studio 7.1. + * Fixed issues with compiling on Mac OS X. + * Fixed issues with compiling on Cygwin. + +Changes for 1.0.0: + + * Initial Open Source release of Google Test diff --git a/3rdparty/gmock/gtest/CONTRIBUTORS b/3rdparty/gmock/gtest/CONTRIBUTORS new file mode 100644 index 00000000..0934ae13 --- /dev/null +++ b/3rdparty/gmock/gtest/CONTRIBUTORS @@ -0,0 +1,36 @@ +# This file contains a list of people who've made non-trivial +# contribution to the Google C++ Testing Framework project. People +# who commit code to the project are encouraged to add their names +# here. Please keep the list sorted by first names. + +Ajay Joshi +Balázs Dán +Bharat Mediratta +Chandler Carruth +Chris Prince +Chris Taylor +Dan Egnor +Eric Roman +Hady Zalek +Jeffrey Yasskin +Jói Sigurðsson +Keir Mierle +Keith Ray +Kenton Varda +Manuel Klimek +Markus Heule +Mika Raento +Miklós Fazekas +Patrick Hanna +Patrick Riley +Peter Kaminski +Preston Jackson +Rainer Klaffenboeck +Russ Cox +Russ Rufer +Sean Mcafee +Sigurður Ásgeirsson +Tracy Bialik +Vadim Berman +Vlad Losev +Zhanyong Wan diff --git a/3rdparty/gmock/gtest/COPYING b/3rdparty/gmock/gtest/COPYING new file mode 100644 index 00000000..1941a11f --- /dev/null +++ b/3rdparty/gmock/gtest/COPYING @@ -0,0 +1,28 @@ +Copyright 2008, Google Inc. +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 name of Google Inc. nor the names of its +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 +OWNER 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. diff --git a/3rdparty/gmock/gtest/README b/3rdparty/gmock/gtest/README new file mode 100644 index 00000000..ec611900 --- /dev/null +++ b/3rdparty/gmock/gtest/README @@ -0,0 +1,417 @@ +Google C++ Testing Framework +============================ + +http://code.google.com/p/googletest/ + +Overview +-------- + +Google's framework for writing C++ tests on a variety of platforms +(Linux, Mac OS X, Windows, Windows CE, Symbian, etc). Based on the +xUnit architecture. Supports automatic test discovery, a rich set of +assertions, user-defined assertions, death tests, fatal and non-fatal +failures, various options for running the tests, and XML test report +generation. + +Please see the project page above for more information as well as the +mailing list for questions, discussions, and development. There is +also an IRC channel on OFTC (irc.oftc.net) #gtest available. Please +join us! + +Requirements for End Users +-------------------------- + +Google Test is designed to have fairly minimal requirements to build +and use with your projects, but there are some. Currently, we support +Linux, Windows, Mac OS X, and Cygwin. We will also make our best +effort to support other platforms (e.g. Solaris, AIX, and z/OS). +However, since core members of the Google Test project have no access +to these platforms, Google Test may have outstanding issues there. If +you notice any problems on your platform, please notify +googletestframework@googlegroups.com. Patches for fixing them are +even more welcome! + +### Linux Requirements ### + +These are the base requirements to build and use Google Test from a source +package (as described below): + * GNU-compatible Make or gmake + * POSIX-standard shell + * POSIX(-2) Regular Expressions (regex.h) + * A C++98-standard-compliant compiler + +### Windows Requirements ### + + * Microsoft Visual C++ 7.1 or newer + +### Cygwin Requirements ### + + * Cygwin 1.5.25-14 or newer + +### Mac OS X Requirements ### + + * Mac OS X 10.4 Tiger or newer + * Developer Tools Installed + +Also, you'll need CMake 2.6.4 or higher if you want to build the +samples using the provided CMake script, regardless of the platform. + +Requirements for Contributors +----------------------------- + +We welcome patches. If you plan to contribute a patch, you need to +build Google Test and its own tests from an SVN checkout (described +below), which has further requirements: + + * Python version 2.3 or newer (for running some of the tests and + re-generating certain source files from templates) + * CMake 2.6.4 or newer + +Getting the Source +------------------ + +There are two primary ways of getting Google Test's source code: you +can download a stable source release in your preferred archive format, +or directly check out the source from our Subversion (SVN) repositary. +The SVN checkout requires a few extra steps and some extra software +packages on your system, but lets you track the latest development and +make patches much more easily, so we highly encourage it. + +### Source Package ### + +Google Test is released in versioned source packages which can be +downloaded from the download page [1]. Several different archive +formats are provided, but the only difference is the tools used to +manipulate them, and the size of the resulting file. Download +whichever you are most comfortable with. + + [1] http://code.google.com/p/googletest/downloads/list + +Once the package is downloaded, expand it using whichever tools you +prefer for that type. This will result in a new directory with the +name "gtest-X.Y.Z" which contains all of the source code. Here are +some examples on Linux: + + tar -xvzf gtest-X.Y.Z.tar.gz + tar -xvjf gtest-X.Y.Z.tar.bz2 + unzip gtest-X.Y.Z.zip + +### SVN Checkout ### + +To check out the main branch (also known as the "trunk") of Google +Test, run the following Subversion command: + + svn checkout http://googletest.googlecode.com/svn/trunk/ gtest-svn + +Setting up the Build +-------------------- + +To build Google Test and your tests that use it, you need to tell your +build system where to find its headers and source files. The exact +way to do it depends on which build system you use, and is usually +straightforward. + +### Generic Build Instructions ### + +Suppose you put Google Test in directory ${GTEST_DIR}. To build it, +create a library build target (or a project as called by Visual Studio +and Xcode) to compile + + ${GTEST_DIR}/src/gtest-all.cc + +with + + ${GTEST_DIR}/include and ${GTEST_DIR} + +in the header search path. Assuming a Linux-like system and gcc, +something like the following will do: + + g++ -I${GTEST_DIR}/include -I${GTEST_DIR} -c ${GTEST_DIR}/src/gtest-all.cc + ar -rv libgtest.a gtest-all.o + +Next, you should compile your test source file with +${GTEST_DIR}/include in the header search path, and link it with gtest +and any other necessary libraries: + + g++ -I${GTEST_DIR}/include path/to/your_test.cc libgtest.a -o your_test + +As an example, the make/ directory contains a Makefile that you can +use to build Google Test on systems where GNU make is available +(e.g. Linux, Mac OS X, and Cygwin). It doesn't try to build Google +Test's own tests. Instead, it just builds the Google Test library and +a sample test. You can use it as a starting point for your own build +script. + +If the default settings are correct for your environment, the +following commands should succeed: + + cd ${GTEST_DIR}/make + make + ./sample1_unittest + +If you see errors, try to tweak the contents of make/Makefile to make +them go away. There are instructions in make/Makefile on how to do +it. + +### Using CMake ### + +Google Test comes with a CMake build script (CMakeLists.txt) that can +be used on a wide range of platforms ("C" stands for cross-platofrm.). +If you don't have CMake installed already, you can download it for +free from http://www.cmake.org/. + +CMake works by generating native makefiles or build projects that can +be used in the compiler environment of your choice. The typical +workflow starts with: + + mkdir mybuild # Create a directory to hold the build output. + cd mybuild + cmake ${GTEST_DIR} # Generate native build scripts. + +If you want to build Google Test's samples, you should replace the +last command with + + cmake -Dbuild_gtest_samples=ON ${GTEST_DIR} + +If you are on a *nix system, you should now see a Makefile in the +current directory. Just type 'make' to build gtest. + +If you use Windows and have Vistual Studio installed, a gtest.sln file +and several .vcproj files will be created. You can then build them +using Visual Studio. + +On Mac OS X with Xcode installed, a .xcodeproj file will be generated. + +### Legacy Build Scripts ### + +Before settling on CMake, we have been providing hand-maintained build +projects/scripts for Visual Studio, Xcode, and Autotools. While we +continue to provide them for convenience, they are not actively +maintained any more. We highly recommend that you follow the +instructions in the previous two sections to integrate Google Test +with your existing build system. + +If you still need to use the legacy build scripts, here's how: + +The msvc\ folder contains two solutions with Visual C++ projects. +Open the gtest.sln or gtest-md.sln file using Visual Studio, and you +are ready to build Google Test the same way you build any Visual +Studio project. Files that have names ending with -md use DLL +versions of Microsoft runtime libraries (the /MD or the /MDd compiler +option). Files without that suffix use static versions of the runtime +libraries (the /MT or the /MTd option). Please note that one must use +the same option to compile both gtest and the test code. If you use +Visual Studio 2005 or above, we recommend the -md version as /MD is +the default for new projects in these versions of Visual Studio. + +On Mac OS X, open the gtest.xcodeproj in the xcode/ folder using +Xcode. Build the "gtest" target. The universal binary framework will +end up in your selected build directory (selected in the Xcode +"Preferences..." -> "Building" pane and defaults to xcode/build). +Alternatively, at the command line, enter: + + xcodebuild + +This will build the "Release" configuration of gtest.framework in your +default build location. See the "xcodebuild" man page for more +information about building different configurations and building in +different locations. + +Tweaking Google Test +-------------------- + +Google Test can be used in diverse environments. The default +configuration may not work (or may not work well) out of the box in +some environments. However, you can easily tweak Google Test by +defining control macros on the compiler command line. Generally, +these macros are named like GTEST_XYZ and you define them to either 1 +or 0 to enable or disable a certain feature. + +We list the most frequently used macros below. For a complete list, +see file include/gtest/internal/gtest-port.h. + +### Choosing a TR1 Tuple Library ### + +Some Google Test features require the C++ Technical Report 1 (TR1) +tuple library, which is not yet available with all compilers. The +good news is that Google Test implements a subset of TR1 tuple that's +enough for its own need, and will automatically use this when the +compiler doesn't provide TR1 tuple. + +Usually you don't need to care about which tuple library Google Test +uses. However, if your project already uses TR1 tuple, you need to +tell Google Test to use the same TR1 tuple library the rest of your +project uses, or the two tuple implementations will clash. To do +that, add + + -DGTEST_USE_OWN_TR1_TUPLE=0 + +to the compiler flags while compiling Google Test and your tests. If +you want to force Google Test to use its own tuple library, just add + + -DGTEST_USE_OWN_TR1_TUPLE=1 + +to the compiler flags instead. + +If you don't want Google Test to use tuple at all, add + + -DGTEST_HAS_TR1_TUPLE=0 + +and all features using tuple will be disabled. + +### Multi-threaded Tests ### + +Google Test is thread-safe where the pthread library is available. +After #include , you can check the GTEST_IS_THREADSAFE +macro to see whether this is the case (yes if the macro is #defined to +1, no if it's undefined.). + +If Google Test doesn't correctly detect whether pthread is available +in your environment, you can force it with + + -DGTEST_HAS_PTHREAD=1 + +or + + -DGTEST_HAS_PTHREAD=0 + +When Google Test uses pthread, you may need to add flags to your +compiler and/or linker to select the pthread library, or you'll get +link errors. If you use the CMake script or the deprecated Autotools +script, this is taken care of for you. If you use your own build +script, you'll need to read your compiler and linker's manual to +figure out what flags to add. + +### As a Shared Library (DLL) ### + +Google Test is compact, so most users can build and link it as a +static library for the simplicity. You can choose to use Google Test +as a shared library (known as a DLL on Windows) if you prefer. + +To compile gtest as a shared library, add + + -DGTEST_CREATE_SHARED_LIBRARY=1 + +to the compiler flags. You'll also need to tell the linker to produce +a shared library instead - consult your linker's manual for how to do +it. + +To compile your tests that use the gtest shared library, add + + -DGTEST_LINKED_AS_SHARED_LIBRARY=1 + +to the compiler flags. + +### Avoiding Macro Name Clashes ### + +In C++, macros don't obey namespaces. Therefore two libraries that +both define a macro of the same name will clash if you #include both +definitions. In case a Google Test macro clashes with another +library, you can force Google Test to rename its macro to avoid the +conflict. + +Specifically, if both Google Test and some other code define macro +FOO, you can add + + -DGTEST_DONT_DEFINE_FOO=1 + +to the compiler flags to tell Google Test to change the macro's name +from FOO to GTEST_FOO. Currently FOO can be FAIL, SUCCEED, or TEST. +For example, with -DGTEST_DONT_DEFINE_TEST=1, you'll need to write + + GTEST_TEST(SomeTest, DoesThis) { ... } + +instead of + + TEST(SomeTest, DoesThis) { ... } + +in order to define a test. + +Upgrating from an Earlier Version +--------------------------------- + +We strive to keep Google Test releases backward compatible. +Sometimes, though, we have to make some breaking changes for the +users' long-term benefits. This section describes what you'll need to +do if you are upgrading from an earlier version of Google Test. + +### Upgrading from 1.3.0 or Earlier ### + +You may need to explicitly enable or disable Google Test's own TR1 +tuple library. See the instructions in section "Choosing a TR1 Tuple +Library". + +### Upgrading from 1.4.0 or Earlier ### + +The Autotools build script (configure + make) is no longer officially +supportted. You are encouraged to migrate to your own build system or +use CMake. If you still need to use Autotools, you can find +instructions in the README file from Google Test 1.4.0. + +On platforms where the pthread library is available, Google Test uses +it in order to be thread-safe. See the "Multi-threaded Tests" section +for what this means to your build script. + +If you use Microsoft Visual C++ 7.1 with exceptions disabled, Google +Test will no longer compile. This should affect very few people, as a +large portion of STL (including ) doesn't compile in this mode +anyway. We decided to stop supporting it in order to greatly simplify +Google Test's implementation. + +Developing Google Test +---------------------- + +This section discusses how to make your own changes to Google Test. + +### Testing Google Test Itself ### + +To make sure your changes work as intended and don't break existing +functionality, you'll want to compile and run Google Test's own tests. +For that you can use CMake: + + mkdir mybuild + cd mybuild + cmake -Dbuild_all_gtest_tests=ON ${GTEST_DIR} + +Make sure you have Python installed, as some of Google Test's tests +are written in Python. If the cmake command complains about not being +able to find Python ("Could NOT find PythonInterp (missing: +PYTHON_EXECUTABLE)"), try telling it explicitly where your Python +executable can be found: + + cmake -DPYTHON_EXECUTABLE=path/to/python -Dbuild_all_gtest_tests=ON \ + ${GTEST_DIR} + +Next, you can build Google Test and all of its own tests. On *nix, +this is usually done by 'make'. To run the tests, do + + make test + +All tests should pass. + +### Regenerating Source Files ### + +Some of Google Test's source files are generated from templates (not +in the C++ sense) using a script. A template file is named FOO.pump, +where FOO is the name of the file it will generate. For example, the +file include/gtest/internal/gtest-type-util.h.pump is used to generate +gtest-type-util.h in the same directory. + +Normally you don't need to worry about regenerating the source files, +unless you need to modify them. In that case, you should modify the +corresponding .pump files instead and run the pump.py Python script to +regenerate them. You can find pump.py in the scripts/ directory. +Read the Pump manual [2] for how to use it. + + [2] http://code.google.com/p/googletest/wiki/PumpManual + +### Contributing a Patch ### + +We welcome patches. Please read the Google Test developer's guide [3] +for how you can contribute. In particular, make sure you have signed +the Contributor License Agreement, or we won't be able to accept the +patch. + + [3] http://code.google.com/p/googletest/wiki/GoogleTestDevGuide + +Happy testing! diff --git a/3rdparty/gmock/gtest/codegear/gtest.cbproj b/3rdparty/gmock/gtest/codegear/gtest.cbproj new file mode 100644 index 00000000..95c3054b --- /dev/null +++ b/3rdparty/gmock/gtest/codegear/gtest.cbproj @@ -0,0 +1,138 @@ + + + + {bca37a72-5b07-46cf-b44e-89f8e06451a2} + Release + + + true + + + true + true + Base + + + true + true + Base + + + true + lib + JPHNE + NO_STRICT + true + true + CppStaticLibrary + true + rtl.bpi;vcl.bpi;bcbie.bpi;vclx.bpi;vclactnband.bpi;xmlrtl.bpi;bcbsmp.bpi;dbrtl.bpi;vcldb.bpi;bdertl.bpi;vcldbx.bpi;dsnap.bpi;dsnapcon.bpi;vclib.bpi;ibxpress.bpi;adortl.bpi;dbxcds.bpi;dbexpress.bpi;DbxCommonDriver.bpi;websnap.bpi;vclie.bpi;webdsnap.bpi;inet.bpi;inetdbbde.bpi;inetdbxpress.bpi;soaprtl.bpi;Rave75VCL.bpi;teeUI.bpi;tee.bpi;teedb.bpi;IndyCore.bpi;IndySystem.bpi;IndyProtocols.bpi;IntrawebDB_90_100.bpi;Intraweb_90_100.bpi;dclZipForged11.bpi;vclZipForged11.bpi;GR32_BDS2006.bpi;GR32_DSGN_BDS2006.bpi;Jcl.bpi;JclVcl.bpi;JvCoreD11R.bpi;JvSystemD11R.bpi;JvStdCtrlsD11R.bpi;JvAppFrmD11R.bpi;JvBandsD11R.bpi;JvDBD11R.bpi;JvDlgsD11R.bpi;JvBDED11R.bpi;JvCmpD11R.bpi;JvCryptD11R.bpi;JvCtrlsD11R.bpi;JvCustomD11R.bpi;JvDockingD11R.bpi;JvDotNetCtrlsD11R.bpi;JvEDID11R.bpi;JvGlobusD11R.bpi;JvHMID11R.bpi;JvInterpreterD11R.bpi;JvJansD11R.bpi;JvManagedThreadsD11R.bpi;JvMMD11R.bpi;JvNetD11R.bpi;JvPageCompsD11R.bpi;JvPluginD11R.bpi;JvPrintPreviewD11R.bpi;JvRuntimeDesignD11R.bpi;JvTimeFrameworkD11R.bpi;JvValidatorsD11R.bpi;JvWizardD11R.bpi;JvXPCtrlsD11R.bpi;VclSmp.bpi;CExceptionExpert11.bpi + false + $(BDS)\include;$(BDS)\include\dinkumware;$(BDS)\include\vcl;..\src;..\include;.. + rtl.lib;vcl.lib + 32 + $(BDS)\lib;$(BDS)\lib\obj;$(BDS)\lib\psdk + + + false + false + true + _DEBUG;$(Defines) + true + false + true + None + DEBUG + true + Debug + true + true + true + $(BDS)\lib\debug;$(ILINK_LibraryPath) + Full + true + + + NDEBUG;$(Defines) + Release + $(BDS)\lib\release;$(ILINK_LibraryPath) + None + + + CPlusPlusBuilder.Personality + CppStaticLibrary + +FalseFalse1000FalseFalseFalseFalseFalse103312521.0.0.01.0.0.0FalseFalseFalseTrueFalse + + + CodeGear C++Builder Office 2000 Servers Package + CodeGear C++Builder Office XP Servers Package + FalseTrueTrue3$(BDS)\include;$(BDS)\include\dinkumware;$(BDS)\include\vcl;..\src;..\include;..$(BDS)\include;$(BDS)\include\dinkumware;$(BDS)\include\vcl;..\src;..\include;..$(BDS)\include;$(BDS)\include\dinkumware;$(BDS)\include\vcl;..\src;..\src;..\include1$(BDS)\lib;$(BDS)\lib\obj;$(BDS)\lib\psdk1NO_STRICT13216 + + + + + 3 + + + 4 + + + 5 + + + 6 + + + 7 + + + 8 + + + 0 + + + 1 + + + 2 + + + 9 + + + 10 + + + 11 + + + 12 + + + 14 + + + 13 + + + 15 + + + 16 + + + 17 + + + 18 + + + Cfg_1 + + + Cfg_2 + + + \ No newline at end of file diff --git a/3rdparty/gmock/gtest/codegear/gtest.groupproj b/3rdparty/gmock/gtest/codegear/gtest.groupproj new file mode 100644 index 00000000..faf31cab --- /dev/null +++ b/3rdparty/gmock/gtest/codegear/gtest.groupproj @@ -0,0 +1,54 @@ + + + {c1d923e0-6cba-4332-9b6f-3420acbf5091} + + + + + + + + + Default.Personality + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/3rdparty/gmock/gtest/codegear/gtest_all.cc b/3rdparty/gmock/gtest/codegear/gtest_all.cc new file mode 100644 index 00000000..121b2d80 --- /dev/null +++ b/3rdparty/gmock/gtest/codegear/gtest_all.cc @@ -0,0 +1,38 @@ +// Copyright 2009, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: Josh Kelley (joshkel@gmail.com) +// +// Google C++ Testing Framework (Google Test) +// +// C++Builder's IDE cannot build a static library from files with hyphens +// in their name. See http://qc.codegear.com/wc/qcmain.aspx?d=70977 . +// This file serves as a workaround. + +#include "src/gtest-all.cc" diff --git a/3rdparty/gmock/gtest/codegear/gtest_link.cc b/3rdparty/gmock/gtest/codegear/gtest_link.cc new file mode 100644 index 00000000..918eccd1 --- /dev/null +++ b/3rdparty/gmock/gtest/codegear/gtest_link.cc @@ -0,0 +1,40 @@ +// Copyright 2009, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: Josh Kelley (joshkel@gmail.com) +// +// Google C++ Testing Framework (Google Test) +// +// Links gtest.lib and gtest_main.lib into the current project in C++Builder. +// This means that these libraries can't be renamed, but it's the only way to +// ensure that Debug versus Release test builds are linked against the +// appropriate Debug or Release build of the libraries. + +#pragma link "gtest.lib" +#pragma link "gtest_main.lib" diff --git a/3rdparty/gmock/gtest/codegear/gtest_main.cbproj b/3rdparty/gmock/gtest/codegear/gtest_main.cbproj new file mode 100644 index 00000000..d76ce139 --- /dev/null +++ b/3rdparty/gmock/gtest/codegear/gtest_main.cbproj @@ -0,0 +1,82 @@ + + + + {bca37a72-5b07-46cf-b44e-89f8e06451a2} + Release + + + true + + + true + true + Base + + + true + true + Base + + + true + lib + JPHNE + NO_STRICT + true + true + CppStaticLibrary + true + rtl.bpi;vcl.bpi;bcbie.bpi;vclx.bpi;vclactnband.bpi;xmlrtl.bpi;bcbsmp.bpi;dbrtl.bpi;vcldb.bpi;bdertl.bpi;vcldbx.bpi;dsnap.bpi;dsnapcon.bpi;vclib.bpi;ibxpress.bpi;adortl.bpi;dbxcds.bpi;dbexpress.bpi;DbxCommonDriver.bpi;websnap.bpi;vclie.bpi;webdsnap.bpi;inet.bpi;inetdbbde.bpi;inetdbxpress.bpi;soaprtl.bpi;Rave75VCL.bpi;teeUI.bpi;tee.bpi;teedb.bpi;IndyCore.bpi;IndySystem.bpi;IndyProtocols.bpi;IntrawebDB_90_100.bpi;Intraweb_90_100.bpi;dclZipForged11.bpi;vclZipForged11.bpi;GR32_BDS2006.bpi;GR32_DSGN_BDS2006.bpi;Jcl.bpi;JclVcl.bpi;JvCoreD11R.bpi;JvSystemD11R.bpi;JvStdCtrlsD11R.bpi;JvAppFrmD11R.bpi;JvBandsD11R.bpi;JvDBD11R.bpi;JvDlgsD11R.bpi;JvBDED11R.bpi;JvCmpD11R.bpi;JvCryptD11R.bpi;JvCtrlsD11R.bpi;JvCustomD11R.bpi;JvDockingD11R.bpi;JvDotNetCtrlsD11R.bpi;JvEDID11R.bpi;JvGlobusD11R.bpi;JvHMID11R.bpi;JvInterpreterD11R.bpi;JvJansD11R.bpi;JvManagedThreadsD11R.bpi;JvMMD11R.bpi;JvNetD11R.bpi;JvPageCompsD11R.bpi;JvPluginD11R.bpi;JvPrintPreviewD11R.bpi;JvRuntimeDesignD11R.bpi;JvTimeFrameworkD11R.bpi;JvValidatorsD11R.bpi;JvWizardD11R.bpi;JvXPCtrlsD11R.bpi;VclSmp.bpi;CExceptionExpert11.bpi + false + $(BDS)\include;$(BDS)\include\dinkumware;$(BDS)\include\vcl;..\src;..\include;.. + rtl.lib;vcl.lib + 32 + $(BDS)\lib;$(BDS)\lib\obj;$(BDS)\lib\psdk + + + false + false + true + _DEBUG;$(Defines) + true + false + true + None + DEBUG + true + Debug + true + true + true + $(BDS)\lib\debug;$(ILINK_LibraryPath) + Full + true + + + NDEBUG;$(Defines) + Release + $(BDS)\lib\release;$(ILINK_LibraryPath) + None + + + CPlusPlusBuilder.Personality + CppStaticLibrary + +FalseFalse1000FalseFalseFalseFalseFalse103312521.0.0.01.0.0.0FalseFalseFalseTrueFalse + CodeGear C++Builder Office 2000 Servers Package + CodeGear C++Builder Office XP Servers Package + FalseTrueTrue3$(BDS)\include;$(BDS)\include\dinkumware;$(BDS)\include\vcl;..\src;..\include;..$(BDS)\include;$(BDS)\include\dinkumware;$(BDS)\include\vcl;..\src;..\include;..$(BDS)\include;$(BDS)\include\dinkumware;$(BDS)\include\vcl;..\src;..\src;..\include1$(BDS)\lib;$(BDS)\lib\obj;$(BDS)\lib\psdk1NO_STRICT13216 + + + + + 0 + + + Cfg_1 + + + Cfg_2 + + + diff --git a/3rdparty/gmock/gtest/codegear/gtest_unittest.cbproj b/3rdparty/gmock/gtest/codegear/gtest_unittest.cbproj new file mode 100644 index 00000000..dc5db8e4 --- /dev/null +++ b/3rdparty/gmock/gtest/codegear/gtest_unittest.cbproj @@ -0,0 +1,88 @@ + + + + {eea63393-5ac5-4b9c-8909-d75fef2daa41} + Release + + + true + + + true + true + Base + + + true + true + Base + + + exe + true + NO_STRICT + JPHNE + true + ..\test + true + CppConsoleApplication + true + true + rtl.bpi;vcl.bpi;bcbie.bpi;vclx.bpi;vclactnband.bpi;xmlrtl.bpi;bcbsmp.bpi;dbrtl.bpi;vcldb.bpi;bdertl.bpi;vcldbx.bpi;dsnap.bpi;dsnapcon.bpi;vclib.bpi;ibxpress.bpi;adortl.bpi;dbxcds.bpi;dbexpress.bpi;DbxCommonDriver.bpi;websnap.bpi;vclie.bpi;webdsnap.bpi;inet.bpi;inetdbbde.bpi;inetdbxpress.bpi;soaprtl.bpi;Rave75VCL.bpi;teeUI.bpi;tee.bpi;teedb.bpi;IndyCore.bpi;IndySystem.bpi;IndyProtocols.bpi;IntrawebDB_90_100.bpi;Intraweb_90_100.bpi;Jcl.bpi;JclVcl.bpi;JvCoreD11R.bpi;JvSystemD11R.bpi;JvStdCtrlsD11R.bpi;JvAppFrmD11R.bpi;JvBandsD11R.bpi;JvDBD11R.bpi;JvDlgsD11R.bpi;JvBDED11R.bpi;JvCmpD11R.bpi;JvCryptD11R.bpi;JvCtrlsD11R.bpi;JvCustomD11R.bpi;JvDockingD11R.bpi;JvDotNetCtrlsD11R.bpi;JvEDID11R.bpi;JvGlobusD11R.bpi;JvHMID11R.bpi;JvInterpreterD11R.bpi;JvJansD11R.bpi;JvManagedThreadsD11R.bpi;JvMMD11R.bpi;JvNetD11R.bpi;JvPageCompsD11R.bpi;JvPluginD11R.bpi;JvPrintPreviewD11R.bpi;JvRuntimeDesignD11R.bpi;JvTimeFrameworkD11R.bpi;JvValidatorsD11R.bpi;JvWizardD11R.bpi;JvXPCtrlsD11R.bpi;VclSmp.bpi + false + $(BDS)\include;$(BDS)\include\dinkumware;$(BDS)\include\vcl;..\include;..\test;.. + $(BDS)\lib;$(BDS)\lib\obj;$(BDS)\lib\psdk;..\test + true + + + false + false + true + _DEBUG;$(Defines) + true + false + true + None + DEBUG + true + Debug + true + true + true + $(BDS)\lib\debug;$(ILINK_LibraryPath) + Full + true + + + NDEBUG;$(Defines) + Release + $(BDS)\lib\release;$(ILINK_LibraryPath) + None + + + CPlusPlusBuilder.Personality + CppConsoleApplication + +FalseFalse1000FalseFalseFalseFalseFalse103312521.0.0.01.0.0.0FalseFalseFalseTrueFalse + + + CodeGear C++Builder Office 2000 Servers Package + CodeGear C++Builder Office XP Servers Package + FalseTrueTrue3$(BDS)\include;$(BDS)\include\dinkumware;$(BDS)\include\vcl;..\include;..\test;..$(BDS)\include;$(BDS)\include\dinkumware;$(BDS)\include\vcl;..\include;..\test$(BDS)\include;$(BDS)\include\dinkumware;$(BDS)\include\vcl;..\include1$(BDS)\lib;$(BDS)\lib\obj;$(BDS)\lib\psdk;..\test$(BDS)\lib;$(BDS)\lib\obj;$(BDS)\lib\psdk;..\test$(BDS)\lib;$(BDS)\lib\obj;$(BDS)\lib\psdk;$(OUTPUTDIR);..\test2NO_STRICTSTRICT + + + + + 0 + + + 1 + + + Cfg_1 + + + Cfg_2 + + + \ No newline at end of file diff --git a/3rdparty/gmock/gtest/include/gtest/gtest-death-test.h b/3rdparty/gmock/gtest/include/gtest/gtest-death-test.h new file mode 100644 index 00000000..121dc1fb --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/gtest-death-test.h @@ -0,0 +1,283 @@ +// Copyright 2005, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file defines the public API for death tests. It is +// #included by gtest.h so a user doesn't need to include this +// directly. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ + +#include + +namespace testing { + +// This flag controls the style of death tests. Valid values are "threadsafe", +// meaning that the death test child process will re-execute the test binary +// from the start, running only a single death test, or "fast", +// meaning that the child process will execute the test logic immediately +// after forking. +GTEST_DECLARE_string_(death_test_style); + +#if GTEST_HAS_DEATH_TEST + +// The following macros are useful for writing death tests. + +// Here's what happens when an ASSERT_DEATH* or EXPECT_DEATH* is +// executed: +// +// 1. It generates a warning if there is more than one active +// thread. This is because it's safe to fork() or clone() only +// when there is a single thread. +// +// 2. The parent process clone()s a sub-process and runs the death +// test in it; the sub-process exits with code 0 at the end of the +// death test, if it hasn't exited already. +// +// 3. The parent process waits for the sub-process to terminate. +// +// 4. The parent process checks the exit code and error message of +// the sub-process. +// +// Examples: +// +// ASSERT_DEATH(server.SendMessage(56, "Hello"), "Invalid port number"); +// for (int i = 0; i < 5; i++) { +// EXPECT_DEATH(server.ProcessRequest(i), +// "Invalid request .* in ProcessRequest()") +// << "Failed to die on request " << i); +// } +// +// ASSERT_EXIT(server.ExitNow(), ::testing::ExitedWithCode(0), "Exiting"); +// +// bool KilledBySIGHUP(int exit_code) { +// return WIFSIGNALED(exit_code) && WTERMSIG(exit_code) == SIGHUP; +// } +// +// ASSERT_EXIT(client.HangUpServer(), KilledBySIGHUP, "Hanging up!"); +// +// On the regular expressions used in death tests: +// +// On POSIX-compliant systems (*nix), we use the library, +// which uses the POSIX extended regex syntax. +// +// On other platforms (e.g. Windows), we only support a simple regex +// syntax implemented as part of Google Test. This limited +// implementation should be enough most of the time when writing +// death tests; though it lacks many features you can find in PCRE +// or POSIX extended regex syntax. For example, we don't support +// union ("x|y"), grouping ("(xy)"), brackets ("[xy]"), and +// repetition count ("x{5,7}"), among others. +// +// Below is the syntax that we do support. We chose it to be a +// subset of both PCRE and POSIX extended regex, so it's easy to +// learn wherever you come from. In the following: 'A' denotes a +// literal character, period (.), or a single \\ escape sequence; +// 'x' and 'y' denote regular expressions; 'm' and 'n' are for +// natural numbers. +// +// c matches any literal character c +// \\d matches any decimal digit +// \\D matches any character that's not a decimal digit +// \\f matches \f +// \\n matches \n +// \\r matches \r +// \\s matches any ASCII whitespace, including \n +// \\S matches any character that's not a whitespace +// \\t matches \t +// \\v matches \v +// \\w matches any letter, _, or decimal digit +// \\W matches any character that \\w doesn't match +// \\c matches any literal character c, which must be a punctuation +// . matches any single character except \n +// A? matches 0 or 1 occurrences of A +// A* matches 0 or many occurrences of A +// A+ matches 1 or many occurrences of A +// ^ matches the beginning of a string (not that of each line) +// $ matches the end of a string (not that of each line) +// xy matches x followed by y +// +// If you accidentally use PCRE or POSIX extended regex features +// not implemented by us, you will get a run-time failure. In that +// case, please try to rewrite your regular expression within the +// above syntax. +// +// This implementation is *not* meant to be as highly tuned or robust +// as a compiled regex library, but should perform well enough for a +// death test, which already incurs significant overhead by launching +// a child process. +// +// Known caveats: +// +// A "threadsafe" style death test obtains the path to the test +// program from argv[0] and re-executes it in the sub-process. For +// simplicity, the current implementation doesn't search the PATH +// when launching the sub-process. This means that the user must +// invoke the test program via a path that contains at least one +// path separator (e.g. path/to/foo_test and +// /absolute/path/to/bar_test are fine, but foo_test is not). This +// is rarely a problem as people usually don't put the test binary +// directory in PATH. +// +// TODO(wan@google.com): make thread-safe death tests search the PATH. + +// Asserts that a given statement causes the program to exit, with an +// integer exit status that satisfies predicate, and emitting error output +// that matches regex. +#define ASSERT_EXIT(statement, predicate, regex) \ + GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_FATAL_FAILURE_) + +// Like ASSERT_EXIT, but continues on to successive tests in the +// test case, if any: +#define EXPECT_EXIT(statement, predicate, regex) \ + GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_NONFATAL_FAILURE_) + +// Asserts that a given statement causes the program to exit, either by +// explicitly exiting with a nonzero exit code or being killed by a +// signal, and emitting error output that matches regex. +#define ASSERT_DEATH(statement, regex) \ + ASSERT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex) + +// Like ASSERT_DEATH, but continues on to successive tests in the +// test case, if any: +#define EXPECT_DEATH(statement, regex) \ + EXPECT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex) + +// Two predicate classes that can be used in {ASSERT,EXPECT}_EXIT*: + +// Tests that an exit code describes a normal exit with a given exit code. +class GTEST_API_ ExitedWithCode { + public: + explicit ExitedWithCode(int exit_code); + bool operator()(int exit_status) const; + private: + // No implementation - assignment is unsupported. + void operator=(const ExitedWithCode& other); + + const int exit_code_; +}; + +#if !GTEST_OS_WINDOWS +// Tests that an exit code describes an exit due to termination by a +// given signal. +class GTEST_API_ KilledBySignal { + public: + explicit KilledBySignal(int signum); + bool operator()(int exit_status) const; + private: + const int signum_; +}; +#endif // !GTEST_OS_WINDOWS + +// EXPECT_DEBUG_DEATH asserts that the given statements die in debug mode. +// The death testing framework causes this to have interesting semantics, +// since the sideeffects of the call are only visible in opt mode, and not +// in debug mode. +// +// In practice, this can be used to test functions that utilize the +// LOG(DFATAL) macro using the following style: +// +// int DieInDebugOr12(int* sideeffect) { +// if (sideeffect) { +// *sideeffect = 12; +// } +// LOG(DFATAL) << "death"; +// return 12; +// } +// +// TEST(TestCase, TestDieOr12WorksInDgbAndOpt) { +// int sideeffect = 0; +// // Only asserts in dbg. +// EXPECT_DEBUG_DEATH(DieInDebugOr12(&sideeffect), "death"); +// +// #ifdef NDEBUG +// // opt-mode has sideeffect visible. +// EXPECT_EQ(12, sideeffect); +// #else +// // dbg-mode no visible sideeffect. +// EXPECT_EQ(0, sideeffect); +// #endif +// } +// +// This will assert that DieInDebugReturn12InOpt() crashes in debug +// mode, usually due to a DCHECK or LOG(DFATAL), but returns the +// appropriate fallback value (12 in this case) in opt mode. If you +// need to test that a function has appropriate side-effects in opt +// mode, include assertions against the side-effects. A general +// pattern for this is: +// +// EXPECT_DEBUG_DEATH({ +// // Side-effects here will have an effect after this statement in +// // opt mode, but none in debug mode. +// EXPECT_EQ(12, DieInDebugOr12(&sideeffect)); +// }, "death"); +// +#ifdef NDEBUG + +#define EXPECT_DEBUG_DEATH(statement, regex) \ + do { statement; } while (::testing::internal::AlwaysFalse()) + +#define ASSERT_DEBUG_DEATH(statement, regex) \ + do { statement; } while (::testing::internal::AlwaysFalse()) + +#else + +#define EXPECT_DEBUG_DEATH(statement, regex) \ + EXPECT_DEATH(statement, regex) + +#define ASSERT_DEBUG_DEATH(statement, regex) \ + ASSERT_DEATH(statement, regex) + +#endif // NDEBUG for EXPECT_DEBUG_DEATH +#endif // GTEST_HAS_DEATH_TEST + +// EXPECT_DEATH_IF_SUPPORTED(statement, regex) and +// ASSERT_DEATH_IF_SUPPORTED(statement, regex) expand to real death tests if +// death tests are supported; otherwise they just issue a warning. This is +// useful when you are combining death test assertions with normal test +// assertions in one test. +#if GTEST_HAS_DEATH_TEST +#define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ + EXPECT_DEATH(statement, regex) +#define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \ + ASSERT_DEATH(statement, regex) +#else +#define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ + GTEST_UNSUPPORTED_DEATH_TEST_(statement, regex, ) +#define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \ + GTEST_UNSUPPORTED_DEATH_TEST_(statement, regex, return) +#endif + +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/gtest-message.h b/3rdparty/gmock/gtest/include/gtest/gtest-message.h new file mode 100644 index 00000000..f135b694 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/gtest-message.h @@ -0,0 +1,230 @@ +// Copyright 2005, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file defines the Message class. +// +// IMPORTANT NOTE: Due to limitation of the C++ language, we have to +// leave some internal implementation details in this header file. +// They are clearly marked by comments like this: +// +// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +// +// Such code is NOT meant to be used by a user directly, and is subject +// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user +// program! + +#ifndef GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ +#define GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ + +#include + +#include +#include + +namespace testing { + +// The Message class works like an ostream repeater. +// +// Typical usage: +// +// 1. You stream a bunch of values to a Message object. +// It will remember the text in a StrStream. +// 2. Then you stream the Message object to an ostream. +// This causes the text in the Message to be streamed +// to the ostream. +// +// For example; +// +// testing::Message foo; +// foo << 1 << " != " << 2; +// std::cout << foo; +// +// will print "1 != 2". +// +// Message is not intended to be inherited from. In particular, its +// destructor is not virtual. +// +// Note that StrStream behaves differently in gcc and in MSVC. You +// can stream a NULL char pointer to it in the former, but not in the +// latter (it causes an access violation if you do). The Message +// class hides this difference by treating a NULL char pointer as +// "(null)". +class GTEST_API_ Message { + private: + // The type of basic IO manipulators (endl, ends, and flush) for + // narrow streams. + typedef std::ostream& (*BasicNarrowIoManip)(std::ostream&); + + public: + // Constructs an empty Message. + // We allocate the StrStream separately because it otherwise each use of + // ASSERT/EXPECT in a procedure adds over 200 bytes to the procedure's + // stack frame leading to huge stack frames in some cases; gcc does not reuse + // the stack space. + Message() : ss_(new internal::StrStream) { + // By default, we want there to be enough precision when printing + // a double to a Message. + *ss_ << std::setprecision(std::numeric_limits::digits10 + 2); + } + + // Copy constructor. + Message(const Message& msg) : ss_(new internal::StrStream) { // NOLINT + *ss_ << msg.GetString(); + } + + // Constructs a Message from a C-string. + explicit Message(const char* str) : ss_(new internal::StrStream) { + *ss_ << str; + } + + ~Message() { delete ss_; } +#if GTEST_OS_SYMBIAN + // Streams a value (either a pointer or not) to this object. + template + inline Message& operator <<(const T& value) { + StreamHelper(typename internal::is_pointer::type(), value); + return *this; + } +#else + // Streams a non-pointer value to this object. + template + inline Message& operator <<(const T& val) { + ::GTestStreamToHelper(ss_, val); + return *this; + } + + // Streams a pointer value to this object. + // + // This function is an overload of the previous one. When you + // stream a pointer to a Message, this definition will be used as it + // is more specialized. (The C++ Standard, section + // [temp.func.order].) If you stream a non-pointer, then the + // previous definition will be used. + // + // The reason for this overload is that streaming a NULL pointer to + // ostream is undefined behavior. Depending on the compiler, you + // may get "0", "(nil)", "(null)", or an access violation. To + // ensure consistent result across compilers, we always treat NULL + // as "(null)". + template + inline Message& operator <<(T* const& pointer) { // NOLINT + if (pointer == NULL) { + *ss_ << "(null)"; + } else { + ::GTestStreamToHelper(ss_, pointer); + } + return *this; + } +#endif // GTEST_OS_SYMBIAN + + // Since the basic IO manipulators are overloaded for both narrow + // and wide streams, we have to provide this specialized definition + // of operator <<, even though its body is the same as the + // templatized version above. Without this definition, streaming + // endl or other basic IO manipulators to Message will confuse the + // compiler. + Message& operator <<(BasicNarrowIoManip val) { + *ss_ << val; + return *this; + } + + // Instead of 1/0, we want to see true/false for bool values. + Message& operator <<(bool b) { + return *this << (b ? "true" : "false"); + } + + // These two overloads allow streaming a wide C string to a Message + // using the UTF-8 encoding. + Message& operator <<(const wchar_t* wide_c_str) { + return *this << internal::String::ShowWideCString(wide_c_str); + } + Message& operator <<(wchar_t* wide_c_str) { + return *this << internal::String::ShowWideCString(wide_c_str); + } + +#if GTEST_HAS_STD_WSTRING + // Converts the given wide string to a narrow string using the UTF-8 + // encoding, and streams the result to this Message object. + Message& operator <<(const ::std::wstring& wstr); +#endif // GTEST_HAS_STD_WSTRING + +#if GTEST_HAS_GLOBAL_WSTRING + // Converts the given wide string to a narrow string using the UTF-8 + // encoding, and streams the result to this Message object. + Message& operator <<(const ::wstring& wstr); +#endif // GTEST_HAS_GLOBAL_WSTRING + + // Gets the text streamed to this object so far as a String. + // Each '\0' character in the buffer is replaced with "\\0". + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + internal::String GetString() const { + return internal::StrStreamToString(ss_); + } + + private: +#if GTEST_OS_SYMBIAN + // These are needed as the Nokia Symbian Compiler cannot decide between + // const T& and const T* in a function template. The Nokia compiler _can_ + // decide between class template specializations for T and T*, so a + // tr1::type_traits-like is_pointer works, and we can overload on that. + template + inline void StreamHelper(internal::true_type /*dummy*/, T* pointer) { + if (pointer == NULL) { + *ss_ << "(null)"; + } else { + ::GTestStreamToHelper(ss_, pointer); + } + } + template + inline void StreamHelper(internal::false_type /*dummy*/, const T& value) { + ::GTestStreamToHelper(ss_, value); + } +#endif // GTEST_OS_SYMBIAN + + // We'll hold the text streamed to this object here. + internal::StrStream* const ss_; + + // We declare (but don't implement) this to prevent the compiler + // from implementing the assignment operator. + void operator=(const Message&); +}; + +// Streams a Message to an ostream. +inline std::ostream& operator <<(std::ostream& os, const Message& sb) { + return os << sb.GetString(); +} + +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/gtest-param-test.h b/3rdparty/gmock/gtest/include/gtest/gtest-param-test.h new file mode 100644 index 00000000..81006964 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/gtest-param-test.h @@ -0,0 +1,1392 @@ +// This file was GENERATED by a script. DO NOT EDIT BY HAND!!! + +// Copyright 2008, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Authors: vladl@google.com (Vlad Losev) +// +// Macros and functions for implementing parameterized tests +// in Google C++ Testing Framework (Google Test) +// +// This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +#ifndef GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ + + +// Value-parameterized tests allow you to test your code with different +// parameters without writing multiple copies of the same test. +// +// Here is how you use value-parameterized tests: + +#if 0 + +// To write value-parameterized tests, first you should define a fixture +// class. It must be derived from testing::TestWithParam, where T is +// the type of your parameter values. TestWithParam is itself derived +// from testing::Test. T can be any copyable type. If it's a raw pointer, +// you are responsible for managing the lifespan of the pointed values. + +class FooTest : public ::testing::TestWithParam { + // You can implement all the usual class fixture members here. +}; + +// Then, use the TEST_P macro to define as many parameterized tests +// for this fixture as you want. The _P suffix is for "parameterized" +// or "pattern", whichever you prefer to think. + +TEST_P(FooTest, DoesBlah) { + // Inside a test, access the test parameter with the GetParam() method + // of the TestWithParam class: + EXPECT_TRUE(foo.Blah(GetParam())); + ... +} + +TEST_P(FooTest, HasBlahBlah) { + ... +} + +// Finally, you can use INSTANTIATE_TEST_CASE_P to instantiate the test +// case with any set of parameters you want. Google Test defines a number +// of functions for generating test parameters. They return what we call +// (surprise!) parameter generators. Here is a summary of them, which +// are all in the testing namespace: +// +// +// Range(begin, end [, step]) - Yields values {begin, begin+step, +// begin+step+step, ...}. The values do not +// include end. step defaults to 1. +// Values(v1, v2, ..., vN) - Yields values {v1, v2, ..., vN}. +// ValuesIn(container) - Yields values from a C-style array, an STL +// ValuesIn(begin,end) container, or an iterator range [begin, end). +// Bool() - Yields sequence {false, true}. +// Combine(g1, g2, ..., gN) - Yields all combinations (the Cartesian product +// for the math savvy) of the values generated +// by the N generators. +// +// For more details, see comments at the definitions of these functions below +// in this file. +// +// The following statement will instantiate tests from the FooTest test case +// each with parameter values "meeny", "miny", and "moe". + +INSTANTIATE_TEST_CASE_P(InstantiationName, + FooTest, + Values("meeny", "miny", "moe")); + +// To distinguish different instances of the pattern, (yes, you +// can instantiate it more then once) the first argument to the +// INSTANTIATE_TEST_CASE_P macro is a prefix that will be added to the +// actual test case name. Remember to pick unique prefixes for different +// instantiations. The tests from the instantiation above will have +// these names: +// +// * InstantiationName/FooTest.DoesBlah/0 for "meeny" +// * InstantiationName/FooTest.DoesBlah/1 for "miny" +// * InstantiationName/FooTest.DoesBlah/2 for "moe" +// * InstantiationName/FooTest.HasBlahBlah/0 for "meeny" +// * InstantiationName/FooTest.HasBlahBlah/1 for "miny" +// * InstantiationName/FooTest.HasBlahBlah/2 for "moe" +// +// You can use these names in --gtest_filter. +// +// This statement will instantiate all tests from FooTest again, each +// with parameter values "cat" and "dog": + +const char* pets[] = {"cat", "dog"}; +INSTANTIATE_TEST_CASE_P(AnotherInstantiationName, FooTest, ValuesIn(pets)); + +// The tests from the instantiation above will have these names: +// +// * AnotherInstantiationName/FooTest.DoesBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.DoesBlah/1 for "dog" +// * AnotherInstantiationName/FooTest.HasBlahBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.HasBlahBlah/1 for "dog" +// +// Please note that INSTANTIATE_TEST_CASE_P will instantiate all tests +// in the given test case, whether their definitions come before or +// AFTER the INSTANTIATE_TEST_CASE_P statement. +// +// Please also note that generator expressions (including parameters to the +// generators) are evaluated in InitGoogleTest(), after main() has started. +// This allows the user on one hand, to adjust generator parameters in order +// to dynamically determine a set of tests to run and on the other hand, +// give the user a chance to inspect the generated tests with Google Test +// reflection API before RUN_ALL_TESTS() is executed. +// +// You can see samples/sample7_unittest.cc and samples/sample8_unittest.cc +// for more examples. +// +// In the future, we plan to publish the API for defining new parameter +// generators. But for now this interface remains part of the internal +// implementation and is subject to change. + +#endif // 0 + +#include + +#if !GTEST_OS_SYMBIAN +#include +#endif + +// scripts/fuse_gtest.py depends on gtest's own header being #included +// *unconditionally*. Therefore these #includes cannot be moved +// inside #if GTEST_HAS_PARAM_TEST. +#include +#include +#include + +#if GTEST_HAS_PARAM_TEST + +namespace testing { + +// Functions producing parameter generators. +// +// Google Test uses these generators to produce parameters for value- +// parameterized tests. When a parameterized test case is instantiated +// with a particular generator, Google Test creates and runs tests +// for each element in the sequence produced by the generator. +// +// In the following sample, tests from test case FooTest are instantiated +// each three times with parameter values 3, 5, and 8: +// +// class FooTest : public TestWithParam { ... }; +// +// TEST_P(FooTest, TestThis) { +// } +// TEST_P(FooTest, TestThat) { +// } +// INSTANTIATE_TEST_CASE_P(TestSequence, FooTest, Values(3, 5, 8)); +// + +// Range() returns generators providing sequences of values in a range. +// +// Synopsis: +// Range(start, end) +// - returns a generator producing a sequence of values {start, start+1, +// start+2, ..., }. +// Range(start, end, step) +// - returns a generator producing a sequence of values {start, start+step, +// start+step+step, ..., }. +// Notes: +// * The generated sequences never include end. For example, Range(1, 5) +// returns a generator producing a sequence {1, 2, 3, 4}. Range(1, 9, 2) +// returns a generator producing {1, 3, 5, 7}. +// * start and end must have the same type. That type may be any integral or +// floating-point type or a user defined type satisfying these conditions: +// * It must be assignable (have operator=() defined). +// * It must have operator+() (operator+(int-compatible type) for +// two-operand version). +// * It must have operator<() defined. +// Elements in the resulting sequences will also have that type. +// * Condition start < end must be satisfied in order for resulting sequences +// to contain any elements. +// +template +internal::ParamGenerator Range(T start, T end, IncrementT step) { + return internal::ParamGenerator( + new internal::RangeGenerator(start, end, step)); +} + +template +internal::ParamGenerator Range(T start, T end) { + return Range(start, end, 1); +} + +// ValuesIn() function allows generation of tests with parameters coming from +// a container. +// +// Synopsis: +// ValuesIn(const T (&array)[N]) +// - returns a generator producing sequences with elements from +// a C-style array. +// ValuesIn(const Container& container) +// - returns a generator producing sequences with elements from +// an STL-style container. +// ValuesIn(Iterator begin, Iterator end) +// - returns a generator producing sequences with elements from +// a range [begin, end) defined by a pair of STL-style iterators. These +// iterators can also be plain C pointers. +// +// Please note that ValuesIn copies the values from the containers +// passed in and keeps them to generate tests in RUN_ALL_TESTS(). +// +// Examples: +// +// This instantiates tests from test case StringTest +// each with C-string values of "foo", "bar", and "baz": +// +// const char* strings[] = {"foo", "bar", "baz"}; +// INSTANTIATE_TEST_CASE_P(StringSequence, SrtingTest, ValuesIn(strings)); +// +// This instantiates tests from test case StlStringTest +// each with STL strings with values "a" and "b": +// +// ::std::vector< ::std::string> GetParameterStrings() { +// ::std::vector< ::std::string> v; +// v.push_back("a"); +// v.push_back("b"); +// return v; +// } +// +// INSTANTIATE_TEST_CASE_P(CharSequence, +// StlStringTest, +// ValuesIn(GetParameterStrings())); +// +// +// This will also instantiate tests from CharTest +// each with parameter values 'a' and 'b': +// +// ::std::list GetParameterChars() { +// ::std::list list; +// list.push_back('a'); +// list.push_back('b'); +// return list; +// } +// ::std::list l = GetParameterChars(); +// INSTANTIATE_TEST_CASE_P(CharSequence2, +// CharTest, +// ValuesIn(l.begin(), l.end())); +// +template +internal::ParamGenerator< + typename ::std::iterator_traits::value_type> ValuesIn( + ForwardIterator begin, + ForwardIterator end) { + typedef typename ::std::iterator_traits::value_type + ParamType; + return internal::ParamGenerator( + new internal::ValuesInIteratorRangeGenerator(begin, end)); +} + +template +internal::ParamGenerator ValuesIn(const T (&array)[N]) { + return ValuesIn(array, array + N); +} + +template +internal::ParamGenerator ValuesIn( + const Container& container) { + return ValuesIn(container.begin(), container.end()); +} + +// Values() allows generating tests from explicitly specified list of +// parameters. +// +// Synopsis: +// Values(T v1, T v2, ..., T vN) +// - returns a generator producing sequences with elements v1, v2, ..., vN. +// +// For example, this instantiates tests from test case BarTest each +// with values "one", "two", and "three": +// +// INSTANTIATE_TEST_CASE_P(NumSequence, BarTest, Values("one", "two", "three")); +// +// This instantiates tests from test case BazTest each with values 1, 2, 3.5. +// The exact type of values will depend on the type of parameter in BazTest. +// +// INSTANTIATE_TEST_CASE_P(FloatingNumbers, BazTest, Values(1, 2, 3.5)); +// +// Currently, Values() supports from 1 to 50 parameters. +// +template +internal::ValueArray1 Values(T1 v1) { + return internal::ValueArray1(v1); +} + +template +internal::ValueArray2 Values(T1 v1, T2 v2) { + return internal::ValueArray2(v1, v2); +} + +template +internal::ValueArray3 Values(T1 v1, T2 v2, T3 v3) { + return internal::ValueArray3(v1, v2, v3); +} + +template +internal::ValueArray4 Values(T1 v1, T2 v2, T3 v3, T4 v4) { + return internal::ValueArray4(v1, v2, v3, v4); +} + +template +internal::ValueArray5 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5) { + return internal::ValueArray5(v1, v2, v3, v4, v5); +} + +template +internal::ValueArray6 Values(T1 v1, T2 v2, T3 v3, + T4 v4, T5 v5, T6 v6) { + return internal::ValueArray6(v1, v2, v3, v4, v5, v6); +} + +template +internal::ValueArray7 Values(T1 v1, T2 v2, T3 v3, + T4 v4, T5 v5, T6 v6, T7 v7) { + return internal::ValueArray7(v1, v2, v3, v4, v5, + v6, v7); +} + +template +internal::ValueArray8 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8) { + return internal::ValueArray8(v1, v2, v3, v4, + v5, v6, v7, v8); +} + +template +internal::ValueArray9 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9) { + return internal::ValueArray9(v1, v2, v3, + v4, v5, v6, v7, v8, v9); +} + +template +internal::ValueArray10 Values(T1 v1, + T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10) { + return internal::ValueArray10(v1, + v2, v3, v4, v5, v6, v7, v8, v9, v10); +} + +template +internal::ValueArray11 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11) { + return internal::ValueArray11(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11); +} + +template +internal::ValueArray12 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12) { + return internal::ValueArray12(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12); +} + +template +internal::ValueArray13 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13) { + return internal::ValueArray13(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13); +} + +template +internal::ValueArray14 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14) { + return internal::ValueArray14(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14); +} + +template +internal::ValueArray15 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, + T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15) { + return internal::ValueArray15(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, + v13, v14, v15); +} + +template +internal::ValueArray16 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16) { + return internal::ValueArray16(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15, v16); +} + +template +internal::ValueArray17 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17) { + return internal::ValueArray17(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, + v11, v12, v13, v14, v15, v16, v17); +} + +template +internal::ValueArray18 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, + T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18) { + return internal::ValueArray18(v1, v2, v3, v4, v5, v6, v7, v8, v9, + v10, v11, v12, v13, v14, v15, v16, v17, v18); +} + +template +internal::ValueArray19 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, + T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, + T15 v15, T16 v16, T17 v17, T18 v18, T19 v19) { + return internal::ValueArray19(v1, v2, v3, v4, v5, v6, v7, v8, + v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19); +} + +template +internal::ValueArray20 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20) { + return internal::ValueArray20(v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20); +} + +template +internal::ValueArray21 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21) { + return internal::ValueArray21(v1, v2, v3, v4, v5, v6, + v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21); +} + +template +internal::ValueArray22 Values(T1 v1, T2 v2, T3 v3, + T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22) { + return internal::ValueArray22(v1, v2, v3, v4, + v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22); +} + +template +internal::ValueArray23 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23) { + return internal::ValueArray23(v1, v2, v3, + v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23); +} + +template +internal::ValueArray24 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23, T24 v24) { + return internal::ValueArray24(v1, v2, + v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, + v19, v20, v21, v22, v23, v24); +} + +template +internal::ValueArray25 Values(T1 v1, + T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, + T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, + T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25) { + return internal::ValueArray25(v1, + v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, + v18, v19, v20, v21, v22, v23, v24, v25); +} + +template +internal::ValueArray26 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26) { + return internal::ValueArray26(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, + v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26); +} + +template +internal::ValueArray27 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27) { + return internal::ValueArray27(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, + v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27); +} + +template +internal::ValueArray28 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28) { + return internal::ValueArray28(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, + v28); +} + +template +internal::ValueArray29 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29) { + return internal::ValueArray29(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, + v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, + v27, v28, v29); +} + +template +internal::ValueArray30 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, + T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, + T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, + T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30) { + return internal::ValueArray30(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, + v26, v27, v28, v29, v30); +} + +template +internal::ValueArray31 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31) { + return internal::ValueArray31(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, + v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, + v25, v26, v27, v28, v29, v30, v31); +} + +template +internal::ValueArray32 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32) { + return internal::ValueArray32(v1, v2, v3, v4, v5, v6, v7, v8, v9, + v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, v32); +} + +template +internal::ValueArray33 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, + T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32, T33 v33) { + return internal::ValueArray33(v1, v2, v3, v4, v5, v6, v7, v8, + v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, v32, v33); +} + +template +internal::ValueArray34 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, + T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, + T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, + T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, + T31 v31, T32 v32, T33 v33, T34 v34) { + return internal::ValueArray34(v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, + v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34); +} + +template +internal::ValueArray35 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, + T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, + T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35) { + return internal::ValueArray35(v1, v2, v3, v4, v5, v6, + v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, + v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35); +} + +template +internal::ValueArray36 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, + T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, + T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36) { + return internal::ValueArray36(v1, v2, v3, v4, + v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, + v34, v35, v36); +} + +template +internal::ValueArray37 Values(T1 v1, T2 v2, T3 v3, + T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, + T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, + T37 v37) { + return internal::ValueArray37(v1, v2, v3, + v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, + v34, v35, v36, v37); +} + +template +internal::ValueArray38 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, + T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, + T37 v37, T38 v38) { + return internal::ValueArray38(v1, v2, + v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, + v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, + v33, v34, v35, v36, v37, v38); +} + +template +internal::ValueArray39 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, + T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, + T37 v37, T38 v38, T39 v39) { + return internal::ValueArray39(v1, + v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, + v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, + v32, v33, v34, v35, v36, v37, v38, v39); +} + +template +internal::ValueArray40 Values(T1 v1, + T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, + T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, + T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, + T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, + T36 v36, T37 v37, T38 v38, T39 v39, T40 v40) { + return internal::ValueArray40(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, + v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, + v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40); +} + +template +internal::ValueArray41 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41) { + return internal::ValueArray41(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, + v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, + v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41); +} + +template +internal::ValueArray42 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42) { + return internal::ValueArray42(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, + v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, + v42); +} + +template +internal::ValueArray43 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43) { + return internal::ValueArray43(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, + v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, + v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, + v41, v42, v43); +} + +template +internal::ValueArray44 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44) { + return internal::ValueArray44(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, + v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, + v40, v41, v42, v43, v44); +} + +template +internal::ValueArray45 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, + T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, + T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, + T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, + T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, + T41 v41, T42 v42, T43 v43, T44 v44, T45 v45) { + return internal::ValueArray45(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, + v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, + v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, + v39, v40, v41, v42, v43, v44, v45); +} + +template +internal::ValueArray46 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, + T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46) { + return internal::ValueArray46(v1, v2, v3, v4, v5, v6, v7, v8, v9, + v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, + v38, v39, v40, v41, v42, v43, v44, v45, v46); +} + +template +internal::ValueArray47 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, + T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47) { + return internal::ValueArray47(v1, v2, v3, v4, v5, v6, v7, v8, + v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, + v38, v39, v40, v41, v42, v43, v44, v45, v46, v47); +} + +template +internal::ValueArray48 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, + T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, + T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, + T48 v48) { + return internal::ValueArray48(v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, + v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, + v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48); +} + +template +internal::ValueArray49 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, + T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, + T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, + T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, + T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, + T39 v39, T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, + T47 v47, T48 v48, T49 v49) { + return internal::ValueArray49(v1, v2, v3, v4, v5, v6, + v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, + v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, + v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49); +} + +template +internal::ValueArray50 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, + T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, + T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, + T38 v38, T39 v39, T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, + T46 v46, T47 v47, T48 v48, T49 v49, T50 v50) { + return internal::ValueArray50(v1, v2, v3, v4, + v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, + v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, + v48, v49, v50); +} + +// Bool() allows generating tests with parameters in a set of (false, true). +// +// Synopsis: +// Bool() +// - returns a generator producing sequences with elements {false, true}. +// +// It is useful when testing code that depends on Boolean flags. Combinations +// of multiple flags can be tested when several Bool()'s are combined using +// Combine() function. +// +// In the following example all tests in the test case FlagDependentTest +// will be instantiated twice with parameters false and true. +// +// class FlagDependentTest : public testing::TestWithParam { +// virtual void SetUp() { +// external_flag = GetParam(); +// } +// } +// INSTANTIATE_TEST_CASE_P(BoolSequence, FlagDependentTest, Bool()); +// +inline internal::ParamGenerator Bool() { + return Values(false, true); +} + +#if GTEST_HAS_COMBINE +// Combine() allows the user to combine two or more sequences to produce +// values of a Cartesian product of those sequences' elements. +// +// Synopsis: +// Combine(gen1, gen2, ..., genN) +// - returns a generator producing sequences with elements coming from +// the Cartesian product of elements from the sequences generated by +// gen1, gen2, ..., genN. The sequence elements will have a type of +// tuple where T1, T2, ..., TN are the types +// of elements from sequences produces by gen1, gen2, ..., genN. +// +// Combine can have up to 10 arguments. This number is currently limited +// by the maximum number of elements in the tuple implementation used by Google +// Test. +// +// Example: +// +// This will instantiate tests in test case AnimalTest each one with +// the parameter values tuple("cat", BLACK), tuple("cat", WHITE), +// tuple("dog", BLACK), and tuple("dog", WHITE): +// +// enum Color { BLACK, GRAY, WHITE }; +// class AnimalTest +// : public testing::TestWithParam > {...}; +// +// TEST_P(AnimalTest, AnimalLooksNice) {...} +// +// INSTANTIATE_TEST_CASE_P(AnimalVariations, AnimalTest, +// Combine(Values("cat", "dog"), +// Values(BLACK, WHITE))); +// +// This will instantiate tests in FlagDependentTest with all variations of two +// Boolean flags: +// +// class FlagDependentTest +// : public testing::TestWithParam > { +// virtual void SetUp() { +// // Assigns external_flag_1 and external_flag_2 values from the tuple. +// tie(external_flag_1, external_flag_2) = GetParam(); +// } +// }; +// +// TEST_P(FlagDependentTest, TestFeature1) { +// // Test your code using external_flag_1 and external_flag_2 here. +// } +// INSTANTIATE_TEST_CASE_P(TwoBoolSequence, FlagDependentTest, +// Combine(Bool(), Bool())); +// +template +internal::CartesianProductHolder2 Combine( + const Generator1& g1, const Generator2& g2) { + return internal::CartesianProductHolder2( + g1, g2); +} + +template +internal::CartesianProductHolder3 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3) { + return internal::CartesianProductHolder3( + g1, g2, g3); +} + +template +internal::CartesianProductHolder4 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4) { + return internal::CartesianProductHolder4( + g1, g2, g3, g4); +} + +template +internal::CartesianProductHolder5 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5) { + return internal::CartesianProductHolder5( + g1, g2, g3, g4, g5); +} + +template +internal::CartesianProductHolder6 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6) { + return internal::CartesianProductHolder6( + g1, g2, g3, g4, g5, g6); +} + +template +internal::CartesianProductHolder7 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6, + const Generator7& g7) { + return internal::CartesianProductHolder7( + g1, g2, g3, g4, g5, g6, g7); +} + +template +internal::CartesianProductHolder8 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6, + const Generator7& g7, const Generator8& g8) { + return internal::CartesianProductHolder8( + g1, g2, g3, g4, g5, g6, g7, g8); +} + +template +internal::CartesianProductHolder9 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6, + const Generator7& g7, const Generator8& g8, const Generator9& g9) { + return internal::CartesianProductHolder9( + g1, g2, g3, g4, g5, g6, g7, g8, g9); +} + +template +internal::CartesianProductHolder10 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6, + const Generator7& g7, const Generator8& g8, const Generator9& g9, + const Generator10& g10) { + return internal::CartesianProductHolder10( + g1, g2, g3, g4, g5, g6, g7, g8, g9, g10); +} +#endif // GTEST_HAS_COMBINE + + + +#define TEST_P(test_case_name, test_name) \ + class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \ + : public test_case_name { \ + public: \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {} \ + virtual void TestBody(); \ + private: \ + static int AddToRegistry() { \ + ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ + GetTestCasePatternHolder(\ + #test_case_name, __FILE__, __LINE__)->AddTestPattern(\ + #test_case_name, \ + #test_name, \ + new ::testing::internal::TestMetaFactory< \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>()); \ + return 0; \ + } \ + static int gtest_registering_dummy_; \ + GTEST_DISALLOW_COPY_AND_ASSIGN_(\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)); \ + }; \ + int GTEST_TEST_CLASS_NAME_(test_case_name, \ + test_name)::gtest_registering_dummy_ = \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::AddToRegistry(); \ + void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() + +#define INSTANTIATE_TEST_CASE_P(prefix, test_case_name, generator) \ + ::testing::internal::ParamGenerator \ + gtest_##prefix##test_case_name##_EvalGenerator_() { return generator; } \ + int gtest_##prefix##test_case_name##_dummy_ = \ + ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ + GetTestCasePatternHolder(\ + #test_case_name, __FILE__, __LINE__)->AddTestCaseInstantiation(\ + #prefix, \ + >est_##prefix##test_case_name##_EvalGenerator_, \ + __FILE__, __LINE__) + +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/gtest-param-test.h.pump b/3rdparty/gmock/gtest/include/gtest/gtest-param-test.h.pump new file mode 100644 index 00000000..a2311882 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/gtest-param-test.h.pump @@ -0,0 +1,457 @@ +$$ -*- mode: c++; -*- +$var n = 50 $$ Maximum length of Values arguments we want to support. +$var maxtuple = 10 $$ Maximum number of Combine arguments we want to support. +// Copyright 2008, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Authors: vladl@google.com (Vlad Losev) +// +// Macros and functions for implementing parameterized tests +// in Google C++ Testing Framework (Google Test) +// +// This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +#ifndef GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ + + +// Value-parameterized tests allow you to test your code with different +// parameters without writing multiple copies of the same test. +// +// Here is how you use value-parameterized tests: + +#if 0 + +// To write value-parameterized tests, first you should define a fixture +// class. It must be derived from testing::TestWithParam, where T is +// the type of your parameter values. TestWithParam is itself derived +// from testing::Test. T can be any copyable type. If it's a raw pointer, +// you are responsible for managing the lifespan of the pointed values. + +class FooTest : public ::testing::TestWithParam { + // You can implement all the usual class fixture members here. +}; + +// Then, use the TEST_P macro to define as many parameterized tests +// for this fixture as you want. The _P suffix is for "parameterized" +// or "pattern", whichever you prefer to think. + +TEST_P(FooTest, DoesBlah) { + // Inside a test, access the test parameter with the GetParam() method + // of the TestWithParam class: + EXPECT_TRUE(foo.Blah(GetParam())); + ... +} + +TEST_P(FooTest, HasBlahBlah) { + ... +} + +// Finally, you can use INSTANTIATE_TEST_CASE_P to instantiate the test +// case with any set of parameters you want. Google Test defines a number +// of functions for generating test parameters. They return what we call +// (surprise!) parameter generators. Here is a summary of them, which +// are all in the testing namespace: +// +// +// Range(begin, end [, step]) - Yields values {begin, begin+step, +// begin+step+step, ...}. The values do not +// include end. step defaults to 1. +// Values(v1, v2, ..., vN) - Yields values {v1, v2, ..., vN}. +// ValuesIn(container) - Yields values from a C-style array, an STL +// ValuesIn(begin,end) container, or an iterator range [begin, end). +// Bool() - Yields sequence {false, true}. +// Combine(g1, g2, ..., gN) - Yields all combinations (the Cartesian product +// for the math savvy) of the values generated +// by the N generators. +// +// For more details, see comments at the definitions of these functions below +// in this file. +// +// The following statement will instantiate tests from the FooTest test case +// each with parameter values "meeny", "miny", and "moe". + +INSTANTIATE_TEST_CASE_P(InstantiationName, + FooTest, + Values("meeny", "miny", "moe")); + +// To distinguish different instances of the pattern, (yes, you +// can instantiate it more then once) the first argument to the +// INSTANTIATE_TEST_CASE_P macro is a prefix that will be added to the +// actual test case name. Remember to pick unique prefixes for different +// instantiations. The tests from the instantiation above will have +// these names: +// +// * InstantiationName/FooTest.DoesBlah/0 for "meeny" +// * InstantiationName/FooTest.DoesBlah/1 for "miny" +// * InstantiationName/FooTest.DoesBlah/2 for "moe" +// * InstantiationName/FooTest.HasBlahBlah/0 for "meeny" +// * InstantiationName/FooTest.HasBlahBlah/1 for "miny" +// * InstantiationName/FooTest.HasBlahBlah/2 for "moe" +// +// You can use these names in --gtest_filter. +// +// This statement will instantiate all tests from FooTest again, each +// with parameter values "cat" and "dog": + +const char* pets[] = {"cat", "dog"}; +INSTANTIATE_TEST_CASE_P(AnotherInstantiationName, FooTest, ValuesIn(pets)); + +// The tests from the instantiation above will have these names: +// +// * AnotherInstantiationName/FooTest.DoesBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.DoesBlah/1 for "dog" +// * AnotherInstantiationName/FooTest.HasBlahBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.HasBlahBlah/1 for "dog" +// +// Please note that INSTANTIATE_TEST_CASE_P will instantiate all tests +// in the given test case, whether their definitions come before or +// AFTER the INSTANTIATE_TEST_CASE_P statement. +// +// Please also note that generator expressions are evaluated in +// RUN_ALL_TESTS(), after main() has started. This allows evaluation of +// parameter list based on command line parameters. +// +// You can see samples/sample7_unittest.cc and samples/sample8_unittest.cc +// for more examples. +// +// In the future, we plan to publish the API for defining new parameter +// generators. But for now this interface remains part of the internal +// implementation and is subject to change. + +#endif // 0 + +#include + +#if !GTEST_OS_SYMBIAN +#include +#endif + +// scripts/fuse_gtest.py depends on gtest's own header being #included +// *unconditionally*. Therefore these #includes cannot be moved +// inside #if GTEST_HAS_PARAM_TEST. +#include +#include +#include + +#if GTEST_HAS_PARAM_TEST + +namespace testing { + +// Functions producing parameter generators. +// +// Google Test uses these generators to produce parameters for value- +// parameterized tests. When a parameterized test case is instantiated +// with a particular generator, Google Test creates and runs tests +// for each element in the sequence produced by the generator. +// +// In the following sample, tests from test case FooTest are instantiated +// each three times with parameter values 3, 5, and 8: +// +// class FooTest : public TestWithParam { ... }; +// +// TEST_P(FooTest, TestThis) { +// } +// TEST_P(FooTest, TestThat) { +// } +// INSTANTIATE_TEST_CASE_P(TestSequence, FooTest, Values(3, 5, 8)); +// + +// Range() returns generators providing sequences of values in a range. +// +// Synopsis: +// Range(start, end) +// - returns a generator producing a sequence of values {start, start+1, +// start+2, ..., }. +// Range(start, end, step) +// - returns a generator producing a sequence of values {start, start+step, +// start+step+step, ..., }. +// Notes: +// * The generated sequences never include end. For example, Range(1, 5) +// returns a generator producing a sequence {1, 2, 3, 4}. Range(1, 9, 2) +// returns a generator producing {1, 3, 5, 7}. +// * start and end must have the same type. That type may be any integral or +// floating-point type or a user defined type satisfying these conditions: +// * It must be assignable (have operator=() defined). +// * It must have operator+() (operator+(int-compatible type) for +// two-operand version). +// * It must have operator<() defined. +// Elements in the resulting sequences will also have that type. +// * Condition start < end must be satisfied in order for resulting sequences +// to contain any elements. +// +template +internal::ParamGenerator Range(T start, T end, IncrementT step) { + return internal::ParamGenerator( + new internal::RangeGenerator(start, end, step)); +} + +template +internal::ParamGenerator Range(T start, T end) { + return Range(start, end, 1); +} + +// ValuesIn() function allows generation of tests with parameters coming from +// a container. +// +// Synopsis: +// ValuesIn(const T (&array)[N]) +// - returns a generator producing sequences with elements from +// a C-style array. +// ValuesIn(const Container& container) +// - returns a generator producing sequences with elements from +// an STL-style container. +// ValuesIn(Iterator begin, Iterator end) +// - returns a generator producing sequences with elements from +// a range [begin, end) defined by a pair of STL-style iterators. These +// iterators can also be plain C pointers. +// +// Please note that ValuesIn copies the values from the containers +// passed in and keeps them to generate tests in RUN_ALL_TESTS(). +// +// Examples: +// +// This instantiates tests from test case StringTest +// each with C-string values of "foo", "bar", and "baz": +// +// const char* strings[] = {"foo", "bar", "baz"}; +// INSTANTIATE_TEST_CASE_P(StringSequence, SrtingTest, ValuesIn(strings)); +// +// This instantiates tests from test case StlStringTest +// each with STL strings with values "a" and "b": +// +// ::std::vector< ::std::string> GetParameterStrings() { +// ::std::vector< ::std::string> v; +// v.push_back("a"); +// v.push_back("b"); +// return v; +// } +// +// INSTANTIATE_TEST_CASE_P(CharSequence, +// StlStringTest, +// ValuesIn(GetParameterStrings())); +// +// +// This will also instantiate tests from CharTest +// each with parameter values 'a' and 'b': +// +// ::std::list GetParameterChars() { +// ::std::list list; +// list.push_back('a'); +// list.push_back('b'); +// return list; +// } +// ::std::list l = GetParameterChars(); +// INSTANTIATE_TEST_CASE_P(CharSequence2, +// CharTest, +// ValuesIn(l.begin(), l.end())); +// +template +internal::ParamGenerator< + typename ::std::iterator_traits::value_type> ValuesIn( + ForwardIterator begin, + ForwardIterator end) { + typedef typename ::std::iterator_traits::value_type + ParamType; + return internal::ParamGenerator( + new internal::ValuesInIteratorRangeGenerator(begin, end)); +} + +template +internal::ParamGenerator ValuesIn(const T (&array)[N]) { + return ValuesIn(array, array + N); +} + +template +internal::ParamGenerator ValuesIn( + const Container& container) { + return ValuesIn(container.begin(), container.end()); +} + +// Values() allows generating tests from explicitly specified list of +// parameters. +// +// Synopsis: +// Values(T v1, T v2, ..., T vN) +// - returns a generator producing sequences with elements v1, v2, ..., vN. +// +// For example, this instantiates tests from test case BarTest each +// with values "one", "two", and "three": +// +// INSTANTIATE_TEST_CASE_P(NumSequence, BarTest, Values("one", "two", "three")); +// +// This instantiates tests from test case BazTest each with values 1, 2, 3.5. +// The exact type of values will depend on the type of parameter in BazTest. +// +// INSTANTIATE_TEST_CASE_P(FloatingNumbers, BazTest, Values(1, 2, 3.5)); +// +// Currently, Values() supports from 1 to $n parameters. +// +$range i 1..n +$for i [[ +$range j 1..i + +template <$for j, [[typename T$j]]> +internal::ValueArray$i<$for j, [[T$j]]> Values($for j, [[T$j v$j]]) { + return internal::ValueArray$i<$for j, [[T$j]]>($for j, [[v$j]]); +} + +]] + +// Bool() allows generating tests with parameters in a set of (false, true). +// +// Synopsis: +// Bool() +// - returns a generator producing sequences with elements {false, true}. +// +// It is useful when testing code that depends on Boolean flags. Combinations +// of multiple flags can be tested when several Bool()'s are combined using +// Combine() function. +// +// In the following example all tests in the test case FlagDependentTest +// will be instantiated twice with parameters false and true. +// +// class FlagDependentTest : public testing::TestWithParam { +// virtual void SetUp() { +// external_flag = GetParam(); +// } +// } +// INSTANTIATE_TEST_CASE_P(BoolSequence, FlagDependentTest, Bool()); +// +inline internal::ParamGenerator Bool() { + return Values(false, true); +} + +#if GTEST_HAS_COMBINE +// Combine() allows the user to combine two or more sequences to produce +// values of a Cartesian product of those sequences' elements. +// +// Synopsis: +// Combine(gen1, gen2, ..., genN) +// - returns a generator producing sequences with elements coming from +// the Cartesian product of elements from the sequences generated by +// gen1, gen2, ..., genN. The sequence elements will have a type of +// tuple where T1, T2, ..., TN are the types +// of elements from sequences produces by gen1, gen2, ..., genN. +// +// Combine can have up to $maxtuple arguments. This number is currently limited +// by the maximum number of elements in the tuple implementation used by Google +// Test. +// +// Example: +// +// This will instantiate tests in test case AnimalTest each one with +// the parameter values tuple("cat", BLACK), tuple("cat", WHITE), +// tuple("dog", BLACK), and tuple("dog", WHITE): +// +// enum Color { BLACK, GRAY, WHITE }; +// class AnimalTest +// : public testing::TestWithParam > {...}; +// +// TEST_P(AnimalTest, AnimalLooksNice) {...} +// +// INSTANTIATE_TEST_CASE_P(AnimalVariations, AnimalTest, +// Combine(Values("cat", "dog"), +// Values(BLACK, WHITE))); +// +// This will instantiate tests in FlagDependentTest with all variations of two +// Boolean flags: +// +// class FlagDependentTest +// : public testing::TestWithParam > { +// virtual void SetUp() { +// // Assigns external_flag_1 and external_flag_2 values from the tuple. +// tie(external_flag_1, external_flag_2) = GetParam(); +// } +// }; +// +// TEST_P(FlagDependentTest, TestFeature1) { +// // Test your code using external_flag_1 and external_flag_2 here. +// } +// INSTANTIATE_TEST_CASE_P(TwoBoolSequence, FlagDependentTest, +// Combine(Bool(), Bool())); +// +$range i 2..maxtuple +$for i [[ +$range j 1..i + +template <$for j, [[typename Generator$j]]> +internal::CartesianProductHolder$i<$for j, [[Generator$j]]> Combine( + $for j, [[const Generator$j& g$j]]) { + return internal::CartesianProductHolder$i<$for j, [[Generator$j]]>( + $for j, [[g$j]]); +} + +]] +#endif // GTEST_HAS_COMBINE + + + +#define TEST_P(test_case_name, test_name) \ + class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \ + : public test_case_name { \ + public: \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {} \ + virtual void TestBody(); \ + private: \ + static int AddToRegistry() { \ + ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ + GetTestCasePatternHolder(\ + #test_case_name, __FILE__, __LINE__)->AddTestPattern(\ + #test_case_name, \ + #test_name, \ + new ::testing::internal::TestMetaFactory< \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>()); \ + return 0; \ + } \ + static int gtest_registering_dummy_; \ + GTEST_DISALLOW_COPY_AND_ASSIGN_(\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)); \ + }; \ + int GTEST_TEST_CLASS_NAME_(test_case_name, \ + test_name)::gtest_registering_dummy_ = \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::AddToRegistry(); \ + void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() + +#define INSTANTIATE_TEST_CASE_P(prefix, test_case_name, generator) \ + ::testing::internal::ParamGenerator \ + gtest_##prefix##test_case_name##_EvalGenerator_() { return generator; } \ + int gtest_##prefix##test_case_name##_dummy_ = \ + ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ + GetTestCasePatternHolder(\ + #test_case_name, __FILE__, __LINE__)->AddTestCaseInstantiation(\ + #prefix, \ + >est_##prefix##test_case_name##_EvalGenerator_, \ + __FILE__, __LINE__) + +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/gtest-spi.h b/3rdparty/gmock/gtest/include/gtest/gtest-spi.h new file mode 100644 index 00000000..c41da484 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/gtest-spi.h @@ -0,0 +1,232 @@ +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) +// +// Utilities for testing Google Test itself and code that uses Google Test +// (e.g. frameworks built on top of Google Test). + +#ifndef GTEST_INCLUDE_GTEST_GTEST_SPI_H_ +#define GTEST_INCLUDE_GTEST_GTEST_SPI_H_ + +#include + +namespace testing { + +// This helper class can be used to mock out Google Test failure reporting +// so that we can test Google Test or code that builds on Google Test. +// +// An object of this class appends a TestPartResult object to the +// TestPartResultArray object given in the constructor whenever a Google Test +// failure is reported. It can either intercept only failures that are +// generated in the same thread that created this object or it can intercept +// all generated failures. The scope of this mock object can be controlled with +// the second argument to the two arguments constructor. +class GTEST_API_ ScopedFakeTestPartResultReporter + : public TestPartResultReporterInterface { + public: + // The two possible mocking modes of this object. + enum InterceptMode { + INTERCEPT_ONLY_CURRENT_THREAD, // Intercepts only thread local failures. + INTERCEPT_ALL_THREADS // Intercepts all failures. + }; + + // The c'tor sets this object as the test part result reporter used + // by Google Test. The 'result' parameter specifies where to report the + // results. This reporter will only catch failures generated in the current + // thread. DEPRECATED + explicit ScopedFakeTestPartResultReporter(TestPartResultArray* result); + + // Same as above, but you can choose the interception scope of this object. + ScopedFakeTestPartResultReporter(InterceptMode intercept_mode, + TestPartResultArray* result); + + // The d'tor restores the previous test part result reporter. + virtual ~ScopedFakeTestPartResultReporter(); + + // Appends the TestPartResult object to the TestPartResultArray + // received in the constructor. + // + // This method is from the TestPartResultReporterInterface + // interface. + virtual void ReportTestPartResult(const TestPartResult& result); + private: + void Init(); + + const InterceptMode intercept_mode_; + TestPartResultReporterInterface* old_reporter_; + TestPartResultArray* const result_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedFakeTestPartResultReporter); +}; + +namespace internal { + +// A helper class for implementing EXPECT_FATAL_FAILURE() and +// EXPECT_NONFATAL_FAILURE(). Its destructor verifies that the given +// TestPartResultArray contains exactly one failure that has the given +// type and contains the given substring. If that's not the case, a +// non-fatal failure will be generated. +class GTEST_API_ SingleFailureChecker { + public: + // The constructor remembers the arguments. + SingleFailureChecker(const TestPartResultArray* results, + TestPartResult::Type type, + const char* substr); + ~SingleFailureChecker(); + private: + const TestPartResultArray* const results_; + const TestPartResult::Type type_; + const String substr_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(SingleFailureChecker); +}; + +} // namespace internal + +} // namespace testing + +// A set of macros for testing Google Test assertions or code that's expected +// to generate Google Test fatal failures. It verifies that the given +// statement will cause exactly one fatal Google Test failure with 'substr' +// being part of the failure message. +// +// There are two different versions of this macro. EXPECT_FATAL_FAILURE only +// affects and considers failures generated in the current thread and +// EXPECT_FATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. +// +// The verification of the assertion is done correctly even when the statement +// throws an exception or aborts the current function. +// +// Known restrictions: +// - 'statement' cannot reference local non-static variables or +// non-static members of the current object. +// - 'statement' cannot return a value. +// - You cannot stream a failure message to this macro. +// +// Note that even though the implementations of the following two +// macros are much alike, we cannot refactor them to use a common +// helper macro, due to some peculiarity in how the preprocessor +// works. The AcceptsMacroThatExpandsToUnprotectedComma test in +// gtest_unittest.cc will fail to compile if we do that. +#define EXPECT_FATAL_FAILURE(statement, substr) \ + do { \ + class GTestExpectFatalFailureHelper {\ + public:\ + static void Execute() { statement; }\ + };\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kFatalFailure, (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\ + GTestExpectFatalFailureHelper::Execute();\ + }\ + } while (::testing::internal::AlwaysFalse()) + +#define EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ + do { \ + class GTestExpectFatalFailureHelper {\ + public:\ + static void Execute() { statement; }\ + };\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kFatalFailure, (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ALL_THREADS, >est_failures);\ + GTestExpectFatalFailureHelper::Execute();\ + }\ + } while (::testing::internal::AlwaysFalse()) + +// A macro for testing Google Test assertions or code that's expected to +// generate Google Test non-fatal failures. It asserts that the given +// statement will cause exactly one non-fatal Google Test failure with 'substr' +// being part of the failure message. +// +// There are two different versions of this macro. EXPECT_NONFATAL_FAILURE only +// affects and considers failures generated in the current thread and +// EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. +// +// 'statement' is allowed to reference local variables and members of +// the current object. +// +// The verification of the assertion is done correctly even when the statement +// throws an exception or aborts the current function. +// +// Known restrictions: +// - You cannot stream a failure message to this macro. +// +// Note that even though the implementations of the following two +// macros are much alike, we cannot refactor them to use a common +// helper macro, due to some peculiarity in how the preprocessor +// works. If we do that, the code won't compile when the user gives +// EXPECT_NONFATAL_FAILURE() a statement that contains a macro that +// expands to code containing an unprotected comma. The +// AcceptsMacroThatExpandsToUnprotectedComma test in gtest_unittest.cc +// catches that. +// +// For the same reason, we have to write +// if (::testing::internal::AlwaysTrue()) { statement; } +// instead of +// GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) +// to avoid an MSVC warning on unreachable code. +#define EXPECT_NONFATAL_FAILURE(statement, substr) \ + do {\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kNonFatalFailure, \ + (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\ + if (::testing::internal::AlwaysTrue()) { statement; }\ + }\ + } while (::testing::internal::AlwaysFalse()) + +#define EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ + do {\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kNonFatalFailure, \ + (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter::INTERCEPT_ALL_THREADS,\ + >est_failures);\ + if (::testing::internal::AlwaysTrue()) { statement; }\ + }\ + } while (::testing::internal::AlwaysFalse()) + +#endif // GTEST_INCLUDE_GTEST_GTEST_SPI_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/gtest-test-part.h b/3rdparty/gmock/gtest/include/gtest/gtest-test-part.h new file mode 100644 index 00000000..f7147590 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/gtest-test-part.h @@ -0,0 +1,176 @@ +// Copyright 2008, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: mheule@google.com (Markus Heule) +// + +#ifndef GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ +#define GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ + +#include +#include +#include +#include + +namespace testing { + +// A copyable object representing the result of a test part (i.e. an +// assertion or an explicit FAIL(), ADD_FAILURE(), or SUCCESS()). +// +// Don't inherit from TestPartResult as its destructor is not virtual. +class GTEST_API_ TestPartResult { + public: + // The possible outcomes of a test part (i.e. an assertion or an + // explicit SUCCEED(), FAIL(), or ADD_FAILURE()). + enum Type { + kSuccess, // Succeeded. + kNonFatalFailure, // Failed but the test can continue. + kFatalFailure // Failed and the test should be terminated. + }; + + // C'tor. TestPartResult does NOT have a default constructor. + // Always use this constructor (with parameters) to create a + // TestPartResult object. + TestPartResult(Type a_type, + const char* a_file_name, + int a_line_number, + const char* a_message) + : type_(a_type), + file_name_(a_file_name), + line_number_(a_line_number), + summary_(ExtractSummary(a_message)), + message_(a_message) { + } + + // Gets the outcome of the test part. + Type type() const { return type_; } + + // Gets the name of the source file where the test part took place, or + // NULL if it's unknown. + const char* file_name() const { return file_name_.c_str(); } + + // Gets the line in the source file where the test part took place, + // or -1 if it's unknown. + int line_number() const { return line_number_; } + + // Gets the summary of the failure message. + const char* summary() const { return summary_.c_str(); } + + // Gets the message associated with the test part. + const char* message() const { return message_.c_str(); } + + // Returns true iff the test part passed. + bool passed() const { return type_ == kSuccess; } + + // Returns true iff the test part failed. + bool failed() const { return type_ != kSuccess; } + + // Returns true iff the test part non-fatally failed. + bool nonfatally_failed() const { return type_ == kNonFatalFailure; } + + // Returns true iff the test part fatally failed. + bool fatally_failed() const { return type_ == kFatalFailure; } + private: + Type type_; + + // Gets the summary of the failure message by omitting the stack + // trace in it. + static internal::String ExtractSummary(const char* message); + + // The name of the source file where the test part took place, or + // NULL if the source file is unknown. + internal::String file_name_; + // The line in the source file where the test part took place, or -1 + // if the line number is unknown. + int line_number_; + internal::String summary_; // The test failure summary. + internal::String message_; // The test failure message. +}; + +// Prints a TestPartResult object. +std::ostream& operator<<(std::ostream& os, const TestPartResult& result); + +// An array of TestPartResult objects. +// +// Don't inherit from TestPartResultArray as its destructor is not +// virtual. +class GTEST_API_ TestPartResultArray { + public: + TestPartResultArray() {} + + // Appends the given TestPartResult to the array. + void Append(const TestPartResult& result); + + // Returns the TestPartResult at the given index (0-based). + const TestPartResult& GetTestPartResult(int index) const; + + // Returns the number of TestPartResult objects in the array. + int size() const; + + private: + std::vector array_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestPartResultArray); +}; + +// This interface knows how to report a test part result. +class TestPartResultReporterInterface { + public: + virtual ~TestPartResultReporterInterface() {} + + virtual void ReportTestPartResult(const TestPartResult& result) = 0; +}; + +namespace internal { + +// This helper class is used by {ASSERT|EXPECT}_NO_FATAL_FAILURE to check if a +// statement generates new fatal failures. To do so it registers itself as the +// current test part result reporter. Besides checking if fatal failures were +// reported, it only delegates the reporting to the former result reporter. +// The original result reporter is restored in the destructor. +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +class GTEST_API_ HasNewFatalFailureHelper + : public TestPartResultReporterInterface { + public: + HasNewFatalFailureHelper(); + virtual ~HasNewFatalFailureHelper(); + virtual void ReportTestPartResult(const TestPartResult& result); + bool has_new_fatal_failure() const { return has_new_fatal_failure_; } + private: + bool has_new_fatal_failure_; + TestPartResultReporterInterface* original_reporter_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(HasNewFatalFailureHelper); +}; + +} // namespace internal + +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/gtest-typed-test.h b/3rdparty/gmock/gtest/include/gtest/gtest-typed-test.h new file mode 100644 index 00000000..1ec8eb8d --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/gtest-typed-test.h @@ -0,0 +1,259 @@ +// Copyright 2008 Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +#ifndef GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ + +// This header implements typed tests and type-parameterized tests. + +// Typed (aka type-driven) tests repeat the same test for types in a +// list. You must know which types you want to test with when writing +// typed tests. Here's how you do it: + +#if 0 + +// First, define a fixture class template. It should be parameterized +// by a type. Remember to derive it from testing::Test. +template +class FooTest : public testing::Test { + public: + ... + typedef std::list List; + static T shared_; + T value_; +}; + +// Next, associate a list of types with the test case, which will be +// repeated for each type in the list. The typedef is necessary for +// the macro to parse correctly. +typedef testing::Types MyTypes; +TYPED_TEST_CASE(FooTest, MyTypes); + +// If the type list contains only one type, you can write that type +// directly without Types<...>: +// TYPED_TEST_CASE(FooTest, int); + +// Then, use TYPED_TEST() instead of TEST_F() to define as many typed +// tests for this test case as you want. +TYPED_TEST(FooTest, DoesBlah) { + // Inside a test, refer to TypeParam to get the type parameter. + // Since we are inside a derived class template, C++ requires use to + // visit the members of FooTest via 'this'. + TypeParam n = this->value_; + + // To visit static members of the fixture, add the TestFixture:: + // prefix. + n += TestFixture::shared_; + + // To refer to typedefs in the fixture, add the "typename + // TestFixture::" prefix. + typename TestFixture::List values; + values.push_back(n); + ... +} + +TYPED_TEST(FooTest, HasPropertyA) { ... } + +#endif // 0 + +// Type-parameterized tests are abstract test patterns parameterized +// by a type. Compared with typed tests, type-parameterized tests +// allow you to define the test pattern without knowing what the type +// parameters are. The defined pattern can be instantiated with +// different types any number of times, in any number of translation +// units. +// +// If you are designing an interface or concept, you can define a +// suite of type-parameterized tests to verify properties that any +// valid implementation of the interface/concept should have. Then, +// each implementation can easily instantiate the test suite to verify +// that it conforms to the requirements, without having to write +// similar tests repeatedly. Here's an example: + +#if 0 + +// First, define a fixture class template. It should be parameterized +// by a type. Remember to derive it from testing::Test. +template +class FooTest : public testing::Test { + ... +}; + +// Next, declare that you will define a type-parameterized test case +// (the _P suffix is for "parameterized" or "pattern", whichever you +// prefer): +TYPED_TEST_CASE_P(FooTest); + +// Then, use TYPED_TEST_P() to define as many type-parameterized tests +// for this type-parameterized test case as you want. +TYPED_TEST_P(FooTest, DoesBlah) { + // Inside a test, refer to TypeParam to get the type parameter. + TypeParam n = 0; + ... +} + +TYPED_TEST_P(FooTest, HasPropertyA) { ... } + +// Now the tricky part: you need to register all test patterns before +// you can instantiate them. The first argument of the macro is the +// test case name; the rest are the names of the tests in this test +// case. +REGISTER_TYPED_TEST_CASE_P(FooTest, + DoesBlah, HasPropertyA); + +// Finally, you are free to instantiate the pattern with the types you +// want. If you put the above code in a header file, you can #include +// it in multiple C++ source files and instantiate it multiple times. +// +// To distinguish different instances of the pattern, the first +// argument to the INSTANTIATE_* macro is a prefix that will be added +// to the actual test case name. Remember to pick unique prefixes for +// different instances. +typedef testing::Types MyTypes; +INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes); + +// If the type list contains only one type, you can write that type +// directly without Types<...>: +// INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, int); + +#endif // 0 + +#include +#include + +// Implements typed tests. + +#if GTEST_HAS_TYPED_TEST + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the name of the typedef for the type parameters of the +// given test case. +#define GTEST_TYPE_PARAMS_(TestCaseName) gtest_type_params_##TestCaseName##_ + +// The 'Types' template argument below must have spaces around it +// since some compilers may choke on '>>' when passing a template +// instance (e.g. Types) +#define TYPED_TEST_CASE(CaseName, Types) \ + typedef ::testing::internal::TypeList< Types >::type \ + GTEST_TYPE_PARAMS_(CaseName) + +#define TYPED_TEST(CaseName, TestName) \ + template \ + class GTEST_TEST_CLASS_NAME_(CaseName, TestName) \ + : public CaseName { \ + private: \ + typedef CaseName TestFixture; \ + typedef gtest_TypeParam_ TypeParam; \ + virtual void TestBody(); \ + }; \ + bool gtest_##CaseName##_##TestName##_registered_ = \ + ::testing::internal::TypeParameterizedTest< \ + CaseName, \ + ::testing::internal::TemplateSel< \ + GTEST_TEST_CLASS_NAME_(CaseName, TestName)>, \ + GTEST_TYPE_PARAMS_(CaseName)>::Register(\ + "", #CaseName, #TestName, 0); \ + template \ + void GTEST_TEST_CLASS_NAME_(CaseName, TestName)::TestBody() + +#endif // GTEST_HAS_TYPED_TEST + +// Implements type-parameterized tests. + +#if GTEST_HAS_TYPED_TEST_P + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the namespace name that the type-parameterized tests for +// the given type-parameterized test case are defined in. The exact +// name of the namespace is subject to change without notice. +#define GTEST_CASE_NAMESPACE_(TestCaseName) \ + gtest_case_##TestCaseName##_ + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the name of the variable used to remember the names of +// the defined tests in the given test case. +#define GTEST_TYPED_TEST_CASE_P_STATE_(TestCaseName) \ + gtest_typed_test_case_p_state_##TestCaseName##_ + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE DIRECTLY. +// +// Expands to the name of the variable used to remember the names of +// the registered tests in the given test case. +#define GTEST_REGISTERED_TEST_NAMES_(TestCaseName) \ + gtest_registered_test_names_##TestCaseName##_ + +// The variables defined in the type-parameterized test macros are +// static as typically these macros are used in a .h file that can be +// #included in multiple translation units linked together. +#define TYPED_TEST_CASE_P(CaseName) \ + static ::testing::internal::TypedTestCasePState \ + GTEST_TYPED_TEST_CASE_P_STATE_(CaseName) + +#define TYPED_TEST_P(CaseName, TestName) \ + namespace GTEST_CASE_NAMESPACE_(CaseName) { \ + template \ + class TestName : public CaseName { \ + private: \ + typedef CaseName TestFixture; \ + typedef gtest_TypeParam_ TypeParam; \ + virtual void TestBody(); \ + }; \ + static bool gtest_##TestName##_defined_ = \ + GTEST_TYPED_TEST_CASE_P_STATE_(CaseName).AddTestName(\ + __FILE__, __LINE__, #CaseName, #TestName); \ + } \ + template \ + void GTEST_CASE_NAMESPACE_(CaseName)::TestName::TestBody() + +#define REGISTER_TYPED_TEST_CASE_P(CaseName, ...) \ + namespace GTEST_CASE_NAMESPACE_(CaseName) { \ + typedef ::testing::internal::Templates<__VA_ARGS__>::type gtest_AllTests_; \ + } \ + static const char* const GTEST_REGISTERED_TEST_NAMES_(CaseName) = \ + GTEST_TYPED_TEST_CASE_P_STATE_(CaseName).VerifyRegisteredTestNames(\ + __FILE__, __LINE__, #__VA_ARGS__) + +// The 'Types' template argument below must have spaces around it +// since some compilers may choke on '>>' when passing a template +// instance (e.g. Types) +#define INSTANTIATE_TYPED_TEST_CASE_P(Prefix, CaseName, Types) \ + bool gtest_##Prefix##_##CaseName = \ + ::testing::internal::TypeParameterizedTestCase::type>::Register(\ + #Prefix, #CaseName, GTEST_REGISTERED_TEST_NAMES_(CaseName)) + +#endif // GTEST_HAS_TYPED_TEST_P + +#endif // GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/gtest.h b/3rdparty/gmock/gtest/include/gtest/gtest.h new file mode 100644 index 00000000..921fad11 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/gtest.h @@ -0,0 +1,2052 @@ +// Copyright 2005, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file defines the public API for Google Test. It should be +// included by any test program that uses Google Test. +// +// IMPORTANT NOTE: Due to limitation of the C++ language, we have to +// leave some internal implementation details in this header file. +// They are clearly marked by comments like this: +// +// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +// +// Such code is NOT meant to be used by a user directly, and is subject +// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user +// program! +// +// Acknowledgment: Google Test borrowed the idea of automatic test +// registration from Barthelemy Dagenais' (barthelemy@prologique.com) +// easyUnit framework. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// Depending on the platform, different string classes are available. +// On Linux, in addition to ::std::string, Google also makes use of +// class ::string, which has the same interface as ::std::string, but +// has a different implementation. +// +// The user can define GTEST_HAS_GLOBAL_STRING to 1 to indicate that +// ::string is available AND is a distinct type to ::std::string, or +// define it to 0 to indicate otherwise. +// +// If the user's ::std::string and ::string are the same class due to +// aliasing, he should define GTEST_HAS_GLOBAL_STRING to 0. +// +// If the user doesn't define GTEST_HAS_GLOBAL_STRING, it is defined +// heuristically. + +namespace testing { + +// Declares the flags. + +// This flag temporary enables the disabled tests. +GTEST_DECLARE_bool_(also_run_disabled_tests); + +// This flag brings the debugger on an assertion failure. +GTEST_DECLARE_bool_(break_on_failure); + +// This flag controls whether Google Test catches all test-thrown exceptions +// and logs them as failures. +GTEST_DECLARE_bool_(catch_exceptions); + +// This flag enables using colors in terminal output. Available values are +// "yes" to enable colors, "no" (disable colors), or "auto" (the default) +// to let Google Test decide. +GTEST_DECLARE_string_(color); + +// This flag sets up the filter to select by name using a glob pattern +// the tests to run. If the filter is not given all tests are executed. +GTEST_DECLARE_string_(filter); + +// This flag causes the Google Test to list tests. None of the tests listed +// are actually run if the flag is provided. +GTEST_DECLARE_bool_(list_tests); + +// This flag controls whether Google Test emits a detailed XML report to a file +// in addition to its normal textual output. +GTEST_DECLARE_string_(output); + +// This flags control whether Google Test prints the elapsed time for each +// test. +GTEST_DECLARE_bool_(print_time); + +// This flag specifies the random number seed. +GTEST_DECLARE_int32_(random_seed); + +// This flag sets how many times the tests are repeated. The default value +// is 1. If the value is -1 the tests are repeating forever. +GTEST_DECLARE_int32_(repeat); + +// This flag controls whether Google Test includes Google Test internal +// stack frames in failure stack traces. +GTEST_DECLARE_bool_(show_internal_stack_frames); + +// When this flag is specified, tests' order is randomized on every iteration. +GTEST_DECLARE_bool_(shuffle); + +// This flag specifies the maximum number of stack frames to be +// printed in a failure message. +GTEST_DECLARE_int32_(stack_trace_depth); + +// When this flag is specified, a failed assertion will throw an +// exception if exceptions are enabled, or exit the program with a +// non-zero code otherwise. +GTEST_DECLARE_bool_(throw_on_failure); + +// The upper limit for valid stack trace depths. +const int kMaxStackTraceDepth = 100; + +namespace internal { + +class AssertHelper; +class DefaultGlobalTestPartResultReporter; +class ExecDeathTest; +class NoExecDeathTest; +class FinalSuccessChecker; +class GTestFlagSaver; +class TestInfoImpl; +class TestResultAccessor; +class TestEventListenersAccessor; +class TestEventRepeater; +class WindowsDeathTest; +class UnitTestImpl* GetUnitTestImpl(); +void ReportFailureInUnknownLocation(TestPartResult::Type result_type, + const String& message); +class PrettyUnitTestResultPrinter; +class XmlUnitTestResultPrinter; + +// Converts a streamable value to a String. A NULL pointer is +// converted to "(null)". When the input value is a ::string, +// ::std::string, ::wstring, or ::std::wstring object, each NUL +// character in it is replaced with "\\0". +// Declared in gtest-internal.h but defined here, so that it has access +// to the definition of the Message class, required by the ARM +// compiler. +template +String StreamableToString(const T& streamable) { + return (Message() << streamable).GetString(); +} + +} // namespace internal + +// A class for indicating whether an assertion was successful. When +// the assertion wasn't successful, the AssertionResult object +// remembers a non-empty message that describes how it failed. +// +// To create an instance of this class, use one of the factory functions +// (AssertionSuccess() and AssertionFailure()). +// +// This class is useful for two purposes: +// 1. Defining predicate functions to be used with Boolean test assertions +// EXPECT_TRUE/EXPECT_FALSE and their ASSERT_ counterparts +// 2. Defining predicate-format functions to be +// used with predicate assertions (ASSERT_PRED_FORMAT*, etc). +// +// For example, if you define IsEven predicate: +// +// testing::AssertionResult IsEven(int n) { +// if ((n % 2) == 0) +// return testing::AssertionSuccess(); +// else +// return testing::AssertionFailure() << n << " is odd"; +// } +// +// Then the failed expectation EXPECT_TRUE(IsEven(Fib(5))) +// will print the message +// +// Value of: IsEven(Fib(5)) +// Actual: false (5 is odd) +// Expected: true +// +// instead of a more opaque +// +// Value of: IsEven(Fib(5)) +// Actual: false +// Expected: true +// +// in case IsEven is a simple Boolean predicate. +// +// If you expect your predicate to be reused and want to support informative +// messages in EXPECT_FALSE and ASSERT_FALSE (negative assertions show up +// about half as often as positive ones in our tests), supply messages for +// both success and failure cases: +// +// testing::AssertionResult IsEven(int n) { +// if ((n % 2) == 0) +// return testing::AssertionSuccess() << n << " is even"; +// else +// return testing::AssertionFailure() << n << " is odd"; +// } +// +// Then a statement EXPECT_FALSE(IsEven(Fib(6))) will print +// +// Value of: IsEven(Fib(6)) +// Actual: true (8 is even) +// Expected: false +// +// NB: Predicates that support negative Boolean assertions have reduced +// performance in positive ones so be careful not to use them in tests +// that have lots (tens of thousands) of positive Boolean assertions. +// +// To use this class with EXPECT_PRED_FORMAT assertions such as: +// +// // Verifies that Foo() returns an even number. +// EXPECT_PRED_FORMAT1(IsEven, Foo()); +// +// you need to define: +// +// testing::AssertionResult IsEven(const char* expr, int n) { +// if ((n % 2) == 0) +// return testing::AssertionSuccess(); +// else +// return testing::AssertionFailure() +// << "Expected: " << expr << " is even\n Actual: it's " << n; +// } +// +// If Foo() returns 5, you will see the following message: +// +// Expected: Foo() is even +// Actual: it's 5 +// +class GTEST_API_ AssertionResult { + public: + // Copy constructor. + // Used in EXPECT_TRUE/FALSE(assertion_result). + AssertionResult(const AssertionResult& other); + // Used in the EXPECT_TRUE/FALSE(bool_expression). + explicit AssertionResult(bool success) : success_(success) {} + + // Returns true iff the assertion succeeded. + operator bool() const { return success_; } // NOLINT + + // Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE. + AssertionResult operator!() const; + + // Returns the text streamed into this AssertionResult. Test assertions + // use it when they fail (i.e., the predicate's outcome doesn't match the + // assertion's expectation). When nothing has been streamed into the + // object, returns an empty string. + const char* message() const { + return message_.get() != NULL && message_->c_str() != NULL ? + message_->c_str() : ""; + } + // TODO(vladl@google.com): Remove this after making sure no clients use it. + // Deprecated; please use message() instead. + const char* failure_message() const { return message(); } + + // Streams a custom failure message into this object. + template AssertionResult& operator<<(const T& value); + + private: + // No implementation - we want AssertionResult to be + // copy-constructible but not assignable. + void operator=(const AssertionResult& other); + + // Stores result of the assertion predicate. + bool success_; + // Stores the message describing the condition in case the expectation + // construct is not satisfied with the predicate's outcome. + // Referenced via a pointer to avoid taking too much stack frame space + // with test assertions. + internal::scoped_ptr message_; +}; // class AssertionResult + +// Streams a custom failure message into this object. +template +AssertionResult& AssertionResult::operator<<(const T& value) { + Message msg; + if (message_.get() != NULL) + msg << *message_; + msg << value; + message_.reset(new internal::String(msg.GetString())); + return *this; +} + +// Makes a successful assertion result. +GTEST_API_ AssertionResult AssertionSuccess(); + +// Makes a failed assertion result. +GTEST_API_ AssertionResult AssertionFailure(); + +// Makes a failed assertion result with the given failure message. +// Deprecated; use AssertionFailure() << msg. +GTEST_API_ AssertionResult AssertionFailure(const Message& msg); + +// The abstract class that all tests inherit from. +// +// In Google Test, a unit test program contains one or many TestCases, and +// each TestCase contains one or many Tests. +// +// When you define a test using the TEST macro, you don't need to +// explicitly derive from Test - the TEST macro automatically does +// this for you. +// +// The only time you derive from Test is when defining a test fixture +// to be used a TEST_F. For example: +// +// class FooTest : public testing::Test { +// protected: +// virtual void SetUp() { ... } +// virtual void TearDown() { ... } +// ... +// }; +// +// TEST_F(FooTest, Bar) { ... } +// TEST_F(FooTest, Baz) { ... } +// +// Test is not copyable. +class GTEST_API_ Test { + public: + friend class internal::TestInfoImpl; + + // Defines types for pointers to functions that set up and tear down + // a test case. + typedef internal::SetUpTestCaseFunc SetUpTestCaseFunc; + typedef internal::TearDownTestCaseFunc TearDownTestCaseFunc; + + // The d'tor is virtual as we intend to inherit from Test. + virtual ~Test(); + + // Sets up the stuff shared by all tests in this test case. + // + // Google Test will call Foo::SetUpTestCase() before running the first + // test in test case Foo. Hence a sub-class can define its own + // SetUpTestCase() method to shadow the one defined in the super + // class. + static void SetUpTestCase() {} + + // Tears down the stuff shared by all tests in this test case. + // + // Google Test will call Foo::TearDownTestCase() after running the last + // test in test case Foo. Hence a sub-class can define its own + // TearDownTestCase() method to shadow the one defined in the super + // class. + static void TearDownTestCase() {} + + // Returns true iff the current test has a fatal failure. + static bool HasFatalFailure(); + + // Returns true iff the current test has a non-fatal failure. + static bool HasNonfatalFailure(); + + // Returns true iff the current test has a (either fatal or + // non-fatal) failure. + static bool HasFailure() { return HasFatalFailure() || HasNonfatalFailure(); } + + // Logs a property for the current test. Only the last value for a given + // key is remembered. + // These are public static so they can be called from utility functions + // that are not members of the test fixture. + // The arguments are const char* instead strings, as Google Test is used + // on platforms where string doesn't compile. + // + // Note that a driving consideration for these RecordProperty methods + // was to produce xml output suited to the Greenspan charting utility, + // which at present will only chart values that fit in a 32-bit int. It + // is the user's responsibility to restrict their values to 32-bit ints + // if they intend them to be used with Greenspan. + static void RecordProperty(const char* key, const char* value); + static void RecordProperty(const char* key, int value); + + protected: + // Creates a Test object. + Test(); + + // Sets up the test fixture. + virtual void SetUp(); + + // Tears down the test fixture. + virtual void TearDown(); + + private: + // Returns true iff the current test has the same fixture class as + // the first test in the current test case. + static bool HasSameFixtureClass(); + + // Runs the test after the test fixture has been set up. + // + // A sub-class must implement this to define the test logic. + // + // DO NOT OVERRIDE THIS FUNCTION DIRECTLY IN A USER PROGRAM. + // Instead, use the TEST or TEST_F macro. + virtual void TestBody() = 0; + + // Sets up, executes, and tears down the test. + void Run(); + + // Uses a GTestFlagSaver to save and restore all Google Test flags. + const internal::GTestFlagSaver* const gtest_flag_saver_; + + // Often a user mis-spells SetUp() as Setup() and spends a long time + // wondering why it is never called by Google Test. The declaration of + // the following method is solely for catching such an error at + // compile time: + // + // - The return type is deliberately chosen to be not void, so it + // will be a conflict if a user declares void Setup() in his test + // fixture. + // + // - This method is private, so it will be another compiler error + // if a user calls it from his test fixture. + // + // DO NOT OVERRIDE THIS FUNCTION. + // + // If you see an error about overriding the following function or + // about it being private, you have mis-spelled SetUp() as Setup(). + struct Setup_should_be_spelled_SetUp {}; + virtual Setup_should_be_spelled_SetUp* Setup() { return NULL; } + + // We disallow copying Tests. + GTEST_DISALLOW_COPY_AND_ASSIGN_(Test); +}; + +typedef internal::TimeInMillis TimeInMillis; + +// A copyable object representing a user specified test property which can be +// output as a key/value string pair. +// +// Don't inherit from TestProperty as its destructor is not virtual. +class TestProperty { + public: + // C'tor. TestProperty does NOT have a default constructor. + // Always use this constructor (with parameters) to create a + // TestProperty object. + TestProperty(const char* a_key, const char* a_value) : + key_(a_key), value_(a_value) { + } + + // Gets the user supplied key. + const char* key() const { + return key_.c_str(); + } + + // Gets the user supplied value. + const char* value() const { + return value_.c_str(); + } + + // Sets a new value, overriding the one supplied in the constructor. + void SetValue(const char* new_value) { + value_ = new_value; + } + + private: + // The key supplied by the user. + internal::String key_; + // The value supplied by the user. + internal::String value_; +}; + +// The result of a single Test. This includes a list of +// TestPartResults, a list of TestProperties, a count of how many +// death tests there are in the Test, and how much time it took to run +// the Test. +// +// TestResult is not copyable. +class GTEST_API_ TestResult { + public: + // Creates an empty TestResult. + TestResult(); + + // D'tor. Do not inherit from TestResult. + ~TestResult(); + + // Gets the number of all test parts. This is the sum of the number + // of successful test parts and the number of failed test parts. + int total_part_count() const; + + // Returns the number of the test properties. + int test_property_count() const; + + // Returns true iff the test passed (i.e. no test part failed). + bool Passed() const { return !Failed(); } + + // Returns true iff the test failed. + bool Failed() const; + + // Returns true iff the test fatally failed. + bool HasFatalFailure() const; + + // Returns true iff the test has a non-fatal failure. + bool HasNonfatalFailure() const; + + // Returns the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const { return elapsed_time_; } + + // Returns the i-th test part result among all the results. i can range + // from 0 to test_property_count() - 1. If i is not in that range, aborts + // the program. + const TestPartResult& GetTestPartResult(int i) const; + + // Returns the i-th test property. i can range from 0 to + // test_property_count() - 1. If i is not in that range, aborts the + // program. + const TestProperty& GetTestProperty(int i) const; + + private: + friend class TestInfo; + friend class UnitTest; + friend class internal::DefaultGlobalTestPartResultReporter; + friend class internal::ExecDeathTest; + friend class internal::TestInfoImpl; + friend class internal::TestResultAccessor; + friend class internal::UnitTestImpl; + friend class internal::WindowsDeathTest; + + // Gets the vector of TestPartResults. + const std::vector& test_part_results() const { + return test_part_results_; + } + + // Gets the vector of TestProperties. + const std::vector& test_properties() const { + return test_properties_; + } + + // Sets the elapsed time. + void set_elapsed_time(TimeInMillis elapsed) { elapsed_time_ = elapsed; } + + // Adds a test property to the list. The property is validated and may add + // a non-fatal failure if invalid (e.g., if it conflicts with reserved + // key names). If a property is already recorded for the same key, the + // value will be updated, rather than storing multiple values for the same + // key. + void RecordProperty(const TestProperty& test_property); + + // Adds a failure if the key is a reserved attribute of Google Test + // testcase tags. Returns true if the property is valid. + // TODO(russr): Validate attribute names are legal and human readable. + static bool ValidateTestProperty(const TestProperty& test_property); + + // Adds a test part result to the list. + void AddTestPartResult(const TestPartResult& test_part_result); + + // Returns the death test count. + int death_test_count() const { return death_test_count_; } + + // Increments the death test count, returning the new count. + int increment_death_test_count() { return ++death_test_count_; } + + // Clears the test part results. + void ClearTestPartResults(); + + // Clears the object. + void Clear(); + + // Protects mutable state of the property vector and of owned + // properties, whose values may be updated. + internal::Mutex test_properites_mutex_; + + // The vector of TestPartResults + std::vector test_part_results_; + // The vector of TestProperties + std::vector test_properties_; + // Running count of death tests. + int death_test_count_; + // The elapsed time, in milliseconds. + TimeInMillis elapsed_time_; + + // We disallow copying TestResult. + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestResult); +}; // class TestResult + +// A TestInfo object stores the following information about a test: +// +// Test case name +// Test name +// Whether the test should be run +// A function pointer that creates the test object when invoked +// Test result +// +// The constructor of TestInfo registers itself with the UnitTest +// singleton such that the RUN_ALL_TESTS() macro knows which tests to +// run. +class GTEST_API_ TestInfo { + public: + // Destructs a TestInfo object. This function is not virtual, so + // don't inherit from TestInfo. + ~TestInfo(); + + // Returns the test case name. + const char* test_case_name() const; + + // Returns the test name. + const char* name() const; + + // Returns the test case comment. + const char* test_case_comment() const; + + // Returns the test comment. + const char* comment() const; + + // Returns true if this test should run, that is if the test is not disabled + // (or it is disabled but the also_run_disabled_tests flag has been specified) + // and its full name matches the user-specified filter. + // + // Google Test allows the user to filter the tests by their full names. + // The full name of a test Bar in test case Foo is defined as + // "Foo.Bar". Only the tests that match the filter will run. + // + // A filter is a colon-separated list of glob (not regex) patterns, + // optionally followed by a '-' and a colon-separated list of + // negative patterns (tests to exclude). A test is run if it + // matches one of the positive patterns and does not match any of + // the negative patterns. + // + // For example, *A*:Foo.* is a filter that matches any string that + // contains the character 'A' or starts with "Foo.". + bool should_run() const; + + // Returns the result of the test. + const TestResult* result() const; + + private: +#if GTEST_HAS_DEATH_TEST + friend class internal::DefaultDeathTestFactory; +#endif // GTEST_HAS_DEATH_TEST + friend class Test; + friend class TestCase; + friend class internal::TestInfoImpl; + friend class internal::UnitTestImpl; + friend TestInfo* internal::MakeAndRegisterTestInfo( + const char* test_case_name, const char* name, + const char* test_case_comment, const char* comment, + internal::TypeId fixture_class_id, + Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc, + internal::TestFactoryBase* factory); + + // Returns true if this test matches the user-specified filter. + bool matches_filter() const; + + // Increments the number of death tests encountered in this test so + // far. + int increment_death_test_count(); + + // Accessors for the implementation object. + internal::TestInfoImpl* impl() { return impl_; } + const internal::TestInfoImpl* impl() const { return impl_; } + + // Constructs a TestInfo object. The newly constructed instance assumes + // ownership of the factory object. + TestInfo(const char* test_case_name, const char* name, + const char* test_case_comment, const char* comment, + internal::TypeId fixture_class_id, + internal::TestFactoryBase* factory); + + // An opaque implementation object. + internal::TestInfoImpl* impl_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestInfo); +}; + +// A test case, which consists of a vector of TestInfos. +// +// TestCase is not copyable. +class GTEST_API_ TestCase { + public: + // Creates a TestCase with the given name. + // + // TestCase does NOT have a default constructor. Always use this + // constructor to create a TestCase object. + // + // Arguments: + // + // name: name of the test case + // set_up_tc: pointer to the function that sets up the test case + // tear_down_tc: pointer to the function that tears down the test case + TestCase(const char* name, const char* comment, + Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc); + + // Destructor of TestCase. + virtual ~TestCase(); + + // Gets the name of the TestCase. + const char* name() const { return name_.c_str(); } + + // Returns the test case comment. + const char* comment() const { return comment_.c_str(); } + + // Returns true if any test in this test case should run. + bool should_run() const { return should_run_; } + + // Gets the number of successful tests in this test case. + int successful_test_count() const; + + // Gets the number of failed tests in this test case. + int failed_test_count() const; + + // Gets the number of disabled tests in this test case. + int disabled_test_count() const; + + // Get the number of tests in this test case that should run. + int test_to_run_count() const; + + // Gets the number of all tests in this test case. + int total_test_count() const; + + // Returns true iff the test case passed. + bool Passed() const { return !Failed(); } + + // Returns true iff the test case failed. + bool Failed() const { return failed_test_count() > 0; } + + // Returns the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const { return elapsed_time_; } + + // Returns the i-th test among all the tests. i can range from 0 to + // total_test_count() - 1. If i is not in that range, returns NULL. + const TestInfo* GetTestInfo(int i) const; + + private: + friend class Test; + friend class internal::UnitTestImpl; + + // Gets the (mutable) vector of TestInfos in this TestCase. + std::vector& test_info_list() { return test_info_list_; } + + // Gets the (immutable) vector of TestInfos in this TestCase. + const std::vector& test_info_list() const { + return test_info_list_; + } + + // Returns the i-th test among all the tests. i can range from 0 to + // total_test_count() - 1. If i is not in that range, returns NULL. + TestInfo* GetMutableTestInfo(int i); + + // Sets the should_run member. + void set_should_run(bool should) { should_run_ = should; } + + // Adds a TestInfo to this test case. Will delete the TestInfo upon + // destruction of the TestCase object. + void AddTestInfo(TestInfo * test_info); + + // Clears the results of all tests in this test case. + void ClearResult(); + + // Clears the results of all tests in the given test case. + static void ClearTestCaseResult(TestCase* test_case) { + test_case->ClearResult(); + } + + // Runs every test in this TestCase. + void Run(); + + // Returns true iff test passed. + static bool TestPassed(const TestInfo * test_info); + + // Returns true iff test failed. + static bool TestFailed(const TestInfo * test_info); + + // Returns true iff test is disabled. + static bool TestDisabled(const TestInfo * test_info); + + // Returns true if the given test should run. + static bool ShouldRunTest(const TestInfo *test_info); + + // Shuffles the tests in this test case. + void ShuffleTests(internal::Random* random); + + // Restores the test order to before the first shuffle. + void UnshuffleTests(); + + // Name of the test case. + internal::String name_; + // Comment on the test case. + internal::String comment_; + // The vector of TestInfos in their original order. It owns the + // elements in the vector. + std::vector test_info_list_; + // Provides a level of indirection for the test list to allow easy + // shuffling and restoring the test order. The i-th element in this + // vector is the index of the i-th test in the shuffled test list. + std::vector test_indices_; + // Pointer to the function that sets up the test case. + Test::SetUpTestCaseFunc set_up_tc_; + // Pointer to the function that tears down the test case. + Test::TearDownTestCaseFunc tear_down_tc_; + // True iff any test in this test case should run. + bool should_run_; + // Elapsed time, in milliseconds. + TimeInMillis elapsed_time_; + + // We disallow copying TestCases. + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestCase); +}; + +// An Environment object is capable of setting up and tearing down an +// environment. The user should subclass this to define his own +// environment(s). +// +// An Environment object does the set-up and tear-down in virtual +// methods SetUp() and TearDown() instead of the constructor and the +// destructor, as: +// +// 1. You cannot safely throw from a destructor. This is a problem +// as in some cases Google Test is used where exceptions are enabled, and +// we may want to implement ASSERT_* using exceptions where they are +// available. +// 2. You cannot use ASSERT_* directly in a constructor or +// destructor. +class Environment { + public: + // The d'tor is virtual as we need to subclass Environment. + virtual ~Environment() {} + + // Override this to define how to set up the environment. + virtual void SetUp() {} + + // Override this to define how to tear down the environment. + virtual void TearDown() {} + private: + // If you see an error about overriding the following function or + // about it being private, you have mis-spelled SetUp() as Setup(). + struct Setup_should_be_spelled_SetUp {}; + virtual Setup_should_be_spelled_SetUp* Setup() { return NULL; } +}; + +// The interface for tracing execution of tests. The methods are organized in +// the order the corresponding events are fired. +class TestEventListener { + public: + virtual ~TestEventListener() {} + + // Fired before any test activity starts. + virtual void OnTestProgramStart(const UnitTest& unit_test) = 0; + + // Fired before each iteration of tests starts. There may be more than + // one iteration if GTEST_FLAG(repeat) is set. iteration is the iteration + // index, starting from 0. + virtual void OnTestIterationStart(const UnitTest& unit_test, + int iteration) = 0; + + // Fired before environment set-up for each iteration of tests starts. + virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test) = 0; + + // Fired after environment set-up for each iteration of tests ends. + virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) = 0; + + // Fired before the test case starts. + virtual void OnTestCaseStart(const TestCase& test_case) = 0; + + // Fired before the test starts. + virtual void OnTestStart(const TestInfo& test_info) = 0; + + // Fired after a failed assertion or a SUCCESS(). + virtual void OnTestPartResult(const TestPartResult& test_part_result) = 0; + + // Fired after the test ends. + virtual void OnTestEnd(const TestInfo& test_info) = 0; + + // Fired after the test case ends. + virtual void OnTestCaseEnd(const TestCase& test_case) = 0; + + // Fired before environment tear-down for each iteration of tests starts. + virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test) = 0; + + // Fired after environment tear-down for each iteration of tests ends. + virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) = 0; + + // Fired after each iteration of tests finishes. + virtual void OnTestIterationEnd(const UnitTest& unit_test, + int iteration) = 0; + + // Fired after all test activities have ended. + virtual void OnTestProgramEnd(const UnitTest& unit_test) = 0; +}; + +// The convenience class for users who need to override just one or two +// methods and are not concerned that a possible change to a signature of +// the methods they override will not be caught during the build. For +// comments about each method please see the definition of TestEventListener +// above. +class EmptyTestEventListener : public TestEventListener { + public: + virtual void OnTestProgramStart(const UnitTest& /*unit_test*/) {} + virtual void OnTestIterationStart(const UnitTest& /*unit_test*/, + int /*iteration*/) {} + virtual void OnEnvironmentsSetUpStart(const UnitTest& /*unit_test*/) {} + virtual void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) {} + virtual void OnTestCaseStart(const TestCase& /*test_case*/) {} + virtual void OnTestStart(const TestInfo& /*test_info*/) {} + virtual void OnTestPartResult(const TestPartResult& /*test_part_result*/) {} + virtual void OnTestEnd(const TestInfo& /*test_info*/) {} + virtual void OnTestCaseEnd(const TestCase& /*test_case*/) {} + virtual void OnEnvironmentsTearDownStart(const UnitTest& /*unit_test*/) {} + virtual void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) {} + virtual void OnTestIterationEnd(const UnitTest& /*unit_test*/, + int /*iteration*/) {} + virtual void OnTestProgramEnd(const UnitTest& /*unit_test*/) {} +}; + +// TestEventListeners lets users add listeners to track events in Google Test. +class GTEST_API_ TestEventListeners { + public: + TestEventListeners(); + ~TestEventListeners(); + + // Appends an event listener to the end of the list. Google Test assumes + // the ownership of the listener (i.e. it will delete the listener when + // the test program finishes). + void Append(TestEventListener* listener); + + // Removes the given event listener from the list and returns it. It then + // becomes the caller's responsibility to delete the listener. Returns + // NULL if the listener is not found in the list. + TestEventListener* Release(TestEventListener* listener); + + // Returns the standard listener responsible for the default console + // output. Can be removed from the listeners list to shut down default + // console output. Note that removing this object from the listener list + // with Release transfers its ownership to the caller and makes this + // function return NULL the next time. + TestEventListener* default_result_printer() const { + return default_result_printer_; + } + + // Returns the standard listener responsible for the default XML output + // controlled by the --gtest_output=xml flag. Can be removed from the + // listeners list by users who want to shut down the default XML output + // controlled by this flag and substitute it with custom one. Note that + // removing this object from the listener list with Release transfers its + // ownership to the caller and makes this function return NULL the next + // time. + TestEventListener* default_xml_generator() const { + return default_xml_generator_; + } + + private: + friend class TestCase; + friend class internal::DefaultGlobalTestPartResultReporter; + friend class internal::NoExecDeathTest; + friend class internal::TestEventListenersAccessor; + friend class internal::TestInfoImpl; + friend class internal::UnitTestImpl; + + // Returns repeater that broadcasts the TestEventListener events to all + // subscribers. + TestEventListener* repeater(); + + // Sets the default_result_printer attribute to the provided listener. + // The listener is also added to the listener list and previous + // default_result_printer is removed from it and deleted. The listener can + // also be NULL in which case it will not be added to the list. Does + // nothing if the previous and the current listener objects are the same. + void SetDefaultResultPrinter(TestEventListener* listener); + + // Sets the default_xml_generator attribute to the provided listener. The + // listener is also added to the listener list and previous + // default_xml_generator is removed from it and deleted. The listener can + // also be NULL in which case it will not be added to the list. Does + // nothing if the previous and the current listener objects are the same. + void SetDefaultXmlGenerator(TestEventListener* listener); + + // Controls whether events will be forwarded by the repeater to the + // listeners in the list. + bool EventForwardingEnabled() const; + void SuppressEventForwarding(); + + // The actual list of listeners. + internal::TestEventRepeater* repeater_; + // Listener responsible for the standard result output. + TestEventListener* default_result_printer_; + // Listener responsible for the creation of the XML output file. + TestEventListener* default_xml_generator_; + + // We disallow copying TestEventListeners. + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestEventListeners); +}; + +// A UnitTest consists of a vector of TestCases. +// +// This is a singleton class. The only instance of UnitTest is +// created when UnitTest::GetInstance() is first called. This +// instance is never deleted. +// +// UnitTest is not copyable. +// +// This class is thread-safe as long as the methods are called +// according to their specification. +class GTEST_API_ UnitTest { + public: + // Gets the singleton UnitTest object. The first time this method + // is called, a UnitTest object is constructed and returned. + // Consecutive calls will return the same object. + static UnitTest* GetInstance(); + + // Runs all tests in this UnitTest object and prints the result. + // Returns 0 if successful, or 1 otherwise. + // + // This method can only be called from the main thread. + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + int Run() GTEST_MUST_USE_RESULT_; + + // Returns the working directory when the first TEST() or TEST_F() + // was executed. The UnitTest object owns the string. + const char* original_working_dir() const; + + // Returns the TestCase object for the test that's currently running, + // or NULL if no test is running. + const TestCase* current_test_case() const; + + // Returns the TestInfo object for the test that's currently running, + // or NULL if no test is running. + const TestInfo* current_test_info() const; + + // Returns the random seed used at the start of the current test run. + int random_seed() const; + +#if GTEST_HAS_PARAM_TEST + // Returns the ParameterizedTestCaseRegistry object used to keep track of + // value-parameterized tests and instantiate and register them. + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + internal::ParameterizedTestCaseRegistry& parameterized_test_registry(); +#endif // GTEST_HAS_PARAM_TEST + + // Gets the number of successful test cases. + int successful_test_case_count() const; + + // Gets the number of failed test cases. + int failed_test_case_count() const; + + // Gets the number of all test cases. + int total_test_case_count() const; + + // Gets the number of all test cases that contain at least one test + // that should run. + int test_case_to_run_count() const; + + // Gets the number of successful tests. + int successful_test_count() const; + + // Gets the number of failed tests. + int failed_test_count() const; + + // Gets the number of disabled tests. + int disabled_test_count() const; + + // Gets the number of all tests. + int total_test_count() const; + + // Gets the number of tests that should run. + int test_to_run_count() const; + + // Gets the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const; + + // Returns true iff the unit test passed (i.e. all test cases passed). + bool Passed() const; + + // Returns true iff the unit test failed (i.e. some test case failed + // or something outside of all tests failed). + bool Failed() const; + + // Gets the i-th test case among all the test cases. i can range from 0 to + // total_test_case_count() - 1. If i is not in that range, returns NULL. + const TestCase* GetTestCase(int i) const; + + // Returns the list of event listeners that can be used to track events + // inside Google Test. + TestEventListeners& listeners(); + + private: + // Registers and returns a global test environment. When a test + // program is run, all global test environments will be set-up in + // the order they were registered. After all tests in the program + // have finished, all global test environments will be torn-down in + // the *reverse* order they were registered. + // + // The UnitTest object takes ownership of the given environment. + // + // This method can only be called from the main thread. + Environment* AddEnvironment(Environment* env); + + // Adds a TestPartResult to the current TestResult object. All + // Google Test assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc) + // eventually call this to report their results. The user code + // should use the assertion macros instead of calling this directly. + void AddTestPartResult(TestPartResult::Type result_type, + const char* file_name, + int line_number, + const internal::String& message, + const internal::String& os_stack_trace); + + // Adds a TestProperty to the current TestResult object. If the result already + // contains a property with the same key, the value will be updated. + void RecordPropertyForCurrentTest(const char* key, const char* value); + + // Gets the i-th test case among all the test cases. i can range from 0 to + // total_test_case_count() - 1. If i is not in that range, returns NULL. + TestCase* GetMutableTestCase(int i); + + // Accessors for the implementation object. + internal::UnitTestImpl* impl() { return impl_; } + const internal::UnitTestImpl* impl() const { return impl_; } + + // These classes and funcions are friends as they need to access private + // members of UnitTest. + friend class Test; + friend class internal::AssertHelper; + friend class internal::ScopedTrace; + friend Environment* AddGlobalTestEnvironment(Environment* env); + friend internal::UnitTestImpl* internal::GetUnitTestImpl(); + friend void internal::ReportFailureInUnknownLocation( + TestPartResult::Type result_type, + const internal::String& message); + + // Creates an empty UnitTest. + UnitTest(); + + // D'tor + virtual ~UnitTest(); + + // Pushes a trace defined by SCOPED_TRACE() on to the per-thread + // Google Test trace stack. + void PushGTestTrace(const internal::TraceInfo& trace); + + // Pops a trace from the per-thread Google Test trace stack. + void PopGTestTrace(); + + // Protects mutable state in *impl_. This is mutable as some const + // methods need to lock it too. + mutable internal::Mutex mutex_; + + // Opaque implementation object. This field is never changed once + // the object is constructed. We don't mark it as const here, as + // doing so will cause a warning in the constructor of UnitTest. + // Mutable state in *impl_ is protected by mutex_. + internal::UnitTestImpl* impl_; + + // We disallow copying UnitTest. + GTEST_DISALLOW_COPY_AND_ASSIGN_(UnitTest); +}; + +// A convenient wrapper for adding an environment for the test +// program. +// +// You should call this before RUN_ALL_TESTS() is called, probably in +// main(). If you use gtest_main, you need to call this before main() +// starts for it to take effect. For example, you can define a global +// variable like this: +// +// testing::Environment* const foo_env = +// testing::AddGlobalTestEnvironment(new FooEnvironment); +// +// However, we strongly recommend you to write your own main() and +// call AddGlobalTestEnvironment() there, as relying on initialization +// of global variables makes the code harder to read and may cause +// problems when you register multiple environments from different +// translation units and the environments have dependencies among them +// (remember that the compiler doesn't guarantee the order in which +// global variables from different translation units are initialized). +inline Environment* AddGlobalTestEnvironment(Environment* env) { + return UnitTest::GetInstance()->AddEnvironment(env); +} + +// Initializes Google Test. This must be called before calling +// RUN_ALL_TESTS(). In particular, it parses a command line for the +// flags that Google Test recognizes. Whenever a Google Test flag is +// seen, it is removed from argv, and *argc is decremented. +// +// No value is returned. Instead, the Google Test flag variables are +// updated. +// +// Calling the function for the second time has no user-visible effect. +GTEST_API_ void InitGoogleTest(int* argc, char** argv); + +// This overloaded version can be used in Windows programs compiled in +// UNICODE mode. +GTEST_API_ void InitGoogleTest(int* argc, wchar_t** argv); + +namespace internal { + +// These overloaded versions handle ::std::string and ::std::wstring. +GTEST_API_ inline String FormatForFailureMessage(const ::std::string& str) { + return (Message() << '"' << str << '"').GetString(); +} + +#if GTEST_HAS_STD_WSTRING +GTEST_API_ inline String FormatForFailureMessage(const ::std::wstring& wstr) { + return (Message() << "L\"" << wstr << '"').GetString(); +} +#endif // GTEST_HAS_STD_WSTRING + +// These overloaded versions handle ::string and ::wstring. +#if GTEST_HAS_GLOBAL_STRING +GTEST_API_ inline String FormatForFailureMessage(const ::string& str) { + return (Message() << '"' << str << '"').GetString(); +} +#endif // GTEST_HAS_GLOBAL_STRING + +#if GTEST_HAS_GLOBAL_WSTRING +GTEST_API_ inline String FormatForFailureMessage(const ::wstring& wstr) { + return (Message() << "L\"" << wstr << '"').GetString(); +} +#endif // GTEST_HAS_GLOBAL_WSTRING + +// Formats a comparison assertion (e.g. ASSERT_EQ, EXPECT_LT, and etc) +// operand to be used in a failure message. The type (but not value) +// of the other operand may affect the format. This allows us to +// print a char* as a raw pointer when it is compared against another +// char*, and print it as a C string when it is compared against an +// std::string object, for example. +// +// The default implementation ignores the type of the other operand. +// Some specialized versions are used to handle formatting wide or +// narrow C strings. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +template +String FormatForComparisonFailureMessage(const T1& value, + const T2& /* other_operand */) { + return FormatForFailureMessage(value); +} + +// The helper function for {ASSERT|EXPECT}_EQ. +template +AssertionResult CmpHelperEQ(const char* expected_expression, + const char* actual_expression, + const T1& expected, + const T2& actual) { +#ifdef _MSC_VER +#pragma warning(push) // Saves the current warning state. +#pragma warning(disable:4389) // Temporarily disables warning on + // signed/unsigned mismatch. +#endif + + if (expected == actual) { + return AssertionSuccess(); + } + +#ifdef _MSC_VER +#pragma warning(pop) // Restores the warning state. +#endif + + return EqFailure(expected_expression, + actual_expression, + FormatForComparisonFailureMessage(expected, actual), + FormatForComparisonFailureMessage(actual, expected), + false); +} + +// With this overloaded version, we allow anonymous enums to be used +// in {ASSERT|EXPECT}_EQ when compiled with gcc 4, as anonymous enums +// can be implicitly cast to BiggestInt. +GTEST_API_ AssertionResult CmpHelperEQ(const char* expected_expression, + const char* actual_expression, + BiggestInt expected, + BiggestInt actual); + +// The helper class for {ASSERT|EXPECT}_EQ. The template argument +// lhs_is_null_literal is true iff the first argument to ASSERT_EQ() +// is a null pointer literal. The following default implementation is +// for lhs_is_null_literal being false. +template +class EqHelper { + public: + // This templatized version is for the general case. + template + static AssertionResult Compare(const char* expected_expression, + const char* actual_expression, + const T1& expected, + const T2& actual) { + return CmpHelperEQ(expected_expression, actual_expression, expected, + actual); + } + + // With this overloaded version, we allow anonymous enums to be used + // in {ASSERT|EXPECT}_EQ when compiled with gcc 4, as anonymous + // enums can be implicitly cast to BiggestInt. + // + // Even though its body looks the same as the above version, we + // cannot merge the two, as it will make anonymous enums unhappy. + static AssertionResult Compare(const char* expected_expression, + const char* actual_expression, + BiggestInt expected, + BiggestInt actual) { + return CmpHelperEQ(expected_expression, actual_expression, expected, + actual); + } +}; + +// This specialization is used when the first argument to ASSERT_EQ() +// is a null pointer literal. +template <> +class EqHelper { + public: + // We define two overloaded versions of Compare(). The first + // version will be picked when the second argument to ASSERT_EQ() is + // NOT a pointer, e.g. ASSERT_EQ(0, AnIntFunction()) or + // EXPECT_EQ(false, a_bool). + template + static AssertionResult Compare(const char* expected_expression, + const char* actual_expression, + const T1& expected, + const T2& actual) { + return CmpHelperEQ(expected_expression, actual_expression, expected, + actual); + } + + // This version will be picked when the second argument to + // ASSERT_EQ() is a pointer, e.g. ASSERT_EQ(NULL, a_pointer). + template + static AssertionResult Compare(const char* expected_expression, + const char* actual_expression, + const T1& /* expected */, + T2* actual) { + // We already know that 'expected' is a null pointer. + return CmpHelperEQ(expected_expression, actual_expression, + static_cast(NULL), actual); + } +}; + +// A macro for implementing the helper functions needed to implement +// ASSERT_?? and EXPECT_??. It is here just to avoid copy-and-paste +// of similar code. +// +// For each templatized helper function, we also define an overloaded +// version for BiggestInt in order to reduce code bloat and allow +// anonymous enums to be used with {ASSERT|EXPECT}_?? when compiled +// with gcc 4. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +#define GTEST_IMPL_CMP_HELPER_(op_name, op)\ +template \ +AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \ + const T1& val1, const T2& val2) {\ + if (val1 op val2) {\ + return AssertionSuccess();\ + } else {\ + Message msg;\ + msg << "Expected: (" << expr1 << ") " #op " (" << expr2\ + << "), actual: " << FormatForComparisonFailureMessage(val1, val2)\ + << " vs " << FormatForComparisonFailureMessage(val2, val1);\ + return AssertionFailure(msg);\ + }\ +}\ +GTEST_API_ AssertionResult CmpHelper##op_name(\ + const char* expr1, const char* expr2, BiggestInt val1, BiggestInt val2) + +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + +// Implements the helper function for {ASSERT|EXPECT}_NE +GTEST_IMPL_CMP_HELPER_(NE, !=); +// Implements the helper function for {ASSERT|EXPECT}_LE +GTEST_IMPL_CMP_HELPER_(LE, <=); +// Implements the helper function for {ASSERT|EXPECT}_LT +GTEST_IMPL_CMP_HELPER_(LT, < ); +// Implements the helper function for {ASSERT|EXPECT}_GE +GTEST_IMPL_CMP_HELPER_(GE, >=); +// Implements the helper function for {ASSERT|EXPECT}_GT +GTEST_IMPL_CMP_HELPER_(GT, > ); + +#undef GTEST_IMPL_CMP_HELPER_ + +// The helper function for {ASSERT|EXPECT}_STREQ. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTREQ(const char* expected_expression, + const char* actual_expression, + const char* expected, + const char* actual); + +// The helper function for {ASSERT|EXPECT}_STRCASEEQ. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRCASEEQ(const char* expected_expression, + const char* actual_expression, + const char* expected, + const char* actual); + +// The helper function for {ASSERT|EXPECT}_STRNE. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2); + +// The helper function for {ASSERT|EXPECT}_STRCASENE. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRCASENE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2); + + +// Helper function for *_STREQ on wide strings. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTREQ(const char* expected_expression, + const char* actual_expression, + const wchar_t* expected, + const wchar_t* actual); + +// Helper function for *_STRNE on wide strings. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const wchar_t* s1, + const wchar_t* s2); + +} // namespace internal + +// IsSubstring() and IsNotSubstring() are intended to be used as the +// first argument to {EXPECT,ASSERT}_PRED_FORMAT2(), not by +// themselves. They check whether needle is a substring of haystack +// (NULL is considered a substring of itself only), and return an +// appropriate error message when they fail. +// +// The {needle,haystack}_expr arguments are the stringified +// expressions that generated the two real arguments. +GTEST_API_ AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack); +GTEST_API_ AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack); +GTEST_API_ AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack); +GTEST_API_ AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack); +GTEST_API_ AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack); +GTEST_API_ AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack); + +#if GTEST_HAS_STD_WSTRING +GTEST_API_ AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack); +GTEST_API_ AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack); +#endif // GTEST_HAS_STD_WSTRING + +namespace internal { + +// Helper template function for comparing floating-points. +// +// Template parameter: +// +// RawType: the raw floating-point type (either float or double) +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +template +AssertionResult CmpHelperFloatingPointEQ(const char* expected_expression, + const char* actual_expression, + RawType expected, + RawType actual) { + const FloatingPoint lhs(expected), rhs(actual); + + if (lhs.AlmostEquals(rhs)) { + return AssertionSuccess(); + } + + StrStream expected_ss; + expected_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << expected; + + StrStream actual_ss; + actual_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << actual; + + return EqFailure(expected_expression, + actual_expression, + StrStreamToString(&expected_ss), + StrStreamToString(&actual_ss), + false); +} + +// Helper function for implementing ASSERT_NEAR. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult DoubleNearPredFormat(const char* expr1, + const char* expr2, + const char* abs_error_expr, + double val1, + double val2, + double abs_error); + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// A class that enables one to stream messages to assertion macros +class GTEST_API_ AssertHelper { + public: + // Constructor. + AssertHelper(TestPartResult::Type type, + const char* file, + int line, + const char* message); + ~AssertHelper(); + + // Message assignment is a semantic trick to enable assertion + // streaming; see the GTEST_MESSAGE_ macro below. + void operator=(const Message& message) const; + + private: + // We put our data in a struct so that the size of the AssertHelper class can + // be as small as possible. This is important because gcc is incapable of + // re-using stack space even for temporary variables, so every EXPECT_EQ + // reserves stack space for another AssertHelper. + struct AssertHelperData { + AssertHelperData(TestPartResult::Type t, + const char* srcfile, + int line_num, + const char* msg) + : type(t), file(srcfile), line(line_num), message(msg) { } + + TestPartResult::Type const type; + const char* const file; + int const line; + String const message; + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(AssertHelperData); + }; + + AssertHelperData* const data_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(AssertHelper); +}; + +} // namespace internal + +#if GTEST_HAS_PARAM_TEST +// The abstract base class that all value-parameterized tests inherit from. +// +// This class adds support for accessing the test parameter value via +// the GetParam() method. +// +// Use it with one of the parameter generator defining functions, like Range(), +// Values(), ValuesIn(), Bool(), and Combine(). +// +// class FooTest : public ::testing::TestWithParam { +// protected: +// FooTest() { +// // Can use GetParam() here. +// } +// virtual ~FooTest() { +// // Can use GetParam() here. +// } +// virtual void SetUp() { +// // Can use GetParam() here. +// } +// virtual void TearDown { +// // Can use GetParam() here. +// } +// }; +// TEST_P(FooTest, DoesBar) { +// // Can use GetParam() method here. +// Foo foo; +// ASSERT_TRUE(foo.DoesBar(GetParam())); +// } +// INSTANTIATE_TEST_CASE_P(OneToTenRange, FooTest, ::testing::Range(1, 10)); + +template +class TestWithParam : public Test { + public: + typedef T ParamType; + + // The current parameter value. Is also available in the test fixture's + // constructor. + const ParamType& GetParam() const { return *parameter_; } + + private: + // Sets parameter value. The caller is responsible for making sure the value + // remains alive and unchanged throughout the current test. + static void SetParam(const ParamType* parameter) { + parameter_ = parameter; + } + + // Static value used for accessing parameter during a test lifetime. + static const ParamType* parameter_; + + // TestClass must be a subclass of TestWithParam. + template friend class internal::ParameterizedTestFactory; +}; + +template +const T* TestWithParam::parameter_ = NULL; + +#endif // GTEST_HAS_PARAM_TEST + +// Macros for indicating success/failure in test code. + +// ADD_FAILURE unconditionally adds a failure to the current test. +// SUCCEED generates a success - it doesn't automatically make the +// current test successful, as a test is only successful when it has +// no failure. +// +// EXPECT_* verifies that a certain condition is satisfied. If not, +// it behaves like ADD_FAILURE. In particular: +// +// EXPECT_TRUE verifies that a Boolean condition is true. +// EXPECT_FALSE verifies that a Boolean condition is false. +// +// FAIL and ASSERT_* are similar to ADD_FAILURE and EXPECT_*, except +// that they will also abort the current function on failure. People +// usually want the fail-fast behavior of FAIL and ASSERT_*, but those +// writing data-driven tests often find themselves using ADD_FAILURE +// and EXPECT_* more. +// +// Examples: +// +// EXPECT_TRUE(server.StatusIsOK()); +// ASSERT_FALSE(server.HasPendingRequest(port)) +// << "There are still pending requests " << "on port " << port; + +// Generates a nonfatal failure with a generic message. +#define ADD_FAILURE() GTEST_NONFATAL_FAILURE_("Failed") + +// Generates a fatal failure with a generic message. +#define GTEST_FAIL() GTEST_FATAL_FAILURE_("Failed") + +// Define this macro to 1 to omit the definition of FAIL(), which is a +// generic name and clashes with some other libraries. +#if !GTEST_DONT_DEFINE_FAIL +#define FAIL() GTEST_FAIL() +#endif + +// Generates a success with a generic message. +#define GTEST_SUCCEED() GTEST_SUCCESS_("Succeeded") + +// Define this macro to 1 to omit the definition of SUCCEED(), which +// is a generic name and clashes with some other libraries. +#if !GTEST_DONT_DEFINE_SUCCEED +#define SUCCEED() GTEST_SUCCEED() +#endif + +// Macros for testing exceptions. +// +// * {ASSERT|EXPECT}_THROW(statement, expected_exception): +// Tests that the statement throws the expected exception. +// * {ASSERT|EXPECT}_NO_THROW(statement): +// Tests that the statement doesn't throw any exception. +// * {ASSERT|EXPECT}_ANY_THROW(statement): +// Tests that the statement throws an exception. + +#define EXPECT_THROW(statement, expected_exception) \ + GTEST_TEST_THROW_(statement, expected_exception, GTEST_NONFATAL_FAILURE_) +#define EXPECT_NO_THROW(statement) \ + GTEST_TEST_NO_THROW_(statement, GTEST_NONFATAL_FAILURE_) +#define EXPECT_ANY_THROW(statement) \ + GTEST_TEST_ANY_THROW_(statement, GTEST_NONFATAL_FAILURE_) +#define ASSERT_THROW(statement, expected_exception) \ + GTEST_TEST_THROW_(statement, expected_exception, GTEST_FATAL_FAILURE_) +#define ASSERT_NO_THROW(statement) \ + GTEST_TEST_NO_THROW_(statement, GTEST_FATAL_FAILURE_) +#define ASSERT_ANY_THROW(statement) \ + GTEST_TEST_ANY_THROW_(statement, GTEST_FATAL_FAILURE_) + +// Boolean assertions. Condition can be either a Boolean expression or an +// AssertionResult. For more information on how to use AssertionResult with +// these macros see comments on that class. +#define EXPECT_TRUE(condition) \ + GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \ + GTEST_NONFATAL_FAILURE_) +#define EXPECT_FALSE(condition) \ + GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \ + GTEST_NONFATAL_FAILURE_) +#define ASSERT_TRUE(condition) \ + GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \ + GTEST_FATAL_FAILURE_) +#define ASSERT_FALSE(condition) \ + GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \ + GTEST_FATAL_FAILURE_) + +// Includes the auto-generated header that implements a family of +// generic predicate assertion macros. +#include + +// Macros for testing equalities and inequalities. +// +// * {ASSERT|EXPECT}_EQ(expected, actual): Tests that expected == actual +// * {ASSERT|EXPECT}_NE(v1, v2): Tests that v1 != v2 +// * {ASSERT|EXPECT}_LT(v1, v2): Tests that v1 < v2 +// * {ASSERT|EXPECT}_LE(v1, v2): Tests that v1 <= v2 +// * {ASSERT|EXPECT}_GT(v1, v2): Tests that v1 > v2 +// * {ASSERT|EXPECT}_GE(v1, v2): Tests that v1 >= v2 +// +// When they are not, Google Test prints both the tested expressions and +// their actual values. The values must be compatible built-in types, +// or you will get a compiler error. By "compatible" we mean that the +// values can be compared by the respective operator. +// +// Note: +// +// 1. It is possible to make a user-defined type work with +// {ASSERT|EXPECT}_??(), but that requires overloading the +// comparison operators and is thus discouraged by the Google C++ +// Usage Guide. Therefore, you are advised to use the +// {ASSERT|EXPECT}_TRUE() macro to assert that two objects are +// equal. +// +// 2. The {ASSERT|EXPECT}_??() macros do pointer comparisons on +// pointers (in particular, C strings). Therefore, if you use it +// with two C strings, you are testing how their locations in memory +// are related, not how their content is related. To compare two C +// strings by content, use {ASSERT|EXPECT}_STR*(). +// +// 3. {ASSERT|EXPECT}_EQ(expected, actual) is preferred to +// {ASSERT|EXPECT}_TRUE(expected == actual), as the former tells you +// what the actual value is when it fails, and similarly for the +// other comparisons. +// +// 4. Do not depend on the order in which {ASSERT|EXPECT}_??() +// evaluate their arguments, which is undefined. +// +// 5. These macros evaluate their arguments exactly once. +// +// Examples: +// +// EXPECT_NE(5, Foo()); +// EXPECT_EQ(NULL, a_pointer); +// ASSERT_LT(i, array_size); +// ASSERT_GT(records.size(), 0) << "There is no record left."; + +#define EXPECT_EQ(expected, actual) \ + EXPECT_PRED_FORMAT2(::testing::internal:: \ + EqHelper::Compare, \ + expected, actual) +#define EXPECT_NE(expected, actual) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperNE, expected, actual) +#define EXPECT_LE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2) +#define EXPECT_LT(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2) +#define EXPECT_GE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2) +#define EXPECT_GT(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) + +#define ASSERT_EQ(expected, actual) \ + ASSERT_PRED_FORMAT2(::testing::internal:: \ + EqHelper::Compare, \ + expected, actual) +#define ASSERT_NE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2) +#define ASSERT_LE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2) +#define ASSERT_LT(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2) +#define ASSERT_GE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2) +#define ASSERT_GT(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) + +// C String Comparisons. All tests treat NULL and any non-NULL string +// as different. Two NULLs are equal. +// +// * {ASSERT|EXPECT}_STREQ(s1, s2): Tests that s1 == s2 +// * {ASSERT|EXPECT}_STRNE(s1, s2): Tests that s1 != s2 +// * {ASSERT|EXPECT}_STRCASEEQ(s1, s2): Tests that s1 == s2, ignoring case +// * {ASSERT|EXPECT}_STRCASENE(s1, s2): Tests that s1 != s2, ignoring case +// +// For wide or narrow string objects, you can use the +// {ASSERT|EXPECT}_??() macros. +// +// Don't depend on the order in which the arguments are evaluated, +// which is undefined. +// +// These macros evaluate their arguments exactly once. + +#define EXPECT_STREQ(expected, actual) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, expected, actual) +#define EXPECT_STRNE(s1, s2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2) +#define EXPECT_STRCASEEQ(expected, actual) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, expected, actual) +#define EXPECT_STRCASENE(s1, s2)\ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2) + +#define ASSERT_STREQ(expected, actual) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, expected, actual) +#define ASSERT_STRNE(s1, s2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2) +#define ASSERT_STRCASEEQ(expected, actual) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, expected, actual) +#define ASSERT_STRCASENE(s1, s2)\ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2) + +// Macros for comparing floating-point numbers. +// +// * {ASSERT|EXPECT}_FLOAT_EQ(expected, actual): +// Tests that two float values are almost equal. +// * {ASSERT|EXPECT}_DOUBLE_EQ(expected, actual): +// Tests that two double values are almost equal. +// * {ASSERT|EXPECT}_NEAR(v1, v2, abs_error): +// Tests that v1 and v2 are within the given distance to each other. +// +// Google Test uses ULP-based comparison to automatically pick a default +// error bound that is appropriate for the operands. See the +// FloatingPoint template class in gtest-internal.h if you are +// interested in the implementation details. + +#define EXPECT_FLOAT_EQ(expected, actual)\ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + expected, actual) + +#define EXPECT_DOUBLE_EQ(expected, actual)\ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + expected, actual) + +#define ASSERT_FLOAT_EQ(expected, actual)\ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + expected, actual) + +#define ASSERT_DOUBLE_EQ(expected, actual)\ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + expected, actual) + +#define EXPECT_NEAR(val1, val2, abs_error)\ + EXPECT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, \ + val1, val2, abs_error) + +#define ASSERT_NEAR(val1, val2, abs_error)\ + ASSERT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, \ + val1, val2, abs_error) + +// These predicate format functions work on floating-point values, and +// can be used in {ASSERT|EXPECT}_PRED_FORMAT2*(), e.g. +// +// EXPECT_PRED_FORMAT2(testing::DoubleLE, Foo(), 5.0); + +// Asserts that val1 is less than, or almost equal to, val2. Fails +// otherwise. In particular, it fails if either val1 or val2 is NaN. +GTEST_API_ AssertionResult FloatLE(const char* expr1, const char* expr2, + float val1, float val2); +GTEST_API_ AssertionResult DoubleLE(const char* expr1, const char* expr2, + double val1, double val2); + + +#if GTEST_OS_WINDOWS + +// Macros that test for HRESULT failure and success, these are only useful +// on Windows, and rely on Windows SDK macros and APIs to compile. +// +// * {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED}(expr) +// +// When expr unexpectedly fails or succeeds, Google Test prints the +// expected result and the actual result with both a human-readable +// string representation of the error, if available, as well as the +// hex result code. +#define EXPECT_HRESULT_SUCCEEDED(expr) \ + EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr)) + +#define ASSERT_HRESULT_SUCCEEDED(expr) \ + ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr)) + +#define EXPECT_HRESULT_FAILED(expr) \ + EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr)) + +#define ASSERT_HRESULT_FAILED(expr) \ + ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr)) + +#endif // GTEST_OS_WINDOWS + +// Macros that execute statement and check that it doesn't generate new fatal +// failures in the current thread. +// +// * {ASSERT|EXPECT}_NO_FATAL_FAILURE(statement); +// +// Examples: +// +// EXPECT_NO_FATAL_FAILURE(Process()); +// ASSERT_NO_FATAL_FAILURE(Process()) << "Process() failed"; +// +#define ASSERT_NO_FATAL_FAILURE(statement) \ + GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_FATAL_FAILURE_) +#define EXPECT_NO_FATAL_FAILURE(statement) \ + GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_NONFATAL_FAILURE_) + +// Causes a trace (including the source file path, the current line +// number, and the given message) to be included in every test failure +// message generated by code in the current scope. The effect is +// undone when the control leaves the current scope. +// +// The message argument can be anything streamable to std::ostream. +// +// In the implementation, we include the current line number as part +// of the dummy variable name, thus allowing multiple SCOPED_TRACE()s +// to appear in the same block - as long as they are on different +// lines. +#define SCOPED_TRACE(message) \ + ::testing::internal::ScopedTrace GTEST_CONCAT_TOKEN_(gtest_trace_, __LINE__)(\ + __FILE__, __LINE__, ::testing::Message() << (message)) + +namespace internal { + +// This template is declared, but intentionally undefined. +template +struct StaticAssertTypeEqHelper; + +template +struct StaticAssertTypeEqHelper {}; + +} // namespace internal + +// Compile-time assertion for type equality. +// StaticAssertTypeEq() compiles iff type1 and type2 are +// the same type. The value it returns is not interesting. +// +// Instead of making StaticAssertTypeEq a class template, we make it a +// function template that invokes a helper class template. This +// prevents a user from misusing StaticAssertTypeEq by +// defining objects of that type. +// +// CAVEAT: +// +// When used inside a method of a class template, +// StaticAssertTypeEq() is effective ONLY IF the method is +// instantiated. For example, given: +// +// template class Foo { +// public: +// void Bar() { testing::StaticAssertTypeEq(); } +// }; +// +// the code: +// +// void Test1() { Foo foo; } +// +// will NOT generate a compiler error, as Foo::Bar() is never +// actually instantiated. Instead, you need: +// +// void Test2() { Foo foo; foo.Bar(); } +// +// to cause a compiler error. +template +bool StaticAssertTypeEq() { + internal::StaticAssertTypeEqHelper(); + return true; +} + +// Defines a test. +// +// The first parameter is the name of the test case, and the second +// parameter is the name of the test within the test case. +// +// The convention is to end the test case name with "Test". For +// example, a test case for the Foo class can be named FooTest. +// +// The user should put his test code between braces after using this +// macro. Example: +// +// TEST(FooTest, InitializesCorrectly) { +// Foo foo; +// EXPECT_TRUE(foo.StatusIsOK()); +// } + +// Note that we call GetTestTypeId() instead of GetTypeId< +// ::testing::Test>() here to get the type ID of testing::Test. This +// is to work around a suspected linker bug when using Google Test as +// a framework on Mac OS X. The bug causes GetTypeId< +// ::testing::Test>() to return different values depending on whether +// the call is from the Google Test framework itself or from user test +// code. GetTestTypeId() is guaranteed to always return the same +// value, as it always calls GetTypeId<>() from the Google Test +// framework. +#define GTEST_TEST(test_case_name, test_name)\ + GTEST_TEST_(test_case_name, test_name, \ + ::testing::Test, ::testing::internal::GetTestTypeId()) + +// Define this macro to 1 to omit the definition of TEST(), which +// is a generic name and clashes with some other libraries. +#if !GTEST_DONT_DEFINE_TEST +#define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name) +#endif + +// Defines a test that uses a test fixture. +// +// The first parameter is the name of the test fixture class, which +// also doubles as the test case name. The second parameter is the +// name of the test within the test case. +// +// A test fixture class must be declared earlier. The user should put +// his test code between braces after using this macro. Example: +// +// class FooTest : public testing::Test { +// protected: +// virtual void SetUp() { b_.AddElement(3); } +// +// Foo a_; +// Foo b_; +// }; +// +// TEST_F(FooTest, InitializesCorrectly) { +// EXPECT_TRUE(a_.StatusIsOK()); +// } +// +// TEST_F(FooTest, ReturnsElementCountCorrectly) { +// EXPECT_EQ(0, a_.size()); +// EXPECT_EQ(1, b_.size()); +// } + +#define TEST_F(test_fixture, test_name)\ + GTEST_TEST_(test_fixture, test_name, test_fixture, \ + ::testing::internal::GetTypeId()) + +// Use this macro in main() to run all tests. It returns 0 if all +// tests are successful, or 1 otherwise. +// +// RUN_ALL_TESTS() should be invoked after the command line has been +// parsed by InitGoogleTest(). + +#define RUN_ALL_TESTS()\ + (::testing::UnitTest::GetInstance()->Run()) + +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/gtest_pred_impl.h b/3rdparty/gmock/gtest/include/gtest/gtest_pred_impl.h new file mode 100644 index 00000000..e1e2f8c4 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/gtest_pred_impl.h @@ -0,0 +1,368 @@ +// Copyright 2006, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. + +// This file is AUTOMATICALLY GENERATED on 10/02/2008 by command +// 'gen_gtest_pred_impl.py 5'. DO NOT EDIT BY HAND! +// +// Implements a family of generic predicate assertion macros. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ + +// Makes sure this header is not included before gtest.h. +#ifndef GTEST_INCLUDE_GTEST_GTEST_H_ +#error Do not include gtest_pred_impl.h directly. Include gtest.h instead. +#endif // GTEST_INCLUDE_GTEST_GTEST_H_ + +// This header implements a family of generic predicate assertion +// macros: +// +// ASSERT_PRED_FORMAT1(pred_format, v1) +// ASSERT_PRED_FORMAT2(pred_format, v1, v2) +// ... +// +// where pred_format is a function or functor that takes n (in the +// case of ASSERT_PRED_FORMATn) values and their source expression +// text, and returns a testing::AssertionResult. See the definition +// of ASSERT_EQ in gtest.h for an example. +// +// If you don't care about formatting, you can use the more +// restrictive version: +// +// ASSERT_PRED1(pred, v1) +// ASSERT_PRED2(pred, v1, v2) +// ... +// +// where pred is an n-ary function or functor that returns bool, +// and the values v1, v2, ..., must support the << operator for +// streaming to std::ostream. +// +// We also define the EXPECT_* variations. +// +// For now we only support predicates whose arity is at most 5. +// Please email googletestframework@googlegroups.com if you need +// support for higher arities. + +// GTEST_ASSERT_ is the basic statement to which all of the assertions +// in this file reduce. Don't use this in your code. + +#define GTEST_ASSERT_(expression, on_failure) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const ::testing::AssertionResult gtest_ar = (expression)) \ + ; \ + else \ + on_failure(gtest_ar.failure_message()) + + +// Helper function for implementing {EXPECT|ASSERT}_PRED1. Don't use +// this in your code. +template +AssertionResult AssertPred1Helper(const char* pred_text, + const char* e1, + Pred pred, + const T1& v1) { + if (pred(v1)) return AssertionSuccess(); + + Message msg; + msg << pred_text << "(" + << e1 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1; + return AssertionFailure(msg); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT1. +// Don't use this in your code. +#define GTEST_PRED_FORMAT1_(pred_format, v1, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, v1),\ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED1. Don't use +// this in your code. +#define GTEST_PRED1_(pred, v1, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred1Helper(#pred, \ + #v1, \ + pred, \ + v1), on_failure) + +// Unary predicate assertion macros. +#define EXPECT_PRED_FORMAT1(pred_format, v1) \ + GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED1(pred, v1) \ + GTEST_PRED1_(pred, v1, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT1(pred_format, v1) \ + GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED1(pred, v1) \ + GTEST_PRED1_(pred, v1, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED2. Don't use +// this in your code. +template +AssertionResult AssertPred2Helper(const char* pred_text, + const char* e1, + const char* e2, + Pred pred, + const T1& v1, + const T2& v2) { + if (pred(v1, v2)) return AssertionSuccess(); + + Message msg; + msg << pred_text << "(" + << e1 << ", " + << e2 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2; + return AssertionFailure(msg); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT2. +// Don't use this in your code. +#define GTEST_PRED_FORMAT2_(pred_format, v1, v2, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, v1, v2),\ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED2. Don't use +// this in your code. +#define GTEST_PRED2_(pred, v1, v2, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred2Helper(#pred, \ + #v1, \ + #v2, \ + pred, \ + v1, \ + v2), on_failure) + +// Binary predicate assertion macros. +#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \ + GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED2(pred, v1, v2) \ + GTEST_PRED2_(pred, v1, v2, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT2(pred_format, v1, v2) \ + GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED2(pred, v1, v2) \ + GTEST_PRED2_(pred, v1, v2, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED3. Don't use +// this in your code. +template +AssertionResult AssertPred3Helper(const char* pred_text, + const char* e1, + const char* e2, + const char* e3, + Pred pred, + const T1& v1, + const T2& v2, + const T3& v3) { + if (pred(v1, v2, v3)) return AssertionSuccess(); + + Message msg; + msg << pred_text << "(" + << e1 << ", " + << e2 << ", " + << e3 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2 + << "\n" << e3 << " evaluates to " << v3; + return AssertionFailure(msg); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT3. +// Don't use this in your code. +#define GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, v1, v2, v3),\ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED3. Don't use +// this in your code. +#define GTEST_PRED3_(pred, v1, v2, v3, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred3Helper(#pred, \ + #v1, \ + #v2, \ + #v3, \ + pred, \ + v1, \ + v2, \ + v3), on_failure) + +// Ternary predicate assertion macros. +#define EXPECT_PRED_FORMAT3(pred_format, v1, v2, v3) \ + GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED3(pred, v1, v2, v3) \ + GTEST_PRED3_(pred, v1, v2, v3, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT3(pred_format, v1, v2, v3) \ + GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED3(pred, v1, v2, v3) \ + GTEST_PRED3_(pred, v1, v2, v3, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED4. Don't use +// this in your code. +template +AssertionResult AssertPred4Helper(const char* pred_text, + const char* e1, + const char* e2, + const char* e3, + const char* e4, + Pred pred, + const T1& v1, + const T2& v2, + const T3& v3, + const T4& v4) { + if (pred(v1, v2, v3, v4)) return AssertionSuccess(); + + Message msg; + msg << pred_text << "(" + << e1 << ", " + << e2 << ", " + << e3 << ", " + << e4 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2 + << "\n" << e3 << " evaluates to " << v3 + << "\n" << e4 << " evaluates to " << v4; + return AssertionFailure(msg); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT4. +// Don't use this in your code. +#define GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, v1, v2, v3, v4),\ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED4. Don't use +// this in your code. +#define GTEST_PRED4_(pred, v1, v2, v3, v4, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred4Helper(#pred, \ + #v1, \ + #v2, \ + #v3, \ + #v4, \ + pred, \ + v1, \ + v2, \ + v3, \ + v4), on_failure) + +// 4-ary predicate assertion macros. +#define EXPECT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \ + GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED4(pred, v1, v2, v3, v4) \ + GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \ + GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED4(pred, v1, v2, v3, v4) \ + GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED5. Don't use +// this in your code. +template +AssertionResult AssertPred5Helper(const char* pred_text, + const char* e1, + const char* e2, + const char* e3, + const char* e4, + const char* e5, + Pred pred, + const T1& v1, + const T2& v2, + const T3& v3, + const T4& v4, + const T5& v5) { + if (pred(v1, v2, v3, v4, v5)) return AssertionSuccess(); + + Message msg; + msg << pred_text << "(" + << e1 << ", " + << e2 << ", " + << e3 << ", " + << e4 << ", " + << e5 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2 + << "\n" << e3 << " evaluates to " << v3 + << "\n" << e4 << " evaluates to " << v4 + << "\n" << e5 << " evaluates to " << v5; + return AssertionFailure(msg); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT5. +// Don't use this in your code. +#define GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, #v5, v1, v2, v3, v4, v5),\ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED5. Don't use +// this in your code. +#define GTEST_PRED5_(pred, v1, v2, v3, v4, v5, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred5Helper(#pred, \ + #v1, \ + #v2, \ + #v3, \ + #v4, \ + #v5, \ + pred, \ + v1, \ + v2, \ + v3, \ + v4, \ + v5), on_failure) + +// 5-ary predicate assertion macros. +#define EXPECT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \ + GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED5(pred, v1, v2, v3, v4, v5) \ + GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \ + GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED5(pred, v1, v2, v3, v4, v5) \ + GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_) + + + +#endif // GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/gtest_prod.h b/3rdparty/gmock/gtest/include/gtest/gtest_prod.h new file mode 100644 index 00000000..da80ddc6 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/gtest_prod.h @@ -0,0 +1,58 @@ +// Copyright 2006, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) +// +// Google C++ Testing Framework definitions useful in production code. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_PROD_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PROD_H_ + +// When you need to test the private or protected members of a class, +// use the FRIEND_TEST macro to declare your tests as friends of the +// class. For example: +// +// class MyClass { +// private: +// void MyMethod(); +// FRIEND_TEST(MyClassTest, MyMethod); +// }; +// +// class MyClassTest : public testing::Test { +// // ... +// }; +// +// TEST_F(MyClassTest, MyMethod) { +// // Can call MyClass::MyMethod() here. +// } + +#define FRIEND_TEST(test_case_name, test_name)\ +friend class test_case_name##_##test_name##_Test + +#endif // GTEST_INCLUDE_GTEST_GTEST_PROD_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/internal/gtest-death-test-internal.h b/3rdparty/gmock/gtest/include/gtest/internal/gtest-death-test-internal.h new file mode 100644 index 00000000..e4330848 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/internal/gtest-death-test-internal.h @@ -0,0 +1,275 @@ +// Copyright 2005, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Authors: wan@google.com (Zhanyong Wan), eefacm@gmail.com (Sean Mcafee) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file defines internal utilities needed for implementing +// death tests. They are subject to change without notice. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ + +#include + +namespace testing { +namespace internal { + +GTEST_DECLARE_string_(internal_run_death_test); + +// Names of the flags (needed for parsing Google Test flags). +const char kDeathTestStyleFlag[] = "death_test_style"; +const char kDeathTestUseFork[] = "death_test_use_fork"; +const char kInternalRunDeathTestFlag[] = "internal_run_death_test"; + +#if GTEST_HAS_DEATH_TEST + +// DeathTest is a class that hides much of the complexity of the +// GTEST_DEATH_TEST_ macro. It is abstract; its static Create method +// returns a concrete class that depends on the prevailing death test +// style, as defined by the --gtest_death_test_style and/or +// --gtest_internal_run_death_test flags. + +// In describing the results of death tests, these terms are used with +// the corresponding definitions: +// +// exit status: The integer exit information in the format specified +// by wait(2) +// exit code: The integer code passed to exit(3), _exit(2), or +// returned from main() +class GTEST_API_ DeathTest { + public: + // Create returns false if there was an error determining the + // appropriate action to take for the current death test; for example, + // if the gtest_death_test_style flag is set to an invalid value. + // The LastMessage method will return a more detailed message in that + // case. Otherwise, the DeathTest pointer pointed to by the "test" + // argument is set. If the death test should be skipped, the pointer + // is set to NULL; otherwise, it is set to the address of a new concrete + // DeathTest object that controls the execution of the current test. + static bool Create(const char* statement, const RE* regex, + const char* file, int line, DeathTest** test); + DeathTest(); + virtual ~DeathTest() { } + + // A helper class that aborts a death test when it's deleted. + class ReturnSentinel { + public: + explicit ReturnSentinel(DeathTest* test) : test_(test) { } + ~ReturnSentinel() { test_->Abort(TEST_ENCOUNTERED_RETURN_STATEMENT); } + private: + DeathTest* const test_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(ReturnSentinel); + } GTEST_ATTRIBUTE_UNUSED_; + + // An enumeration of possible roles that may be taken when a death + // test is encountered. EXECUTE means that the death test logic should + // be executed immediately. OVERSEE means that the program should prepare + // the appropriate environment for a child process to execute the death + // test, then wait for it to complete. + enum TestRole { OVERSEE_TEST, EXECUTE_TEST }; + + // An enumeration of the two reasons that a test might be aborted. + enum AbortReason { TEST_ENCOUNTERED_RETURN_STATEMENT, TEST_DID_NOT_DIE }; + + // Assumes one of the above roles. + virtual TestRole AssumeRole() = 0; + + // Waits for the death test to finish and returns its status. + virtual int Wait() = 0; + + // Returns true if the death test passed; that is, the test process + // exited during the test, its exit status matches a user-supplied + // predicate, and its stderr output matches a user-supplied regular + // expression. + // The user-supplied predicate may be a macro expression rather + // than a function pointer or functor, or else Wait and Passed could + // be combined. + virtual bool Passed(bool exit_status_ok) = 0; + + // Signals that the death test did not die as expected. + virtual void Abort(AbortReason reason) = 0; + + // Returns a human-readable outcome message regarding the outcome of + // the last death test. + static const char* LastMessage(); + + static void set_last_death_test_message(const String& message); + + private: + // A string containing a description of the outcome of the last death test. + static String last_death_test_message_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(DeathTest); +}; + +// Factory interface for death tests. May be mocked out for testing. +class DeathTestFactory { + public: + virtual ~DeathTestFactory() { } + virtual bool Create(const char* statement, const RE* regex, + const char* file, int line, DeathTest** test) = 0; +}; + +// A concrete DeathTestFactory implementation for normal use. +class DefaultDeathTestFactory : public DeathTestFactory { + public: + virtual bool Create(const char* statement, const RE* regex, + const char* file, int line, DeathTest** test); +}; + +// Returns true if exit_status describes a process that was terminated +// by a signal, or exited normally with a nonzero exit code. +GTEST_API_ bool ExitedUnsuccessfully(int exit_status); + +// This macro is for implementing ASSERT_DEATH*, EXPECT_DEATH*, +// ASSERT_EXIT*, and EXPECT_EXIT*. +#define GTEST_DEATH_TEST_(statement, predicate, regex, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + const ::testing::internal::RE& gtest_regex = (regex); \ + ::testing::internal::DeathTest* gtest_dt; \ + if (!::testing::internal::DeathTest::Create(#statement, >est_regex, \ + __FILE__, __LINE__, >est_dt)) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \ + } \ + if (gtest_dt != NULL) { \ + ::testing::internal::scoped_ptr< ::testing::internal::DeathTest> \ + gtest_dt_ptr(gtest_dt); \ + switch (gtest_dt->AssumeRole()) { \ + case ::testing::internal::DeathTest::OVERSEE_TEST: \ + if (!gtest_dt->Passed(predicate(gtest_dt->Wait()))) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \ + } \ + break; \ + case ::testing::internal::DeathTest::EXECUTE_TEST: { \ + ::testing::internal::DeathTest::ReturnSentinel \ + gtest_sentinel(gtest_dt); \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + gtest_dt->Abort(::testing::internal::DeathTest::TEST_DID_NOT_DIE); \ + break; \ + } \ + } \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__): \ + fail(::testing::internal::DeathTest::LastMessage()) +// The symbol "fail" here expands to something into which a message +// can be streamed. + +// A class representing the parsed contents of the +// --gtest_internal_run_death_test flag, as it existed when +// RUN_ALL_TESTS was called. +class InternalRunDeathTestFlag { + public: + InternalRunDeathTestFlag(const String& a_file, + int a_line, + int an_index, + int a_write_fd) + : file_(a_file), line_(a_line), index_(an_index), + write_fd_(a_write_fd) {} + + ~InternalRunDeathTestFlag() { + if (write_fd_ >= 0) + posix::Close(write_fd_); + } + + String file() const { return file_; } + int line() const { return line_; } + int index() const { return index_; } + int write_fd() const { return write_fd_; } + + private: + String file_; + int line_; + int index_; + int write_fd_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(InternalRunDeathTestFlag); +}; + +// Returns a newly created InternalRunDeathTestFlag object with fields +// initialized from the GTEST_FLAG(internal_run_death_test) flag if +// the flag is specified; otherwise returns NULL. +InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag(); + +#else // GTEST_HAS_DEATH_TEST + +// This macro is used for implementing macros such as +// EXPECT_DEATH_IF_SUPPORTED and ASSERT_DEATH_IF_SUPPORTED on systems where +// death tests are not supported. Those macros must compile on such systems +// iff EXPECT_DEATH and ASSERT_DEATH compile with the same parameters on +// systems that support death tests. This allows one to write such a macro +// on a system that does not support death tests and be sure that it will +// compile on a death-test supporting system. +// +// Parameters: +// statement - A statement that a macro such as EXPECT_DEATH would test +// for program termination. This macro has to make sure this +// statement is compiled but not executed, to ensure that +// EXPECT_DEATH_IF_SUPPORTED compiles with a certain +// parameter iff EXPECT_DEATH compiles with it. +// regex - A regex that a macro such as EXPECT_DEATH would use to test +// the output of statement. This parameter has to be +// compiled but not evaluated by this macro, to ensure that +// this macro only accepts expressions that a macro such as +// EXPECT_DEATH would accept. +// terminator - Must be an empty statement for EXPECT_DEATH_IF_SUPPORTED +// and a return statement for ASSERT_DEATH_IF_SUPPORTED. +// This ensures that ASSERT_DEATH_IF_SUPPORTED will not +// compile inside functions where ASSERT_DEATH doesn't +// compile. +// +// The branch that has an always false condition is used to ensure that +// statement and regex are compiled (and thus syntactically correct) but +// never executed. The unreachable code macro protects the terminator +// statement from generating an 'unreachable code' warning in case +// statement unconditionally returns or throws. The Message constructor at +// the end allows the syntax of streaming additional messages into the +// macro, for compilational compatibility with EXPECT_DEATH/ASSERT_DEATH. +#define GTEST_UNSUPPORTED_DEATH_TEST_(statement, regex, terminator) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + GTEST_LOG_(WARNING) \ + << "Death tests are not supported on this platform.\n" \ + << "Statement '" #statement "' cannot be verified."; \ + } else if (::testing::internal::AlwaysFalse()) { \ + ::testing::internal::RE::PartialMatch(".*", (regex)); \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + terminator; \ + } else \ + ::testing::Message() + +#endif // GTEST_HAS_DEATH_TEST + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/internal/gtest-filepath.h b/3rdparty/gmock/gtest/include/gtest/internal/gtest-filepath.h new file mode 100644 index 00000000..4b76d795 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/internal/gtest-filepath.h @@ -0,0 +1,210 @@ +// Copyright 2008, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: keith.ray@gmail.com (Keith Ray) +// +// Google Test filepath utilities +// +// This header file declares classes and functions used internally by +// Google Test. They are subject to change without notice. +// +// This file is #included in . +// Do not include this header file separately! + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ + +#include + +namespace testing { +namespace internal { + +// FilePath - a class for file and directory pathname manipulation which +// handles platform-specific conventions (like the pathname separator). +// Used for helper functions for naming files in a directory for xml output. +// Except for Set methods, all methods are const or static, which provides an +// "immutable value object" -- useful for peace of mind. +// A FilePath with a value ending in a path separator ("like/this/") represents +// a directory, otherwise it is assumed to represent a file. In either case, +// it may or may not represent an actual file or directory in the file system. +// Names are NOT checked for syntax correctness -- no checking for illegal +// characters, malformed paths, etc. + +class GTEST_API_ FilePath { + public: + FilePath() : pathname_("") { } + FilePath(const FilePath& rhs) : pathname_(rhs.pathname_) { } + + explicit FilePath(const char* pathname) : pathname_(pathname) { + Normalize(); + } + + explicit FilePath(const String& pathname) : pathname_(pathname) { + Normalize(); + } + + FilePath& operator=(const FilePath& rhs) { + Set(rhs); + return *this; + } + + void Set(const FilePath& rhs) { + pathname_ = rhs.pathname_; + } + + String ToString() const { return pathname_; } + const char* c_str() const { return pathname_.c_str(); } + + // Returns the current working directory, or "" if unsuccessful. + static FilePath GetCurrentDir(); + + // Given directory = "dir", base_name = "test", number = 0, + // extension = "xml", returns "dir/test.xml". If number is greater + // than zero (e.g., 12), returns "dir/test_12.xml". + // On Windows platform, uses \ as the separator rather than /. + static FilePath MakeFileName(const FilePath& directory, + const FilePath& base_name, + int number, + const char* extension); + + // Given directory = "dir", relative_path = "test.xml", + // returns "dir/test.xml". + // On Windows, uses \ as the separator rather than /. + static FilePath ConcatPaths(const FilePath& directory, + const FilePath& relative_path); + + // Returns a pathname for a file that does not currently exist. The pathname + // will be directory/base_name.extension or + // directory/base_name_.extension if directory/base_name.extension + // already exists. The number will be incremented until a pathname is found + // that does not already exist. + // Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'. + // There could be a race condition if two or more processes are calling this + // function at the same time -- they could both pick the same filename. + static FilePath GenerateUniqueFileName(const FilePath& directory, + const FilePath& base_name, + const char* extension); + + // Returns true iff the path is NULL or "". + bool IsEmpty() const { return c_str() == NULL || *c_str() == '\0'; } + + // If input name has a trailing separator character, removes it and returns + // the name, otherwise return the name string unmodified. + // On Windows platform, uses \ as the separator, other platforms use /. + FilePath RemoveTrailingPathSeparator() const; + + // Returns a copy of the FilePath with the directory part removed. + // Example: FilePath("path/to/file").RemoveDirectoryName() returns + // FilePath("file"). If there is no directory part ("just_a_file"), it returns + // the FilePath unmodified. If there is no file part ("just_a_dir/") it + // returns an empty FilePath (""). + // On Windows platform, '\' is the path separator, otherwise it is '/'. + FilePath RemoveDirectoryName() const; + + // RemoveFileName returns the directory path with the filename removed. + // Example: FilePath("path/to/file").RemoveFileName() returns "path/to/". + // If the FilePath is "a_file" or "/a_file", RemoveFileName returns + // FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does + // not have a file, like "just/a/dir/", it returns the FilePath unmodified. + // On Windows platform, '\' is the path separator, otherwise it is '/'. + FilePath RemoveFileName() const; + + // Returns a copy of the FilePath with the case-insensitive extension removed. + // Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns + // FilePath("dir/file"). If a case-insensitive extension is not + // found, returns a copy of the original FilePath. + FilePath RemoveExtension(const char* extension) const; + + // Creates directories so that path exists. Returns true if successful or if + // the directories already exist; returns false if unable to create + // directories for any reason. Will also return false if the FilePath does + // not represent a directory (that is, it doesn't end with a path separator). + bool CreateDirectoriesRecursively() const; + + // Create the directory so that path exists. Returns true if successful or + // if the directory already exists; returns false if unable to create the + // directory for any reason, including if the parent directory does not + // exist. Not named "CreateDirectory" because that's a macro on Windows. + bool CreateFolder() const; + + // Returns true if FilePath describes something in the file-system, + // either a file, directory, or whatever, and that something exists. + bool FileOrDirectoryExists() const; + + // Returns true if pathname describes a directory in the file-system + // that exists. + bool DirectoryExists() const; + + // Returns true if FilePath ends with a path separator, which indicates that + // it is intended to represent a directory. Returns false otherwise. + // This does NOT check that a directory (or file) actually exists. + bool IsDirectory() const; + + // Returns true if pathname describes a root directory. (Windows has one + // root directory per disk drive.) + bool IsRootDirectory() const; + + // Returns true if pathname describes an absolute path. + bool IsAbsolutePath() const; + + private: + // Replaces multiple consecutive separators with a single separator. + // For example, "bar///foo" becomes "bar/foo". Does not eliminate other + // redundancies that might be in a pathname involving "." or "..". + // + // A pathname with multiple consecutive separators may occur either through + // user error or as a result of some scripts or APIs that generate a pathname + // with a trailing separator. On other platforms the same API or script + // may NOT generate a pathname with a trailing "/". Then elsewhere that + // pathname may have another "/" and pathname components added to it, + // without checking for the separator already being there. + // The script language and operating system may allow paths like "foo//bar" + // but some of the functions in FilePath will not handle that correctly. In + // particular, RemoveTrailingPathSeparator() only removes one separator, and + // it is called in CreateDirectoriesRecursively() assuming that it will change + // a pathname from directory syntax (trailing separator) to filename syntax. + // + // On Windows this method also replaces the alternate path separator '/' with + // the primary path separator '\\', so that for example "bar\\/\\foo" becomes + // "bar\\foo". + + void Normalize(); + + // Returns a pointer to the last occurence of a valid path separator in + // the FilePath. On Windows, for example, both '/' and '\' are valid path + // separators. Returns NULL if no path separator was found. + const char* FindLastPathSeparator() const; + + String pathname_; +}; // class FilePath + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/internal/gtest-internal.h b/3rdparty/gmock/gtest/include/gtest/internal/gtest-internal.h new file mode 100644 index 00000000..31a66e99 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/internal/gtest-internal.h @@ -0,0 +1,923 @@ +// Copyright 2005, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Authors: wan@google.com (Zhanyong Wan), eefacm@gmail.com (Sean Mcafee) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file declares functions and macros used internally by +// Google Test. They are subject to change without notice. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ + +#include + +#if GTEST_OS_LINUX +#include +#include +#include +#include +#endif // GTEST_OS_LINUX + +#include +#include +#include +#include +#include + +#include +#include +#include + +// Due to C++ preprocessor weirdness, we need double indirection to +// concatenate two tokens when one of them is __LINE__. Writing +// +// foo ## __LINE__ +// +// will result in the token foo__LINE__, instead of foo followed by +// the current line number. For more details, see +// http://www.parashift.com/c++-faq-lite/misc-technical-issues.html#faq-39.6 +#define GTEST_CONCAT_TOKEN_(foo, bar) GTEST_CONCAT_TOKEN_IMPL_(foo, bar) +#define GTEST_CONCAT_TOKEN_IMPL_(foo, bar) foo ## bar + +// Google Test defines the testing::Message class to allow construction of +// test messages via the << operator. The idea is that anything +// streamable to std::ostream can be streamed to a testing::Message. +// This allows a user to use his own types in Google Test assertions by +// overloading the << operator. +// +// util/gtl/stl_logging-inl.h overloads << for STL containers. These +// overloads cannot be defined in the std namespace, as that will be +// undefined behavior. Therefore, they are defined in the global +// namespace instead. +// +// C++'s symbol lookup rule (i.e. Koenig lookup) says that these +// overloads are visible in either the std namespace or the global +// namespace, but not other namespaces, including the testing +// namespace which Google Test's Message class is in. +// +// To allow STL containers (and other types that has a << operator +// defined in the global namespace) to be used in Google Test assertions, +// testing::Message must access the custom << operator from the global +// namespace. Hence this helper function. +// +// Note: Jeffrey Yasskin suggested an alternative fix by "using +// ::operator<<;" in the definition of Message's operator<<. That fix +// doesn't require a helper function, but unfortunately doesn't +// compile with MSVC. +template +inline void GTestStreamToHelper(std::ostream* os, const T& val) { + *os << val; +} + +namespace testing { + +// Forward declaration of classes. + +class AssertionResult; // Result of an assertion. +class Message; // Represents a failure message. +class Test; // Represents a test. +class TestInfo; // Information about a test. +class TestPartResult; // Result of a test part. +class UnitTest; // A collection of test cases. + +namespace internal { + +struct TraceInfo; // Information about a trace point. +class ScopedTrace; // Implements scoped trace. +class TestInfoImpl; // Opaque implementation of TestInfo +class UnitTestImpl; // Opaque implementation of UnitTest + +// How many times InitGoogleTest() has been called. +extern int g_init_gtest_count; + +// The text used in failure messages to indicate the start of the +// stack trace. +GTEST_API_ extern const char kStackTraceMarker[]; + +// A secret type that Google Test users don't know about. It has no +// definition on purpose. Therefore it's impossible to create a +// Secret object, which is what we want. +class Secret; + +// Two overloaded helpers for checking at compile time whether an +// expression is a null pointer literal (i.e. NULL or any 0-valued +// compile-time integral constant). Their return values have +// different sizes, so we can use sizeof() to test which version is +// picked by the compiler. These helpers have no implementations, as +// we only need their signatures. +// +// Given IsNullLiteralHelper(x), the compiler will pick the first +// version if x can be implicitly converted to Secret*, and pick the +// second version otherwise. Since Secret is a secret and incomplete +// type, the only expression a user can write that has type Secret* is +// a null pointer literal. Therefore, we know that x is a null +// pointer literal if and only if the first version is picked by the +// compiler. +char IsNullLiteralHelper(Secret* p); +char (&IsNullLiteralHelper(...))[2]; // NOLINT + +// A compile-time bool constant that is true if and only if x is a +// null pointer literal (i.e. NULL or any 0-valued compile-time +// integral constant). +#ifdef GTEST_ELLIPSIS_NEEDS_POD_ +// We lose support for NULL detection where the compiler doesn't like +// passing non-POD classes through ellipsis (...). +#define GTEST_IS_NULL_LITERAL_(x) false +#else +#define GTEST_IS_NULL_LITERAL_(x) \ + (sizeof(::testing::internal::IsNullLiteralHelper(x)) == 1) +#endif // GTEST_ELLIPSIS_NEEDS_POD_ + +// Appends the user-supplied message to the Google-Test-generated message. +GTEST_API_ String AppendUserMessage(const String& gtest_msg, + const Message& user_msg); + +// A helper class for creating scoped traces in user programs. +class GTEST_API_ ScopedTrace { + public: + // The c'tor pushes the given source file location and message onto + // a trace stack maintained by Google Test. + ScopedTrace(const char* file, int line, const Message& message); + + // The d'tor pops the info pushed by the c'tor. + // + // Note that the d'tor is not virtual in order to be efficient. + // Don't inherit from ScopedTrace! + ~ScopedTrace(); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedTrace); +} GTEST_ATTRIBUTE_UNUSED_; // A ScopedTrace object does its job in its + // c'tor and d'tor. Therefore it doesn't + // need to be used otherwise. + +// Converts a streamable value to a String. A NULL pointer is +// converted to "(null)". When the input value is a ::string, +// ::std::string, ::wstring, or ::std::wstring object, each NUL +// character in it is replaced with "\\0". +// Declared here but defined in gtest.h, so that it has access +// to the definition of the Message class, required by the ARM +// compiler. +template +String StreamableToString(const T& streamable); + +// Formats a value to be used in a failure message. + +#ifdef GTEST_NEEDS_IS_POINTER_ + +// These are needed as the Nokia Symbian and IBM XL C/C++ compilers +// cannot decide between const T& and const T* in a function template. +// These compilers _can_ decide between class template specializations +// for T and T*, so a tr1::type_traits-like is_pointer works, and we +// can overload on that. + +// This overload makes sure that all pointers (including +// those to char or wchar_t) are printed as raw pointers. +template +inline String FormatValueForFailureMessage(internal::true_type /*dummy*/, + T* pointer) { + return StreamableToString(static_cast(pointer)); +} + +template +inline String FormatValueForFailureMessage(internal::false_type /*dummy*/, + const T& value) { + return StreamableToString(value); +} + +template +inline String FormatForFailureMessage(const T& value) { + return FormatValueForFailureMessage( + typename internal::is_pointer::type(), value); +} + +#else + +// These are needed as the above solution using is_pointer has the +// limitation that T cannot be a type without external linkage, when +// compiled using MSVC. + +template +inline String FormatForFailureMessage(const T& value) { + return StreamableToString(value); +} + +// This overload makes sure that all pointers (including +// those to char or wchar_t) are printed as raw pointers. +template +inline String FormatForFailureMessage(T* pointer) { + return StreamableToString(static_cast(pointer)); +} + +#endif // GTEST_NEEDS_IS_POINTER_ + +// These overloaded versions handle narrow and wide characters. +GTEST_API_ String FormatForFailureMessage(char ch); +GTEST_API_ String FormatForFailureMessage(wchar_t wchar); + +// When this operand is a const char* or char*, and the other operand +// is a ::std::string or ::string, we print this operand as a C string +// rather than a pointer. We do the same for wide strings. + +// This internal macro is used to avoid duplicated code. +#define GTEST_FORMAT_IMPL_(operand2_type, operand1_printer)\ +inline String FormatForComparisonFailureMessage(\ + operand2_type::value_type* str, const operand2_type& /*operand2*/) {\ + return operand1_printer(str);\ +}\ +inline String FormatForComparisonFailureMessage(\ + const operand2_type::value_type* str, const operand2_type& /*operand2*/) {\ + return operand1_printer(str);\ +} + +GTEST_FORMAT_IMPL_(::std::string, String::ShowCStringQuoted) +#if GTEST_HAS_STD_WSTRING +GTEST_FORMAT_IMPL_(::std::wstring, String::ShowWideCStringQuoted) +#endif // GTEST_HAS_STD_WSTRING + +#if GTEST_HAS_GLOBAL_STRING +GTEST_FORMAT_IMPL_(::string, String::ShowCStringQuoted) +#endif // GTEST_HAS_GLOBAL_STRING +#if GTEST_HAS_GLOBAL_WSTRING +GTEST_FORMAT_IMPL_(::wstring, String::ShowWideCStringQuoted) +#endif // GTEST_HAS_GLOBAL_WSTRING + +#undef GTEST_FORMAT_IMPL_ + +// Constructs and returns the message for an equality assertion +// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure. +// +// The first four parameters are the expressions used in the assertion +// and their values, as strings. For example, for ASSERT_EQ(foo, bar) +// where foo is 5 and bar is 6, we have: +// +// expected_expression: "foo" +// actual_expression: "bar" +// expected_value: "5" +// actual_value: "6" +// +// The ignoring_case parameter is true iff the assertion is a +// *_STRCASEEQ*. When it's true, the string " (ignoring case)" will +// be inserted into the message. +GTEST_API_ AssertionResult EqFailure(const char* expected_expression, + const char* actual_expression, + const String& expected_value, + const String& actual_value, + bool ignoring_case); + +// Constructs a failure message for Boolean assertions such as EXPECT_TRUE. +GTEST_API_ String GetBoolAssertionFailureMessage( + const AssertionResult& assertion_result, + const char* expression_text, + const char* actual_predicate_value, + const char* expected_predicate_value); + +// This template class represents an IEEE floating-point number +// (either single-precision or double-precision, depending on the +// template parameters). +// +// The purpose of this class is to do more sophisticated number +// comparison. (Due to round-off error, etc, it's very unlikely that +// two floating-points will be equal exactly. Hence a naive +// comparison by the == operation often doesn't work.) +// +// Format of IEEE floating-point: +// +// The most-significant bit being the leftmost, an IEEE +// floating-point looks like +// +// sign_bit exponent_bits fraction_bits +// +// Here, sign_bit is a single bit that designates the sign of the +// number. +// +// For float, there are 8 exponent bits and 23 fraction bits. +// +// For double, there are 11 exponent bits and 52 fraction bits. +// +// More details can be found at +// http://en.wikipedia.org/wiki/IEEE_floating-point_standard. +// +// Template parameter: +// +// RawType: the raw floating-point type (either float or double) +template +class FloatingPoint { + public: + // Defines the unsigned integer type that has the same size as the + // floating point number. + typedef typename TypeWithSize::UInt Bits; + + // Constants. + + // # of bits in a number. + static const size_t kBitCount = 8*sizeof(RawType); + + // # of fraction bits in a number. + static const size_t kFractionBitCount = + std::numeric_limits::digits - 1; + + // # of exponent bits in a number. + static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount; + + // The mask for the sign bit. + static const Bits kSignBitMask = static_cast(1) << (kBitCount - 1); + + // The mask for the fraction bits. + static const Bits kFractionBitMask = + ~static_cast(0) >> (kExponentBitCount + 1); + + // The mask for the exponent bits. + static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask); + + // How many ULP's (Units in the Last Place) we want to tolerate when + // comparing two numbers. The larger the value, the more error we + // allow. A 0 value means that two numbers must be exactly the same + // to be considered equal. + // + // The maximum error of a single floating-point operation is 0.5 + // units in the last place. On Intel CPU's, all floating-point + // calculations are done with 80-bit precision, while double has 64 + // bits. Therefore, 4 should be enough for ordinary use. + // + // See the following article for more details on ULP: + // http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm. + static const size_t kMaxUlps = 4; + + // Constructs a FloatingPoint from a raw floating-point number. + // + // On an Intel CPU, passing a non-normalized NAN (Not a Number) + // around may change its bits, although the new value is guaranteed + // to be also a NAN. Therefore, don't expect this constructor to + // preserve the bits in x when x is a NAN. + explicit FloatingPoint(const RawType& x) { u_.value_ = x; } + + // Static methods + + // Reinterprets a bit pattern as a floating-point number. + // + // This function is needed to test the AlmostEquals() method. + static RawType ReinterpretBits(const Bits bits) { + FloatingPoint fp(0); + fp.u_.bits_ = bits; + return fp.u_.value_; + } + + // Returns the floating-point number that represent positive infinity. + static RawType Infinity() { + return ReinterpretBits(kExponentBitMask); + } + + // Non-static methods + + // Returns the bits that represents this number. + const Bits &bits() const { return u_.bits_; } + + // Returns the exponent bits of this number. + Bits exponent_bits() const { return kExponentBitMask & u_.bits_; } + + // Returns the fraction bits of this number. + Bits fraction_bits() const { return kFractionBitMask & u_.bits_; } + + // Returns the sign bit of this number. + Bits sign_bit() const { return kSignBitMask & u_.bits_; } + + // Returns true iff this is NAN (not a number). + bool is_nan() const { + // It's a NAN if the exponent bits are all ones and the fraction + // bits are not entirely zeros. + return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0); + } + + // Returns true iff this number is at most kMaxUlps ULP's away from + // rhs. In particular, this function: + // + // - returns false if either number is (or both are) NAN. + // - treats really large numbers as almost equal to infinity. + // - thinks +0.0 and -0.0 are 0 DLP's apart. + bool AlmostEquals(const FloatingPoint& rhs) const { + // The IEEE standard says that any comparison operation involving + // a NAN must return false. + if (is_nan() || rhs.is_nan()) return false; + + return DistanceBetweenSignAndMagnitudeNumbers(u_.bits_, rhs.u_.bits_) + <= kMaxUlps; + } + + private: + // The data type used to store the actual floating-point number. + union FloatingPointUnion { + RawType value_; // The raw floating-point number. + Bits bits_; // The bits that represent the number. + }; + + // Converts an integer from the sign-and-magnitude representation to + // the biased representation. More precisely, let N be 2 to the + // power of (kBitCount - 1), an integer x is represented by the + // unsigned number x + N. + // + // For instance, + // + // -N + 1 (the most negative number representable using + // sign-and-magnitude) is represented by 1; + // 0 is represented by N; and + // N - 1 (the biggest number representable using + // sign-and-magnitude) is represented by 2N - 1. + // + // Read http://en.wikipedia.org/wiki/Signed_number_representations + // for more details on signed number representations. + static Bits SignAndMagnitudeToBiased(const Bits &sam) { + if (kSignBitMask & sam) { + // sam represents a negative number. + return ~sam + 1; + } else { + // sam represents a positive number. + return kSignBitMask | sam; + } + } + + // Given two numbers in the sign-and-magnitude representation, + // returns the distance between them as an unsigned number. + static Bits DistanceBetweenSignAndMagnitudeNumbers(const Bits &sam1, + const Bits &sam2) { + const Bits biased1 = SignAndMagnitudeToBiased(sam1); + const Bits biased2 = SignAndMagnitudeToBiased(sam2); + return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1); + } + + FloatingPointUnion u_; +}; + +// Typedefs the instances of the FloatingPoint template class that we +// care to use. +typedef FloatingPoint Float; +typedef FloatingPoint Double; + +// In order to catch the mistake of putting tests that use different +// test fixture classes in the same test case, we need to assign +// unique IDs to fixture classes and compare them. The TypeId type is +// used to hold such IDs. The user should treat TypeId as an opaque +// type: the only operation allowed on TypeId values is to compare +// them for equality using the == operator. +typedef const void* TypeId; + +template +class TypeIdHelper { + public: + // dummy_ must not have a const type. Otherwise an overly eager + // compiler (e.g. MSVC 7.1 & 8.0) may try to merge + // TypeIdHelper::dummy_ for different Ts as an "optimization". + static bool dummy_; +}; + +template +bool TypeIdHelper::dummy_ = false; + +// GetTypeId() returns the ID of type T. Different values will be +// returned for different types. Calling the function twice with the +// same type argument is guaranteed to return the same ID. +template +TypeId GetTypeId() { + // The compiler is required to allocate a different + // TypeIdHelper::dummy_ variable for each T used to instantiate + // the template. Therefore, the address of dummy_ is guaranteed to + // be unique. + return &(TypeIdHelper::dummy_); +} + +// Returns the type ID of ::testing::Test. Always call this instead +// of GetTypeId< ::testing::Test>() to get the type ID of +// ::testing::Test, as the latter may give the wrong result due to a +// suspected linker bug when compiling Google Test as a Mac OS X +// framework. +GTEST_API_ TypeId GetTestTypeId(); + +// Defines the abstract factory interface that creates instances +// of a Test object. +class TestFactoryBase { + public: + virtual ~TestFactoryBase() {} + + // Creates a test instance to run. The instance is both created and destroyed + // within TestInfoImpl::Run() + virtual Test* CreateTest() = 0; + + protected: + TestFactoryBase() {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestFactoryBase); +}; + +// This class provides implementation of TeastFactoryBase interface. +// It is used in TEST and TEST_F macros. +template +class TestFactoryImpl : public TestFactoryBase { + public: + virtual Test* CreateTest() { return new TestClass; } +}; + +#if GTEST_OS_WINDOWS + +// Predicate-formatters for implementing the HRESULT checking macros +// {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED} +// We pass a long instead of HRESULT to avoid causing an +// include dependency for the HRESULT type. +GTEST_API_ AssertionResult IsHRESULTSuccess(const char* expr, + long hr); // NOLINT +GTEST_API_ AssertionResult IsHRESULTFailure(const char* expr, + long hr); // NOLINT + +#endif // GTEST_OS_WINDOWS + +// Formats a source file path and a line number as they would appear +// in a compiler error message. +inline String FormatFileLocation(const char* file, int line) { + const char* const file_name = file == NULL ? "unknown file" : file; + if (line < 0) { + return String::Format("%s:", file_name); + } +#ifdef _MSC_VER + return String::Format("%s(%d):", file_name, line); +#else + return String::Format("%s:%d:", file_name, line); +#endif // _MSC_VER +} + +// Types of SetUpTestCase() and TearDownTestCase() functions. +typedef void (*SetUpTestCaseFunc)(); +typedef void (*TearDownTestCaseFunc)(); + +// Creates a new TestInfo object and registers it with Google Test; +// returns the created object. +// +// Arguments: +// +// test_case_name: name of the test case +// name: name of the test +// test_case_comment: a comment on the test case that will be included in +// the test output +// comment: a comment on the test that will be included in the +// test output +// fixture_class_id: ID of the test fixture class +// set_up_tc: pointer to the function that sets up the test case +// tear_down_tc: pointer to the function that tears down the test case +// factory: pointer to the factory that creates a test object. +// The newly created TestInfo instance will assume +// ownership of the factory object. +GTEST_API_ TestInfo* MakeAndRegisterTestInfo( + const char* test_case_name, const char* name, + const char* test_case_comment, const char* comment, + TypeId fixture_class_id, + SetUpTestCaseFunc set_up_tc, + TearDownTestCaseFunc tear_down_tc, + TestFactoryBase* factory); + +// If *pstr starts with the given prefix, modifies *pstr to be right +// past the prefix and returns true; otherwise leaves *pstr unchanged +// and returns false. None of pstr, *pstr, and prefix can be NULL. +bool SkipPrefix(const char* prefix, const char** pstr); + +#if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +// State of the definition of a type-parameterized test case. +class GTEST_API_ TypedTestCasePState { + public: + TypedTestCasePState() : registered_(false) {} + + // Adds the given test name to defined_test_names_ and return true + // if the test case hasn't been registered; otherwise aborts the + // program. + bool AddTestName(const char* file, int line, const char* case_name, + const char* test_name) { + if (registered_) { + fprintf(stderr, "%s Test %s must be defined before " + "REGISTER_TYPED_TEST_CASE_P(%s, ...).\n", + FormatFileLocation(file, line).c_str(), test_name, case_name); + fflush(stderr); + posix::Abort(); + } + defined_test_names_.insert(test_name); + return true; + } + + // Verifies that registered_tests match the test names in + // defined_test_names_; returns registered_tests if successful, or + // aborts the program otherwise. + const char* VerifyRegisteredTestNames( + const char* file, int line, const char* registered_tests); + + private: + bool registered_; + ::std::set defined_test_names_; +}; + +// Skips to the first non-space char after the first comma in 'str'; +// returns NULL if no comma is found in 'str'. +inline const char* SkipComma(const char* str) { + const char* comma = strchr(str, ','); + if (comma == NULL) { + return NULL; + } + while (isspace(*(++comma))) {} + return comma; +} + +// Returns the prefix of 'str' before the first comma in it; returns +// the entire string if it contains no comma. +inline String GetPrefixUntilComma(const char* str) { + const char* comma = strchr(str, ','); + return comma == NULL ? String(str) : String(str, comma - str); +} + +// TypeParameterizedTest::Register() +// registers a list of type-parameterized tests with Google Test. The +// return value is insignificant - we just need to return something +// such that we can call this function in a namespace scope. +// +// Implementation note: The GTEST_TEMPLATE_ macro declares a template +// template parameter. It's defined in gtest-type-util.h. +template +class TypeParameterizedTest { + public: + // 'index' is the index of the test in the type list 'Types' + // specified in INSTANTIATE_TYPED_TEST_CASE_P(Prefix, TestCase, + // Types). Valid values for 'index' are [0, N - 1] where N is the + // length of Types. + static bool Register(const char* prefix, const char* case_name, + const char* test_names, int index) { + typedef typename Types::Head Type; + typedef Fixture FixtureClass; + typedef typename GTEST_BIND_(TestSel, Type) TestClass; + + // First, registers the first type-parameterized test in the type + // list. + MakeAndRegisterTestInfo( + String::Format("%s%s%s/%d", prefix, prefix[0] == '\0' ? "" : "/", + case_name, index).c_str(), + GetPrefixUntilComma(test_names).c_str(), + String::Format("TypeParam = %s", GetTypeName().c_str()).c_str(), + "", + GetTypeId(), + TestClass::SetUpTestCase, + TestClass::TearDownTestCase, + new TestFactoryImpl); + + // Next, recurses (at compile time) with the tail of the type list. + return TypeParameterizedTest + ::Register(prefix, case_name, test_names, index + 1); + } +}; + +// The base case for the compile time recursion. +template +class TypeParameterizedTest { + public: + static bool Register(const char* /*prefix*/, const char* /*case_name*/, + const char* /*test_names*/, int /*index*/) { + return true; + } +}; + +// TypeParameterizedTestCase::Register() +// registers *all combinations* of 'Tests' and 'Types' with Google +// Test. The return value is insignificant - we just need to return +// something such that we can call this function in a namespace scope. +template +class TypeParameterizedTestCase { + public: + static bool Register(const char* prefix, const char* case_name, + const char* test_names) { + typedef typename Tests::Head Head; + + // First, register the first test in 'Test' for each type in 'Types'. + TypeParameterizedTest::Register( + prefix, case_name, test_names, 0); + + // Next, recurses (at compile time) with the tail of the test list. + return TypeParameterizedTestCase + ::Register(prefix, case_name, SkipComma(test_names)); + } +}; + +// The base case for the compile time recursion. +template +class TypeParameterizedTestCase { + public: + static bool Register(const char* /*prefix*/, const char* /*case_name*/, + const char* /*test_names*/) { + return true; + } +}; + +#endif // GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +// Returns the current OS stack trace as a String. +// +// The maximum number of stack frames to be included is specified by +// the gtest_stack_trace_depth flag. The skip_count parameter +// specifies the number of top frames to be skipped, which doesn't +// count against the number of frames to be included. +// +// For example, if Foo() calls Bar(), which in turn calls +// GetCurrentOsStackTraceExceptTop(..., 1), Foo() will be included in +// the trace but Bar() and GetCurrentOsStackTraceExceptTop() won't. +GTEST_API_ String GetCurrentOsStackTraceExceptTop(UnitTest* unit_test, + int skip_count); + +// Helpers for suppressing warnings on unreachable code or constant +// condition. + +// Always returns true. +GTEST_API_ bool AlwaysTrue(); + +// Always returns false. +inline bool AlwaysFalse() { return !AlwaysTrue(); } + +// A simple Linear Congruential Generator for generating random +// numbers with a uniform distribution. Unlike rand() and srand(), it +// doesn't use global state (and therefore can't interfere with user +// code). Unlike rand_r(), it's portable. An LCG isn't very random, +// but it's good enough for our purposes. +class GTEST_API_ Random { + public: + static const UInt32 kMaxRange = 1u << 31; + + explicit Random(UInt32 seed) : state_(seed) {} + + void Reseed(UInt32 seed) { state_ = seed; } + + // Generates a random number from [0, range). Crashes if 'range' is + // 0 or greater than kMaxRange. + UInt32 Generate(UInt32 range); + + private: + UInt32 state_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(Random); +}; + +} // namespace internal +} // namespace testing + +#define GTEST_MESSAGE_(message, result_type) \ + ::testing::internal::AssertHelper(result_type, __FILE__, __LINE__, message) \ + = ::testing::Message() + +#define GTEST_FATAL_FAILURE_(message) \ + return GTEST_MESSAGE_(message, ::testing::TestPartResult::kFatalFailure) + +#define GTEST_NONFATAL_FAILURE_(message) \ + GTEST_MESSAGE_(message, ::testing::TestPartResult::kNonFatalFailure) + +#define GTEST_SUCCESS_(message) \ + GTEST_MESSAGE_(message, ::testing::TestPartResult::kSuccess) + +// Suppresses MSVC warnings 4072 (unreachable code) for the code following +// statement if it returns or throws (or doesn't return or throw in some +// situations). +#define GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) \ + if (::testing::internal::AlwaysTrue()) { statement; } + +#define GTEST_TEST_THROW_(statement, expected_exception, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const char* gtest_msg = "") { \ + bool gtest_caught_expected = false; \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } \ + catch (expected_exception const&) { \ + gtest_caught_expected = true; \ + } \ + catch (...) { \ + gtest_msg = "Expected: " #statement " throws an exception of type " \ + #expected_exception ".\n Actual: it throws a different " \ + "type."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } \ + if (!gtest_caught_expected) { \ + gtest_msg = "Expected: " #statement " throws an exception of type " \ + #expected_exception ".\n Actual: it throws nothing."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__): \ + fail(gtest_msg) + +#define GTEST_TEST_NO_THROW_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const char* gtest_msg = "") { \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } \ + catch (...) { \ + gtest_msg = "Expected: " #statement " doesn't throw an exception.\n" \ + " Actual: it throws."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__): \ + fail(gtest_msg) + +#define GTEST_TEST_ANY_THROW_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const char* gtest_msg = "") { \ + bool gtest_caught_any = false; \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } \ + catch (...) { \ + gtest_caught_any = true; \ + } \ + if (!gtest_caught_any) { \ + gtest_msg = "Expected: " #statement " throws an exception.\n" \ + " Actual: it doesn't."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__): \ + fail(gtest_msg) + + +// Implements Boolean test assertions such as EXPECT_TRUE. expression can be +// either a boolean expression or an AssertionResult. text is a textual +// represenation of expression as it was passed into the EXPECT_TRUE. +#define GTEST_TEST_BOOLEAN_(expression, text, actual, expected, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const ::testing::AssertionResult gtest_ar_ = \ + ::testing::AssertionResult(expression)) \ + ; \ + else \ + fail(::testing::internal::GetBoolAssertionFailureMessage(\ + gtest_ar_, text, #actual, #expected).c_str()) + +#define GTEST_TEST_NO_FATAL_FAILURE_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const char* gtest_msg = "") { \ + ::testing::internal::HasNewFatalFailureHelper gtest_fatal_failure_checker; \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + if (gtest_fatal_failure_checker.has_new_fatal_failure()) { \ + gtest_msg = "Expected: " #statement " doesn't generate new fatal " \ + "failures in the current thread.\n" \ + " Actual: it does."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__): \ + fail(gtest_msg) + +// Expands to the name of the class that implements the given test. +#define GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \ + test_case_name##_##test_name##_Test + +// Helper macro for defining tests. +#define GTEST_TEST_(test_case_name, test_name, parent_class, parent_id)\ +class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\ + public:\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}\ + private:\ + virtual void TestBody();\ + static ::testing::TestInfo* const test_info_;\ + GTEST_DISALLOW_COPY_AND_ASSIGN_(\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\ +};\ +\ +::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\ + ::test_info_ =\ + ::testing::internal::MakeAndRegisterTestInfo(\ + #test_case_name, #test_name, "", "", \ + (parent_id), \ + parent_class::SetUpTestCase, \ + parent_class::TearDownTestCase, \ + new ::testing::internal::TestFactoryImpl<\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\ +void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/internal/gtest-linked_ptr.h b/3rdparty/gmock/gtest/include/gtest/internal/gtest-linked_ptr.h new file mode 100644 index 00000000..540ef4cd --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/internal/gtest-linked_ptr.h @@ -0,0 +1,242 @@ +// Copyright 2003 Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Authors: Dan Egnor (egnor@google.com) +// +// A "smart" pointer type with reference tracking. Every pointer to a +// particular object is kept on a circular linked list. When the last pointer +// to an object is destroyed or reassigned, the object is deleted. +// +// Used properly, this deletes the object when the last reference goes away. +// There are several caveats: +// - Like all reference counting schemes, cycles lead to leaks. +// - Each smart pointer is actually two pointers (8 bytes instead of 4). +// - Every time a pointer is assigned, the entire list of pointers to that +// object is traversed. This class is therefore NOT SUITABLE when there +// will often be more than two or three pointers to a particular object. +// - References are only tracked as long as linked_ptr<> objects are copied. +// If a linked_ptr<> is converted to a raw pointer and back, BAD THINGS +// will happen (double deletion). +// +// A good use of this class is storing object references in STL containers. +// You can safely put linked_ptr<> in a vector<>. +// Other uses may not be as good. +// +// Note: If you use an incomplete type with linked_ptr<>, the class +// *containing* linked_ptr<> must have a constructor and destructor (even +// if they do nothing!). +// +// Bill Gibbons suggested we use something like this. +// +// Thread Safety: +// Unlike other linked_ptr implementations, in this implementation +// a linked_ptr object is thread-safe in the sense that: +// - it's safe to copy linked_ptr objects concurrently, +// - it's safe to copy *from* a linked_ptr and read its underlying +// raw pointer (e.g. via get()) concurrently, and +// - it's safe to write to two linked_ptrs that point to the same +// shared object concurrently. +// TODO(wan@google.com): rename this to safe_linked_ptr to avoid +// confusion with normal linked_ptr. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_LINKED_PTR_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_LINKED_PTR_H_ + +#include +#include + +#include + +namespace testing { +namespace internal { + +// Protects copying of all linked_ptr objects. +GTEST_API_ GTEST_DECLARE_STATIC_MUTEX_(g_linked_ptr_mutex); + +// This is used internally by all instances of linked_ptr<>. It needs to be +// a non-template class because different types of linked_ptr<> can refer to +// the same object (linked_ptr(obj) vs linked_ptr(obj)). +// So, it needs to be possible for different types of linked_ptr to participate +// in the same circular linked list, so we need a single class type here. +// +// DO NOT USE THIS CLASS DIRECTLY YOURSELF. Use linked_ptr. +class linked_ptr_internal { + public: + // Create a new circle that includes only this instance. + void join_new() { + next_ = this; + } + + // Many linked_ptr operations may change p.link_ for some linked_ptr + // variable p in the same circle as this object. Therefore we need + // to prevent two such operations from occurring concurrently. + // + // Note that different types of linked_ptr objects can coexist in a + // circle (e.g. linked_ptr, linked_ptr, and + // linked_ptr). Therefore we must use a single mutex to + // protect all linked_ptr objects. This can create serious + // contention in production code, but is acceptable in a testing + // framework. + + // Join an existing circle. + // L < g_linked_ptr_mutex + void join(linked_ptr_internal const* ptr) { + MutexLock lock(&g_linked_ptr_mutex); + + linked_ptr_internal const* p = ptr; + while (p->next_ != ptr) p = p->next_; + p->next_ = this; + next_ = ptr; + } + + // Leave whatever circle we're part of. Returns true if we were the + // last member of the circle. Once this is done, you can join() another. + // L < g_linked_ptr_mutex + bool depart() { + MutexLock lock(&g_linked_ptr_mutex); + + if (next_ == this) return true; + linked_ptr_internal const* p = next_; + while (p->next_ != this) p = p->next_; + p->next_ = next_; + return false; + } + + private: + mutable linked_ptr_internal const* next_; +}; + +template +class linked_ptr { + public: + typedef T element_type; + + // Take over ownership of a raw pointer. This should happen as soon as + // possible after the object is created. + explicit linked_ptr(T* ptr = NULL) { capture(ptr); } + ~linked_ptr() { depart(); } + + // Copy an existing linked_ptr<>, adding ourselves to the list of references. + template linked_ptr(linked_ptr const& ptr) { copy(&ptr); } + linked_ptr(linked_ptr const& ptr) { // NOLINT + assert(&ptr != this); + copy(&ptr); + } + + // Assignment releases the old value and acquires the new. + template linked_ptr& operator=(linked_ptr const& ptr) { + depart(); + copy(&ptr); + return *this; + } + + linked_ptr& operator=(linked_ptr const& ptr) { + if (&ptr != this) { + depart(); + copy(&ptr); + } + return *this; + } + + // Smart pointer members. + void reset(T* ptr = NULL) { + depart(); + capture(ptr); + } + T* get() const { return value_; } + T* operator->() const { return value_; } + T& operator*() const { return *value_; } + // Release ownership of the pointed object and returns it. + // Sole ownership by this linked_ptr object is required. + T* release() { + bool last = link_.depart(); + assert(last); + T* v = value_; + value_ = NULL; + return v; + } + + bool operator==(T* p) const { return value_ == p; } + bool operator!=(T* p) const { return value_ != p; } + template + bool operator==(linked_ptr const& ptr) const { + return value_ == ptr.get(); + } + template + bool operator!=(linked_ptr const& ptr) const { + return value_ != ptr.get(); + } + + private: + template + friend class linked_ptr; + + T* value_; + linked_ptr_internal link_; + + void depart() { + if (link_.depart()) delete value_; + } + + void capture(T* ptr) { + value_ = ptr; + link_.join_new(); + } + + template void copy(linked_ptr const* ptr) { + value_ = ptr->get(); + if (value_) + link_.join(&ptr->link_); + else + link_.join_new(); + } +}; + +template inline +bool operator==(T* ptr, const linked_ptr& x) { + return ptr == x.get(); +} + +template inline +bool operator!=(T* ptr, const linked_ptr& x) { + return ptr != x.get(); +} + +// A function to convert T* into linked_ptr +// Doing e.g. make_linked_ptr(new FooBarBaz(arg)) is a shorter notation +// for linked_ptr >(new FooBarBaz(arg)) +template +linked_ptr make_linked_ptr(T* ptr) { + return linked_ptr(ptr); +} + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_LINKED_PTR_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/internal/gtest-param-util-generated.h b/3rdparty/gmock/gtest/include/gtest/internal/gtest-param-util-generated.h new file mode 100644 index 00000000..ab4ab566 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/internal/gtest-param-util-generated.h @@ -0,0 +1,4820 @@ +// This file was GENERATED by a script. DO NOT EDIT BY HAND!!! + +// Copyright 2008 Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: vladl@google.com (Vlad Losev) + +// Type and function utilities for implementing parameterized tests. +// This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +// Currently Google Test supports at most 50 arguments in Values, +// and at most 10 arguments in Combine. Please contact +// googletestframework@googlegroups.com if you need more. +// Please note that the number of arguments to Combine is limited +// by the maximum arity of the implementation of tr1::tuple which is +// currently set at 10. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ + +// scripts/fuse_gtest.py depends on gtest's own header being #included +// *unconditionally*. Therefore these #includes cannot be moved +// inside #if GTEST_HAS_PARAM_TEST. +#include +#include + +#if GTEST_HAS_PARAM_TEST + +namespace testing { + +// Forward declarations of ValuesIn(), which is implemented in +// include/gtest/gtest-param-test.h. +template +internal::ParamGenerator< + typename ::std::iterator_traits::value_type> ValuesIn( + ForwardIterator begin, ForwardIterator end); + +template +internal::ParamGenerator ValuesIn(const T (&array)[N]); + +template +internal::ParamGenerator ValuesIn( + const Container& container); + +namespace internal { + +// Used in the Values() function to provide polymorphic capabilities. +template +class ValueArray1 { + public: + explicit ValueArray1(T1 v1) : v1_(v1) {} + + template + operator ParamGenerator() const { return ValuesIn(&v1_, &v1_ + 1); } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray1& other); + + const T1 v1_; +}; + +template +class ValueArray2 { + public: + ValueArray2(T1 v1, T2 v2) : v1_(v1), v2_(v2) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray2& other); + + const T1 v1_; + const T2 v2_; +}; + +template +class ValueArray3 { + public: + ValueArray3(T1 v1, T2 v2, T3 v3) : v1_(v1), v2_(v2), v3_(v3) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray3& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; +}; + +template +class ValueArray4 { + public: + ValueArray4(T1 v1, T2 v2, T3 v3, T4 v4) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray4& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; +}; + +template +class ValueArray5 { + public: + ValueArray5(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray5& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; +}; + +template +class ValueArray6 { + public: + ValueArray6(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray6& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; +}; + +template +class ValueArray7 { + public: + ValueArray7(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray7& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; +}; + +template +class ValueArray8 { + public: + ValueArray8(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray8& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; +}; + +template +class ValueArray9 { + public: + ValueArray9(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, + T9 v9) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray9& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; +}; + +template +class ValueArray10 { + public: + ValueArray10(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray10& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; +}; + +template +class ValueArray11 { + public: + ValueArray11(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray11& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; +}; + +template +class ValueArray12 { + public: + ValueArray12(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray12& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; +}; + +template +class ValueArray13 { + public: + ValueArray13(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray13& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; +}; + +template +class ValueArray14 { + public: + ValueArray14(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray14& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; +}; + +template +class ValueArray15 { + public: + ValueArray15(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray15& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; +}; + +template +class ValueArray16 { + public: + ValueArray16(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray16& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; +}; + +template +class ValueArray17 { + public: + ValueArray17(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, + T17 v17) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray17& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; +}; + +template +class ValueArray18 { + public: + ValueArray18(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray18& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; +}; + +template +class ValueArray19 { + public: + ValueArray19(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), + v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray19& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; +}; + +template +class ValueArray20 { + public: + ValueArray20(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), + v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), + v19_(v19), v20_(v20) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray20& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; +}; + +template +class ValueArray21 { + public: + ValueArray21(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), + v18_(v18), v19_(v19), v20_(v20), v21_(v21) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray21& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; +}; + +template +class ValueArray22 { + public: + ValueArray22(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray22& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; +}; + +template +class ValueArray23 { + public: + ValueArray23(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, + v23_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray23& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; +}; + +template +class ValueArray24 { + public: + ValueArray24(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), + v22_(v22), v23_(v23), v24_(v24) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray24& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; +}; + +template +class ValueArray25 { + public: + ValueArray25(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, + T25 v25) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray25& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; +}; + +template +class ValueArray26 { + public: + ValueArray26(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray26& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; +}; + +template +class ValueArray27 { + public: + ValueArray27(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), + v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), + v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), + v26_(v26), v27_(v27) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray27& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; +}; + +template +class ValueArray28 { + public: + ValueArray28(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), + v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), + v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), + v25_(v25), v26_(v26), v27_(v27), v28_(v28) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray28& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; +}; + +template +class ValueArray29 { + public: + ValueArray29(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), + v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), + v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray29& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; +}; + +template +class ValueArray30 { + public: + ValueArray30(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray30& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; +}; + +template +class ValueArray31 { + public: + ValueArray31(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray31& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; +}; + +template +class ValueArray32 { + public: + ValueArray32(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), + v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), + v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray32& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; +}; + +template +class ValueArray33 { + public: + ValueArray33(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, + T33 v33) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray33& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; +}; + +template +class ValueArray34 { + public: + ValueArray34(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray34& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; +}; + +template +class ValueArray35 { + public: + ValueArray35(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), + v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), + v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), + v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), + v32_(v32), v33_(v33), v34_(v34), v35_(v35) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, + v35_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray35& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; +}; + +template +class ValueArray36 { + public: + ValueArray36(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), + v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), + v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), + v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), + v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), v36_(v36) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray36& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; +}; + +template +class ValueArray37 { + public: + ValueArray37(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), + v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), + v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), + v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), + v36_(v36), v37_(v37) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray37& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; +}; + +template +class ValueArray38 { + public: + ValueArray38(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), + v35_(v35), v36_(v36), v37_(v37), v38_(v38) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray38& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; +}; + +template +class ValueArray39 { + public: + ValueArray39(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), + v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray39& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; +}; + +template +class ValueArray40 { + public: + ValueArray40(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), + v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), + v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), + v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), + v40_(v40) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray40& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; +}; + +template +class ValueArray41 { + public: + ValueArray41(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, + T41 v41) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), + v39_(v39), v40_(v40), v41_(v41) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray41& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; +}; + +template +class ValueArray42 { + public: + ValueArray42(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), + v39_(v39), v40_(v40), v41_(v41), v42_(v42) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray42& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; +}; + +template +class ValueArray43 { + public: + ValueArray43(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), + v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), + v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), + v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), + v32_(v32), v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), + v38_(v38), v39_(v39), v40_(v40), v41_(v41), v42_(v42), v43_(v43) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray43& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; +}; + +template +class ValueArray44 { + public: + ValueArray44(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), + v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), + v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), + v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), + v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), v36_(v36), + v37_(v37), v38_(v38), v39_(v39), v40_(v40), v41_(v41), v42_(v42), + v43_(v43), v44_(v44) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray44& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; +}; + +template +class ValueArray45 { + public: + ValueArray45(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), + v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), + v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), + v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), + v36_(v36), v37_(v37), v38_(v38), v39_(v39), v40_(v40), v41_(v41), + v42_(v42), v43_(v43), v44_(v44), v45_(v45) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray45& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; +}; + +template +class ValueArray46 { + public: + ValueArray46(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), + v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), v40_(v40), + v41_(v41), v42_(v42), v43_(v43), v44_(v44), v45_(v45), v46_(v46) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_, v46_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray46& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; +}; + +template +class ValueArray47 { + public: + ValueArray47(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), + v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), v40_(v40), + v41_(v41), v42_(v42), v43_(v43), v44_(v44), v45_(v45), v46_(v46), + v47_(v47) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_, v46_, + v47_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray47& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; + const T47 v47_; +}; + +template +class ValueArray48 { + public: + ValueArray48(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, T48 v48) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), + v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), + v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), + v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), + v40_(v40), v41_(v41), v42_(v42), v43_(v43), v44_(v44), v45_(v45), + v46_(v46), v47_(v47), v48_(v48) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_, v46_, v47_, + v48_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray48& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; + const T47 v47_; + const T48 v48_; +}; + +template +class ValueArray49 { + public: + ValueArray49(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, T48 v48, + T49 v49) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), + v39_(v39), v40_(v40), v41_(v41), v42_(v42), v43_(v43), v44_(v44), + v45_(v45), v46_(v46), v47_(v47), v48_(v48), v49_(v49) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_, v46_, v47_, + v48_, v49_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray49& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; + const T47 v47_; + const T48 v48_; + const T49 v49_; +}; + +template +class ValueArray50 { + public: + ValueArray50(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, T48 v48, T49 v49, + T50 v50) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), + v39_(v39), v40_(v40), v41_(v41), v42_(v42), v43_(v43), v44_(v44), + v45_(v45), v46_(v46), v47_(v47), v48_(v48), v49_(v49), v50_(v50) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_, v46_, v47_, + v48_, v49_, v50_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray50& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; + const T47 v47_; + const T48 v48_; + const T49 v49_; + const T50 v50_; +}; + +#if GTEST_HAS_COMBINE +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Generates values from the Cartesian product of values produced +// by the argument generators. +// +template +class CartesianProductGenerator2 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator2(const ParamGenerator& g1, + const ParamGenerator& g2) + : g1_(g1), g2_(g2) {} + virtual ~CartesianProductGenerator2() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current2_; + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + ParamType current_value_; + }; // class CartesianProductGenerator2::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator2& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; +}; // class CartesianProductGenerator2 + + +template +class CartesianProductGenerator3 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator3(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3) + : g1_(g1), g2_(g2), g3_(g3) {} + virtual ~CartesianProductGenerator3() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current3_; + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + ParamType current_value_; + }; // class CartesianProductGenerator3::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator3& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; +}; // class CartesianProductGenerator3 + + +template +class CartesianProductGenerator4 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator4(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4) {} + virtual ~CartesianProductGenerator4() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current4_; + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + ParamType current_value_; + }; // class CartesianProductGenerator4::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator4& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; +}; // class CartesianProductGenerator4 + + +template +class CartesianProductGenerator5 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator5(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5) {} + virtual ~CartesianProductGenerator5() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current5_; + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + ParamType current_value_; + }; // class CartesianProductGenerator5::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator5& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; +}; // class CartesianProductGenerator5 + + +template +class CartesianProductGenerator6 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator6(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6) {} + virtual ~CartesianProductGenerator6() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current6_; + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + ParamType current_value_; + }; // class CartesianProductGenerator6::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator6& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; +}; // class CartesianProductGenerator6 + + +template +class CartesianProductGenerator7 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator7(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6, const ParamGenerator& g7) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7) {} + virtual ~CartesianProductGenerator7() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_, + g7_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6, + const ParamGenerator& g7, + const typename ParamGenerator::iterator& current7) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6), + begin7_(g7.begin()), end7_(g7.end()), current7_(current7) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current7_; + if (current7_ == end7_) { + current7_ = begin7_; + ++current6_; + } + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_ && + current7_ == typed_other->current7_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_), + begin7_(other.begin7_), + end7_(other.end7_), + current7_(other.current7_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_, *current7_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_ || + current7_ == end7_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + const typename ParamGenerator::iterator begin7_; + const typename ParamGenerator::iterator end7_; + typename ParamGenerator::iterator current7_; + ParamType current_value_; + }; // class CartesianProductGenerator7::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator7& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; + const ParamGenerator g7_; +}; // class CartesianProductGenerator7 + + +template +class CartesianProductGenerator8 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator8(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6, const ParamGenerator& g7, + const ParamGenerator& g8) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), + g8_(g8) {} + virtual ~CartesianProductGenerator8() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_, + g7_.begin(), g8_, g8_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end(), g8_, + g8_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6, + const ParamGenerator& g7, + const typename ParamGenerator::iterator& current7, + const ParamGenerator& g8, + const typename ParamGenerator::iterator& current8) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6), + begin7_(g7.begin()), end7_(g7.end()), current7_(current7), + begin8_(g8.begin()), end8_(g8.end()), current8_(current8) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current8_; + if (current8_ == end8_) { + current8_ = begin8_; + ++current7_; + } + if (current7_ == end7_) { + current7_ = begin7_; + ++current6_; + } + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_ && + current7_ == typed_other->current7_ && + current8_ == typed_other->current8_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_), + begin7_(other.begin7_), + end7_(other.end7_), + current7_(other.current7_), + begin8_(other.begin8_), + end8_(other.end8_), + current8_(other.current8_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_, *current7_, *current8_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_ || + current7_ == end7_ || + current8_ == end8_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + const typename ParamGenerator::iterator begin7_; + const typename ParamGenerator::iterator end7_; + typename ParamGenerator::iterator current7_; + const typename ParamGenerator::iterator begin8_; + const typename ParamGenerator::iterator end8_; + typename ParamGenerator::iterator current8_; + ParamType current_value_; + }; // class CartesianProductGenerator8::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator8& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; + const ParamGenerator g7_; + const ParamGenerator g8_; +}; // class CartesianProductGenerator8 + + +template +class CartesianProductGenerator9 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator9(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6, const ParamGenerator& g7, + const ParamGenerator& g8, const ParamGenerator& g9) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8), + g9_(g9) {} + virtual ~CartesianProductGenerator9() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_, + g7_.begin(), g8_, g8_.begin(), g9_, g9_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end(), g8_, + g8_.end(), g9_, g9_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6, + const ParamGenerator& g7, + const typename ParamGenerator::iterator& current7, + const ParamGenerator& g8, + const typename ParamGenerator::iterator& current8, + const ParamGenerator& g9, + const typename ParamGenerator::iterator& current9) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6), + begin7_(g7.begin()), end7_(g7.end()), current7_(current7), + begin8_(g8.begin()), end8_(g8.end()), current8_(current8), + begin9_(g9.begin()), end9_(g9.end()), current9_(current9) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current9_; + if (current9_ == end9_) { + current9_ = begin9_; + ++current8_; + } + if (current8_ == end8_) { + current8_ = begin8_; + ++current7_; + } + if (current7_ == end7_) { + current7_ = begin7_; + ++current6_; + } + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_ && + current7_ == typed_other->current7_ && + current8_ == typed_other->current8_ && + current9_ == typed_other->current9_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_), + begin7_(other.begin7_), + end7_(other.end7_), + current7_(other.current7_), + begin8_(other.begin8_), + end8_(other.end8_), + current8_(other.current8_), + begin9_(other.begin9_), + end9_(other.end9_), + current9_(other.current9_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_, *current7_, *current8_, + *current9_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_ || + current7_ == end7_ || + current8_ == end8_ || + current9_ == end9_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + const typename ParamGenerator::iterator begin7_; + const typename ParamGenerator::iterator end7_; + typename ParamGenerator::iterator current7_; + const typename ParamGenerator::iterator begin8_; + const typename ParamGenerator::iterator end8_; + typename ParamGenerator::iterator current8_; + const typename ParamGenerator::iterator begin9_; + const typename ParamGenerator::iterator end9_; + typename ParamGenerator::iterator current9_; + ParamType current_value_; + }; // class CartesianProductGenerator9::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator9& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; + const ParamGenerator g7_; + const ParamGenerator g8_; + const ParamGenerator g9_; +}; // class CartesianProductGenerator9 + + +template +class CartesianProductGenerator10 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator10(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6, const ParamGenerator& g7, + const ParamGenerator& g8, const ParamGenerator& g9, + const ParamGenerator& g10) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8), + g9_(g9), g10_(g10) {} + virtual ~CartesianProductGenerator10() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_, + g7_.begin(), g8_, g8_.begin(), g9_, g9_.begin(), g10_, g10_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end(), g8_, + g8_.end(), g9_, g9_.end(), g10_, g10_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6, + const ParamGenerator& g7, + const typename ParamGenerator::iterator& current7, + const ParamGenerator& g8, + const typename ParamGenerator::iterator& current8, + const ParamGenerator& g9, + const typename ParamGenerator::iterator& current9, + const ParamGenerator& g10, + const typename ParamGenerator::iterator& current10) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6), + begin7_(g7.begin()), end7_(g7.end()), current7_(current7), + begin8_(g8.begin()), end8_(g8.end()), current8_(current8), + begin9_(g9.begin()), end9_(g9.end()), current9_(current9), + begin10_(g10.begin()), end10_(g10.end()), current10_(current10) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current10_; + if (current10_ == end10_) { + current10_ = begin10_; + ++current9_; + } + if (current9_ == end9_) { + current9_ = begin9_; + ++current8_; + } + if (current8_ == end8_) { + current8_ = begin8_; + ++current7_; + } + if (current7_ == end7_) { + current7_ = begin7_; + ++current6_; + } + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_ && + current7_ == typed_other->current7_ && + current8_ == typed_other->current8_ && + current9_ == typed_other->current9_ && + current10_ == typed_other->current10_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_), + begin7_(other.begin7_), + end7_(other.end7_), + current7_(other.current7_), + begin8_(other.begin8_), + end8_(other.end8_), + current8_(other.current8_), + begin9_(other.begin9_), + end9_(other.end9_), + current9_(other.current9_), + begin10_(other.begin10_), + end10_(other.end10_), + current10_(other.current10_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_, *current7_, *current8_, + *current9_, *current10_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_ || + current7_ == end7_ || + current8_ == end8_ || + current9_ == end9_ || + current10_ == end10_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + const typename ParamGenerator::iterator begin7_; + const typename ParamGenerator::iterator end7_; + typename ParamGenerator::iterator current7_; + const typename ParamGenerator::iterator begin8_; + const typename ParamGenerator::iterator end8_; + typename ParamGenerator::iterator current8_; + const typename ParamGenerator::iterator begin9_; + const typename ParamGenerator::iterator end9_; + typename ParamGenerator::iterator current9_; + const typename ParamGenerator::iterator begin10_; + const typename ParamGenerator::iterator end10_; + typename ParamGenerator::iterator current10_; + ParamType current_value_; + }; // class CartesianProductGenerator10::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator10& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; + const ParamGenerator g7_; + const ParamGenerator g8_; + const ParamGenerator g9_; + const ParamGenerator g10_; +}; // class CartesianProductGenerator10 + + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Helper classes providing Combine() with polymorphic features. They allow +// casting CartesianProductGeneratorN to ParamGenerator if T is +// convertible to U. +// +template +class CartesianProductHolder2 { + public: +CartesianProductHolder2(const Generator1& g1, const Generator2& g2) + : g1_(g1), g2_(g2) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator2( + static_cast >(g1_), + static_cast >(g2_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder2& other); + + const Generator1 g1_; + const Generator2 g2_; +}; // class CartesianProductHolder2 + +template +class CartesianProductHolder3 { + public: +CartesianProductHolder3(const Generator1& g1, const Generator2& g2, + const Generator3& g3) + : g1_(g1), g2_(g2), g3_(g3) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator3( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder3& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; +}; // class CartesianProductHolder3 + +template +class CartesianProductHolder4 { + public: +CartesianProductHolder4(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator4( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder4& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; +}; // class CartesianProductHolder4 + +template +class CartesianProductHolder5 { + public: +CartesianProductHolder5(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator5( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder5& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; +}; // class CartesianProductHolder5 + +template +class CartesianProductHolder6 { + public: +CartesianProductHolder6(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator6( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder6& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; +}; // class CartesianProductHolder6 + +template +class CartesianProductHolder7 { + public: +CartesianProductHolder7(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6, const Generator7& g7) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator7( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_), + static_cast >(g7_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder7& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; + const Generator7 g7_; +}; // class CartesianProductHolder7 + +template +class CartesianProductHolder8 { + public: +CartesianProductHolder8(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6, const Generator7& g7, const Generator8& g8) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), + g8_(g8) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator8( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_), + static_cast >(g7_), + static_cast >(g8_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder8& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; + const Generator7 g7_; + const Generator8 g8_; +}; // class CartesianProductHolder8 + +template +class CartesianProductHolder9 { + public: +CartesianProductHolder9(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6, const Generator7& g7, const Generator8& g8, + const Generator9& g9) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8), + g9_(g9) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator9( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_), + static_cast >(g7_), + static_cast >(g8_), + static_cast >(g9_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder9& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; + const Generator7 g7_; + const Generator8 g8_; + const Generator9 g9_; +}; // class CartesianProductHolder9 + +template +class CartesianProductHolder10 { + public: +CartesianProductHolder10(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6, const Generator7& g7, const Generator8& g8, + const Generator9& g9, const Generator10& g10) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8), + g9_(g9), g10_(g10) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator10( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_), + static_cast >(g7_), + static_cast >(g8_), + static_cast >(g9_), + static_cast >(g10_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder10& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; + const Generator7 g7_; + const Generator8 g8_; + const Generator9 g9_; + const Generator10 g10_; +}; // class CartesianProductHolder10 + +#endif // GTEST_HAS_COMBINE + +} // namespace internal +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/internal/gtest-param-util-generated.h.pump b/3rdparty/gmock/gtest/include/gtest/internal/gtest-param-util-generated.h.pump new file mode 100644 index 00000000..baedfbc2 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/internal/gtest-param-util-generated.h.pump @@ -0,0 +1,301 @@ +$$ -*- mode: c++; -*- +$var n = 50 $$ Maximum length of Values arguments we want to support. +$var maxtuple = 10 $$ Maximum number of Combine arguments we want to support. +// Copyright 2008 Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: vladl@google.com (Vlad Losev) + +// Type and function utilities for implementing parameterized tests. +// This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +// Currently Google Test supports at most $n arguments in Values, +// and at most $maxtuple arguments in Combine. Please contact +// googletestframework@googlegroups.com if you need more. +// Please note that the number of arguments to Combine is limited +// by the maximum arity of the implementation of tr1::tuple which is +// currently set at $maxtuple. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ + +// scripts/fuse_gtest.py depends on gtest's own header being #included +// *unconditionally*. Therefore these #includes cannot be moved +// inside #if GTEST_HAS_PARAM_TEST. +#include +#include + +#if GTEST_HAS_PARAM_TEST + +namespace testing { + +// Forward declarations of ValuesIn(), which is implemented in +// include/gtest/gtest-param-test.h. +template +internal::ParamGenerator< + typename ::std::iterator_traits::value_type> ValuesIn( + ForwardIterator begin, ForwardIterator end); + +template +internal::ParamGenerator ValuesIn(const T (&array)[N]); + +template +internal::ParamGenerator ValuesIn( + const Container& container); + +namespace internal { + +// Used in the Values() function to provide polymorphic capabilities. +template +class ValueArray1 { + public: + explicit ValueArray1(T1 v1) : v1_(v1) {} + + template + operator ParamGenerator() const { return ValuesIn(&v1_, &v1_ + 1); } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray1& other); + + const T1 v1_; +}; + +$range i 2..n +$for i [[ +$range j 1..i + +template <$for j, [[typename T$j]]> +class ValueArray$i { + public: + ValueArray$i($for j, [[T$j v$j]]) : $for j, [[v$(j)_(v$j)]] {} + + template + operator ParamGenerator() const { + const T array[] = {$for j, [[v$(j)_]]}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray$i& other); + +$for j [[ + + const T$j v$(j)_; +]] + +}; + +]] + +#if GTEST_HAS_COMBINE +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Generates values from the Cartesian product of values produced +// by the argument generators. +// +$range i 2..maxtuple +$for i [[ +$range j 1..i +$range k 2..i + +template <$for j, [[typename T$j]]> +class CartesianProductGenerator$i + : public ParamGeneratorInterface< ::std::tr1::tuple<$for j, [[T$j]]> > { + public: + typedef ::std::tr1::tuple<$for j, [[T$j]]> ParamType; + + CartesianProductGenerator$i($for j, [[const ParamGenerator& g$j]]) + : $for j, [[g$(j)_(g$j)]] {} + virtual ~CartesianProductGenerator$i() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, $for j, [[g$(j)_, g$(j)_.begin()]]); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, $for j, [[g$(j)_, g$(j)_.end()]]); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, $for j, [[ + + const ParamGenerator& g$j, + const typename ParamGenerator::iterator& current$(j)]]) + : base_(base), +$for j, [[ + + begin$(j)_(g$j.begin()), end$(j)_(g$j.end()), current$(j)_(current$j) +]] { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current$(i)_; + +$for k [[ + if (current$(i+2-k)_ == end$(i+2-k)_) { + current$(i+2-k)_ = begin$(i+2-k)_; + ++current$(i+2-k-1)_; + } + +]] + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ($for j && [[ + + current$(j)_ == typed_other->current$(j)_ +]]); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), $for j, [[ + + begin$(j)_(other.begin$(j)_), + end$(j)_(other.end$(j)_), + current$(j)_(other.current$(j)_) +]] { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType($for j, [[*current$(j)_]]); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return +$for j || [[ + + current$(j)_ == end$(j)_ +]]; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. +$for j [[ + + const typename ParamGenerator::iterator begin$(j)_; + const typename ParamGenerator::iterator end$(j)_; + typename ParamGenerator::iterator current$(j)_; +]] + + ParamType current_value_; + }; // class CartesianProductGenerator$i::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator$i& other); + + +$for j [[ + const ParamGenerator g$(j)_; + +]] +}; // class CartesianProductGenerator$i + + +]] + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Helper classes providing Combine() with polymorphic features. They allow +// casting CartesianProductGeneratorN to ParamGenerator if T is +// convertible to U. +// +$range i 2..maxtuple +$for i [[ +$range j 1..i + +template <$for j, [[class Generator$j]]> +class CartesianProductHolder$i { + public: +CartesianProductHolder$i($for j, [[const Generator$j& g$j]]) + : $for j, [[g$(j)_(g$j)]] {} + template <$for j, [[typename T$j]]> + operator ParamGenerator< ::std::tr1::tuple<$for j, [[T$j]]> >() const { + return ParamGenerator< ::std::tr1::tuple<$for j, [[T$j]]> >( + new CartesianProductGenerator$i<$for j, [[T$j]]>( +$for j,[[ + + static_cast >(g$(j)_) +]])); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder$i& other); + + +$for j [[ + const Generator$j g$(j)_; + +]] +}; // class CartesianProductHolder$i + +]] + +#endif // GTEST_HAS_COMBINE + +} // namespace internal +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/internal/gtest-param-util.h b/3rdparty/gmock/gtest/include/gtest/internal/gtest-param-util.h new file mode 100644 index 00000000..0cbb58c2 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/internal/gtest-param-util.h @@ -0,0 +1,619 @@ +// Copyright 2008 Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: vladl@google.com (Vlad Losev) + +// Type and function utilities for implementing parameterized tests. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ + +#include +#include +#include + +// scripts/fuse_gtest.py depends on gtest's own header being #included +// *unconditionally*. Therefore these #includes cannot be moved +// inside #if GTEST_HAS_PARAM_TEST. +#include +#include +#include + +#if GTEST_HAS_PARAM_TEST + +namespace testing { +namespace internal { + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Outputs a message explaining invalid registration of different +// fixture class for the same test case. This may happen when +// TEST_P macro is used to define two tests with the same name +// but in different namespaces. +GTEST_API_ void ReportInvalidTestCaseType(const char* test_case_name, + const char* file, int line); + +template class ParamGeneratorInterface; +template class ParamGenerator; + +// Interface for iterating over elements provided by an implementation +// of ParamGeneratorInterface. +template +class ParamIteratorInterface { + public: + virtual ~ParamIteratorInterface() {} + // A pointer to the base generator instance. + // Used only for the purposes of iterator comparison + // to make sure that two iterators belong to the same generator. + virtual const ParamGeneratorInterface* BaseGenerator() const = 0; + // Advances iterator to point to the next element + // provided by the generator. The caller is responsible + // for not calling Advance() on an iterator equal to + // BaseGenerator()->End(). + virtual void Advance() = 0; + // Clones the iterator object. Used for implementing copy semantics + // of ParamIterator. + virtual ParamIteratorInterface* Clone() const = 0; + // Dereferences the current iterator and provides (read-only) access + // to the pointed value. It is the caller's responsibility not to call + // Current() on an iterator equal to BaseGenerator()->End(). + // Used for implementing ParamGenerator::operator*(). + virtual const T* Current() const = 0; + // Determines whether the given iterator and other point to the same + // element in the sequence generated by the generator. + // Used for implementing ParamGenerator::operator==(). + virtual bool Equals(const ParamIteratorInterface& other) const = 0; +}; + +// Class iterating over elements provided by an implementation of +// ParamGeneratorInterface. It wraps ParamIteratorInterface +// and implements the const forward iterator concept. +template +class ParamIterator { + public: + typedef T value_type; + typedef const T& reference; + typedef ptrdiff_t difference_type; + + // ParamIterator assumes ownership of the impl_ pointer. + ParamIterator(const ParamIterator& other) : impl_(other.impl_->Clone()) {} + ParamIterator& operator=(const ParamIterator& other) { + if (this != &other) + impl_.reset(other.impl_->Clone()); + return *this; + } + + const T& operator*() const { return *impl_->Current(); } + const T* operator->() const { return impl_->Current(); } + // Prefix version of operator++. + ParamIterator& operator++() { + impl_->Advance(); + return *this; + } + // Postfix version of operator++. + ParamIterator operator++(int /*unused*/) { + ParamIteratorInterface* clone = impl_->Clone(); + impl_->Advance(); + return ParamIterator(clone); + } + bool operator==(const ParamIterator& other) const { + return impl_.get() == other.impl_.get() || impl_->Equals(*other.impl_); + } + bool operator!=(const ParamIterator& other) const { + return !(*this == other); + } + + private: + friend class ParamGenerator; + explicit ParamIterator(ParamIteratorInterface* impl) : impl_(impl) {} + scoped_ptr > impl_; +}; + +// ParamGeneratorInterface is the binary interface to access generators +// defined in other translation units. +template +class ParamGeneratorInterface { + public: + typedef T ParamType; + + virtual ~ParamGeneratorInterface() {} + + // Generator interface definition + virtual ParamIteratorInterface* Begin() const = 0; + virtual ParamIteratorInterface* End() const = 0; +}; + +// Wraps ParamGeneratorInterface and provides general generator syntax +// compatible with the STL Container concept. +// This class implements copy initialization semantics and the contained +// ParamGeneratorInterface instance is shared among all copies +// of the original object. This is possible because that instance is immutable. +template +class ParamGenerator { + public: + typedef ParamIterator iterator; + + explicit ParamGenerator(ParamGeneratorInterface* impl) : impl_(impl) {} + ParamGenerator(const ParamGenerator& other) : impl_(other.impl_) {} + + ParamGenerator& operator=(const ParamGenerator& other) { + impl_ = other.impl_; + return *this; + } + + iterator begin() const { return iterator(impl_->Begin()); } + iterator end() const { return iterator(impl_->End()); } + + private: + ::testing::internal::linked_ptr > impl_; +}; + +// Generates values from a range of two comparable values. Can be used to +// generate sequences of user-defined types that implement operator+() and +// operator<(). +// This class is used in the Range() function. +template +class RangeGenerator : public ParamGeneratorInterface { + public: + RangeGenerator(T begin, T end, IncrementT step) + : begin_(begin), end_(end), + step_(step), end_index_(CalculateEndIndex(begin, end, step)) {} + virtual ~RangeGenerator() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, begin_, 0, step_); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, end_, end_index_, step_); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, T value, int index, + IncrementT step) + : base_(base), value_(value), index_(index), step_(step) {} + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + virtual void Advance() { + value_ = value_ + step_; + index_++; + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const T* Current() const { return &value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const int other_index = + CheckedDowncastToActualType(&other)->index_; + return index_ == other_index; + } + + private: + Iterator(const Iterator& other) + : ParamIteratorInterface(), + base_(other.base_), value_(other.value_), index_(other.index_), + step_(other.step_) {} + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + T value_; + int index_; + const IncrementT step_; + }; // class RangeGenerator::Iterator + + static int CalculateEndIndex(const T& begin, + const T& end, + const IncrementT& step) { + int end_index = 0; + for (T i = begin; i < end; i = i + step) + end_index++; + return end_index; + } + + // No implementation - assignment is unsupported. + void operator=(const RangeGenerator& other); + + const T begin_; + const T end_; + const IncrementT step_; + // The index for the end() iterator. All the elements in the generated + // sequence are indexed (0-based) to aid iterator comparison. + const int end_index_; +}; // class RangeGenerator + + +// Generates values from a pair of STL-style iterators. Used in the +// ValuesIn() function. The elements are copied from the source range +// since the source can be located on the stack, and the generator +// is likely to persist beyond that stack frame. +template +class ValuesInIteratorRangeGenerator : public ParamGeneratorInterface { + public: + template + ValuesInIteratorRangeGenerator(ForwardIterator begin, ForwardIterator end) + : container_(begin, end) {} + virtual ~ValuesInIteratorRangeGenerator() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, container_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, container_.end()); + } + + private: + typedef typename ::std::vector ContainerType; + + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + typename ContainerType::const_iterator iterator) + : base_(base), iterator_(iterator) {} + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + virtual void Advance() { + ++iterator_; + value_.reset(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + // We need to use cached value referenced by iterator_ because *iterator_ + // can return a temporary object (and of type other then T), so just + // having "return &*iterator_;" doesn't work. + // value_ is updated here and not in Advance() because Advance() + // can advance iterator_ beyond the end of the range, and we cannot + // detect that fact. The client code, on the other hand, is + // responsible for not calling Current() on an out-of-range iterator. + virtual const T* Current() const { + if (value_.get() == NULL) + value_.reset(new T(*iterator_)); + return value_.get(); + } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + return iterator_ == + CheckedDowncastToActualType(&other)->iterator_; + } + + private: + Iterator(const Iterator& other) + // The explicit constructor call suppresses a false warning + // emitted by gcc when supplied with the -Wextra option. + : ParamIteratorInterface(), + base_(other.base_), + iterator_(other.iterator_) {} + + const ParamGeneratorInterface* const base_; + typename ContainerType::const_iterator iterator_; + // A cached value of *iterator_. We keep it here to allow access by + // pointer in the wrapping iterator's operator->(). + // value_ needs to be mutable to be accessed in Current(). + // Use of scoped_ptr helps manage cached value's lifetime, + // which is bound by the lifespan of the iterator itself. + mutable scoped_ptr value_; + }; // class ValuesInIteratorRangeGenerator::Iterator + + // No implementation - assignment is unsupported. + void operator=(const ValuesInIteratorRangeGenerator& other); + + const ContainerType container_; +}; // class ValuesInIteratorRangeGenerator + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Stores a parameter value and later creates tests parameterized with that +// value. +template +class ParameterizedTestFactory : public TestFactoryBase { + public: + typedef typename TestClass::ParamType ParamType; + explicit ParameterizedTestFactory(ParamType parameter) : + parameter_(parameter) {} + virtual Test* CreateTest() { + TestClass::SetParam(¶meter_); + return new TestClass(); + } + + private: + const ParamType parameter_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestFactory); +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// TestMetaFactoryBase is a base class for meta-factories that create +// test factories for passing into MakeAndRegisterTestInfo function. +template +class TestMetaFactoryBase { + public: + virtual ~TestMetaFactoryBase() {} + + virtual TestFactoryBase* CreateTestFactory(ParamType parameter) = 0; +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// TestMetaFactory creates test factories for passing into +// MakeAndRegisterTestInfo function. Since MakeAndRegisterTestInfo receives +// ownership of test factory pointer, same factory object cannot be passed +// into that method twice. But ParameterizedTestCaseInfo is going to call +// it for each Test/Parameter value combination. Thus it needs meta factory +// creator class. +template +class TestMetaFactory + : public TestMetaFactoryBase { + public: + typedef typename TestCase::ParamType ParamType; + + TestMetaFactory() {} + + virtual TestFactoryBase* CreateTestFactory(ParamType parameter) { + return new ParameterizedTestFactory(parameter); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestMetaFactory); +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestCaseInfoBase is a generic interface +// to ParameterizedTestCaseInfo classes. ParameterizedTestCaseInfoBase +// accumulates test information provided by TEST_P macro invocations +// and generators provided by INSTANTIATE_TEST_CASE_P macro invocations +// and uses that information to register all resulting test instances +// in RegisterTests method. The ParameterizeTestCaseRegistry class holds +// a collection of pointers to the ParameterizedTestCaseInfo objects +// and calls RegisterTests() on each of them when asked. +class ParameterizedTestCaseInfoBase { + public: + virtual ~ParameterizedTestCaseInfoBase() {} + + // Base part of test case name for display purposes. + virtual const String& GetTestCaseName() const = 0; + // Test case id to verify identity. + virtual TypeId GetTestCaseTypeId() const = 0; + // UnitTest class invokes this method to register tests in this + // test case right before running them in RUN_ALL_TESTS macro. + // This method should not be called more then once on any single + // instance of a ParameterizedTestCaseInfoBase derived class. + virtual void RegisterTests() = 0; + + protected: + ParameterizedTestCaseInfoBase() {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseInfoBase); +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestCaseInfo accumulates tests obtained from TEST_P +// macro invocations for a particular test case and generators +// obtained from INSTANTIATE_TEST_CASE_P macro invocations for that +// test case. It registers tests with all values generated by all +// generators when asked. +template +class ParameterizedTestCaseInfo : public ParameterizedTestCaseInfoBase { + public: + // ParamType and GeneratorCreationFunc are private types but are required + // for declarations of public methods AddTestPattern() and + // AddTestCaseInstantiation(). + typedef typename TestCase::ParamType ParamType; + // A function that returns an instance of appropriate generator type. + typedef ParamGenerator(GeneratorCreationFunc)(); + + explicit ParameterizedTestCaseInfo(const char* name) + : test_case_name_(name) {} + + // Test case base name for display purposes. + virtual const String& GetTestCaseName() const { return test_case_name_; } + // Test case id to verify identity. + virtual TypeId GetTestCaseTypeId() const { return GetTypeId(); } + // TEST_P macro uses AddTestPattern() to record information + // about a single test in a LocalTestInfo structure. + // test_case_name is the base name of the test case (without invocation + // prefix). test_base_name is the name of an individual test without + // parameter index. For the test SequenceA/FooTest.DoBar/1 FooTest is + // test case base name and DoBar is test base name. + void AddTestPattern(const char* test_case_name, + const char* test_base_name, + TestMetaFactoryBase* meta_factory) { + tests_.push_back(linked_ptr(new TestInfo(test_case_name, + test_base_name, + meta_factory))); + } + // INSTANTIATE_TEST_CASE_P macro uses AddGenerator() to record information + // about a generator. + int AddTestCaseInstantiation(const char* instantiation_name, + GeneratorCreationFunc* func, + const char* /* file */, + int /* line */) { + instantiations_.push_back(::std::make_pair(instantiation_name, func)); + return 0; // Return value used only to run this method in namespace scope. + } + // UnitTest class invokes this method to register tests in this test case + // test cases right before running tests in RUN_ALL_TESTS macro. + // This method should not be called more then once on any single + // instance of a ParameterizedTestCaseInfoBase derived class. + // UnitTest has a guard to prevent from calling this method more then once. + virtual void RegisterTests() { + for (typename TestInfoContainer::iterator test_it = tests_.begin(); + test_it != tests_.end(); ++test_it) { + linked_ptr test_info = *test_it; + for (typename InstantiationContainer::iterator gen_it = + instantiations_.begin(); gen_it != instantiations_.end(); + ++gen_it) { + const String& instantiation_name = gen_it->first; + ParamGenerator generator((*gen_it->second)()); + + Message test_case_name_stream; + if ( !instantiation_name.empty() ) + test_case_name_stream << instantiation_name.c_str() << "/"; + test_case_name_stream << test_info->test_case_base_name.c_str(); + + int i = 0; + for (typename ParamGenerator::iterator param_it = + generator.begin(); + param_it != generator.end(); ++param_it, ++i) { + Message test_name_stream; + test_name_stream << test_info->test_base_name.c_str() << "/" << i; + ::testing::internal::MakeAndRegisterTestInfo( + test_case_name_stream.GetString().c_str(), + test_name_stream.GetString().c_str(), + "", // test_case_comment + "", // comment; TODO(vladl@google.com): provide parameter value + // representation. + GetTestCaseTypeId(), + TestCase::SetUpTestCase, + TestCase::TearDownTestCase, + test_info->test_meta_factory->CreateTestFactory(*param_it)); + } // for param_it + } // for gen_it + } // for test_it + } // RegisterTests + + private: + // LocalTestInfo structure keeps information about a single test registered + // with TEST_P macro. + struct TestInfo { + TestInfo(const char* a_test_case_base_name, + const char* a_test_base_name, + TestMetaFactoryBase* a_test_meta_factory) : + test_case_base_name(a_test_case_base_name), + test_base_name(a_test_base_name), + test_meta_factory(a_test_meta_factory) {} + + const String test_case_base_name; + const String test_base_name; + const scoped_ptr > test_meta_factory; + }; + typedef ::std::vector > TestInfoContainer; + // Keeps pairs of + // received from INSTANTIATE_TEST_CASE_P macros. + typedef ::std::vector > + InstantiationContainer; + + const String test_case_name_; + TestInfoContainer tests_; + InstantiationContainer instantiations_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseInfo); +}; // class ParameterizedTestCaseInfo + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestCaseRegistry contains a map of ParameterizedTestCaseInfoBase +// classes accessed by test case names. TEST_P and INSTANTIATE_TEST_CASE_P +// macros use it to locate their corresponding ParameterizedTestCaseInfo +// descriptors. +class ParameterizedTestCaseRegistry { + public: + ParameterizedTestCaseRegistry() {} + ~ParameterizedTestCaseRegistry() { + for (TestCaseInfoContainer::iterator it = test_case_infos_.begin(); + it != test_case_infos_.end(); ++it) { + delete *it; + } + } + + // Looks up or creates and returns a structure containing information about + // tests and instantiations of a particular test case. + template + ParameterizedTestCaseInfo* GetTestCasePatternHolder( + const char* test_case_name, + const char* file, + int line) { + ParameterizedTestCaseInfo* typed_test_info = NULL; + for (TestCaseInfoContainer::iterator it = test_case_infos_.begin(); + it != test_case_infos_.end(); ++it) { + if ((*it)->GetTestCaseName() == test_case_name) { + if ((*it)->GetTestCaseTypeId() != GetTypeId()) { + // Complain about incorrect usage of Google Test facilities + // and terminate the program since we cannot guaranty correct + // test case setup and tear-down in this case. + ReportInvalidTestCaseType(test_case_name, file, line); + abort(); + } else { + // At this point we are sure that the object we found is of the same + // type we are looking for, so we downcast it to that type + // without further checks. + typed_test_info = CheckedDowncastToActualType< + ParameterizedTestCaseInfo >(*it); + } + break; + } + } + if (typed_test_info == NULL) { + typed_test_info = new ParameterizedTestCaseInfo(test_case_name); + test_case_infos_.push_back(typed_test_info); + } + return typed_test_info; + } + void RegisterTests() { + for (TestCaseInfoContainer::iterator it = test_case_infos_.begin(); + it != test_case_infos_.end(); ++it) { + (*it)->RegisterTests(); + } + } + + private: + typedef ::std::vector TestCaseInfoContainer; + + TestCaseInfoContainer test_case_infos_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseRegistry); +}; + +} // namespace internal +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/internal/gtest-port.h b/3rdparty/gmock/gtest/include/gtest/internal/gtest-port.h new file mode 100644 index 00000000..a2a62be9 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/internal/gtest-port.h @@ -0,0 +1,1497 @@ +// Copyright 2005, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Authors: wan@google.com (Zhanyong Wan) +// +// Low-level types and utilities for porting Google Test to various +// platforms. They are subject to change without notice. DO NOT USE +// THEM IN USER CODE. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ + +// The user can define the following macros in the build script to +// control Google Test's behavior. If the user doesn't define a macro +// in this list, Google Test will define it. +// +// GTEST_HAS_CLONE - Define it to 1/0 to indicate that clone(2) +// is/isn't available. +// GTEST_HAS_EXCEPTIONS - Define it to 1/0 to indicate that exceptions +// are enabled. +// GTEST_HAS_GLOBAL_STRING - Define it to 1/0 to indicate that ::string +// is/isn't available (some systems define +// ::string, which is different to std::string). +// GTEST_HAS_GLOBAL_WSTRING - Define it to 1/0 to indicate that ::string +// is/isn't available (some systems define +// ::wstring, which is different to std::wstring). +// GTEST_HAS_PTHREAD - Define it to 1/0 to indicate that +// is/isn't available. +// GTEST_HAS_RTTI - Define it to 1/0 to indicate that RTTI is/isn't +// enabled. +// GTEST_HAS_STD_WSTRING - Define it to 1/0 to indicate that +// std::wstring does/doesn't work (Google Test can +// be used where std::wstring is unavailable). +// GTEST_HAS_TR1_TUPLE - Define it to 1/0 to indicate tr1::tuple +// is/isn't available. +// GTEST_HAS_SEH - Define it to 1/0 to indicate whether the +// compiler supports Microsoft's "Structured +// Exception Handling". +// GTEST_USE_OWN_TR1_TUPLE - Define it to 1/0 to indicate whether Google +// Test's own tr1 tuple implementation should be +// used. Unused when the user sets +// GTEST_HAS_TR1_TUPLE to 0. +// GTEST_LINKED_AS_SHARED_LIBRARY +// - Define to 1 when compiling tests that use +// Google Test as a shared library (known as +// DLL on Windows). +// GTEST_CREATE_SHARED_LIBRARY +// - Define to 1 when compiling Google Test itself +// as a shared library. + +// This header defines the following utilities: +// +// Macros indicating the current platform (defined to 1 if compiled on +// the given platform; otherwise undefined): +// GTEST_OS_AIX - IBM AIX +// GTEST_OS_CYGWIN - Cygwin +// GTEST_OS_LINUX - Linux +// GTEST_OS_MAC - Mac OS X +// GTEST_OS_SOLARIS - Sun Solaris +// GTEST_OS_SYMBIAN - Symbian +// GTEST_OS_WINDOWS - Windows (Desktop, MinGW, or Mobile) +// GTEST_OS_WINDOWS_DESKTOP - Windows Desktop +// GTEST_OS_WINDOWS_MINGW - MinGW +// GTEST_OS_WINDOWS_MOBILE - Windows Mobile +// GTEST_OS_ZOS - z/OS +// +// Among the platforms, Cygwin, Linux, Max OS X, and Windows have the +// most stable support. Since core members of the Google Test project +// don't have access to other platforms, support for them may be less +// stable. If you notice any problems on your platform, please notify +// googletestframework@googlegroups.com (patches for fixing them are +// even more welcome!). +// +// Note that it is possible that none of the GTEST_OS_* macros are defined. +// +// Macros indicating available Google Test features (defined to 1 if +// the corresponding feature is supported; otherwise undefined): +// GTEST_HAS_COMBINE - the Combine() function (for value-parameterized +// tests) +// GTEST_HAS_DEATH_TEST - death tests +// GTEST_HAS_PARAM_TEST - value-parameterized tests +// GTEST_HAS_TYPED_TEST - typed tests +// GTEST_HAS_TYPED_TEST_P - type-parameterized tests +// GTEST_USES_POSIX_RE - enhanced POSIX regex is used. +// GTEST_USES_SIMPLE_RE - our own simple regex is used; +// the above two are mutually exclusive. +// GTEST_CAN_COMPARE_NULL - accepts untyped NULL in EXPECT_EQ(). +// +// Macros for basic C++ coding: +// GTEST_AMBIGUOUS_ELSE_BLOCKER_ - for disabling a gcc warning. +// GTEST_ATTRIBUTE_UNUSED_ - declares that a class' instances or a +// variable don't have to be used. +// GTEST_DISALLOW_ASSIGN_ - disables operator=. +// GTEST_DISALLOW_COPY_AND_ASSIGN_ - disables copy ctor and operator=. +// GTEST_MUST_USE_RESULT_ - declares that a function's result must be used. +// +// Synchronization: +// Mutex, MutexLock, ThreadLocal, GetThreadCount() +// - synchronization primitives. +// GTEST_IS_THREADSAFE - defined to 1 to indicate that the above +// synchronization primitives have real implementations +// and Google Test is thread-safe; or 0 otherwise. +// +// Template meta programming: +// is_pointer - as in TR1; needed on Symbian and IBM XL C/C++ only. +// +// Smart pointers: +// scoped_ptr - as in TR2. +// +// Regular expressions: +// RE - a simple regular expression class using the POSIX +// Extended Regular Expression syntax. Not available on +// Windows. +// +// Logging: +// GTEST_LOG_() - logs messages at the specified severity level. +// LogToStderr() - directs all log messages to stderr. +// FlushInfoLog() - flushes informational log messages. +// +// Stdout and stderr capturing: +// CaptureStdout() - starts capturing stdout. +// GetCapturedStdout() - stops capturing stdout and returns the captured +// string. +// CaptureStderr() - starts capturing stderr. +// GetCapturedStderr() - stops capturing stderr and returns the captured +// string. +// +// Integer types: +// TypeWithSize - maps an integer to a int type. +// Int32, UInt32, Int64, UInt64, TimeInMillis +// - integers of known sizes. +// BiggestInt - the biggest signed integer type. +// +// Command-line utilities: +// GTEST_FLAG() - references a flag. +// GTEST_DECLARE_*() - declares a flag. +// GTEST_DEFINE_*() - defines a flag. +// GetArgvs() - returns the command line as a vector of strings. +// +// Environment variable utilities: +// GetEnv() - gets the value of an environment variable. +// BoolFromGTestEnv() - parses a bool environment variable. +// Int32FromGTestEnv() - parses an Int32 environment variable. +// StringFromGTestEnv() - parses a string environment variable. + +#include // For ptrdiff_t +#include +#include +#include +#ifndef _WIN32_WCE +#include +#endif // !_WIN32_WCE + +#include // NOLINT +#include // NOLINT +#include // NOLINT + +#define GTEST_DEV_EMAIL_ "googletestframework@@googlegroups.com" +#define GTEST_FLAG_PREFIX_ "gtest_" +#define GTEST_FLAG_PREFIX_DASH_ "gtest-" +#define GTEST_FLAG_PREFIX_UPPER_ "GTEST_" +#define GTEST_NAME_ "Google Test" +#define GTEST_PROJECT_URL_ "http://code.google.com/p/googletest/" + +// Determines the version of gcc that is used to compile this. +#ifdef __GNUC__ +// 40302 means version 4.3.2. +#define GTEST_GCC_VER_ \ + (__GNUC__*10000 + __GNUC_MINOR__*100 + __GNUC_PATCHLEVEL__) +#endif // __GNUC__ + +// Determines the platform on which Google Test is compiled. +#ifdef __CYGWIN__ +#define GTEST_OS_CYGWIN 1 +#elif defined __SYMBIAN32__ +#define GTEST_OS_SYMBIAN 1 +#elif defined _WIN32 +#define GTEST_OS_WINDOWS 1 +#ifdef _WIN32_WCE +#define GTEST_OS_WINDOWS_MOBILE 1 +#elif defined(__MINGW__) || defined(__MINGW32__) +#define GTEST_OS_WINDOWS_MINGW 1 +#else +#define GTEST_OS_WINDOWS_DESKTOP 1 +#endif // _WIN32_WCE +#elif defined __APPLE__ +#define GTEST_OS_MAC 1 +#elif defined __linux__ +#define GTEST_OS_LINUX 1 +#elif defined __MVS__ +#define GTEST_OS_ZOS 1 +#elif defined(__sun) && defined(__SVR4) +#define GTEST_OS_SOLARIS 1 +#elif defined(_AIX) +#define GTEST_OS_AIX 1 +#endif // __CYGWIN__ + +#if GTEST_OS_CYGWIN || GTEST_OS_LINUX || GTEST_OS_MAC || GTEST_OS_SYMBIAN || \ + GTEST_OS_SOLARIS || GTEST_OS_AIX + +// On some platforms, needs someone to define size_t, and +// won't compile otherwise. We can #include it here as we already +// included , which is guaranteed to define size_t through +// . +#include // NOLINT +#include // NOLINT +#include // NOLINT +#include // NOLINT +#include // NOLINT + +#define GTEST_USES_POSIX_RE 1 + +#elif GTEST_OS_WINDOWS + +#if !GTEST_OS_WINDOWS_MOBILE +#include // NOLINT +#include // NOLINT +#endif + +// is not available on Windows. Use our own simple regex +// implementation instead. +#define GTEST_USES_SIMPLE_RE 1 + +#else + +// may not be available on this platform. Use our own +// simple regex implementation instead. +#define GTEST_USES_SIMPLE_RE 1 + +#endif // GTEST_OS_CYGWIN || GTEST_OS_LINUX || GTEST_OS_MAC || + // GTEST_OS_SYMBIAN || GTEST_OS_SOLARIS || GTEST_OS_AIX + +#ifndef GTEST_HAS_EXCEPTIONS +// The user didn't tell us whether exceptions are enabled, so we need +// to figure it out. +#if defined(_MSC_VER) || defined(__BORLANDC__) +// MSVC's and C++Builder's implementations of the STL use the _HAS_EXCEPTIONS +// macro to enable exceptions, so we'll do the same. +// Assumes that exceptions are enabled by default. +#ifndef _HAS_EXCEPTIONS +#define _HAS_EXCEPTIONS 1 +#endif // _HAS_EXCEPTIONS +#define GTEST_HAS_EXCEPTIONS _HAS_EXCEPTIONS +#elif defined(__GNUC__) && __EXCEPTIONS +// gcc defines __EXCEPTIONS to 1 iff exceptions are enabled. +#define GTEST_HAS_EXCEPTIONS 1 +#elif defined(__SUNPRO_CC) +// Sun Pro CC supports exceptions. However, there is no compile-time way of +// detecting whether they are enabled or not. Therefore, we assume that +// they are enabled unless the user tells us otherwise. +#define GTEST_HAS_EXCEPTIONS 1 +#elif defined(__IBMCPP__) && __EXCEPTIONS +// xlC defines __EXCEPTIONS to 1 iff exceptions are enabled. +#define GTEST_HAS_EXCEPTIONS 1 +#else +// For other compilers, we assume exceptions are disabled to be +// conservative. +#define GTEST_HAS_EXCEPTIONS 0 +#endif // defined(_MSC_VER) || defined(__BORLANDC__) +#endif // GTEST_HAS_EXCEPTIONS + +#if !defined(GTEST_HAS_STD_STRING) +// Even though we don't use this macro any longer, we keep it in case +// some clients still depend on it. +#define GTEST_HAS_STD_STRING 1 +#elif !GTEST_HAS_STD_STRING +// The user told us that ::std::string isn't available. +#error "Google Test cannot be used where ::std::string isn't available." +#endif // !defined(GTEST_HAS_STD_STRING) + +#ifndef GTEST_HAS_GLOBAL_STRING +// The user didn't tell us whether ::string is available, so we need +// to figure it out. + +#define GTEST_HAS_GLOBAL_STRING 0 + +#endif // GTEST_HAS_GLOBAL_STRING + +#ifndef GTEST_HAS_STD_WSTRING +// The user didn't tell us whether ::std::wstring is available, so we need +// to figure it out. +// TODO(wan@google.com): uses autoconf to detect whether ::std::wstring +// is available. + +// Cygwin 1.5 and below doesn't support ::std::wstring. +// Cygwin 1.7 might add wstring support; this should be updated when clear. +// Solaris' libc++ doesn't support it either. +#define GTEST_HAS_STD_WSTRING (!(GTEST_OS_CYGWIN || GTEST_OS_SOLARIS)) + +#endif // GTEST_HAS_STD_WSTRING + +#ifndef GTEST_HAS_GLOBAL_WSTRING +// The user didn't tell us whether ::wstring is available, so we need +// to figure it out. +#define GTEST_HAS_GLOBAL_WSTRING \ + (GTEST_HAS_STD_WSTRING && GTEST_HAS_GLOBAL_STRING) +#endif // GTEST_HAS_GLOBAL_WSTRING + +// Determines whether RTTI is available. +#ifndef GTEST_HAS_RTTI +// The user didn't tell us whether RTTI is enabled, so we need to +// figure it out. + +#ifdef _MSC_VER + +#ifdef _CPPRTTI // MSVC defines this macro iff RTTI is enabled. +#define GTEST_HAS_RTTI 1 +#else +#define GTEST_HAS_RTTI 0 +#endif + +// Starting with version 4.3.2, gcc defines __GXX_RTTI iff RTTI is enabled. +#elif defined(__GNUC__) && (GTEST_GCC_VER_ >= 40302) + +#ifdef __GXX_RTTI +#define GTEST_HAS_RTTI 1 +#else +#define GTEST_HAS_RTTI 0 +#endif // __GXX_RTTI + +// Starting with version 9.0 IBM Visual Age defines __RTTI_ALL__ to 1 if +// both the typeid and dynamic_cast features are present. +#elif defined(__IBMCPP__) && (__IBMCPP__ >= 900) + +#ifdef __RTTI_ALL__ +#define GTEST_HAS_RTTI 1 +#else +#define GTEST_HAS_RTTI 0 +#endif + +#else + +// For all other compilers, we assume RTTI is enabled. +#define GTEST_HAS_RTTI 1 + +#endif // _MSC_VER + +#endif // GTEST_HAS_RTTI + +// It's this header's responsibility to #include when RTTI +// is enabled. +#if GTEST_HAS_RTTI +#include +#endif + +// Determines whether Google Test can use the pthreads library. +#ifndef GTEST_HAS_PTHREAD +// The user didn't tell us explicitly, so we assume pthreads support is +// available on Linux and Mac. +// +// To disable threading support in Google Test, add -DGTEST_HAS_PTHREAD=0 +// to your compiler flags. +#define GTEST_HAS_PTHREAD (GTEST_OS_LINUX || GTEST_OS_MAC) +#endif // GTEST_HAS_PTHREAD + +// Determines whether Google Test can use tr1/tuple. You can define +// this macro to 0 to prevent Google Test from using tuple (any +// feature depending on tuple with be disabled in this mode). +#ifndef GTEST_HAS_TR1_TUPLE +// The user didn't tell us not to do it, so we assume it's OK. +#define GTEST_HAS_TR1_TUPLE 1 +#endif // GTEST_HAS_TR1_TUPLE + +// Determines whether Google Test's own tr1 tuple implementation +// should be used. +#ifndef GTEST_USE_OWN_TR1_TUPLE +// The user didn't tell us, so we need to figure it out. + +// We use our own TR1 tuple if we aren't sure the user has an +// implementation of it already. At this time, GCC 4.0.0+ and MSVC +// 2010 are the only mainstream compilers that come with a TR1 tuple +// implementation. NVIDIA's CUDA NVCC compiler pretends to be GCC by +// defining __GNUC__ and friends, but cannot compile GCC's tuple +// implementation. MSVC 2008 (9.0) provides TR1 tuple in a 323 MB +// Feature Pack download, which we cannot assume the user has. +#if (defined(__GNUC__) && !defined(__CUDACC__) && (GTEST_GCC_VER_ >= 40000)) \ + || _MSC_VER >= 1600 +#define GTEST_USE_OWN_TR1_TUPLE 0 +#else +#define GTEST_USE_OWN_TR1_TUPLE 1 +#endif + +#endif // GTEST_USE_OWN_TR1_TUPLE + +// To avoid conditional compilation everywhere, we make it +// gtest-port.h's responsibility to #include the header implementing +// tr1/tuple. +#if GTEST_HAS_TR1_TUPLE + +#if GTEST_USE_OWN_TR1_TUPLE +#include +#elif GTEST_OS_SYMBIAN + +// On Symbian, BOOST_HAS_TR1_TUPLE causes Boost's TR1 tuple library to +// use STLport's tuple implementation, which unfortunately doesn't +// work as the copy of STLport distributed with Symbian is incomplete. +// By making sure BOOST_HAS_TR1_TUPLE is undefined, we force Boost to +// use its own tuple implementation. +#ifdef BOOST_HAS_TR1_TUPLE +#undef BOOST_HAS_TR1_TUPLE +#endif // BOOST_HAS_TR1_TUPLE + +// This prevents , which defines +// BOOST_HAS_TR1_TUPLE, from being #included by Boost's . +#define BOOST_TR1_DETAIL_CONFIG_HPP_INCLUDED +#include + +#elif defined(__GNUC__) && (GTEST_GCC_VER_ >= 40000) +// GCC 4.0+ implements tr1/tuple in the header. This does +// not conform to the TR1 spec, which requires the header to be . + +#if !GTEST_HAS_RTTI && GTEST_GCC_VER_ < 40302 +// Until version 4.3.2, gcc has a bug that causes , +// which is #included by , to not compile when RTTI is +// disabled. _TR1_FUNCTIONAL is the header guard for +// . Hence the following #define is a hack to prevent +// from being included. +#define _TR1_FUNCTIONAL 1 +#include +#undef _TR1_FUNCTIONAL // Allows the user to #include + // if he chooses to. +#else +#include // NOLINT +#endif // !GTEST_HAS_RTTI && GTEST_GCC_VER_ < 40302 + +#else +// If the compiler is not GCC 4.0+, we assume the user is using a +// spec-conforming TR1 implementation. +#include // NOLINT +#endif // GTEST_USE_OWN_TR1_TUPLE + +#endif // GTEST_HAS_TR1_TUPLE + +// Determines whether clone(2) is supported. +// Usually it will only be available on Linux, excluding +// Linux on the Itanium architecture. +// Also see http://linux.die.net/man/2/clone. +#ifndef GTEST_HAS_CLONE +// The user didn't tell us, so we need to figure it out. + +#if GTEST_OS_LINUX && !defined(__ia64__) +#define GTEST_HAS_CLONE 1 +#else +#define GTEST_HAS_CLONE 0 +#endif // GTEST_OS_LINUX && !defined(__ia64__) + +#endif // GTEST_HAS_CLONE + +// Determines whether to support stream redirection. This is used to test +// output correctness and to implement death tests. +#if !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_SYMBIAN +#define GTEST_HAS_STREAM_REDIRECTION_ 1 +#endif // !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_SYMBIAN + +// Determines whether to support death tests. +// Google Test does not support death tests for VC 7.1 and earlier as +// abort() in a VC 7.1 application compiled as GUI in debug config +// pops up a dialog window that cannot be suppressed programmatically. +#if (GTEST_OS_LINUX || GTEST_OS_MAC || GTEST_OS_CYGWIN || GTEST_OS_SOLARIS || \ + (GTEST_OS_WINDOWS_DESKTOP && _MSC_VER >= 1400) || \ + GTEST_OS_WINDOWS_MINGW || GTEST_OS_AIX) +#define GTEST_HAS_DEATH_TEST 1 +#include // NOLINT +#endif + +// We don't support MSVC 7.1 with exceptions disabled now. Therefore +// all the compilers we care about are adequate for supporting +// value-parameterized tests. +#define GTEST_HAS_PARAM_TEST 1 + +// Determines whether to support type-driven tests. + +// Typed tests need and variadic macros, which GCC, VC++ 8.0, +// Sun Pro CC, and IBM Visual Age support. +#if defined(__GNUC__) || (_MSC_VER >= 1400) || defined(__SUNPRO_CC) || \ + defined(__IBMCPP__) +#define GTEST_HAS_TYPED_TEST 1 +#define GTEST_HAS_TYPED_TEST_P 1 +#endif + +// Determines whether to support Combine(). This only makes sense when +// value-parameterized tests are enabled. The implementation doesn't +// work on Sun Studio since it doesn't understand templated conversion +// operators. +#if GTEST_HAS_PARAM_TEST && GTEST_HAS_TR1_TUPLE && !defined(__SUNPRO_CC) +#define GTEST_HAS_COMBINE 1 +#endif + +// Determines whether the system compiler uses UTF-16 for encoding wide strings. +#define GTEST_WIDE_STRING_USES_UTF16_ \ + (GTEST_OS_WINDOWS || GTEST_OS_CYGWIN || GTEST_OS_SYMBIAN || GTEST_OS_AIX) + +// Defines some utility macros. + +// The GNU compiler emits a warning if nested "if" statements are followed by +// an "else" statement and braces are not used to explicitly disambiguate the +// "else" binding. This leads to problems with code like: +// +// if (gate) +// ASSERT_*(condition) << "Some message"; +// +// The "switch (0) case 0:" idiom is used to suppress this. +#ifdef __INTEL_COMPILER +#define GTEST_AMBIGUOUS_ELSE_BLOCKER_ +#else +#define GTEST_AMBIGUOUS_ELSE_BLOCKER_ switch (0) case 0: // NOLINT +#endif + +// Use this annotation at the end of a struct/class definition to +// prevent the compiler from optimizing away instances that are never +// used. This is useful when all interesting logic happens inside the +// c'tor and / or d'tor. Example: +// +// struct Foo { +// Foo() { ... } +// } GTEST_ATTRIBUTE_UNUSED_; +// +// Also use it after a variable or parameter declaration to tell the +// compiler the variable/parameter does not have to be used. +#if defined(__GNUC__) && !defined(COMPILER_ICC) +#define GTEST_ATTRIBUTE_UNUSED_ __attribute__ ((unused)) +#else +#define GTEST_ATTRIBUTE_UNUSED_ +#endif + +// A macro to disallow operator= +// This should be used in the private: declarations for a class. +#define GTEST_DISALLOW_ASSIGN_(type)\ + void operator=(type const &) + +// A macro to disallow copy constructor and operator= +// This should be used in the private: declarations for a class. +#define GTEST_DISALLOW_COPY_AND_ASSIGN_(type)\ + type(type const &);\ + GTEST_DISALLOW_ASSIGN_(type) + +// Tell the compiler to warn about unused return values for functions declared +// with this macro. The macro should be used on function declarations +// following the argument list: +// +// Sprocket* AllocateSprocket() GTEST_MUST_USE_RESULT_; +#if defined(__GNUC__) && (GTEST_GCC_VER_ >= 30400) && !defined(COMPILER_ICC) +#define GTEST_MUST_USE_RESULT_ __attribute__ ((warn_unused_result)) +#else +#define GTEST_MUST_USE_RESULT_ +#endif // __GNUC__ && (GTEST_GCC_VER_ >= 30400) && !COMPILER_ICC + +// Determine whether the compiler supports Microsoft's Structured Exception +// Handling. This is supported by several Windows compilers but generally +// does not exist on any other system. +#ifndef GTEST_HAS_SEH +// The user didn't tell us, so we need to figure it out. + +#if defined(_MSC_VER) || defined(__BORLANDC__) +// These two compilers are known to support SEH. +#define GTEST_HAS_SEH 1 +#else +// Assume no SEH. +#define GTEST_HAS_SEH 0 +#endif + +#endif // GTEST_HAS_SEH + +#ifdef _MSC_VER + +#if GTEST_LINKED_AS_SHARED_LIBRARY +#define GTEST_API_ __declspec(dllimport) +#elif GTEST_CREATE_SHARED_LIBRARY +#define GTEST_API_ __declspec(dllexport) +#endif + +#endif // _MSC_VER + +#ifndef GTEST_API_ +#define GTEST_API_ +#endif + +namespace testing { + +class Message; + +namespace internal { + +class String; + +typedef ::std::stringstream StrStream; + +// A helper for suppressing warnings on constant condition. It just +// returns 'condition'. +GTEST_API_ bool IsTrue(bool condition); + +// Defines scoped_ptr. + +// This implementation of scoped_ptr is PARTIAL - it only contains +// enough stuff to satisfy Google Test's need. +template +class scoped_ptr { + public: + typedef T element_type; + + explicit scoped_ptr(T* p = NULL) : ptr_(p) {} + ~scoped_ptr() { reset(); } + + T& operator*() const { return *ptr_; } + T* operator->() const { return ptr_; } + T* get() const { return ptr_; } + + T* release() { + T* const ptr = ptr_; + ptr_ = NULL; + return ptr; + } + + void reset(T* p = NULL) { + if (p != ptr_) { + if (IsTrue(sizeof(T) > 0)) { // Makes sure T is a complete type. + delete ptr_; + } + ptr_ = p; + } + } + private: + T* ptr_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(scoped_ptr); +}; + +// Defines RE. + +// A simple C++ wrapper for . It uses the POSIX Extended +// Regular Expression syntax. +class GTEST_API_ RE { + public: + // A copy constructor is required by the Standard to initialize object + // references from r-values. + RE(const RE& other) { Init(other.pattern()); } + + // Constructs an RE from a string. + RE(const ::std::string& regex) { Init(regex.c_str()); } // NOLINT + +#if GTEST_HAS_GLOBAL_STRING + RE(const ::string& regex) { Init(regex.c_str()); } // NOLINT +#endif // GTEST_HAS_GLOBAL_STRING + + RE(const char* regex) { Init(regex); } // NOLINT + ~RE(); + + // Returns the string representation of the regex. + const char* pattern() const { return pattern_; } + + // FullMatch(str, re) returns true iff regular expression re matches + // the entire str. + // PartialMatch(str, re) returns true iff regular expression re + // matches a substring of str (including str itself). + // + // TODO(wan@google.com): make FullMatch() and PartialMatch() work + // when str contains NUL characters. + static bool FullMatch(const ::std::string& str, const RE& re) { + return FullMatch(str.c_str(), re); + } + static bool PartialMatch(const ::std::string& str, const RE& re) { + return PartialMatch(str.c_str(), re); + } + +#if GTEST_HAS_GLOBAL_STRING + static bool FullMatch(const ::string& str, const RE& re) { + return FullMatch(str.c_str(), re); + } + static bool PartialMatch(const ::string& str, const RE& re) { + return PartialMatch(str.c_str(), re); + } +#endif // GTEST_HAS_GLOBAL_STRING + + static bool FullMatch(const char* str, const RE& re); + static bool PartialMatch(const char* str, const RE& re); + + private: + void Init(const char* regex); + + // We use a const char* instead of a string, as Google Test may be used + // where string is not available. We also do not use Google Test's own + // String type here, in order to simplify dependencies between the + // files. + const char* pattern_; + bool is_valid_; +#if GTEST_USES_POSIX_RE + regex_t full_regex_; // For FullMatch(). + regex_t partial_regex_; // For PartialMatch(). +#else // GTEST_USES_SIMPLE_RE + const char* full_pattern_; // For FullMatch(); +#endif + + GTEST_DISALLOW_ASSIGN_(RE); +}; + +// Defines logging utilities: +// GTEST_LOG_(severity) - logs messages at the specified severity level. The +// message itself is streamed into the macro. +// LogToStderr() - directs all log messages to stderr. +// FlushInfoLog() - flushes informational log messages. + +enum GTestLogSeverity { + GTEST_INFO, + GTEST_WARNING, + GTEST_ERROR, + GTEST_FATAL +}; + +// Formats log entry severity, provides a stream object for streaming the +// log message, and terminates the message with a newline when going out of +// scope. +class GTEST_API_ GTestLog { + public: + GTestLog(GTestLogSeverity severity, const char* file, int line); + + // Flushes the buffers and, if severity is GTEST_FATAL, aborts the program. + ~GTestLog(); + + ::std::ostream& GetStream() { return ::std::cerr; } + + private: + const GTestLogSeverity severity_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestLog); +}; + +#define GTEST_LOG_(severity) \ + ::testing::internal::GTestLog(::testing::internal::GTEST_##severity, \ + __FILE__, __LINE__).GetStream() + +inline void LogToStderr() {} +inline void FlushInfoLog() { fflush(NULL); } + +// INTERNAL IMPLEMENTATION - DO NOT USE. +// +// GTEST_CHECK_ is an all-mode assert. It aborts the program if the condition +// is not satisfied. +// Synopsys: +// GTEST_CHECK_(boolean_condition); +// or +// GTEST_CHECK_(boolean_condition) << "Additional message"; +// +// This checks the condition and if the condition is not satisfied +// it prints message about the condition violation, including the +// condition itself, plus additional message streamed into it, if any, +// and then it aborts the program. It aborts the program irrespective of +// whether it is built in the debug mode or not. +#define GTEST_CHECK_(condition) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::IsTrue(condition)) \ + ; \ + else \ + GTEST_LOG_(FATAL) << "Condition " #condition " failed. " + +// An all-mode assert to verify that the given POSIX-style function +// call returns 0 (indicating success). Known limitation: this +// doesn't expand to a balanced 'if' statement, so enclose the macro +// in {} if you need to use it as the only statement in an 'if' +// branch. +#define GTEST_CHECK_POSIX_SUCCESS_(posix_call) \ + if (const int gtest_error = (posix_call)) \ + GTEST_LOG_(FATAL) << #posix_call << "failed with error " \ + << gtest_error + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Downcasts the pointer of type Base to Derived. +// Derived must be a subclass of Base. The parameter MUST +// point to a class of type Derived, not any subclass of it. +// When RTTI is available, the function performs a runtime +// check to enforce this. +template +Derived* CheckedDowncastToActualType(Base* base) { +#if GTEST_HAS_RTTI + GTEST_CHECK_(typeid(*base) == typeid(Derived)); + return dynamic_cast(base); // NOLINT +#else + return static_cast(base); // Poor man's downcast. +#endif +} + +#if GTEST_HAS_STREAM_REDIRECTION_ + +// Defines the stderr capturer: +// CaptureStdout - starts capturing stdout. +// GetCapturedStdout - stops capturing stdout and returns the captured string. +// CaptureStderr - starts capturing stderr. +// GetCapturedStderr - stops capturing stderr and returns the captured string. +// +GTEST_API_ void CaptureStdout(); +GTEST_API_ String GetCapturedStdout(); +GTEST_API_ void CaptureStderr(); +GTEST_API_ String GetCapturedStderr(); + +#endif // GTEST_HAS_STREAM_REDIRECTION_ + + +#if GTEST_HAS_DEATH_TEST + +// A copy of all command line arguments. Set by InitGoogleTest(). +extern ::std::vector g_argvs; + +// GTEST_HAS_DEATH_TEST implies we have ::std::string. +const ::std::vector& GetArgvs(); + +#endif // GTEST_HAS_DEATH_TEST + +// Defines synchronization primitives. + +#if GTEST_HAS_PTHREAD + +// Sleeps for (roughly) n milli-seconds. This function is only for +// testing Google Test's own constructs. Don't use it in user tests, +// either directly or indirectly. +inline void SleepMilliseconds(int n) { + const timespec time = { + 0, // 0 seconds. + n * 1000L * 1000L, // And n ms. + }; + nanosleep(&time, NULL); +} + +// Allows a controller thread to pause execution of newly created +// threads until notified. Instances of this class must be created +// and destroyed in the controller thread. +// +// This class is only for testing Google Test's own constructs. Do not +// use it in user tests, either directly or indirectly. +class Notification { + public: + Notification() : notified_(false) {} + + // Notifies all threads created with this notification to start. Must + // be called from the controller thread. + void Notify() { notified_ = true; } + + // Blocks until the controller thread notifies. Must be called from a test + // thread. + void WaitForNotification() { + while(!notified_) { + SleepMilliseconds(10); + } + } + + private: + volatile bool notified_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(Notification); +}; + +// As a C-function, ThreadFuncWithCLinkage cannot be templated itself. +// Consequently, it cannot select a correct instantiation of ThreadWithParam +// in order to call its Run(). Introducing ThreadWithParamBase as a +// non-templated base class for ThreadWithParam allows us to bypass this +// problem. +class ThreadWithParamBase { + public: + virtual ~ThreadWithParamBase() {} + virtual void Run() = 0; +}; + +// pthread_create() accepts a pointer to a function type with the C linkage. +// According to the Standard (7.5/1), function types with different linkages +// are different even if they are otherwise identical. Some compilers (for +// example, SunStudio) treat them as different types. Since class methods +// cannot be defined with C-linkage we need to define a free C-function to +// pass into pthread_create(). +extern "C" inline void* ThreadFuncWithCLinkage(void* thread) { + static_cast(thread)->Run(); + return NULL; +} + +// Helper class for testing Google Test's multi-threading constructs. +// To use it, write: +// +// void ThreadFunc(int param) { /* Do things with param */ } +// Notification thread_can_start; +// ... +// // The thread_can_start parameter is optional; you can supply NULL. +// ThreadWithParam thread(&ThreadFunc, 5, &thread_can_start); +// thread_can_start.Notify(); +// +// These classes are only for testing Google Test's own constructs. Do +// not use them in user tests, either directly or indirectly. +template +class ThreadWithParam : public ThreadWithParamBase { + public: + typedef void (*UserThreadFunc)(T); + + ThreadWithParam( + UserThreadFunc func, T param, Notification* thread_can_start) + : func_(func), + param_(param), + thread_can_start_(thread_can_start), + finished_(false) { + ThreadWithParamBase* const base = this; + // The thread can be created only after all fields except thread_ + // have been initialized. + GTEST_CHECK_POSIX_SUCCESS_( + pthread_create(&thread_, 0, &ThreadFuncWithCLinkage, base)); + } + ~ThreadWithParam() { Join(); } + + void Join() { + if (!finished_) { + GTEST_CHECK_POSIX_SUCCESS_(pthread_join(thread_, 0)); + finished_ = true; + } + } + + virtual void Run() { + if (thread_can_start_ != NULL) + thread_can_start_->WaitForNotification(); + func_(param_); + } + + private: + const UserThreadFunc func_; // User-supplied thread function. + const T param_; // User-supplied parameter to the thread function. + // When non-NULL, used to block execution until the controller thread + // notifies. + Notification* const thread_can_start_; + bool finished_; // true iff we know that the thread function has finished. + pthread_t thread_; // The native thread object. + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParam); +}; + +// gtest-port.h guarantees to #include when GTEST_HAS_PTHREAD is +// true. +#include + +// MutexBase and Mutex implement mutex on pthreads-based platforms. They +// are used in conjunction with class MutexLock: +// +// Mutex mutex; +// ... +// MutexLock lock(&mutex); // Acquires the mutex and releases it at the end +// // of the current scope. +// +// MutexBase implements behavior for both statically and dynamically +// allocated mutexes. Do not use MutexBase directly. Instead, write +// the following to define a static mutex: +// +// GTEST_DEFINE_STATIC_MUTEX_(g_some_mutex); +// +// You can forward declare a static mutex like this: +// +// GTEST_DECLARE_STATIC_MUTEX_(g_some_mutex); +// +// To create a dynamic mutex, just define an object of type Mutex. +class MutexBase { + public: + // Acquires this mutex. + void Lock() { + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_lock(&mutex_)); + owner_ = pthread_self(); + } + + // Releases this mutex. + void Unlock() { + // We don't protect writing to owner_ here, as it's the caller's + // responsibility to ensure that the current thread holds the + // mutex when this is called. + owner_ = 0; + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_unlock(&mutex_)); + } + + // Does nothing if the current thread holds the mutex. Otherwise, crashes + // with high probability. + void AssertHeld() const { + GTEST_CHECK_(owner_ == pthread_self()) + << "The current thread is not holding the mutex @" << this; + } + + // A static mutex may be used before main() is entered. It may even + // be used before the dynamic initialization stage. Therefore we + // must be able to initialize a static mutex object at link time. + // This means MutexBase has to be a POD and its member variables + // have to be public. + public: + pthread_mutex_t mutex_; // The underlying pthread mutex. + pthread_t owner_; // The thread holding the mutex; 0 means no one holds it. +}; + +// Forward-declares a static mutex. +#define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ + extern ::testing::internal::MutexBase mutex + +// Defines and statically (i.e. at link time) initializes a static mutex. +#define GTEST_DEFINE_STATIC_MUTEX_(mutex) \ + ::testing::internal::MutexBase mutex = { PTHREAD_MUTEX_INITIALIZER, 0 } + +// The Mutex class can only be used for mutexes created at runtime. It +// shares its API with MutexBase otherwise. +class Mutex : public MutexBase { + public: + Mutex() { + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_init(&mutex_, NULL)); + owner_ = 0; + } + ~Mutex() { + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_destroy(&mutex_)); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(Mutex); +}; + +// We cannot name this class MutexLock as the ctor declaration would +// conflict with a macro named MutexLock, which is defined on some +// platforms. Hence the typedef trick below. +class GTestMutexLock { + public: + explicit GTestMutexLock(MutexBase* mutex) + : mutex_(mutex) { mutex_->Lock(); } + + ~GTestMutexLock() { mutex_->Unlock(); } + + private: + MutexBase* const mutex_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestMutexLock); +}; + +typedef GTestMutexLock MutexLock; + +// Helpers for ThreadLocal. + +// pthread_key_create() requires DeleteThreadLocalValue() to have +// C-linkage. Therefore it cannot be templatized to access +// ThreadLocal. Hence the need for class +// ThreadLocalValueHolderBase. +class ThreadLocalValueHolderBase { + public: + virtual ~ThreadLocalValueHolderBase() {} +}; + +// Called by pthread to delete thread-local data stored by +// pthread_setspecific(). +extern "C" inline void DeleteThreadLocalValue(void* value_holder) { + delete static_cast(value_holder); +} + +// Implements thread-local storage on pthreads-based systems. +// +// // Thread 1 +// ThreadLocal tl(100); // 100 is the default value for each thread. +// +// // Thread 2 +// tl.set(150); // Changes the value for thread 2 only. +// EXPECT_EQ(150, tl.get()); +// +// // Thread 1 +// EXPECT_EQ(100, tl.get()); // In thread 1, tl has the original value. +// tl.set(200); +// EXPECT_EQ(200, tl.get()); +// +// The template type argument T must have a public copy constructor. +// In addition, the default ThreadLocal constructor requires T to have +// a public default constructor. +// +// An object managed for a thread by a ThreadLocal instance is deleted +// when the thread exits. Or, if the ThreadLocal instance dies in +// that thread, when the ThreadLocal dies. It's the user's +// responsibility to ensure that all other threads using a ThreadLocal +// have exited when it dies, or the per-thread objects for those +// threads will not be deleted. +// +// Google Test only uses global ThreadLocal objects. That means they +// will die after main() has returned. Therefore, no per-thread +// object managed by Google Test will be leaked as long as all threads +// using Google Test have exited when main() returns. +template +class ThreadLocal { + public: + ThreadLocal() : key_(CreateKey()), + default_() {} + explicit ThreadLocal(const T& value) : key_(CreateKey()), + default_(value) {} + + ~ThreadLocal() { + // Destroys the managed object for the current thread, if any. + DeleteThreadLocalValue(pthread_getspecific(key_)); + + // Releases resources associated with the key. This will *not* + // delete managed objects for other threads. + GTEST_CHECK_POSIX_SUCCESS_(pthread_key_delete(key_)); + } + + T* pointer() { return GetOrCreateValue(); } + const T* pointer() const { return GetOrCreateValue(); } + const T& get() const { return *pointer(); } + void set(const T& value) { *pointer() = value; } + + private: + // Holds a value of type T. + class ValueHolder : public ThreadLocalValueHolderBase { + public: + explicit ValueHolder(const T& value) : value_(value) {} + + T* pointer() { return &value_; } + + private: + T value_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolder); + }; + + static pthread_key_t CreateKey() { + pthread_key_t key; + // When a thread exits, DeleteThreadLocalValue() will be called on + // the object managed for that thread. + GTEST_CHECK_POSIX_SUCCESS_( + pthread_key_create(&key, &DeleteThreadLocalValue)); + return key; + } + + T* GetOrCreateValue() const { + ThreadLocalValueHolderBase* const holder = + static_cast(pthread_getspecific(key_)); + if (holder != NULL) { + return CheckedDowncastToActualType(holder)->pointer(); + } + + ValueHolder* const new_holder = new ValueHolder(default_); + ThreadLocalValueHolderBase* const holder_base = new_holder; + GTEST_CHECK_POSIX_SUCCESS_(pthread_setspecific(key_, holder_base)); + return new_holder->pointer(); + } + + // A key pthreads uses for looking up per-thread values. + const pthread_key_t key_; + const T default_; // The default value for each thread. + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocal); +}; + +#define GTEST_IS_THREADSAFE 1 + +#else // GTEST_HAS_PTHREAD + +// A dummy implementation of synchronization primitives (mutex, lock, +// and thread-local variable). Necessary for compiling Google Test where +// mutex is not supported - using Google Test in multiple threads is not +// supported on such platforms. + +class Mutex { + public: + Mutex() {} + void AssertHeld() const {} +}; + +#define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ + extern ::testing::internal::Mutex mutex + +#define GTEST_DEFINE_STATIC_MUTEX_(mutex) ::testing::internal::Mutex mutex + +class GTestMutexLock { + public: + explicit GTestMutexLock(Mutex*) {} // NOLINT +}; + +typedef GTestMutexLock MutexLock; + +template +class ThreadLocal { + public: + ThreadLocal() : value_() {} + explicit ThreadLocal(const T& value) : value_(value) {} + T* pointer() { return &value_; } + const T* pointer() const { return &value_; } + const T& get() const { return value_; } + void set(const T& value) { value_ = value; } + private: + T value_; +}; + +// The above synchronization primitives have dummy implementations. +// Therefore Google Test is not thread-safe. +#define GTEST_IS_THREADSAFE 0 + +#endif // GTEST_HAS_PTHREAD + +// Returns the number of threads running in the process, or 0 to indicate that +// we cannot detect it. +GTEST_API_ size_t GetThreadCount(); + +// Passing non-POD classes through ellipsis (...) crashes the ARM +// compiler and generates a warning in Sun Studio. The Nokia Symbian +// and the IBM XL C/C++ compiler try to instantiate a copy constructor +// for objects passed through ellipsis (...), failing for uncopyable +// objects. We define this to ensure that only POD is passed through +// ellipsis on these systems. +#if defined(__SYMBIAN32__) || defined(__IBMCPP__) || defined(__SUNPRO_CC) +// We lose support for NULL detection where the compiler doesn't like +// passing non-POD classes through ellipsis (...). +#define GTEST_ELLIPSIS_NEEDS_POD_ 1 +#else +#define GTEST_CAN_COMPARE_NULL 1 +#endif + +// The Nokia Symbian and IBM XL C/C++ compilers cannot decide between +// const T& and const T* in a function template. These compilers +// _can_ decide between class template specializations for T and T*, +// so a tr1::type_traits-like is_pointer works. +#if defined(__SYMBIAN32__) || defined(__IBMCPP__) +#define GTEST_NEEDS_IS_POINTER_ 1 +#endif + +template +struct bool_constant { + typedef bool_constant type; + static const bool value = bool_value; +}; +template const bool bool_constant::value; + +typedef bool_constant false_type; +typedef bool_constant true_type; + +template +struct is_pointer : public false_type {}; + +template +struct is_pointer : public true_type {}; + +#if GTEST_OS_WINDOWS +#define GTEST_PATH_SEP_ "\\" +#define GTEST_HAS_ALT_PATH_SEP_ 1 +// The biggest signed integer type the compiler supports. +typedef __int64 BiggestInt; +#else +#define GTEST_PATH_SEP_ "/" +#define GTEST_HAS_ALT_PATH_SEP_ 0 +typedef long long BiggestInt; // NOLINT +#endif // GTEST_OS_WINDOWS + +// The testing::internal::posix namespace holds wrappers for common +// POSIX functions. These wrappers hide the differences between +// Windows/MSVC and POSIX systems. Since some compilers define these +// standard functions as macros, the wrapper cannot have the same name +// as the wrapped function. + +namespace posix { + +// Functions with a different name on Windows. + +#if GTEST_OS_WINDOWS + +typedef struct _stat StatStruct; + +#ifdef __BORLANDC__ +inline int IsATTY(int fd) { return isatty(fd); } +inline int StrCaseCmp(const char* s1, const char* s2) { + return stricmp(s1, s2); +} +inline char* StrDup(const char* src) { return strdup(src); } +#else // !__BORLANDC__ +#if GTEST_OS_WINDOWS_MOBILE +inline int IsATTY(int /* fd */) { return 0; } +#else +inline int IsATTY(int fd) { return _isatty(fd); } +#endif // GTEST_OS_WINDOWS_MOBILE +inline int StrCaseCmp(const char* s1, const char* s2) { + return _stricmp(s1, s2); +} +inline char* StrDup(const char* src) { return _strdup(src); } +#endif // __BORLANDC__ + +#if GTEST_OS_WINDOWS_MOBILE +inline int FileNo(FILE* file) { return reinterpret_cast(_fileno(file)); } +// Stat(), RmDir(), and IsDir() are not needed on Windows CE at this +// time and thus not defined there. +#else +inline int FileNo(FILE* file) { return _fileno(file); } +inline int Stat(const char* path, StatStruct* buf) { return _stat(path, buf); } +inline int RmDir(const char* dir) { return _rmdir(dir); } +inline bool IsDir(const StatStruct& st) { + return (_S_IFDIR & st.st_mode) != 0; +} +#endif // GTEST_OS_WINDOWS_MOBILE + +#else + +typedef struct stat StatStruct; + +inline int FileNo(FILE* file) { return fileno(file); } +inline int IsATTY(int fd) { return isatty(fd); } +inline int Stat(const char* path, StatStruct* buf) { return stat(path, buf); } +inline int StrCaseCmp(const char* s1, const char* s2) { + return strcasecmp(s1, s2); +} +inline char* StrDup(const char* src) { return strdup(src); } +inline int RmDir(const char* dir) { return rmdir(dir); } +inline bool IsDir(const StatStruct& st) { return S_ISDIR(st.st_mode); } + +#endif // GTEST_OS_WINDOWS + +// Functions deprecated by MSVC 8.0. + +#ifdef _MSC_VER +// Temporarily disable warning 4996 (deprecated function). +#pragma warning(push) +#pragma warning(disable:4996) +#endif + +inline const char* StrNCpy(char* dest, const char* src, size_t n) { + return strncpy(dest, src, n); +} + +// ChDir(), FReopen(), FDOpen(), Read(), Write(), Close(), and +// StrError() aren't needed on Windows CE at this time and thus not +// defined there. + +#if !GTEST_OS_WINDOWS_MOBILE +inline int ChDir(const char* dir) { return chdir(dir); } +#endif +inline FILE* FOpen(const char* path, const char* mode) { + return fopen(path, mode); +} +#if !GTEST_OS_WINDOWS_MOBILE +inline FILE *FReopen(const char* path, const char* mode, FILE* stream) { + return freopen(path, mode, stream); +} +inline FILE* FDOpen(int fd, const char* mode) { return fdopen(fd, mode); } +#endif +inline int FClose(FILE* fp) { return fclose(fp); } +#if !GTEST_OS_WINDOWS_MOBILE +inline int Read(int fd, void* buf, unsigned int count) { + return static_cast(read(fd, buf, count)); +} +inline int Write(int fd, const void* buf, unsigned int count) { + return static_cast(write(fd, buf, count)); +} +inline int Close(int fd) { return close(fd); } +inline const char* StrError(int errnum) { return strerror(errnum); } +#endif +inline const char* GetEnv(const char* name) { +#if GTEST_OS_WINDOWS_MOBILE + // We are on Windows CE, which has no environment variables. + return NULL; +#elif defined(__BORLANDC__) || defined(__SunOS_5_8) || defined(__SunOS_5_9) + // Environment variables which we programmatically clear will be set to the + // empty string rather than unset (NULL). Handle that case. + const char* const env = getenv(name); + return (env != NULL && env[0] != '\0') ? env : NULL; +#else + return getenv(name); +#endif +} + +#ifdef _MSC_VER +#pragma warning(pop) // Restores the warning state. +#endif + +#if GTEST_OS_WINDOWS_MOBILE +// Windows CE has no C library. The abort() function is used in +// several places in Google Test. This implementation provides a reasonable +// imitation of standard behaviour. +void Abort(); +#else +inline void Abort() { abort(); } +#endif // GTEST_OS_WINDOWS_MOBILE + +} // namespace posix + +// The maximum number a BiggestInt can represent. This definition +// works no matter BiggestInt is represented in one's complement or +// two's complement. +// +// We cannot rely on numeric_limits in STL, as __int64 and long long +// are not part of standard C++ and numeric_limits doesn't need to be +// defined for them. +const BiggestInt kMaxBiggestInt = + ~(static_cast(1) << (8*sizeof(BiggestInt) - 1)); + +// This template class serves as a compile-time function from size to +// type. It maps a size in bytes to a primitive type with that +// size. e.g. +// +// TypeWithSize<4>::UInt +// +// is typedef-ed to be unsigned int (unsigned integer made up of 4 +// bytes). +// +// Such functionality should belong to STL, but I cannot find it +// there. +// +// Google Test uses this class in the implementation of floating-point +// comparison. +// +// For now it only handles UInt (unsigned int) as that's all Google Test +// needs. Other types can be easily added in the future if need +// arises. +template +class TypeWithSize { + public: + // This prevents the user from using TypeWithSize with incorrect + // values of N. + typedef void UInt; +}; + +// The specialization for size 4. +template <> +class TypeWithSize<4> { + public: + // unsigned int has size 4 in both gcc and MSVC. + // + // As base/basictypes.h doesn't compile on Windows, we cannot use + // uint32, uint64, and etc here. + typedef int Int; + typedef unsigned int UInt; +}; + +// The specialization for size 8. +template <> +class TypeWithSize<8> { + public: +#if GTEST_OS_WINDOWS + typedef __int64 Int; + typedef unsigned __int64 UInt; +#else + typedef long long Int; // NOLINT + typedef unsigned long long UInt; // NOLINT +#endif // GTEST_OS_WINDOWS +}; + +// Integer types of known sizes. +typedef TypeWithSize<4>::Int Int32; +typedef TypeWithSize<4>::UInt UInt32; +typedef TypeWithSize<8>::Int Int64; +typedef TypeWithSize<8>::UInt UInt64; +typedef TypeWithSize<8>::Int TimeInMillis; // Represents time in milliseconds. + +// Utilities for command line flags and environment variables. + +// Macro for referencing flags. +#define GTEST_FLAG(name) FLAGS_gtest_##name + +// Macros for declaring flags. +#define GTEST_DECLARE_bool_(name) GTEST_API_ extern bool GTEST_FLAG(name) +#define GTEST_DECLARE_int32_(name) \ + GTEST_API_ extern ::testing::internal::Int32 GTEST_FLAG(name) +#define GTEST_DECLARE_string_(name) \ + GTEST_API_ extern ::testing::internal::String GTEST_FLAG(name) + +// Macros for defining flags. +#define GTEST_DEFINE_bool_(name, default_val, doc) \ + GTEST_API_ bool GTEST_FLAG(name) = (default_val) +#define GTEST_DEFINE_int32_(name, default_val, doc) \ + GTEST_API_ ::testing::internal::Int32 GTEST_FLAG(name) = (default_val) +#define GTEST_DEFINE_string_(name, default_val, doc) \ + GTEST_API_ ::testing::internal::String GTEST_FLAG(name) = (default_val) + +// Parses 'str' for a 32-bit signed integer. If successful, writes the result +// to *value and returns true; otherwise leaves *value unchanged and returns +// false. +// TODO(chandlerc): Find a better way to refactor flag and environment parsing +// out of both gtest-port.cc and gtest.cc to avoid exporting this utility +// function. +bool ParseInt32(const Message& src_text, const char* str, Int32* value); + +// Parses a bool/Int32/string from the environment variable +// corresponding to the given Google Test flag. +bool BoolFromGTestEnv(const char* flag, bool default_val); +GTEST_API_ Int32 Int32FromGTestEnv(const char* flag, Int32 default_val); +const char* StringFromGTestEnv(const char* flag, const char* default_val); + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/internal/gtest-string.h b/3rdparty/gmock/gtest/include/gtest/internal/gtest-string.h new file mode 100644 index 00000000..aff093de --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/internal/gtest-string.h @@ -0,0 +1,350 @@ +// Copyright 2005, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Authors: wan@google.com (Zhanyong Wan), eefacm@gmail.com (Sean Mcafee) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file declares the String class and functions used internally by +// Google Test. They are subject to change without notice. They should not used +// by code external to Google Test. +// +// This header file is #included by . +// It should not be #included by other files. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ + +#ifdef __BORLANDC__ +// string.h is not guaranteed to provide strcpy on C++ Builder. +#include +#endif + +#include +#include + +#include + +namespace testing { +namespace internal { + +// String - a UTF-8 string class. +// +// For historic reasons, we don't use std::string. +// +// TODO(wan@google.com): replace this class with std::string or +// implement it in terms of the latter. +// +// Note that String can represent both NULL and the empty string, +// while std::string cannot represent NULL. +// +// NULL and the empty string are considered different. NULL is less +// than anything (including the empty string) except itself. +// +// This class only provides minimum functionality necessary for +// implementing Google Test. We do not intend to implement a full-fledged +// string class here. +// +// Since the purpose of this class is to provide a substitute for +// std::string on platforms where it cannot be used, we define a copy +// constructor and assignment operators such that we don't need +// conditional compilation in a lot of places. +// +// In order to make the representation efficient, the d'tor of String +// is not virtual. Therefore DO NOT INHERIT FROM String. +class GTEST_API_ String { + public: + // Static utility methods + + // Returns the input enclosed in double quotes if it's not NULL; + // otherwise returns "(null)". For example, "\"Hello\"" is returned + // for input "Hello". + // + // This is useful for printing a C string in the syntax of a literal. + // + // Known issue: escape sequences are not handled yet. + static String ShowCStringQuoted(const char* c_str); + + // Clones a 0-terminated C string, allocating memory using new. The + // caller is responsible for deleting the return value using + // delete[]. Returns the cloned string, or NULL if the input is + // NULL. + // + // This is different from strdup() in string.h, which allocates + // memory using malloc(). + static const char* CloneCString(const char* c_str); + +#if GTEST_OS_WINDOWS_MOBILE + // Windows CE does not have the 'ANSI' versions of Win32 APIs. To be + // able to pass strings to Win32 APIs on CE we need to convert them + // to 'Unicode', UTF-16. + + // Creates a UTF-16 wide string from the given ANSI string, allocating + // memory using new. The caller is responsible for deleting the return + // value using delete[]. Returns the wide string, or NULL if the + // input is NULL. + // + // The wide string is created using the ANSI codepage (CP_ACP) to + // match the behaviour of the ANSI versions of Win32 calls and the + // C runtime. + static LPCWSTR AnsiToUtf16(const char* c_str); + + // Creates an ANSI string from the given wide string, allocating + // memory using new. The caller is responsible for deleting the return + // value using delete[]. Returns the ANSI string, or NULL if the + // input is NULL. + // + // The returned string is created using the ANSI codepage (CP_ACP) to + // match the behaviour of the ANSI versions of Win32 calls and the + // C runtime. + static const char* Utf16ToAnsi(LPCWSTR utf16_str); +#endif + + // Compares two C strings. Returns true iff they have the same content. + // + // Unlike strcmp(), this function can handle NULL argument(s). A + // NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool CStringEquals(const char* lhs, const char* rhs); + + // Converts a wide C string to a String using the UTF-8 encoding. + // NULL will be converted to "(null)". If an error occurred during + // the conversion, "(failed to convert from wide string)" is + // returned. + static String ShowWideCString(const wchar_t* wide_c_str); + + // Similar to ShowWideCString(), except that this function encloses + // the converted string in double quotes. + static String ShowWideCStringQuoted(const wchar_t* wide_c_str); + + // Compares two wide C strings. Returns true iff they have the same + // content. + // + // Unlike wcscmp(), this function can handle NULL argument(s). A + // NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool WideCStringEquals(const wchar_t* lhs, const wchar_t* rhs); + + // Compares two C strings, ignoring case. Returns true iff they + // have the same content. + // + // Unlike strcasecmp(), this function can handle NULL argument(s). + // A NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool CaseInsensitiveCStringEquals(const char* lhs, + const char* rhs); + + // Compares two wide C strings, ignoring case. Returns true iff they + // have the same content. + // + // Unlike wcscasecmp(), this function can handle NULL argument(s). + // A NULL C string is considered different to any non-NULL wide C string, + // including the empty string. + // NB: The implementations on different platforms slightly differ. + // On windows, this method uses _wcsicmp which compares according to LC_CTYPE + // environment variable. On GNU platform this method uses wcscasecmp + // which compares according to LC_CTYPE category of the current locale. + // On MacOS X, it uses towlower, which also uses LC_CTYPE category of the + // current locale. + static bool CaseInsensitiveWideCStringEquals(const wchar_t* lhs, + const wchar_t* rhs); + + // Formats a list of arguments to a String, using the same format + // spec string as for printf. + // + // We do not use the StringPrintf class as it is not universally + // available. + // + // The result is limited to 4096 characters (including the tailing + // 0). If 4096 characters are not enough to format the input, + // "" is returned. + static String Format(const char* format, ...); + + // C'tors + + // The default c'tor constructs a NULL string. + String() : c_str_(NULL), length_(0) {} + + // Constructs a String by cloning a 0-terminated C string. + String(const char* a_c_str) { // NOLINT + if (a_c_str == NULL) { + c_str_ = NULL; + length_ = 0; + } else { + ConstructNonNull(a_c_str, strlen(a_c_str)); + } + } + + // Constructs a String by copying a given number of chars from a + // buffer. E.g. String("hello", 3) creates the string "hel", + // String("a\0bcd", 4) creates "a\0bc", String(NULL, 0) creates "", + // and String(NULL, 1) results in access violation. + String(const char* buffer, size_t a_length) { + ConstructNonNull(buffer, a_length); + } + + // The copy c'tor creates a new copy of the string. The two + // String objects do not share content. + String(const String& str) : c_str_(NULL), length_(0) { *this = str; } + + // D'tor. String is intended to be a final class, so the d'tor + // doesn't need to be virtual. + ~String() { delete[] c_str_; } + + // Allows a String to be implicitly converted to an ::std::string or + // ::string, and vice versa. Converting a String containing a NULL + // pointer to ::std::string or ::string is undefined behavior. + // Converting a ::std::string or ::string containing an embedded NUL + // character to a String will result in the prefix up to the first + // NUL character. + String(const ::std::string& str) { + ConstructNonNull(str.c_str(), str.length()); + } + + operator ::std::string() const { return ::std::string(c_str(), length()); } + +#if GTEST_HAS_GLOBAL_STRING + String(const ::string& str) { + ConstructNonNull(str.c_str(), str.length()); + } + + operator ::string() const { return ::string(c_str(), length()); } +#endif // GTEST_HAS_GLOBAL_STRING + + // Returns true iff this is an empty string (i.e. ""). + bool empty() const { return (c_str() != NULL) && (length() == 0); } + + // Compares this with another String. + // Returns < 0 if this is less than rhs, 0 if this is equal to rhs, or > 0 + // if this is greater than rhs. + int Compare(const String& rhs) const; + + // Returns true iff this String equals the given C string. A NULL + // string and a non-NULL string are considered not equal. + bool operator==(const char* a_c_str) const { return Compare(a_c_str) == 0; } + + // Returns true iff this String is less than the given String. A + // NULL string is considered less than "". + bool operator<(const String& rhs) const { return Compare(rhs) < 0; } + + // Returns true iff this String doesn't equal the given C string. A NULL + // string and a non-NULL string are considered not equal. + bool operator!=(const char* a_c_str) const { return !(*this == a_c_str); } + + // Returns true iff this String ends with the given suffix. *Any* + // String is considered to end with a NULL or empty suffix. + bool EndsWith(const char* suffix) const; + + // Returns true iff this String ends with the given suffix, not considering + // case. Any String is considered to end with a NULL or empty suffix. + bool EndsWithCaseInsensitive(const char* suffix) const; + + // Returns the length of the encapsulated string, or 0 if the + // string is NULL. + size_t length() const { return length_; } + + // Gets the 0-terminated C string this String object represents. + // The String object still owns the string. Therefore the caller + // should NOT delete the return value. + const char* c_str() const { return c_str_; } + + // Assigns a C string to this object. Self-assignment works. + const String& operator=(const char* a_c_str) { + return *this = String(a_c_str); + } + + // Assigns a String object to this object. Self-assignment works. + const String& operator=(const String& rhs) { + if (this != &rhs) { + delete[] c_str_; + if (rhs.c_str() == NULL) { + c_str_ = NULL; + length_ = 0; + } else { + ConstructNonNull(rhs.c_str(), rhs.length()); + } + } + + return *this; + } + + private: + // Constructs a non-NULL String from the given content. This + // function can only be called when data_ has not been allocated. + // ConstructNonNull(NULL, 0) results in an empty string (""). + // ConstructNonNull(NULL, non_zero) is undefined behavior. + void ConstructNonNull(const char* buffer, size_t a_length) { + char* const str = new char[a_length + 1]; + memcpy(str, buffer, a_length); + str[a_length] = '\0'; + c_str_ = str; + length_ = a_length; + } + + const char* c_str_; + size_t length_; +}; // class String + +// Streams a String to an ostream. Each '\0' character in the String +// is replaced with "\\0". +inline ::std::ostream& operator<<(::std::ostream& os, const String& str) { + if (str.c_str() == NULL) { + os << "(null)"; + } else { + const char* const c_str = str.c_str(); + for (size_t i = 0; i != str.length(); i++) { + if (c_str[i] == '\0') { + os << "\\0"; + } else { + os << c_str[i]; + } + } + } + return os; +} + +// Gets the content of the StrStream's buffer as a String. Each '\0' +// character in the buffer is replaced with "\\0". +GTEST_API_ String StrStreamToString(StrStream* stream); + +// Converts a streamable value to a String. A NULL pointer is +// converted to "(null)". When the input value is a ::string, +// ::std::string, ::wstring, or ::std::wstring object, each NUL +// character in it is replaced with "\\0". + +// Declared here but defined in gtest.h, so that it has access +// to the definition of the Message class, required by the ARM +// compiler. +template +String StreamableToString(const T& streamable); + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/internal/gtest-tuple.h b/3rdparty/gmock/gtest/include/gtest/internal/gtest-tuple.h new file mode 100644 index 00000000..16178fc0 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/internal/gtest-tuple.h @@ -0,0 +1,968 @@ +// This file was GENERATED by a script. DO NOT EDIT BY HAND!!! + +// Copyright 2009 Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Implements a subset of TR1 tuple needed by Google Test and Google Mock. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_ + +#include // For ::std::pair. + +// The compiler used in Symbian has a bug that prevents us from declaring the +// tuple template as a friend (it complains that tuple is redefined). This +// hack bypasses the bug by declaring the members that should otherwise be +// private as public. +// Sun Studio versions < 12 also have the above bug. +#if defined(__SYMBIAN32__) || (defined(__SUNPRO_CC) && __SUNPRO_CC < 0x590) +#define GTEST_DECLARE_TUPLE_AS_FRIEND_ public: +#else +#define GTEST_DECLARE_TUPLE_AS_FRIEND_ \ + template friend class tuple; \ + private: +#endif + +// GTEST_n_TUPLE_(T) is the type of an n-tuple. +#define GTEST_0_TUPLE_(T) tuple<> +#define GTEST_1_TUPLE_(T) tuple +#define GTEST_2_TUPLE_(T) tuple +#define GTEST_3_TUPLE_(T) tuple +#define GTEST_4_TUPLE_(T) tuple +#define GTEST_5_TUPLE_(T) tuple +#define GTEST_6_TUPLE_(T) tuple +#define GTEST_7_TUPLE_(T) tuple +#define GTEST_8_TUPLE_(T) tuple +#define GTEST_9_TUPLE_(T) tuple +#define GTEST_10_TUPLE_(T) tuple + +// GTEST_n_TYPENAMES_(T) declares a list of n typenames. +#define GTEST_0_TYPENAMES_(T) +#define GTEST_1_TYPENAMES_(T) typename T##0 +#define GTEST_2_TYPENAMES_(T) typename T##0, typename T##1 +#define GTEST_3_TYPENAMES_(T) typename T##0, typename T##1, typename T##2 +#define GTEST_4_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3 +#define GTEST_5_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4 +#define GTEST_6_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4, typename T##5 +#define GTEST_7_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4, typename T##5, typename T##6 +#define GTEST_8_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4, typename T##5, typename T##6, typename T##7 +#define GTEST_9_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4, typename T##5, typename T##6, \ + typename T##7, typename T##8 +#define GTEST_10_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4, typename T##5, typename T##6, \ + typename T##7, typename T##8, typename T##9 + +// In theory, defining stuff in the ::std namespace is undefined +// behavior. We can do this as we are playing the role of a standard +// library vendor. +namespace std { +namespace tr1 { + +template +class tuple; + +// Anything in namespace gtest_internal is Google Test's INTERNAL +// IMPLEMENTATION DETAIL and MUST NOT BE USED DIRECTLY in user code. +namespace gtest_internal { + +// ByRef::type is T if T is a reference; otherwise it's const T&. +template +struct ByRef { typedef const T& type; }; // NOLINT +template +struct ByRef { typedef T& type; }; // NOLINT + +// A handy wrapper for ByRef. +#define GTEST_BY_REF_(T) typename ::std::tr1::gtest_internal::ByRef::type + +// AddRef::type is T if T is a reference; otherwise it's T&. This +// is the same as tr1::add_reference::type. +template +struct AddRef { typedef T& type; }; // NOLINT +template +struct AddRef { typedef T& type; }; // NOLINT + +// A handy wrapper for AddRef. +#define GTEST_ADD_REF_(T) typename ::std::tr1::gtest_internal::AddRef::type + +// A helper for implementing get(). +template class Get; + +// A helper for implementing tuple_element. kIndexValid is true +// iff k < the number of fields in tuple type T. +template +struct TupleElement; + +template +struct TupleElement { typedef T0 type; }; + +template +struct TupleElement { typedef T1 type; }; + +template +struct TupleElement { typedef T2 type; }; + +template +struct TupleElement { typedef T3 type; }; + +template +struct TupleElement { typedef T4 type; }; + +template +struct TupleElement { typedef T5 type; }; + +template +struct TupleElement { typedef T6 type; }; + +template +struct TupleElement { typedef T7 type; }; + +template +struct TupleElement { typedef T8 type; }; + +template +struct TupleElement { typedef T9 type; }; + +} // namespace gtest_internal + +template <> +class tuple<> { + public: + tuple() {} + tuple(const tuple& /* t */) {} + tuple& operator=(const tuple& /* t */) { return *this; } +}; + +template +class GTEST_1_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0) : f0_(f0) {} + + tuple(const tuple& t) : f0_(t.f0_) {} + + template + tuple(const GTEST_1_TUPLE_(U)& t) : f0_(t.f0_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_1_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_1_TUPLE_(U)& t) { + f0_ = t.f0_; + return *this; + } + + T0 f0_; +}; + +template +class GTEST_2_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1) : f0_(f0), + f1_(f1) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_) {} + + template + tuple(const GTEST_2_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_) {} + template + tuple(const ::std::pair& p) : f0_(p.first), f1_(p.second) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_2_TUPLE_(U)& t) { + return CopyFrom(t); + } + template + tuple& operator=(const ::std::pair& p) { + f0_ = p.first; + f1_ = p.second; + return *this; + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_2_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + return *this; + } + + T0 f0_; + T1 f1_; +}; + +template +class GTEST_3_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2) : f0_(f0), f1_(f1), f2_(f2) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_) {} + + template + tuple(const GTEST_3_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_3_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_3_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; +}; + +template +class GTEST_4_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3) : f0_(f0), f1_(f1), f2_(f2), + f3_(f3) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_) {} + + template + tuple(const GTEST_4_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_4_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_4_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; +}; + +template +class GTEST_5_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, + GTEST_BY_REF_(T4) f4) : f0_(f0), f1_(f1), f2_(f2), f3_(f3), f4_(f4) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_) {} + + template + tuple(const GTEST_5_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_5_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_5_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; +}; + +template +class GTEST_6_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4, + GTEST_BY_REF_(T5) f5) : f0_(f0), f1_(f1), f2_(f2), f3_(f3), f4_(f4), + f5_(f5) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_), f5_(t.f5_) {} + + template + tuple(const GTEST_6_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_), f5_(t.f5_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_6_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_6_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + f5_ = t.f5_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; + T5 f5_; +}; + +template +class GTEST_7_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_(), f6_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4, + GTEST_BY_REF_(T5) f5, GTEST_BY_REF_(T6) f6) : f0_(f0), f1_(f1), f2_(f2), + f3_(f3), f4_(f4), f5_(f5), f6_(f6) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_), f5_(t.f5_), f6_(t.f6_) {} + + template + tuple(const GTEST_7_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_), f5_(t.f5_), f6_(t.f6_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_7_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_7_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + f5_ = t.f5_; + f6_ = t.f6_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; + T5 f5_; + T6 f6_; +}; + +template +class GTEST_8_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_(), f6_(), f7_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4, + GTEST_BY_REF_(T5) f5, GTEST_BY_REF_(T6) f6, + GTEST_BY_REF_(T7) f7) : f0_(f0), f1_(f1), f2_(f2), f3_(f3), f4_(f4), + f5_(f5), f6_(f6), f7_(f7) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_) {} + + template + tuple(const GTEST_8_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_8_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_8_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + f5_ = t.f5_; + f6_ = t.f6_; + f7_ = t.f7_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; + T5 f5_; + T6 f6_; + T7 f7_; +}; + +template +class GTEST_9_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_(), f6_(), f7_(), f8_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4, + GTEST_BY_REF_(T5) f5, GTEST_BY_REF_(T6) f6, GTEST_BY_REF_(T7) f7, + GTEST_BY_REF_(T8) f8) : f0_(f0), f1_(f1), f2_(f2), f3_(f3), f4_(f4), + f5_(f5), f6_(f6), f7_(f7), f8_(f8) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_), f8_(t.f8_) {} + + template + tuple(const GTEST_9_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_), f8_(t.f8_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_9_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_9_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + f5_ = t.f5_; + f6_ = t.f6_; + f7_ = t.f7_; + f8_ = t.f8_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; + T5 f5_; + T6 f6_; + T7 f7_; + T8 f8_; +}; + +template +class tuple { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_(), f6_(), f7_(), f8_(), + f9_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4, + GTEST_BY_REF_(T5) f5, GTEST_BY_REF_(T6) f6, GTEST_BY_REF_(T7) f7, + GTEST_BY_REF_(T8) f8, GTEST_BY_REF_(T9) f9) : f0_(f0), f1_(f1), f2_(f2), + f3_(f3), f4_(f4), f5_(f5), f6_(f6), f7_(f7), f8_(f8), f9_(f9) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_), f8_(t.f8_), f9_(t.f9_) {} + + template + tuple(const GTEST_10_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_), f8_(t.f8_), + f9_(t.f9_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_10_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_10_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + f5_ = t.f5_; + f6_ = t.f6_; + f7_ = t.f7_; + f8_ = t.f8_; + f9_ = t.f9_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; + T5 f5_; + T6 f6_; + T7 f7_; + T8 f8_; + T9 f9_; +}; + +// 6.1.3.2 Tuple creation functions. + +// Known limitations: we don't support passing an +// std::tr1::reference_wrapper to make_tuple(). And we don't +// implement tie(). + +inline tuple<> make_tuple() { return tuple<>(); } + +template +inline GTEST_1_TUPLE_(T) make_tuple(const T0& f0) { + return GTEST_1_TUPLE_(T)(f0); +} + +template +inline GTEST_2_TUPLE_(T) make_tuple(const T0& f0, const T1& f1) { + return GTEST_2_TUPLE_(T)(f0, f1); +} + +template +inline GTEST_3_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2) { + return GTEST_3_TUPLE_(T)(f0, f1, f2); +} + +template +inline GTEST_4_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3) { + return GTEST_4_TUPLE_(T)(f0, f1, f2, f3); +} + +template +inline GTEST_5_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4) { + return GTEST_5_TUPLE_(T)(f0, f1, f2, f3, f4); +} + +template +inline GTEST_6_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4, const T5& f5) { + return GTEST_6_TUPLE_(T)(f0, f1, f2, f3, f4, f5); +} + +template +inline GTEST_7_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4, const T5& f5, const T6& f6) { + return GTEST_7_TUPLE_(T)(f0, f1, f2, f3, f4, f5, f6); +} + +template +inline GTEST_8_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4, const T5& f5, const T6& f6, const T7& f7) { + return GTEST_8_TUPLE_(T)(f0, f1, f2, f3, f4, f5, f6, f7); +} + +template +inline GTEST_9_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4, const T5& f5, const T6& f6, const T7& f7, + const T8& f8) { + return GTEST_9_TUPLE_(T)(f0, f1, f2, f3, f4, f5, f6, f7, f8); +} + +template +inline GTEST_10_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4, const T5& f5, const T6& f6, const T7& f7, + const T8& f8, const T9& f9) { + return GTEST_10_TUPLE_(T)(f0, f1, f2, f3, f4, f5, f6, f7, f8, f9); +} + +// 6.1.3.3 Tuple helper classes. + +template struct tuple_size; + +template +struct tuple_size { static const int value = 0; }; + +template +struct tuple_size { static const int value = 1; }; + +template +struct tuple_size { static const int value = 2; }; + +template +struct tuple_size { static const int value = 3; }; + +template +struct tuple_size { static const int value = 4; }; + +template +struct tuple_size { static const int value = 5; }; + +template +struct tuple_size { static const int value = 6; }; + +template +struct tuple_size { static const int value = 7; }; + +template +struct tuple_size { static const int value = 8; }; + +template +struct tuple_size { static const int value = 9; }; + +template +struct tuple_size { static const int value = 10; }; + +template +struct tuple_element { + typedef typename gtest_internal::TupleElement< + k < (tuple_size::value), k, Tuple>::type type; +}; + +#define GTEST_TUPLE_ELEMENT_(k, Tuple) typename tuple_element::type + +// 6.1.3.4 Element access. + +namespace gtest_internal { + +template <> +class Get<0> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(0, Tuple)) + Field(Tuple& t) { return t.f0_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(0, Tuple)) + ConstField(const Tuple& t) { return t.f0_; } +}; + +template <> +class Get<1> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(1, Tuple)) + Field(Tuple& t) { return t.f1_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(1, Tuple)) + ConstField(const Tuple& t) { return t.f1_; } +}; + +template <> +class Get<2> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(2, Tuple)) + Field(Tuple& t) { return t.f2_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(2, Tuple)) + ConstField(const Tuple& t) { return t.f2_; } +}; + +template <> +class Get<3> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(3, Tuple)) + Field(Tuple& t) { return t.f3_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(3, Tuple)) + ConstField(const Tuple& t) { return t.f3_; } +}; + +template <> +class Get<4> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(4, Tuple)) + Field(Tuple& t) { return t.f4_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(4, Tuple)) + ConstField(const Tuple& t) { return t.f4_; } +}; + +template <> +class Get<5> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(5, Tuple)) + Field(Tuple& t) { return t.f5_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(5, Tuple)) + ConstField(const Tuple& t) { return t.f5_; } +}; + +template <> +class Get<6> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(6, Tuple)) + Field(Tuple& t) { return t.f6_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(6, Tuple)) + ConstField(const Tuple& t) { return t.f6_; } +}; + +template <> +class Get<7> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(7, Tuple)) + Field(Tuple& t) { return t.f7_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(7, Tuple)) + ConstField(const Tuple& t) { return t.f7_; } +}; + +template <> +class Get<8> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(8, Tuple)) + Field(Tuple& t) { return t.f8_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(8, Tuple)) + ConstField(const Tuple& t) { return t.f8_; } +}; + +template <> +class Get<9> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(9, Tuple)) + Field(Tuple& t) { return t.f9_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(9, Tuple)) + ConstField(const Tuple& t) { return t.f9_; } +}; + +} // namespace gtest_internal + +template +GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(k, GTEST_10_TUPLE_(T))) +get(GTEST_10_TUPLE_(T)& t) { + return gtest_internal::Get::Field(t); +} + +template +GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(k, GTEST_10_TUPLE_(T))) +get(const GTEST_10_TUPLE_(T)& t) { + return gtest_internal::Get::ConstField(t); +} + +// 6.1.3.5 Relational operators + +// We only implement == and !=, as we don't have a need for the rest yet. + +namespace gtest_internal { + +// SameSizeTuplePrefixComparator::Eq(t1, t2) returns true if the +// first k fields of t1 equals the first k fields of t2. +// SameSizeTuplePrefixComparator(k1, k2) would be a compiler error if +// k1 != k2. +template +struct SameSizeTuplePrefixComparator; + +template <> +struct SameSizeTuplePrefixComparator<0, 0> { + template + static bool Eq(const Tuple1& /* t1 */, const Tuple2& /* t2 */) { + return true; + } +}; + +template +struct SameSizeTuplePrefixComparator { + template + static bool Eq(const Tuple1& t1, const Tuple2& t2) { + return SameSizeTuplePrefixComparator::Eq(t1, t2) && + ::std::tr1::get(t1) == ::std::tr1::get(t2); + } +}; + +} // namespace gtest_internal + +template +inline bool operator==(const GTEST_10_TUPLE_(T)& t, + const GTEST_10_TUPLE_(U)& u) { + return gtest_internal::SameSizeTuplePrefixComparator< + tuple_size::value, + tuple_size::value>::Eq(t, u); +} + +template +inline bool operator!=(const GTEST_10_TUPLE_(T)& t, + const GTEST_10_TUPLE_(U)& u) { return !(t == u); } + +// 6.1.4 Pairs. +// Unimplemented. + +} // namespace tr1 +} // namespace std + +#undef GTEST_0_TUPLE_ +#undef GTEST_1_TUPLE_ +#undef GTEST_2_TUPLE_ +#undef GTEST_3_TUPLE_ +#undef GTEST_4_TUPLE_ +#undef GTEST_5_TUPLE_ +#undef GTEST_6_TUPLE_ +#undef GTEST_7_TUPLE_ +#undef GTEST_8_TUPLE_ +#undef GTEST_9_TUPLE_ +#undef GTEST_10_TUPLE_ + +#undef GTEST_0_TYPENAMES_ +#undef GTEST_1_TYPENAMES_ +#undef GTEST_2_TYPENAMES_ +#undef GTEST_3_TYPENAMES_ +#undef GTEST_4_TYPENAMES_ +#undef GTEST_5_TYPENAMES_ +#undef GTEST_6_TYPENAMES_ +#undef GTEST_7_TYPENAMES_ +#undef GTEST_8_TYPENAMES_ +#undef GTEST_9_TYPENAMES_ +#undef GTEST_10_TYPENAMES_ + +#undef GTEST_DECLARE_TUPLE_AS_FRIEND_ +#undef GTEST_BY_REF_ +#undef GTEST_ADD_REF_ +#undef GTEST_TUPLE_ELEMENT_ + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/internal/gtest-tuple.h.pump b/3rdparty/gmock/gtest/include/gtest/internal/gtest-tuple.h.pump new file mode 100644 index 00000000..85ebc806 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/internal/gtest-tuple.h.pump @@ -0,0 +1,336 @@ +$$ -*- mode: c++; -*- +$var n = 10 $$ Maximum number of tuple fields we want to support. +$$ This meta comment fixes auto-indentation in Emacs. }} +// Copyright 2009 Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Implements a subset of TR1 tuple needed by Google Test and Google Mock. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_ + +#include // For ::std::pair. + +// The compiler used in Symbian has a bug that prevents us from declaring the +// tuple template as a friend (it complains that tuple is redefined). This +// hack bypasses the bug by declaring the members that should otherwise be +// private as public. +// Sun Studio versions < 12 also have the above bug. +#if defined(__SYMBIAN32__) || (defined(__SUNPRO_CC) && __SUNPRO_CC < 0x590) +#define GTEST_DECLARE_TUPLE_AS_FRIEND_ public: +#else +#define GTEST_DECLARE_TUPLE_AS_FRIEND_ \ + template friend class tuple; \ + private: +#endif + + +$range i 0..n-1 +$range j 0..n +$range k 1..n +// GTEST_n_TUPLE_(T) is the type of an n-tuple. +#define GTEST_0_TUPLE_(T) tuple<> + +$for k [[ +$range m 0..k-1 +$range m2 k..n-1 +#define GTEST_$(k)_TUPLE_(T) tuple<$for m, [[T##$m]]$for m2 [[, void]]> + +]] + +// GTEST_n_TYPENAMES_(T) declares a list of n typenames. + +$for j [[ +$range m 0..j-1 +#define GTEST_$(j)_TYPENAMES_(T) $for m, [[typename T##$m]] + + +]] + +// In theory, defining stuff in the ::std namespace is undefined +// behavior. We can do this as we are playing the role of a standard +// library vendor. +namespace std { +namespace tr1 { + +template <$for i, [[typename T$i = void]]> +class tuple; + +// Anything in namespace gtest_internal is Google Test's INTERNAL +// IMPLEMENTATION DETAIL and MUST NOT BE USED DIRECTLY in user code. +namespace gtest_internal { + +// ByRef::type is T if T is a reference; otherwise it's const T&. +template +struct ByRef { typedef const T& type; }; // NOLINT +template +struct ByRef { typedef T& type; }; // NOLINT + +// A handy wrapper for ByRef. +#define GTEST_BY_REF_(T) typename ::std::tr1::gtest_internal::ByRef::type + +// AddRef::type is T if T is a reference; otherwise it's T&. This +// is the same as tr1::add_reference::type. +template +struct AddRef { typedef T& type; }; // NOLINT +template +struct AddRef { typedef T& type; }; // NOLINT + +// A handy wrapper for AddRef. +#define GTEST_ADD_REF_(T) typename ::std::tr1::gtest_internal::AddRef::type + +// A helper for implementing get(). +template class Get; + +// A helper for implementing tuple_element. kIndexValid is true +// iff k < the number of fields in tuple type T. +template +struct TupleElement; + + +$for i [[ +template +struct TupleElement [[]] +{ typedef T$i type; }; + + +]] +} // namespace gtest_internal + +template <> +class tuple<> { + public: + tuple() {} + tuple(const tuple& /* t */) {} + tuple& operator=(const tuple& /* t */) { return *this; } +}; + + +$for k [[ +$range m 0..k-1 +template +class $if k < n [[GTEST_$(k)_TUPLE_(T)]] $else [[tuple]] { + public: + template friend class gtest_internal::Get; + + tuple() : $for m, [[f$(m)_()]] {} + + explicit tuple($for m, [[GTEST_BY_REF_(T$m) f$m]]) : [[]] +$for m, [[f$(m)_(f$m)]] {} + + tuple(const tuple& t) : $for m, [[f$(m)_(t.f$(m)_)]] {} + + template + tuple(const GTEST_$(k)_TUPLE_(U)& t) : $for m, [[f$(m)_(t.f$(m)_)]] {} + +$if k == 2 [[ + template + tuple(const ::std::pair& p) : f0_(p.first), f1_(p.second) {} + +]] + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_$(k)_TUPLE_(U)& t) { + return CopyFrom(t); + } + +$if k == 2 [[ + template + tuple& operator=(const ::std::pair& p) { + f0_ = p.first; + f1_ = p.second; + return *this; + } + +]] + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_$(k)_TUPLE_(U)& t) { + +$for m [[ + f$(m)_ = t.f$(m)_; + +]] + return *this; + } + + +$for m [[ + T$m f$(m)_; + +]] +}; + + +]] +// 6.1.3.2 Tuple creation functions. + +// Known limitations: we don't support passing an +// std::tr1::reference_wrapper to make_tuple(). And we don't +// implement tie(). + +inline tuple<> make_tuple() { return tuple<>(); } + +$for k [[ +$range m 0..k-1 + +template +inline GTEST_$(k)_TUPLE_(T) make_tuple($for m, [[const T$m& f$m]]) { + return GTEST_$(k)_TUPLE_(T)($for m, [[f$m]]); +} + +]] + +// 6.1.3.3 Tuple helper classes. + +template struct tuple_size; + + +$for j [[ +template +struct tuple_size { static const int value = $j; }; + + +]] +template +struct tuple_element { + typedef typename gtest_internal::TupleElement< + k < (tuple_size::value), k, Tuple>::type type; +}; + +#define GTEST_TUPLE_ELEMENT_(k, Tuple) typename tuple_element::type + +// 6.1.3.4 Element access. + +namespace gtest_internal { + + +$for i [[ +template <> +class Get<$i> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_($i, Tuple)) + Field(Tuple& t) { return t.f$(i)_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_($i, Tuple)) + ConstField(const Tuple& t) { return t.f$(i)_; } +}; + + +]] +} // namespace gtest_internal + +template +GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(k, GTEST_$(n)_TUPLE_(T))) +get(GTEST_$(n)_TUPLE_(T)& t) { + return gtest_internal::Get::Field(t); +} + +template +GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(k, GTEST_$(n)_TUPLE_(T))) +get(const GTEST_$(n)_TUPLE_(T)& t) { + return gtest_internal::Get::ConstField(t); +} + +// 6.1.3.5 Relational operators + +// We only implement == and !=, as we don't have a need for the rest yet. + +namespace gtest_internal { + +// SameSizeTuplePrefixComparator::Eq(t1, t2) returns true if the +// first k fields of t1 equals the first k fields of t2. +// SameSizeTuplePrefixComparator(k1, k2) would be a compiler error if +// k1 != k2. +template +struct SameSizeTuplePrefixComparator; + +template <> +struct SameSizeTuplePrefixComparator<0, 0> { + template + static bool Eq(const Tuple1& /* t1 */, const Tuple2& /* t2 */) { + return true; + } +}; + +template +struct SameSizeTuplePrefixComparator { + template + static bool Eq(const Tuple1& t1, const Tuple2& t2) { + return SameSizeTuplePrefixComparator::Eq(t1, t2) && + ::std::tr1::get(t1) == ::std::tr1::get(t2); + } +}; + +} // namespace gtest_internal + +template +inline bool operator==(const GTEST_$(n)_TUPLE_(T)& t, + const GTEST_$(n)_TUPLE_(U)& u) { + return gtest_internal::SameSizeTuplePrefixComparator< + tuple_size::value, + tuple_size::value>::Eq(t, u); +} + +template +inline bool operator!=(const GTEST_$(n)_TUPLE_(T)& t, + const GTEST_$(n)_TUPLE_(U)& u) { return !(t == u); } + +// 6.1.4 Pairs. +// Unimplemented. + +} // namespace tr1 +} // namespace std + + +$for j [[ +#undef GTEST_$(j)_TUPLE_ + +]] + + +$for j [[ +#undef GTEST_$(j)_TYPENAMES_ + +]] + +#undef GTEST_DECLARE_TUPLE_AS_FRIEND_ +#undef GTEST_BY_REF_ +#undef GTEST_ADD_REF_ +#undef GTEST_TUPLE_ELEMENT_ + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/internal/gtest-type-util.h b/3rdparty/gmock/gtest/include/gtest/internal/gtest-type-util.h new file mode 100644 index 00000000..093eee6f --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/internal/gtest-type-util.h @@ -0,0 +1,3321 @@ +// This file was GENERATED by command: +// pump.py gtest-type-util.h.pump +// DO NOT EDIT BY HAND!!! + +// Copyright 2008 Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Type utilities needed for implementing typed and type-parameterized +// tests. This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +// Currently we support at most 50 types in a list, and at most 50 +// type-parameterized tests in one type-parameterized test case. +// Please contact googletestframework@googlegroups.com if you need +// more. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ + +#include +#include + +#if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +// #ifdef __GNUC__ is too general here. It is possible to use gcc without using +// libstdc++ (which is where cxxabi.h comes from). +#ifdef __GLIBCXX__ +#include +#endif // __GLIBCXX__ + +namespace testing { +namespace internal { + +// AssertyTypeEq::type is defined iff T1 and T2 are the same +// type. This can be used as a compile-time assertion to ensure that +// two types are equal. + +template +struct AssertTypeEq; + +template +struct AssertTypeEq { + typedef bool type; +}; + +// GetTypeName() returns a human-readable name of type T. +template +String GetTypeName() { +#if GTEST_HAS_RTTI + + const char* const name = typeid(T).name(); +#ifdef __GLIBCXX__ + int status = 0; + // gcc's implementation of typeid(T).name() mangles the type name, + // so we have to demangle it. + char* const readable_name = abi::__cxa_demangle(name, 0, 0, &status); + const String name_str(status == 0 ? readable_name : name); + free(readable_name); + return name_str; +#else + return name; +#endif // __GLIBCXX__ + +#else + return ""; +#endif // GTEST_HAS_RTTI +} + +// A unique type used as the default value for the arguments of class +// template Types. This allows us to simulate variadic templates +// (e.g. Types, Type, and etc), which C++ doesn't +// support directly. +struct None {}; + +// The following family of struct and struct templates are used to +// represent type lists. In particular, TypesN +// represents a type list with N types (T1, T2, ..., and TN) in it. +// Except for Types0, every struct in the family has two member types: +// Head for the first type in the list, and Tail for the rest of the +// list. + +// The empty type list. +struct Types0 {}; + +// Type lists of length 1, 2, 3, and so on. + +template +struct Types1 { + typedef T1 Head; + typedef Types0 Tail; +}; +template +struct Types2 { + typedef T1 Head; + typedef Types1 Tail; +}; + +template +struct Types3 { + typedef T1 Head; + typedef Types2 Tail; +}; + +template +struct Types4 { + typedef T1 Head; + typedef Types3 Tail; +}; + +template +struct Types5 { + typedef T1 Head; + typedef Types4 Tail; +}; + +template +struct Types6 { + typedef T1 Head; + typedef Types5 Tail; +}; + +template +struct Types7 { + typedef T1 Head; + typedef Types6 Tail; +}; + +template +struct Types8 { + typedef T1 Head; + typedef Types7 Tail; +}; + +template +struct Types9 { + typedef T1 Head; + typedef Types8 Tail; +}; + +template +struct Types10 { + typedef T1 Head; + typedef Types9 Tail; +}; + +template +struct Types11 { + typedef T1 Head; + typedef Types10 Tail; +}; + +template +struct Types12 { + typedef T1 Head; + typedef Types11 Tail; +}; + +template +struct Types13 { + typedef T1 Head; + typedef Types12 Tail; +}; + +template +struct Types14 { + typedef T1 Head; + typedef Types13 Tail; +}; + +template +struct Types15 { + typedef T1 Head; + typedef Types14 Tail; +}; + +template +struct Types16 { + typedef T1 Head; + typedef Types15 Tail; +}; + +template +struct Types17 { + typedef T1 Head; + typedef Types16 Tail; +}; + +template +struct Types18 { + typedef T1 Head; + typedef Types17 Tail; +}; + +template +struct Types19 { + typedef T1 Head; + typedef Types18 Tail; +}; + +template +struct Types20 { + typedef T1 Head; + typedef Types19 Tail; +}; + +template +struct Types21 { + typedef T1 Head; + typedef Types20 Tail; +}; + +template +struct Types22 { + typedef T1 Head; + typedef Types21 Tail; +}; + +template +struct Types23 { + typedef T1 Head; + typedef Types22 Tail; +}; + +template +struct Types24 { + typedef T1 Head; + typedef Types23 Tail; +}; + +template +struct Types25 { + typedef T1 Head; + typedef Types24 Tail; +}; + +template +struct Types26 { + typedef T1 Head; + typedef Types25 Tail; +}; + +template +struct Types27 { + typedef T1 Head; + typedef Types26 Tail; +}; + +template +struct Types28 { + typedef T1 Head; + typedef Types27 Tail; +}; + +template +struct Types29 { + typedef T1 Head; + typedef Types28 Tail; +}; + +template +struct Types30 { + typedef T1 Head; + typedef Types29 Tail; +}; + +template +struct Types31 { + typedef T1 Head; + typedef Types30 Tail; +}; + +template +struct Types32 { + typedef T1 Head; + typedef Types31 Tail; +}; + +template +struct Types33 { + typedef T1 Head; + typedef Types32 Tail; +}; + +template +struct Types34 { + typedef T1 Head; + typedef Types33 Tail; +}; + +template +struct Types35 { + typedef T1 Head; + typedef Types34 Tail; +}; + +template +struct Types36 { + typedef T1 Head; + typedef Types35 Tail; +}; + +template +struct Types37 { + typedef T1 Head; + typedef Types36 Tail; +}; + +template +struct Types38 { + typedef T1 Head; + typedef Types37 Tail; +}; + +template +struct Types39 { + typedef T1 Head; + typedef Types38 Tail; +}; + +template +struct Types40 { + typedef T1 Head; + typedef Types39 Tail; +}; + +template +struct Types41 { + typedef T1 Head; + typedef Types40 Tail; +}; + +template +struct Types42 { + typedef T1 Head; + typedef Types41 Tail; +}; + +template +struct Types43 { + typedef T1 Head; + typedef Types42 Tail; +}; + +template +struct Types44 { + typedef T1 Head; + typedef Types43 Tail; +}; + +template +struct Types45 { + typedef T1 Head; + typedef Types44 Tail; +}; + +template +struct Types46 { + typedef T1 Head; + typedef Types45 Tail; +}; + +template +struct Types47 { + typedef T1 Head; + typedef Types46 Tail; +}; + +template +struct Types48 { + typedef T1 Head; + typedef Types47 Tail; +}; + +template +struct Types49 { + typedef T1 Head; + typedef Types48 Tail; +}; + +template +struct Types50 { + typedef T1 Head; + typedef Types49 Tail; +}; + + +} // namespace internal + +// We don't want to require the users to write TypesN<...> directly, +// as that would require them to count the length. Types<...> is much +// easier to write, but generates horrible messages when there is a +// compiler error, as gcc insists on printing out each template +// argument, even if it has the default value (this means Types +// will appear as Types in the compiler +// errors). +// +// Our solution is to combine the best part of the two approaches: a +// user would write Types, and Google Test will translate +// that to TypesN internally to make error messages +// readable. The translation is done by the 'type' member of the +// Types template. +template +struct Types { + typedef internal::Types50 type; +}; + +template <> +struct Types { + typedef internal::Types0 type; +}; +template +struct Types { + typedef internal::Types1 type; +}; +template +struct Types { + typedef internal::Types2 type; +}; +template +struct Types { + typedef internal::Types3 type; +}; +template +struct Types { + typedef internal::Types4 type; +}; +template +struct Types { + typedef internal::Types5 type; +}; +template +struct Types { + typedef internal::Types6 type; +}; +template +struct Types { + typedef internal::Types7 type; +}; +template +struct Types { + typedef internal::Types8 type; +}; +template +struct Types { + typedef internal::Types9 type; +}; +template +struct Types { + typedef internal::Types10 type; +}; +template +struct Types { + typedef internal::Types11 type; +}; +template +struct Types { + typedef internal::Types12 type; +}; +template +struct Types { + typedef internal::Types13 type; +}; +template +struct Types { + typedef internal::Types14 type; +}; +template +struct Types { + typedef internal::Types15 type; +}; +template +struct Types { + typedef internal::Types16 type; +}; +template +struct Types { + typedef internal::Types17 type; +}; +template +struct Types { + typedef internal::Types18 type; +}; +template +struct Types { + typedef internal::Types19 type; +}; +template +struct Types { + typedef internal::Types20 type; +}; +template +struct Types { + typedef internal::Types21 type; +}; +template +struct Types { + typedef internal::Types22 type; +}; +template +struct Types { + typedef internal::Types23 type; +}; +template +struct Types { + typedef internal::Types24 type; +}; +template +struct Types { + typedef internal::Types25 type; +}; +template +struct Types { + typedef internal::Types26 type; +}; +template +struct Types { + typedef internal::Types27 type; +}; +template +struct Types { + typedef internal::Types28 type; +}; +template +struct Types { + typedef internal::Types29 type; +}; +template +struct Types { + typedef internal::Types30 type; +}; +template +struct Types { + typedef internal::Types31 type; +}; +template +struct Types { + typedef internal::Types32 type; +}; +template +struct Types { + typedef internal::Types33 type; +}; +template +struct Types { + typedef internal::Types34 type; +}; +template +struct Types { + typedef internal::Types35 type; +}; +template +struct Types { + typedef internal::Types36 type; +}; +template +struct Types { + typedef internal::Types37 type; +}; +template +struct Types { + typedef internal::Types38 type; +}; +template +struct Types { + typedef internal::Types39 type; +}; +template +struct Types { + typedef internal::Types40 type; +}; +template +struct Types { + typedef internal::Types41 type; +}; +template +struct Types { + typedef internal::Types42 type; +}; +template +struct Types { + typedef internal::Types43 type; +}; +template +struct Types { + typedef internal::Types44 type; +}; +template +struct Types { + typedef internal::Types45 type; +}; +template +struct Types { + typedef internal::Types46 type; +}; +template +struct Types { + typedef internal::Types47 type; +}; +template +struct Types { + typedef internal::Types48 type; +}; +template +struct Types { + typedef internal::Types49 type; +}; + +namespace internal { + +#define GTEST_TEMPLATE_ template class + +// The template "selector" struct TemplateSel is used to +// represent Tmpl, which must be a class template with one type +// parameter, as a type. TemplateSel::Bind::type is defined +// as the type Tmpl. This allows us to actually instantiate the +// template "selected" by TemplateSel. +// +// This trick is necessary for simulating typedef for class templates, +// which C++ doesn't support directly. +template +struct TemplateSel { + template + struct Bind { + typedef Tmpl type; + }; +}; + +#define GTEST_BIND_(TmplSel, T) \ + TmplSel::template Bind::type + +// A unique struct template used as the default value for the +// arguments of class template Templates. This allows us to simulate +// variadic templates (e.g. Templates, Templates, +// and etc), which C++ doesn't support directly. +template +struct NoneT {}; + +// The following family of struct and struct templates are used to +// represent template lists. In particular, TemplatesN represents a list of N templates (T1, T2, ..., and TN). Except +// for Templates0, every struct in the family has two member types: +// Head for the selector of the first template in the list, and Tail +// for the rest of the list. + +// The empty template list. +struct Templates0 {}; + +// Template lists of length 1, 2, 3, and so on. + +template +struct Templates1 { + typedef TemplateSel Head; + typedef Templates0 Tail; +}; +template +struct Templates2 { + typedef TemplateSel Head; + typedef Templates1 Tail; +}; + +template +struct Templates3 { + typedef TemplateSel Head; + typedef Templates2 Tail; +}; + +template +struct Templates4 { + typedef TemplateSel Head; + typedef Templates3 Tail; +}; + +template +struct Templates5 { + typedef TemplateSel Head; + typedef Templates4 Tail; +}; + +template +struct Templates6 { + typedef TemplateSel Head; + typedef Templates5 Tail; +}; + +template +struct Templates7 { + typedef TemplateSel Head; + typedef Templates6 Tail; +}; + +template +struct Templates8 { + typedef TemplateSel Head; + typedef Templates7 Tail; +}; + +template +struct Templates9 { + typedef TemplateSel Head; + typedef Templates8 Tail; +}; + +template +struct Templates10 { + typedef TemplateSel Head; + typedef Templates9 Tail; +}; + +template +struct Templates11 { + typedef TemplateSel Head; + typedef Templates10 Tail; +}; + +template +struct Templates12 { + typedef TemplateSel Head; + typedef Templates11 Tail; +}; + +template +struct Templates13 { + typedef TemplateSel Head; + typedef Templates12 Tail; +}; + +template +struct Templates14 { + typedef TemplateSel Head; + typedef Templates13 Tail; +}; + +template +struct Templates15 { + typedef TemplateSel Head; + typedef Templates14 Tail; +}; + +template +struct Templates16 { + typedef TemplateSel Head; + typedef Templates15 Tail; +}; + +template +struct Templates17 { + typedef TemplateSel Head; + typedef Templates16 Tail; +}; + +template +struct Templates18 { + typedef TemplateSel Head; + typedef Templates17 Tail; +}; + +template +struct Templates19 { + typedef TemplateSel Head; + typedef Templates18 Tail; +}; + +template +struct Templates20 { + typedef TemplateSel Head; + typedef Templates19 Tail; +}; + +template +struct Templates21 { + typedef TemplateSel Head; + typedef Templates20 Tail; +}; + +template +struct Templates22 { + typedef TemplateSel Head; + typedef Templates21 Tail; +}; + +template +struct Templates23 { + typedef TemplateSel Head; + typedef Templates22 Tail; +}; + +template +struct Templates24 { + typedef TemplateSel Head; + typedef Templates23 Tail; +}; + +template +struct Templates25 { + typedef TemplateSel Head; + typedef Templates24 Tail; +}; + +template +struct Templates26 { + typedef TemplateSel Head; + typedef Templates25 Tail; +}; + +template +struct Templates27 { + typedef TemplateSel Head; + typedef Templates26 Tail; +}; + +template +struct Templates28 { + typedef TemplateSel Head; + typedef Templates27 Tail; +}; + +template +struct Templates29 { + typedef TemplateSel Head; + typedef Templates28 Tail; +}; + +template +struct Templates30 { + typedef TemplateSel Head; + typedef Templates29 Tail; +}; + +template +struct Templates31 { + typedef TemplateSel Head; + typedef Templates30 Tail; +}; + +template +struct Templates32 { + typedef TemplateSel Head; + typedef Templates31 Tail; +}; + +template +struct Templates33 { + typedef TemplateSel Head; + typedef Templates32 Tail; +}; + +template +struct Templates34 { + typedef TemplateSel Head; + typedef Templates33 Tail; +}; + +template +struct Templates35 { + typedef TemplateSel Head; + typedef Templates34 Tail; +}; + +template +struct Templates36 { + typedef TemplateSel Head; + typedef Templates35 Tail; +}; + +template +struct Templates37 { + typedef TemplateSel Head; + typedef Templates36 Tail; +}; + +template +struct Templates38 { + typedef TemplateSel Head; + typedef Templates37 Tail; +}; + +template +struct Templates39 { + typedef TemplateSel Head; + typedef Templates38 Tail; +}; + +template +struct Templates40 { + typedef TemplateSel Head; + typedef Templates39 Tail; +}; + +template +struct Templates41 { + typedef TemplateSel Head; + typedef Templates40 Tail; +}; + +template +struct Templates42 { + typedef TemplateSel Head; + typedef Templates41 Tail; +}; + +template +struct Templates43 { + typedef TemplateSel Head; + typedef Templates42 Tail; +}; + +template +struct Templates44 { + typedef TemplateSel Head; + typedef Templates43 Tail; +}; + +template +struct Templates45 { + typedef TemplateSel Head; + typedef Templates44 Tail; +}; + +template +struct Templates46 { + typedef TemplateSel Head; + typedef Templates45 Tail; +}; + +template +struct Templates47 { + typedef TemplateSel Head; + typedef Templates46 Tail; +}; + +template +struct Templates48 { + typedef TemplateSel Head; + typedef Templates47 Tail; +}; + +template +struct Templates49 { + typedef TemplateSel Head; + typedef Templates48 Tail; +}; + +template +struct Templates50 { + typedef TemplateSel Head; + typedef Templates49 Tail; +}; + + +// We don't want to require the users to write TemplatesN<...> directly, +// as that would require them to count the length. Templates<...> is much +// easier to write, but generates horrible messages when there is a +// compiler error, as gcc insists on printing out each template +// argument, even if it has the default value (this means Templates +// will appear as Templates in the compiler +// errors). +// +// Our solution is to combine the best part of the two approaches: a +// user would write Templates, and Google Test will translate +// that to TemplatesN internally to make error messages +// readable. The translation is done by the 'type' member of the +// Templates template. +template +struct Templates { + typedef Templates50 type; +}; + +template <> +struct Templates { + typedef Templates0 type; +}; +template +struct Templates { + typedef Templates1 type; +}; +template +struct Templates { + typedef Templates2 type; +}; +template +struct Templates { + typedef Templates3 type; +}; +template +struct Templates { + typedef Templates4 type; +}; +template +struct Templates { + typedef Templates5 type; +}; +template +struct Templates { + typedef Templates6 type; +}; +template +struct Templates { + typedef Templates7 type; +}; +template +struct Templates { + typedef Templates8 type; +}; +template +struct Templates { + typedef Templates9 type; +}; +template +struct Templates { + typedef Templates10 type; +}; +template +struct Templates { + typedef Templates11 type; +}; +template +struct Templates { + typedef Templates12 type; +}; +template +struct Templates { + typedef Templates13 type; +}; +template +struct Templates { + typedef Templates14 type; +}; +template +struct Templates { + typedef Templates15 type; +}; +template +struct Templates { + typedef Templates16 type; +}; +template +struct Templates { + typedef Templates17 type; +}; +template +struct Templates { + typedef Templates18 type; +}; +template +struct Templates { + typedef Templates19 type; +}; +template +struct Templates { + typedef Templates20 type; +}; +template +struct Templates { + typedef Templates21 type; +}; +template +struct Templates { + typedef Templates22 type; +}; +template +struct Templates { + typedef Templates23 type; +}; +template +struct Templates { + typedef Templates24 type; +}; +template +struct Templates { + typedef Templates25 type; +}; +template +struct Templates { + typedef Templates26 type; +}; +template +struct Templates { + typedef Templates27 type; +}; +template +struct Templates { + typedef Templates28 type; +}; +template +struct Templates { + typedef Templates29 type; +}; +template +struct Templates { + typedef Templates30 type; +}; +template +struct Templates { + typedef Templates31 type; +}; +template +struct Templates { + typedef Templates32 type; +}; +template +struct Templates { + typedef Templates33 type; +}; +template +struct Templates { + typedef Templates34 type; +}; +template +struct Templates { + typedef Templates35 type; +}; +template +struct Templates { + typedef Templates36 type; +}; +template +struct Templates { + typedef Templates37 type; +}; +template +struct Templates { + typedef Templates38 type; +}; +template +struct Templates { + typedef Templates39 type; +}; +template +struct Templates { + typedef Templates40 type; +}; +template +struct Templates { + typedef Templates41 type; +}; +template +struct Templates { + typedef Templates42 type; +}; +template +struct Templates { + typedef Templates43 type; +}; +template +struct Templates { + typedef Templates44 type; +}; +template +struct Templates { + typedef Templates45 type; +}; +template +struct Templates { + typedef Templates46 type; +}; +template +struct Templates { + typedef Templates47 type; +}; +template +struct Templates { + typedef Templates48 type; +}; +template +struct Templates { + typedef Templates49 type; +}; + +// The TypeList template makes it possible to use either a single type +// or a Types<...> list in TYPED_TEST_CASE() and +// INSTANTIATE_TYPED_TEST_CASE_P(). + +template +struct TypeList { typedef Types1 type; }; + +template +struct TypeList > { + typedef typename Types::type type; +}; + +} // namespace internal +} // namespace testing + +#endif // GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ diff --git a/3rdparty/gmock/gtest/include/gtest/internal/gtest-type-util.h.pump b/3rdparty/gmock/gtest/include/gtest/internal/gtest-type-util.h.pump new file mode 100644 index 00000000..5aed1e55 --- /dev/null +++ b/3rdparty/gmock/gtest/include/gtest/internal/gtest-type-util.h.pump @@ -0,0 +1,287 @@ +$$ -*- mode: c++; -*- +$var n = 50 $$ Maximum length of type lists we want to support. +// Copyright 2008 Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Type utilities needed for implementing typed and type-parameterized +// tests. This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +// Currently we support at most $n types in a list, and at most $n +// type-parameterized tests in one type-parameterized test case. +// Please contact googletestframework@googlegroups.com if you need +// more. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ + +#include +#include + +#if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +// #ifdef __GNUC__ is too general here. It is possible to use gcc without using +// libstdc++ (which is where cxxabi.h comes from). +#ifdef __GLIBCXX__ +#include +#endif // __GLIBCXX__ + +namespace testing { +namespace internal { + +// AssertyTypeEq::type is defined iff T1 and T2 are the same +// type. This can be used as a compile-time assertion to ensure that +// two types are equal. + +template +struct AssertTypeEq; + +template +struct AssertTypeEq { + typedef bool type; +}; + +// GetTypeName() returns a human-readable name of type T. +template +String GetTypeName() { +#if GTEST_HAS_RTTI + + const char* const name = typeid(T).name(); +#ifdef __GLIBCXX__ + int status = 0; + // gcc's implementation of typeid(T).name() mangles the type name, + // so we have to demangle it. + char* const readable_name = abi::__cxa_demangle(name, 0, 0, &status); + const String name_str(status == 0 ? readable_name : name); + free(readable_name); + return name_str; +#else + return name; +#endif // __GLIBCXX__ + +#else + return ""; +#endif // GTEST_HAS_RTTI +} + +// A unique type used as the default value for the arguments of class +// template Types. This allows us to simulate variadic templates +// (e.g. Types, Type, and etc), which C++ doesn't +// support directly. +struct None {}; + +// The following family of struct and struct templates are used to +// represent type lists. In particular, TypesN +// represents a type list with N types (T1, T2, ..., and TN) in it. +// Except for Types0, every struct in the family has two member types: +// Head for the first type in the list, and Tail for the rest of the +// list. + +// The empty type list. +struct Types0 {}; + +// Type lists of length 1, 2, 3, and so on. + +template +struct Types1 { + typedef T1 Head; + typedef Types0 Tail; +}; + +$range i 2..n + +$for i [[ +$range j 1..i +$range k 2..i +template <$for j, [[typename T$j]]> +struct Types$i { + typedef T1 Head; + typedef Types$(i-1)<$for k, [[T$k]]> Tail; +}; + + +]] + +} // namespace internal + +// We don't want to require the users to write TypesN<...> directly, +// as that would require them to count the length. Types<...> is much +// easier to write, but generates horrible messages when there is a +// compiler error, as gcc insists on printing out each template +// argument, even if it has the default value (this means Types +// will appear as Types in the compiler +// errors). +// +// Our solution is to combine the best part of the two approaches: a +// user would write Types, and Google Test will translate +// that to TypesN internally to make error messages +// readable. The translation is done by the 'type' member of the +// Types template. + +$range i 1..n +template <$for i, [[typename T$i = internal::None]]> +struct Types { + typedef internal::Types$n<$for i, [[T$i]]> type; +}; + +template <> +struct Types<$for i, [[internal::None]]> { + typedef internal::Types0 type; +}; + +$range i 1..n-1 +$for i [[ +$range j 1..i +$range k i+1..n +template <$for j, [[typename T$j]]> +struct Types<$for j, [[T$j]]$for k[[, internal::None]]> { + typedef internal::Types$i<$for j, [[T$j]]> type; +}; + +]] + +namespace internal { + +#define GTEST_TEMPLATE_ template class + +// The template "selector" struct TemplateSel is used to +// represent Tmpl, which must be a class template with one type +// parameter, as a type. TemplateSel::Bind::type is defined +// as the type Tmpl. This allows us to actually instantiate the +// template "selected" by TemplateSel. +// +// This trick is necessary for simulating typedef for class templates, +// which C++ doesn't support directly. +template +struct TemplateSel { + template + struct Bind { + typedef Tmpl type; + }; +}; + +#define GTEST_BIND_(TmplSel, T) \ + TmplSel::template Bind::type + +// A unique struct template used as the default value for the +// arguments of class template Templates. This allows us to simulate +// variadic templates (e.g. Templates, Templates, +// and etc), which C++ doesn't support directly. +template +struct NoneT {}; + +// The following family of struct and struct templates are used to +// represent template lists. In particular, TemplatesN represents a list of N templates (T1, T2, ..., and TN). Except +// for Templates0, every struct in the family has two member types: +// Head for the selector of the first template in the list, and Tail +// for the rest of the list. + +// The empty template list. +struct Templates0 {}; + +// Template lists of length 1, 2, 3, and so on. + +template +struct Templates1 { + typedef TemplateSel Head; + typedef Templates0 Tail; +}; + +$range i 2..n + +$for i [[ +$range j 1..i +$range k 2..i +template <$for j, [[GTEST_TEMPLATE_ T$j]]> +struct Templates$i { + typedef TemplateSel Head; + typedef Templates$(i-1)<$for k, [[T$k]]> Tail; +}; + + +]] + +// We don't want to require the users to write TemplatesN<...> directly, +// as that would require them to count the length. Templates<...> is much +// easier to write, but generates horrible messages when there is a +// compiler error, as gcc insists on printing out each template +// argument, even if it has the default value (this means Templates +// will appear as Templates in the compiler +// errors). +// +// Our solution is to combine the best part of the two approaches: a +// user would write Templates, and Google Test will translate +// that to TemplatesN internally to make error messages +// readable. The translation is done by the 'type' member of the +// Templates template. + +$range i 1..n +template <$for i, [[GTEST_TEMPLATE_ T$i = NoneT]]> +struct Templates { + typedef Templates$n<$for i, [[T$i]]> type; +}; + +template <> +struct Templates<$for i, [[NoneT]]> { + typedef Templates0 type; +}; + +$range i 1..n-1 +$for i [[ +$range j 1..i +$range k i+1..n +template <$for j, [[GTEST_TEMPLATE_ T$j]]> +struct Templates<$for j, [[T$j]]$for k[[, NoneT]]> { + typedef Templates$i<$for j, [[T$j]]> type; +}; + +]] + +// The TypeList template makes it possible to use either a single type +// or a Types<...> list in TYPED_TEST_CASE() and +// INSTANTIATE_TYPED_TEST_CASE_P(). + +template +struct TypeList { typedef Types1 type; }; + + +$range i 1..n +template <$for i, [[typename T$i]]> +struct TypeList > { + typedef typename Types<$for i, [[T$i]]>::type type; +}; + +} // namespace internal +} // namespace testing + +#endif // GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ diff --git a/3rdparty/gmock/gtest/src/gtest-all.cc b/3rdparty/gmock/gtest/src/gtest-all.cc new file mode 100644 index 00000000..fe34765f --- /dev/null +++ b/3rdparty/gmock/gtest/src/gtest-all.cc @@ -0,0 +1,47 @@ +// Copyright 2008, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: mheule@google.com (Markus Heule) +// +// Google C++ Testing Framework (Google Test) +// +// Sometimes it's desirable to build Google Test by compiling a single file. +// This file serves this purpose. + +// This line ensures that gtest.h can be compiled on its own, even +// when it's fused. +#include + +// The following lines pull in the real gtest *.cc files. +#include "src/gtest.cc" +#include "src/gtest-death-test.cc" +#include "src/gtest-filepath.cc" +#include "src/gtest-port.cc" +#include "src/gtest-test-part.cc" +#include "src/gtest-typed-test.cc" diff --git a/3rdparty/gmock/gtest/src/gtest-death-test.cc b/3rdparty/gmock/gtest/src/gtest-death-test.cc new file mode 100644 index 00000000..3b73b01d --- /dev/null +++ b/3rdparty/gmock/gtest/src/gtest-death-test.cc @@ -0,0 +1,1172 @@ +// Copyright 2005, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan), vladl@google.com (Vlad Losev) +// +// This file implements death tests. + +#include +#include + +#if GTEST_HAS_DEATH_TEST + +#if GTEST_OS_MAC +#include +#endif // GTEST_OS_MAC + +#include +#include +#include +#include + +#if GTEST_OS_WINDOWS +#include +#else +#include +#include +#endif // GTEST_OS_WINDOWS + +#endif // GTEST_HAS_DEATH_TEST + +#include +#include + +// Indicates that this translation unit is part of Google Test's +// implementation. It must come before gtest-internal-inl.h is +// included, or there will be a compiler error. This trick is to +// prevent a user from accidentally including gtest-internal-inl.h in +// his code. +#define GTEST_IMPLEMENTATION_ 1 +#include "src/gtest-internal-inl.h" +#undef GTEST_IMPLEMENTATION_ + +namespace testing { + +// Constants. + +// The default death test style. +static const char kDefaultDeathTestStyle[] = "fast"; + +GTEST_DEFINE_string_( + death_test_style, + internal::StringFromGTestEnv("death_test_style", kDefaultDeathTestStyle), + "Indicates how to run a death test in a forked child process: " + "\"threadsafe\" (child process re-executes the test binary " + "from the beginning, running only the specific death test) or " + "\"fast\" (child process runs the death test immediately " + "after forking)."); + +GTEST_DEFINE_bool_( + death_test_use_fork, + internal::BoolFromGTestEnv("death_test_use_fork", false), + "Instructs to use fork()/_exit() instead of clone() in death tests. " + "Ignored and always uses fork() on POSIX systems where clone() is not " + "implemented. Useful when running under valgrind or similar tools if " + "those do not support clone(). Valgrind 3.3.1 will just fail if " + "it sees an unsupported combination of clone() flags. " + "It is not recommended to use this flag w/o valgrind though it will " + "work in 99% of the cases. Once valgrind is fixed, this flag will " + "most likely be removed."); + +namespace internal { +GTEST_DEFINE_string_( + internal_run_death_test, "", + "Indicates the file, line number, temporal index of " + "the single death test to run, and a file descriptor to " + "which a success code may be sent, all separated by " + "colons. This flag is specified if and only if the current " + "process is a sub-process launched for running a thread-safe " + "death test. FOR INTERNAL USE ONLY."); +} // namespace internal + +#if GTEST_HAS_DEATH_TEST + +// ExitedWithCode constructor. +ExitedWithCode::ExitedWithCode(int exit_code) : exit_code_(exit_code) { +} + +// ExitedWithCode function-call operator. +bool ExitedWithCode::operator()(int exit_status) const { +#if GTEST_OS_WINDOWS + return exit_status == exit_code_; +#else + return WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == exit_code_; +#endif // GTEST_OS_WINDOWS +} + +#if !GTEST_OS_WINDOWS +// KilledBySignal constructor. +KilledBySignal::KilledBySignal(int signum) : signum_(signum) { +} + +// KilledBySignal function-call operator. +bool KilledBySignal::operator()(int exit_status) const { + return WIFSIGNALED(exit_status) && WTERMSIG(exit_status) == signum_; +} +#endif // !GTEST_OS_WINDOWS + +namespace internal { + +// Utilities needed for death tests. + +// Generates a textual description of a given exit code, in the format +// specified by wait(2). +static String ExitSummary(int exit_code) { + Message m; +#if GTEST_OS_WINDOWS + m << "Exited with exit status " << exit_code; +#else + if (WIFEXITED(exit_code)) { + m << "Exited with exit status " << WEXITSTATUS(exit_code); + } else if (WIFSIGNALED(exit_code)) { + m << "Terminated by signal " << WTERMSIG(exit_code); + } +#ifdef WCOREDUMP + if (WCOREDUMP(exit_code)) { + m << " (core dumped)"; + } +#endif +#endif // GTEST_OS_WINDOWS + return m.GetString(); +} + +// Returns true if exit_status describes a process that was terminated +// by a signal, or exited normally with a nonzero exit code. +bool ExitedUnsuccessfully(int exit_status) { + return !ExitedWithCode(0)(exit_status); +} + +#if !GTEST_OS_WINDOWS +// Generates a textual failure message when a death test finds more than +// one thread running, or cannot determine the number of threads, prior +// to executing the given statement. It is the responsibility of the +// caller not to pass a thread_count of 1. +static String DeathTestThreadWarning(size_t thread_count) { + Message msg; + msg << "Death tests use fork(), which is unsafe particularly" + << " in a threaded context. For this test, " << GTEST_NAME_ << " "; + if (thread_count == 0) + msg << "couldn't detect the number of threads."; + else + msg << "detected " << thread_count << " threads."; + return msg.GetString(); +} +#endif // !GTEST_OS_WINDOWS + +// Flag characters for reporting a death test that did not die. +static const char kDeathTestLived = 'L'; +static const char kDeathTestReturned = 'R'; +static const char kDeathTestInternalError = 'I'; + +// An enumeration describing all of the possible ways that a death test +// can conclude. DIED means that the process died while executing the +// test code; LIVED means that process lived beyond the end of the test +// code; and RETURNED means that the test statement attempted a "return," +// which is not allowed. IN_PROGRESS means the test has not yet +// concluded. +enum DeathTestOutcome { IN_PROGRESS, DIED, LIVED, RETURNED }; + +// Routine for aborting the program which is safe to call from an +// exec-style death test child process, in which case the error +// message is propagated back to the parent process. Otherwise, the +// message is simply printed to stderr. In either case, the program +// then exits with status 1. +void DeathTestAbort(const String& message) { + // On a POSIX system, this function may be called from a threadsafe-style + // death test child process, which operates on a very small stack. Use + // the heap for any additional non-minuscule memory requirements. + const InternalRunDeathTestFlag* const flag = + GetUnitTestImpl()->internal_run_death_test_flag(); + if (flag != NULL) { + FILE* parent = posix::FDOpen(flag->write_fd(), "w"); + fputc(kDeathTestInternalError, parent); + fprintf(parent, "%s", message.c_str()); + fflush(parent); + _exit(1); + } else { + fprintf(stderr, "%s", message.c_str()); + fflush(stderr); + abort(); + } +} + +// A replacement for CHECK that calls DeathTestAbort if the assertion +// fails. +#define GTEST_DEATH_TEST_CHECK_(expression) \ + do { \ + if (!::testing::internal::IsTrue(expression)) { \ + DeathTestAbort(::testing::internal::String::Format( \ + "CHECK failed: File %s, line %d: %s", \ + __FILE__, __LINE__, #expression)); \ + } \ + } while (::testing::internal::AlwaysFalse()) + +// This macro is similar to GTEST_DEATH_TEST_CHECK_, but it is meant for +// evaluating any system call that fulfills two conditions: it must return +// -1 on failure, and set errno to EINTR when it is interrupted and +// should be tried again. The macro expands to a loop that repeatedly +// evaluates the expression as long as it evaluates to -1 and sets +// errno to EINTR. If the expression evaluates to -1 but errno is +// something other than EINTR, DeathTestAbort is called. +#define GTEST_DEATH_TEST_CHECK_SYSCALL_(expression) \ + do { \ + int gtest_retval; \ + do { \ + gtest_retval = (expression); \ + } while (gtest_retval == -1 && errno == EINTR); \ + if (gtest_retval == -1) { \ + DeathTestAbort(::testing::internal::String::Format( \ + "CHECK failed: File %s, line %d: %s != -1", \ + __FILE__, __LINE__, #expression)); \ + } \ + } while (::testing::internal::AlwaysFalse()) + +// Returns the message describing the last system error in errno. +String GetLastErrnoDescription() { + return String(errno == 0 ? "" : posix::StrError(errno)); +} + +// This is called from a death test parent process to read a failure +// message from the death test child process and log it with the FATAL +// severity. On Windows, the message is read from a pipe handle. On other +// platforms, it is read from a file descriptor. +static void FailFromInternalError(int fd) { + Message error; + char buffer[256]; + int num_read; + + do { + while ((num_read = posix::Read(fd, buffer, 255)) > 0) { + buffer[num_read] = '\0'; + error << buffer; + } + } while (num_read == -1 && errno == EINTR); + + if (num_read == 0) { + GTEST_LOG_(FATAL) << error.GetString(); + } else { + const int last_error = errno; + GTEST_LOG_(FATAL) << "Error while reading death test internal: " + << GetLastErrnoDescription() << " [" << last_error << "]"; + } +} + +// Death test constructor. Increments the running death test count +// for the current test. +DeathTest::DeathTest() { + TestInfo* const info = GetUnitTestImpl()->current_test_info(); + if (info == NULL) { + DeathTestAbort("Cannot run a death test outside of a TEST or " + "TEST_F construct"); + } +} + +// Creates and returns a death test by dispatching to the current +// death test factory. +bool DeathTest::Create(const char* statement, const RE* regex, + const char* file, int line, DeathTest** test) { + return GetUnitTestImpl()->death_test_factory()->Create( + statement, regex, file, line, test); +} + +const char* DeathTest::LastMessage() { + return last_death_test_message_.c_str(); +} + +void DeathTest::set_last_death_test_message(const String& message) { + last_death_test_message_ = message; +} + +String DeathTest::last_death_test_message_; + +// Provides cross platform implementation for some death functionality. +class DeathTestImpl : public DeathTest { + protected: + DeathTestImpl(const char* a_statement, const RE* a_regex) + : statement_(a_statement), + regex_(a_regex), + spawned_(false), + status_(-1), + outcome_(IN_PROGRESS), + read_fd_(-1), + write_fd_(-1) {} + + // read_fd_ is expected to be closed and cleared by a derived class. + ~DeathTestImpl() { GTEST_DEATH_TEST_CHECK_(read_fd_ == -1); } + + void Abort(AbortReason reason); + virtual bool Passed(bool status_ok); + + const char* statement() const { return statement_; } + const RE* regex() const { return regex_; } + bool spawned() const { return spawned_; } + void set_spawned(bool is_spawned) { spawned_ = is_spawned; } + int status() const { return status_; } + void set_status(int a_status) { status_ = a_status; } + DeathTestOutcome outcome() const { return outcome_; } + void set_outcome(DeathTestOutcome an_outcome) { outcome_ = an_outcome; } + int read_fd() const { return read_fd_; } + void set_read_fd(int fd) { read_fd_ = fd; } + int write_fd() const { return write_fd_; } + void set_write_fd(int fd) { write_fd_ = fd; } + + // Called in the parent process only. Reads the result code of the death + // test child process via a pipe, interprets it to set the outcome_ + // member, and closes read_fd_. Outputs diagnostics and terminates in + // case of unexpected codes. + void ReadAndInterpretStatusByte(); + + private: + // The textual content of the code this object is testing. This class + // doesn't own this string and should not attempt to delete it. + const char* const statement_; + // The regular expression which test output must match. DeathTestImpl + // doesn't own this object and should not attempt to delete it. + const RE* const regex_; + // True if the death test child process has been successfully spawned. + bool spawned_; + // The exit status of the child process. + int status_; + // How the death test concluded. + DeathTestOutcome outcome_; + // Descriptor to the read end of the pipe to the child process. It is + // always -1 in the child process. The child keeps its write end of the + // pipe in write_fd_. + int read_fd_; + // Descriptor to the child's write end of the pipe to the parent process. + // It is always -1 in the parent process. The parent keeps its end of the + // pipe in read_fd_. + int write_fd_; +}; + +// Called in the parent process only. Reads the result code of the death +// test child process via a pipe, interprets it to set the outcome_ +// member, and closes read_fd_. Outputs diagnostics and terminates in +// case of unexpected codes. +void DeathTestImpl::ReadAndInterpretStatusByte() { + char flag; + int bytes_read; + + // The read() here blocks until data is available (signifying the + // failure of the death test) or until the pipe is closed (signifying + // its success), so it's okay to call this in the parent before + // the child process has exited. + do { + bytes_read = posix::Read(read_fd(), &flag, 1); + } while (bytes_read == -1 && errno == EINTR); + + if (bytes_read == 0) { + set_outcome(DIED); + } else if (bytes_read == 1) { + switch (flag) { + case kDeathTestReturned: + set_outcome(RETURNED); + break; + case kDeathTestLived: + set_outcome(LIVED); + break; + case kDeathTestInternalError: + FailFromInternalError(read_fd()); // Does not return. + break; + default: + GTEST_LOG_(FATAL) << "Death test child process reported " + << "unexpected status byte (" + << static_cast(flag) << ")"; + } + } else { + GTEST_LOG_(FATAL) << "Read from death test child process failed: " + << GetLastErrnoDescription(); + } + GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Close(read_fd())); + set_read_fd(-1); +} + +// Signals that the death test code which should have exited, didn't. +// Should be called only in a death test child process. +// Writes a status byte to the child's status file descriptor, then +// calls _exit(1). +void DeathTestImpl::Abort(AbortReason reason) { + // The parent process considers the death test to be a failure if + // it finds any data in our pipe. So, here we write a single flag byte + // to the pipe, then exit. + const char status_ch = + reason == TEST_DID_NOT_DIE ? kDeathTestLived : kDeathTestReturned; + GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Write(write_fd(), &status_ch, 1)); + GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Close(write_fd())); + _exit(1); // Exits w/o any normal exit hooks (we were supposed to crash) +} + +// Assesses the success or failure of a death test, using both private +// members which have previously been set, and one argument: +// +// Private data members: +// outcome: An enumeration describing how the death test +// concluded: DIED, LIVED, or RETURNED. The death test fails +// in the latter two cases. +// status: The exit status of the child process. On *nix, it is in the +// in the format specified by wait(2). On Windows, this is the +// value supplied to the ExitProcess() API or a numeric code +// of the exception that terminated the program. +// regex: A regular expression object to be applied to +// the test's captured standard error output; the death test +// fails if it does not match. +// +// Argument: +// status_ok: true if exit_status is acceptable in the context of +// this particular death test, which fails if it is false +// +// Returns true iff all of the above conditions are met. Otherwise, the +// first failing condition, in the order given above, is the one that is +// reported. Also sets the last death test message string. +bool DeathTestImpl::Passed(bool status_ok) { + if (!spawned()) + return false; + + const String error_message = GetCapturedStderr(); + + bool success = false; + Message buffer; + + buffer << "Death test: " << statement() << "\n"; + switch (outcome()) { + case LIVED: + buffer << " Result: failed to die.\n" + << " Error msg: " << error_message; + break; + case RETURNED: + buffer << " Result: illegal return in test statement.\n" + << " Error msg: " << error_message; + break; + case DIED: + if (status_ok) { + const bool matched = RE::PartialMatch(error_message.c_str(), *regex()); + if (matched) { + success = true; + } else { + buffer << " Result: died but not with expected error.\n" + << " Expected: " << regex()->pattern() << "\n" + << "Actual msg: " << error_message; + } + } else { + buffer << " Result: died but not with expected exit code:\n" + << " " << ExitSummary(status()) << "\n"; + } + break; + case IN_PROGRESS: + default: + GTEST_LOG_(FATAL) + << "DeathTest::Passed somehow called before conclusion of test"; + } + + DeathTest::set_last_death_test_message(buffer.GetString()); + return success; +} + +#if GTEST_OS_WINDOWS +// WindowsDeathTest implements death tests on Windows. Due to the +// specifics of starting new processes on Windows, death tests there are +// always threadsafe, and Google Test considers the +// --gtest_death_test_style=fast setting to be equivalent to +// --gtest_death_test_style=threadsafe there. +// +// A few implementation notes: Like the Linux version, the Windows +// implementation uses pipes for child-to-parent communication. But due to +// the specifics of pipes on Windows, some extra steps are required: +// +// 1. The parent creates a communication pipe and stores handles to both +// ends of it. +// 2. The parent starts the child and provides it with the information +// necessary to acquire the handle to the write end of the pipe. +// 3. The child acquires the write end of the pipe and signals the parent +// using a Windows event. +// 4. Now the parent can release the write end of the pipe on its side. If +// this is done before step 3, the object's reference count goes down to +// 0 and it is destroyed, preventing the child from acquiring it. The +// parent now has to release it, or read operations on the read end of +// the pipe will not return when the child terminates. +// 5. The parent reads child's output through the pipe (outcome code and +// any possible error messages) from the pipe, and its stderr and then +// determines whether to fail the test. +// +// Note: to distinguish Win32 API calls from the local method and function +// calls, the former are explicitly resolved in the global namespace. +// +class WindowsDeathTest : public DeathTestImpl { + public: + WindowsDeathTest(const char* statement, + const RE* regex, + const char* file, + int line) + : DeathTestImpl(statement, regex), file_(file), line_(line) {} + + // All of these virtual functions are inherited from DeathTest. + virtual int Wait(); + virtual TestRole AssumeRole(); + + private: + // The name of the file in which the death test is located. + const char* const file_; + // The line number on which the death test is located. + const int line_; + // Handle to the write end of the pipe to the child process. + AutoHandle write_handle_; + // Child process handle. + AutoHandle child_handle_; + // Event the child process uses to signal the parent that it has + // acquired the handle to the write end of the pipe. After seeing this + // event the parent can release its own handles to make sure its + // ReadFile() calls return when the child terminates. + AutoHandle event_handle_; +}; + +// Waits for the child in a death test to exit, returning its exit +// status, or 0 if no child process exists. As a side effect, sets the +// outcome data member. +int WindowsDeathTest::Wait() { + if (!spawned()) + return 0; + + // Wait until the child either signals that it has acquired the write end + // of the pipe or it dies. + const HANDLE wait_handles[2] = { child_handle_.Get(), event_handle_.Get() }; + switch (::WaitForMultipleObjects(2, + wait_handles, + FALSE, // Waits for any of the handles. + INFINITE)) { + case WAIT_OBJECT_0: + case WAIT_OBJECT_0 + 1: + break; + default: + GTEST_DEATH_TEST_CHECK_(false); // Should not get here. + } + + // The child has acquired the write end of the pipe or exited. + // We release the handle on our side and continue. + write_handle_.Reset(); + event_handle_.Reset(); + + ReadAndInterpretStatusByte(); + + // Waits for the child process to exit if it haven't already. This + // returns immediately if the child has already exited, regardless of + // whether previous calls to WaitForMultipleObjects synchronized on this + // handle or not. + GTEST_DEATH_TEST_CHECK_( + WAIT_OBJECT_0 == ::WaitForSingleObject(child_handle_.Get(), + INFINITE)); + DWORD status; + GTEST_DEATH_TEST_CHECK_(::GetExitCodeProcess(child_handle_.Get(), &status) + != FALSE); + child_handle_.Reset(); + set_status(static_cast(status)); + return this->status(); +} + +// The AssumeRole process for a Windows death test. It creates a child +// process with the same executable as the current process to run the +// death test. The child process is given the --gtest_filter and +// --gtest_internal_run_death_test flags such that it knows to run the +// current death test only. +DeathTest::TestRole WindowsDeathTest::AssumeRole() { + const UnitTestImpl* const impl = GetUnitTestImpl(); + const InternalRunDeathTestFlag* const flag = + impl->internal_run_death_test_flag(); + const TestInfo* const info = impl->current_test_info(); + const int death_test_index = info->result()->death_test_count(); + + if (flag != NULL) { + // ParseInternalRunDeathTestFlag() has performed all the necessary + // processing. + set_write_fd(flag->write_fd()); + return EXECUTE_TEST; + } + + // WindowsDeathTest uses an anonymous pipe to communicate results of + // a death test. + SECURITY_ATTRIBUTES handles_are_inheritable = { + sizeof(SECURITY_ATTRIBUTES), NULL, TRUE }; + HANDLE read_handle, write_handle; + GTEST_DEATH_TEST_CHECK_( + ::CreatePipe(&read_handle, &write_handle, &handles_are_inheritable, + 0) // Default buffer size. + != FALSE); + set_read_fd(::_open_osfhandle(reinterpret_cast(read_handle), + O_RDONLY)); + write_handle_.Reset(write_handle); + event_handle_.Reset(::CreateEvent( + &handles_are_inheritable, + TRUE, // The event will automatically reset to non-signaled state. + FALSE, // The initial state is non-signalled. + NULL)); // The even is unnamed. + GTEST_DEATH_TEST_CHECK_(event_handle_.Get() != NULL); + const String filter_flag = String::Format("--%s%s=%s.%s", + GTEST_FLAG_PREFIX_, kFilterFlag, + info->test_case_name(), + info->name()); + const String internal_flag = String::Format( + "--%s%s=%s|%d|%d|%u|%Iu|%Iu", + GTEST_FLAG_PREFIX_, + kInternalRunDeathTestFlag, + file_, line_, + death_test_index, + static_cast(::GetCurrentProcessId()), + // size_t has the same with as pointers on both 32-bit and 64-bit + // Windows platforms. + // See http://msdn.microsoft.com/en-us/library/tcxf1dw6.aspx. + reinterpret_cast(write_handle), + reinterpret_cast(event_handle_.Get())); + + char executable_path[_MAX_PATH + 1]; // NOLINT + GTEST_DEATH_TEST_CHECK_( + _MAX_PATH + 1 != ::GetModuleFileNameA(NULL, + executable_path, + _MAX_PATH)); + + String command_line = String::Format("%s %s \"%s\"", + ::GetCommandLineA(), + filter_flag.c_str(), + internal_flag.c_str()); + + DeathTest::set_last_death_test_message(""); + + CaptureStderr(); + // Flush the log buffers since the log streams are shared with the child. + FlushInfoLog(); + + // The child process will share the standard handles with the parent. + STARTUPINFOA startup_info; + memset(&startup_info, 0, sizeof(STARTUPINFO)); + startup_info.dwFlags = STARTF_USESTDHANDLES; + startup_info.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE); + startup_info.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE); + startup_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE); + + PROCESS_INFORMATION process_info; + GTEST_DEATH_TEST_CHECK_(::CreateProcessA( + executable_path, + const_cast(command_line.c_str()), + NULL, // Retuned process handle is not inheritable. + NULL, // Retuned thread handle is not inheritable. + TRUE, // Child inherits all inheritable handles (for write_handle_). + 0x0, // Default creation flags. + NULL, // Inherit the parent's environment. + UnitTest::GetInstance()->original_working_dir(), + &startup_info, + &process_info) != FALSE); + child_handle_.Reset(process_info.hProcess); + ::CloseHandle(process_info.hThread); + set_spawned(true); + return OVERSEE_TEST; +} +#else // We are not on Windows. + +// ForkingDeathTest provides implementations for most of the abstract +// methods of the DeathTest interface. Only the AssumeRole method is +// left undefined. +class ForkingDeathTest : public DeathTestImpl { + public: + ForkingDeathTest(const char* statement, const RE* regex); + + // All of these virtual functions are inherited from DeathTest. + virtual int Wait(); + + protected: + void set_child_pid(pid_t child_pid) { child_pid_ = child_pid; } + + private: + // PID of child process during death test; 0 in the child process itself. + pid_t child_pid_; +}; + +// Constructs a ForkingDeathTest. +ForkingDeathTest::ForkingDeathTest(const char* a_statement, const RE* a_regex) + : DeathTestImpl(a_statement, a_regex), + child_pid_(-1) {} + +// Waits for the child in a death test to exit, returning its exit +// status, or 0 if no child process exists. As a side effect, sets the +// outcome data member. +int ForkingDeathTest::Wait() { + if (!spawned()) + return 0; + + ReadAndInterpretStatusByte(); + + int status_value; + GTEST_DEATH_TEST_CHECK_SYSCALL_(waitpid(child_pid_, &status_value, 0)); + set_status(status_value); + return status_value; +} + +// A concrete death test class that forks, then immediately runs the test +// in the child process. +class NoExecDeathTest : public ForkingDeathTest { + public: + NoExecDeathTest(const char* a_statement, const RE* a_regex) : + ForkingDeathTest(a_statement, a_regex) { } + virtual TestRole AssumeRole(); +}; + +// The AssumeRole process for a fork-and-run death test. It implements a +// straightforward fork, with a simple pipe to transmit the status byte. +DeathTest::TestRole NoExecDeathTest::AssumeRole() { + const size_t thread_count = GetThreadCount(); + if (thread_count != 1) { + GTEST_LOG_(WARNING) << DeathTestThreadWarning(thread_count); + } + + int pipe_fd[2]; + GTEST_DEATH_TEST_CHECK_(pipe(pipe_fd) != -1); + + DeathTest::set_last_death_test_message(""); + CaptureStderr(); + // When we fork the process below, the log file buffers are copied, but the + // file descriptors are shared. We flush all log files here so that closing + // the file descriptors in the child process doesn't throw off the + // synchronization between descriptors and buffers in the parent process. + // This is as close to the fork as possible to avoid a race condition in case + // there are multiple threads running before the death test, and another + // thread writes to the log file. + FlushInfoLog(); + + const pid_t child_pid = fork(); + GTEST_DEATH_TEST_CHECK_(child_pid != -1); + set_child_pid(child_pid); + if (child_pid == 0) { + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[0])); + set_write_fd(pipe_fd[1]); + // Redirects all logging to stderr in the child process to prevent + // concurrent writes to the log files. We capture stderr in the parent + // process and append the child process' output to a log. + LogToStderr(); + // Event forwarding to the listeners of event listener API mush be shut + // down in death test subprocesses. + GetUnitTestImpl()->listeners()->SuppressEventForwarding(); + return EXECUTE_TEST; + } else { + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[1])); + set_read_fd(pipe_fd[0]); + set_spawned(true); + return OVERSEE_TEST; + } +} + +// A concrete death test class that forks and re-executes the main +// program from the beginning, with command-line flags set that cause +// only this specific death test to be run. +class ExecDeathTest : public ForkingDeathTest { + public: + ExecDeathTest(const char* a_statement, const RE* a_regex, + const char* file, int line) : + ForkingDeathTest(a_statement, a_regex), file_(file), line_(line) { } + virtual TestRole AssumeRole(); + private: + // The name of the file in which the death test is located. + const char* const file_; + // The line number on which the death test is located. + const int line_; +}; + +// Utility class for accumulating command-line arguments. +class Arguments { + public: + Arguments() { + args_.push_back(NULL); + } + + ~Arguments() { + for (std::vector::iterator i = args_.begin(); i != args_.end(); + ++i) { + free(*i); + } + } + void AddArgument(const char* argument) { + args_.insert(args_.end() - 1, posix::StrDup(argument)); + } + + template + void AddArguments(const ::std::vector& arguments) { + for (typename ::std::vector::const_iterator i = arguments.begin(); + i != arguments.end(); + ++i) { + args_.insert(args_.end() - 1, posix::StrDup(i->c_str())); + } + } + char* const* Argv() { + return &args_[0]; + } + private: + std::vector args_; +}; + +// A struct that encompasses the arguments to the child process of a +// threadsafe-style death test process. +struct ExecDeathTestArgs { + char* const* argv; // Command-line arguments for the child's call to exec + int close_fd; // File descriptor to close; the read end of a pipe +}; + +#if GTEST_OS_MAC +inline char** GetEnviron() { + // When Google Test is built as a framework on MacOS X, the environ variable + // is unavailable. Apple's documentation (man environ) recommends using + // _NSGetEnviron() instead. + return *_NSGetEnviron(); +} +#else +// Some POSIX platforms expect you to declare environ. extern "C" makes +// it reside in the global namespace. +extern "C" char** environ; +inline char** GetEnviron() { return environ; } +#endif // GTEST_OS_MAC + +// The main function for a threadsafe-style death test child process. +// This function is called in a clone()-ed process and thus must avoid +// any potentially unsafe operations like malloc or libc functions. +static int ExecDeathTestChildMain(void* child_arg) { + ExecDeathTestArgs* const args = static_cast(child_arg); + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(args->close_fd)); + + // We need to execute the test program in the same environment where + // it was originally invoked. Therefore we change to the original + // working directory first. + const char* const original_dir = + UnitTest::GetInstance()->original_working_dir(); + // We can safely call chdir() as it's a direct system call. + if (chdir(original_dir) != 0) { + DeathTestAbort(String::Format("chdir(\"%s\") failed: %s", + original_dir, + GetLastErrnoDescription().c_str())); + return EXIT_FAILURE; + } + + // We can safely call execve() as it's a direct system call. We + // cannot use execvp() as it's a libc function and thus potentially + // unsafe. Since execve() doesn't search the PATH, the user must + // invoke the test program via a valid path that contains at least + // one path separator. + execve(args->argv[0], args->argv, GetEnviron()); + DeathTestAbort(String::Format("execve(%s, ...) in %s failed: %s", + args->argv[0], + original_dir, + GetLastErrnoDescription().c_str())); + return EXIT_FAILURE; +} + +// Two utility routines that together determine the direction the stack +// grows. +// This could be accomplished more elegantly by a single recursive +// function, but we want to guard against the unlikely possibility of +// a smart compiler optimizing the recursion away. +bool StackLowerThanAddress(const void* ptr) { + int dummy; + return &dummy < ptr; +} + +bool StackGrowsDown() { + int dummy; + return StackLowerThanAddress(&dummy); +} + +// A threadsafe implementation of fork(2) for threadsafe-style death tests +// that uses clone(2). It dies with an error message if anything goes +// wrong. +static pid_t ExecDeathTestFork(char* const* argv, int close_fd) { + ExecDeathTestArgs args = { argv, close_fd }; + pid_t child_pid = -1; + +#if GTEST_HAS_CLONE + const bool use_fork = GTEST_FLAG(death_test_use_fork); + + if (!use_fork) { + static const bool stack_grows_down = StackGrowsDown(); + const size_t stack_size = getpagesize(); + // MMAP_ANONYMOUS is not defined on Mac, so we use MAP_ANON instead. + void* const stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE, -1, 0); + GTEST_DEATH_TEST_CHECK_(stack != MAP_FAILED); + void* const stack_top = + static_cast(stack) + (stack_grows_down ? stack_size : 0); + + child_pid = clone(&ExecDeathTestChildMain, stack_top, SIGCHLD, &args); + + GTEST_DEATH_TEST_CHECK_(munmap(stack, stack_size) != -1); + } +#else + const bool use_fork = true; +#endif // GTEST_HAS_CLONE + + if (use_fork && (child_pid = fork()) == 0) { + ExecDeathTestChildMain(&args); + _exit(0); + } + + GTEST_DEATH_TEST_CHECK_(child_pid != -1); + return child_pid; +} + +// The AssumeRole process for a fork-and-exec death test. It re-executes the +// main program from the beginning, setting the --gtest_filter +// and --gtest_internal_run_death_test flags to cause only the current +// death test to be re-run. +DeathTest::TestRole ExecDeathTest::AssumeRole() { + const UnitTestImpl* const impl = GetUnitTestImpl(); + const InternalRunDeathTestFlag* const flag = + impl->internal_run_death_test_flag(); + const TestInfo* const info = impl->current_test_info(); + const int death_test_index = info->result()->death_test_count(); + + if (flag != NULL) { + set_write_fd(flag->write_fd()); + return EXECUTE_TEST; + } + + int pipe_fd[2]; + GTEST_DEATH_TEST_CHECK_(pipe(pipe_fd) != -1); + // Clear the close-on-exec flag on the write end of the pipe, lest + // it be closed when the child process does an exec: + GTEST_DEATH_TEST_CHECK_(fcntl(pipe_fd[1], F_SETFD, 0) != -1); + + const String filter_flag = + String::Format("--%s%s=%s.%s", + GTEST_FLAG_PREFIX_, kFilterFlag, + info->test_case_name(), info->name()); + const String internal_flag = + String::Format("--%s%s=%s|%d|%d|%d", + GTEST_FLAG_PREFIX_, kInternalRunDeathTestFlag, + file_, line_, death_test_index, pipe_fd[1]); + Arguments args; + args.AddArguments(GetArgvs()); + args.AddArgument(filter_flag.c_str()); + args.AddArgument(internal_flag.c_str()); + + DeathTest::set_last_death_test_message(""); + + CaptureStderr(); + // See the comment in NoExecDeathTest::AssumeRole for why the next line + // is necessary. + FlushInfoLog(); + + const pid_t child_pid = ExecDeathTestFork(args.Argv(), pipe_fd[0]); + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[1])); + set_child_pid(child_pid); + set_read_fd(pipe_fd[0]); + set_spawned(true); + return OVERSEE_TEST; +} + +#endif // !GTEST_OS_WINDOWS + +// Creates a concrete DeathTest-derived class that depends on the +// --gtest_death_test_style flag, and sets the pointer pointed to +// by the "test" argument to its address. If the test should be +// skipped, sets that pointer to NULL. Returns true, unless the +// flag is set to an invalid value. +bool DefaultDeathTestFactory::Create(const char* statement, const RE* regex, + const char* file, int line, + DeathTest** test) { + UnitTestImpl* const impl = GetUnitTestImpl(); + const InternalRunDeathTestFlag* const flag = + impl->internal_run_death_test_flag(); + const int death_test_index = impl->current_test_info() + ->increment_death_test_count(); + + if (flag != NULL) { + if (death_test_index > flag->index()) { + DeathTest::set_last_death_test_message(String::Format( + "Death test count (%d) somehow exceeded expected maximum (%d)", + death_test_index, flag->index())); + return false; + } + + if (!(flag->file() == file && flag->line() == line && + flag->index() == death_test_index)) { + *test = NULL; + return true; + } + } + +#if GTEST_OS_WINDOWS + if (GTEST_FLAG(death_test_style) == "threadsafe" || + GTEST_FLAG(death_test_style) == "fast") { + *test = new WindowsDeathTest(statement, regex, file, line); + } +#else + if (GTEST_FLAG(death_test_style) == "threadsafe") { + *test = new ExecDeathTest(statement, regex, file, line); + } else if (GTEST_FLAG(death_test_style) == "fast") { + *test = new NoExecDeathTest(statement, regex); + } +#endif // GTEST_OS_WINDOWS + else { // NOLINT - this is more readable than unbalanced brackets inside #if. + DeathTest::set_last_death_test_message(String::Format( + "Unknown death test style \"%s\" encountered", + GTEST_FLAG(death_test_style).c_str())); + return false; + } + + return true; +} + +// Splits a given string on a given delimiter, populating a given +// vector with the fields. GTEST_HAS_DEATH_TEST implies that we have +// ::std::string, so we can use it here. +static void SplitString(const ::std::string& str, char delimiter, + ::std::vector< ::std::string>* dest) { + ::std::vector< ::std::string> parsed; + ::std::string::size_type pos = 0; + while (::testing::internal::AlwaysTrue()) { + const ::std::string::size_type colon = str.find(delimiter, pos); + if (colon == ::std::string::npos) { + parsed.push_back(str.substr(pos)); + break; + } else { + parsed.push_back(str.substr(pos, colon - pos)); + pos = colon + 1; + } + } + dest->swap(parsed); +} + +#if GTEST_OS_WINDOWS +// Recreates the pipe and event handles from the provided parameters, +// signals the event, and returns a file descriptor wrapped around the pipe +// handle. This function is called in the child process only. +int GetStatusFileDescriptor(unsigned int parent_process_id, + size_t write_handle_as_size_t, + size_t event_handle_as_size_t) { + AutoHandle parent_process_handle(::OpenProcess(PROCESS_DUP_HANDLE, + FALSE, // Non-inheritable. + parent_process_id)); + if (parent_process_handle.Get() == INVALID_HANDLE_VALUE) { + DeathTestAbort(String::Format("Unable to open parent process %u", + parent_process_id)); + } + + // TODO(vladl@google.com): Replace the following check with a + // compile-time assertion when available. + GTEST_CHECK_(sizeof(HANDLE) <= sizeof(size_t)); + + const HANDLE write_handle = + reinterpret_cast(write_handle_as_size_t); + HANDLE dup_write_handle; + + // The newly initialized handle is accessible only in in the parent + // process. To obtain one accessible within the child, we need to use + // DuplicateHandle. + if (!::DuplicateHandle(parent_process_handle.Get(), write_handle, + ::GetCurrentProcess(), &dup_write_handle, + 0x0, // Requested privileges ignored since + // DUPLICATE_SAME_ACCESS is used. + FALSE, // Request non-inheritable handler. + DUPLICATE_SAME_ACCESS)) { + DeathTestAbort(String::Format( + "Unable to duplicate the pipe handle %Iu from the parent process %u", + write_handle_as_size_t, parent_process_id)); + } + + const HANDLE event_handle = reinterpret_cast(event_handle_as_size_t); + HANDLE dup_event_handle; + + if (!::DuplicateHandle(parent_process_handle.Get(), event_handle, + ::GetCurrentProcess(), &dup_event_handle, + 0x0, + FALSE, + DUPLICATE_SAME_ACCESS)) { + DeathTestAbort(String::Format( + "Unable to duplicate the event handle %Iu from the parent process %u", + event_handle_as_size_t, parent_process_id)); + } + + const int write_fd = + ::_open_osfhandle(reinterpret_cast(dup_write_handle), O_APPEND); + if (write_fd == -1) { + DeathTestAbort(String::Format( + "Unable to convert pipe handle %Iu to a file descriptor", + write_handle_as_size_t)); + } + + // Signals the parent that the write end of the pipe has been acquired + // so the parent can release its own write end. + ::SetEvent(dup_event_handle); + + return write_fd; +} +#endif // GTEST_OS_WINDOWS + +// Returns a newly created InternalRunDeathTestFlag object with fields +// initialized from the GTEST_FLAG(internal_run_death_test) flag if +// the flag is specified; otherwise returns NULL. +InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag() { + if (GTEST_FLAG(internal_run_death_test) == "") return NULL; + + // GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we + // can use it here. + int line = -1; + int index = -1; + ::std::vector< ::std::string> fields; + SplitString(GTEST_FLAG(internal_run_death_test).c_str(), '|', &fields); + int write_fd = -1; + +#if GTEST_OS_WINDOWS + unsigned int parent_process_id = 0; + size_t write_handle_as_size_t = 0; + size_t event_handle_as_size_t = 0; + + if (fields.size() != 6 + || !ParseNaturalNumber(fields[1], &line) + || !ParseNaturalNumber(fields[2], &index) + || !ParseNaturalNumber(fields[3], &parent_process_id) + || !ParseNaturalNumber(fields[4], &write_handle_as_size_t) + || !ParseNaturalNumber(fields[5], &event_handle_as_size_t)) { + DeathTestAbort(String::Format( + "Bad --gtest_internal_run_death_test flag: %s", + GTEST_FLAG(internal_run_death_test).c_str())); + } + write_fd = GetStatusFileDescriptor(parent_process_id, + write_handle_as_size_t, + event_handle_as_size_t); +#else + if (fields.size() != 4 + || !ParseNaturalNumber(fields[1], &line) + || !ParseNaturalNumber(fields[2], &index) + || !ParseNaturalNumber(fields[3], &write_fd)) { + DeathTestAbort(String::Format( + "Bad --gtest_internal_run_death_test flag: %s", + GTEST_FLAG(internal_run_death_test).c_str())); + } +#endif // GTEST_OS_WINDOWS + return new InternalRunDeathTestFlag(fields[0], line, index, write_fd); +} + +} // namespace internal + +#endif // GTEST_HAS_DEATH_TEST + +} // namespace testing diff --git a/3rdparty/gmock/gtest/src/gtest-filepath.cc b/3rdparty/gmock/gtest/src/gtest-filepath.cc new file mode 100644 index 00000000..c1ef9188 --- /dev/null +++ b/3rdparty/gmock/gtest/src/gtest-filepath.cc @@ -0,0 +1,380 @@ +// Copyright 2008, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Authors: keith.ray@gmail.com (Keith Ray) + +#include +#include + +#include + +#if GTEST_OS_WINDOWS_MOBILE +#include +#elif GTEST_OS_WINDOWS +#include +#include +#elif GTEST_OS_SYMBIAN +// Symbian OpenC has PATH_MAX in sys/syslimits.h +#include +#else +#include +#include // Some Linux distributions define PATH_MAX here. +#endif // GTEST_OS_WINDOWS_MOBILE + +#if GTEST_OS_WINDOWS +#define GTEST_PATH_MAX_ _MAX_PATH +#elif defined(PATH_MAX) +#define GTEST_PATH_MAX_ PATH_MAX +#elif defined(_XOPEN_PATH_MAX) +#define GTEST_PATH_MAX_ _XOPEN_PATH_MAX +#else +#define GTEST_PATH_MAX_ _POSIX_PATH_MAX +#endif // GTEST_OS_WINDOWS + +#include + +namespace testing { +namespace internal { + +#if GTEST_OS_WINDOWS +// On Windows, '\\' is the standard path separator, but many tools and the +// Windows API also accept '/' as an alternate path separator. Unless otherwise +// noted, a file path can contain either kind of path separators, or a mixture +// of them. +const char kPathSeparator = '\\'; +const char kAlternatePathSeparator = '/'; +const char kPathSeparatorString[] = "\\"; +const char kAlternatePathSeparatorString[] = "/"; +#if GTEST_OS_WINDOWS_MOBILE +// Windows CE doesn't have a current directory. You should not use +// the current directory in tests on Windows CE, but this at least +// provides a reasonable fallback. +const char kCurrentDirectoryString[] = "\\"; +// Windows CE doesn't define INVALID_FILE_ATTRIBUTES +const DWORD kInvalidFileAttributes = 0xffffffff; +#else +const char kCurrentDirectoryString[] = ".\\"; +#endif // GTEST_OS_WINDOWS_MOBILE +#else +const char kPathSeparator = '/'; +const char kPathSeparatorString[] = "/"; +const char kCurrentDirectoryString[] = "./"; +#endif // GTEST_OS_WINDOWS + +// Returns whether the given character is a valid path separator. +static bool IsPathSeparator(char c) { +#if GTEST_HAS_ALT_PATH_SEP_ + return (c == kPathSeparator) || (c == kAlternatePathSeparator); +#else + return c == kPathSeparator; +#endif +} + +// Returns the current working directory, or "" if unsuccessful. +FilePath FilePath::GetCurrentDir() { +#if GTEST_OS_WINDOWS_MOBILE + // Windows CE doesn't have a current directory, so we just return + // something reasonable. + return FilePath(kCurrentDirectoryString); +#elif GTEST_OS_WINDOWS + char cwd[GTEST_PATH_MAX_ + 1] = { '\0' }; + return FilePath(_getcwd(cwd, sizeof(cwd)) == NULL ? "" : cwd); +#else + char cwd[GTEST_PATH_MAX_ + 1] = { '\0' }; + return FilePath(getcwd(cwd, sizeof(cwd)) == NULL ? "" : cwd); +#endif // GTEST_OS_WINDOWS_MOBILE +} + +// Returns a copy of the FilePath with the case-insensitive extension removed. +// Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns +// FilePath("dir/file"). If a case-insensitive extension is not +// found, returns a copy of the original FilePath. +FilePath FilePath::RemoveExtension(const char* extension) const { + String dot_extension(String::Format(".%s", extension)); + if (pathname_.EndsWithCaseInsensitive(dot_extension.c_str())) { + return FilePath(String(pathname_.c_str(), pathname_.length() - 4)); + } + return *this; +} + +// Returns a pointer to the last occurence of a valid path separator in +// the FilePath. On Windows, for example, both '/' and '\' are valid path +// separators. Returns NULL if no path separator was found. +const char* FilePath::FindLastPathSeparator() const { + const char* const last_sep = strrchr(c_str(), kPathSeparator); +#if GTEST_HAS_ALT_PATH_SEP_ + const char* const last_alt_sep = strrchr(c_str(), kAlternatePathSeparator); + // Comparing two pointers of which only one is NULL is undefined. + if (last_alt_sep != NULL && + (last_sep == NULL || last_alt_sep > last_sep)) { + return last_alt_sep; + } +#endif + return last_sep; +} + +// Returns a copy of the FilePath with the directory part removed. +// Example: FilePath("path/to/file").RemoveDirectoryName() returns +// FilePath("file"). If there is no directory part ("just_a_file"), it returns +// the FilePath unmodified. If there is no file part ("just_a_dir/") it +// returns an empty FilePath (""). +// On Windows platform, '\' is the path separator, otherwise it is '/'. +FilePath FilePath::RemoveDirectoryName() const { + const char* const last_sep = FindLastPathSeparator(); + return last_sep ? FilePath(String(last_sep + 1)) : *this; +} + +// RemoveFileName returns the directory path with the filename removed. +// Example: FilePath("path/to/file").RemoveFileName() returns "path/to/". +// If the FilePath is "a_file" or "/a_file", RemoveFileName returns +// FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does +// not have a file, like "just/a/dir/", it returns the FilePath unmodified. +// On Windows platform, '\' is the path separator, otherwise it is '/'. +FilePath FilePath::RemoveFileName() const { + const char* const last_sep = FindLastPathSeparator(); + String dir; + if (last_sep) { + dir = String(c_str(), last_sep + 1 - c_str()); + } else { + dir = kCurrentDirectoryString; + } + return FilePath(dir); +} + +// Helper functions for naming files in a directory for xml output. + +// Given directory = "dir", base_name = "test", number = 0, +// extension = "xml", returns "dir/test.xml". If number is greater +// than zero (e.g., 12), returns "dir/test_12.xml". +// On Windows platform, uses \ as the separator rather than /. +FilePath FilePath::MakeFileName(const FilePath& directory, + const FilePath& base_name, + int number, + const char* extension) { + String file; + if (number == 0) { + file = String::Format("%s.%s", base_name.c_str(), extension); + } else { + file = String::Format("%s_%d.%s", base_name.c_str(), number, extension); + } + return ConcatPaths(directory, FilePath(file)); +} + +// Given directory = "dir", relative_path = "test.xml", returns "dir/test.xml". +// On Windows, uses \ as the separator rather than /. +FilePath FilePath::ConcatPaths(const FilePath& directory, + const FilePath& relative_path) { + if (directory.IsEmpty()) + return relative_path; + const FilePath dir(directory.RemoveTrailingPathSeparator()); + return FilePath(String::Format("%s%c%s", dir.c_str(), kPathSeparator, + relative_path.c_str())); +} + +// Returns true if pathname describes something findable in the file-system, +// either a file, directory, or whatever. +bool FilePath::FileOrDirectoryExists() const { +#if GTEST_OS_WINDOWS_MOBILE + LPCWSTR unicode = String::AnsiToUtf16(pathname_.c_str()); + const DWORD attributes = GetFileAttributes(unicode); + delete [] unicode; + return attributes != kInvalidFileAttributes; +#else + posix::StatStruct file_stat; + return posix::Stat(pathname_.c_str(), &file_stat) == 0; +#endif // GTEST_OS_WINDOWS_MOBILE +} + +// Returns true if pathname describes a directory in the file-system +// that exists. +bool FilePath::DirectoryExists() const { + bool result = false; +#if GTEST_OS_WINDOWS + // Don't strip off trailing separator if path is a root directory on + // Windows (like "C:\\"). + const FilePath& path(IsRootDirectory() ? *this : + RemoveTrailingPathSeparator()); +#else + const FilePath& path(*this); +#endif + +#if GTEST_OS_WINDOWS_MOBILE + LPCWSTR unicode = String::AnsiToUtf16(path.c_str()); + const DWORD attributes = GetFileAttributes(unicode); + delete [] unicode; + if ((attributes != kInvalidFileAttributes) && + (attributes & FILE_ATTRIBUTE_DIRECTORY)) { + result = true; + } +#else + posix::StatStruct file_stat; + result = posix::Stat(path.c_str(), &file_stat) == 0 && + posix::IsDir(file_stat); +#endif // GTEST_OS_WINDOWS_MOBILE + + return result; +} + +// Returns true if pathname describes a root directory. (Windows has one +// root directory per disk drive.) +bool FilePath::IsRootDirectory() const { +#if GTEST_OS_WINDOWS + // TODO(wan@google.com): on Windows a network share like + // \\server\share can be a root directory, although it cannot be the + // current directory. Handle this properly. + return pathname_.length() == 3 && IsAbsolutePath(); +#else + return pathname_.length() == 1 && IsPathSeparator(pathname_.c_str()[0]); +#endif +} + +// Returns true if pathname describes an absolute path. +bool FilePath::IsAbsolutePath() const { + const char* const name = pathname_.c_str(); +#if GTEST_OS_WINDOWS + return pathname_.length() >= 3 && + ((name[0] >= 'a' && name[0] <= 'z') || + (name[0] >= 'A' && name[0] <= 'Z')) && + name[1] == ':' && + IsPathSeparator(name[2]); +#else + return IsPathSeparator(name[0]); +#endif +} + +// Returns a pathname for a file that does not currently exist. The pathname +// will be directory/base_name.extension or +// directory/base_name_.extension if directory/base_name.extension +// already exists. The number will be incremented until a pathname is found +// that does not already exist. +// Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'. +// There could be a race condition if two or more processes are calling this +// function at the same time -- they could both pick the same filename. +FilePath FilePath::GenerateUniqueFileName(const FilePath& directory, + const FilePath& base_name, + const char* extension) { + FilePath full_pathname; + int number = 0; + do { + full_pathname.Set(MakeFileName(directory, base_name, number++, extension)); + } while (full_pathname.FileOrDirectoryExists()); + return full_pathname; +} + +// Returns true if FilePath ends with a path separator, which indicates that +// it is intended to represent a directory. Returns false otherwise. +// This does NOT check that a directory (or file) actually exists. +bool FilePath::IsDirectory() const { + return !pathname_.empty() && + IsPathSeparator(pathname_.c_str()[pathname_.length() - 1]); +} + +// Create directories so that path exists. Returns true if successful or if +// the directories already exist; returns false if unable to create directories +// for any reason. +bool FilePath::CreateDirectoriesRecursively() const { + if (!this->IsDirectory()) { + return false; + } + + if (pathname_.length() == 0 || this->DirectoryExists()) { + return true; + } + + const FilePath parent(this->RemoveTrailingPathSeparator().RemoveFileName()); + return parent.CreateDirectoriesRecursively() && this->CreateFolder(); +} + +// Create the directory so that path exists. Returns true if successful or +// if the directory already exists; returns false if unable to create the +// directory for any reason, including if the parent directory does not +// exist. Not named "CreateDirectory" because that's a macro on Windows. +bool FilePath::CreateFolder() const { +#if GTEST_OS_WINDOWS_MOBILE + FilePath removed_sep(this->RemoveTrailingPathSeparator()); + LPCWSTR unicode = String::AnsiToUtf16(removed_sep.c_str()); + int result = CreateDirectory(unicode, NULL) ? 0 : -1; + delete [] unicode; +#elif GTEST_OS_WINDOWS + int result = _mkdir(pathname_.c_str()); +#else + int result = mkdir(pathname_.c_str(), 0777); +#endif // GTEST_OS_WINDOWS_MOBILE + + if (result == -1) { + return this->DirectoryExists(); // An error is OK if the directory exists. + } + return true; // No error. +} + +// If input name has a trailing separator character, remove it and return the +// name, otherwise return the name string unmodified. +// On Windows platform, uses \ as the separator, other platforms use /. +FilePath FilePath::RemoveTrailingPathSeparator() const { + return IsDirectory() + ? FilePath(String(pathname_.c_str(), pathname_.length() - 1)) + : *this; +} + +// Removes any redundant separators that might be in the pathname. +// For example, "bar///foo" becomes "bar/foo". Does not eliminate other +// redundancies that might be in a pathname involving "." or "..". +// TODO(wan@google.com): handle Windows network shares (e.g. \\server\share). +void FilePath::Normalize() { + if (pathname_.c_str() == NULL) { + pathname_ = ""; + return; + } + const char* src = pathname_.c_str(); + char* const dest = new char[pathname_.length() + 1]; + char* dest_ptr = dest; + memset(dest_ptr, 0, pathname_.length() + 1); + + while (*src != '\0') { + *dest_ptr = *src; + if (!IsPathSeparator(*src)) { + src++; + } else { +#if GTEST_HAS_ALT_PATH_SEP_ + if (*dest_ptr == kAlternatePathSeparator) { + *dest_ptr = kPathSeparator; + } +#endif + while (IsPathSeparator(*src)) + src++; + } + dest_ptr++; + } + *dest_ptr = '\0'; + pathname_ = dest; + delete[] dest; +} + +} // namespace internal +} // namespace testing diff --git a/3rdparty/gmock/gtest/src/gtest-internal-inl.h b/3rdparty/gmock/gtest/src/gtest-internal-inl.h new file mode 100644 index 00000000..855b2155 --- /dev/null +++ b/3rdparty/gmock/gtest/src/gtest-internal-inl.h @@ -0,0 +1,1074 @@ +// Copyright 2005, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. + +// Utility functions and classes used by the Google C++ testing framework. +// +// Author: wan@google.com (Zhanyong Wan) +// +// This file contains purely Google Test's internal implementation. Please +// DO NOT #INCLUDE IT IN A USER PROGRAM. + +#ifndef GTEST_SRC_GTEST_INTERNAL_INL_H_ +#define GTEST_SRC_GTEST_INTERNAL_INL_H_ + +// GTEST_IMPLEMENTATION_ is defined to 1 iff the current translation unit is +// part of Google Test's implementation; otherwise it's undefined. +#if !GTEST_IMPLEMENTATION_ +// A user is trying to include this from his code - just say no. +#error "gtest-internal-inl.h is part of Google Test's internal implementation." +#error "It must not be included except by Google Test itself." +#endif // GTEST_IMPLEMENTATION_ + +#ifndef _WIN32_WCE +#include +#endif // !_WIN32_WCE +#include +#include // For strtoll/_strtoul64/malloc/free. +#include // For memmove. + +#include +#include +#include + +#include + +#if GTEST_OS_WINDOWS +#include // For DWORD. +#endif // GTEST_OS_WINDOWS + +#include // NOLINT +#include + +namespace testing { + +// Declares the flags. +// +// We don't want the users to modify this flag in the code, but want +// Google Test's own unit tests to be able to access it. Therefore we +// declare it here as opposed to in gtest.h. +GTEST_DECLARE_bool_(death_test_use_fork); + +namespace internal { + +// The value of GetTestTypeId() as seen from within the Google Test +// library. This is solely for testing GetTestTypeId(). +GTEST_API_ extern const TypeId kTestTypeIdInGoogleTest; + +// Names of the flags (needed for parsing Google Test flags). +const char kAlsoRunDisabledTestsFlag[] = "also_run_disabled_tests"; +const char kBreakOnFailureFlag[] = "break_on_failure"; +const char kCatchExceptionsFlag[] = "catch_exceptions"; +const char kColorFlag[] = "color"; +const char kFilterFlag[] = "filter"; +const char kListTestsFlag[] = "list_tests"; +const char kOutputFlag[] = "output"; +const char kPrintTimeFlag[] = "print_time"; +const char kRandomSeedFlag[] = "random_seed"; +const char kRepeatFlag[] = "repeat"; +const char kShuffleFlag[] = "shuffle"; +const char kStackTraceDepthFlag[] = "stack_trace_depth"; +const char kThrowOnFailureFlag[] = "throw_on_failure"; + +// A valid random seed must be in [1, kMaxRandomSeed]. +const int kMaxRandomSeed = 99999; + +// g_help_flag is true iff the --help flag or an equivalent form is +// specified on the command line. +GTEST_API_ extern bool g_help_flag; + +// Returns the current time in milliseconds. +GTEST_API_ TimeInMillis GetTimeInMillis(); + +// Returns true iff Google Test should use colors in the output. +GTEST_API_ bool ShouldUseColor(bool stdout_is_tty); + +// Formats the given time in milliseconds as seconds. +GTEST_API_ std::string FormatTimeInMillisAsSeconds(TimeInMillis ms); + +// Parses a string for an Int32 flag, in the form of "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +GTEST_API_ bool ParseInt32Flag( + const char* str, const char* flag, Int32* value); + +// Returns a random seed in range [1, kMaxRandomSeed] based on the +// given --gtest_random_seed flag value. +inline int GetRandomSeedFromFlag(Int32 random_seed_flag) { + const unsigned int raw_seed = (random_seed_flag == 0) ? + static_cast(GetTimeInMillis()) : + static_cast(random_seed_flag); + + // Normalizes the actual seed to range [1, kMaxRandomSeed] such that + // it's easy to type. + const int normalized_seed = + static_cast((raw_seed - 1U) % + static_cast(kMaxRandomSeed)) + 1; + return normalized_seed; +} + +// Returns the first valid random seed after 'seed'. The behavior is +// undefined if 'seed' is invalid. The seed after kMaxRandomSeed is +// considered to be 1. +inline int GetNextRandomSeed(int seed) { + GTEST_CHECK_(1 <= seed && seed <= kMaxRandomSeed) + << "Invalid random seed " << seed << " - must be in [1, " + << kMaxRandomSeed << "]."; + const int next_seed = seed + 1; + return (next_seed > kMaxRandomSeed) ? 1 : next_seed; +} + +// This class saves the values of all Google Test flags in its c'tor, and +// restores them in its d'tor. +class GTestFlagSaver { + public: + // The c'tor. + GTestFlagSaver() { + also_run_disabled_tests_ = GTEST_FLAG(also_run_disabled_tests); + break_on_failure_ = GTEST_FLAG(break_on_failure); + catch_exceptions_ = GTEST_FLAG(catch_exceptions); + color_ = GTEST_FLAG(color); + death_test_style_ = GTEST_FLAG(death_test_style); + death_test_use_fork_ = GTEST_FLAG(death_test_use_fork); + filter_ = GTEST_FLAG(filter); + internal_run_death_test_ = GTEST_FLAG(internal_run_death_test); + list_tests_ = GTEST_FLAG(list_tests); + output_ = GTEST_FLAG(output); + print_time_ = GTEST_FLAG(print_time); + random_seed_ = GTEST_FLAG(random_seed); + repeat_ = GTEST_FLAG(repeat); + shuffle_ = GTEST_FLAG(shuffle); + stack_trace_depth_ = GTEST_FLAG(stack_trace_depth); + throw_on_failure_ = GTEST_FLAG(throw_on_failure); + } + + // The d'tor is not virtual. DO NOT INHERIT FROM THIS CLASS. + ~GTestFlagSaver() { + GTEST_FLAG(also_run_disabled_tests) = also_run_disabled_tests_; + GTEST_FLAG(break_on_failure) = break_on_failure_; + GTEST_FLAG(catch_exceptions) = catch_exceptions_; + GTEST_FLAG(color) = color_; + GTEST_FLAG(death_test_style) = death_test_style_; + GTEST_FLAG(death_test_use_fork) = death_test_use_fork_; + GTEST_FLAG(filter) = filter_; + GTEST_FLAG(internal_run_death_test) = internal_run_death_test_; + GTEST_FLAG(list_tests) = list_tests_; + GTEST_FLAG(output) = output_; + GTEST_FLAG(print_time) = print_time_; + GTEST_FLAG(random_seed) = random_seed_; + GTEST_FLAG(repeat) = repeat_; + GTEST_FLAG(shuffle) = shuffle_; + GTEST_FLAG(stack_trace_depth) = stack_trace_depth_; + GTEST_FLAG(throw_on_failure) = throw_on_failure_; + } + private: + // Fields for saving the original values of flags. + bool also_run_disabled_tests_; + bool break_on_failure_; + bool catch_exceptions_; + String color_; + String death_test_style_; + bool death_test_use_fork_; + String filter_; + String internal_run_death_test_; + bool list_tests_; + String output_; + bool print_time_; + bool pretty_; + internal::Int32 random_seed_; + internal::Int32 repeat_; + bool shuffle_; + internal::Int32 stack_trace_depth_; + bool throw_on_failure_; +} GTEST_ATTRIBUTE_UNUSED_; + +// Converts a Unicode code point to a narrow string in UTF-8 encoding. +// code_point parameter is of type UInt32 because wchar_t may not be +// wide enough to contain a code point. +// The output buffer str must containt at least 32 characters. +// The function returns the address of the output buffer. +// If the code_point is not a valid Unicode code point +// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be output +// as '(Invalid Unicode 0xXXXXXXXX)'. +GTEST_API_ char* CodePointToUtf8(UInt32 code_point, char* str); + +// Converts a wide string to a narrow string in UTF-8 encoding. +// The wide string is assumed to have the following encoding: +// UTF-16 if sizeof(wchar_t) == 2 (on Windows, Cygwin, Symbian OS) +// UTF-32 if sizeof(wchar_t) == 4 (on Linux) +// Parameter str points to a null-terminated wide string. +// Parameter num_chars may additionally limit the number +// of wchar_t characters processed. -1 is used when the entire string +// should be processed. +// If the string contains code points that are not valid Unicode code points +// (i.e. outside of Unicode range U+0 to U+10FFFF) they will be output +// as '(Invalid Unicode 0xXXXXXXXX)'. If the string is in UTF16 encoding +// and contains invalid UTF-16 surrogate pairs, values in those pairs +// will be encoded as individual Unicode characters from Basic Normal Plane. +GTEST_API_ String WideStringToUtf8(const wchar_t* str, int num_chars); + +// Reads the GTEST_SHARD_STATUS_FILE environment variable, and creates the file +// if the variable is present. If a file already exists at this location, this +// function will write over it. If the variable is present, but the file cannot +// be created, prints an error and exits. +void WriteToShardStatusFileIfNeeded(); + +// Checks whether sharding is enabled by examining the relevant +// environment variable values. If the variables are present, +// but inconsistent (e.g., shard_index >= total_shards), prints +// an error and exits. If in_subprocess_for_death_test, sharding is +// disabled because it must only be applied to the original test +// process. Otherwise, we could filter out death tests we intended to execute. +GTEST_API_ bool ShouldShard(const char* total_shards_str, + const char* shard_index_str, + bool in_subprocess_for_death_test); + +// Parses the environment variable var as an Int32. If it is unset, +// returns default_val. If it is not an Int32, prints an error and +// and aborts. +GTEST_API_ Int32 Int32FromEnvOrDie(const char* env_var, Int32 default_val); + +// Given the total number of shards, the shard index, and the test id, +// returns true iff the test should be run on this shard. The test id is +// some arbitrary but unique non-negative integer assigned to each test +// method. Assumes that 0 <= shard_index < total_shards. +GTEST_API_ bool ShouldRunTestOnShard( + int total_shards, int shard_index, int test_id); + +// STL container utilities. + +// Returns the number of elements in the given container that satisfy +// the given predicate. +template +inline int CountIf(const Container& c, Predicate predicate) { + return static_cast(std::count_if(c.begin(), c.end(), predicate)); +} + +// Applies a function/functor to each element in the container. +template +void ForEach(const Container& c, Functor functor) { + std::for_each(c.begin(), c.end(), functor); +} + +// Returns the i-th element of the vector, or default_value if i is not +// in range [0, v.size()). +template +inline E GetElementOr(const std::vector& v, int i, E default_value) { + return (i < 0 || i >= static_cast(v.size())) ? default_value : v[i]; +} + +// Performs an in-place shuffle of a range of the vector's elements. +// 'begin' and 'end' are element indices as an STL-style range; +// i.e. [begin, end) are shuffled, where 'end' == size() means to +// shuffle to the end of the vector. +template +void ShuffleRange(internal::Random* random, int begin, int end, + std::vector* v) { + const int size = static_cast(v->size()); + GTEST_CHECK_(0 <= begin && begin <= size) + << "Invalid shuffle range start " << begin << ": must be in range [0, " + << size << "]."; + GTEST_CHECK_(begin <= end && end <= size) + << "Invalid shuffle range finish " << end << ": must be in range [" + << begin << ", " << size << "]."; + + // Fisher-Yates shuffle, from + // http://en.wikipedia.org/wiki/Fisher-Yates_shuffle + for (int range_width = end - begin; range_width >= 2; range_width--) { + const int last_in_range = begin + range_width - 1; + const int selected = begin + random->Generate(range_width); + std::swap((*v)[selected], (*v)[last_in_range]); + } +} + +// Performs an in-place shuffle of the vector's elements. +template +inline void Shuffle(internal::Random* random, std::vector* v) { + ShuffleRange(random, 0, static_cast(v->size()), v); +} + +// A function for deleting an object. Handy for being used as a +// functor. +template +static void Delete(T* x) { + delete x; +} + +// A predicate that checks the key of a TestProperty against a known key. +// +// TestPropertyKeyIs is copyable. +class TestPropertyKeyIs { + public: + // Constructor. + // + // TestPropertyKeyIs has NO default constructor. + explicit TestPropertyKeyIs(const char* key) + : key_(key) {} + + // Returns true iff the test name of test property matches on key_. + bool operator()(const TestProperty& test_property) const { + return String(test_property.key()).Compare(key_) == 0; + } + + private: + String key_; +}; + +class TestInfoImpl { + public: + TestInfoImpl(TestInfo* parent, const char* test_case_name, + const char* name, const char* test_case_comment, + const char* comment, TypeId fixture_class_id, + internal::TestFactoryBase* factory); + ~TestInfoImpl(); + + // Returns true if this test should run. + bool should_run() const { return should_run_; } + + // Sets the should_run member. + void set_should_run(bool should) { should_run_ = should; } + + // Returns true if this test is disabled. Disabled tests are not run. + bool is_disabled() const { return is_disabled_; } + + // Sets the is_disabled member. + void set_is_disabled(bool is) { is_disabled_ = is; } + + // Returns true if this test matches the filter specified by the user. + bool matches_filter() const { return matches_filter_; } + + // Sets the matches_filter member. + void set_matches_filter(bool matches) { matches_filter_ = matches; } + + // Returns the test case name. + const char* test_case_name() const { return test_case_name_.c_str(); } + + // Returns the test name. + const char* name() const { return name_.c_str(); } + + // Returns the test case comment. + const char* test_case_comment() const { return test_case_comment_.c_str(); } + + // Returns the test comment. + const char* comment() const { return comment_.c_str(); } + + // Returns the ID of the test fixture class. + TypeId fixture_class_id() const { return fixture_class_id_; } + + // Returns the test result. + TestResult* result() { return &result_; } + const TestResult* result() const { return &result_; } + + // Creates the test object, runs it, records its result, and then + // deletes it. + void Run(); + + // Clears the test result. + void ClearResult() { result_.Clear(); } + + // Clears the test result in the given TestInfo object. + static void ClearTestResult(TestInfo * test_info) { + test_info->impl()->ClearResult(); + } + + private: + // These fields are immutable properties of the test. + TestInfo* const parent_; // The owner of this object + const String test_case_name_; // Test case name + const String name_; // Test name + const String test_case_comment_; // Test case comment + const String comment_; // Test comment + const TypeId fixture_class_id_; // ID of the test fixture class + bool should_run_; // True iff this test should run + bool is_disabled_; // True iff this test is disabled + bool matches_filter_; // True if this test matches the + // user-specified filter. + internal::TestFactoryBase* const factory_; // The factory that creates + // the test object + + // This field is mutable and needs to be reset before running the + // test for the second time. + TestResult result_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestInfoImpl); +}; + +// Class UnitTestOptions. +// +// This class contains functions for processing options the user +// specifies when running the tests. It has only static members. +// +// In most cases, the user can specify an option using either an +// environment variable or a command line flag. E.g. you can set the +// test filter using either GTEST_FILTER or --gtest_filter. If both +// the variable and the flag are present, the latter overrides the +// former. +class GTEST_API_ UnitTestOptions { + public: + // Functions for processing the gtest_output flag. + + // Returns the output format, or "" for normal printed output. + static String GetOutputFormat(); + + // Returns the absolute path of the requested output file, or the + // default (test_detail.xml in the original working directory) if + // none was explicitly specified. + static String GetAbsolutePathToOutputFile(); + + // Functions for processing the gtest_filter flag. + + // Returns true iff the wildcard pattern matches the string. The + // first ':' or '\0' character in pattern marks the end of it. + // + // This recursive algorithm isn't very efficient, but is clear and + // works well enough for matching test names, which are short. + static bool PatternMatchesString(const char *pattern, const char *str); + + // Returns true iff the user-specified filter matches the test case + // name and the test name. + static bool FilterMatchesTest(const String &test_case_name, + const String &test_name); + +#if GTEST_OS_WINDOWS + // Function for supporting the gtest_catch_exception flag. + + // Returns EXCEPTION_EXECUTE_HANDLER if Google Test should handle the + // given SEH exception, or EXCEPTION_CONTINUE_SEARCH otherwise. + // This function is useful as an __except condition. + static int GTestShouldProcessSEH(DWORD exception_code); +#endif // GTEST_OS_WINDOWS + + // Returns true if "name" matches the ':' separated list of glob-style + // filters in "filter". + static bool MatchesFilter(const String& name, const char* filter); +}; + +// Returns the current application's name, removing directory path if that +// is present. Used by UnitTestOptions::GetOutputFile. +GTEST_API_ FilePath GetCurrentExecutableName(); + +// The role interface for getting the OS stack trace as a string. +class OsStackTraceGetterInterface { + public: + OsStackTraceGetterInterface() {} + virtual ~OsStackTraceGetterInterface() {} + + // Returns the current OS stack trace as a String. Parameters: + // + // max_depth - the maximum number of stack frames to be included + // in the trace. + // skip_count - the number of top frames to be skipped; doesn't count + // against max_depth. + virtual String CurrentStackTrace(int max_depth, int skip_count) = 0; + + // UponLeavingGTest() should be called immediately before Google Test calls + // user code. It saves some information about the current stack that + // CurrentStackTrace() will use to find and hide Google Test stack frames. + virtual void UponLeavingGTest() = 0; + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(OsStackTraceGetterInterface); +}; + +// A working implementation of the OsStackTraceGetterInterface interface. +class OsStackTraceGetter : public OsStackTraceGetterInterface { + public: + OsStackTraceGetter() : caller_frame_(NULL) {} + virtual String CurrentStackTrace(int max_depth, int skip_count); + virtual void UponLeavingGTest(); + + // This string is inserted in place of stack frames that are part of + // Google Test's implementation. + static const char* const kElidedFramesMarker; + + private: + Mutex mutex_; // protects all internal state + + // We save the stack frame below the frame that calls user code. + // We do this because the address of the frame immediately below + // the user code changes between the call to UponLeavingGTest() + // and any calls to CurrentStackTrace() from within the user code. + void* caller_frame_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(OsStackTraceGetter); +}; + +// Information about a Google Test trace point. +struct TraceInfo { + const char* file; + int line; + String message; +}; + +// This is the default global test part result reporter used in UnitTestImpl. +// This class should only be used by UnitTestImpl. +class DefaultGlobalTestPartResultReporter + : public TestPartResultReporterInterface { + public: + explicit DefaultGlobalTestPartResultReporter(UnitTestImpl* unit_test); + // Implements the TestPartResultReporterInterface. Reports the test part + // result in the current test. + virtual void ReportTestPartResult(const TestPartResult& result); + + private: + UnitTestImpl* const unit_test_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultGlobalTestPartResultReporter); +}; + +// This is the default per thread test part result reporter used in +// UnitTestImpl. This class should only be used by UnitTestImpl. +class DefaultPerThreadTestPartResultReporter + : public TestPartResultReporterInterface { + public: + explicit DefaultPerThreadTestPartResultReporter(UnitTestImpl* unit_test); + // Implements the TestPartResultReporterInterface. The implementation just + // delegates to the current global test part result reporter of *unit_test_. + virtual void ReportTestPartResult(const TestPartResult& result); + + private: + UnitTestImpl* const unit_test_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultPerThreadTestPartResultReporter); +}; + +// The private implementation of the UnitTest class. We don't protect +// the methods under a mutex, as this class is not accessible by a +// user and the UnitTest class that delegates work to this class does +// proper locking. +class GTEST_API_ UnitTestImpl { + public: + explicit UnitTestImpl(UnitTest* parent); + virtual ~UnitTestImpl(); + + // There are two different ways to register your own TestPartResultReporter. + // You can register your own repoter to listen either only for test results + // from the current thread or for results from all threads. + // By default, each per-thread test result repoter just passes a new + // TestPartResult to the global test result reporter, which registers the + // test part result for the currently running test. + + // Returns the global test part result reporter. + TestPartResultReporterInterface* GetGlobalTestPartResultReporter(); + + // Sets the global test part result reporter. + void SetGlobalTestPartResultReporter( + TestPartResultReporterInterface* reporter); + + // Returns the test part result reporter for the current thread. + TestPartResultReporterInterface* GetTestPartResultReporterForCurrentThread(); + + // Sets the test part result reporter for the current thread. + void SetTestPartResultReporterForCurrentThread( + TestPartResultReporterInterface* reporter); + + // Gets the number of successful test cases. + int successful_test_case_count() const; + + // Gets the number of failed test cases. + int failed_test_case_count() const; + + // Gets the number of all test cases. + int total_test_case_count() const; + + // Gets the number of all test cases that contain at least one test + // that should run. + int test_case_to_run_count() const; + + // Gets the number of successful tests. + int successful_test_count() const; + + // Gets the number of failed tests. + int failed_test_count() const; + + // Gets the number of disabled tests. + int disabled_test_count() const; + + // Gets the number of all tests. + int total_test_count() const; + + // Gets the number of tests that should run. + int test_to_run_count() const; + + // Gets the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const { return elapsed_time_; } + + // Returns true iff the unit test passed (i.e. all test cases passed). + bool Passed() const { return !Failed(); } + + // Returns true iff the unit test failed (i.e. some test case failed + // or something outside of all tests failed). + bool Failed() const { + return failed_test_case_count() > 0 || ad_hoc_test_result()->Failed(); + } + + // Gets the i-th test case among all the test cases. i can range from 0 to + // total_test_case_count() - 1. If i is not in that range, returns NULL. + const TestCase* GetTestCase(int i) const { + const int index = GetElementOr(test_case_indices_, i, -1); + return index < 0 ? NULL : test_cases_[i]; + } + + // Gets the i-th test case among all the test cases. i can range from 0 to + // total_test_case_count() - 1. If i is not in that range, returns NULL. + TestCase* GetMutableTestCase(int i) { + const int index = GetElementOr(test_case_indices_, i, -1); + return index < 0 ? NULL : test_cases_[index]; + } + + // Provides access to the event listener list. + TestEventListeners* listeners() { return &listeners_; } + + // Returns the TestResult for the test that's currently running, or + // the TestResult for the ad hoc test if no test is running. + TestResult* current_test_result(); + + // Returns the TestResult for the ad hoc test. + const TestResult* ad_hoc_test_result() const { return &ad_hoc_test_result_; } + + // Sets the OS stack trace getter. + // + // Does nothing if the input and the current OS stack trace getter + // are the same; otherwise, deletes the old getter and makes the + // input the current getter. + void set_os_stack_trace_getter(OsStackTraceGetterInterface* getter); + + // Returns the current OS stack trace getter if it is not NULL; + // otherwise, creates an OsStackTraceGetter, makes it the current + // getter, and returns it. + OsStackTraceGetterInterface* os_stack_trace_getter(); + + // Returns the current OS stack trace as a String. + // + // The maximum number of stack frames to be included is specified by + // the gtest_stack_trace_depth flag. The skip_count parameter + // specifies the number of top frames to be skipped, which doesn't + // count against the number of frames to be included. + // + // For example, if Foo() calls Bar(), which in turn calls + // CurrentOsStackTraceExceptTop(1), Foo() will be included in the + // trace but Bar() and CurrentOsStackTraceExceptTop() won't. + String CurrentOsStackTraceExceptTop(int skip_count); + + // Finds and returns a TestCase with the given name. If one doesn't + // exist, creates one and returns it. + // + // Arguments: + // + // test_case_name: name of the test case + // set_up_tc: pointer to the function that sets up the test case + // tear_down_tc: pointer to the function that tears down the test case + TestCase* GetTestCase(const char* test_case_name, + const char* comment, + Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc); + + // Adds a TestInfo to the unit test. + // + // Arguments: + // + // set_up_tc: pointer to the function that sets up the test case + // tear_down_tc: pointer to the function that tears down the test case + // test_info: the TestInfo object + void AddTestInfo(Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc, + TestInfo * test_info) { + // In order to support thread-safe death tests, we need to + // remember the original working directory when the test program + // was first invoked. We cannot do this in RUN_ALL_TESTS(), as + // the user may have changed the current directory before calling + // RUN_ALL_TESTS(). Therefore we capture the current directory in + // AddTestInfo(), which is called to register a TEST or TEST_F + // before main() is reached. + if (original_working_dir_.IsEmpty()) { + original_working_dir_.Set(FilePath::GetCurrentDir()); + GTEST_CHECK_(!original_working_dir_.IsEmpty()) + << "Failed to get the current working directory."; + } + + GetTestCase(test_info->test_case_name(), + test_info->test_case_comment(), + set_up_tc, + tear_down_tc)->AddTestInfo(test_info); + } + +#if GTEST_HAS_PARAM_TEST + // Returns ParameterizedTestCaseRegistry object used to keep track of + // value-parameterized tests and instantiate and register them. + internal::ParameterizedTestCaseRegistry& parameterized_test_registry() { + return parameterized_test_registry_; + } +#endif // GTEST_HAS_PARAM_TEST + + // Sets the TestCase object for the test that's currently running. + void set_current_test_case(TestCase* a_current_test_case) { + current_test_case_ = a_current_test_case; + } + + // Sets the TestInfo object for the test that's currently running. If + // current_test_info is NULL, the assertion results will be stored in + // ad_hoc_test_result_. + void set_current_test_info(TestInfo* a_current_test_info) { + current_test_info_ = a_current_test_info; + } + + // Registers all parameterized tests defined using TEST_P and + // INSTANTIATE_TEST_P, creating regular tests for each test/parameter + // combination. This method can be called more then once; it has + // guards protecting from registering the tests more then once. + // If value-parameterized tests are disabled, RegisterParameterizedTests + // is present but does nothing. + void RegisterParameterizedTests(); + + // Runs all tests in this UnitTest object, prints the result, and + // returns 0 if all tests are successful, or 1 otherwise. If any + // exception is thrown during a test on Windows, this test is + // considered to be failed, but the rest of the tests will still be + // run. (We disable exceptions on Linux and Mac OS X, so the issue + // doesn't apply there.) + int RunAllTests(); + + // Clears the results of all tests, including the ad hoc test. + void ClearResult() { + ForEach(test_cases_, TestCase::ClearTestCaseResult); + ad_hoc_test_result_.Clear(); + } + + enum ReactionToSharding { + HONOR_SHARDING_PROTOCOL, + IGNORE_SHARDING_PROTOCOL + }; + + // Matches the full name of each test against the user-specified + // filter to decide whether the test should run, then records the + // result in each TestCase and TestInfo object. + // If shard_tests == HONOR_SHARDING_PROTOCOL, further filters tests + // based on sharding variables in the environment. + // Returns the number of tests that should run. + int FilterTests(ReactionToSharding shard_tests); + + // Prints the names of the tests matching the user-specified filter flag. + void ListTestsMatchingFilter(); + + const TestCase* current_test_case() const { return current_test_case_; } + TestInfo* current_test_info() { return current_test_info_; } + const TestInfo* current_test_info() const { return current_test_info_; } + + // Returns the vector of environments that need to be set-up/torn-down + // before/after the tests are run. + std::vector& environments() { return environments_; } + + // Getters for the per-thread Google Test trace stack. + std::vector& gtest_trace_stack() { + return *(gtest_trace_stack_.pointer()); + } + const std::vector& gtest_trace_stack() const { + return gtest_trace_stack_.get(); + } + +#if GTEST_HAS_DEATH_TEST + void InitDeathTestSubprocessControlInfo() { + internal_run_death_test_flag_.reset(ParseInternalRunDeathTestFlag()); + } + // Returns a pointer to the parsed --gtest_internal_run_death_test + // flag, or NULL if that flag was not specified. + // This information is useful only in a death test child process. + // Must not be called before a call to InitGoogleTest. + const InternalRunDeathTestFlag* internal_run_death_test_flag() const { + return internal_run_death_test_flag_.get(); + } + + // Returns a pointer to the current death test factory. + internal::DeathTestFactory* death_test_factory() { + return death_test_factory_.get(); + } + + void SuppressTestEventsIfInSubprocess(); + + friend class ReplaceDeathTestFactory; +#endif // GTEST_HAS_DEATH_TEST + + // Initializes the event listener performing XML output as specified by + // UnitTestOptions. Must not be called before InitGoogleTest. + void ConfigureXmlOutput(); + + // Performs initialization dependent upon flag values obtained in + // ParseGoogleTestFlagsOnly. Is called from InitGoogleTest after the call to + // ParseGoogleTestFlagsOnly. In case a user neglects to call InitGoogleTest + // this function is also called from RunAllTests. Since this function can be + // called more than once, it has to be idempotent. + void PostFlagParsingInit(); + + // Gets the random seed used at the start of the current test iteration. + int random_seed() const { return random_seed_; } + + // Gets the random number generator. + internal::Random* random() { return &random_; } + + // Shuffles all test cases, and the tests within each test case, + // making sure that death tests are still run first. + void ShuffleTests(); + + // Restores the test cases and tests to their order before the first shuffle. + void UnshuffleTests(); + + private: + friend class ::testing::UnitTest; + + // The UnitTest object that owns this implementation object. + UnitTest* const parent_; + + // The working directory when the first TEST() or TEST_F() was + // executed. + internal::FilePath original_working_dir_; + + // The default test part result reporters. + DefaultGlobalTestPartResultReporter default_global_test_part_result_reporter_; + DefaultPerThreadTestPartResultReporter + default_per_thread_test_part_result_reporter_; + + // Points to (but doesn't own) the global test part result reporter. + TestPartResultReporterInterface* global_test_part_result_repoter_; + + // Protects read and write access to global_test_part_result_reporter_. + internal::Mutex global_test_part_result_reporter_mutex_; + + // Points to (but doesn't own) the per-thread test part result reporter. + internal::ThreadLocal + per_thread_test_part_result_reporter_; + + // The vector of environments that need to be set-up/torn-down + // before/after the tests are run. + std::vector environments_; + + // The vector of TestCases in their original order. It owns the + // elements in the vector. + std::vector test_cases_; + + // Provides a level of indirection for the test case list to allow + // easy shuffling and restoring the test case order. The i-th + // element of this vector is the index of the i-th test case in the + // shuffled order. + std::vector test_case_indices_; + +#if GTEST_HAS_PARAM_TEST + // ParameterizedTestRegistry object used to register value-parameterized + // tests. + internal::ParameterizedTestCaseRegistry parameterized_test_registry_; + + // Indicates whether RegisterParameterizedTests() has been called already. + bool parameterized_tests_registered_; +#endif // GTEST_HAS_PARAM_TEST + + // Index of the last death test case registered. Initially -1. + int last_death_test_case_; + + // This points to the TestCase for the currently running test. It + // changes as Google Test goes through one test case after another. + // When no test is running, this is set to NULL and Google Test + // stores assertion results in ad_hoc_test_result_. Initially NULL. + TestCase* current_test_case_; + + // This points to the TestInfo for the currently running test. It + // changes as Google Test goes through one test after another. When + // no test is running, this is set to NULL and Google Test stores + // assertion results in ad_hoc_test_result_. Initially NULL. + TestInfo* current_test_info_; + + // Normally, a user only writes assertions inside a TEST or TEST_F, + // or inside a function called by a TEST or TEST_F. Since Google + // Test keeps track of which test is current running, it can + // associate such an assertion with the test it belongs to. + // + // If an assertion is encountered when no TEST or TEST_F is running, + // Google Test attributes the assertion result to an imaginary "ad hoc" + // test, and records the result in ad_hoc_test_result_. + TestResult ad_hoc_test_result_; + + // The list of event listeners that can be used to track events inside + // Google Test. + TestEventListeners listeners_; + + // The OS stack trace getter. Will be deleted when the UnitTest + // object is destructed. By default, an OsStackTraceGetter is used, + // but the user can set this field to use a custom getter if that is + // desired. + OsStackTraceGetterInterface* os_stack_trace_getter_; + + // True iff PostFlagParsingInit() has been called. + bool post_flag_parse_init_performed_; + + // The random number seed used at the beginning of the test run. + int random_seed_; + + // Our random number generator. + internal::Random random_; + + // How long the test took to run, in milliseconds. + TimeInMillis elapsed_time_; + +#if GTEST_HAS_DEATH_TEST + // The decomposed components of the gtest_internal_run_death_test flag, + // parsed when RUN_ALL_TESTS is called. + internal::scoped_ptr internal_run_death_test_flag_; + internal::scoped_ptr death_test_factory_; +#endif // GTEST_HAS_DEATH_TEST + + // A per-thread stack of traces created by the SCOPED_TRACE() macro. + internal::ThreadLocal > gtest_trace_stack_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(UnitTestImpl); +}; // class UnitTestImpl + +// Convenience function for accessing the global UnitTest +// implementation object. +inline UnitTestImpl* GetUnitTestImpl() { + return UnitTest::GetInstance()->impl(); +} + +// Internal helper functions for implementing the simple regular +// expression matcher. +GTEST_API_ bool IsInSet(char ch, const char* str); +GTEST_API_ bool IsDigit(char ch); +GTEST_API_ bool IsPunct(char ch); +GTEST_API_ bool IsRepeat(char ch); +GTEST_API_ bool IsWhiteSpace(char ch); +GTEST_API_ bool IsWordChar(char ch); +GTEST_API_ bool IsValidEscape(char ch); +GTEST_API_ bool AtomMatchesChar(bool escaped, char pattern, char ch); +GTEST_API_ bool ValidateRegex(const char* regex); +GTEST_API_ bool MatchRegexAtHead(const char* regex, const char* str); +GTEST_API_ bool MatchRepetitionAndRegexAtHead( + bool escaped, char ch, char repeat, const char* regex, const char* str); +GTEST_API_ bool MatchRegexAnywhere(const char* regex, const char* str); + +// Parses the command line for Google Test flags, without initializing +// other parts of Google Test. +GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, char** argv); +GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv); + +#if GTEST_HAS_DEATH_TEST + +// Returns the message describing the last system error, regardless of the +// platform. +String GetLastErrnoDescription(); + +#if GTEST_OS_WINDOWS +// Provides leak-safe Windows kernel handle ownership. +class AutoHandle { + public: + AutoHandle() : handle_(INVALID_HANDLE_VALUE) {} + explicit AutoHandle(HANDLE handle) : handle_(handle) {} + + ~AutoHandle() { Reset(); } + + HANDLE Get() const { return handle_; } + void Reset() { Reset(INVALID_HANDLE_VALUE); } + void Reset(HANDLE handle) { + if (handle != handle_) { + if (handle_ != INVALID_HANDLE_VALUE) + ::CloseHandle(handle_); + handle_ = handle; + } + } + + private: + HANDLE handle_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(AutoHandle); +}; +#endif // GTEST_OS_WINDOWS + +// Attempts to parse a string into a positive integer pointed to by the +// number parameter. Returns true if that is possible. +// GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we can use +// it here. +template +bool ParseNaturalNumber(const ::std::string& str, Integer* number) { + // Fail fast if the given string does not begin with a digit; + // this bypasses strtoXXX's "optional leading whitespace and plus + // or minus sign" semantics, which are undesirable here. + if (str.empty() || !isdigit(str[0])) { + return false; + } + errno = 0; + + char* end; + // BiggestConvertible is the largest integer type that system-provided + // string-to-number conversion routines can return. +#if GTEST_OS_WINDOWS && !defined(__GNUC__) + // MSVC and C++ Builder define __int64 instead of the standard long long. + typedef unsigned __int64 BiggestConvertible; + const BiggestConvertible parsed = _strtoui64(str.c_str(), &end, 10); +#else + typedef unsigned long long BiggestConvertible; // NOLINT + const BiggestConvertible parsed = strtoull(str.c_str(), &end, 10); +#endif // GTEST_OS_WINDOWS && !defined(__GNUC__) + const bool parse_success = *end == '\0' && errno == 0; + + // TODO(vladl@google.com): Convert this to compile time assertion when it is + // available. + GTEST_CHECK_(sizeof(Integer) <= sizeof(parsed)); + + const Integer result = static_cast(parsed); + if (parse_success && static_cast(result) == parsed) { + *number = result; + return true; + } + return false; +} +#endif // GTEST_HAS_DEATH_TEST + +// TestResult contains some private methods that should be hidden from +// Google Test user but are required for testing. This class allow our tests +// to access them. +// +// This class is supplied only for the purpose of testing Google Test's own +// constructs. Do not use it in user tests, either directly or indirectly. +class TestResultAccessor { + public: + static void RecordProperty(TestResult* test_result, + const TestProperty& property) { + test_result->RecordProperty(property); + } + + static void ClearTestPartResults(TestResult* test_result) { + test_result->ClearTestPartResults(); + } + + static const std::vector& test_part_results( + const TestResult& test_result) { + return test_result.test_part_results(); + } +}; + +} // namespace internal +} // namespace testing + +#endif // GTEST_SRC_GTEST_INTERNAL_INL_H_ diff --git a/3rdparty/gmock/gtest/src/gtest-port.cc b/3rdparty/gmock/gtest/src/gtest-port.cc new file mode 100644 index 00000000..b9504f56 --- /dev/null +++ b/3rdparty/gmock/gtest/src/gtest-port.cc @@ -0,0 +1,711 @@ +// Copyright 2008, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +#include + +#include +#include +#include + +#if GTEST_OS_WINDOWS_MOBILE +#include // For TerminateProcess() +#elif GTEST_OS_WINDOWS +#include +#include +#else +#include +#endif // GTEST_OS_WINDOWS_MOBILE + +#if GTEST_OS_MAC +#include +#include +#include +#endif // GTEST_OS_MAC + +#include +#include +#include + +// Indicates that this translation unit is part of Google Test's +// implementation. It must come before gtest-internal-inl.h is +// included, or there will be a compiler error. This trick is to +// prevent a user from accidentally including gtest-internal-inl.h in +// his code. +#define GTEST_IMPLEMENTATION_ 1 +#include "src/gtest-internal-inl.h" +#undef GTEST_IMPLEMENTATION_ + +namespace testing { +namespace internal { + +#if defined(_MSC_VER) || defined(__BORLANDC__) +// MSVC and C++Builder do not provide a definition of STDERR_FILENO. +const int kStdOutFileno = 1; +const int kStdErrFileno = 2; +#else +const int kStdOutFileno = STDOUT_FILENO; +const int kStdErrFileno = STDERR_FILENO; +#endif // _MSC_VER + +#if GTEST_OS_MAC + +// Returns the number of threads running in the process, or 0 to indicate that +// we cannot detect it. +size_t GetThreadCount() { + const task_t task = mach_task_self(); + mach_msg_type_number_t thread_count; + thread_act_array_t thread_list; + const kern_return_t status = task_threads(task, &thread_list, &thread_count); + if (status == KERN_SUCCESS) { + // task_threads allocates resources in thread_list and we need to free them + // to avoid leaks. + vm_deallocate(task, + reinterpret_cast(thread_list), + sizeof(thread_t) * thread_count); + return static_cast(thread_count); + } else { + return 0; + } +} + +#else + +size_t GetThreadCount() { + // There's no portable way to detect the number of threads, so we just + // return 0 to indicate that we cannot detect it. + return 0; +} + +#endif // GTEST_OS_MAC + +#if GTEST_USES_POSIX_RE + +// Implements RE. Currently only needed for death tests. + +RE::~RE() { + if (is_valid_) { + // regfree'ing an invalid regex might crash because the content + // of the regex is undefined. Since the regex's are essentially + // the same, one cannot be valid (or invalid) without the other + // being so too. + regfree(&partial_regex_); + regfree(&full_regex_); + } + free(const_cast(pattern_)); +} + +// Returns true iff regular expression re matches the entire str. +bool RE::FullMatch(const char* str, const RE& re) { + if (!re.is_valid_) return false; + + regmatch_t match; + return regexec(&re.full_regex_, str, 1, &match, 0) == 0; +} + +// Returns true iff regular expression re matches a substring of str +// (including str itself). +bool RE::PartialMatch(const char* str, const RE& re) { + if (!re.is_valid_) return false; + + regmatch_t match; + return regexec(&re.partial_regex_, str, 1, &match, 0) == 0; +} + +// Initializes an RE from its string representation. +void RE::Init(const char* regex) { + pattern_ = posix::StrDup(regex); + + // Reserves enough bytes to hold the regular expression used for a + // full match. + const size_t full_regex_len = strlen(regex) + 10; + char* const full_pattern = new char[full_regex_len]; + + snprintf(full_pattern, full_regex_len, "^(%s)$", regex); + is_valid_ = regcomp(&full_regex_, full_pattern, REG_EXTENDED) == 0; + // We want to call regcomp(&partial_regex_, ...) even if the + // previous expression returns false. Otherwise partial_regex_ may + // not be properly initialized can may cause trouble when it's + // freed. + // + // Some implementation of POSIX regex (e.g. on at least some + // versions of Cygwin) doesn't accept the empty string as a valid + // regex. We change it to an equivalent form "()" to be safe. + if (is_valid_) { + const char* const partial_regex = (*regex == '\0') ? "()" : regex; + is_valid_ = regcomp(&partial_regex_, partial_regex, REG_EXTENDED) == 0; + } + EXPECT_TRUE(is_valid_) + << "Regular expression \"" << regex + << "\" is not a valid POSIX Extended regular expression."; + + delete[] full_pattern; +} + +#elif GTEST_USES_SIMPLE_RE + +// Returns true iff ch appears anywhere in str (excluding the +// terminating '\0' character). +bool IsInSet(char ch, const char* str) { + return ch != '\0' && strchr(str, ch) != NULL; +} + +// Returns true iff ch belongs to the given classification. Unlike +// similar functions in , these aren't affected by the +// current locale. +bool IsDigit(char ch) { return '0' <= ch && ch <= '9'; } +bool IsPunct(char ch) { + return IsInSet(ch, "^-!\"#$%&'()*+,./:;<=>?@[\\]_`{|}~"); +} +bool IsRepeat(char ch) { return IsInSet(ch, "?*+"); } +bool IsWhiteSpace(char ch) { return IsInSet(ch, " \f\n\r\t\v"); } +bool IsWordChar(char ch) { + return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || + ('0' <= ch && ch <= '9') || ch == '_'; +} + +// Returns true iff "\\c" is a supported escape sequence. +bool IsValidEscape(char c) { + return (IsPunct(c) || IsInSet(c, "dDfnrsStvwW")); +} + +// Returns true iff the given atom (specified by escaped and pattern) +// matches ch. The result is undefined if the atom is invalid. +bool AtomMatchesChar(bool escaped, char pattern_char, char ch) { + if (escaped) { // "\\p" where p is pattern_char. + switch (pattern_char) { + case 'd': return IsDigit(ch); + case 'D': return !IsDigit(ch); + case 'f': return ch == '\f'; + case 'n': return ch == '\n'; + case 'r': return ch == '\r'; + case 's': return IsWhiteSpace(ch); + case 'S': return !IsWhiteSpace(ch); + case 't': return ch == '\t'; + case 'v': return ch == '\v'; + case 'w': return IsWordChar(ch); + case 'W': return !IsWordChar(ch); + } + return IsPunct(pattern_char) && pattern_char == ch; + } + + return (pattern_char == '.' && ch != '\n') || pattern_char == ch; +} + +// Helper function used by ValidateRegex() to format error messages. +String FormatRegexSyntaxError(const char* regex, int index) { + return (Message() << "Syntax error at index " << index + << " in simple regular expression \"" << regex << "\": ").GetString(); +} + +// Generates non-fatal failures and returns false if regex is invalid; +// otherwise returns true. +bool ValidateRegex(const char* regex) { + if (regex == NULL) { + // TODO(wan@google.com): fix the source file location in the + // assertion failures to match where the regex is used in user + // code. + ADD_FAILURE() << "NULL is not a valid simple regular expression."; + return false; + } + + bool is_valid = true; + + // True iff ?, *, or + can follow the previous atom. + bool prev_repeatable = false; + for (int i = 0; regex[i]; i++) { + if (regex[i] == '\\') { // An escape sequence + i++; + if (regex[i] == '\0') { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i - 1) + << "'\\' cannot appear at the end."; + return false; + } + + if (!IsValidEscape(regex[i])) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i - 1) + << "invalid escape sequence \"\\" << regex[i] << "\"."; + is_valid = false; + } + prev_repeatable = true; + } else { // Not an escape sequence. + const char ch = regex[i]; + + if (ch == '^' && i > 0) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'^' can only appear at the beginning."; + is_valid = false; + } else if (ch == '$' && regex[i + 1] != '\0') { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'$' can only appear at the end."; + is_valid = false; + } else if (IsInSet(ch, "()[]{}|")) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'" << ch << "' is unsupported."; + is_valid = false; + } else if (IsRepeat(ch) && !prev_repeatable) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'" << ch << "' can only follow a repeatable token."; + is_valid = false; + } + + prev_repeatable = !IsInSet(ch, "^$?*+"); + } + } + + return is_valid; +} + +// Matches a repeated regex atom followed by a valid simple regular +// expression. The regex atom is defined as c if escaped is false, +// or \c otherwise. repeat is the repetition meta character (?, *, +// or +). The behavior is undefined if str contains too many +// characters to be indexable by size_t, in which case the test will +// probably time out anyway. We are fine with this limitation as +// std::string has it too. +bool MatchRepetitionAndRegexAtHead( + bool escaped, char c, char repeat, const char* regex, + const char* str) { + const size_t min_count = (repeat == '+') ? 1 : 0; + const size_t max_count = (repeat == '?') ? 1 : + static_cast(-1) - 1; + // We cannot call numeric_limits::max() as it conflicts with the + // max() macro on Windows. + + for (size_t i = 0; i <= max_count; ++i) { + // We know that the atom matches each of the first i characters in str. + if (i >= min_count && MatchRegexAtHead(regex, str + i)) { + // We have enough matches at the head, and the tail matches too. + // Since we only care about *whether* the pattern matches str + // (as opposed to *how* it matches), there is no need to find a + // greedy match. + return true; + } + if (str[i] == '\0' || !AtomMatchesChar(escaped, c, str[i])) + return false; + } + return false; +} + +// Returns true iff regex matches a prefix of str. regex must be a +// valid simple regular expression and not start with "^", or the +// result is undefined. +bool MatchRegexAtHead(const char* regex, const char* str) { + if (*regex == '\0') // An empty regex matches a prefix of anything. + return true; + + // "$" only matches the end of a string. Note that regex being + // valid guarantees that there's nothing after "$" in it. + if (*regex == '$') + return *str == '\0'; + + // Is the first thing in regex an escape sequence? + const bool escaped = *regex == '\\'; + if (escaped) + ++regex; + if (IsRepeat(regex[1])) { + // MatchRepetitionAndRegexAtHead() calls MatchRegexAtHead(), so + // here's an indirect recursion. It terminates as the regex gets + // shorter in each recursion. + return MatchRepetitionAndRegexAtHead( + escaped, regex[0], regex[1], regex + 2, str); + } else { + // regex isn't empty, isn't "$", and doesn't start with a + // repetition. We match the first atom of regex with the first + // character of str and recurse. + return (*str != '\0') && AtomMatchesChar(escaped, *regex, *str) && + MatchRegexAtHead(regex + 1, str + 1); + } +} + +// Returns true iff regex matches any substring of str. regex must be +// a valid simple regular expression, or the result is undefined. +// +// The algorithm is recursive, but the recursion depth doesn't exceed +// the regex length, so we won't need to worry about running out of +// stack space normally. In rare cases the time complexity can be +// exponential with respect to the regex length + the string length, +// but usually it's must faster (often close to linear). +bool MatchRegexAnywhere(const char* regex, const char* str) { + if (regex == NULL || str == NULL) + return false; + + if (*regex == '^') + return MatchRegexAtHead(regex + 1, str); + + // A successful match can be anywhere in str. + do { + if (MatchRegexAtHead(regex, str)) + return true; + } while (*str++ != '\0'); + return false; +} + +// Implements the RE class. + +RE::~RE() { + free(const_cast(pattern_)); + free(const_cast(full_pattern_)); +} + +// Returns true iff regular expression re matches the entire str. +bool RE::FullMatch(const char* str, const RE& re) { + return re.is_valid_ && MatchRegexAnywhere(re.full_pattern_, str); +} + +// Returns true iff regular expression re matches a substring of str +// (including str itself). +bool RE::PartialMatch(const char* str, const RE& re) { + return re.is_valid_ && MatchRegexAnywhere(re.pattern_, str); +} + +// Initializes an RE from its string representation. +void RE::Init(const char* regex) { + pattern_ = full_pattern_ = NULL; + if (regex != NULL) { + pattern_ = posix::StrDup(regex); + } + + is_valid_ = ValidateRegex(regex); + if (!is_valid_) { + // No need to calculate the full pattern when the regex is invalid. + return; + } + + const size_t len = strlen(regex); + // Reserves enough bytes to hold the regular expression used for a + // full match: we need space to prepend a '^', append a '$', and + // terminate the string with '\0'. + char* buffer = static_cast(malloc(len + 3)); + full_pattern_ = buffer; + + if (*regex != '^') + *buffer++ = '^'; // Makes sure full_pattern_ starts with '^'. + + // We don't use snprintf or strncpy, as they trigger a warning when + // compiled with VC++ 8.0. + memcpy(buffer, regex, len); + buffer += len; + + if (len == 0 || regex[len - 1] != '$') + *buffer++ = '$'; // Makes sure full_pattern_ ends with '$'. + + *buffer = '\0'; +} + +#endif // GTEST_USES_POSIX_RE + + +GTestLog::GTestLog(GTestLogSeverity severity, const char* file, int line) + : severity_(severity) { + const char* const marker = + severity == GTEST_INFO ? "[ INFO ]" : + severity == GTEST_WARNING ? "[WARNING]" : + severity == GTEST_ERROR ? "[ ERROR ]" : "[ FATAL ]"; + GetStream() << ::std::endl << marker << " " + << FormatFileLocation(file, line).c_str() << ": "; +} + +// Flushes the buffers and, if severity is GTEST_FATAL, aborts the program. +GTestLog::~GTestLog() { + GetStream() << ::std::endl; + if (severity_ == GTEST_FATAL) { + fflush(stderr); + posix::Abort(); + } +} +// Disable Microsoft deprecation warnings for POSIX functions called from +// this class (creat, dup, dup2, and close) +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4996) +#endif // _MSC_VER + +#if GTEST_HAS_STREAM_REDIRECTION_ + +// Object that captures an output stream (stdout/stderr). +class CapturedStream { + public: + // The ctor redirects the stream to a temporary file. + CapturedStream(int fd) : fd_(fd), uncaptured_fd_(dup(fd)) { +#if GTEST_OS_WINDOWS + char temp_dir_path[MAX_PATH + 1] = { '\0' }; // NOLINT + char temp_file_path[MAX_PATH + 1] = { '\0' }; // NOLINT + + ::GetTempPathA(sizeof(temp_dir_path), temp_dir_path); + const UINT success = ::GetTempFileNameA(temp_dir_path, + "gtest_redir", + 0, // Generate unique file name. + temp_file_path); + GTEST_CHECK_(success != 0) + << "Unable to create a temporary file in " << temp_dir_path; + const int captured_fd = creat(temp_file_path, _S_IREAD | _S_IWRITE); + GTEST_CHECK_(captured_fd != -1) << "Unable to open temporary file " + << temp_file_path; + filename_ = temp_file_path; +#else + // There's no guarantee that a test has write access to the + // current directory, so we create the temporary file in the /tmp + // directory instead. + char name_template[] = "/tmp/captured_stream.XXXXXX"; + const int captured_fd = mkstemp(name_template); + filename_ = name_template; +#endif // GTEST_OS_WINDOWS + fflush(NULL); + dup2(captured_fd, fd_); + close(captured_fd); + } + + ~CapturedStream() { + remove(filename_.c_str()); + } + + String GetCapturedString() { + if (uncaptured_fd_ != -1) { + // Restores the original stream. + fflush(NULL); + dup2(uncaptured_fd_, fd_); + close(uncaptured_fd_); + uncaptured_fd_ = -1; + } + + FILE* const file = posix::FOpen(filename_.c_str(), "r"); + const String content = ReadEntireFile(file); + posix::FClose(file); + return content; + } + + private: + // Reads the entire content of a file as a String. + static String ReadEntireFile(FILE* file); + + // Returns the size (in bytes) of a file. + static size_t GetFileSize(FILE* file); + + const int fd_; // A stream to capture. + int uncaptured_fd_; + // Name of the temporary file holding the stderr output. + ::std::string filename_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(CapturedStream); +}; + +// Returns the size (in bytes) of a file. +size_t CapturedStream::GetFileSize(FILE* file) { + fseek(file, 0, SEEK_END); + return static_cast(ftell(file)); +} + +// Reads the entire content of a file as a string. +String CapturedStream::ReadEntireFile(FILE* file) { + const size_t file_size = GetFileSize(file); + char* const buffer = new char[file_size]; + + size_t bytes_last_read = 0; // # of bytes read in the last fread() + size_t bytes_read = 0; // # of bytes read so far + + fseek(file, 0, SEEK_SET); + + // Keeps reading the file until we cannot read further or the + // pre-determined file size is reached. + do { + bytes_last_read = fread(buffer+bytes_read, 1, file_size-bytes_read, file); + bytes_read += bytes_last_read; + } while (bytes_last_read > 0 && bytes_read < file_size); + + const String content(buffer, bytes_read); + delete[] buffer; + + return content; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + +static CapturedStream* g_captured_stderr = NULL; +static CapturedStream* g_captured_stdout = NULL; + +// Starts capturing an output stream (stdout/stderr). +void CaptureStream(int fd, const char* stream_name, CapturedStream** stream) { + if (*stream != NULL) { + GTEST_LOG_(FATAL) << "Only one " << stream_name + << " capturer can exist at a time."; + } + *stream = new CapturedStream(fd); +} + +// Stops capturing the output stream and returns the captured string. +String GetCapturedStream(CapturedStream** captured_stream) { + const String content = (*captured_stream)->GetCapturedString(); + + delete *captured_stream; + *captured_stream = NULL; + + return content; +} + +// Starts capturing stdout. +void CaptureStdout() { + CaptureStream(kStdOutFileno, "stdout", &g_captured_stdout); +} + +// Starts capturing stderr. +void CaptureStderr() { + CaptureStream(kStdErrFileno, "stderr", &g_captured_stderr); +} + +// Stops capturing stdout and returns the captured string. +String GetCapturedStdout() { return GetCapturedStream(&g_captured_stdout); } + +// Stops capturing stderr and returns the captured string. +String GetCapturedStderr() { return GetCapturedStream(&g_captured_stderr); } + +#endif // GTEST_HAS_STREAM_REDIRECTION_ + +#if GTEST_HAS_DEATH_TEST + +// A copy of all command line arguments. Set by InitGoogleTest(). +::std::vector g_argvs; + +// Returns the command line as a vector of strings. +const ::std::vector& GetArgvs() { return g_argvs; } + +#endif // GTEST_HAS_DEATH_TEST + +#if GTEST_OS_WINDOWS_MOBILE +namespace posix { +void Abort() { + DebugBreak(); + TerminateProcess(GetCurrentProcess(), 1); +} +} // namespace posix +#endif // GTEST_OS_WINDOWS_MOBILE + +// Returns the name of the environment variable corresponding to the +// given flag. For example, FlagToEnvVar("foo") will return +// "GTEST_FOO" in the open-source version. +static String FlagToEnvVar(const char* flag) { + const String full_flag = + (Message() << GTEST_FLAG_PREFIX_ << flag).GetString(); + + Message env_var; + for (size_t i = 0; i != full_flag.length(); i++) { + env_var << static_cast(toupper(full_flag.c_str()[i])); + } + + return env_var.GetString(); +} + +// Parses 'str' for a 32-bit signed integer. If successful, writes +// the result to *value and returns true; otherwise leaves *value +// unchanged and returns false. +bool ParseInt32(const Message& src_text, const char* str, Int32* value) { + // Parses the environment variable as a decimal integer. + char* end = NULL; + const long long_value = strtol(str, &end, 10); // NOLINT + + // Has strtol() consumed all characters in the string? + if (*end != '\0') { + // No - an invalid character was encountered. + Message msg; + msg << "WARNING: " << src_text + << " is expected to be a 32-bit integer, but actually" + << " has value \"" << str << "\".\n"; + printf("%s", msg.GetString().c_str()); + fflush(stdout); + return false; + } + + // Is the parsed value in the range of an Int32? + const Int32 result = static_cast(long_value); + if (long_value == LONG_MAX || long_value == LONG_MIN || + // The parsed value overflows as a long. (strtol() returns + // LONG_MAX or LONG_MIN when the input overflows.) + result != long_value + // The parsed value overflows as an Int32. + ) { + Message msg; + msg << "WARNING: " << src_text + << " is expected to be a 32-bit integer, but actually" + << " has value " << str << ", which overflows.\n"; + printf("%s", msg.GetString().c_str()); + fflush(stdout); + return false; + } + + *value = result; + return true; +} + +// Reads and returns the Boolean environment variable corresponding to +// the given flag; if it's not set, returns default_value. +// +// The value is considered true iff it's not "0". +bool BoolFromGTestEnv(const char* flag, bool default_value) { + const String env_var = FlagToEnvVar(flag); + const char* const string_value = posix::GetEnv(env_var.c_str()); + return string_value == NULL ? + default_value : strcmp(string_value, "0") != 0; +} + +// Reads and returns a 32-bit integer stored in the environment +// variable corresponding to the given flag; if it isn't set or +// doesn't represent a valid 32-bit integer, returns default_value. +Int32 Int32FromGTestEnv(const char* flag, Int32 default_value) { + const String env_var = FlagToEnvVar(flag); + const char* const string_value = posix::GetEnv(env_var.c_str()); + if (string_value == NULL) { + // The environment variable is not set. + return default_value; + } + + Int32 result = default_value; + if (!ParseInt32(Message() << "Environment variable " << env_var, + string_value, &result)) { + printf("The default value %s is used.\n", + (Message() << default_value).GetString().c_str()); + fflush(stdout); + return default_value; + } + + return result; +} + +// Reads and returns the string environment variable corresponding to +// the given flag; if it's not set, returns default_value. +const char* StringFromGTestEnv(const char* flag, const char* default_value) { + const String env_var = FlagToEnvVar(flag); + const char* const value = posix::GetEnv(env_var.c_str()); + return value == NULL ? default_value : value; +} + +} // namespace internal +} // namespace testing diff --git a/3rdparty/gmock/gtest/src/gtest-test-part.cc b/3rdparty/gmock/gtest/src/gtest-test-part.cc new file mode 100644 index 00000000..5d183a44 --- /dev/null +++ b/3rdparty/gmock/gtest/src/gtest-test-part.cc @@ -0,0 +1,110 @@ +// Copyright 2008, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: mheule@google.com (Markus Heule) +// +// The Google C++ Testing Framework (Google Test) + +#include + +// Indicates that this translation unit is part of Google Test's +// implementation. It must come before gtest-internal-inl.h is +// included, or there will be a compiler error. This trick is to +// prevent a user from accidentally including gtest-internal-inl.h in +// his code. +#define GTEST_IMPLEMENTATION_ 1 +#include "src/gtest-internal-inl.h" +#undef GTEST_IMPLEMENTATION_ + +namespace testing { + +using internal::GetUnitTestImpl; + +// Gets the summary of the failure message by omitting the stack trace +// in it. +internal::String TestPartResult::ExtractSummary(const char* message) { + const char* const stack_trace = strstr(message, internal::kStackTraceMarker); + return stack_trace == NULL ? internal::String(message) : + internal::String(message, stack_trace - message); +} + +// Prints a TestPartResult object. +std::ostream& operator<<(std::ostream& os, const TestPartResult& result) { + return os + << result.file_name() << ":" << result.line_number() << ": " + << (result.type() == TestPartResult::kSuccess ? "Success" : + result.type() == TestPartResult::kFatalFailure ? "Fatal failure" : + "Non-fatal failure") << ":\n" + << result.message() << std::endl; +} + +// Appends a TestPartResult to the array. +void TestPartResultArray::Append(const TestPartResult& result) { + array_.push_back(result); +} + +// Returns the TestPartResult at the given index (0-based). +const TestPartResult& TestPartResultArray::GetTestPartResult(int index) const { + if (index < 0 || index >= size()) { + printf("\nInvalid index (%d) into TestPartResultArray.\n", index); + internal::posix::Abort(); + } + + return array_[index]; +} + +// Returns the number of TestPartResult objects in the array. +int TestPartResultArray::size() const { + return static_cast(array_.size()); +} + +namespace internal { + +HasNewFatalFailureHelper::HasNewFatalFailureHelper() + : has_new_fatal_failure_(false), + original_reporter_(GetUnitTestImpl()-> + GetTestPartResultReporterForCurrentThread()) { + GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread(this); +} + +HasNewFatalFailureHelper::~HasNewFatalFailureHelper() { + GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread( + original_reporter_); +} + +void HasNewFatalFailureHelper::ReportTestPartResult( + const TestPartResult& result) { + if (result.fatally_failed()) + has_new_fatal_failure_ = true; + original_reporter_->ReportTestPartResult(result); +} + +} // namespace internal + +} // namespace testing diff --git a/3rdparty/gmock/gtest/src/gtest-typed-test.cc b/3rdparty/gmock/gtest/src/gtest-typed-test.cc new file mode 100644 index 00000000..3cc4b5de --- /dev/null +++ b/3rdparty/gmock/gtest/src/gtest-typed-test.cc @@ -0,0 +1,110 @@ +// Copyright 2008 Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +#include +#include + +namespace testing { +namespace internal { + +#if GTEST_HAS_TYPED_TEST_P + +// Skips to the first non-space char in str. Returns an empty string if str +// contains only whitespace characters. +static const char* SkipSpaces(const char* str) { + while (isspace(*str)) + str++; + return str; +} + +// Verifies that registered_tests match the test names in +// defined_test_names_; returns registered_tests if successful, or +// aborts the program otherwise. +const char* TypedTestCasePState::VerifyRegisteredTestNames( + const char* file, int line, const char* registered_tests) { + typedef ::std::set::const_iterator DefinedTestIter; + registered_ = true; + + // Skip initial whitespace in registered_tests since some + // preprocessors prefix stringizied literals with whitespace. + registered_tests = SkipSpaces(registered_tests); + + Message errors; + ::std::set tests; + for (const char* names = registered_tests; names != NULL; + names = SkipComma(names)) { + const String name = GetPrefixUntilComma(names); + if (tests.count(name) != 0) { + errors << "Test " << name << " is listed more than once.\n"; + continue; + } + + bool found = false; + for (DefinedTestIter it = defined_test_names_.begin(); + it != defined_test_names_.end(); + ++it) { + if (name == *it) { + found = true; + break; + } + } + + if (found) { + tests.insert(name); + } else { + errors << "No test named " << name + << " can be found in this test case.\n"; + } + } + + for (DefinedTestIter it = defined_test_names_.begin(); + it != defined_test_names_.end(); + ++it) { + if (tests.count(*it) == 0) { + errors << "You forgot to list test " << *it << ".\n"; + } + } + + const String& errors_str = errors.GetString(); + if (errors_str != "") { + fprintf(stderr, "%s %s", FormatFileLocation(file, line).c_str(), + errors_str.c_str()); + fflush(stderr); + posix::Abort(); + } + + return registered_tests; +} + +#endif // GTEST_HAS_TYPED_TEST_P + +} // namespace internal +} // namespace testing diff --git a/3rdparty/gmock/gtest/src/gtest.cc b/3rdparty/gmock/gtest/src/gtest.cc new file mode 100644 index 00000000..342d4582 --- /dev/null +++ b/3rdparty/gmock/gtest/src/gtest.cc @@ -0,0 +1,4704 @@ +// Copyright 2005, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) +// +// The Google C++ Testing Framework (Google Test) + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if GTEST_OS_LINUX + +// TODO(kenton@google.com): Use autoconf to detect availability of +// gettimeofday(). +#define GTEST_HAS_GETTIMEOFDAY_ 1 + +#include +#include +#include +// Declares vsnprintf(). This header is not available on Windows. +#include +#include +#include +#include +#include +#include + +#elif GTEST_OS_SYMBIAN +#define GTEST_HAS_GETTIMEOFDAY_ 1 +#include // NOLINT + +#elif GTEST_OS_ZOS +#define GTEST_HAS_GETTIMEOFDAY_ 1 +#include // NOLINT + +// On z/OS we additionally need strings.h for strcasecmp. +#include // NOLINT + +#elif GTEST_OS_WINDOWS_MOBILE // We are on Windows CE. + +#include // NOLINT + +#elif GTEST_OS_WINDOWS // We are on Windows proper. + +#include // NOLINT +#include // NOLINT +#include // NOLINT +#include // NOLINT + +#if GTEST_OS_WINDOWS_MINGW +// MinGW has gettimeofday() but not _ftime64(). +// TODO(kenton@google.com): Use autoconf to detect availability of +// gettimeofday(). +// TODO(kenton@google.com): There are other ways to get the time on +// Windows, like GetTickCount() or GetSystemTimeAsFileTime(). MinGW +// supports these. consider using them instead. +#define GTEST_HAS_GETTIMEOFDAY_ 1 +#include // NOLINT +#endif // GTEST_OS_WINDOWS_MINGW + +// cpplint thinks that the header is already included, so we want to +// silence it. +#include // NOLINT + +#else + +// Assume other platforms have gettimeofday(). +// TODO(kenton@google.com): Use autoconf to detect availability of +// gettimeofday(). +#define GTEST_HAS_GETTIMEOFDAY_ 1 + +// cpplint thinks that the header is already included, so we want to +// silence it. +#include // NOLINT +#include // NOLINT + +#endif // GTEST_OS_LINUX + +#if GTEST_HAS_EXCEPTIONS +#include +#endif + +// Indicates that this translation unit is part of Google Test's +// implementation. It must come before gtest-internal-inl.h is +// included, or there will be a compiler error. This trick is to +// prevent a user from accidentally including gtest-internal-inl.h in +// his code. +#define GTEST_IMPLEMENTATION_ 1 +#include "src/gtest-internal-inl.h" +#undef GTEST_IMPLEMENTATION_ + +#if GTEST_OS_WINDOWS +#define vsnprintf _vsnprintf +#endif // GTEST_OS_WINDOWS + +namespace testing { + +using internal::CountIf; +using internal::ForEach; +using internal::GetElementOr; +using internal::Shuffle; + +// Constants. + +// A test whose test case name or test name matches this filter is +// disabled and not run. +static const char kDisableTestFilter[] = "DISABLED_*:*/DISABLED_*"; + +// A test case whose name matches this filter is considered a death +// test case and will be run before test cases whose name doesn't +// match this filter. +static const char kDeathTestCaseFilter[] = "*DeathTest:*DeathTest/*"; + +// A test filter that matches everything. +static const char kUniversalFilter[] = "*"; + +// The default output file for XML output. +static const char kDefaultOutputFile[] = "test_detail.xml"; + +// The environment variable name for the test shard index. +static const char kTestShardIndex[] = "GTEST_SHARD_INDEX"; +// The environment variable name for the total number of test shards. +static const char kTestTotalShards[] = "GTEST_TOTAL_SHARDS"; +// The environment variable name for the test shard status file. +static const char kTestShardStatusFile[] = "GTEST_SHARD_STATUS_FILE"; + +namespace internal { + +// The text used in failure messages to indicate the start of the +// stack trace. +const char kStackTraceMarker[] = "\nStack trace:\n"; + +// g_help_flag is true iff the --help flag or an equivalent form is +// specified on the command line. +bool g_help_flag = false; + +} // namespace internal + +GTEST_DEFINE_bool_( + also_run_disabled_tests, + internal::BoolFromGTestEnv("also_run_disabled_tests", false), + "Run disabled tests too, in addition to the tests normally being run."); + +GTEST_DEFINE_bool_( + break_on_failure, + internal::BoolFromGTestEnv("break_on_failure", false), + "True iff a failed assertion should be a debugger break-point."); + +GTEST_DEFINE_bool_( + catch_exceptions, + internal::BoolFromGTestEnv("catch_exceptions", false), + "True iff " GTEST_NAME_ + " should catch exceptions and treat them as test failures."); + +GTEST_DEFINE_string_( + color, + internal::StringFromGTestEnv("color", "auto"), + "Whether to use colors in the output. Valid values: yes, no, " + "and auto. 'auto' means to use colors if the output is " + "being sent to a terminal and the TERM environment variable " + "is set to xterm, xterm-color, xterm-256color, linux or cygwin."); + +GTEST_DEFINE_string_( + filter, + internal::StringFromGTestEnv("filter", kUniversalFilter), + "A colon-separated list of glob (not regex) patterns " + "for filtering the tests to run, optionally followed by a " + "'-' and a : separated list of negative patterns (tests to " + "exclude). A test is run if it matches one of the positive " + "patterns and does not match any of the negative patterns."); + +GTEST_DEFINE_bool_(list_tests, false, + "List all tests without running them."); + +GTEST_DEFINE_string_( + output, + internal::StringFromGTestEnv("output", ""), + "A format (currently must be \"xml\"), optionally followed " + "by a colon and an output file name or directory. A directory " + "is indicated by a trailing pathname separator. " + "Examples: \"xml:filename.xml\", \"xml::directoryname/\". " + "If a directory is specified, output files will be created " + "within that directory, with file-names based on the test " + "executable's name and, if necessary, made unique by adding " + "digits."); + +GTEST_DEFINE_bool_( + print_time, + internal::BoolFromGTestEnv("print_time", true), + "True iff " GTEST_NAME_ + " should display elapsed time in text output."); + +GTEST_DEFINE_int32_( + random_seed, + internal::Int32FromGTestEnv("random_seed", 0), + "Random number seed to use when shuffling test orders. Must be in range " + "[1, 99999], or 0 to use a seed based on the current time."); + +GTEST_DEFINE_int32_( + repeat, + internal::Int32FromGTestEnv("repeat", 1), + "How many times to repeat each test. Specify a negative number " + "for repeating forever. Useful for shaking out flaky tests."); + +GTEST_DEFINE_bool_( + show_internal_stack_frames, false, + "True iff " GTEST_NAME_ " should include internal stack frames when " + "printing test failure stack traces."); + +GTEST_DEFINE_bool_( + shuffle, + internal::BoolFromGTestEnv("shuffle", false), + "True iff " GTEST_NAME_ + " should randomize tests' order on every run."); + +GTEST_DEFINE_int32_( + stack_trace_depth, + internal::Int32FromGTestEnv("stack_trace_depth", kMaxStackTraceDepth), + "The maximum number of stack frames to print when an " + "assertion fails. The valid range is 0 through 100, inclusive."); + +GTEST_DEFINE_bool_( + throw_on_failure, + internal::BoolFromGTestEnv("throw_on_failure", false), + "When this flag is specified, a failed assertion will throw an exception " + "if exceptions are enabled or exit the program with a non-zero code " + "otherwise."); + +namespace internal { + +// Generates a random number from [0, range), using a Linear +// Congruential Generator (LCG). Crashes if 'range' is 0 or greater +// than kMaxRange. +UInt32 Random::Generate(UInt32 range) { + // These constants are the same as are used in glibc's rand(3). + state_ = (1103515245U*state_ + 12345U) % kMaxRange; + + GTEST_CHECK_(range > 0) + << "Cannot generate a number in the range [0, 0)."; + GTEST_CHECK_(range <= kMaxRange) + << "Generation of a number in [0, " << range << ") was requested, " + << "but this can only generate numbers in [0, " << kMaxRange << ")."; + + // Converting via modulus introduces a bit of downward bias, but + // it's simple, and a linear congruential generator isn't too good + // to begin with. + return state_ % range; +} + +// GTestIsInitialized() returns true iff the user has initialized +// Google Test. Useful for catching the user mistake of not initializing +// Google Test before calling RUN_ALL_TESTS(). +// +// A user must call testing::InitGoogleTest() to initialize Google +// Test. g_init_gtest_count is set to the number of times +// InitGoogleTest() has been called. We don't protect this variable +// under a mutex as it is only accessed in the main thread. +int g_init_gtest_count = 0; +static bool GTestIsInitialized() { return g_init_gtest_count != 0; } + +// Iterates over a vector of TestCases, keeping a running sum of the +// results of calling a given int-returning method on each. +// Returns the sum. +static int SumOverTestCaseList(const std::vector& case_list, + int (TestCase::*method)() const) { + int sum = 0; + for (size_t i = 0; i < case_list.size(); i++) { + sum += (case_list[i]->*method)(); + } + return sum; +} + +// Returns true iff the test case passed. +static bool TestCasePassed(const TestCase* test_case) { + return test_case->should_run() && test_case->Passed(); +} + +// Returns true iff the test case failed. +static bool TestCaseFailed(const TestCase* test_case) { + return test_case->should_run() && test_case->Failed(); +} + +// Returns true iff test_case contains at least one test that should +// run. +static bool ShouldRunTestCase(const TestCase* test_case) { + return test_case->should_run(); +} + +// AssertHelper constructor. +AssertHelper::AssertHelper(TestPartResult::Type type, + const char* file, + int line, + const char* message) + : data_(new AssertHelperData(type, file, line, message)) { +} + +AssertHelper::~AssertHelper() { + delete data_; +} + +// Message assignment, for assertion streaming support. +void AssertHelper::operator=(const Message& message) const { + UnitTest::GetInstance()-> + AddTestPartResult(data_->type, data_->file, data_->line, + AppendUserMessage(data_->message, message), + UnitTest::GetInstance()->impl() + ->CurrentOsStackTraceExceptTop(1) + // Skips the stack frame for this function itself. + ); // NOLINT +} + +// Mutex for linked pointers. +GTEST_DEFINE_STATIC_MUTEX_(g_linked_ptr_mutex); + +// Application pathname gotten in InitGoogleTest. +String g_executable_path; + +// Returns the current application's name, removing directory path if that +// is present. +FilePath GetCurrentExecutableName() { + FilePath result; + +#if GTEST_OS_WINDOWS + result.Set(FilePath(g_executable_path).RemoveExtension("exe")); +#else + result.Set(FilePath(g_executable_path)); +#endif // GTEST_OS_WINDOWS + + return result.RemoveDirectoryName(); +} + +// Functions for processing the gtest_output flag. + +// Returns the output format, or "" for normal printed output. +String UnitTestOptions::GetOutputFormat() { + const char* const gtest_output_flag = GTEST_FLAG(output).c_str(); + if (gtest_output_flag == NULL) return String(""); + + const char* const colon = strchr(gtest_output_flag, ':'); + return (colon == NULL) ? + String(gtest_output_flag) : + String(gtest_output_flag, colon - gtest_output_flag); +} + +// Returns the name of the requested output file, or the default if none +// was explicitly specified. +String UnitTestOptions::GetAbsolutePathToOutputFile() { + const char* const gtest_output_flag = GTEST_FLAG(output).c_str(); + if (gtest_output_flag == NULL) + return String(""); + + const char* const colon = strchr(gtest_output_flag, ':'); + if (colon == NULL) + return String(internal::FilePath::ConcatPaths( + internal::FilePath( + UnitTest::GetInstance()->original_working_dir()), + internal::FilePath(kDefaultOutputFile)).ToString() ); + + internal::FilePath output_name(colon + 1); + if (!output_name.IsAbsolutePath()) + // TODO(wan@google.com): on Windows \some\path is not an absolute + // path (as its meaning depends on the current drive), yet the + // following logic for turning it into an absolute path is wrong. + // Fix it. + output_name = internal::FilePath::ConcatPaths( + internal::FilePath(UnitTest::GetInstance()->original_working_dir()), + internal::FilePath(colon + 1)); + + if (!output_name.IsDirectory()) + return output_name.ToString(); + + internal::FilePath result(internal::FilePath::GenerateUniqueFileName( + output_name, internal::GetCurrentExecutableName(), + GetOutputFormat().c_str())); + return result.ToString(); +} + +// Returns true iff the wildcard pattern matches the string. The +// first ':' or '\0' character in pattern marks the end of it. +// +// This recursive algorithm isn't very efficient, but is clear and +// works well enough for matching test names, which are short. +bool UnitTestOptions::PatternMatchesString(const char *pattern, + const char *str) { + switch (*pattern) { + case '\0': + case ':': // Either ':' or '\0' marks the end of the pattern. + return *str == '\0'; + case '?': // Matches any single character. + return *str != '\0' && PatternMatchesString(pattern + 1, str + 1); + case '*': // Matches any string (possibly empty) of characters. + return (*str != '\0' && PatternMatchesString(pattern, str + 1)) || + PatternMatchesString(pattern + 1, str); + default: // Non-special character. Matches itself. + return *pattern == *str && + PatternMatchesString(pattern + 1, str + 1); + } +} + +bool UnitTestOptions::MatchesFilter(const String& name, const char* filter) { + const char *cur_pattern = filter; + for (;;) { + if (PatternMatchesString(cur_pattern, name.c_str())) { + return true; + } + + // Finds the next pattern in the filter. + cur_pattern = strchr(cur_pattern, ':'); + + // Returns if no more pattern can be found. + if (cur_pattern == NULL) { + return false; + } + + // Skips the pattern separater (the ':' character). + cur_pattern++; + } +} + +// TODO(keithray): move String function implementations to gtest-string.cc. + +// Returns true iff the user-specified filter matches the test case +// name and the test name. +bool UnitTestOptions::FilterMatchesTest(const String &test_case_name, + const String &test_name) { + const String& full_name = String::Format("%s.%s", + test_case_name.c_str(), + test_name.c_str()); + + // Split --gtest_filter at '-', if there is one, to separate into + // positive filter and negative filter portions + const char* const p = GTEST_FLAG(filter).c_str(); + const char* const dash = strchr(p, '-'); + String positive; + String negative; + if (dash == NULL) { + positive = GTEST_FLAG(filter).c_str(); // Whole string is a positive filter + negative = String(""); + } else { + positive = String(p, dash - p); // Everything up to the dash + negative = String(dash+1); // Everything after the dash + if (positive.empty()) { + // Treat '-test1' as the same as '*-test1' + positive = kUniversalFilter; + } + } + + // A filter is a colon-separated list of patterns. It matches a + // test if any pattern in it matches the test. + return (MatchesFilter(full_name, positive.c_str()) && + !MatchesFilter(full_name, negative.c_str())); +} + +#if GTEST_OS_WINDOWS +// Returns EXCEPTION_EXECUTE_HANDLER if Google Test should handle the +// given SEH exception, or EXCEPTION_CONTINUE_SEARCH otherwise. +// This function is useful as an __except condition. +int UnitTestOptions::GTestShouldProcessSEH(DWORD exception_code) { + // Google Test should handle an exception if: + // 1. the user wants it to, AND + // 2. this is not a breakpoint exception. + return (GTEST_FLAG(catch_exceptions) && + exception_code != EXCEPTION_BREAKPOINT) ? + EXCEPTION_EXECUTE_HANDLER : + EXCEPTION_CONTINUE_SEARCH; +} +#endif // GTEST_OS_WINDOWS + +} // namespace internal + +// The c'tor sets this object as the test part result reporter used by +// Google Test. The 'result' parameter specifies where to report the +// results. Intercepts only failures from the current thread. +ScopedFakeTestPartResultReporter::ScopedFakeTestPartResultReporter( + TestPartResultArray* result) + : intercept_mode_(INTERCEPT_ONLY_CURRENT_THREAD), + result_(result) { + Init(); +} + +// The c'tor sets this object as the test part result reporter used by +// Google Test. The 'result' parameter specifies where to report the +// results. +ScopedFakeTestPartResultReporter::ScopedFakeTestPartResultReporter( + InterceptMode intercept_mode, TestPartResultArray* result) + : intercept_mode_(intercept_mode), + result_(result) { + Init(); +} + +void ScopedFakeTestPartResultReporter::Init() { + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + if (intercept_mode_ == INTERCEPT_ALL_THREADS) { + old_reporter_ = impl->GetGlobalTestPartResultReporter(); + impl->SetGlobalTestPartResultReporter(this); + } else { + old_reporter_ = impl->GetTestPartResultReporterForCurrentThread(); + impl->SetTestPartResultReporterForCurrentThread(this); + } +} + +// The d'tor restores the test part result reporter used by Google Test +// before. +ScopedFakeTestPartResultReporter::~ScopedFakeTestPartResultReporter() { + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + if (intercept_mode_ == INTERCEPT_ALL_THREADS) { + impl->SetGlobalTestPartResultReporter(old_reporter_); + } else { + impl->SetTestPartResultReporterForCurrentThread(old_reporter_); + } +} + +// Increments the test part result count and remembers the result. +// This method is from the TestPartResultReporterInterface interface. +void ScopedFakeTestPartResultReporter::ReportTestPartResult( + const TestPartResult& result) { + result_->Append(result); +} + +namespace internal { + +// Returns the type ID of ::testing::Test. We should always call this +// instead of GetTypeId< ::testing::Test>() to get the type ID of +// testing::Test. This is to work around a suspected linker bug when +// using Google Test as a framework on Mac OS X. The bug causes +// GetTypeId< ::testing::Test>() to return different values depending +// on whether the call is from the Google Test framework itself or +// from user test code. GetTestTypeId() is guaranteed to always +// return the same value, as it always calls GetTypeId<>() from the +// gtest.cc, which is within the Google Test framework. +TypeId GetTestTypeId() { + return GetTypeId(); +} + +// The value of GetTestTypeId() as seen from within the Google Test +// library. This is solely for testing GetTestTypeId(). +extern const TypeId kTestTypeIdInGoogleTest = GetTestTypeId(); + +// This predicate-formatter checks that 'results' contains a test part +// failure of the given type and that the failure message contains the +// given substring. +AssertionResult HasOneFailure(const char* /* results_expr */, + const char* /* type_expr */, + const char* /* substr_expr */, + const TestPartResultArray& results, + TestPartResult::Type type, + const char* substr) { + const String expected(type == TestPartResult::kFatalFailure ? + "1 fatal failure" : + "1 non-fatal failure"); + Message msg; + if (results.size() != 1) { + msg << "Expected: " << expected << "\n" + << " Actual: " << results.size() << " failures"; + for (int i = 0; i < results.size(); i++) { + msg << "\n" << results.GetTestPartResult(i); + } + return AssertionFailure(msg); + } + + const TestPartResult& r = results.GetTestPartResult(0); + if (r.type() != type) { + msg << "Expected: " << expected << "\n" + << " Actual:\n" + << r; + return AssertionFailure(msg); + } + + if (strstr(r.message(), substr) == NULL) { + msg << "Expected: " << expected << " containing \"" + << substr << "\"\n" + << " Actual:\n" + << r; + return AssertionFailure(msg); + } + + return AssertionSuccess(); +} + +// The constructor of SingleFailureChecker remembers where to look up +// test part results, what type of failure we expect, and what +// substring the failure message should contain. +SingleFailureChecker:: SingleFailureChecker( + const TestPartResultArray* results, + TestPartResult::Type type, + const char* substr) + : results_(results), + type_(type), + substr_(substr) {} + +// The destructor of SingleFailureChecker verifies that the given +// TestPartResultArray contains exactly one failure that has the given +// type and contains the given substring. If that's not the case, a +// non-fatal failure will be generated. +SingleFailureChecker::~SingleFailureChecker() { + EXPECT_PRED_FORMAT3(HasOneFailure, *results_, type_, substr_.c_str()); +} + +DefaultGlobalTestPartResultReporter::DefaultGlobalTestPartResultReporter( + UnitTestImpl* unit_test) : unit_test_(unit_test) {} + +void DefaultGlobalTestPartResultReporter::ReportTestPartResult( + const TestPartResult& result) { + unit_test_->current_test_result()->AddTestPartResult(result); + unit_test_->listeners()->repeater()->OnTestPartResult(result); +} + +DefaultPerThreadTestPartResultReporter::DefaultPerThreadTestPartResultReporter( + UnitTestImpl* unit_test) : unit_test_(unit_test) {} + +void DefaultPerThreadTestPartResultReporter::ReportTestPartResult( + const TestPartResult& result) { + unit_test_->GetGlobalTestPartResultReporter()->ReportTestPartResult(result); +} + +// Returns the global test part result reporter. +TestPartResultReporterInterface* +UnitTestImpl::GetGlobalTestPartResultReporter() { + internal::MutexLock lock(&global_test_part_result_reporter_mutex_); + return global_test_part_result_repoter_; +} + +// Sets the global test part result reporter. +void UnitTestImpl::SetGlobalTestPartResultReporter( + TestPartResultReporterInterface* reporter) { + internal::MutexLock lock(&global_test_part_result_reporter_mutex_); + global_test_part_result_repoter_ = reporter; +} + +// Returns the test part result reporter for the current thread. +TestPartResultReporterInterface* +UnitTestImpl::GetTestPartResultReporterForCurrentThread() { + return per_thread_test_part_result_reporter_.get(); +} + +// Sets the test part result reporter for the current thread. +void UnitTestImpl::SetTestPartResultReporterForCurrentThread( + TestPartResultReporterInterface* reporter) { + per_thread_test_part_result_reporter_.set(reporter); +} + +// Gets the number of successful test cases. +int UnitTestImpl::successful_test_case_count() const { + return CountIf(test_cases_, TestCasePassed); +} + +// Gets the number of failed test cases. +int UnitTestImpl::failed_test_case_count() const { + return CountIf(test_cases_, TestCaseFailed); +} + +// Gets the number of all test cases. +int UnitTestImpl::total_test_case_count() const { + return static_cast(test_cases_.size()); +} + +// Gets the number of all test cases that contain at least one test +// that should run. +int UnitTestImpl::test_case_to_run_count() const { + return CountIf(test_cases_, ShouldRunTestCase); +} + +// Gets the number of successful tests. +int UnitTestImpl::successful_test_count() const { + return SumOverTestCaseList(test_cases_, &TestCase::successful_test_count); +} + +// Gets the number of failed tests. +int UnitTestImpl::failed_test_count() const { + return SumOverTestCaseList(test_cases_, &TestCase::failed_test_count); +} + +// Gets the number of disabled tests. +int UnitTestImpl::disabled_test_count() const { + return SumOverTestCaseList(test_cases_, &TestCase::disabled_test_count); +} + +// Gets the number of all tests. +int UnitTestImpl::total_test_count() const { + return SumOverTestCaseList(test_cases_, &TestCase::total_test_count); +} + +// Gets the number of tests that should run. +int UnitTestImpl::test_to_run_count() const { + return SumOverTestCaseList(test_cases_, &TestCase::test_to_run_count); +} + +// Returns the current OS stack trace as a String. +// +// The maximum number of stack frames to be included is specified by +// the gtest_stack_trace_depth flag. The skip_count parameter +// specifies the number of top frames to be skipped, which doesn't +// count against the number of frames to be included. +// +// For example, if Foo() calls Bar(), which in turn calls +// CurrentOsStackTraceExceptTop(1), Foo() will be included in the +// trace but Bar() and CurrentOsStackTraceExceptTop() won't. +String UnitTestImpl::CurrentOsStackTraceExceptTop(int skip_count) { + (void)skip_count; + return String(""); +} + +// Returns the current time in milliseconds. +TimeInMillis GetTimeInMillis() { +#if GTEST_OS_WINDOWS_MOBILE || defined(__BORLANDC__) + // Difference between 1970-01-01 and 1601-01-01 in milliseconds. + // http://analogous.blogspot.com/2005/04/epoch.html + const TimeInMillis kJavaEpochToWinFileTimeDelta = + static_cast(116444736UL) * 100000UL; + const DWORD kTenthMicrosInMilliSecond = 10000; + + SYSTEMTIME now_systime; + FILETIME now_filetime; + ULARGE_INTEGER now_int64; + // TODO(kenton@google.com): Shouldn't this just use + // GetSystemTimeAsFileTime()? + GetSystemTime(&now_systime); + if (SystemTimeToFileTime(&now_systime, &now_filetime)) { + now_int64.LowPart = now_filetime.dwLowDateTime; + now_int64.HighPart = now_filetime.dwHighDateTime; + now_int64.QuadPart = (now_int64.QuadPart / kTenthMicrosInMilliSecond) - + kJavaEpochToWinFileTimeDelta; + return now_int64.QuadPart; + } + return 0; +#elif GTEST_OS_WINDOWS && !GTEST_HAS_GETTIMEOFDAY_ + __timeb64 now; +#ifdef _MSC_VER + // MSVC 8 deprecates _ftime64(), so we want to suppress warning 4996 + // (deprecated function) there. + // TODO(kenton@google.com): Use GetTickCount()? Or use + // SystemTimeToFileTime() +#pragma warning(push) // Saves the current warning state. +#pragma warning(disable:4996) // Temporarily disables warning 4996. + _ftime64(&now); +#pragma warning(pop) // Restores the warning state. +#else + _ftime64(&now); +#endif // _MSC_VER + return static_cast(now.time) * 1000 + now.millitm; +#elif GTEST_HAS_GETTIMEOFDAY_ + struct timeval now; + gettimeofday(&now, NULL); + return static_cast(now.tv_sec) * 1000 + now.tv_usec / 1000; +#else +#error "Don't know how to get the current time on your system." +#endif +} + +// Utilities + +// class String + +// Returns the input enclosed in double quotes if it's not NULL; +// otherwise returns "(null)". For example, "\"Hello\"" is returned +// for input "Hello". +// +// This is useful for printing a C string in the syntax of a literal. +// +// Known issue: escape sequences are not handled yet. +String String::ShowCStringQuoted(const char* c_str) { + return c_str ? String::Format("\"%s\"", c_str) : String("(null)"); +} + +// Copies at most length characters from str into a newly-allocated +// piece of memory of size length+1. The memory is allocated with new[]. +// A terminating null byte is written to the memory, and a pointer to it +// is returned. If str is NULL, NULL is returned. +static char* CloneString(const char* str, size_t length) { + if (str == NULL) { + return NULL; + } else { + char* const clone = new char[length + 1]; + posix::StrNCpy(clone, str, length); + clone[length] = '\0'; + return clone; + } +} + +// Clones a 0-terminated C string, allocating memory using new. The +// caller is responsible for deleting[] the return value. Returns the +// cloned string, or NULL if the input is NULL. +const char * String::CloneCString(const char* c_str) { + return (c_str == NULL) ? + NULL : CloneString(c_str, strlen(c_str)); +} + +#if GTEST_OS_WINDOWS_MOBILE +// Creates a UTF-16 wide string from the given ANSI string, allocating +// memory using new. The caller is responsible for deleting the return +// value using delete[]. Returns the wide string, or NULL if the +// input is NULL. +LPCWSTR String::AnsiToUtf16(const char* ansi) { + if (!ansi) return NULL; + const int length = strlen(ansi); + const int unicode_length = + MultiByteToWideChar(CP_ACP, 0, ansi, length, + NULL, 0); + WCHAR* unicode = new WCHAR[unicode_length + 1]; + MultiByteToWideChar(CP_ACP, 0, ansi, length, + unicode, unicode_length); + unicode[unicode_length] = 0; + return unicode; +} + +// Creates an ANSI string from the given wide string, allocating +// memory using new. The caller is responsible for deleting the return +// value using delete[]. Returns the ANSI string, or NULL if the +// input is NULL. +const char* String::Utf16ToAnsi(LPCWSTR utf16_str) { + if (!utf16_str) return NULL; + const int ansi_length = + WideCharToMultiByte(CP_ACP, 0, utf16_str, -1, + NULL, 0, NULL, NULL); + char* ansi = new char[ansi_length + 1]; + WideCharToMultiByte(CP_ACP, 0, utf16_str, -1, + ansi, ansi_length, NULL, NULL); + ansi[ansi_length] = 0; + return ansi; +} + +#endif // GTEST_OS_WINDOWS_MOBILE + +// Compares two C strings. Returns true iff they have the same content. +// +// Unlike strcmp(), this function can handle NULL argument(s). A NULL +// C string is considered different to any non-NULL C string, +// including the empty string. +bool String::CStringEquals(const char * lhs, const char * rhs) { + if ( lhs == NULL ) return rhs == NULL; + + if ( rhs == NULL ) return false; + + return strcmp(lhs, rhs) == 0; +} + +#if GTEST_HAS_STD_WSTRING || GTEST_HAS_GLOBAL_WSTRING + +// Converts an array of wide chars to a narrow string using the UTF-8 +// encoding, and streams the result to the given Message object. +static void StreamWideCharsToMessage(const wchar_t* wstr, size_t length, + Message* msg) { + // TODO(wan): consider allowing a testing::String object to + // contain '\0'. This will make it behave more like std::string, + // and will allow ToUtf8String() to return the correct encoding + // for '\0' s.t. we can get rid of the conditional here (and in + // several other places). + for (size_t i = 0; i != length; ) { // NOLINT + if (wstr[i] != L'\0') { + *msg << WideStringToUtf8(wstr + i, static_cast(length - i)); + while (i != length && wstr[i] != L'\0') + i++; + } else { + *msg << '\0'; + i++; + } + } +} + +#endif // GTEST_HAS_STD_WSTRING || GTEST_HAS_GLOBAL_WSTRING + +} // namespace internal + +#if GTEST_HAS_STD_WSTRING +// Converts the given wide string to a narrow string using the UTF-8 +// encoding, and streams the result to this Message object. +Message& Message::operator <<(const ::std::wstring& wstr) { + internal::StreamWideCharsToMessage(wstr.c_str(), wstr.length(), this); + return *this; +} +#endif // GTEST_HAS_STD_WSTRING + +#if GTEST_HAS_GLOBAL_WSTRING +// Converts the given wide string to a narrow string using the UTF-8 +// encoding, and streams the result to this Message object. +Message& Message::operator <<(const ::wstring& wstr) { + internal::StreamWideCharsToMessage(wstr.c_str(), wstr.length(), this); + return *this; +} +#endif // GTEST_HAS_GLOBAL_WSTRING + +namespace internal { + +// Formats a value to be used in a failure message. + +// For a char value, we print it as a C++ char literal and as an +// unsigned integer (both in decimal and in hexadecimal). +String FormatForFailureMessage(char ch) { + const unsigned int ch_as_uint = ch; + // A String object cannot contain '\0', so we print "\\0" when ch is + // '\0'. + return String::Format("'%s' (%u, 0x%X)", + ch ? String::Format("%c", ch).c_str() : "\\0", + ch_as_uint, ch_as_uint); +} + +// For a wchar_t value, we print it as a C++ wchar_t literal and as an +// unsigned integer (both in decimal and in hexidecimal). +String FormatForFailureMessage(wchar_t wchar) { + // The C++ standard doesn't specify the exact size of the wchar_t + // type. It just says that it shall have the same size as another + // integral type, called its underlying type. + // + // Therefore, in order to print a wchar_t value in the numeric form, + // we first convert it to the largest integral type (UInt64) and + // then print the converted value. + // + // We use streaming to print the value as "%llu" doesn't work + // correctly with MSVC 7.1. + const UInt64 wchar_as_uint64 = wchar; + Message msg; + // A String object cannot contain '\0', so we print "\\0" when wchar is + // L'\0'. + char buffer[32]; // CodePointToUtf8 requires a buffer that big. + msg << "L'" + << (wchar ? CodePointToUtf8(static_cast(wchar), buffer) : "\\0") + << "' (" << wchar_as_uint64 << ", 0x" << ::std::setbase(16) + << wchar_as_uint64 << ")"; + return msg.GetString(); +} + +} // namespace internal + +// AssertionResult constructors. +// Used in EXPECT_TRUE/FALSE(assertion_result). +AssertionResult::AssertionResult(const AssertionResult& other) + : success_(other.success_), + message_(other.message_.get() != NULL ? + new internal::String(*other.message_) : + static_cast(NULL)) { +} + +// Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE. +AssertionResult AssertionResult::operator!() const { + AssertionResult negation(!success_); + if (message_.get() != NULL) + negation << *message_; + return negation; +} + +// Makes a successful assertion result. +AssertionResult AssertionSuccess() { + return AssertionResult(true); +} + +// Makes a failed assertion result. +AssertionResult AssertionFailure() { + return AssertionResult(false); +} + +// Makes a failed assertion result with the given failure message. +// Deprecated; use AssertionFailure() << message. +AssertionResult AssertionFailure(const Message& message) { + return AssertionFailure() << message; +} + +namespace internal { + +// Constructs and returns the message for an equality assertion +// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure. +// +// The first four parameters are the expressions used in the assertion +// and their values, as strings. For example, for ASSERT_EQ(foo, bar) +// where foo is 5 and bar is 6, we have: +// +// expected_expression: "foo" +// actual_expression: "bar" +// expected_value: "5" +// actual_value: "6" +// +// The ignoring_case parameter is true iff the assertion is a +// *_STRCASEEQ*. When it's true, the string " (ignoring case)" will +// be inserted into the message. +AssertionResult EqFailure(const char* expected_expression, + const char* actual_expression, + const String& expected_value, + const String& actual_value, + bool ignoring_case) { + Message msg; + msg << "Value of: " << actual_expression; + if (actual_value != actual_expression) { + msg << "\n Actual: " << actual_value; + } + + msg << "\nExpected: " << expected_expression; + if (ignoring_case) { + msg << " (ignoring case)"; + } + if (expected_value != expected_expression) { + msg << "\nWhich is: " << expected_value; + } + + return AssertionFailure(msg); +} + +// Constructs a failure message for Boolean assertions such as EXPECT_TRUE. +String GetBoolAssertionFailureMessage(const AssertionResult& assertion_result, + const char* expression_text, + const char* actual_predicate_value, + const char* expected_predicate_value) { + const char* actual_message = assertion_result.message(); + Message msg; + msg << "Value of: " << expression_text + << "\n Actual: " << actual_predicate_value; + if (actual_message[0] != '\0') + msg << " (" << actual_message << ")"; + msg << "\nExpected: " << expected_predicate_value; + return msg.GetString(); +} + +// Helper function for implementing ASSERT_NEAR. +AssertionResult DoubleNearPredFormat(const char* expr1, + const char* expr2, + const char* abs_error_expr, + double val1, + double val2, + double abs_error) { + const double diff = fabs(val1 - val2); + if (diff <= abs_error) return AssertionSuccess(); + + // TODO(wan): do not print the value of an expression if it's + // already a literal. + Message msg; + msg << "The difference between " << expr1 << " and " << expr2 + << " is " << diff << ", which exceeds " << abs_error_expr << ", where\n" + << expr1 << " evaluates to " << val1 << ",\n" + << expr2 << " evaluates to " << val2 << ", and\n" + << abs_error_expr << " evaluates to " << abs_error << "."; + return AssertionFailure(msg); +} + + +// Helper template for implementing FloatLE() and DoubleLE(). +template +AssertionResult FloatingPointLE(const char* expr1, + const char* expr2, + RawType val1, + RawType val2) { + // Returns success if val1 is less than val2, + if (val1 < val2) { + return AssertionSuccess(); + } + + // or if val1 is almost equal to val2. + const FloatingPoint lhs(val1), rhs(val2); + if (lhs.AlmostEquals(rhs)) { + return AssertionSuccess(); + } + + // Note that the above two checks will both fail if either val1 or + // val2 is NaN, as the IEEE floating-point standard requires that + // any predicate involving a NaN must return false. + + StrStream val1_ss; + val1_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << val1; + + StrStream val2_ss; + val2_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << val2; + + Message msg; + msg << "Expected: (" << expr1 << ") <= (" << expr2 << ")\n" + << " Actual: " << StrStreamToString(&val1_ss) << " vs " + << StrStreamToString(&val2_ss); + + return AssertionFailure(msg); +} + +} // namespace internal + +// Asserts that val1 is less than, or almost equal to, val2. Fails +// otherwise. In particular, it fails if either val1 or val2 is NaN. +AssertionResult FloatLE(const char* expr1, const char* expr2, + float val1, float val2) { + return internal::FloatingPointLE(expr1, expr2, val1, val2); +} + +// Asserts that val1 is less than, or almost equal to, val2. Fails +// otherwise. In particular, it fails if either val1 or val2 is NaN. +AssertionResult DoubleLE(const char* expr1, const char* expr2, + double val1, double val2) { + return internal::FloatingPointLE(expr1, expr2, val1, val2); +} + +namespace internal { + +// The helper function for {ASSERT|EXPECT}_EQ with int or enum +// arguments. +AssertionResult CmpHelperEQ(const char* expected_expression, + const char* actual_expression, + BiggestInt expected, + BiggestInt actual) { + if (expected == actual) { + return AssertionSuccess(); + } + + return EqFailure(expected_expression, + actual_expression, + FormatForComparisonFailureMessage(expected, actual), + FormatForComparisonFailureMessage(actual, expected), + false); +} + +// A macro for implementing the helper functions needed to implement +// ASSERT_?? and EXPECT_?? with integer or enum arguments. It is here +// just to avoid copy-and-paste of similar code. +#define GTEST_IMPL_CMP_HELPER_(op_name, op)\ +AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \ + BiggestInt val1, BiggestInt val2) {\ + if (val1 op val2) {\ + return AssertionSuccess();\ + } else {\ + Message msg;\ + msg << "Expected: (" << expr1 << ") " #op " (" << expr2\ + << "), actual: " << FormatForComparisonFailureMessage(val1, val2)\ + << " vs " << FormatForComparisonFailureMessage(val2, val1);\ + return AssertionFailure(msg);\ + }\ +} + +// Implements the helper function for {ASSERT|EXPECT}_NE with int or +// enum arguments. +GTEST_IMPL_CMP_HELPER_(NE, !=) +// Implements the helper function for {ASSERT|EXPECT}_LE with int or +// enum arguments. +GTEST_IMPL_CMP_HELPER_(LE, <=) +// Implements the helper function for {ASSERT|EXPECT}_LT with int or +// enum arguments. +GTEST_IMPL_CMP_HELPER_(LT, < ) +// Implements the helper function for {ASSERT|EXPECT}_GE with int or +// enum arguments. +GTEST_IMPL_CMP_HELPER_(GE, >=) +// Implements the helper function for {ASSERT|EXPECT}_GT with int or +// enum arguments. +GTEST_IMPL_CMP_HELPER_(GT, > ) + +#undef GTEST_IMPL_CMP_HELPER_ + +// The helper function for {ASSERT|EXPECT}_STREQ. +AssertionResult CmpHelperSTREQ(const char* expected_expression, + const char* actual_expression, + const char* expected, + const char* actual) { + if (String::CStringEquals(expected, actual)) { + return AssertionSuccess(); + } + + return EqFailure(expected_expression, + actual_expression, + String::ShowCStringQuoted(expected), + String::ShowCStringQuoted(actual), + false); +} + +// The helper function for {ASSERT|EXPECT}_STRCASEEQ. +AssertionResult CmpHelperSTRCASEEQ(const char* expected_expression, + const char* actual_expression, + const char* expected, + const char* actual) { + if (String::CaseInsensitiveCStringEquals(expected, actual)) { + return AssertionSuccess(); + } + + return EqFailure(expected_expression, + actual_expression, + String::ShowCStringQuoted(expected), + String::ShowCStringQuoted(actual), + true); +} + +// The helper function for {ASSERT|EXPECT}_STRNE. +AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2) { + if (!String::CStringEquals(s1, s2)) { + return AssertionSuccess(); + } else { + Message msg; + msg << "Expected: (" << s1_expression << ") != (" + << s2_expression << "), actual: \"" + << s1 << "\" vs \"" << s2 << "\""; + return AssertionFailure(msg); + } +} + +// The helper function for {ASSERT|EXPECT}_STRCASENE. +AssertionResult CmpHelperSTRCASENE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2) { + if (!String::CaseInsensitiveCStringEquals(s1, s2)) { + return AssertionSuccess(); + } else { + Message msg; + msg << "Expected: (" << s1_expression << ") != (" + << s2_expression << ") (ignoring case), actual: \"" + << s1 << "\" vs \"" << s2 << "\""; + return AssertionFailure(msg); + } +} + +} // namespace internal + +namespace { + +// Helper functions for implementing IsSubString() and IsNotSubstring(). + +// This group of overloaded functions return true iff needle is a +// substring of haystack. NULL is considered a substring of itself +// only. + +bool IsSubstringPred(const char* needle, const char* haystack) { + if (needle == NULL || haystack == NULL) + return needle == haystack; + + return strstr(haystack, needle) != NULL; +} + +bool IsSubstringPred(const wchar_t* needle, const wchar_t* haystack) { + if (needle == NULL || haystack == NULL) + return needle == haystack; + + return wcsstr(haystack, needle) != NULL; +} + +// StringType here can be either ::std::string or ::std::wstring. +template +bool IsSubstringPred(const StringType& needle, + const StringType& haystack) { + return haystack.find(needle) != StringType::npos; +} + +// This function implements either IsSubstring() or IsNotSubstring(), +// depending on the value of the expected_to_be_substring parameter. +// StringType here can be const char*, const wchar_t*, ::std::string, +// or ::std::wstring. +template +AssertionResult IsSubstringImpl( + bool expected_to_be_substring, + const char* needle_expr, const char* haystack_expr, + const StringType& needle, const StringType& haystack) { + if (IsSubstringPred(needle, haystack) == expected_to_be_substring) + return AssertionSuccess(); + + const bool is_wide_string = sizeof(needle[0]) > 1; + const char* const begin_string_quote = is_wide_string ? "L\"" : "\""; + return AssertionFailure( + Message() + << "Value of: " << needle_expr << "\n" + << " Actual: " << begin_string_quote << needle << "\"\n" + << "Expected: " << (expected_to_be_substring ? "" : "not ") + << "a substring of " << haystack_expr << "\n" + << "Which is: " << begin_string_quote << haystack << "\""); +} + +} // namespace + +// IsSubstring() and IsNotSubstring() check whether needle is a +// substring of haystack (NULL is considered a substring of itself +// only), and return an appropriate error message when they fail. + +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} + +#if GTEST_HAS_STD_WSTRING +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} +#endif // GTEST_HAS_STD_WSTRING + +namespace internal { + +#if GTEST_OS_WINDOWS + +namespace { + +// Helper function for IsHRESULT{SuccessFailure} predicates +AssertionResult HRESULTFailureHelper(const char* expr, + const char* expected, + long hr) { // NOLINT +#if GTEST_OS_WINDOWS_MOBILE + // Windows CE doesn't support FormatMessage. + const char error_text[] = ""; +#else + // Looks up the human-readable system message for the HRESULT code + // and since we're not passing any params to FormatMessage, we don't + // want inserts expanded. + const DWORD kFlags = FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS; + const DWORD kBufSize = 4096; // String::Format can't exceed this length. + // Gets the system's human readable message string for this HRESULT. + char error_text[kBufSize] = { '\0' }; + DWORD message_length = ::FormatMessageA(kFlags, + 0, // no source, we're asking system + hr, // the error + 0, // no line width restrictions + error_text, // output buffer + kBufSize, // buf size + NULL); // no arguments for inserts + // Trims tailing white space (FormatMessage leaves a trailing cr-lf) + for (; message_length && isspace(error_text[message_length - 1]); + --message_length) { + error_text[message_length - 1] = '\0'; + } +#endif // GTEST_OS_WINDOWS_MOBILE + + const String error_hex(String::Format("0x%08X ", hr)); + Message msg; + msg << "Expected: " << expr << " " << expected << ".\n" + << " Actual: " << error_hex << error_text << "\n"; + + return ::testing::AssertionFailure(msg); +} + +} // namespace + +AssertionResult IsHRESULTSuccess(const char* expr, long hr) { // NOLINT + if (SUCCEEDED(hr)) { + return AssertionSuccess(); + } + return HRESULTFailureHelper(expr, "succeeds", hr); +} + +AssertionResult IsHRESULTFailure(const char* expr, long hr) { // NOLINT + if (FAILED(hr)) { + return AssertionSuccess(); + } + return HRESULTFailureHelper(expr, "fails", hr); +} + +#endif // GTEST_OS_WINDOWS + +// Utility functions for encoding Unicode text (wide strings) in +// UTF-8. + +// A Unicode code-point can have upto 21 bits, and is encoded in UTF-8 +// like this: +// +// Code-point length Encoding +// 0 - 7 bits 0xxxxxxx +// 8 - 11 bits 110xxxxx 10xxxxxx +// 12 - 16 bits 1110xxxx 10xxxxxx 10xxxxxx +// 17 - 21 bits 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + +// The maximum code-point a one-byte UTF-8 sequence can represent. +const UInt32 kMaxCodePoint1 = (static_cast(1) << 7) - 1; + +// The maximum code-point a two-byte UTF-8 sequence can represent. +const UInt32 kMaxCodePoint2 = (static_cast(1) << (5 + 6)) - 1; + +// The maximum code-point a three-byte UTF-8 sequence can represent. +const UInt32 kMaxCodePoint3 = (static_cast(1) << (4 + 2*6)) - 1; + +// The maximum code-point a four-byte UTF-8 sequence can represent. +const UInt32 kMaxCodePoint4 = (static_cast(1) << (3 + 3*6)) - 1; + +// Chops off the n lowest bits from a bit pattern. Returns the n +// lowest bits. As a side effect, the original bit pattern will be +// shifted to the right by n bits. +inline UInt32 ChopLowBits(UInt32* bits, int n) { + const UInt32 low_bits = *bits & ((static_cast(1) << n) - 1); + *bits >>= n; + return low_bits; +} + +// Converts a Unicode code point to a narrow string in UTF-8 encoding. +// code_point parameter is of type UInt32 because wchar_t may not be +// wide enough to contain a code point. +// The output buffer str must containt at least 32 characters. +// The function returns the address of the output buffer. +// If the code_point is not a valid Unicode code point +// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be output +// as '(Invalid Unicode 0xXXXXXXXX)'. +char* CodePointToUtf8(UInt32 code_point, char* str) { + if (code_point <= kMaxCodePoint1) { + str[1] = '\0'; + str[0] = static_cast(code_point); // 0xxxxxxx + } else if (code_point <= kMaxCodePoint2) { + str[2] = '\0'; + str[1] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[0] = static_cast(0xC0 | code_point); // 110xxxxx + } else if (code_point <= kMaxCodePoint3) { + str[3] = '\0'; + str[2] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[1] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[0] = static_cast(0xE0 | code_point); // 1110xxxx + } else if (code_point <= kMaxCodePoint4) { + str[4] = '\0'; + str[3] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[2] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[1] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[0] = static_cast(0xF0 | code_point); // 11110xxx + } else { + // The longest string String::Format can produce when invoked + // with these parameters is 28 character long (not including + // the terminating nul character). We are asking for 32 character + // buffer just in case. This is also enough for strncpy to + // null-terminate the destination string. + posix::StrNCpy( + str, String::Format("(Invalid Unicode 0x%X)", code_point).c_str(), 32); + str[31] = '\0'; // Makes sure no change in the format to strncpy leaves + // the result unterminated. + } + return str; +} + +// The following two functions only make sense if the the system +// uses UTF-16 for wide string encoding. All supported systems +// with 16 bit wchar_t (Windows, Cygwin, Symbian OS) do use UTF-16. + +// Determines if the arguments constitute UTF-16 surrogate pair +// and thus should be combined into a single Unicode code point +// using CreateCodePointFromUtf16SurrogatePair. +inline bool IsUtf16SurrogatePair(wchar_t first, wchar_t second) { + return sizeof(wchar_t) == 2 && + (first & 0xFC00) == 0xD800 && (second & 0xFC00) == 0xDC00; +} + +// Creates a Unicode code point from UTF16 surrogate pair. +inline UInt32 CreateCodePointFromUtf16SurrogatePair(wchar_t first, + wchar_t second) { + const UInt32 mask = (1 << 10) - 1; + return (sizeof(wchar_t) == 2) ? + (((first & mask) << 10) | (second & mask)) + 0x10000 : + // This function should not be called when the condition is + // false, but we provide a sensible default in case it is. + static_cast(first); +} + +// Converts a wide string to a narrow string in UTF-8 encoding. +// The wide string is assumed to have the following encoding: +// UTF-16 if sizeof(wchar_t) == 2 (on Windows, Cygwin, Symbian OS) +// UTF-32 if sizeof(wchar_t) == 4 (on Linux) +// Parameter str points to a null-terminated wide string. +// Parameter num_chars may additionally limit the number +// of wchar_t characters processed. -1 is used when the entire string +// should be processed. +// If the string contains code points that are not valid Unicode code points +// (i.e. outside of Unicode range U+0 to U+10FFFF) they will be output +// as '(Invalid Unicode 0xXXXXXXXX)'. If the string is in UTF16 encoding +// and contains invalid UTF-16 surrogate pairs, values in those pairs +// will be encoded as individual Unicode characters from Basic Normal Plane. +String WideStringToUtf8(const wchar_t* str, int num_chars) { + if (num_chars == -1) + num_chars = static_cast(wcslen(str)); + + StrStream stream; + for (int i = 0; i < num_chars; ++i) { + UInt32 unicode_code_point; + + if (str[i] == L'\0') { + break; + } else if (i + 1 < num_chars && IsUtf16SurrogatePair(str[i], str[i + 1])) { + unicode_code_point = CreateCodePointFromUtf16SurrogatePair(str[i], + str[i + 1]); + i++; + } else { + unicode_code_point = static_cast(str[i]); + } + + char buffer[32]; // CodePointToUtf8 requires a buffer this big. + stream << CodePointToUtf8(unicode_code_point, buffer); + } + return StrStreamToString(&stream); +} + +// Converts a wide C string to a String using the UTF-8 encoding. +// NULL will be converted to "(null)". +String String::ShowWideCString(const wchar_t * wide_c_str) { + if (wide_c_str == NULL) return String("(null)"); + + return String(internal::WideStringToUtf8(wide_c_str, -1).c_str()); +} + +// Similar to ShowWideCString(), except that this function encloses +// the converted string in double quotes. +String String::ShowWideCStringQuoted(const wchar_t* wide_c_str) { + if (wide_c_str == NULL) return String("(null)"); + + return String::Format("L\"%s\"", + String::ShowWideCString(wide_c_str).c_str()); +} + +// Compares two wide C strings. Returns true iff they have the same +// content. +// +// Unlike wcscmp(), this function can handle NULL argument(s). A NULL +// C string is considered different to any non-NULL C string, +// including the empty string. +bool String::WideCStringEquals(const wchar_t * lhs, const wchar_t * rhs) { + if (lhs == NULL) return rhs == NULL; + + if (rhs == NULL) return false; + + return wcscmp(lhs, rhs) == 0; +} + +// Helper function for *_STREQ on wide strings. +AssertionResult CmpHelperSTREQ(const char* expected_expression, + const char* actual_expression, + const wchar_t* expected, + const wchar_t* actual) { + if (String::WideCStringEquals(expected, actual)) { + return AssertionSuccess(); + } + + return EqFailure(expected_expression, + actual_expression, + String::ShowWideCStringQuoted(expected), + String::ShowWideCStringQuoted(actual), + false); +} + +// Helper function for *_STRNE on wide strings. +AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const wchar_t* s1, + const wchar_t* s2) { + if (!String::WideCStringEquals(s1, s2)) { + return AssertionSuccess(); + } + + Message msg; + msg << "Expected: (" << s1_expression << ") != (" + << s2_expression << "), actual: " + << String::ShowWideCStringQuoted(s1) + << " vs " << String::ShowWideCStringQuoted(s2); + return AssertionFailure(msg); +} + +// Compares two C strings, ignoring case. Returns true iff they have +// the same content. +// +// Unlike strcasecmp(), this function can handle NULL argument(s). A +// NULL C string is considered different to any non-NULL C string, +// including the empty string. +bool String::CaseInsensitiveCStringEquals(const char * lhs, const char * rhs) { + if (lhs == NULL) + return rhs == NULL; + if (rhs == NULL) + return false; + return posix::StrCaseCmp(lhs, rhs) == 0; +} + + // Compares two wide C strings, ignoring case. Returns true iff they + // have the same content. + // + // Unlike wcscasecmp(), this function can handle NULL argument(s). + // A NULL C string is considered different to any non-NULL wide C string, + // including the empty string. + // NB: The implementations on different platforms slightly differ. + // On windows, this method uses _wcsicmp which compares according to LC_CTYPE + // environment variable. On GNU platform this method uses wcscasecmp + // which compares according to LC_CTYPE category of the current locale. + // On MacOS X, it uses towlower, which also uses LC_CTYPE category of the + // current locale. +bool String::CaseInsensitiveWideCStringEquals(const wchar_t* lhs, + const wchar_t* rhs) { + if ( lhs == NULL ) return rhs == NULL; + + if ( rhs == NULL ) return false; + +#if GTEST_OS_WINDOWS + return _wcsicmp(lhs, rhs) == 0; +#elif GTEST_OS_LINUX + return wcscasecmp(lhs, rhs) == 0; +#else + // Mac OS X and Cygwin don't define wcscasecmp. Other unknown OSes + // may not define it either. + wint_t left, right; + do { + left = towlower(*lhs++); + right = towlower(*rhs++); + } while (left && left == right); + return left == right; +#endif // OS selector +} + +// Compares this with another String. +// Returns < 0 if this is less than rhs, 0 if this is equal to rhs, or > 0 +// if this is greater than rhs. +int String::Compare(const String & rhs) const { + const char* const lhs_c_str = c_str(); + const char* const rhs_c_str = rhs.c_str(); + + if (lhs_c_str == NULL) { + return rhs_c_str == NULL ? 0 : -1; // NULL < anything except NULL + } else if (rhs_c_str == NULL) { + return 1; + } + + const size_t shorter_str_len = + length() <= rhs.length() ? length() : rhs.length(); + for (size_t i = 0; i != shorter_str_len; i++) { + if (lhs_c_str[i] < rhs_c_str[i]) { + return -1; + } else if (lhs_c_str[i] > rhs_c_str[i]) { + return 1; + } + } + return (length() < rhs.length()) ? -1 : + (length() > rhs.length()) ? 1 : 0; +} + +// Returns true iff this String ends with the given suffix. *Any* +// String is considered to end with a NULL or empty suffix. +bool String::EndsWith(const char* suffix) const { + if (suffix == NULL || CStringEquals(suffix, "")) return true; + + if (c_str() == NULL) return false; + + const size_t this_len = strlen(c_str()); + const size_t suffix_len = strlen(suffix); + return (this_len >= suffix_len) && + CStringEquals(c_str() + this_len - suffix_len, suffix); +} + +// Returns true iff this String ends with the given suffix, ignoring case. +// Any String is considered to end with a NULL or empty suffix. +bool String::EndsWithCaseInsensitive(const char* suffix) const { + if (suffix == NULL || CStringEquals(suffix, "")) return true; + + if (c_str() == NULL) return false; + + const size_t this_len = strlen(c_str()); + const size_t suffix_len = strlen(suffix); + return (this_len >= suffix_len) && + CaseInsensitiveCStringEquals(c_str() + this_len - suffix_len, suffix); +} + +// Formats a list of arguments to a String, using the same format +// spec string as for printf. +// +// We do not use the StringPrintf class as it is not universally +// available. +// +// The result is limited to 4096 characters (including the tailing 0). +// If 4096 characters are not enough to format the input, or if +// there's an error, "" is +// returned. +String String::Format(const char * format, ...) { + va_list args; + va_start(args, format); + + char buffer[4096]; + const int kBufferSize = sizeof(buffer)/sizeof(buffer[0]); + + // MSVC 8 deprecates vsnprintf(), so we want to suppress warning + // 4996 (deprecated function) there. +#ifdef _MSC_VER // We are using MSVC. +#pragma warning(push) // Saves the current warning state. +#pragma warning(disable:4996) // Temporarily disables warning 4996. + const int size = vsnprintf(buffer, kBufferSize, format, args); +#pragma warning(pop) // Restores the warning state. +#else // We are not using MSVC. + const int size = vsnprintf(buffer, kBufferSize, format, args); +#endif // _MSC_VER + va_end(args); + + // vsnprintf()'s behavior is not portable. When the buffer is not + // big enough, it returns a negative value in MSVC, and returns the + // needed buffer size on Linux. When there is an output error, it + // always returns a negative value. For simplicity, we lump the two + // error cases together. + if (size < 0 || size >= kBufferSize) { + return String(""); + } else { + return String(buffer, size); + } +} + +// Converts the buffer in a StrStream to a String, converting NUL +// bytes to "\\0" along the way. +String StrStreamToString(StrStream* ss) { + const ::std::string& str = ss->str(); + const char* const start = str.c_str(); + const char* const end = start + str.length(); + + // We need to use a helper StrStream to do this transformation + // because String doesn't support push_back(). + StrStream helper; + for (const char* ch = start; ch != end; ++ch) { + if (*ch == '\0') { + helper << "\\0"; // Replaces NUL with "\\0"; + } else { + helper.put(*ch); + } + } + + return String(helper.str().c_str()); +} + +// Appends the user-supplied message to the Google-Test-generated message. +String AppendUserMessage(const String& gtest_msg, + const Message& user_msg) { + // Appends the user message if it's non-empty. + const String user_msg_string = user_msg.GetString(); + if (user_msg_string.empty()) { + return gtest_msg; + } + + Message msg; + msg << gtest_msg << "\n" << user_msg_string; + + return msg.GetString(); +} + +} // namespace internal + +// class TestResult + +// Creates an empty TestResult. +TestResult::TestResult() + : death_test_count_(0), + elapsed_time_(0) { +} + +// D'tor. +TestResult::~TestResult() { +} + +// Returns the i-th test part result among all the results. i can +// range from 0 to total_part_count() - 1. If i is not in that range, +// aborts the program. +const TestPartResult& TestResult::GetTestPartResult(int i) const { + if (i < 0 || i >= total_part_count()) + internal::posix::Abort(); + return test_part_results_.at(i); +} + +// Returns the i-th test property. i can range from 0 to +// test_property_count() - 1. If i is not in that range, aborts the +// program. +const TestProperty& TestResult::GetTestProperty(int i) const { + if (i < 0 || i >= test_property_count()) + internal::posix::Abort(); + return test_properties_.at(i); +} + +// Clears the test part results. +void TestResult::ClearTestPartResults() { + test_part_results_.clear(); +} + +// Adds a test part result to the list. +void TestResult::AddTestPartResult(const TestPartResult& test_part_result) { + test_part_results_.push_back(test_part_result); +} + +// Adds a test property to the list. If a property with the same key as the +// supplied property is already represented, the value of this test_property +// replaces the old value for that key. +void TestResult::RecordProperty(const TestProperty& test_property) { + if (!ValidateTestProperty(test_property)) { + return; + } + internal::MutexLock lock(&test_properites_mutex_); + const std::vector::iterator property_with_matching_key = + std::find_if(test_properties_.begin(), test_properties_.end(), + internal::TestPropertyKeyIs(test_property.key())); + if (property_with_matching_key == test_properties_.end()) { + test_properties_.push_back(test_property); + return; + } + property_with_matching_key->SetValue(test_property.value()); +} + +// Adds a failure if the key is a reserved attribute of Google Test +// testcase tags. Returns true if the property is valid. +bool TestResult::ValidateTestProperty(const TestProperty& test_property) { + internal::String key(test_property.key()); + if (key == "name" || key == "status" || key == "time" || key == "classname") { + ADD_FAILURE() + << "Reserved key used in RecordProperty(): " + << key + << " ('name', 'status', 'time', and 'classname' are reserved by " + << GTEST_NAME_ << ")"; + return false; + } + return true; +} + +// Clears the object. +void TestResult::Clear() { + test_part_results_.clear(); + test_properties_.clear(); + death_test_count_ = 0; + elapsed_time_ = 0; +} + +// Returns true iff the test failed. +bool TestResult::Failed() const { + for (int i = 0; i < total_part_count(); ++i) { + if (GetTestPartResult(i).failed()) + return true; + } + return false; +} + +// Returns true iff the test part fatally failed. +static bool TestPartFatallyFailed(const TestPartResult& result) { + return result.fatally_failed(); +} + +// Returns true iff the test fatally failed. +bool TestResult::HasFatalFailure() const { + return CountIf(test_part_results_, TestPartFatallyFailed) > 0; +} + +// Returns true iff the test part non-fatally failed. +static bool TestPartNonfatallyFailed(const TestPartResult& result) { + return result.nonfatally_failed(); +} + +// Returns true iff the test has a non-fatal failure. +bool TestResult::HasNonfatalFailure() const { + return CountIf(test_part_results_, TestPartNonfatallyFailed) > 0; +} + +// Gets the number of all test parts. This is the sum of the number +// of successful test parts and the number of failed test parts. +int TestResult::total_part_count() const { + return static_cast(test_part_results_.size()); +} + +// Returns the number of the test properties. +int TestResult::test_property_count() const { + return static_cast(test_properties_.size()); +} + +// class Test + +// Creates a Test object. + +// The c'tor saves the values of all Google Test flags. +Test::Test() + : gtest_flag_saver_(new internal::GTestFlagSaver) { +} + +// The d'tor restores the values of all Google Test flags. +Test::~Test() { + delete gtest_flag_saver_; +} + +// Sets up the test fixture. +// +// A sub-class may override this. +void Test::SetUp() { +} + +// Tears down the test fixture. +// +// A sub-class may override this. +void Test::TearDown() { +} + +// Allows user supplied key value pairs to be recorded for later output. +void Test::RecordProperty(const char* key, const char* value) { + UnitTest::GetInstance()->RecordPropertyForCurrentTest(key, value); +} + +// Allows user supplied key value pairs to be recorded for later output. +void Test::RecordProperty(const char* key, int value) { + Message value_message; + value_message << value; + RecordProperty(key, value_message.GetString().c_str()); +} + +namespace internal { + +void ReportFailureInUnknownLocation(TestPartResult::Type result_type, + const String& message) { + // This function is a friend of UnitTest and as such has access to + // AddTestPartResult. + UnitTest::GetInstance()->AddTestPartResult( + result_type, + NULL, // No info about the source file where the exception occurred. + -1, // We have no info on which line caused the exception. + message, + String()); // No stack trace, either. +} + +} // namespace internal + +#if GTEST_OS_WINDOWS +// We are on Windows. + +// Adds an "exception thrown" fatal failure to the current test. +static void AddExceptionThrownFailure(DWORD exception_code, + const char* location) { + Message message; + message << "Exception thrown with code 0x" << std::setbase(16) << + exception_code << std::setbase(10) << " in " << location << "."; + + internal::ReportFailureInUnknownLocation(TestPartResult::kFatalFailure, + message.GetString()); +} + +#endif // GTEST_OS_WINDOWS + +// Google Test requires all tests in the same test case to use the same test +// fixture class. This function checks if the current test has the +// same fixture class as the first test in the current test case. If +// yes, it returns true; otherwise it generates a Google Test failure and +// returns false. +bool Test::HasSameFixtureClass() { + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + const TestCase* const test_case = impl->current_test_case(); + + // Info about the first test in the current test case. + const internal::TestInfoImpl* const first_test_info = + test_case->test_info_list()[0]->impl(); + const internal::TypeId first_fixture_id = first_test_info->fixture_class_id(); + const char* const first_test_name = first_test_info->name(); + + // Info about the current test. + const internal::TestInfoImpl* const this_test_info = + impl->current_test_info()->impl(); + const internal::TypeId this_fixture_id = this_test_info->fixture_class_id(); + const char* const this_test_name = this_test_info->name(); + + if (this_fixture_id != first_fixture_id) { + // Is the first test defined using TEST? + const bool first_is_TEST = first_fixture_id == internal::GetTestTypeId(); + // Is this test defined using TEST? + const bool this_is_TEST = this_fixture_id == internal::GetTestTypeId(); + + if (first_is_TEST || this_is_TEST) { + // The user mixed TEST and TEST_F in this test case - we'll tell + // him/her how to fix it. + + // Gets the name of the TEST and the name of the TEST_F. Note + // that first_is_TEST and this_is_TEST cannot both be true, as + // the fixture IDs are different for the two tests. + const char* const TEST_name = + first_is_TEST ? first_test_name : this_test_name; + const char* const TEST_F_name = + first_is_TEST ? this_test_name : first_test_name; + + ADD_FAILURE() + << "All tests in the same test case must use the same test fixture\n" + << "class, so mixing TEST_F and TEST in the same test case is\n" + << "illegal. In test case " << this_test_info->test_case_name() + << ",\n" + << "test " << TEST_F_name << " is defined using TEST_F but\n" + << "test " << TEST_name << " is defined using TEST. You probably\n" + << "want to change the TEST to TEST_F or move it to another test\n" + << "case."; + } else { + // The user defined two fixture classes with the same name in + // two namespaces - we'll tell him/her how to fix it. + ADD_FAILURE() + << "All tests in the same test case must use the same test fixture\n" + << "class. However, in test case " + << this_test_info->test_case_name() << ",\n" + << "you defined test " << first_test_name + << " and test " << this_test_name << "\n" + << "using two different test fixture classes. This can happen if\n" + << "the two classes are from different namespaces or translation\n" + << "units and have the same name. You should probably rename one\n" + << "of the classes to put the tests into different test cases."; + } + return false; + } + + return true; +} + +// Runs the test and updates the test result. +void Test::Run() { + if (!HasSameFixtureClass()) return; + + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); +#if GTEST_HAS_SEH + // Catch SEH-style exceptions. + impl->os_stack_trace_getter()->UponLeavingGTest(); + __try { + SetUp(); + } __except(internal::UnitTestOptions::GTestShouldProcessSEH( + GetExceptionCode())) { + AddExceptionThrownFailure(GetExceptionCode(), "SetUp()"); + } + + // We will run the test only if SetUp() had no fatal failure. + if (!HasFatalFailure()) { + impl->os_stack_trace_getter()->UponLeavingGTest(); + __try { + TestBody(); + } __except(internal::UnitTestOptions::GTestShouldProcessSEH( + GetExceptionCode())) { + AddExceptionThrownFailure(GetExceptionCode(), "the test body"); + } + } + + // However, we want to clean up as much as possible. Hence we will + // always call TearDown(), even if SetUp() or the test body has + // failed. + impl->os_stack_trace_getter()->UponLeavingGTest(); + __try { + TearDown(); + } __except(internal::UnitTestOptions::GTestShouldProcessSEH( + GetExceptionCode())) { + AddExceptionThrownFailure(GetExceptionCode(), "TearDown()"); + } + +#else // We are on a compiler or platform that doesn't support SEH. + impl->os_stack_trace_getter()->UponLeavingGTest(); + SetUp(); + + // We will run the test only if SetUp() was successful. + if (!HasFatalFailure()) { + impl->os_stack_trace_getter()->UponLeavingGTest(); + TestBody(); + } + + // However, we want to clean up as much as possible. Hence we will + // always call TearDown(), even if SetUp() or the test body has + // failed. + impl->os_stack_trace_getter()->UponLeavingGTest(); + TearDown(); +#endif // GTEST_HAS_SEH +} + + +// Returns true iff the current test has a fatal failure. +bool Test::HasFatalFailure() { + return internal::GetUnitTestImpl()->current_test_result()->HasFatalFailure(); +} + +// Returns true iff the current test has a non-fatal failure. +bool Test::HasNonfatalFailure() { + return internal::GetUnitTestImpl()->current_test_result()-> + HasNonfatalFailure(); +} + +// class TestInfo + +// Constructs a TestInfo object. It assumes ownership of the test factory +// object via impl_. +TestInfo::TestInfo(const char* a_test_case_name, + const char* a_name, + const char* a_test_case_comment, + const char* a_comment, + internal::TypeId fixture_class_id, + internal::TestFactoryBase* factory) { + impl_ = new internal::TestInfoImpl(this, a_test_case_name, a_name, + a_test_case_comment, a_comment, + fixture_class_id, factory); +} + +// Destructs a TestInfo object. +TestInfo::~TestInfo() { + delete impl_; +} + +namespace internal { + +// Creates a new TestInfo object and registers it with Google Test; +// returns the created object. +// +// Arguments: +// +// test_case_name: name of the test case +// name: name of the test +// test_case_comment: a comment on the test case that will be included in +// the test output +// comment: a comment on the test that will be included in the +// test output +// fixture_class_id: ID of the test fixture class +// set_up_tc: pointer to the function that sets up the test case +// tear_down_tc: pointer to the function that tears down the test case +// factory: pointer to the factory that creates a test object. +// The newly created TestInfo instance will assume +// ownership of the factory object. +TestInfo* MakeAndRegisterTestInfo( + const char* test_case_name, const char* name, + const char* test_case_comment, const char* comment, + TypeId fixture_class_id, + SetUpTestCaseFunc set_up_tc, + TearDownTestCaseFunc tear_down_tc, + TestFactoryBase* factory) { + TestInfo* const test_info = + new TestInfo(test_case_name, name, test_case_comment, comment, + fixture_class_id, factory); + GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info); + return test_info; +} + +#if GTEST_HAS_PARAM_TEST +void ReportInvalidTestCaseType(const char* test_case_name, + const char* file, int line) { + Message errors; + errors + << "Attempted redefinition of test case " << test_case_name << ".\n" + << "All tests in the same test case must use the same test fixture\n" + << "class. However, in test case " << test_case_name << ", you tried\n" + << "to define a test using a fixture class different from the one\n" + << "used earlier. This can happen if the two fixture classes are\n" + << "from different namespaces and have the same name. You should\n" + << "probably rename one of the classes to put the tests into different\n" + << "test cases."; + + fprintf(stderr, "%s %s", FormatFileLocation(file, line).c_str(), + errors.GetString().c_str()); +} +#endif // GTEST_HAS_PARAM_TEST + +} // namespace internal + +// Returns the test case name. +const char* TestInfo::test_case_name() const { + return impl_->test_case_name(); +} + +// Returns the test name. +const char* TestInfo::name() const { + return impl_->name(); +} + +// Returns the test case comment. +const char* TestInfo::test_case_comment() const { + return impl_->test_case_comment(); +} + +// Returns the test comment. +const char* TestInfo::comment() const { + return impl_->comment(); +} + +// Returns true if this test should run. +bool TestInfo::should_run() const { return impl_->should_run(); } + +// Returns true if this test matches the user-specified filter. +bool TestInfo::matches_filter() const { return impl_->matches_filter(); } + +// Returns the result of the test. +const TestResult* TestInfo::result() const { return impl_->result(); } + +// Increments the number of death tests encountered in this test so +// far. +int TestInfo::increment_death_test_count() { + return impl_->result()->increment_death_test_count(); +} + +namespace { + +// A predicate that checks the test name of a TestInfo against a known +// value. +// +// This is used for implementation of the TestCase class only. We put +// it in the anonymous namespace to prevent polluting the outer +// namespace. +// +// TestNameIs is copyable. +class TestNameIs { + public: + // Constructor. + // + // TestNameIs has NO default constructor. + explicit TestNameIs(const char* name) + : name_(name) {} + + // Returns true iff the test name of test_info matches name_. + bool operator()(const TestInfo * test_info) const { + return test_info && internal::String(test_info->name()).Compare(name_) == 0; + } + + private: + internal::String name_; +}; + +} // namespace + +namespace internal { + +// This method expands all parameterized tests registered with macros TEST_P +// and INSTANTIATE_TEST_CASE_P into regular tests and registers those. +// This will be done just once during the program runtime. +void UnitTestImpl::RegisterParameterizedTests() { +#if GTEST_HAS_PARAM_TEST + if (!parameterized_tests_registered_) { + parameterized_test_registry_.RegisterTests(); + parameterized_tests_registered_ = true; + } +#endif +} + +// Creates the test object, runs it, records its result, and then +// deletes it. +void TestInfoImpl::Run() { + if (!should_run_) return; + + // Tells UnitTest where to store test result. + UnitTestImpl* const impl = internal::GetUnitTestImpl(); + impl->set_current_test_info(parent_); + + TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater(); + + // Notifies the unit test event listeners that a test is about to start. + repeater->OnTestStart(*parent_); + + const TimeInMillis start = GetTimeInMillis(); + + impl->os_stack_trace_getter()->UponLeavingGTest(); +#if GTEST_HAS_SEH + // Catch SEH-style exceptions. + Test* test = NULL; + + __try { + // Creates the test object. + test = factory_->CreateTest(); + } __except(internal::UnitTestOptions::GTestShouldProcessSEH( + GetExceptionCode())) { + AddExceptionThrownFailure(GetExceptionCode(), + "the test fixture's constructor"); + return; + } +#else // We are on a compiler or platform that doesn't support SEH. + + // TODO(wan): If test->Run() throws, test won't be deleted. This is + // not a problem now as we don't use exceptions. If we were to + // enable exceptions, we should revise the following to be + // exception-safe. + + // Creates the test object. + Test* test = factory_->CreateTest(); +#endif // GTEST_HAS_SEH + + // Runs the test only if the constructor of the test fixture didn't + // generate a fatal failure. + if (!Test::HasFatalFailure()) { + test->Run(); + } + + // Deletes the test object. + impl->os_stack_trace_getter()->UponLeavingGTest(); + delete test; + test = NULL; + + result_.set_elapsed_time(GetTimeInMillis() - start); + + // Notifies the unit test event listener that a test has just finished. + repeater->OnTestEnd(*parent_); + + // Tells UnitTest to stop associating assertion results to this + // test. + impl->set_current_test_info(NULL); +} + +} // namespace internal + +// class TestCase + +// Gets the number of successful tests in this test case. +int TestCase::successful_test_count() const { + return CountIf(test_info_list_, TestPassed); +} + +// Gets the number of failed tests in this test case. +int TestCase::failed_test_count() const { + return CountIf(test_info_list_, TestFailed); +} + +int TestCase::disabled_test_count() const { + return CountIf(test_info_list_, TestDisabled); +} + +// Get the number of tests in this test case that should run. +int TestCase::test_to_run_count() const { + return CountIf(test_info_list_, ShouldRunTest); +} + +// Gets the number of all tests. +int TestCase::total_test_count() const { + return static_cast(test_info_list_.size()); +} + +// Creates a TestCase with the given name. +// +// Arguments: +// +// name: name of the test case +// set_up_tc: pointer to the function that sets up the test case +// tear_down_tc: pointer to the function that tears down the test case +TestCase::TestCase(const char* a_name, const char* a_comment, + Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc) + : name_(a_name), + comment_(a_comment), + set_up_tc_(set_up_tc), + tear_down_tc_(tear_down_tc), + should_run_(false), + elapsed_time_(0) { +} + +// Destructor of TestCase. +TestCase::~TestCase() { + // Deletes every Test in the collection. + ForEach(test_info_list_, internal::Delete); +} + +// Returns the i-th test among all the tests. i can range from 0 to +// total_test_count() - 1. If i is not in that range, returns NULL. +const TestInfo* TestCase::GetTestInfo(int i) const { + const int index = GetElementOr(test_indices_, i, -1); + return index < 0 ? NULL : test_info_list_[index]; +} + +// Returns the i-th test among all the tests. i can range from 0 to +// total_test_count() - 1. If i is not in that range, returns NULL. +TestInfo* TestCase::GetMutableTestInfo(int i) { + const int index = GetElementOr(test_indices_, i, -1); + return index < 0 ? NULL : test_info_list_[index]; +} + +// Adds a test to this test case. Will delete the test upon +// destruction of the TestCase object. +void TestCase::AddTestInfo(TestInfo * test_info) { + test_info_list_.push_back(test_info); + test_indices_.push_back(static_cast(test_indices_.size())); +} + +// Runs every test in this TestCase. +void TestCase::Run() { + if (!should_run_) return; + + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + impl->set_current_test_case(this); + + TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater(); + + repeater->OnTestCaseStart(*this); + impl->os_stack_trace_getter()->UponLeavingGTest(); + set_up_tc_(); + + const internal::TimeInMillis start = internal::GetTimeInMillis(); + for (int i = 0; i < total_test_count(); i++) { + GetMutableTestInfo(i)->impl()->Run(); + } + elapsed_time_ = internal::GetTimeInMillis() - start; + + impl->os_stack_trace_getter()->UponLeavingGTest(); + tear_down_tc_(); + repeater->OnTestCaseEnd(*this); + impl->set_current_test_case(NULL); +} + +// Clears the results of all tests in this test case. +void TestCase::ClearResult() { + ForEach(test_info_list_, internal::TestInfoImpl::ClearTestResult); +} + +// Returns true iff test passed. +bool TestCase::TestPassed(const TestInfo * test_info) { + const internal::TestInfoImpl* const impl = test_info->impl(); + return impl->should_run() && impl->result()->Passed(); +} + +// Returns true iff test failed. +bool TestCase::TestFailed(const TestInfo * test_info) { + const internal::TestInfoImpl* const impl = test_info->impl(); + return impl->should_run() && impl->result()->Failed(); +} + +// Returns true iff test is disabled. +bool TestCase::TestDisabled(const TestInfo * test_info) { + return test_info->impl()->is_disabled(); +} + +// Returns true if the given test should run. +bool TestCase::ShouldRunTest(const TestInfo *test_info) { + return test_info->impl()->should_run(); +} + +// Shuffles the tests in this test case. +void TestCase::ShuffleTests(internal::Random* random) { + Shuffle(random, &test_indices_); +} + +// Restores the test order to before the first shuffle. +void TestCase::UnshuffleTests() { + for (size_t i = 0; i < test_indices_.size(); i++) { + test_indices_[i] = static_cast(i); + } +} + +// Formats a countable noun. Depending on its quantity, either the +// singular form or the plural form is used. e.g. +// +// FormatCountableNoun(1, "formula", "formuli") returns "1 formula". +// FormatCountableNoun(5, "book", "books") returns "5 books". +static internal::String FormatCountableNoun(int count, + const char * singular_form, + const char * plural_form) { + return internal::String::Format("%d %s", count, + count == 1 ? singular_form : plural_form); +} + +// Formats the count of tests. +static internal::String FormatTestCount(int test_count) { + return FormatCountableNoun(test_count, "test", "tests"); +} + +// Formats the count of test cases. +static internal::String FormatTestCaseCount(int test_case_count) { + return FormatCountableNoun(test_case_count, "test case", "test cases"); +} + +// Converts a TestPartResult::Type enum to human-friendly string +// representation. Both kNonFatalFailure and kFatalFailure are translated +// to "Failure", as the user usually doesn't care about the difference +// between the two when viewing the test result. +static const char * TestPartResultTypeToString(TestPartResult::Type type) { + switch (type) { + case TestPartResult::kSuccess: + return "Success"; + + case TestPartResult::kNonFatalFailure: + case TestPartResult::kFatalFailure: +#ifdef _MSC_VER + return "error: "; +#else + return "Failure\n"; +#endif + } + + return "Unknown result type"; +} + +// Prints a TestPartResult to a String. +static internal::String PrintTestPartResultToString( + const TestPartResult& test_part_result) { + return (Message() + << internal::FormatFileLocation(test_part_result.file_name(), + test_part_result.line_number()) + << " " << TestPartResultTypeToString(test_part_result.type()) + << test_part_result.message()).GetString(); +} + +// Prints a TestPartResult. +static void PrintTestPartResult(const TestPartResult& test_part_result) { + const internal::String& result = + PrintTestPartResultToString(test_part_result); + printf("%s\n", result.c_str()); + fflush(stdout); + // If the test program runs in Visual Studio or a debugger, the + // following statements add the test part result message to the Output + // window such that the user can double-click on it to jump to the + // corresponding source code location; otherwise they do nothing. +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + // We don't call OutputDebugString*() on Windows Mobile, as printing + // to stdout is done by OutputDebugString() there already - we don't + // want the same message printed twice. + ::OutputDebugStringA(result.c_str()); + ::OutputDebugStringA("\n"); +#endif +} + +// class PrettyUnitTestResultPrinter + +namespace internal { + +enum GTestColor { + COLOR_DEFAULT, + COLOR_RED, + COLOR_GREEN, + COLOR_YELLOW +}; + +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + +// Returns the character attribute for the given color. +WORD GetColorAttribute(GTestColor color) { + switch (color) { + case COLOR_RED: return FOREGROUND_RED; + case COLOR_GREEN: return FOREGROUND_GREEN; + case COLOR_YELLOW: return FOREGROUND_RED | FOREGROUND_GREEN; + default: return 0; + } +} + +#else + +// Returns the ANSI color code for the given color. COLOR_DEFAULT is +// an invalid input. +const char* GetAnsiColorCode(GTestColor color) { + switch (color) { + case COLOR_RED: return "1"; + case COLOR_GREEN: return "2"; + case COLOR_YELLOW: return "3"; + default: return NULL; + }; +} + +#endif // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + +// Returns true iff Google Test should use colors in the output. +bool ShouldUseColor(bool stdout_is_tty) { + const char* const gtest_color = GTEST_FLAG(color).c_str(); + + if (String::CaseInsensitiveCStringEquals(gtest_color, "auto")) { +#if GTEST_OS_WINDOWS + // On Windows the TERM variable is usually not set, but the + // console there does support colors. + return stdout_is_tty; +#else + // On non-Windows platforms, we rely on the TERM variable. + const char* const term = posix::GetEnv("TERM"); + const bool term_supports_color = + String::CStringEquals(term, "xterm") || + String::CStringEquals(term, "xterm-color") || + String::CStringEquals(term, "xterm-256color") || + String::CStringEquals(term, "linux") || + String::CStringEquals(term, "cygwin"); + return stdout_is_tty && term_supports_color; +#endif // GTEST_OS_WINDOWS + } + + return String::CaseInsensitiveCStringEquals(gtest_color, "yes") || + String::CaseInsensitiveCStringEquals(gtest_color, "true") || + String::CaseInsensitiveCStringEquals(gtest_color, "t") || + String::CStringEquals(gtest_color, "1"); + // We take "yes", "true", "t", and "1" as meaning "yes". If the + // value is neither one of these nor "auto", we treat it as "no" to + // be conservative. +} + +// Helpers for printing colored strings to stdout. Note that on Windows, we +// cannot simply emit special characters and have the terminal change colors. +// This routine must actually emit the characters rather than return a string +// that would be colored when printed, as can be done on Linux. +void ColoredPrintf(GTestColor color, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + +#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_SYMBIAN || GTEST_OS_ZOS + const bool use_color = false; +#else + static const bool in_color_mode = + ShouldUseColor(posix::IsATTY(posix::FileNo(stdout)) != 0); + const bool use_color = in_color_mode && (color != COLOR_DEFAULT); +#endif // GTEST_OS_WINDOWS_MOBILE || GTEST_OS_SYMBIAN || GTEST_OS_ZOS + // The '!= 0' comparison is necessary to satisfy MSVC 7.1. + + if (!use_color) { + vprintf(fmt, args); + va_end(args); + return; + } + +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + const HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); + + // Gets the current text color. + CONSOLE_SCREEN_BUFFER_INFO buffer_info; + GetConsoleScreenBufferInfo(stdout_handle, &buffer_info); + const WORD old_color_attrs = buffer_info.wAttributes; + + // We need to flush the stream buffers into the console before each + // SetConsoleTextAttribute call lest it affect the text that is already + // printed but has not yet reached the console. + fflush(stdout); + SetConsoleTextAttribute(stdout_handle, + GetColorAttribute(color) | FOREGROUND_INTENSITY); + vprintf(fmt, args); + + fflush(stdout); + // Restores the text color. + SetConsoleTextAttribute(stdout_handle, old_color_attrs); +#else + printf("\033[0;3%sm", GetAnsiColorCode(color)); + vprintf(fmt, args); + printf("\033[m"); // Resets the terminal to default. +#endif // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + va_end(args); +} + +// This class implements the TestEventListener interface. +// +// Class PrettyUnitTestResultPrinter is copyable. +class PrettyUnitTestResultPrinter : public TestEventListener { + public: + PrettyUnitTestResultPrinter() {} + static void PrintTestName(const char * test_case, const char * test) { + printf("%s.%s", test_case, test); + } + + // The following methods override what's in the TestEventListener class. + virtual void OnTestProgramStart(const UnitTest& /*unit_test*/) {} + virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration); + virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test); + virtual void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) {} + virtual void OnTestCaseStart(const TestCase& test_case); + virtual void OnTestStart(const TestInfo& test_info); + virtual void OnTestPartResult(const TestPartResult& result); + virtual void OnTestEnd(const TestInfo& test_info); + virtual void OnTestCaseEnd(const TestCase& test_case); + virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test); + virtual void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) {} + virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration); + virtual void OnTestProgramEnd(const UnitTest& /*unit_test*/) {} + + private: + static void PrintFailedTests(const UnitTest& unit_test); + + internal::String test_case_name_; +}; + + // Fired before each iteration of tests starts. +void PrettyUnitTestResultPrinter::OnTestIterationStart( + const UnitTest& unit_test, int iteration) { + if (GTEST_FLAG(repeat) != 1) + printf("\nRepeating all tests (iteration %d) . . .\n\n", iteration + 1); + + const char* const filter = GTEST_FLAG(filter).c_str(); + + // Prints the filter if it's not *. This reminds the user that some + // tests may be skipped. + if (!internal::String::CStringEquals(filter, kUniversalFilter)) { + ColoredPrintf(COLOR_YELLOW, + "Note: %s filter = %s\n", GTEST_NAME_, filter); + } + + if (internal::ShouldShard(kTestTotalShards, kTestShardIndex, false)) { + ColoredPrintf(COLOR_YELLOW, + "Note: This is test shard %s of %s.\n", + internal::posix::GetEnv(kTestShardIndex), + internal::posix::GetEnv(kTestTotalShards)); + } + + if (GTEST_FLAG(shuffle)) { + ColoredPrintf(COLOR_YELLOW, + "Note: Randomizing tests' orders with a seed of %d .\n", + unit_test.random_seed()); + } + + ColoredPrintf(COLOR_GREEN, "[==========] "); + printf("Running %s from %s.\n", + FormatTestCount(unit_test.test_to_run_count()).c_str(), + FormatTestCaseCount(unit_test.test_case_to_run_count()).c_str()); + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnEnvironmentsSetUpStart( + const UnitTest& /*unit_test*/) { + ColoredPrintf(COLOR_GREEN, "[----------] "); + printf("Global test environment set-up.\n"); + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnTestCaseStart(const TestCase& test_case) { + test_case_name_ = test_case.name(); + const internal::String counts = + FormatCountableNoun(test_case.test_to_run_count(), "test", "tests"); + ColoredPrintf(COLOR_GREEN, "[----------] "); + printf("%s from %s", counts.c_str(), test_case_name_.c_str()); + if (test_case.comment()[0] == '\0') { + printf("\n"); + } else { + printf(", where %s\n", test_case.comment()); + } + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnTestStart(const TestInfo& test_info) { + ColoredPrintf(COLOR_GREEN, "[ RUN ] "); + PrintTestName(test_case_name_.c_str(), test_info.name()); + if (test_info.comment()[0] == '\0') { + printf("\n"); + } else { + printf(", where %s\n", test_info.comment()); + } + fflush(stdout); +} + +// Called after an assertion failure. +void PrettyUnitTestResultPrinter::OnTestPartResult( + const TestPartResult& result) { + // If the test part succeeded, we don't need to do anything. + if (result.type() == TestPartResult::kSuccess) + return; + + // Print failure message from the assertion (e.g. expected this and got that). + PrintTestPartResult(result); + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnTestEnd(const TestInfo& test_info) { + if (test_info.result()->Passed()) { + ColoredPrintf(COLOR_GREEN, "[ OK ] "); + } else { + ColoredPrintf(COLOR_RED, "[ FAILED ] "); + } + PrintTestName(test_case_name_.c_str(), test_info.name()); + if (GTEST_FLAG(print_time)) { + printf(" (%s ms)\n", internal::StreamableToString( + test_info.result()->elapsed_time()).c_str()); + } else { + printf("\n"); + } + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnTestCaseEnd(const TestCase& test_case) { + if (!GTEST_FLAG(print_time)) return; + + test_case_name_ = test_case.name(); + const internal::String counts = + FormatCountableNoun(test_case.test_to_run_count(), "test", "tests"); + ColoredPrintf(COLOR_GREEN, "[----------] "); + printf("%s from %s (%s ms total)\n\n", + counts.c_str(), test_case_name_.c_str(), + internal::StreamableToString(test_case.elapsed_time()).c_str()); + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnEnvironmentsTearDownStart( + const UnitTest& /*unit_test*/) { + ColoredPrintf(COLOR_GREEN, "[----------] "); + printf("Global test environment tear-down\n"); + fflush(stdout); +} + +// Internal helper for printing the list of failed tests. +void PrettyUnitTestResultPrinter::PrintFailedTests(const UnitTest& unit_test) { + const int failed_test_count = unit_test.failed_test_count(); + if (failed_test_count == 0) { + return; + } + + for (int i = 0; i < unit_test.total_test_case_count(); ++i) { + const TestCase& test_case = *unit_test.GetTestCase(i); + if (!test_case.should_run() || (test_case.failed_test_count() == 0)) { + continue; + } + for (int j = 0; j < test_case.total_test_count(); ++j) { + const TestInfo& test_info = *test_case.GetTestInfo(j); + if (!test_info.should_run() || test_info.result()->Passed()) { + continue; + } + ColoredPrintf(COLOR_RED, "[ FAILED ] "); + printf("%s.%s", test_case.name(), test_info.name()); + if (test_case.comment()[0] != '\0' || + test_info.comment()[0] != '\0') { + printf(", where %s", test_case.comment()); + if (test_case.comment()[0] != '\0' && + test_info.comment()[0] != '\0') { + printf(" and "); + } + } + printf("%s\n", test_info.comment()); + } + } +} + + void PrettyUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, + int /*iteration*/) { + ColoredPrintf(COLOR_GREEN, "[==========] "); + printf("%s from %s ran.", + FormatTestCount(unit_test.test_to_run_count()).c_str(), + FormatTestCaseCount(unit_test.test_case_to_run_count()).c_str()); + if (GTEST_FLAG(print_time)) { + printf(" (%s ms total)", + internal::StreamableToString(unit_test.elapsed_time()).c_str()); + } + printf("\n"); + ColoredPrintf(COLOR_GREEN, "[ PASSED ] "); + printf("%s.\n", FormatTestCount(unit_test.successful_test_count()).c_str()); + + int num_failures = unit_test.failed_test_count(); + if (!unit_test.Passed()) { + const int failed_test_count = unit_test.failed_test_count(); + ColoredPrintf(COLOR_RED, "[ FAILED ] "); + printf("%s, listed below:\n", FormatTestCount(failed_test_count).c_str()); + PrintFailedTests(unit_test); + printf("\n%2d FAILED %s\n", num_failures, + num_failures == 1 ? "TEST" : "TESTS"); + } + + int num_disabled = unit_test.disabled_test_count(); + if (num_disabled && !GTEST_FLAG(also_run_disabled_tests)) { + if (!num_failures) { + printf("\n"); // Add a spacer if no FAILURE banner is displayed. + } + ColoredPrintf(COLOR_YELLOW, + " YOU HAVE %d DISABLED %s\n\n", + num_disabled, + num_disabled == 1 ? "TEST" : "TESTS"); + } + // Ensure that Google Test output is printed before, e.g., heapchecker output. + fflush(stdout); +} + +// End PrettyUnitTestResultPrinter + +// class TestEventRepeater +// +// This class forwards events to other event listeners. +class TestEventRepeater : public TestEventListener { + public: + TestEventRepeater() : forwarding_enabled_(true) {} + virtual ~TestEventRepeater(); + void Append(TestEventListener *listener); + TestEventListener* Release(TestEventListener* listener); + + // Controls whether events will be forwarded to listeners_. Set to false + // in death test child processes. + bool forwarding_enabled() const { return forwarding_enabled_; } + void set_forwarding_enabled(bool enable) { forwarding_enabled_ = enable; } + + virtual void OnTestProgramStart(const UnitTest& unit_test); + virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration); + virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test); + virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test); + virtual void OnTestCaseStart(const TestCase& test_case); + virtual void OnTestStart(const TestInfo& test_info); + virtual void OnTestPartResult(const TestPartResult& result); + virtual void OnTestEnd(const TestInfo& test_info); + virtual void OnTestCaseEnd(const TestCase& test_case); + virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test); + virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test); + virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration); + virtual void OnTestProgramEnd(const UnitTest& unit_test); + + private: + // Controls whether events will be forwarded to listeners_. Set to false + // in death test child processes. + bool forwarding_enabled_; + // The list of listeners that receive events. + std::vector listeners_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestEventRepeater); +}; + +TestEventRepeater::~TestEventRepeater() { + ForEach(listeners_, Delete); +} + +void TestEventRepeater::Append(TestEventListener *listener) { + listeners_.push_back(listener); +} + +// TODO(vladl@google.com): Factor the search functionality into Vector::Find. +TestEventListener* TestEventRepeater::Release(TestEventListener *listener) { + for (size_t i = 0; i < listeners_.size(); ++i) { + if (listeners_[i] == listener) { + listeners_.erase(listeners_.begin() + i); + return listener; + } + } + + return NULL; +} + +// Since most methods are very similar, use macros to reduce boilerplate. +// This defines a member that forwards the call to all listeners. +#define GTEST_REPEATER_METHOD_(Name, Type) \ +void TestEventRepeater::Name(const Type& parameter) { \ + if (forwarding_enabled_) { \ + for (size_t i = 0; i < listeners_.size(); i++) { \ + listeners_[i]->Name(parameter); \ + } \ + } \ +} +// This defines a member that forwards the call to all listeners in reverse +// order. +#define GTEST_REVERSE_REPEATER_METHOD_(Name, Type) \ +void TestEventRepeater::Name(const Type& parameter) { \ + if (forwarding_enabled_) { \ + for (int i = static_cast(listeners_.size()) - 1; i >= 0; i--) { \ + listeners_[i]->Name(parameter); \ + } \ + } \ +} + +GTEST_REPEATER_METHOD_(OnTestProgramStart, UnitTest) +GTEST_REPEATER_METHOD_(OnEnvironmentsSetUpStart, UnitTest) +GTEST_REPEATER_METHOD_(OnTestCaseStart, TestCase) +GTEST_REPEATER_METHOD_(OnTestStart, TestInfo) +GTEST_REPEATER_METHOD_(OnTestPartResult, TestPartResult) +GTEST_REPEATER_METHOD_(OnEnvironmentsTearDownStart, UnitTest) +GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsSetUpEnd, UnitTest) +GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsTearDownEnd, UnitTest) +GTEST_REVERSE_REPEATER_METHOD_(OnTestEnd, TestInfo) +GTEST_REVERSE_REPEATER_METHOD_(OnTestCaseEnd, TestCase) +GTEST_REVERSE_REPEATER_METHOD_(OnTestProgramEnd, UnitTest) + +#undef GTEST_REPEATER_METHOD_ +#undef GTEST_REVERSE_REPEATER_METHOD_ + +void TestEventRepeater::OnTestIterationStart(const UnitTest& unit_test, + int iteration) { + if (forwarding_enabled_) { + for (size_t i = 0; i < listeners_.size(); i++) { + listeners_[i]->OnTestIterationStart(unit_test, iteration); + } + } +} + +void TestEventRepeater::OnTestIterationEnd(const UnitTest& unit_test, + int iteration) { + if (forwarding_enabled_) { + for (int i = static_cast(listeners_.size()) - 1; i >= 0; i--) { + listeners_[i]->OnTestIterationEnd(unit_test, iteration); + } + } +} + +// End TestEventRepeater + +// This class generates an XML output file. +class XmlUnitTestResultPrinter : public EmptyTestEventListener { + public: + explicit XmlUnitTestResultPrinter(const char* output_file); + + virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration); + + private: + // Is c a whitespace character that is normalized to a space character + // when it appears in an XML attribute value? + static bool IsNormalizableWhitespace(char c) { + return c == 0x9 || c == 0xA || c == 0xD; + } + + // May c appear in a well-formed XML document? + static bool IsValidXmlCharacter(char c) { + return IsNormalizableWhitespace(c) || c >= 0x20; + } + + // Returns an XML-escaped copy of the input string str. If + // is_attribute is true, the text is meant to appear as an attribute + // value, and normalizable whitespace is preserved by replacing it + // with character references. + static String EscapeXml(const char* str, bool is_attribute); + + // Returns the given string with all characters invalid in XML removed. + static String RemoveInvalidXmlCharacters(const char* str); + + // Convenience wrapper around EscapeXml when str is an attribute value. + static String EscapeXmlAttribute(const char* str) { + return EscapeXml(str, true); + } + + // Convenience wrapper around EscapeXml when str is not an attribute value. + static String EscapeXmlText(const char* str) { return EscapeXml(str, false); } + + // Streams an XML CDATA section, escaping invalid CDATA sequences as needed. + static void OutputXmlCDataSection(::std::ostream* stream, const char* data); + + // Streams an XML representation of a TestInfo object. + static void OutputXmlTestInfo(::std::ostream* stream, + const char* test_case_name, + const TestInfo& test_info); + + // Prints an XML representation of a TestCase object + static void PrintXmlTestCase(FILE* out, const TestCase& test_case); + + // Prints an XML summary of unit_test to output stream out. + static void PrintXmlUnitTest(FILE* out, const UnitTest& unit_test); + + // Produces a string representing the test properties in a result as space + // delimited XML attributes based on the property key="value" pairs. + // When the String is not empty, it includes a space at the beginning, + // to delimit this attribute from prior attributes. + static String TestPropertiesAsXmlAttributes(const TestResult& result); + + // The output file. + const String output_file_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(XmlUnitTestResultPrinter); +}; + +// Creates a new XmlUnitTestResultPrinter. +XmlUnitTestResultPrinter::XmlUnitTestResultPrinter(const char* output_file) + : output_file_(output_file) { + if (output_file_.c_str() == NULL || output_file_.empty()) { + fprintf(stderr, "XML output file may not be null\n"); + fflush(stderr); + exit(EXIT_FAILURE); + } +} + +// Called after the unit test ends. +void XmlUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, + int /*iteration*/) { + FILE* xmlout = NULL; + FilePath output_file(output_file_); + FilePath output_dir(output_file.RemoveFileName()); + + if (output_dir.CreateDirectoriesRecursively()) { + xmlout = posix::FOpen(output_file_.c_str(), "w"); + } + if (xmlout == NULL) { + // TODO(wan): report the reason of the failure. + // + // We don't do it for now as: + // + // 1. There is no urgent need for it. + // 2. It's a bit involved to make the errno variable thread-safe on + // all three operating systems (Linux, Windows, and Mac OS). + // 3. To interpret the meaning of errno in a thread-safe way, + // we need the strerror_r() function, which is not available on + // Windows. + fprintf(stderr, + "Unable to open file \"%s\"\n", + output_file_.c_str()); + fflush(stderr); + exit(EXIT_FAILURE); + } + PrintXmlUnitTest(xmlout, unit_test); + fclose(xmlout); +} + +// Returns an XML-escaped copy of the input string str. If is_attribute +// is true, the text is meant to appear as an attribute value, and +// normalizable whitespace is preserved by replacing it with character +// references. +// +// Invalid XML characters in str, if any, are stripped from the output. +// It is expected that most, if not all, of the text processed by this +// module will consist of ordinary English text. +// If this module is ever modified to produce version 1.1 XML output, +// most invalid characters can be retained using character references. +// TODO(wan): It might be nice to have a minimally invasive, human-readable +// escaping scheme for invalid characters, rather than dropping them. +String XmlUnitTestResultPrinter::EscapeXml(const char* str, bool is_attribute) { + Message m; + + if (str != NULL) { + for (const char* src = str; *src; ++src) { + switch (*src) { + case '<': + m << "<"; + break; + case '>': + m << ">"; + break; + case '&': + m << "&"; + break; + case '\'': + if (is_attribute) + m << "'"; + else + m << '\''; + break; + case '"': + if (is_attribute) + m << """; + else + m << '"'; + break; + default: + if (IsValidXmlCharacter(*src)) { + if (is_attribute && IsNormalizableWhitespace(*src)) + m << String::Format("&#x%02X;", unsigned(*src)); + else + m << *src; + } + break; + } + } + } + + return m.GetString(); +} + +// Returns the given string with all characters invalid in XML removed. +// Currently invalid characters are dropped from the string. An +// alternative is to replace them with certain characters such as . or ?. +String XmlUnitTestResultPrinter::RemoveInvalidXmlCharacters(const char* str) { + char* const output = new char[strlen(str) + 1]; + char* appender = output; + for (char ch = *str; ch != '\0'; ch = *++str) + if (IsValidXmlCharacter(ch)) + *appender++ = ch; + *appender = '\0'; + + String ret_value(output); + delete[] output; + return ret_value; +} + +// The following routines generate an XML representation of a UnitTest +// object. +// +// This is how Google Test concepts map to the DTD: +// +// <-- corresponds to a UnitTest object +// <-- corresponds to a TestCase object +// <-- corresponds to a TestInfo object +// ... +// ... +// ... +// <-- individual assertion failures +// +// +// + +// Formats the given time in milliseconds as seconds. +std::string FormatTimeInMillisAsSeconds(TimeInMillis ms) { + ::std::stringstream ss; + ss << ms/1000.0; + return ss.str(); +} + +// Streams an XML CDATA section, escaping invalid CDATA sequences as needed. +void XmlUnitTestResultPrinter::OutputXmlCDataSection(::std::ostream* stream, + const char* data) { + const char* segment = data; + *stream << ""); + if (next_segment != NULL) { + stream->write( + segment, static_cast(next_segment - segment)); + *stream << "]]>]]>"); + } else { + *stream << segment; + break; + } + } + *stream << "]]>"; +} + +// Prints an XML representation of a TestInfo object. +// TODO(wan): There is also value in printing properties with the plain printer. +void XmlUnitTestResultPrinter::OutputXmlTestInfo(::std::ostream* stream, + const char* test_case_name, + const TestInfo& test_info) { + const TestResult& result = *test_info.result(); + *stream << " \n"; + *stream << " "; + const String message = RemoveInvalidXmlCharacters(String::Format( + "%s:%d\n%s", + part.file_name(), part.line_number(), + part.message()).c_str()); + OutputXmlCDataSection(stream, message.c_str()); + *stream << "\n"; + } + } + + if (failures == 0) + *stream << " />\n"; + else + *stream << " \n"; +} + +// Prints an XML representation of a TestCase object +void XmlUnitTestResultPrinter::PrintXmlTestCase(FILE* out, + const TestCase& test_case) { + fprintf(out, + " \n", + FormatTimeInMillisAsSeconds(test_case.elapsed_time()).c_str()); + for (int i = 0; i < test_case.total_test_count(); ++i) { + StrStream stream; + OutputXmlTestInfo(&stream, test_case.name(), *test_case.GetTestInfo(i)); + fprintf(out, "%s", StrStreamToString(&stream).c_str()); + } + fprintf(out, " \n"); +} + +// Prints an XML summary of unit_test to output stream out. +void XmlUnitTestResultPrinter::PrintXmlUnitTest(FILE* out, + const UnitTest& unit_test) { + fprintf(out, "\n"); + fprintf(out, + "\n"); + for (int i = 0; i < unit_test.total_test_case_count(); ++i) + PrintXmlTestCase(out, *unit_test.GetTestCase(i)); + fprintf(out, "\n"); +} + +// Produces a string representing the test properties in a result as space +// delimited XML attributes based on the property key="value" pairs. +String XmlUnitTestResultPrinter::TestPropertiesAsXmlAttributes( + const TestResult& result) { + Message attributes; + for (int i = 0; i < result.test_property_count(); ++i) { + const TestProperty& property = result.GetTestProperty(i); + attributes << " " << property.key() << "=" + << "\"" << EscapeXmlAttribute(property.value()) << "\""; + } + return attributes.GetString(); +} + +// End XmlUnitTestResultPrinter + +// Class ScopedTrace + +// Pushes the given source file location and message onto a per-thread +// trace stack maintained by Google Test. +// L < UnitTest::mutex_ +ScopedTrace::ScopedTrace(const char* file, int line, const Message& message) { + TraceInfo trace; + trace.file = file; + trace.line = line; + trace.message = message.GetString(); + + UnitTest::GetInstance()->PushGTestTrace(trace); +} + +// Pops the info pushed by the c'tor. +// L < UnitTest::mutex_ +ScopedTrace::~ScopedTrace() { + UnitTest::GetInstance()->PopGTestTrace(); +} + + +// class OsStackTraceGetter + +// Returns the current OS stack trace as a String. Parameters: +// +// max_depth - the maximum number of stack frames to be included +// in the trace. +// skip_count - the number of top frames to be skipped; doesn't count +// against max_depth. +// +// L < mutex_ +// We use "L < mutex_" to denote that the function may acquire mutex_. +String OsStackTraceGetter::CurrentStackTrace(int, int) { + return String(""); +} + +// L < mutex_ +void OsStackTraceGetter::UponLeavingGTest() { +} + +const char* const +OsStackTraceGetter::kElidedFramesMarker = + "... " GTEST_NAME_ " internal frames ..."; + +} // namespace internal + +// class TestEventListeners + +TestEventListeners::TestEventListeners() + : repeater_(new internal::TestEventRepeater()), + default_result_printer_(NULL), + default_xml_generator_(NULL) { +} + +TestEventListeners::~TestEventListeners() { delete repeater_; } + +// Returns the standard listener responsible for the default console +// output. Can be removed from the listeners list to shut down default +// console output. Note that removing this object from the listener list +// with Release transfers its ownership to the user. +void TestEventListeners::Append(TestEventListener* listener) { + repeater_->Append(listener); +} + +// Removes the given event listener from the list and returns it. It then +// becomes the caller's responsibility to delete the listener. Returns +// NULL if the listener is not found in the list. +TestEventListener* TestEventListeners::Release(TestEventListener* listener) { + if (listener == default_result_printer_) + default_result_printer_ = NULL; + else if (listener == default_xml_generator_) + default_xml_generator_ = NULL; + return repeater_->Release(listener); +} + +// Returns repeater that broadcasts the TestEventListener events to all +// subscribers. +TestEventListener* TestEventListeners::repeater() { return repeater_; } + +// Sets the default_result_printer attribute to the provided listener. +// The listener is also added to the listener list and previous +// default_result_printer is removed from it and deleted. The listener can +// also be NULL in which case it will not be added to the list. Does +// nothing if the previous and the current listener objects are the same. +void TestEventListeners::SetDefaultResultPrinter(TestEventListener* listener) { + if (default_result_printer_ != listener) { + // It is an error to pass this method a listener that is already in the + // list. + delete Release(default_result_printer_); + default_result_printer_ = listener; + if (listener != NULL) + Append(listener); + } +} + +// Sets the default_xml_generator attribute to the provided listener. The +// listener is also added to the listener list and previous +// default_xml_generator is removed from it and deleted. The listener can +// also be NULL in which case it will not be added to the list. Does +// nothing if the previous and the current listener objects are the same. +void TestEventListeners::SetDefaultXmlGenerator(TestEventListener* listener) { + if (default_xml_generator_ != listener) { + // It is an error to pass this method a listener that is already in the + // list. + delete Release(default_xml_generator_); + default_xml_generator_ = listener; + if (listener != NULL) + Append(listener); + } +} + +// Controls whether events will be forwarded by the repeater to the +// listeners in the list. +bool TestEventListeners::EventForwardingEnabled() const { + return repeater_->forwarding_enabled(); +} + +void TestEventListeners::SuppressEventForwarding() { + repeater_->set_forwarding_enabled(false); +} + +// class UnitTest + +// Gets the singleton UnitTest object. The first time this method is +// called, a UnitTest object is constructed and returned. Consecutive +// calls will return the same object. +// +// We don't protect this under mutex_ as a user is not supposed to +// call this before main() starts, from which point on the return +// value will never change. +UnitTest * UnitTest::GetInstance() { + // When compiled with MSVC 7.1 in optimized mode, destroying the + // UnitTest object upon exiting the program messes up the exit code, + // causing successful tests to appear failed. We have to use a + // different implementation in this case to bypass the compiler bug. + // This implementation makes the compiler happy, at the cost of + // leaking the UnitTest object. + + // CodeGear C++Builder insists on a public destructor for the + // default implementation. Use this implementation to keep good OO + // design with private destructor. + +#if (_MSC_VER == 1310 && !defined(_DEBUG)) || defined(__BORLANDC__) + static UnitTest* const instance = new UnitTest; + return instance; +#else + static UnitTest instance; + return &instance; +#endif // (_MSC_VER == 1310 && !defined(_DEBUG)) || defined(__BORLANDC__) +} + +// Gets the number of successful test cases. +int UnitTest::successful_test_case_count() const { + return impl()->successful_test_case_count(); +} + +// Gets the number of failed test cases. +int UnitTest::failed_test_case_count() const { + return impl()->failed_test_case_count(); +} + +// Gets the number of all test cases. +int UnitTest::total_test_case_count() const { + return impl()->total_test_case_count(); +} + +// Gets the number of all test cases that contain at least one test +// that should run. +int UnitTest::test_case_to_run_count() const { + return impl()->test_case_to_run_count(); +} + +// Gets the number of successful tests. +int UnitTest::successful_test_count() const { + return impl()->successful_test_count(); +} + +// Gets the number of failed tests. +int UnitTest::failed_test_count() const { return impl()->failed_test_count(); } + +// Gets the number of disabled tests. +int UnitTest::disabled_test_count() const { + return impl()->disabled_test_count(); +} + +// Gets the number of all tests. +int UnitTest::total_test_count() const { return impl()->total_test_count(); } + +// Gets the number of tests that should run. +int UnitTest::test_to_run_count() const { return impl()->test_to_run_count(); } + +// Gets the elapsed time, in milliseconds. +internal::TimeInMillis UnitTest::elapsed_time() const { + return impl()->elapsed_time(); +} + +// Returns true iff the unit test passed (i.e. all test cases passed). +bool UnitTest::Passed() const { return impl()->Passed(); } + +// Returns true iff the unit test failed (i.e. some test case failed +// or something outside of all tests failed). +bool UnitTest::Failed() const { return impl()->Failed(); } + +// Gets the i-th test case among all the test cases. i can range from 0 to +// total_test_case_count() - 1. If i is not in that range, returns NULL. +const TestCase* UnitTest::GetTestCase(int i) const { + return impl()->GetTestCase(i); +} + +// Gets the i-th test case among all the test cases. i can range from 0 to +// total_test_case_count() - 1. If i is not in that range, returns NULL. +TestCase* UnitTest::GetMutableTestCase(int i) { + return impl()->GetMutableTestCase(i); +} + +// Returns the list of event listeners that can be used to track events +// inside Google Test. +TestEventListeners& UnitTest::listeners() { + return *impl()->listeners(); +} + +// Registers and returns a global test environment. When a test +// program is run, all global test environments will be set-up in the +// order they were registered. After all tests in the program have +// finished, all global test environments will be torn-down in the +// *reverse* order they were registered. +// +// The UnitTest object takes ownership of the given environment. +// +// We don't protect this under mutex_, as we only support calling it +// from the main thread. +Environment* UnitTest::AddEnvironment(Environment* env) { + if (env == NULL) { + return NULL; + } + + impl_->environments().push_back(env); + return env; +} + +#if GTEST_HAS_EXCEPTIONS +// A failed Google Test assertion will throw an exception of this type +// when exceptions are enabled. We derive it from std::runtime_error, +// which is for errors presumably detectable only at run time. Since +// std::runtime_error inherits from std::exception, many testing +// frameworks know how to extract and print the message inside it. +class GoogleTestFailureException : public ::std::runtime_error { + public: + explicit GoogleTestFailureException(const TestPartResult& failure) + : ::std::runtime_error(PrintTestPartResultToString(failure).c_str()) {} +}; +#endif + +// Adds a TestPartResult to the current TestResult object. All Google Test +// assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc) eventually call +// this to report their results. The user code should use the +// assertion macros instead of calling this directly. +// L < mutex_ +void UnitTest::AddTestPartResult(TestPartResult::Type result_type, + const char* file_name, + int line_number, + const internal::String& message, + const internal::String& os_stack_trace) { + Message msg; + msg << message; + + internal::MutexLock lock(&mutex_); + if (impl_->gtest_trace_stack().size() > 0) { + msg << "\n" << GTEST_NAME_ << " trace:"; + + for (int i = static_cast(impl_->gtest_trace_stack().size()); + i > 0; --i) { + const internal::TraceInfo& trace = impl_->gtest_trace_stack()[i - 1]; + msg << "\n" << internal::FormatFileLocation(trace.file, trace.line) + << " " << trace.message; + } + } + + if (os_stack_trace.c_str() != NULL && !os_stack_trace.empty()) { + msg << internal::kStackTraceMarker << os_stack_trace; + } + + const TestPartResult result = + TestPartResult(result_type, file_name, line_number, + msg.GetString().c_str()); + impl_->GetTestPartResultReporterForCurrentThread()-> + ReportTestPartResult(result); + + if (result_type != TestPartResult::kSuccess) { + // gtest_break_on_failure takes precedence over + // gtest_throw_on_failure. This allows a user to set the latter + // in the code (perhaps in order to use Google Test assertions + // with another testing framework) and specify the former on the + // command line for debugging. + if (GTEST_FLAG(break_on_failure)) { +#if GTEST_OS_WINDOWS + // Using DebugBreak on Windows allows gtest to still break into a debugger + // when a failure happens and both the --gtest_break_on_failure and + // the --gtest_catch_exceptions flags are specified. + DebugBreak(); +#else + *static_cast(NULL) = 1; +#endif // GTEST_OS_WINDOWS + } else if (GTEST_FLAG(throw_on_failure)) { +#if GTEST_HAS_EXCEPTIONS + throw GoogleTestFailureException(result); +#else + // We cannot call abort() as it generates a pop-up in debug mode + // that cannot be suppressed in VC 7.1 or below. + exit(1); +#endif + } + } +} + +// Creates and adds a property to the current TestResult. If a property matching +// the supplied value already exists, updates its value instead. +void UnitTest::RecordPropertyForCurrentTest(const char* key, + const char* value) { + const TestProperty test_property(key, value); + impl_->current_test_result()->RecordProperty(test_property); +} + +// Runs all tests in this UnitTest object and prints the result. +// Returns 0 if successful, or 1 otherwise. +// +// We don't protect this under mutex_, as we only support calling it +// from the main thread. +int UnitTest::Run() { +#if GTEST_HAS_SEH + // Catch SEH-style exceptions. + + const bool in_death_test_child_process = + internal::GTEST_FLAG(internal_run_death_test).length() > 0; + + // Either the user wants Google Test to catch exceptions thrown by the + // tests or this is executing in the context of death test child + // process. In either case the user does not want to see pop-up dialogs + // about crashes - they are expected.. + if (GTEST_FLAG(catch_exceptions) || in_death_test_child_process) { +#if !GTEST_OS_WINDOWS_MOBILE + // SetErrorMode doesn't exist on CE. + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | + SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); +#endif // !GTEST_OS_WINDOWS_MOBILE + +#if (defined(_MSC_VER) || GTEST_OS_WINDOWS_MINGW) && !GTEST_OS_WINDOWS_MOBILE + // Death test children can be terminated with _abort(). On Windows, + // _abort() can show a dialog with a warning message. This forces the + // abort message to go to stderr instead. + _set_error_mode(_OUT_TO_STDERR); +#endif + +#if _MSC_VER >= 1400 && !GTEST_OS_WINDOWS_MOBILE + // In the debug version, Visual Studio pops up a separate dialog + // offering a choice to debug the aborted program. We need to suppress + // this dialog or it will pop up for every EXPECT/ASSERT_DEATH statement + // executed. Google Test will notify the user of any unexpected + // failure via stderr. + // + // VC++ doesn't define _set_abort_behavior() prior to the version 8.0. + // Users of prior VC versions shall suffer the agony and pain of + // clicking through the countless debug dialogs. + // TODO(vladl@google.com): find a way to suppress the abort dialog() in the + // debug mode when compiled with VC 7.1 or lower. + if (!GTEST_FLAG(break_on_failure)) + _set_abort_behavior( + 0x0, // Clear the following flags: + _WRITE_ABORT_MSG | _CALL_REPORTFAULT); // pop-up window, core dump. +#endif + } + + __try { + return impl_->RunAllTests(); + } __except(internal::UnitTestOptions::GTestShouldProcessSEH( + GetExceptionCode())) { + printf("Exception thrown with code 0x%x.\nFAIL\n", GetExceptionCode()); + fflush(stdout); + return 1; + } + +#else // We are on a compiler or platform that doesn't support SEH. + + return impl_->RunAllTests(); +#endif // GTEST_HAS_SEH +} + +// Returns the working directory when the first TEST() or TEST_F() was +// executed. +const char* UnitTest::original_working_dir() const { + return impl_->original_working_dir_.c_str(); +} + +// Returns the TestCase object for the test that's currently running, +// or NULL if no test is running. +// L < mutex_ +const TestCase* UnitTest::current_test_case() const { + internal::MutexLock lock(&mutex_); + return impl_->current_test_case(); +} + +// Returns the TestInfo object for the test that's currently running, +// or NULL if no test is running. +// L < mutex_ +const TestInfo* UnitTest::current_test_info() const { + internal::MutexLock lock(&mutex_); + return impl_->current_test_info(); +} + +// Returns the random seed used at the start of the current test run. +int UnitTest::random_seed() const { return impl_->random_seed(); } + +#if GTEST_HAS_PARAM_TEST +// Returns ParameterizedTestCaseRegistry object used to keep track of +// value-parameterized tests and instantiate and register them. +// L < mutex_ +internal::ParameterizedTestCaseRegistry& + UnitTest::parameterized_test_registry() { + return impl_->parameterized_test_registry(); +} +#endif // GTEST_HAS_PARAM_TEST + +// Creates an empty UnitTest. +UnitTest::UnitTest() { + impl_ = new internal::UnitTestImpl(this); +} + +// Destructor of UnitTest. +UnitTest::~UnitTest() { + delete impl_; +} + +// Pushes a trace defined by SCOPED_TRACE() on to the per-thread +// Google Test trace stack. +// L < mutex_ +void UnitTest::PushGTestTrace(const internal::TraceInfo& trace) { + internal::MutexLock lock(&mutex_); + impl_->gtest_trace_stack().push_back(trace); +} + +// Pops a trace from the per-thread Google Test trace stack. +// L < mutex_ +void UnitTest::PopGTestTrace() { + internal::MutexLock lock(&mutex_); + impl_->gtest_trace_stack().pop_back(); +} + +namespace internal { + +UnitTestImpl::UnitTestImpl(UnitTest* parent) + : parent_(parent), +#ifdef _MSC_VER +#pragma warning(push) // Saves the current warning state. +#pragma warning(disable:4355) // Temporarily disables warning 4355 + // (using this in initializer). + default_global_test_part_result_reporter_(this), + default_per_thread_test_part_result_reporter_(this), +#pragma warning(pop) // Restores the warning state again. +#else + default_global_test_part_result_reporter_(this), + default_per_thread_test_part_result_reporter_(this), +#endif // _MSC_VER + global_test_part_result_repoter_( + &default_global_test_part_result_reporter_), + per_thread_test_part_result_reporter_( + &default_per_thread_test_part_result_reporter_), +#if GTEST_HAS_PARAM_TEST + parameterized_test_registry_(), + parameterized_tests_registered_(false), +#endif // GTEST_HAS_PARAM_TEST + last_death_test_case_(-1), + current_test_case_(NULL), + current_test_info_(NULL), + ad_hoc_test_result_(), + os_stack_trace_getter_(NULL), + post_flag_parse_init_performed_(false), + random_seed_(0), // Will be overridden by the flag before first use. + random_(0), // Will be reseeded before first use. +#if GTEST_HAS_DEATH_TEST + elapsed_time_(0), + internal_run_death_test_flag_(NULL), + death_test_factory_(new DefaultDeathTestFactory) { +#else + elapsed_time_(0) { +#endif // GTEST_HAS_DEATH_TEST + listeners()->SetDefaultResultPrinter(new PrettyUnitTestResultPrinter); +} + +UnitTestImpl::~UnitTestImpl() { + // Deletes every TestCase. + ForEach(test_cases_, internal::Delete); + + // Deletes every Environment. + ForEach(environments_, internal::Delete); + + delete os_stack_trace_getter_; +} + +#if GTEST_HAS_DEATH_TEST +// Disables event forwarding if the control is currently in a death test +// subprocess. Must not be called before InitGoogleTest. +void UnitTestImpl::SuppressTestEventsIfInSubprocess() { + if (internal_run_death_test_flag_.get() != NULL) + listeners()->SuppressEventForwarding(); +} +#endif // GTEST_HAS_DEATH_TEST + +// Initializes event listeners performing XML output as specified by +// UnitTestOptions. Must not be called before InitGoogleTest. +void UnitTestImpl::ConfigureXmlOutput() { + const String& output_format = UnitTestOptions::GetOutputFormat(); + if (output_format == "xml") { + listeners()->SetDefaultXmlGenerator(new XmlUnitTestResultPrinter( + UnitTestOptions::GetAbsolutePathToOutputFile().c_str())); + } else if (output_format != "") { + printf("WARNING: unrecognized output format \"%s\" ignored.\n", + output_format.c_str()); + fflush(stdout); + } +} + +// Performs initialization dependent upon flag values obtained in +// ParseGoogleTestFlagsOnly. Is called from InitGoogleTest after the call to +// ParseGoogleTestFlagsOnly. In case a user neglects to call InitGoogleTest +// this function is also called from RunAllTests. Since this function can be +// called more than once, it has to be idempotent. +void UnitTestImpl::PostFlagParsingInit() { + // Ensures that this function does not execute more than once. + if (!post_flag_parse_init_performed_) { + post_flag_parse_init_performed_ = true; + +#if GTEST_HAS_DEATH_TEST + InitDeathTestSubprocessControlInfo(); + SuppressTestEventsIfInSubprocess(); +#endif // GTEST_HAS_DEATH_TEST + + // Registers parameterized tests. This makes parameterized tests + // available to the UnitTest reflection API without running + // RUN_ALL_TESTS. + RegisterParameterizedTests(); + + // Configures listeners for XML output. This makes it possible for users + // to shut down the default XML output before invoking RUN_ALL_TESTS. + ConfigureXmlOutput(); + } +} + +// A predicate that checks the name of a TestCase against a known +// value. +// +// This is used for implementation of the UnitTest class only. We put +// it in the anonymous namespace to prevent polluting the outer +// namespace. +// +// TestCaseNameIs is copyable. +class TestCaseNameIs { + public: + // Constructor. + explicit TestCaseNameIs(const String& name) + : name_(name) {} + + // Returns true iff the name of test_case matches name_. + bool operator()(const TestCase* test_case) const { + return test_case != NULL && strcmp(test_case->name(), name_.c_str()) == 0; + } + + private: + String name_; +}; + +// Finds and returns a TestCase with the given name. If one doesn't +// exist, creates one and returns it. It's the CALLER'S +// RESPONSIBILITY to ensure that this function is only called WHEN THE +// TESTS ARE NOT SHUFFLED. +// +// Arguments: +// +// test_case_name: name of the test case +// set_up_tc: pointer to the function that sets up the test case +// tear_down_tc: pointer to the function that tears down the test case +TestCase* UnitTestImpl::GetTestCase(const char* test_case_name, + const char* comment, + Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc) { + // Can we find a TestCase with the given name? + const std::vector::const_iterator test_case = + std::find_if(test_cases_.begin(), test_cases_.end(), + TestCaseNameIs(test_case_name)); + + if (test_case != test_cases_.end()) + return *test_case; + + // No. Let's create one. + TestCase* const new_test_case = + new TestCase(test_case_name, comment, set_up_tc, tear_down_tc); + + // Is this a death test case? + if (internal::UnitTestOptions::MatchesFilter(String(test_case_name), + kDeathTestCaseFilter)) { + // Yes. Inserts the test case after the last death test case + // defined so far. This only works when the test cases haven't + // been shuffled. Otherwise we may end up running a death test + // after a non-death test. + ++last_death_test_case_; + test_cases_.insert(test_cases_.begin() + last_death_test_case_, + new_test_case); + } else { + // No. Appends to the end of the list. + test_cases_.push_back(new_test_case); + } + + test_case_indices_.push_back(static_cast(test_case_indices_.size())); + return new_test_case; +} + +// Helpers for setting up / tearing down the given environment. They +// are for use in the ForEach() function. +static void SetUpEnvironment(Environment* env) { env->SetUp(); } +static void TearDownEnvironment(Environment* env) { env->TearDown(); } + +// Runs all tests in this UnitTest object, prints the result, and +// returns 0 if all tests are successful, or 1 otherwise. If any +// exception is thrown during a test on Windows, this test is +// considered to be failed, but the rest of the tests will still be +// run. (We disable exceptions on Linux and Mac OS X, so the issue +// doesn't apply there.) +// When parameterized tests are enabled, it expands and registers +// parameterized tests first in RegisterParameterizedTests(). +// All other functions called from RunAllTests() may safely assume that +// parameterized tests are ready to be counted and run. +int UnitTestImpl::RunAllTests() { + // Makes sure InitGoogleTest() was called. + if (!GTestIsInitialized()) { + printf("%s", + "\nThis test program did NOT call ::testing::InitGoogleTest " + "before calling RUN_ALL_TESTS(). Please fix it.\n"); + return 1; + } + + // Do not run any test if the --help flag was specified. + if (g_help_flag) + return 0; + + // Repeats the call to the post-flag parsing initialization in case the + // user didn't call InitGoogleTest. + PostFlagParsingInit(); + + // Even if sharding is not on, test runners may want to use the + // GTEST_SHARD_STATUS_FILE to query whether the test supports the sharding + // protocol. + internal::WriteToShardStatusFileIfNeeded(); + + // True iff we are in a subprocess for running a thread-safe-style + // death test. + bool in_subprocess_for_death_test = false; + +#if GTEST_HAS_DEATH_TEST + in_subprocess_for_death_test = (internal_run_death_test_flag_.get() != NULL); +#endif // GTEST_HAS_DEATH_TEST + + const bool should_shard = ShouldShard(kTestTotalShards, kTestShardIndex, + in_subprocess_for_death_test); + + // Compares the full test names with the filter to decide which + // tests to run. + const bool has_tests_to_run = FilterTests(should_shard + ? HONOR_SHARDING_PROTOCOL + : IGNORE_SHARDING_PROTOCOL) > 0; + + // Lists the tests and exits if the --gtest_list_tests flag was specified. + if (GTEST_FLAG(list_tests)) { + // This must be called *after* FilterTests() has been called. + ListTestsMatchingFilter(); + return 0; + } + + random_seed_ = GTEST_FLAG(shuffle) ? + GetRandomSeedFromFlag(GTEST_FLAG(random_seed)) : 0; + + // True iff at least one test has failed. + bool failed = false; + + TestEventListener* repeater = listeners()->repeater(); + + repeater->OnTestProgramStart(*parent_); + + // How many times to repeat the tests? We don't want to repeat them + // when we are inside the subprocess of a death test. + const int repeat = in_subprocess_for_death_test ? 1 : GTEST_FLAG(repeat); + // Repeats forever if the repeat count is negative. + const bool forever = repeat < 0; + for (int i = 0; forever || i != repeat; i++) { + ClearResult(); + + const TimeInMillis start = GetTimeInMillis(); + + // Shuffles test cases and tests if requested. + if (has_tests_to_run && GTEST_FLAG(shuffle)) { + random()->Reseed(random_seed_); + // This should be done before calling OnTestIterationStart(), + // such that a test event listener can see the actual test order + // in the event. + ShuffleTests(); + } + + // Tells the unit test event listeners that the tests are about to start. + repeater->OnTestIterationStart(*parent_, i); + + // Runs each test case if there is at least one test to run. + if (has_tests_to_run) { + // Sets up all environments beforehand. + repeater->OnEnvironmentsSetUpStart(*parent_); + ForEach(environments_, SetUpEnvironment); + repeater->OnEnvironmentsSetUpEnd(*parent_); + + // Runs the tests only if there was no fatal failure during global + // set-up. + if (!Test::HasFatalFailure()) { + for (int test_index = 0; test_index < total_test_case_count(); + test_index++) { + GetMutableTestCase(test_index)->Run(); + } + } + + // Tears down all environments in reverse order afterwards. + repeater->OnEnvironmentsTearDownStart(*parent_); + std::for_each(environments_.rbegin(), environments_.rend(), + TearDownEnvironment); + repeater->OnEnvironmentsTearDownEnd(*parent_); + } + + elapsed_time_ = GetTimeInMillis() - start; + + // Tells the unit test event listener that the tests have just finished. + repeater->OnTestIterationEnd(*parent_, i); + + // Gets the result and clears it. + if (!Passed()) { + failed = true; + } + + // Restores the original test order after the iteration. This + // allows the user to quickly repro a failure that happens in the + // N-th iteration without repeating the first (N - 1) iterations. + // This is not enclosed in "if (GTEST_FLAG(shuffle)) { ... }", in + // case the user somehow changes the value of the flag somewhere + // (it's always safe to unshuffle the tests). + UnshuffleTests(); + + if (GTEST_FLAG(shuffle)) { + // Picks a new random seed for each iteration. + random_seed_ = GetNextRandomSeed(random_seed_); + } + } + + repeater->OnTestProgramEnd(*parent_); + + // Returns 0 if all tests passed, or 1 other wise. + return failed ? 1 : 0; +} + +// Reads the GTEST_SHARD_STATUS_FILE environment variable, and creates the file +// if the variable is present. If a file already exists at this location, this +// function will write over it. If the variable is present, but the file cannot +// be created, prints an error and exits. +void WriteToShardStatusFileIfNeeded() { + const char* const test_shard_file = posix::GetEnv(kTestShardStatusFile); + if (test_shard_file != NULL) { + FILE* const file = posix::FOpen(test_shard_file, "w"); + if (file == NULL) { + ColoredPrintf(COLOR_RED, + "Could not write to the test shard status file \"%s\" " + "specified by the %s environment variable.\n", + test_shard_file, kTestShardStatusFile); + fflush(stdout); + exit(EXIT_FAILURE); + } + fclose(file); + } +} + +// Checks whether sharding is enabled by examining the relevant +// environment variable values. If the variables are present, +// but inconsistent (i.e., shard_index >= total_shards), prints +// an error and exits. If in_subprocess_for_death_test, sharding is +// disabled because it must only be applied to the original test +// process. Otherwise, we could filter out death tests we intended to execute. +bool ShouldShard(const char* total_shards_env, + const char* shard_index_env, + bool in_subprocess_for_death_test) { + if (in_subprocess_for_death_test) { + return false; + } + + const Int32 total_shards = Int32FromEnvOrDie(total_shards_env, -1); + const Int32 shard_index = Int32FromEnvOrDie(shard_index_env, -1); + + if (total_shards == -1 && shard_index == -1) { + return false; + } else if (total_shards == -1 && shard_index != -1) { + const Message msg = Message() + << "Invalid environment variables: you have " + << kTestShardIndex << " = " << shard_index + << ", but have left " << kTestTotalShards << " unset.\n"; + ColoredPrintf(COLOR_RED, msg.GetString().c_str()); + fflush(stdout); + exit(EXIT_FAILURE); + } else if (total_shards != -1 && shard_index == -1) { + const Message msg = Message() + << "Invalid environment variables: you have " + << kTestTotalShards << " = " << total_shards + << ", but have left " << kTestShardIndex << " unset.\n"; + ColoredPrintf(COLOR_RED, msg.GetString().c_str()); + fflush(stdout); + exit(EXIT_FAILURE); + } else if (shard_index < 0 || shard_index >= total_shards) { + const Message msg = Message() + << "Invalid environment variables: we require 0 <= " + << kTestShardIndex << " < " << kTestTotalShards + << ", but you have " << kTestShardIndex << "=" << shard_index + << ", " << kTestTotalShards << "=" << total_shards << ".\n"; + ColoredPrintf(COLOR_RED, msg.GetString().c_str()); + fflush(stdout); + exit(EXIT_FAILURE); + } + + return total_shards > 1; +} + +// Parses the environment variable var as an Int32. If it is unset, +// returns default_val. If it is not an Int32, prints an error +// and aborts. +Int32 Int32FromEnvOrDie(const char* const var, Int32 default_val) { + const char* str_val = posix::GetEnv(var); + if (str_val == NULL) { + return default_val; + } + + Int32 result; + if (!ParseInt32(Message() << "The value of environment variable " << var, + str_val, &result)) { + exit(EXIT_FAILURE); + } + return result; +} + +// Given the total number of shards, the shard index, and the test id, +// returns true iff the test should be run on this shard. The test id is +// some arbitrary but unique non-negative integer assigned to each test +// method. Assumes that 0 <= shard_index < total_shards. +bool ShouldRunTestOnShard(int total_shards, int shard_index, int test_id) { + return (test_id % total_shards) == shard_index; +} + +// Compares the name of each test with the user-specified filter to +// decide whether the test should be run, then records the result in +// each TestCase and TestInfo object. +// If shard_tests == true, further filters tests based on sharding +// variables in the environment - see +// http://code.google.com/p/googletest/wiki/GoogleTestAdvancedGuide. +// Returns the number of tests that should run. +int UnitTestImpl::FilterTests(ReactionToSharding shard_tests) { + const Int32 total_shards = shard_tests == HONOR_SHARDING_PROTOCOL ? + Int32FromEnvOrDie(kTestTotalShards, -1) : -1; + const Int32 shard_index = shard_tests == HONOR_SHARDING_PROTOCOL ? + Int32FromEnvOrDie(kTestShardIndex, -1) : -1; + + // num_runnable_tests are the number of tests that will + // run across all shards (i.e., match filter and are not disabled). + // num_selected_tests are the number of tests to be run on + // this shard. + int num_runnable_tests = 0; + int num_selected_tests = 0; + for (size_t i = 0; i < test_cases_.size(); i++) { + TestCase* const test_case = test_cases_[i]; + const String &test_case_name = test_case->name(); + test_case->set_should_run(false); + + for (size_t j = 0; j < test_case->test_info_list().size(); j++) { + TestInfo* const test_info = test_case->test_info_list()[j]; + const String test_name(test_info->name()); + // A test is disabled if test case name or test name matches + // kDisableTestFilter. + const bool is_disabled = + internal::UnitTestOptions::MatchesFilter(test_case_name, + kDisableTestFilter) || + internal::UnitTestOptions::MatchesFilter(test_name, + kDisableTestFilter); + test_info->impl()->set_is_disabled(is_disabled); + + const bool matches_filter = + internal::UnitTestOptions::FilterMatchesTest(test_case_name, + test_name); + test_info->impl()->set_matches_filter(matches_filter); + + const bool is_runnable = + (GTEST_FLAG(also_run_disabled_tests) || !is_disabled) && + matches_filter; + + const bool is_selected = is_runnable && + (shard_tests == IGNORE_SHARDING_PROTOCOL || + ShouldRunTestOnShard(total_shards, shard_index, + num_runnable_tests)); + + num_runnable_tests += is_runnable; + num_selected_tests += is_selected; + + test_info->impl()->set_should_run(is_selected); + test_case->set_should_run(test_case->should_run() || is_selected); + } + } + return num_selected_tests; +} + +// Prints the names of the tests matching the user-specified filter flag. +void UnitTestImpl::ListTestsMatchingFilter() { + for (size_t i = 0; i < test_cases_.size(); i++) { + const TestCase* const test_case = test_cases_[i]; + bool printed_test_case_name = false; + + for (size_t j = 0; j < test_case->test_info_list().size(); j++) { + const TestInfo* const test_info = + test_case->test_info_list()[j]; + if (test_info->matches_filter()) { + if (!printed_test_case_name) { + printed_test_case_name = true; + printf("%s.\n", test_case->name()); + } + printf(" %s\n", test_info->name()); + } + } + } + fflush(stdout); +} + +// Sets the OS stack trace getter. +// +// Does nothing if the input and the current OS stack trace getter are +// the same; otherwise, deletes the old getter and makes the input the +// current getter. +void UnitTestImpl::set_os_stack_trace_getter( + OsStackTraceGetterInterface* getter) { + if (os_stack_trace_getter_ != getter) { + delete os_stack_trace_getter_; + os_stack_trace_getter_ = getter; + } +} + +// Returns the current OS stack trace getter if it is not NULL; +// otherwise, creates an OsStackTraceGetter, makes it the current +// getter, and returns it. +OsStackTraceGetterInterface* UnitTestImpl::os_stack_trace_getter() { + if (os_stack_trace_getter_ == NULL) { + os_stack_trace_getter_ = new OsStackTraceGetter; + } + + return os_stack_trace_getter_; +} + +// Returns the TestResult for the test that's currently running, or +// the TestResult for the ad hoc test if no test is running. +TestResult* UnitTestImpl::current_test_result() { + return current_test_info_ ? + current_test_info_->impl()->result() : &ad_hoc_test_result_; +} + +// Shuffles all test cases, and the tests within each test case, +// making sure that death tests are still run first. +void UnitTestImpl::ShuffleTests() { + // Shuffles the death test cases. + ShuffleRange(random(), 0, last_death_test_case_ + 1, &test_case_indices_); + + // Shuffles the non-death test cases. + ShuffleRange(random(), last_death_test_case_ + 1, + static_cast(test_cases_.size()), &test_case_indices_); + + // Shuffles the tests inside each test case. + for (size_t i = 0; i < test_cases_.size(); i++) { + test_cases_[i]->ShuffleTests(random()); + } +} + +// Restores the test cases and tests to their order before the first shuffle. +void UnitTestImpl::UnshuffleTests() { + for (size_t i = 0; i < test_cases_.size(); i++) { + // Unshuffles the tests in each test case. + test_cases_[i]->UnshuffleTests(); + // Resets the index of each test case. + test_case_indices_[i] = static_cast(i); + } +} + +// TestInfoImpl constructor. The new instance assumes ownership of the test +// factory object. +TestInfoImpl::TestInfoImpl(TestInfo* parent, + const char* a_test_case_name, + const char* a_name, + const char* a_test_case_comment, + const char* a_comment, + TypeId a_fixture_class_id, + internal::TestFactoryBase* factory) : + parent_(parent), + test_case_name_(String(a_test_case_name)), + name_(String(a_name)), + test_case_comment_(String(a_test_case_comment)), + comment_(String(a_comment)), + fixture_class_id_(a_fixture_class_id), + should_run_(false), + is_disabled_(false), + matches_filter_(false), + factory_(factory) { +} + +// TestInfoImpl destructor. +TestInfoImpl::~TestInfoImpl() { + delete factory_; +} + +// Returns the current OS stack trace as a String. +// +// The maximum number of stack frames to be included is specified by +// the gtest_stack_trace_depth flag. The skip_count parameter +// specifies the number of top frames to be skipped, which doesn't +// count against the number of frames to be included. +// +// For example, if Foo() calls Bar(), which in turn calls +// GetCurrentOsStackTraceExceptTop(..., 1), Foo() will be included in +// the trace but Bar() and GetCurrentOsStackTraceExceptTop() won't. +String GetCurrentOsStackTraceExceptTop(UnitTest* /*unit_test*/, + int skip_count) { + // We pass skip_count + 1 to skip this wrapper function in addition + // to what the user really wants to skip. + return GetUnitTestImpl()->CurrentOsStackTraceExceptTop(skip_count + 1); +} + +// Used by the GTEST_HIDE_UNREACHABLE_CODE_ macro to suppress unreachable +// code warnings. +namespace { +class ClassUniqueToAlwaysTrue {}; +} + +bool IsTrue(bool condition) { return condition; } + +bool AlwaysTrue() { +#if GTEST_HAS_EXCEPTIONS + // This condition is always false so AlwaysTrue() never actually throws, + // but it makes the compiler think that it may throw. + if (IsTrue(false)) + throw ClassUniqueToAlwaysTrue(); +#endif // GTEST_HAS_EXCEPTIONS + return true; +} + +// If *pstr starts with the given prefix, modifies *pstr to be right +// past the prefix and returns true; otherwise leaves *pstr unchanged +// and returns false. None of pstr, *pstr, and prefix can be NULL. +bool SkipPrefix(const char* prefix, const char** pstr) { + const size_t prefix_len = strlen(prefix); + if (strncmp(*pstr, prefix, prefix_len) == 0) { + *pstr += prefix_len; + return true; + } + return false; +} + +// Parses a string as a command line flag. The string should have +// the format "--flag=value". When def_optional is true, the "=value" +// part can be omitted. +// +// Returns the value of the flag, or NULL if the parsing failed. +const char* ParseFlagValue(const char* str, + const char* flag, + bool def_optional) { + // str and flag must not be NULL. + if (str == NULL || flag == NULL) return NULL; + + // The flag must start with "--" followed by GTEST_FLAG_PREFIX_. + const String flag_str = String::Format("--%s%s", GTEST_FLAG_PREFIX_, flag); + const size_t flag_len = flag_str.length(); + if (strncmp(str, flag_str.c_str(), flag_len) != 0) return NULL; + + // Skips the flag name. + const char* flag_end = str + flag_len; + + // When def_optional is true, it's OK to not have a "=value" part. + if (def_optional && (flag_end[0] == '\0')) { + return flag_end; + } + + // If def_optional is true and there are more characters after the + // flag name, or if def_optional is false, there must be a '=' after + // the flag name. + if (flag_end[0] != '=') return NULL; + + // Returns the string after "=". + return flag_end + 1; +} + +// Parses a string for a bool flag, in the form of either +// "--flag=value" or "--flag". +// +// In the former case, the value is taken as true as long as it does +// not start with '0', 'f', or 'F'. +// +// In the latter case, the value is taken as true. +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseBoolFlag(const char* str, const char* flag, bool* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, true); + + // Aborts if the parsing failed. + if (value_str == NULL) return false; + + // Converts the string value to a bool. + *value = !(*value_str == '0' || *value_str == 'f' || *value_str == 'F'); + return true; +} + +// Parses a string for an Int32 flag, in the form of +// "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseInt32Flag(const char* str, const char* flag, Int32* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, false); + + // Aborts if the parsing failed. + if (value_str == NULL) return false; + + // Sets *value to the value of the flag. + return ParseInt32(Message() << "The value of flag --" << flag, + value_str, value); +} + +// Parses a string for a string flag, in the form of +// "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseStringFlag(const char* str, const char* flag, String* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, false); + + // Aborts if the parsing failed. + if (value_str == NULL) return false; + + // Sets *value to the value of the flag. + *value = value_str; + return true; +} + +// Determines whether a string has a prefix that Google Test uses for its +// flags, i.e., starts with GTEST_FLAG_PREFIX_ or GTEST_FLAG_PREFIX_DASH_. +// If Google Test detects that a command line flag has its prefix but is not +// recognized, it will print its help message. Flags starting with +// GTEST_INTERNAL_PREFIX_ followed by "internal_" are considered Google Test +// internal flags and do not trigger the help message. +static bool HasGoogleTestFlagPrefix(const char* str) { + return (SkipPrefix("--", &str) || + SkipPrefix("-", &str) || + SkipPrefix("/", &str)) && + !SkipPrefix(GTEST_FLAG_PREFIX_ "internal_", &str) && + (SkipPrefix(GTEST_FLAG_PREFIX_, &str) || + SkipPrefix(GTEST_FLAG_PREFIX_DASH_, &str)); +} + +// Prints a string containing code-encoded text. The following escape +// sequences can be used in the string to control the text color: +// +// @@ prints a single '@' character. +// @R changes the color to red. +// @G changes the color to green. +// @Y changes the color to yellow. +// @D changes to the default terminal text color. +// +// TODO(wan@google.com): Write tests for this once we add stdout +// capturing to Google Test. +static void PrintColorEncoded(const char* str) { + GTestColor color = COLOR_DEFAULT; // The current color. + + // Conceptually, we split the string into segments divided by escape + // sequences. Then we print one segment at a time. At the end of + // each iteration, the str pointer advances to the beginning of the + // next segment. + for (;;) { + const char* p = strchr(str, '@'); + if (p == NULL) { + ColoredPrintf(color, "%s", str); + return; + } + + ColoredPrintf(color, "%s", String(str, p - str).c_str()); + + const char ch = p[1]; + str = p + 2; + if (ch == '@') { + ColoredPrintf(color, "@"); + } else if (ch == 'D') { + color = COLOR_DEFAULT; + } else if (ch == 'R') { + color = COLOR_RED; + } else if (ch == 'G') { + color = COLOR_GREEN; + } else if (ch == 'Y') { + color = COLOR_YELLOW; + } else { + --str; + } + } +} + +static const char kColorEncodedHelpMessage[] = +"This program contains tests written using " GTEST_NAME_ ". You can use the\n" +"following command line flags to control its behavior:\n" +"\n" +"Test Selection:\n" +" @G--" GTEST_FLAG_PREFIX_ "list_tests@D\n" +" List the names of all tests instead of running them. The name of\n" +" TEST(Foo, Bar) is \"Foo.Bar\".\n" +" @G--" GTEST_FLAG_PREFIX_ "filter=@YPOSTIVE_PATTERNS" + "[@G-@YNEGATIVE_PATTERNS]@D\n" +" Run only the tests whose name matches one of the positive patterns but\n" +" none of the negative patterns. '?' matches any single character; '*'\n" +" matches any substring; ':' separates two patterns.\n" +" @G--" GTEST_FLAG_PREFIX_ "also_run_disabled_tests@D\n" +" Run all disabled tests too.\n" +"\n" +"Test Execution:\n" +" @G--" GTEST_FLAG_PREFIX_ "repeat=@Y[COUNT]@D\n" +" Run the tests repeatedly; use a negative count to repeat forever.\n" +" @G--" GTEST_FLAG_PREFIX_ "shuffle@D\n" +" Randomize tests' orders on every iteration.\n" +" @G--" GTEST_FLAG_PREFIX_ "random_seed=@Y[NUMBER]@D\n" +" Random number seed to use for shuffling test orders (between 1 and\n" +" 99999, or 0 to use a seed based on the current time).\n" +"\n" +"Test Output:\n" +" @G--" GTEST_FLAG_PREFIX_ "color=@Y(@Gyes@Y|@Gno@Y|@Gauto@Y)@D\n" +" Enable/disable colored output. The default is @Gauto@D.\n" +" -@G-" GTEST_FLAG_PREFIX_ "print_time=0@D\n" +" Don't print the elapsed time of each test.\n" +" @G--" GTEST_FLAG_PREFIX_ "output=xml@Y[@G:@YDIRECTORY_PATH@G" + GTEST_PATH_SEP_ "@Y|@G:@YFILE_PATH]@D\n" +" Generate an XML report in the given directory or with the given file\n" +" name. @YFILE_PATH@D defaults to @Gtest_details.xml@D.\n" +"\n" +"Assertion Behavior:\n" +#if GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS +" @G--" GTEST_FLAG_PREFIX_ "death_test_style=@Y(@Gfast@Y|@Gthreadsafe@Y)@D\n" +" Set the default death test style.\n" +#endif // GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS +" @G--" GTEST_FLAG_PREFIX_ "break_on_failure@D\n" +" Turn assertion failures into debugger break-points.\n" +" @G--" GTEST_FLAG_PREFIX_ "throw_on_failure@D\n" +" Turn assertion failures into C++ exceptions.\n" +#if GTEST_OS_WINDOWS +" @G--" GTEST_FLAG_PREFIX_ "catch_exceptions@D\n" +" Suppress pop-ups caused by exceptions.\n" +#endif // GTEST_OS_WINDOWS +"\n" +"Except for @G--" GTEST_FLAG_PREFIX_ "list_tests@D, you can alternatively set " + "the corresponding\n" +"environment variable of a flag (all letters in upper-case). For example, to\n" +"disable colored text output, you can either specify @G--" GTEST_FLAG_PREFIX_ + "color=no@D or set\n" +"the @G" GTEST_FLAG_PREFIX_UPPER_ "COLOR@D environment variable to @Gno@D.\n" +"\n" +"For more information, please read the " GTEST_NAME_ " documentation at\n" +"@G" GTEST_PROJECT_URL_ "@D. If you find a bug in " GTEST_NAME_ "\n" +"(not one in your own code or tests), please report it to\n" +"@G<" GTEST_DEV_EMAIL_ ">@D.\n"; + +// Parses the command line for Google Test flags, without initializing +// other parts of Google Test. The type parameter CharType can be +// instantiated to either char or wchar_t. +template +void ParseGoogleTestFlagsOnlyImpl(int* argc, CharType** argv) { + for (int i = 1; i < *argc; i++) { + const String arg_string = StreamableToString(argv[i]); + const char* const arg = arg_string.c_str(); + + using internal::ParseBoolFlag; + using internal::ParseInt32Flag; + using internal::ParseStringFlag; + + // Do we see a Google Test flag? + if (ParseBoolFlag(arg, kAlsoRunDisabledTestsFlag, + >EST_FLAG(also_run_disabled_tests)) || + ParseBoolFlag(arg, kBreakOnFailureFlag, + >EST_FLAG(break_on_failure)) || + ParseBoolFlag(arg, kCatchExceptionsFlag, + >EST_FLAG(catch_exceptions)) || + ParseStringFlag(arg, kColorFlag, >EST_FLAG(color)) || + ParseStringFlag(arg, kDeathTestStyleFlag, + >EST_FLAG(death_test_style)) || + ParseBoolFlag(arg, kDeathTestUseFork, + >EST_FLAG(death_test_use_fork)) || + ParseStringFlag(arg, kFilterFlag, >EST_FLAG(filter)) || + ParseStringFlag(arg, kInternalRunDeathTestFlag, + >EST_FLAG(internal_run_death_test)) || + ParseBoolFlag(arg, kListTestsFlag, >EST_FLAG(list_tests)) || + ParseStringFlag(arg, kOutputFlag, >EST_FLAG(output)) || + ParseBoolFlag(arg, kPrintTimeFlag, >EST_FLAG(print_time)) || + ParseInt32Flag(arg, kRandomSeedFlag, >EST_FLAG(random_seed)) || + ParseInt32Flag(arg, kRepeatFlag, >EST_FLAG(repeat)) || + ParseBoolFlag(arg, kShuffleFlag, >EST_FLAG(shuffle)) || + ParseInt32Flag(arg, kStackTraceDepthFlag, + >EST_FLAG(stack_trace_depth)) || + ParseBoolFlag(arg, kThrowOnFailureFlag, >EST_FLAG(throw_on_failure)) + ) { + // Yes. Shift the remainder of the argv list left by one. Note + // that argv has (*argc + 1) elements, the last one always being + // NULL. The following loop moves the trailing NULL element as + // well. + for (int j = i; j != *argc; j++) { + argv[j] = argv[j + 1]; + } + + // Decrements the argument count. + (*argc)--; + + // We also need to decrement the iterator as we just removed + // an element. + i--; + } else if (arg_string == "--help" || arg_string == "-h" || + arg_string == "-?" || arg_string == "/?" || + HasGoogleTestFlagPrefix(arg)) { + // Both help flag and unrecognized Google Test flags (excluding + // internal ones) trigger help display. + g_help_flag = true; + } + } + + if (g_help_flag) { + // We print the help here instead of in RUN_ALL_TESTS(), as the + // latter may not be called at all if the user is using Google + // Test with another testing framework. + PrintColorEncoded(kColorEncodedHelpMessage); + } +} + +// Parses the command line for Google Test flags, without initializing +// other parts of Google Test. +void ParseGoogleTestFlagsOnly(int* argc, char** argv) { + ParseGoogleTestFlagsOnlyImpl(argc, argv); +} +void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv) { + ParseGoogleTestFlagsOnlyImpl(argc, argv); +} + +// The internal implementation of InitGoogleTest(). +// +// The type parameter CharType can be instantiated to either char or +// wchar_t. +template +void InitGoogleTestImpl(int* argc, CharType** argv) { + g_init_gtest_count++; + + // We don't want to run the initialization code twice. + if (g_init_gtest_count != 1) return; + + if (*argc <= 0) return; + + internal::g_executable_path = internal::StreamableToString(argv[0]); + +#if GTEST_HAS_DEATH_TEST + g_argvs.clear(); + for (int i = 0; i != *argc; i++) { + g_argvs.push_back(StreamableToString(argv[i])); + } +#endif // GTEST_HAS_DEATH_TEST + + ParseGoogleTestFlagsOnly(argc, argv); + GetUnitTestImpl()->PostFlagParsingInit(); +} + +} // namespace internal + +// Initializes Google Test. This must be called before calling +// RUN_ALL_TESTS(). In particular, it parses a command line for the +// flags that Google Test recognizes. Whenever a Google Test flag is +// seen, it is removed from argv, and *argc is decremented. +// +// No value is returned. Instead, the Google Test flag variables are +// updated. +// +// Calling the function for the second time has no user-visible effect. +void InitGoogleTest(int* argc, char** argv) { + internal::InitGoogleTestImpl(argc, argv); +} + +// This overloaded version can be used in Windows programs compiled in +// UNICODE mode. +void InitGoogleTest(int* argc, wchar_t** argv) { + internal::InitGoogleTestImpl(argc, argv); +} + +} // namespace testing diff --git a/3rdparty/gmock/gtest/src/gtest_main.cc b/3rdparty/gmock/gtest/src/gtest_main.cc new file mode 100644 index 00000000..d20c02fd --- /dev/null +++ b/3rdparty/gmock/gtest/src/gtest_main.cc @@ -0,0 +1,39 @@ +// Copyright 2006, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. + +#include + +#include + +int main(int argc, char **argv) { + std::cout << "Running main() from gtest_main.cc\n"; + + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/3rdparty/gmock/include/gmock/gmock-actions.h b/3rdparty/gmock/include/gmock/gmock-actions.h new file mode 100644 index 00000000..007ad9d3 --- /dev/null +++ b/3rdparty/gmock/include/gmock/gmock-actions.h @@ -0,0 +1,1028 @@ +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used actions. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ + +#include +#include + +#ifndef _WIN32_WCE +#include +#endif + +#include +#include +#include + +namespace testing { + +// To implement an action Foo, define: +// 1. a class FooAction that implements the ActionInterface interface, and +// 2. a factory function that creates an Action object from a +// const FooAction*. +// +// The two-level delegation design follows that of Matcher, providing +// consistency for extension developers. It also eases ownership +// management as Action objects can now be copied like plain values. + +namespace internal { + +template +class MonomorphicDoDefaultActionImpl; + +template +class ActionAdaptor; + +// BuiltInDefaultValue::Get() returns the "built-in" default +// value for type T, which is NULL when T is a pointer type, 0 when T +// is a numeric type, false when T is bool, or "" when T is string or +// std::string. For any other type T, this value is undefined and the +// function will abort the process. +template +class BuiltInDefaultValue { + public: + // This function returns true iff type T has a built-in default value. + static bool Exists() { return false; } + static T Get() { + Assert(false, __FILE__, __LINE__, + "Default action undefined for the function return type."); + return internal::Invalid(); + // The above statement will never be reached, but is required in + // order for this function to compile. + } +}; + +// This partial specialization says that we use the same built-in +// default value for T and const T. +template +class BuiltInDefaultValue { + public: + static bool Exists() { return BuiltInDefaultValue::Exists(); } + static T Get() { return BuiltInDefaultValue::Get(); } +}; + +// This partial specialization defines the default values for pointer +// types. +template +class BuiltInDefaultValue { + public: + static bool Exists() { return true; } + static T* Get() { return NULL; } +}; + +// The following specializations define the default values for +// specific types we care about. +#define GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(type, value) \ + template <> \ + class BuiltInDefaultValue { \ + public: \ + static bool Exists() { return true; } \ + static type Get() { return value; } \ + } + +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(void, ); // NOLINT +#if GTEST_HAS_GLOBAL_STRING +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(::string, ""); +#endif // GTEST_HAS_GLOBAL_STRING +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(::std::string, ""); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(bool, false); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned char, '\0'); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed char, '\0'); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(char, '\0'); + +// There's no need for a default action for signed wchar_t, as that +// type is the same as wchar_t for gcc, and invalid for MSVC. +// +// There's also no need for a default action for unsigned wchar_t, as +// that type is the same as unsigned int for gcc, and invalid for +// MSVC. +#if GMOCK_WCHAR_T_IS_NATIVE_ +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(wchar_t, 0U); // NOLINT +#endif + +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned short, 0U); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed short, 0); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned int, 0U); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed int, 0); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned long, 0UL); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed long, 0L); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(UInt64, 0); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(Int64, 0); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(float, 0); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(double, 0); + +#undef GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_ + +} // namespace internal + +// When an unexpected function call is encountered, Google Mock will +// let it return a default value if the user has specified one for its +// return type, or if the return type has a built-in default value; +// otherwise Google Mock won't know what value to return and will have +// to abort the process. +// +// The DefaultValue class allows a user to specify the +// default value for a type T that is both copyable and publicly +// destructible (i.e. anything that can be used as a function return +// type). The usage is: +// +// // Sets the default value for type T to be foo. +// DefaultValue::Set(foo); +template +class DefaultValue { + public: + // Sets the default value for type T; requires T to be + // copy-constructable and have a public destructor. + static void Set(T x) { + delete value_; + value_ = new T(x); + } + + // Unsets the default value for type T. + static void Clear() { + delete value_; + value_ = NULL; + } + + // Returns true iff the user has set the default value for type T. + static bool IsSet() { return value_ != NULL; } + + // Returns true if T has a default return value set by the user or there + // exists a built-in default value. + static bool Exists() { + return IsSet() || internal::BuiltInDefaultValue::Exists(); + } + + // Returns the default value for type T if the user has set one; + // otherwise returns the built-in default value if there is one; + // otherwise aborts the process. + static T Get() { + return value_ == NULL ? + internal::BuiltInDefaultValue::Get() : *value_; + } + private: + static const T* value_; +}; + +// This partial specialization allows a user to set default values for +// reference types. +template +class DefaultValue { + public: + // Sets the default value for type T&. + static void Set(T& x) { // NOLINT + address_ = &x; + } + + // Unsets the default value for type T&. + static void Clear() { + address_ = NULL; + } + + // Returns true iff the user has set the default value for type T&. + static bool IsSet() { return address_ != NULL; } + + // Returns true if T has a default return value set by the user or there + // exists a built-in default value. + static bool Exists() { + return IsSet() || internal::BuiltInDefaultValue::Exists(); + } + + // Returns the default value for type T& if the user has set one; + // otherwise returns the built-in default value if there is one; + // otherwise aborts the process. + static T& Get() { + return address_ == NULL ? + internal::BuiltInDefaultValue::Get() : *address_; + } + private: + static T* address_; +}; + +// This specialization allows DefaultValue::Get() to +// compile. +template <> +class DefaultValue { + public: + static bool Exists() { return true; } + static void Get() {} +}; + +// Points to the user-set default value for type T. +template +const T* DefaultValue::value_ = NULL; + +// Points to the user-set default value for type T&. +template +T* DefaultValue::address_ = NULL; + +// Implement this interface to define an action for function type F. +template +class ActionInterface { + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + ActionInterface() : is_do_default_(false) {} + + virtual ~ActionInterface() {} + + // Performs the action. This method is not const, as in general an + // action can have side effects and be stateful. For example, a + // get-the-next-element-from-the-collection action will need to + // remember the current element. + virtual Result Perform(const ArgumentTuple& args) = 0; + + // Returns true iff this is the DoDefault() action. + bool IsDoDefault() const { return is_do_default_; } + + private: + template + friend class internal::MonomorphicDoDefaultActionImpl; + + // This private constructor is reserved for implementing + // DoDefault(), the default action for a given mock function. + explicit ActionInterface(bool is_do_default) + : is_do_default_(is_do_default) {} + + // True iff this action is DoDefault(). + const bool is_do_default_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ActionInterface); +}; + +// An Action is a copyable and IMMUTABLE (except by assignment) +// object that represents an action to be taken when a mock function +// of type F is called. The implementation of Action is just a +// linked_ptr to const ActionInterface, so copying is fairly cheap. +// Don't inherit from Action! +// +// You can view an object implementing ActionInterface as a +// concrete action (including its current state), and an Action +// object as a handle to it. +template +class Action { + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + // Constructs a null Action. Needed for storing Action objects in + // STL containers. + Action() : impl_(NULL) {} + + // Constructs an Action from its implementation. + explicit Action(ActionInterface* impl) : impl_(impl) {} + + // Copy constructor. + Action(const Action& action) : impl_(action.impl_) {} + + // This constructor allows us to turn an Action object into an + // Action, as long as F's arguments can be implicitly converted + // to Func's and Func's return type can be implicitly converted to + // F's. + template + explicit Action(const Action& action); + + // Returns true iff this is the DoDefault() action. + bool IsDoDefault() const { return impl_->IsDoDefault(); } + + // Performs the action. Note that this method is const even though + // the corresponding method in ActionInterface is not. The reason + // is that a const Action means that it cannot be re-bound to + // another concrete action, not that the concrete action it binds to + // cannot change state. (Think of the difference between a const + // pointer and a pointer to const.) + Result Perform(const ArgumentTuple& args) const { + return impl_->Perform(args); + } + + private: + template + friend class internal::ActionAdaptor; + + internal::linked_ptr > impl_; +}; + +// The PolymorphicAction class template makes it easy to implement a +// polymorphic action (i.e. an action that can be used in mock +// functions of than one type, e.g. Return()). +// +// To define a polymorphic action, a user first provides a COPYABLE +// implementation class that has a Perform() method template: +// +// class FooAction { +// public: +// template +// Result Perform(const ArgumentTuple& args) const { +// // Processes the arguments and returns a result, using +// // tr1::get(args) to get the N-th (0-based) argument in the tuple. +// } +// ... +// }; +// +// Then the user creates the polymorphic action using +// MakePolymorphicAction(object) where object has type FooAction. See +// the definition of Return(void) and SetArgumentPointee(value) for +// complete examples. +template +class PolymorphicAction { + public: + explicit PolymorphicAction(const Impl& impl) : impl_(impl) {} + + template + operator Action() const { + return Action(new MonomorphicImpl(impl_)); + } + + private: + template + class MonomorphicImpl : public ActionInterface { + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + explicit MonomorphicImpl(const Impl& impl) : impl_(impl) {} + + virtual Result Perform(const ArgumentTuple& args) { + return impl_.template Perform(args); + } + + private: + Impl impl_; + + GTEST_DISALLOW_ASSIGN_(MonomorphicImpl); + }; + + Impl impl_; + + GTEST_DISALLOW_ASSIGN_(PolymorphicAction); +}; + +// Creates an Action from its implementation and returns it. The +// created Action object owns the implementation. +template +Action MakeAction(ActionInterface* impl) { + return Action(impl); +} + +// Creates a polymorphic action from its implementation. This is +// easier to use than the PolymorphicAction constructor as it +// doesn't require you to explicitly write the template argument, e.g. +// +// MakePolymorphicAction(foo); +// vs +// PolymorphicAction(foo); +template +inline PolymorphicAction MakePolymorphicAction(const Impl& impl) { + return PolymorphicAction(impl); +} + +namespace internal { + +// Allows an Action object to pose as an Action, as long as F2 +// and F1 are compatible. +template +class ActionAdaptor : public ActionInterface { + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + explicit ActionAdaptor(const Action& from) : impl_(from.impl_) {} + + virtual Result Perform(const ArgumentTuple& args) { + return impl_->Perform(args); + } + + private: + const internal::linked_ptr > impl_; + + GTEST_DISALLOW_ASSIGN_(ActionAdaptor); +}; + +// Implements the polymorphic Return(x) action, which can be used in +// any function that returns the type of x, regardless of the argument +// types. +// +// Note: The value passed into Return must be converted into +// Function::Result when this action is cast to Action rather than +// when that action is performed. This is important in scenarios like +// +// MOCK_METHOD1(Method, T(U)); +// ... +// { +// Foo foo; +// X x(&foo); +// EXPECT_CALL(mock, Method(_)).WillOnce(Return(x)); +// } +// +// In the example above the variable x holds reference to foo which leaves +// scope and gets destroyed. If copying X just copies a reference to foo, +// that copy will be left with a hanging reference. If conversion to T +// makes a copy of foo, the above code is safe. To support that scenario, we +// need to make sure that the type conversion happens inside the EXPECT_CALL +// statement, and conversion of the result of Return to Action is a +// good place for that. +// +template +class ReturnAction { + public: + // Constructs a ReturnAction object from the value to be returned. + // 'value' is passed by value instead of by const reference in order + // to allow Return("string literal") to compile. + explicit ReturnAction(R value) : value_(value) {} + + // This template type conversion operator allows Return(x) to be + // used in ANY function that returns x's type. + template + operator Action() const { + // Assert statement belongs here because this is the best place to verify + // conditions on F. It produces the clearest error messages + // in most compilers. + // Impl really belongs in this scope as a local class but can't + // because MSVC produces duplicate symbols in different translation units + // in this case. Until MS fixes that bug we put Impl into the class scope + // and put the typedef both here (for use in assert statement) and + // in the Impl class. But both definitions must be the same. + typedef typename Function::Result Result; + GMOCK_COMPILE_ASSERT_( + !internal::is_reference::value, + use_ReturnRef_instead_of_Return_to_return_a_reference); + return Action(new Impl(value_)); + } + + private: + // Implements the Return(x) action for a particular function type F. + template + class Impl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + + // The implicit cast is necessary when Result has more than one + // single-argument constructor (e.g. Result is std::vector) and R + // has a type conversion operator template. In that case, value_(value) + // won't compile as the compiler doesn't known which constructor of + // Result to call. implicit_cast forces the compiler to convert R to + // Result without considering explicit constructors, thus resolving the + // ambiguity. value_ is then initialized using its copy constructor. + explicit Impl(R value) + : value_(::testing::internal::implicit_cast(value)) {} + + virtual Result Perform(const ArgumentTuple&) { return value_; } + + private: + GMOCK_COMPILE_ASSERT_(!internal::is_reference::value, + Result_cannot_be_a_reference_type); + Result value_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; + + R value_; + + GTEST_DISALLOW_ASSIGN_(ReturnAction); +}; + +// Implements the ReturnNull() action. +class ReturnNullAction { + public: + // Allows ReturnNull() to be used in any pointer-returning function. + template + static Result Perform(const ArgumentTuple&) { + GMOCK_COMPILE_ASSERT_(internal::is_pointer::value, + ReturnNull_can_be_used_to_return_a_pointer_only); + return NULL; + } +}; + +// Implements the Return() action. +class ReturnVoidAction { + public: + // Allows Return() to be used in any void-returning function. + template + static void Perform(const ArgumentTuple&) { + CompileAssertTypesEqual(); + } +}; + +// Implements the polymorphic ReturnRef(x) action, which can be used +// in any function that returns a reference to the type of x, +// regardless of the argument types. +template +class ReturnRefAction { + public: + // Constructs a ReturnRefAction object from the reference to be returned. + explicit ReturnRefAction(T& ref) : ref_(ref) {} // NOLINT + + // This template type conversion operator allows ReturnRef(x) to be + // used in ANY function that returns a reference to x's type. + template + operator Action() const { + typedef typename Function::Result Result; + // Asserts that the function return type is a reference. This + // catches the user error of using ReturnRef(x) when Return(x) + // should be used, and generates some helpful error message. + GMOCK_COMPILE_ASSERT_(internal::is_reference::value, + use_Return_instead_of_ReturnRef_to_return_a_value); + return Action(new Impl(ref_)); + } + + private: + // Implements the ReturnRef(x) action for a particular function type F. + template + class Impl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + + explicit Impl(T& ref) : ref_(ref) {} // NOLINT + + virtual Result Perform(const ArgumentTuple&) { + return ref_; + } + + private: + T& ref_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; + + T& ref_; + + GTEST_DISALLOW_ASSIGN_(ReturnRefAction); +}; + +// Implements the DoDefault() action for a particular function type F. +template +class MonomorphicDoDefaultActionImpl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + + MonomorphicDoDefaultActionImpl() : ActionInterface(true) {} + + // For technical reasons, DoDefault() cannot be used inside a + // composite action (e.g. DoAll(...)). It can only be used at the + // top level in an EXPECT_CALL(). If this function is called, the + // user must be using DoDefault() inside a composite action, and we + // have to generate a run-time error. + virtual Result Perform(const ArgumentTuple&) { + Assert(false, __FILE__, __LINE__, + "You are using DoDefault() inside a composite action like " + "DoAll() or WithArgs(). This is not supported for technical " + "reasons. Please instead spell out the default action, or " + "assign the default action to an Action variable and use " + "the variable in various places."); + return internal::Invalid(); + // The above statement will never be reached, but is required in + // order for this function to compile. + } +}; + +// Implements the polymorphic DoDefault() action. +class DoDefaultAction { + public: + // This template type conversion operator allows DoDefault() to be + // used in any function. + template + operator Action() const { + return Action(new MonomorphicDoDefaultActionImpl); + } +}; + +// Implements the Assign action to set a given pointer referent to a +// particular value. +template +class AssignAction { + public: + AssignAction(T1* ptr, T2 value) : ptr_(ptr), value_(value) {} + + template + void Perform(const ArgumentTuple& /* args */) const { + *ptr_ = value_; + } + + private: + T1* const ptr_; + const T2 value_; + + GTEST_DISALLOW_ASSIGN_(AssignAction); +}; + +#if !GTEST_OS_WINDOWS_MOBILE + +// Implements the SetErrnoAndReturn action to simulate return from +// various system calls and libc functions. +template +class SetErrnoAndReturnAction { + public: + SetErrnoAndReturnAction(int errno_value, T result) + : errno_(errno_value), + result_(result) {} + template + Result Perform(const ArgumentTuple& /* args */) const { + errno = errno_; + return result_; + } + + private: + const int errno_; + const T result_; + + GTEST_DISALLOW_ASSIGN_(SetErrnoAndReturnAction); +}; + +#endif // !GTEST_OS_WINDOWS_MOBILE + +// Implements the SetArgumentPointee(x) action for any function +// whose N-th argument (0-based) is a pointer to x's type. The +// template parameter kIsProto is true iff type A is ProtocolMessage, +// proto2::Message, or a sub-class of those. +template +class SetArgumentPointeeAction { + public: + // Constructs an action that sets the variable pointed to by the + // N-th function argument to 'value'. + explicit SetArgumentPointeeAction(const A& value) : value_(value) {} + + template + void Perform(const ArgumentTuple& args) const { + CompileAssertTypesEqual(); + *::std::tr1::get(args) = value_; + } + + private: + const A value_; + + GTEST_DISALLOW_ASSIGN_(SetArgumentPointeeAction); +}; + +template +class SetArgumentPointeeAction { + public: + // Constructs an action that sets the variable pointed to by the + // N-th function argument to 'proto'. Both ProtocolMessage and + // proto2::Message have the CopyFrom() method, so the same + // implementation works for both. + explicit SetArgumentPointeeAction(const Proto& proto) : proto_(new Proto) { + proto_->CopyFrom(proto); + } + + template + void Perform(const ArgumentTuple& args) const { + CompileAssertTypesEqual(); + ::std::tr1::get(args)->CopyFrom(*proto_); + } + + private: + const internal::linked_ptr proto_; + + GTEST_DISALLOW_ASSIGN_(SetArgumentPointeeAction); +}; + +// Implements the InvokeWithoutArgs(f) action. The template argument +// FunctionImpl is the implementation type of f, which can be either a +// function pointer or a functor. InvokeWithoutArgs(f) can be used as an +// Action as long as f's type is compatible with F (i.e. f can be +// assigned to a tr1::function). +template +class InvokeWithoutArgsAction { + public: + // The c'tor makes a copy of function_impl (either a function + // pointer or a functor). + explicit InvokeWithoutArgsAction(FunctionImpl function_impl) + : function_impl_(function_impl) {} + + // Allows InvokeWithoutArgs(f) to be used as any action whose type is + // compatible with f. + template + Result Perform(const ArgumentTuple&) { return function_impl_(); } + + private: + FunctionImpl function_impl_; + + GTEST_DISALLOW_ASSIGN_(InvokeWithoutArgsAction); +}; + +// Implements the InvokeWithoutArgs(object_ptr, &Class::Method) action. +template +class InvokeMethodWithoutArgsAction { + public: + InvokeMethodWithoutArgsAction(Class* obj_ptr, MethodPtr method_ptr) + : obj_ptr_(obj_ptr), method_ptr_(method_ptr) {} + + template + Result Perform(const ArgumentTuple&) const { + return (obj_ptr_->*method_ptr_)(); + } + + private: + Class* const obj_ptr_; + const MethodPtr method_ptr_; + + GTEST_DISALLOW_ASSIGN_(InvokeMethodWithoutArgsAction); +}; + +// Implements the IgnoreResult(action) action. +template +class IgnoreResultAction { + public: + explicit IgnoreResultAction(const A& action) : action_(action) {} + + template + operator Action() const { + // Assert statement belongs here because this is the best place to verify + // conditions on F. It produces the clearest error messages + // in most compilers. + // Impl really belongs in this scope as a local class but can't + // because MSVC produces duplicate symbols in different translation units + // in this case. Until MS fixes that bug we put Impl into the class scope + // and put the typedef both here (for use in assert statement) and + // in the Impl class. But both definitions must be the same. + typedef typename internal::Function::Result Result; + + // Asserts at compile time that F returns void. + CompileAssertTypesEqual(); + + return Action(new Impl(action_)); + } + + private: + template + class Impl : public ActionInterface { + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + explicit Impl(const A& action) : action_(action) {} + + virtual void Perform(const ArgumentTuple& args) { + // Performs the action and ignores its result. + action_.Perform(args); + } + + private: + // Type OriginalFunction is the same as F except that its return + // type is IgnoredValue. + typedef typename internal::Function::MakeResultIgnoredValue + OriginalFunction; + + const Action action_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; + + const A action_; + + GTEST_DISALLOW_ASSIGN_(IgnoreResultAction); +}; + +// A ReferenceWrapper object represents a reference to type T, +// which can be either const or not. It can be explicitly converted +// from, and implicitly converted to, a T&. Unlike a reference, +// ReferenceWrapper can be copied and can survive template type +// inference. This is used to support by-reference arguments in the +// InvokeArgument(...) action. The idea was from "reference +// wrappers" in tr1, which we don't have in our source tree yet. +template +class ReferenceWrapper { + public: + // Constructs a ReferenceWrapper object from a T&. + explicit ReferenceWrapper(T& l_value) : pointer_(&l_value) {} // NOLINT + + // Allows a ReferenceWrapper object to be implicitly converted to + // a T&. + operator T&() const { return *pointer_; } + private: + T* pointer_; +}; + +// Allows the expression ByRef(x) to be printed as a reference to x. +template +void PrintTo(const ReferenceWrapper& ref, ::std::ostream* os) { + T& value = ref; + UniversalPrinter::Print(value, os); +} + +// Does two actions sequentially. Used for implementing the DoAll(a1, +// a2, ...) action. +template +class DoBothAction { + public: + DoBothAction(Action1 action1, Action2 action2) + : action1_(action1), action2_(action2) {} + + // This template type conversion operator allows DoAll(a1, ..., a_n) + // to be used in ANY function of compatible type. + template + operator Action() const { + return Action(new Impl(action1_, action2_)); + } + + private: + // Implements the DoAll(...) action for a particular function type F. + template + class Impl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + typedef typename Function::MakeResultVoid VoidResult; + + Impl(const Action& action1, const Action& action2) + : action1_(action1), action2_(action2) {} + + virtual Result Perform(const ArgumentTuple& args) { + action1_.Perform(args); + return action2_.Perform(args); + } + + private: + const Action action1_; + const Action action2_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; + + Action1 action1_; + Action2 action2_; + + GTEST_DISALLOW_ASSIGN_(DoBothAction); +}; + +} // namespace internal + +// An Unused object can be implicitly constructed from ANY value. +// This is handy when defining actions that ignore some or all of the +// mock function arguments. For example, given +// +// MOCK_METHOD3(Foo, double(const string& label, double x, double y)); +// MOCK_METHOD3(Bar, double(int index, double x, double y)); +// +// instead of +// +// double DistanceToOriginWithLabel(const string& label, double x, double y) { +// return sqrt(x*x + y*y); +// } +// double DistanceToOriginWithIndex(int index, double x, double y) { +// return sqrt(x*x + y*y); +// } +// ... +// EXEPCT_CALL(mock, Foo("abc", _, _)) +// .WillOnce(Invoke(DistanceToOriginWithLabel)); +// EXEPCT_CALL(mock, Bar(5, _, _)) +// .WillOnce(Invoke(DistanceToOriginWithIndex)); +// +// you could write +// +// // We can declare any uninteresting argument as Unused. +// double DistanceToOrigin(Unused, double x, double y) { +// return sqrt(x*x + y*y); +// } +// ... +// EXEPCT_CALL(mock, Foo("abc", _, _)).WillOnce(Invoke(DistanceToOrigin)); +// EXEPCT_CALL(mock, Bar(5, _, _)).WillOnce(Invoke(DistanceToOrigin)); +typedef internal::IgnoredValue Unused; + +// This constructor allows us to turn an Action object into an +// Action, as long as To's arguments can be implicitly converted +// to From's and From's return type cann be implicitly converted to +// To's. +template +template +Action::Action(const Action& from) + : impl_(new internal::ActionAdaptor(from)) {} + +// Creates an action that returns 'value'. 'value' is passed by value +// instead of const reference - otherwise Return("string literal") +// will trigger a compiler error about using array as initializer. +template +internal::ReturnAction Return(R value) { + return internal::ReturnAction(value); +} + +// Creates an action that returns NULL. +inline PolymorphicAction ReturnNull() { + return MakePolymorphicAction(internal::ReturnNullAction()); +} + +// Creates an action that returns from a void function. +inline PolymorphicAction Return() { + return MakePolymorphicAction(internal::ReturnVoidAction()); +} + +// Creates an action that returns the reference to a variable. +template +inline internal::ReturnRefAction ReturnRef(R& x) { // NOLINT + return internal::ReturnRefAction(x); +} + +// Creates an action that does the default action for the give mock function. +inline internal::DoDefaultAction DoDefault() { + return internal::DoDefaultAction(); +} + +// Creates an action that sets the variable pointed by the N-th +// (0-based) function argument to 'value'. +template +PolymorphicAction< + internal::SetArgumentPointeeAction< + N, T, internal::IsAProtocolMessage::value> > +SetArgumentPointee(const T& x) { + return MakePolymorphicAction(internal::SetArgumentPointeeAction< + N, T, internal::IsAProtocolMessage::value>(x)); +} + +// Creates an action that sets a pointer referent to a given value. +template +PolymorphicAction > Assign(T1* ptr, T2 val) { + return MakePolymorphicAction(internal::AssignAction(ptr, val)); +} + +#if !GTEST_OS_WINDOWS_MOBILE + +// Creates an action that sets errno and returns the appropriate error. +template +PolymorphicAction > +SetErrnoAndReturn(int errval, T result) { + return MakePolymorphicAction( + internal::SetErrnoAndReturnAction(errval, result)); +} + +#endif // !GTEST_OS_WINDOWS_MOBILE + +// Various overloads for InvokeWithoutArgs(). + +// Creates an action that invokes 'function_impl' with no argument. +template +PolymorphicAction > +InvokeWithoutArgs(FunctionImpl function_impl) { + return MakePolymorphicAction( + internal::InvokeWithoutArgsAction(function_impl)); +} + +// Creates an action that invokes the given method on the given object +// with no argument. +template +PolymorphicAction > +InvokeWithoutArgs(Class* obj_ptr, MethodPtr method_ptr) { + return MakePolymorphicAction( + internal::InvokeMethodWithoutArgsAction( + obj_ptr, method_ptr)); +} + +// Creates an action that performs an_action and throws away its +// result. In other words, it changes the return type of an_action to +// void. an_action MUST NOT return void, or the code won't compile. +template +inline internal::IgnoreResultAction IgnoreResult(const A& an_action) { + return internal::IgnoreResultAction(an_action); +} + +// Creates a reference wrapper for the given L-value. If necessary, +// you can explicitly specify the type of the reference. For example, +// suppose 'derived' is an object of type Derived, ByRef(derived) +// would wrap a Derived&. If you want to wrap a const Base& instead, +// where Base is a base class of Derived, just write: +// +// ByRef(derived) +template +inline internal::ReferenceWrapper ByRef(T& l_value) { // NOLINT + return internal::ReferenceWrapper(l_value); +} + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ diff --git a/3rdparty/gmock/include/gmock/gmock-cardinalities.h b/3rdparty/gmock/include/gmock/gmock-cardinalities.h new file mode 100644 index 00000000..ae4cb641 --- /dev/null +++ b/3rdparty/gmock/include/gmock/gmock-cardinalities.h @@ -0,0 +1,146 @@ +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used cardinalities. More +// cardinalities can be defined by the user implementing the +// CardinalityInterface interface if necessary. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ + +#include +#include // NOLINT +#include +#include + +namespace testing { + +// To implement a cardinality Foo, define: +// 1. a class FooCardinality that implements the +// CardinalityInterface interface, and +// 2. a factory function that creates a Cardinality object from a +// const FooCardinality*. +// +// The two-level delegation design follows that of Matcher, providing +// consistency for extension developers. It also eases ownership +// management as Cardinality objects can now be copied like plain values. + +// The implementation of a cardinality. +class CardinalityInterface { + public: + virtual ~CardinalityInterface() {} + + // Conservative estimate on the lower/upper bound of the number of + // calls allowed. + virtual int ConservativeLowerBound() const { return 0; } + virtual int ConservativeUpperBound() const { return INT_MAX; } + + // Returns true iff call_count calls will satisfy this cardinality. + virtual bool IsSatisfiedByCallCount(int call_count) const = 0; + + // Returns true iff call_count calls will saturate this cardinality. + virtual bool IsSaturatedByCallCount(int call_count) const = 0; + + // Describes self to an ostream. + virtual void DescribeTo(::std::ostream* os) const = 0; +}; + +// A Cardinality is a copyable and IMMUTABLE (except by assignment) +// object that specifies how many times a mock function is expected to +// be called. The implementation of Cardinality is just a linked_ptr +// to const CardinalityInterface, so copying is fairly cheap. +// Don't inherit from Cardinality! +class Cardinality { + public: + // Constructs a null cardinality. Needed for storing Cardinality + // objects in STL containers. + Cardinality() {} + + // Constructs a Cardinality from its implementation. + explicit Cardinality(const CardinalityInterface* impl) : impl_(impl) {} + + // Conservative estimate on the lower/upper bound of the number of + // calls allowed. + int ConservativeLowerBound() const { return impl_->ConservativeLowerBound(); } + int ConservativeUpperBound() const { return impl_->ConservativeUpperBound(); } + + // Returns true iff call_count calls will satisfy this cardinality. + bool IsSatisfiedByCallCount(int call_count) const { + return impl_->IsSatisfiedByCallCount(call_count); + } + + // Returns true iff call_count calls will saturate this cardinality. + bool IsSaturatedByCallCount(int call_count) const { + return impl_->IsSaturatedByCallCount(call_count); + } + + // Returns true iff call_count calls will over-saturate this + // cardinality, i.e. exceed the maximum number of allowed calls. + bool IsOverSaturatedByCallCount(int call_count) const { + return impl_->IsSaturatedByCallCount(call_count) && + !impl_->IsSatisfiedByCallCount(call_count); + } + + // Describes self to an ostream + void DescribeTo(::std::ostream* os) const { impl_->DescribeTo(os); } + + // Describes the given actual call count to an ostream. + static void DescribeActualCallCountTo(int actual_call_count, + ::std::ostream* os); + private: + internal::linked_ptr impl_; +}; + +// Creates a cardinality that allows at least n calls. +Cardinality AtLeast(int n); + +// Creates a cardinality that allows at most n calls. +Cardinality AtMost(int n); + +// Creates a cardinality that allows any number of calls. +Cardinality AnyNumber(); + +// Creates a cardinality that allows between min and max calls. +Cardinality Between(int min, int max); + +// Creates a cardinality that allows exactly n calls. +Cardinality Exactly(int n); + +// Creates a cardinality from its implementation. +inline Cardinality MakeCardinality(const CardinalityInterface* c) { + return Cardinality(c); +} + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ diff --git a/3rdparty/gmock/include/gmock/gmock-generated-actions.h b/3rdparty/gmock/include/gmock/gmock-generated-actions.h new file mode 100644 index 00000000..2b53c7b9 --- /dev/null +++ b/3rdparty/gmock/include/gmock/gmock-generated-actions.h @@ -0,0 +1,2419 @@ +// This file was GENERATED by a script. DO NOT EDIT BY HAND!!! + +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used variadic actions. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ + +#include +#include + +namespace testing { +namespace internal { + +// InvokeHelper knows how to unpack an N-tuple and invoke an N-ary +// function or method with the unpacked values, where F is a function +// type that takes N arguments. +template +class InvokeHelper; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple<>&) { + return function(); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple<>&) { + return (obj_ptr->*method_ptr)(); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args), get<2>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), get<2>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args), get<2>(args), get<3>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), get<2>(args), + get<3>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args), get<5>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args), get<6>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args), get<5>(args), get<6>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args), get<6>(args), get<7>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args), get<5>(args), get<6>(args), get<7>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args), get<6>(args), get<7>(args), get<8>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args), get<5>(args), get<6>(args), get<7>(args), + get<8>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args), get<6>(args), get<7>(args), get<8>(args), + get<9>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args), get<5>(args), get<6>(args), get<7>(args), + get<8>(args), get<9>(args)); + } +}; + +// CallableHelper has static methods for invoking "callables", +// i.e. function pointers and functors. It uses overloading to +// provide a uniform interface for invoking different kinds of +// callables. In particular, you can use: +// +// CallableHelper::Call(callable, a1, a2, ..., an) +// +// to invoke an n-ary callable, where R is its return type. If an +// argument, say a2, needs to be passed by reference, you should write +// ByRef(a2) instead of a2 in the above expression. +template +class CallableHelper { + public: + // Calls a nullary callable. + template + static R Call(Function function) { return function(); } + + // Calls a unary callable. + + // We deliberately pass a1 by value instead of const reference here + // in case it is a C-string literal. If we had declared the + // parameter as 'const A1& a1' and write Call(function, "Hi"), the + // compiler would've thought A1 is 'char[3]', which causes trouble + // when you need to copy a value of type A1. By declaring the + // parameter as 'A1 a1', the compiler will correctly infer that A1 + // is 'const char*' when it sees Call(function, "Hi"). + // + // Since this function is defined inline, the compiler can get rid + // of the copying of the arguments. Therefore the performance won't + // be hurt. + template + static R Call(Function function, A1 a1) { return function(a1); } + + // Calls a binary callable. + template + static R Call(Function function, A1 a1, A2 a2) { + return function(a1, a2); + } + + // Calls a ternary callable. + template + static R Call(Function function, A1 a1, A2 a2, A3 a3) { + return function(a1, a2, a3); + } + + // Calls a 4-ary callable. + template + static R Call(Function function, A1 a1, A2 a2, A3 a3, A4 a4) { + return function(a1, a2, a3, a4); + } + + // Calls a 5-ary callable. + template + static R Call(Function function, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) { + return function(a1, a2, a3, a4, a5); + } + + // Calls a 6-ary callable. + template + static R Call(Function function, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) { + return function(a1, a2, a3, a4, a5, a6); + } + + // Calls a 7-ary callable. + template + static R Call(Function function, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, + A7 a7) { + return function(a1, a2, a3, a4, a5, a6, a7); + } + + // Calls a 8-ary callable. + template + static R Call(Function function, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, + A7 a7, A8 a8) { + return function(a1, a2, a3, a4, a5, a6, a7, a8); + } + + // Calls a 9-ary callable. + template + static R Call(Function function, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, + A7 a7, A8 a8, A9 a9) { + return function(a1, a2, a3, a4, a5, a6, a7, a8, a9); + } + + // Calls a 10-ary callable. + template + static R Call(Function function, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, + A7 a7, A8 a8, A9 a9, A10 a10) { + return function(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); + } + +}; // class CallableHelper + +// An INTERNAL macro for extracting the type of a tuple field. It's +// subject to change without notice - DO NOT USE IN USER CODE! +#define GMOCK_FIELD_(Tuple, N) \ + typename ::std::tr1::tuple_element::type + +// SelectArgs::type is the +// type of an n-ary function whose i-th (1-based) argument type is the +// k{i}-th (0-based) field of ArgumentTuple, which must be a tuple +// type, and whose return type is Result. For example, +// SelectArgs, 0, 3>::type +// is int(bool, long). +// +// SelectArgs::Select(args) +// returns the selected fields (k1, k2, ..., k_n) of args as a tuple. +// For example, +// SelectArgs, 2, 0>::Select( +// ::std::tr1::make_tuple(true, 'a', 2.5)) +// returns ::std::tr1::tuple (2.5, true). +// +// The numbers in list k1, k2, ..., k_n must be >= 0, where n can be +// in the range [0, 10]. Duplicates are allowed and they don't have +// to be in an ascending or descending order. + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5), + GMOCK_FIELD_(ArgumentTuple, k6), GMOCK_FIELD_(ArgumentTuple, k7), + GMOCK_FIELD_(ArgumentTuple, k8), GMOCK_FIELD_(ArgumentTuple, k9), + GMOCK_FIELD_(ArgumentTuple, k10)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args), get(args), get(args), + get(args), get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& /* args */) { + using ::std::tr1::get; + return SelectedArgs(); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args), get(args), + get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5), + GMOCK_FIELD_(ArgumentTuple, k6)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5), + GMOCK_FIELD_(ArgumentTuple, k6), GMOCK_FIELD_(ArgumentTuple, k7)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args), get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5), + GMOCK_FIELD_(ArgumentTuple, k6), GMOCK_FIELD_(ArgumentTuple, k7), + GMOCK_FIELD_(ArgumentTuple, k8)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args), get(args), get(args), + get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5), + GMOCK_FIELD_(ArgumentTuple, k6), GMOCK_FIELD_(ArgumentTuple, k7), + GMOCK_FIELD_(ArgumentTuple, k8), GMOCK_FIELD_(ArgumentTuple, k9)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args), get(args), get(args), + get(args), get(args)); + } +}; + +#undef GMOCK_FIELD_ + +// Implements the WithArgs action. +template +class WithArgsAction { + public: + explicit WithArgsAction(const InnerAction& action) : action_(action) {} + + template + operator Action() const { return MakeAction(new Impl(action_)); } + + private: + template + class Impl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + + explicit Impl(const InnerAction& action) : action_(action) {} + + virtual Result Perform(const ArgumentTuple& args) { + return action_.Perform(SelectArgs::Select(args)); + } + + private: + typedef typename SelectArgs::type InnerFunctionType; + + Action action_; + }; + + const InnerAction action_; + + GTEST_DISALLOW_ASSIGN_(WithArgsAction); +}; + +// A macro from the ACTION* family (defined later in this file) +// defines an action that can be used in a mock function. Typically, +// these actions only care about a subset of the arguments of the mock +// function. For example, if such an action only uses the second +// argument, it can be used in any mock function that takes >= 2 +// arguments where the type of the second argument is compatible. +// +// Therefore, the action implementation must be prepared to take more +// arguments than it needs. The ExcessiveArg type is used to +// represent those excessive arguments. In order to keep the compiler +// error messages tractable, we define it in the testing namespace +// instead of testing::internal. However, this is an INTERNAL TYPE +// and subject to change without notice, so a user MUST NOT USE THIS +// TYPE DIRECTLY. +struct ExcessiveArg {}; + +// A helper class needed for implementing the ACTION* macros. +template +class ActionHelper { + public: + static Result Perform(Impl* impl, const ::std::tr1::tuple<>& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl<>(args, ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, get<0>(args), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, get<0>(args), + get<1>(args), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, get<0>(args), + get<1>(args), get<2>(args), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, get<0>(args), + get<1>(args), get<2>(args), get<3>(args), ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, + get<0>(args), get<1>(args), get<2>(args), get<3>(args), get<4>(args), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, + get<0>(args), get<1>(args), get<2>(args), get<3>(args), get<4>(args), + get<5>(args), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, + get<0>(args), get<1>(args), get<2>(args), get<3>(args), get<4>(args), + get<5>(args), get<6>(args), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args), get<6>(args), get<7>(args), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args), get<6>(args), get<7>(args), get<8>(args), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args), get<6>(args), get<7>(args), get<8>(args), + get<9>(args)); + } +}; + +} // namespace internal + +// Various overloads for Invoke(). + +// WithArgs(an_action) creates an action that passes +// the selected arguments of the mock function to an_action and +// performs it. It serves as an adaptor between actions with +// different argument lists. C++ doesn't support default arguments for +// function templates, so we have to overload it. +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +// Creates an action that does actions a1, a2, ..., sequentially in +// each invocation. +template +inline internal::DoBothAction +DoAll(Action1 a1, Action2 a2) { + return internal::DoBothAction(a1, a2); +} + +template +inline internal::DoBothAction > +DoAll(Action1 a1, Action2 a2, Action3 a3) { + return DoAll(a1, DoAll(a2, a3)); +} + +template +inline internal::DoBothAction > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4) { + return DoAll(a1, DoAll(a2, a3, a4)); +} + +template +inline internal::DoBothAction > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5) { + return DoAll(a1, DoAll(a2, a3, a4, a5)); +} + +template +inline internal::DoBothAction > > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5, Action6 a6) { + return DoAll(a1, DoAll(a2, a3, a4, a5, a6)); +} + +template +inline internal::DoBothAction > > > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5, Action6 a6, + Action7 a7) { + return DoAll(a1, DoAll(a2, a3, a4, a5, a6, a7)); +} + +template +inline internal::DoBothAction > > > > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5, Action6 a6, + Action7 a7, Action8 a8) { + return DoAll(a1, DoAll(a2, a3, a4, a5, a6, a7, a8)); +} + +template +inline internal::DoBothAction > > > > > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5, Action6 a6, + Action7 a7, Action8 a8, Action9 a9) { + return DoAll(a1, DoAll(a2, a3, a4, a5, a6, a7, a8, a9)); +} + +template +inline internal::DoBothAction > > > > > > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5, Action6 a6, + Action7 a7, Action8 a8, Action9 a9, Action10 a10) { + return DoAll(a1, DoAll(a2, a3, a4, a5, a6, a7, a8, a9, a10)); +} + +} // namespace testing + +// The ACTION* family of macros can be used in a namespace scope to +// define custom actions easily. The syntax: +// +// ACTION(name) { statements; } +// +// will define an action with the given name that executes the +// statements. The value returned by the statements will be used as +// the return value of the action. Inside the statements, you can +// refer to the K-th (0-based) argument of the mock function by +// 'argK', and refer to its type by 'argK_type'. For example: +// +// ACTION(IncrementArg1) { +// arg1_type temp = arg1; +// return ++(*temp); +// } +// +// allows you to write +// +// ...WillOnce(IncrementArg1()); +// +// You can also refer to the entire argument tuple and its type by +// 'args' and 'args_type', and refer to the mock function type and its +// return type by 'function_type' and 'return_type'. +// +// Note that you don't need to specify the types of the mock function +// arguments. However rest assured that your code is still type-safe: +// you'll get a compiler error if *arg1 doesn't support the ++ +// operator, or if the type of ++(*arg1) isn't compatible with the +// mock function's return type, for example. +// +// Sometimes you'll want to parameterize the action. For that you can use +// another macro: +// +// ACTION_P(name, param_name) { statements; } +// +// For example: +// +// ACTION_P(Add, n) { return arg0 + n; } +// +// will allow you to write: +// +// ...WillOnce(Add(5)); +// +// Note that you don't need to provide the type of the parameter +// either. If you need to reference the type of a parameter named +// 'foo', you can write 'foo_type'. For example, in the body of +// ACTION_P(Add, n) above, you can write 'n_type' to refer to the type +// of 'n'. +// +// We also provide ACTION_P2, ACTION_P3, ..., up to ACTION_P10 to support +// multi-parameter actions. +// +// For the purpose of typing, you can view +// +// ACTION_Pk(Foo, p1, ..., pk) { ... } +// +// as shorthand for +// +// template +// FooActionPk Foo(p1_type p1, ..., pk_type pk) { ... } +// +// In particular, you can provide the template type arguments +// explicitly when invoking Foo(), as in Foo(5, false); +// although usually you can rely on the compiler to infer the types +// for you automatically. You can assign the result of expression +// Foo(p1, ..., pk) to a variable of type FooActionPk. This can be useful when composing actions. +// +// You can also overload actions with different numbers of parameters: +// +// ACTION_P(Plus, a) { ... } +// ACTION_P2(Plus, a, b) { ... } +// +// While it's tempting to always use the ACTION* macros when defining +// a new action, you should also consider implementing ActionInterface +// or using MakePolymorphicAction() instead, especially if you need to +// use the action a lot. While these approaches require more work, +// they give you more control on the types of the mock function +// arguments and the action parameters, which in general leads to +// better compiler error messages that pay off in the long run. They +// also allow overloading actions based on parameter types (as opposed +// to just based on the number of parameters). +// +// CAVEAT: +// +// ACTION*() can only be used in a namespace scope. The reason is +// that C++ doesn't yet allow function-local types to be used to +// instantiate templates. The up-coming C++0x standard will fix this. +// Once that's done, we'll consider supporting using ACTION*() inside +// a function. +// +// MORE INFORMATION: +// +// To learn more about using these macros, please search for 'ACTION' +// on http://code.google.com/p/googlemock/wiki/CookBook. + +// An internal macro needed for implementing ACTION*(). +#define GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_\ + const args_type& args GTEST_ATTRIBUTE_UNUSED_,\ + arg0_type arg0 GTEST_ATTRIBUTE_UNUSED_,\ + arg1_type arg1 GTEST_ATTRIBUTE_UNUSED_,\ + arg2_type arg2 GTEST_ATTRIBUTE_UNUSED_,\ + arg3_type arg3 GTEST_ATTRIBUTE_UNUSED_,\ + arg4_type arg4 GTEST_ATTRIBUTE_UNUSED_,\ + arg5_type arg5 GTEST_ATTRIBUTE_UNUSED_,\ + arg6_type arg6 GTEST_ATTRIBUTE_UNUSED_,\ + arg7_type arg7 GTEST_ATTRIBUTE_UNUSED_,\ + arg8_type arg8 GTEST_ATTRIBUTE_UNUSED_,\ + arg9_type arg9 GTEST_ATTRIBUTE_UNUSED_ + +// Sometimes you want to give an action explicit template parameters +// that cannot be inferred from its value parameters. ACTION() and +// ACTION_P*() don't support that. ACTION_TEMPLATE() remedies that +// and can be viewed as an extension to ACTION() and ACTION_P*(). +// +// The syntax: +// +// ACTION_TEMPLATE(ActionName, +// HAS_m_TEMPLATE_PARAMS(kind1, name1, ..., kind_m, name_m), +// AND_n_VALUE_PARAMS(p1, ..., p_n)) { statements; } +// +// defines an action template that takes m explicit template +// parameters and n value parameters. name_i is the name of the i-th +// template parameter, and kind_i specifies whether it's a typename, +// an integral constant, or a template. p_i is the name of the i-th +// value parameter. +// +// Example: +// +// // DuplicateArg(output) converts the k-th argument of the mock +// // function to type T and copies it to *output. +// ACTION_TEMPLATE(DuplicateArg, +// HAS_2_TEMPLATE_PARAMS(int, k, typename, T), +// AND_1_VALUE_PARAMS(output)) { +// *output = T(std::tr1::get(args)); +// } +// ... +// int n; +// EXPECT_CALL(mock, Foo(_, _)) +// .WillOnce(DuplicateArg<1, unsigned char>(&n)); +// +// To create an instance of an action template, write: +// +// ActionName(v1, ..., v_n) +// +// where the ts are the template arguments and the vs are the value +// arguments. The value argument types are inferred by the compiler. +// If you want to explicitly specify the value argument types, you can +// provide additional template arguments: +// +// ActionName(v1, ..., v_n) +// +// where u_i is the desired type of v_i. +// +// ACTION_TEMPLATE and ACTION/ACTION_P* can be overloaded on the +// number of value parameters, but not on the number of template +// parameters. Without the restriction, the meaning of the following +// is unclear: +// +// OverloadedAction(x); +// +// Are we using a single-template-parameter action where 'bool' refers +// to the type of x, or are we using a two-template-parameter action +// where the compiler is asked to infer the type of x? +// +// Implementation notes: +// +// GMOCK_INTERNAL_*_HAS_m_TEMPLATE_PARAMS and +// GMOCK_INTERNAL_*_AND_n_VALUE_PARAMS are internal macros for +// implementing ACTION_TEMPLATE. The main trick we use is to create +// new macro invocations when expanding a macro. For example, we have +// +// #define ACTION_TEMPLATE(name, template_params, value_params) +// ... GMOCK_INTERNAL_DECL_##template_params ... +// +// which causes ACTION_TEMPLATE(..., HAS_1_TEMPLATE_PARAMS(typename, T), ...) +// to expand to +// +// ... GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS(typename, T) ... +// +// Since GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS is a macro, the +// preprocessor will continue to expand it to +// +// ... typename T ... +// +// This technique conforms to the C++ standard and is portable. It +// allows us to implement action templates using O(N) code, where N is +// the maximum number of template/value parameters supported. Without +// using it, we'd have to devote O(N^2) amount of code to implement all +// combinations of m and n. + +// Declares the template parameters. +#define GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS(kind0, name0) kind0 name0 +#define GMOCK_INTERNAL_DECL_HAS_2_TEMPLATE_PARAMS(kind0, name0, kind1, \ + name1) kind0 name0, kind1 name1 +#define GMOCK_INTERNAL_DECL_HAS_3_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2) kind0 name0, kind1 name1, kind2 name2 +#define GMOCK_INTERNAL_DECL_HAS_4_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3) kind0 name0, kind1 name1, kind2 name2, \ + kind3 name3 +#define GMOCK_INTERNAL_DECL_HAS_5_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4) kind0 name0, kind1 name1, \ + kind2 name2, kind3 name3, kind4 name4 +#define GMOCK_INTERNAL_DECL_HAS_6_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5) kind0 name0, \ + kind1 name1, kind2 name2, kind3 name3, kind4 name4, kind5 name5 +#define GMOCK_INTERNAL_DECL_HAS_7_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \ + name6) kind0 name0, kind1 name1, kind2 name2, kind3 name3, kind4 name4, \ + kind5 name5, kind6 name6 +#define GMOCK_INTERNAL_DECL_HAS_8_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \ + kind7, name7) kind0 name0, kind1 name1, kind2 name2, kind3 name3, \ + kind4 name4, kind5 name5, kind6 name6, kind7 name7 +#define GMOCK_INTERNAL_DECL_HAS_9_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \ + kind7, name7, kind8, name8) kind0 name0, kind1 name1, kind2 name2, \ + kind3 name3, kind4 name4, kind5 name5, kind6 name6, kind7 name7, \ + kind8 name8 +#define GMOCK_INTERNAL_DECL_HAS_10_TEMPLATE_PARAMS(kind0, name0, kind1, \ + name1, kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \ + name6, kind7, name7, kind8, name8, kind9, name9) kind0 name0, \ + kind1 name1, kind2 name2, kind3 name3, kind4 name4, kind5 name5, \ + kind6 name6, kind7 name7, kind8 name8, kind9 name9 + +// Lists the template parameters. +#define GMOCK_INTERNAL_LIST_HAS_1_TEMPLATE_PARAMS(kind0, name0) name0 +#define GMOCK_INTERNAL_LIST_HAS_2_TEMPLATE_PARAMS(kind0, name0, kind1, \ + name1) name0, name1 +#define GMOCK_INTERNAL_LIST_HAS_3_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2) name0, name1, name2 +#define GMOCK_INTERNAL_LIST_HAS_4_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3) name0, name1, name2, name3 +#define GMOCK_INTERNAL_LIST_HAS_5_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4) name0, name1, name2, name3, \ + name4 +#define GMOCK_INTERNAL_LIST_HAS_6_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5) name0, name1, \ + name2, name3, name4, name5 +#define GMOCK_INTERNAL_LIST_HAS_7_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \ + name6) name0, name1, name2, name3, name4, name5, name6 +#define GMOCK_INTERNAL_LIST_HAS_8_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \ + kind7, name7) name0, name1, name2, name3, name4, name5, name6, name7 +#define GMOCK_INTERNAL_LIST_HAS_9_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \ + kind7, name7, kind8, name8) name0, name1, name2, name3, name4, name5, \ + name6, name7, name8 +#define GMOCK_INTERNAL_LIST_HAS_10_TEMPLATE_PARAMS(kind0, name0, kind1, \ + name1, kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \ + name6, kind7, name7, kind8, name8, kind9, name9) name0, name1, name2, \ + name3, name4, name5, name6, name7, name8, name9 + +// Declares the types of value parameters. +#define GMOCK_INTERNAL_DECL_TYPE_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_DECL_TYPE_AND_1_VALUE_PARAMS(p0) , typename p0##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_2_VALUE_PARAMS(p0, p1) , \ + typename p0##_type, typename p1##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_3_VALUE_PARAMS(p0, p1, p2) , \ + typename p0##_type, typename p1##_type, typename p2##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_4_VALUE_PARAMS(p0, p1, p2, p3) , \ + typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) , \ + typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) , \ + typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type, typename p5##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) , typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type, typename p5##_type, \ + typename p6##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7) , typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type, typename p5##_type, \ + typename p6##_type, typename p7##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8) , typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type, typename p5##_type, \ + typename p6##_type, typename p7##_type, typename p8##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8, p9) , typename p0##_type, typename p1##_type, \ + typename p2##_type, typename p3##_type, typename p4##_type, \ + typename p5##_type, typename p6##_type, typename p7##_type, \ + typename p8##_type, typename p9##_type + +// Initializes the value parameters. +#define GMOCK_INTERNAL_INIT_AND_0_VALUE_PARAMS()\ + () +#define GMOCK_INTERNAL_INIT_AND_1_VALUE_PARAMS(p0)\ + (p0##_type gmock_p0) : p0(gmock_p0) +#define GMOCK_INTERNAL_INIT_AND_2_VALUE_PARAMS(p0, p1)\ + (p0##_type gmock_p0, p1##_type gmock_p1) : p0(gmock_p0), p1(gmock_p1) +#define GMOCK_INTERNAL_INIT_AND_3_VALUE_PARAMS(p0, p1, p2)\ + (p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2) +#define GMOCK_INTERNAL_INIT_AND_4_VALUE_PARAMS(p0, p1, p2, p3)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3) +#define GMOCK_INTERNAL_INIT_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4) +#define GMOCK_INTERNAL_INIT_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5) +#define GMOCK_INTERNAL_INIT_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6) +#define GMOCK_INTERNAL_INIT_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7) +#define GMOCK_INTERNAL_INIT_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8) +#define GMOCK_INTERNAL_INIT_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8, \ + p9##_type gmock_p9) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8), p9(gmock_p9) + +// Declares the fields for storing the value parameters. +#define GMOCK_INTERNAL_DEFN_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_DEFN_AND_1_VALUE_PARAMS(p0) p0##_type p0; +#define GMOCK_INTERNAL_DEFN_AND_2_VALUE_PARAMS(p0, p1) p0##_type p0; \ + p1##_type p1; +#define GMOCK_INTERNAL_DEFN_AND_3_VALUE_PARAMS(p0, p1, p2) p0##_type p0; \ + p1##_type p1; p2##_type p2; +#define GMOCK_INTERNAL_DEFN_AND_4_VALUE_PARAMS(p0, p1, p2, p3) p0##_type p0; \ + p1##_type p1; p2##_type p2; p3##_type p3; +#define GMOCK_INTERNAL_DEFN_AND_5_VALUE_PARAMS(p0, p1, p2, p3, \ + p4) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; +#define GMOCK_INTERNAL_DEFN_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, \ + p5) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; \ + p5##_type p5; +#define GMOCK_INTERNAL_DEFN_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; \ + p5##_type p5; p6##_type p6; +#define GMOCK_INTERNAL_DEFN_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; \ + p5##_type p5; p6##_type p6; p7##_type p7; +#define GMOCK_INTERNAL_DEFN_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; \ + p4##_type p4; p5##_type p5; p6##_type p6; p7##_type p7; p8##_type p8; +#define GMOCK_INTERNAL_DEFN_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; \ + p4##_type p4; p5##_type p5; p6##_type p6; p7##_type p7; p8##_type p8; \ + p9##_type p9; + +// Lists the value parameters. +#define GMOCK_INTERNAL_LIST_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_LIST_AND_1_VALUE_PARAMS(p0) p0 +#define GMOCK_INTERNAL_LIST_AND_2_VALUE_PARAMS(p0, p1) p0, p1 +#define GMOCK_INTERNAL_LIST_AND_3_VALUE_PARAMS(p0, p1, p2) p0, p1, p2 +#define GMOCK_INTERNAL_LIST_AND_4_VALUE_PARAMS(p0, p1, p2, p3) p0, p1, p2, p3 +#define GMOCK_INTERNAL_LIST_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) p0, p1, \ + p2, p3, p4 +#define GMOCK_INTERNAL_LIST_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) p0, \ + p1, p2, p3, p4, p5 +#define GMOCK_INTERNAL_LIST_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) p0, p1, p2, p3, p4, p5, p6 +#define GMOCK_INTERNAL_LIST_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7) p0, p1, p2, p3, p4, p5, p6, p7 +#define GMOCK_INTERNAL_LIST_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8) p0, p1, p2, p3, p4, p5, p6, p7, p8 +#define GMOCK_INTERNAL_LIST_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9) p0, p1, p2, p3, p4, p5, p6, p7, p8, p9 + +// Lists the value parameter types. +#define GMOCK_INTERNAL_LIST_TYPE_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_LIST_TYPE_AND_1_VALUE_PARAMS(p0) , p0##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_2_VALUE_PARAMS(p0, p1) , p0##_type, \ + p1##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_3_VALUE_PARAMS(p0, p1, p2) , p0##_type, \ + p1##_type, p2##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_4_VALUE_PARAMS(p0, p1, p2, p3) , \ + p0##_type, p1##_type, p2##_type, p3##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) , \ + p0##_type, p1##_type, p2##_type, p3##_type, p4##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) , \ + p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, p5##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, p5##_type, \ + p6##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, \ + p5##_type, p6##_type, p7##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, \ + p5##_type, p6##_type, p7##_type, p8##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8, p9) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, \ + p5##_type, p6##_type, p7##_type, p8##_type, p9##_type + +// Declares the value parameters. +#define GMOCK_INTERNAL_DECL_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_DECL_AND_1_VALUE_PARAMS(p0) p0##_type p0 +#define GMOCK_INTERNAL_DECL_AND_2_VALUE_PARAMS(p0, p1) p0##_type p0, \ + p1##_type p1 +#define GMOCK_INTERNAL_DECL_AND_3_VALUE_PARAMS(p0, p1, p2) p0##_type p0, \ + p1##_type p1, p2##_type p2 +#define GMOCK_INTERNAL_DECL_AND_4_VALUE_PARAMS(p0, p1, p2, p3) p0##_type p0, \ + p1##_type p1, p2##_type p2, p3##_type p3 +#define GMOCK_INTERNAL_DECL_AND_5_VALUE_PARAMS(p0, p1, p2, p3, \ + p4) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4 +#define GMOCK_INTERNAL_DECL_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, \ + p5) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \ + p5##_type p5 +#define GMOCK_INTERNAL_DECL_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \ + p5##_type p5, p6##_type p6 +#define GMOCK_INTERNAL_DECL_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \ + p5##_type p5, p6##_type p6, p7##_type p7 +#define GMOCK_INTERNAL_DECL_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8 +#define GMOCK_INTERNAL_DECL_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8, \ + p9##_type p9 + +// The suffix of the class template implementing the action template. +#define GMOCK_INTERNAL_COUNT_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_COUNT_AND_1_VALUE_PARAMS(p0) P +#define GMOCK_INTERNAL_COUNT_AND_2_VALUE_PARAMS(p0, p1) P2 +#define GMOCK_INTERNAL_COUNT_AND_3_VALUE_PARAMS(p0, p1, p2) P3 +#define GMOCK_INTERNAL_COUNT_AND_4_VALUE_PARAMS(p0, p1, p2, p3) P4 +#define GMOCK_INTERNAL_COUNT_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) P5 +#define GMOCK_INTERNAL_COUNT_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) P6 +#define GMOCK_INTERNAL_COUNT_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6) P7 +#define GMOCK_INTERNAL_COUNT_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7) P8 +#define GMOCK_INTERNAL_COUNT_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8) P9 +#define GMOCK_INTERNAL_COUNT_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9) P10 + +// The name of the class template implementing the action template. +#define GMOCK_ACTION_CLASS_(name, value_params)\ + GMOCK_CONCAT_TOKEN_(name##Action, GMOCK_INTERNAL_COUNT_##value_params) + +#define ACTION_TEMPLATE(name, template_params, value_params)\ + template \ + class GMOCK_ACTION_CLASS_(name, value_params) {\ + public:\ + GMOCK_ACTION_CLASS_(name, value_params)\ + GMOCK_INTERNAL_INIT_##value_params {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + explicit gmock_Impl GMOCK_INTERNAL_INIT_##value_params {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + GMOCK_INTERNAL_DEFN_##value_params\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(\ + new gmock_Impl(GMOCK_INTERNAL_LIST_##value_params));\ + }\ + GMOCK_INTERNAL_DEFN_##value_params\ + private:\ + GTEST_DISALLOW_ASSIGN_(GMOCK_ACTION_CLASS_(name, value_params));\ + };\ + template \ + inline GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params> name(\ + GMOCK_INTERNAL_DECL_##value_params) {\ + return GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params>(\ + GMOCK_INTERNAL_LIST_##value_params);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params>::gmock_Impl::\ + gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION(name)\ + class name##Action {\ + public:\ + name##Action() {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl() {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl());\ + }\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##Action);\ + };\ + inline name##Action name() {\ + return name##Action();\ + }\ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##Action::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P(name, p0)\ + template \ + class name##ActionP {\ + public:\ + name##ActionP(p0##_type gmock_p0) : p0(gmock_p0) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + explicit gmock_Impl(p0##_type gmock_p0) : p0(gmock_p0) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0));\ + }\ + p0##_type p0;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP);\ + };\ + template \ + inline name##ActionP name(p0##_type p0) {\ + return name##ActionP(p0);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P2(name, p0, p1)\ + template \ + class name##ActionP2 {\ + public:\ + name##ActionP2(p0##_type gmock_p0, p1##_type gmock_p1) : p0(gmock_p0), \ + p1(gmock_p1) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1) : p0(gmock_p0), \ + p1(gmock_p1) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP2);\ + };\ + template \ + inline name##ActionP2 name(p0##_type p0, \ + p1##_type p1) {\ + return name##ActionP2(p0, p1);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP2::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P3(name, p0, p1, p2)\ + template \ + class name##ActionP3 {\ + public:\ + name##ActionP3(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP3);\ + };\ + template \ + inline name##ActionP3 name(p0##_type p0, \ + p1##_type p1, p2##_type p2) {\ + return name##ActionP3(p0, p1, p2);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP3::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P4(name, p0, p1, p2, p3)\ + template \ + class name##ActionP4 {\ + public:\ + name##ActionP4(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP4);\ + };\ + template \ + inline name##ActionP4 name(p0##_type p0, p1##_type p1, p2##_type p2, \ + p3##_type p3) {\ + return name##ActionP4(p0, p1, \ + p2, p3);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP4::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P5(name, p0, p1, p2, p3, p4)\ + template \ + class name##ActionP5 {\ + public:\ + name##ActionP5(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, \ + p4##_type gmock_p4) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4) : p0(gmock_p0), \ + p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), p4(gmock_p4) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP5);\ + };\ + template \ + inline name##ActionP5 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4) {\ + return name##ActionP5(p0, p1, p2, p3, p4);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP5::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P6(name, p0, p1, p2, p3, p4, p5)\ + template \ + class name##ActionP6 {\ + public:\ + name##ActionP6(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4, p5));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP6);\ + };\ + template \ + inline name##ActionP6 name(p0##_type p0, p1##_type p1, p2##_type p2, \ + p3##_type p3, p4##_type p4, p5##_type p5) {\ + return name##ActionP6(p0, p1, p2, p3, p4, p5);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP6::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P7(name, p0, p1, p2, p3, p4, p5, p6)\ + template \ + class name##ActionP7 {\ + public:\ + name##ActionP7(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), \ + p6(gmock_p6) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4, p5, \ + p6));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP7);\ + };\ + template \ + inline name##ActionP7 name(p0##_type p0, p1##_type p1, \ + p2##_type p2, p3##_type p3, p4##_type p4, p5##_type p5, \ + p6##_type p6) {\ + return name##ActionP7(p0, p1, p2, p3, p4, p5, p6);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP7::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P8(name, p0, p1, p2, p3, p4, p5, p6, p7)\ + template \ + class name##ActionP8 {\ + public:\ + name##ActionP8(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, \ + p7##_type gmock_p7) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7) : p0(gmock_p0), \ + p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), \ + p5(gmock_p5), p6(gmock_p6), p7(gmock_p7) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4, p5, \ + p6, p7));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP8);\ + };\ + template \ + inline name##ActionP8 name(p0##_type p0, \ + p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, p5##_type p5, \ + p6##_type p6, p7##_type p7) {\ + return name##ActionP8(p0, p1, p2, p3, p4, p5, \ + p6, p7);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP8::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P9(name, p0, p1, p2, p3, p4, p5, p6, p7, p8)\ + template \ + class name##ActionP9 {\ + public:\ + name##ActionP9(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7), p8(gmock_p8) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP9);\ + };\ + template \ + inline name##ActionP9 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, \ + p8##_type p8) {\ + return name##ActionP9(p0, p1, p2, \ + p3, p4, p5, p6, p7, p8);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP9::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P10(name, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)\ + template \ + class name##ActionP10 {\ + public:\ + name##ActionP10(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8, p9##_type gmock_p9) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7), p8(gmock_p8), p9(gmock_p9) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8, \ + p9##_type gmock_p9) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7), p8(gmock_p8), p9(gmock_p9) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + p9##_type p9;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8, p9));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + p9##_type p9;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP10);\ + };\ + template \ + inline name##ActionP10 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8, \ + p9##_type p9) {\ + return name##ActionP10(p0, \ + p1, p2, p3, p4, p5, p6, p7, p8, p9);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP10::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +// TODO(wan@google.com): move the following to a different .h file +// such that we don't have to run 'pump' every time the code is +// updated. +namespace testing { + +// The ACTION*() macros trigger warning C4100 (unreferenced formal +// parameter) in MSVC with -W4. Unfortunately they cannot be fixed in +// the macro definition, as the warnings are generated when the macro +// is expanded and macro expansion cannot contain #pragma. Therefore +// we suppress them here. +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4100) +#endif + +// Various overloads for InvokeArgument(). +// +// The InvokeArgument(a1, a2, ..., a_k) action invokes the N-th +// (0-based) argument, which must be a k-ary callable, of the mock +// function, with arguments a1, a2, ..., a_k. +// +// Notes: +// +// 1. The arguments are passed by value by default. If you need to +// pass an argument by reference, wrap it inside ByRef(). For +// example, +// +// InvokeArgument<1>(5, string("Hello"), ByRef(foo)) +// +// passes 5 and string("Hello") by value, and passes foo by +// reference. +// +// 2. If the callable takes an argument by reference but ByRef() is +// not used, it will receive the reference to a copy of the value, +// instead of the original value. For example, when the 0-th +// argument of the mock function takes a const string&, the action +// +// InvokeArgument<0>(string("Hello")) +// +// makes a copy of the temporary string("Hello") object and passes a +// reference of the copy, instead of the original temporary object, +// to the callable. This makes it easy for a user to define an +// InvokeArgument action from temporary values and have it performed +// later. + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_0_VALUE_PARAMS()) { + return internal::CallableHelper::Call( + ::std::tr1::get(args)); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_1_VALUE_PARAMS(p0)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_2_VALUE_PARAMS(p0, p1)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_3_VALUE_PARAMS(p0, p1, p2)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1, p2); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_4_VALUE_PARAMS(p0, p1, p2, p3)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1, p2, p3); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1, p2, p3, p4); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1, p2, p3, p4, p5); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1, p2, p3, p4, p5, p6); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1, p2, p3, p4, p5, p6, p7); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7, p8)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1, p2, p3, p4, p5, p6, p7, p8); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); +} + +// Various overloads for ReturnNew(). +// +// The ReturnNew(a1, a2, ..., a_k) action returns a pointer to a new +// instance of type T, constructed on the heap with constructor arguments +// a1, a2, ..., and a_k. The caller assumes ownership of the returned value. +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_0_VALUE_PARAMS()) { + return new T(); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_1_VALUE_PARAMS(p0)) { + return new T(p0); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_2_VALUE_PARAMS(p0, p1)) { + return new T(p0, p1); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_3_VALUE_PARAMS(p0, p1, p2)) { + return new T(p0, p1, p2); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_4_VALUE_PARAMS(p0, p1, p2, p3)) { + return new T(p0, p1, p2, p3); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)) { + return new T(p0, p1, p2, p3, p4); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)) { + return new T(p0, p1, p2, p3, p4, p5); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)) { + return new T(p0, p1, p2, p3, p4, p5, p6); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7)) { + return new T(p0, p1, p2, p3, p4, p5, p6, p7); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7, p8)) { + return new T(p0, p1, p2, p3, p4, p5, p6, p7, p8); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)) { + return new T(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ diff --git a/3rdparty/gmock/include/gmock/gmock-generated-actions.h.pump b/3rdparty/gmock/include/gmock/gmock-generated-actions.h.pump new file mode 100644 index 00000000..75b1e7a0 --- /dev/null +++ b/3rdparty/gmock/include/gmock/gmock-generated-actions.h.pump @@ -0,0 +1,825 @@ +$$ -*- mode: c++; -*- +$$ This is a Pump source file. Please use Pump to convert it to +$$ gmock-generated-actions.h. +$$ +$var n = 10 $$ The maximum arity we support. +$$}} This meta comment fixes auto-indentation in editors. +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used variadic actions. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ + +#include +#include + +namespace testing { +namespace internal { + +// InvokeHelper knows how to unpack an N-tuple and invoke an N-ary +// function or method with the unpacked values, where F is a function +// type that takes N arguments. +template +class InvokeHelper; + + +$range i 0..n +$for i [[ +$range j 1..i +$var types = [[$for j [[, typename A$j]]]] +$var as = [[$for j, [[A$j]]]] +$var args = [[$if i==0 [[]] $else [[ args]]]] +$var import = [[$if i==0 [[]] $else [[ + using ::std::tr1::get; + +]]]] +$var gets = [[$for j, [[get<$(j - 1)>(args)]]]] +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple<$as>&$args) { +$import return function($gets); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple<$as>&$args) { +$import return (obj_ptr->*method_ptr)($gets); + } +}; + + +]] +// CallableHelper has static methods for invoking "callables", +// i.e. function pointers and functors. It uses overloading to +// provide a uniform interface for invoking different kinds of +// callables. In particular, you can use: +// +// CallableHelper::Call(callable, a1, a2, ..., an) +// +// to invoke an n-ary callable, where R is its return type. If an +// argument, say a2, needs to be passed by reference, you should write +// ByRef(a2) instead of a2 in the above expression. +template +class CallableHelper { + public: + // Calls a nullary callable. + template + static R Call(Function function) { return function(); } + + // Calls a unary callable. + + // We deliberately pass a1 by value instead of const reference here + // in case it is a C-string literal. If we had declared the + // parameter as 'const A1& a1' and write Call(function, "Hi"), the + // compiler would've thought A1 is 'char[3]', which causes trouble + // when you need to copy a value of type A1. By declaring the + // parameter as 'A1 a1', the compiler will correctly infer that A1 + // is 'const char*' when it sees Call(function, "Hi"). + // + // Since this function is defined inline, the compiler can get rid + // of the copying of the arguments. Therefore the performance won't + // be hurt. + template + static R Call(Function function, A1 a1) { return function(a1); } + +$range i 2..n +$for i +[[ +$var arity = [[$if i==2 [[binary]] $elif i==3 [[ternary]] $else [[$i-ary]]]] + + // Calls a $arity callable. + +$range j 1..i +$var typename_As = [[$for j, [[typename A$j]]]] +$var Aas = [[$for j, [[A$j a$j]]]] +$var as = [[$for j, [[a$j]]]] +$var typename_Ts = [[$for j, [[typename T$j]]]] +$var Ts = [[$for j, [[T$j]]]] + template + static R Call(Function function, $Aas) { + return function($as); + } + +]] + +}; // class CallableHelper + +// An INTERNAL macro for extracting the type of a tuple field. It's +// subject to change without notice - DO NOT USE IN USER CODE! +#define GMOCK_FIELD_(Tuple, N) \ + typename ::std::tr1::tuple_element::type + +$range i 1..n + +// SelectArgs::type is the +// type of an n-ary function whose i-th (1-based) argument type is the +// k{i}-th (0-based) field of ArgumentTuple, which must be a tuple +// type, and whose return type is Result. For example, +// SelectArgs, 0, 3>::type +// is int(bool, long). +// +// SelectArgs::Select(args) +// returns the selected fields (k1, k2, ..., k_n) of args as a tuple. +// For example, +// SelectArgs, 2, 0>::Select( +// ::std::tr1::make_tuple(true, 'a', 2.5)) +// returns ::std::tr1::tuple (2.5, true). +// +// The numbers in list k1, k2, ..., k_n must be >= 0, where n can be +// in the range [0, $n]. Duplicates are allowed and they don't have +// to be in an ascending or descending order. + +template +class SelectArgs { + public: + typedef Result type($for i, [[GMOCK_FIELD_(ArgumentTuple, k$i)]]); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs($for i, [[get(args)]]); + } +}; + + +$for i [[ +$range j 1..n +$range j1 1..i-1 +template +class SelectArgs { + public: + typedef Result type($for j1, [[GMOCK_FIELD_(ArgumentTuple, k$j1)]]); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& [[]] +$if i == 1 [[/* args */]] $else [[args]]) { + using ::std::tr1::get; + return SelectedArgs($for j1, [[get(args)]]); + } +}; + + +]] +#undef GMOCK_FIELD_ + +$var ks = [[$for i, [[k$i]]]] + +// Implements the WithArgs action. +template +class WithArgsAction { + public: + explicit WithArgsAction(const InnerAction& action) : action_(action) {} + + template + operator Action() const { return MakeAction(new Impl(action_)); } + + private: + template + class Impl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + + explicit Impl(const InnerAction& action) : action_(action) {} + + virtual Result Perform(const ArgumentTuple& args) { + return action_.Perform(SelectArgs::Select(args)); + } + + private: + typedef typename SelectArgs::type InnerFunctionType; + + Action action_; + }; + + const InnerAction action_; + + GTEST_DISALLOW_ASSIGN_(WithArgsAction); +}; + +// A macro from the ACTION* family (defined later in this file) +// defines an action that can be used in a mock function. Typically, +// these actions only care about a subset of the arguments of the mock +// function. For example, if such an action only uses the second +// argument, it can be used in any mock function that takes >= 2 +// arguments where the type of the second argument is compatible. +// +// Therefore, the action implementation must be prepared to take more +// arguments than it needs. The ExcessiveArg type is used to +// represent those excessive arguments. In order to keep the compiler +// error messages tractable, we define it in the testing namespace +// instead of testing::internal. However, this is an INTERNAL TYPE +// and subject to change without notice, so a user MUST NOT USE THIS +// TYPE DIRECTLY. +struct ExcessiveArg {}; + +// A helper class needed for implementing the ACTION* macros. +template +class ActionHelper { + public: +$range i 0..n +$for i + +[[ +$var template = [[$if i==0 [[]] $else [[ +$range j 0..i-1 + template <$for j, [[typename A$j]]> +]]]] +$range j 0..i-1 +$var As = [[$for j, [[A$j]]]] +$var as = [[$for j, [[get<$j>(args)]]]] +$range k 1..n-i +$var eas = [[$for k, [[ExcessiveArg()]]]] +$var arg_list = [[$if (i==0) | (i==n) [[$as$eas]] $else [[$as, $eas]]]] +$template + static Result Perform(Impl* impl, const ::std::tr1::tuple<$As>& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl<$As>(args, $arg_list); + } + +]] +}; + +} // namespace internal + +// Various overloads for Invoke(). + +// WithArgs(an_action) creates an action that passes +// the selected arguments of the mock function to an_action and +// performs it. It serves as an adaptor between actions with +// different argument lists. C++ doesn't support default arguments for +// function templates, so we have to overload it. + +$range i 1..n +$for i [[ +$range j 1..i +template <$for j [[int k$j, ]]typename InnerAction> +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + + +]] +// Creates an action that does actions a1, a2, ..., sequentially in +// each invocation. +$range i 2..n +$for i [[ +$range j 2..i +$var types = [[$for j, [[typename Action$j]]]] +$var Aas = [[$for j [[, Action$j a$j]]]] + +template +$range k 1..i-1 + +inline $for k [[internal::DoBothAction]] + +DoAll(Action1 a1$Aas) { +$if i==2 [[ + + return internal::DoBothAction(a1, a2); +]] $else [[ +$range j2 2..i + + return DoAll(a1, DoAll($for j2, [[a$j2]])); +]] + +} + +]] + +} // namespace testing + +// The ACTION* family of macros can be used in a namespace scope to +// define custom actions easily. The syntax: +// +// ACTION(name) { statements; } +// +// will define an action with the given name that executes the +// statements. The value returned by the statements will be used as +// the return value of the action. Inside the statements, you can +// refer to the K-th (0-based) argument of the mock function by +// 'argK', and refer to its type by 'argK_type'. For example: +// +// ACTION(IncrementArg1) { +// arg1_type temp = arg1; +// return ++(*temp); +// } +// +// allows you to write +// +// ...WillOnce(IncrementArg1()); +// +// You can also refer to the entire argument tuple and its type by +// 'args' and 'args_type', and refer to the mock function type and its +// return type by 'function_type' and 'return_type'. +// +// Note that you don't need to specify the types of the mock function +// arguments. However rest assured that your code is still type-safe: +// you'll get a compiler error if *arg1 doesn't support the ++ +// operator, or if the type of ++(*arg1) isn't compatible with the +// mock function's return type, for example. +// +// Sometimes you'll want to parameterize the action. For that you can use +// another macro: +// +// ACTION_P(name, param_name) { statements; } +// +// For example: +// +// ACTION_P(Add, n) { return arg0 + n; } +// +// will allow you to write: +// +// ...WillOnce(Add(5)); +// +// Note that you don't need to provide the type of the parameter +// either. If you need to reference the type of a parameter named +// 'foo', you can write 'foo_type'. For example, in the body of +// ACTION_P(Add, n) above, you can write 'n_type' to refer to the type +// of 'n'. +// +// We also provide ACTION_P2, ACTION_P3, ..., up to ACTION_P$n to support +// multi-parameter actions. +// +// For the purpose of typing, you can view +// +// ACTION_Pk(Foo, p1, ..., pk) { ... } +// +// as shorthand for +// +// template +// FooActionPk Foo(p1_type p1, ..., pk_type pk) { ... } +// +// In particular, you can provide the template type arguments +// explicitly when invoking Foo(), as in Foo(5, false); +// although usually you can rely on the compiler to infer the types +// for you automatically. You can assign the result of expression +// Foo(p1, ..., pk) to a variable of type FooActionPk. This can be useful when composing actions. +// +// You can also overload actions with different numbers of parameters: +// +// ACTION_P(Plus, a) { ... } +// ACTION_P2(Plus, a, b) { ... } +// +// While it's tempting to always use the ACTION* macros when defining +// a new action, you should also consider implementing ActionInterface +// or using MakePolymorphicAction() instead, especially if you need to +// use the action a lot. While these approaches require more work, +// they give you more control on the types of the mock function +// arguments and the action parameters, which in general leads to +// better compiler error messages that pay off in the long run. They +// also allow overloading actions based on parameter types (as opposed +// to just based on the number of parameters). +// +// CAVEAT: +// +// ACTION*() can only be used in a namespace scope. The reason is +// that C++ doesn't yet allow function-local types to be used to +// instantiate templates. The up-coming C++0x standard will fix this. +// Once that's done, we'll consider supporting using ACTION*() inside +// a function. +// +// MORE INFORMATION: +// +// To learn more about using these macros, please search for 'ACTION' +// on http://code.google.com/p/googlemock/wiki/CookBook. + +$range i 0..n +$range k 0..n-1 + +// An internal macro needed for implementing ACTION*(). +#define GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_\ + const args_type& args GTEST_ATTRIBUTE_UNUSED_ +$for k [[,\ + arg$k[[]]_type arg$k GTEST_ATTRIBUTE_UNUSED_]] + + +// Sometimes you want to give an action explicit template parameters +// that cannot be inferred from its value parameters. ACTION() and +// ACTION_P*() don't support that. ACTION_TEMPLATE() remedies that +// and can be viewed as an extension to ACTION() and ACTION_P*(). +// +// The syntax: +// +// ACTION_TEMPLATE(ActionName, +// HAS_m_TEMPLATE_PARAMS(kind1, name1, ..., kind_m, name_m), +// AND_n_VALUE_PARAMS(p1, ..., p_n)) { statements; } +// +// defines an action template that takes m explicit template +// parameters and n value parameters. name_i is the name of the i-th +// template parameter, and kind_i specifies whether it's a typename, +// an integral constant, or a template. p_i is the name of the i-th +// value parameter. +// +// Example: +// +// // DuplicateArg(output) converts the k-th argument of the mock +// // function to type T and copies it to *output. +// ACTION_TEMPLATE(DuplicateArg, +// HAS_2_TEMPLATE_PARAMS(int, k, typename, T), +// AND_1_VALUE_PARAMS(output)) { +// *output = T(std::tr1::get(args)); +// } +// ... +// int n; +// EXPECT_CALL(mock, Foo(_, _)) +// .WillOnce(DuplicateArg<1, unsigned char>(&n)); +// +// To create an instance of an action template, write: +// +// ActionName(v1, ..., v_n) +// +// where the ts are the template arguments and the vs are the value +// arguments. The value argument types are inferred by the compiler. +// If you want to explicitly specify the value argument types, you can +// provide additional template arguments: +// +// ActionName(v1, ..., v_n) +// +// where u_i is the desired type of v_i. +// +// ACTION_TEMPLATE and ACTION/ACTION_P* can be overloaded on the +// number of value parameters, but not on the number of template +// parameters. Without the restriction, the meaning of the following +// is unclear: +// +// OverloadedAction(x); +// +// Are we using a single-template-parameter action where 'bool' refers +// to the type of x, or are we using a two-template-parameter action +// where the compiler is asked to infer the type of x? +// +// Implementation notes: +// +// GMOCK_INTERNAL_*_HAS_m_TEMPLATE_PARAMS and +// GMOCK_INTERNAL_*_AND_n_VALUE_PARAMS are internal macros for +// implementing ACTION_TEMPLATE. The main trick we use is to create +// new macro invocations when expanding a macro. For example, we have +// +// #define ACTION_TEMPLATE(name, template_params, value_params) +// ... GMOCK_INTERNAL_DECL_##template_params ... +// +// which causes ACTION_TEMPLATE(..., HAS_1_TEMPLATE_PARAMS(typename, T), ...) +// to expand to +// +// ... GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS(typename, T) ... +// +// Since GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS is a macro, the +// preprocessor will continue to expand it to +// +// ... typename T ... +// +// This technique conforms to the C++ standard and is portable. It +// allows us to implement action templates using O(N) code, where N is +// the maximum number of template/value parameters supported. Without +// using it, we'd have to devote O(N^2) amount of code to implement all +// combinations of m and n. + +// Declares the template parameters. + +$range j 1..n +$for j [[ +$range m 0..j-1 +#define GMOCK_INTERNAL_DECL_HAS_$j[[]] +_TEMPLATE_PARAMS($for m, [[kind$m, name$m]]) $for m, [[kind$m name$m]] + + +]] + +// Lists the template parameters. + +$for j [[ +$range m 0..j-1 +#define GMOCK_INTERNAL_LIST_HAS_$j[[]] +_TEMPLATE_PARAMS($for m, [[kind$m, name$m]]) $for m, [[name$m]] + + +]] + +// Declares the types of value parameters. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_DECL_TYPE_AND_$i[[]] +_VALUE_PARAMS($for j, [[p$j]]) $for j [[, typename p$j##_type]] + + +]] + +// Initializes the value parameters. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_INIT_AND_$i[[]]_VALUE_PARAMS($for j, [[p$j]])\ + ($for j, [[p$j##_type gmock_p$j]])$if i>0 [[ : ]]$for j, [[p$j(gmock_p$j)]] + + +]] + +// Declares the fields for storing the value parameters. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_DEFN_AND_$i[[]] +_VALUE_PARAMS($for j, [[p$j]]) $for j [[p$j##_type p$j; ]] + + +]] + +// Lists the value parameters. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_LIST_AND_$i[[]] +_VALUE_PARAMS($for j, [[p$j]]) $for j, [[p$j]] + + +]] + +// Lists the value parameter types. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_LIST_TYPE_AND_$i[[]] +_VALUE_PARAMS($for j, [[p$j]]) $for j [[, p$j##_type]] + + +]] + +// Declares the value parameters. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_DECL_AND_$i[[]]_VALUE_PARAMS($for j, [[p$j]]) [[]] +$for j, [[p$j##_type p$j]] + + +]] + +// The suffix of the class template implementing the action template. +$for i [[ + + +$range j 0..i-1 +#define GMOCK_INTERNAL_COUNT_AND_$i[[]]_VALUE_PARAMS($for j, [[p$j]]) [[]] +$if i==1 [[P]] $elif i>=2 [[P$i]] +]] + + +// The name of the class template implementing the action template. +#define GMOCK_ACTION_CLASS_(name, value_params)\ + GMOCK_CONCAT_TOKEN_(name##Action, GMOCK_INTERNAL_COUNT_##value_params) + +$range k 0..n-1 + +#define ACTION_TEMPLATE(name, template_params, value_params)\ + template \ + class GMOCK_ACTION_CLASS_(name, value_params) {\ + public:\ + GMOCK_ACTION_CLASS_(name, value_params)\ + GMOCK_INTERNAL_INIT_##value_params {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + explicit gmock_Impl GMOCK_INTERNAL_INIT_##value_params {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template <$for k, [[typename arg$k[[]]_type]]>\ + return_type gmock_PerformImpl(const args_type& args[[]] +$for k [[, arg$k[[]]_type arg$k]]) const;\ + GMOCK_INTERNAL_DEFN_##value_params\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(\ + new gmock_Impl(GMOCK_INTERNAL_LIST_##value_params));\ + }\ + GMOCK_INTERNAL_DEFN_##value_params\ + private:\ + GTEST_DISALLOW_ASSIGN_(GMOCK_ACTION_CLASS_(name, value_params));\ + };\ + template \ + inline GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params> name(\ + GMOCK_INTERNAL_DECL_##value_params) {\ + return GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params>(\ + GMOCK_INTERNAL_LIST_##value_params);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params>::gmock_Impl::\ + gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +$for i + +[[ +$var template = [[$if i==0 [[]] $else [[ +$range j 0..i-1 + + template <$for j, [[typename p$j##_type]]>\ +]]]] +$var class_name = [[name##Action[[$if i==0 [[]] $elif i==1 [[P]] + $else [[P$i]]]]]] +$range j 0..i-1 +$var ctor_param_list = [[$for j, [[p$j##_type gmock_p$j]]]] +$var param_types_and_names = [[$for j, [[p$j##_type p$j]]]] +$var inits = [[$if i==0 [[]] $else [[ : $for j, [[p$j(gmock_p$j)]]]]]] +$var param_field_decls = [[$for j +[[ + + p$j##_type p$j;\ +]]]] +$var param_field_decls2 = [[$for j +[[ + + p$j##_type p$j;\ +]]]] +$var params = [[$for j, [[p$j]]]] +$var param_types = [[$if i==0 [[]] $else [[<$for j, [[p$j##_type]]>]]]] +$var typename_arg_types = [[$for k, [[typename arg$k[[]]_type]]]] +$var arg_types_and_names = [[$for k, [[arg$k[[]]_type arg$k]]]] +$var macro_name = [[$if i==0 [[ACTION]] $elif i==1 [[ACTION_P]] + $else [[ACTION_P$i]]]] + +#define $macro_name(name$for j [[, p$j]])\$template + class $class_name {\ + public:\ + $class_name($ctor_param_list)$inits {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + [[$if i==1 [[explicit ]]]]gmock_Impl($ctor_param_list)$inits {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template <$typename_arg_types>\ + return_type gmock_PerformImpl(const args_type& args, [[]] +$arg_types_and_names) const;\$param_field_decls + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl($params));\ + }\$param_field_decls2 + private:\ + GTEST_DISALLOW_ASSIGN_($class_name);\ + };\$template + inline $class_name$param_types name($param_types_and_names) {\ + return $class_name$param_types($params);\ + }\$template + template \ + template <$typename_arg_types>\ + typename ::testing::internal::Function::Result\ + $class_name$param_types::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const +]] +$$ } // This meta comment fixes auto-indentation in Emacs. It won't +$$ // show up in the generated code. + + +// TODO(wan@google.com): move the following to a different .h file +// such that we don't have to run 'pump' every time the code is +// updated. +namespace testing { + +// The ACTION*() macros trigger warning C4100 (unreferenced formal +// parameter) in MSVC with -W4. Unfortunately they cannot be fixed in +// the macro definition, as the warnings are generated when the macro +// is expanded and macro expansion cannot contain #pragma. Therefore +// we suppress them here. +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4100) +#endif + +// Various overloads for InvokeArgument(). +// +// The InvokeArgument(a1, a2, ..., a_k) action invokes the N-th +// (0-based) argument, which must be a k-ary callable, of the mock +// function, with arguments a1, a2, ..., a_k. +// +// Notes: +// +// 1. The arguments are passed by value by default. If you need to +// pass an argument by reference, wrap it inside ByRef(). For +// example, +// +// InvokeArgument<1>(5, string("Hello"), ByRef(foo)) +// +// passes 5 and string("Hello") by value, and passes foo by +// reference. +// +// 2. If the callable takes an argument by reference but ByRef() is +// not used, it will receive the reference to a copy of the value, +// instead of the original value. For example, when the 0-th +// argument of the mock function takes a const string&, the action +// +// InvokeArgument<0>(string("Hello")) +// +// makes a copy of the temporary string("Hello") object and passes a +// reference of the copy, instead of the original temporary object, +// to the callable. This makes it easy for a user to define an +// InvokeArgument action from temporary values and have it performed +// later. + +$range i 0..n +$for i [[ +$range j 0..i-1 + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_$i[[]]_VALUE_PARAMS($for j, [[p$j]])) { + return internal::CallableHelper::Call( + ::std::tr1::get(args)$for j [[, p$j]]); +} + +]] + +// Various overloads for ReturnNew(). +// +// The ReturnNew(a1, a2, ..., a_k) action returns a pointer to a new +// instance of type T, constructed on the heap with constructor arguments +// a1, a2, ..., and a_k. The caller assumes ownership of the returned value. +$range i 0..n +$for i [[ +$range j 0..i-1 +$var ps = [[$for j, [[p$j]]]] + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_$i[[]]_VALUE_PARAMS($ps)) { + return new T($ps); +} + +]] + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ diff --git a/3rdparty/gmock/include/gmock/gmock-generated-function-mockers.h b/3rdparty/gmock/include/gmock/gmock-generated-function-mockers.h new file mode 100644 index 00000000..3b2ede1e --- /dev/null +++ b/3rdparty/gmock/include/gmock/gmock-generated-function-mockers.h @@ -0,0 +1,924 @@ +// This file was GENERATED by a script. DO NOT EDIT BY HAND!!! + +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements function mockers of various arities. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ + +#include +#include + +namespace testing { +namespace internal { + +template +class FunctionMockerBase; + +// Note: class FunctionMocker really belongs to the ::testing +// namespace. However if we define it in ::testing, MSVC will +// complain when classes in ::testing::internal declare it as a +// friend class template. To workaround this compiler bug, we define +// FunctionMocker in ::testing::internal and import it into ::testing. +template +class FunctionMocker; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With() { + return this->current_spec(); + } + + R Invoke() { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple()); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1)); + return this->current_spec(); + } + + R Invoke(A1 a1) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2, m3)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2, m3, m4)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2, m3, m4, + m5)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5, A6); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5, + const Matcher& m6) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2, m3, m4, m5, + m6)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5, a6)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5, A6, A7); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5, + const Matcher& m6, const Matcher& m7) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2, m3, m4, m5, + m6, m7)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5, a6, a7)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5, A6, A7, A8); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5, + const Matcher& m6, const Matcher& m7, const Matcher& m8) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2, m3, m4, m5, + m6, m7, m8)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5, a6, a7, a8)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5, A6, A7, A8, A9); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5, + const Matcher& m6, const Matcher& m7, const Matcher& m8, + const Matcher& m9) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2, m3, m4, m5, + m6, m7, m8, m9)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5, a6, a7, a8, a9)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5, + const Matcher& m6, const Matcher& m7, const Matcher& m8, + const Matcher& m9, const Matcher& m10) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2, m3, m4, m5, + m6, m7, m8, m9, m10)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, + A10 a10) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5, a6, a7, a8, a9, + a10)); + } +}; + +} // namespace internal + +// The style guide prohibits "using" statements in a namespace scope +// inside a header file. However, the FunctionMocker class template +// is meant to be defined in the ::testing namespace. The following +// line is just a trick for working around a bug in MSVC 8.0, which +// cannot handle it if we define FunctionMocker in ::testing. +using internal::FunctionMocker; + +// The result type of function type F. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_RESULT_(tn, F) tn ::testing::internal::Function::Result + +// The type of argument N of function type F. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_ARG_(tn, F, N) tn ::testing::internal::Function::Argument##N + +// The matcher type for argument N of function type F. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_MATCHER_(tn, F, N) const ::testing::Matcher& + +// The variable for mocking the given method. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_MOCKER_(arity, constness, Method) \ + GMOCK_CONCAT_TOKEN_(gmock##constness##arity##_##Method##_, __LINE__) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD0_(tn, constness, ct, Method, F) \ + GMOCK_RESULT_(tn, F) ct Method() constness { \ + GMOCK_COMPILE_ASSERT_(::std::tr1::tuple_size< \ + tn ::testing::internal::Function::ArgumentTuple>::value == 0, \ + this_method_does_not_take_0_arguments); \ + GMOCK_MOCKER_(0, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(0, constness, Method).Invoke(); \ + } \ + ::testing::MockSpec& \ + gmock_##Method() constness { \ + return GMOCK_MOCKER_(0, constness, Method).RegisterOwner(this).With(); \ + } \ + mutable ::testing::FunctionMocker GMOCK_MOCKER_(0, constness, Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD1_(tn, constness, ct, Method, F) \ + GMOCK_RESULT_(tn, F) ct Method(GMOCK_ARG_(tn, F, 1) gmock_a1) constness { \ + GMOCK_COMPILE_ASSERT_(::std::tr1::tuple_size< \ + tn ::testing::internal::Function::ArgumentTuple>::value == 1, \ + this_method_does_not_take_1_argument); \ + GMOCK_MOCKER_(1, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(1, constness, Method).Invoke(gmock_a1); \ + } \ + ::testing::MockSpec& \ + gmock_##Method(GMOCK_MATCHER_(tn, F, 1) gmock_a1) constness { \ + return GMOCK_MOCKER_(1, constness, \ + Method).RegisterOwner(this).With(gmock_a1); \ + } \ + mutable ::testing::FunctionMocker GMOCK_MOCKER_(1, constness, Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD2_(tn, constness, ct, Method, F) \ + GMOCK_RESULT_(tn, F) ct Method(GMOCK_ARG_(tn, F, 1) gmock_a1, \ + GMOCK_ARG_(tn, F, 2) gmock_a2) constness { \ + GMOCK_COMPILE_ASSERT_(::std::tr1::tuple_size< \ + tn ::testing::internal::Function::ArgumentTuple>::value == 2, \ + this_method_does_not_take_2_arguments); \ + GMOCK_MOCKER_(2, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(2, constness, Method).Invoke(gmock_a1, gmock_a2); \ + } \ + ::testing::MockSpec& \ + gmock_##Method(GMOCK_MATCHER_(tn, F, 1) gmock_a1, \ + GMOCK_MATCHER_(tn, F, 2) gmock_a2) constness { \ + return GMOCK_MOCKER_(2, constness, \ + Method).RegisterOwner(this).With(gmock_a1, gmock_a2); \ + } \ + mutable ::testing::FunctionMocker GMOCK_MOCKER_(2, constness, Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD3_(tn, constness, ct, Method, F) \ + GMOCK_RESULT_(tn, F) ct Method(GMOCK_ARG_(tn, F, 1) gmock_a1, \ + GMOCK_ARG_(tn, F, 2) gmock_a2, \ + GMOCK_ARG_(tn, F, 3) gmock_a3) constness { \ + GMOCK_COMPILE_ASSERT_(::std::tr1::tuple_size< \ + tn ::testing::internal::Function::ArgumentTuple>::value == 3, \ + this_method_does_not_take_3_arguments); \ + GMOCK_MOCKER_(3, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(3, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3); \ + } \ + ::testing::MockSpec& \ + gmock_##Method(GMOCK_MATCHER_(tn, F, 1) gmock_a1, \ + GMOCK_MATCHER_(tn, F, 2) gmock_a2, \ + GMOCK_MATCHER_(tn, F, 3) gmock_a3) constness { \ + return GMOCK_MOCKER_(3, constness, \ + Method).RegisterOwner(this).With(gmock_a1, gmock_a2, gmock_a3); \ + } \ + mutable ::testing::FunctionMocker GMOCK_MOCKER_(3, constness, Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD4_(tn, constness, ct, Method, F) \ + GMOCK_RESULT_(tn, F) ct Method(GMOCK_ARG_(tn, F, 1) gmock_a1, \ + GMOCK_ARG_(tn, F, 2) gmock_a2, \ + GMOCK_ARG_(tn, F, 3) gmock_a3, \ + GMOCK_ARG_(tn, F, 4) gmock_a4) constness { \ + GMOCK_COMPILE_ASSERT_(::std::tr1::tuple_size< \ + tn ::testing::internal::Function::ArgumentTuple>::value == 4, \ + this_method_does_not_take_4_arguments); \ + GMOCK_MOCKER_(4, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(4, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4); \ + } \ + ::testing::MockSpec& \ + gmock_##Method(GMOCK_MATCHER_(tn, F, 1) gmock_a1, \ + GMOCK_MATCHER_(tn, F, 2) gmock_a2, \ + GMOCK_MATCHER_(tn, F, 3) gmock_a3, \ + GMOCK_MATCHER_(tn, F, 4) gmock_a4) constness { \ + return GMOCK_MOCKER_(4, constness, \ + Method).RegisterOwner(this).With(gmock_a1, gmock_a2, gmock_a3, \ + gmock_a4); \ + } \ + mutable ::testing::FunctionMocker GMOCK_MOCKER_(4, constness, Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD5_(tn, constness, ct, Method, F) \ + GMOCK_RESULT_(tn, F) ct Method(GMOCK_ARG_(tn, F, 1) gmock_a1, \ + GMOCK_ARG_(tn, F, 2) gmock_a2, \ + GMOCK_ARG_(tn, F, 3) gmock_a3, \ + GMOCK_ARG_(tn, F, 4) gmock_a4, \ + GMOCK_ARG_(tn, F, 5) gmock_a5) constness { \ + GMOCK_COMPILE_ASSERT_(::std::tr1::tuple_size< \ + tn ::testing::internal::Function::ArgumentTuple>::value == 5, \ + this_method_does_not_take_5_arguments); \ + GMOCK_MOCKER_(5, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(5, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5); \ + } \ + ::testing::MockSpec& \ + gmock_##Method(GMOCK_MATCHER_(tn, F, 1) gmock_a1, \ + GMOCK_MATCHER_(tn, F, 2) gmock_a2, \ + GMOCK_MATCHER_(tn, F, 3) gmock_a3, \ + GMOCK_MATCHER_(tn, F, 4) gmock_a4, \ + GMOCK_MATCHER_(tn, F, 5) gmock_a5) constness { \ + return GMOCK_MOCKER_(5, constness, \ + Method).RegisterOwner(this).With(gmock_a1, gmock_a2, gmock_a3, \ + gmock_a4, gmock_a5); \ + } \ + mutable ::testing::FunctionMocker GMOCK_MOCKER_(5, constness, Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD6_(tn, constness, ct, Method, F) \ + GMOCK_RESULT_(tn, F) ct Method(GMOCK_ARG_(tn, F, 1) gmock_a1, \ + GMOCK_ARG_(tn, F, 2) gmock_a2, \ + GMOCK_ARG_(tn, F, 3) gmock_a3, \ + GMOCK_ARG_(tn, F, 4) gmock_a4, \ + GMOCK_ARG_(tn, F, 5) gmock_a5, \ + GMOCK_ARG_(tn, F, 6) gmock_a6) constness { \ + GMOCK_COMPILE_ASSERT_(::std::tr1::tuple_size< \ + tn ::testing::internal::Function::ArgumentTuple>::value == 6, \ + this_method_does_not_take_6_arguments); \ + GMOCK_MOCKER_(6, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(6, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6); \ + } \ + ::testing::MockSpec& \ + gmock_##Method(GMOCK_MATCHER_(tn, F, 1) gmock_a1, \ + GMOCK_MATCHER_(tn, F, 2) gmock_a2, \ + GMOCK_MATCHER_(tn, F, 3) gmock_a3, \ + GMOCK_MATCHER_(tn, F, 4) gmock_a4, \ + GMOCK_MATCHER_(tn, F, 5) gmock_a5, \ + GMOCK_MATCHER_(tn, F, 6) gmock_a6) constness { \ + return GMOCK_MOCKER_(6, constness, \ + Method).RegisterOwner(this).With(gmock_a1, gmock_a2, gmock_a3, \ + gmock_a4, gmock_a5, gmock_a6); \ + } \ + mutable ::testing::FunctionMocker GMOCK_MOCKER_(6, constness, Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD7_(tn, constness, ct, Method, F) \ + GMOCK_RESULT_(tn, F) ct Method(GMOCK_ARG_(tn, F, 1) gmock_a1, \ + GMOCK_ARG_(tn, F, 2) gmock_a2, \ + GMOCK_ARG_(tn, F, 3) gmock_a3, \ + GMOCK_ARG_(tn, F, 4) gmock_a4, \ + GMOCK_ARG_(tn, F, 5) gmock_a5, \ + GMOCK_ARG_(tn, F, 6) gmock_a6, \ + GMOCK_ARG_(tn, F, 7) gmock_a7) constness { \ + GMOCK_COMPILE_ASSERT_(::std::tr1::tuple_size< \ + tn ::testing::internal::Function::ArgumentTuple>::value == 7, \ + this_method_does_not_take_7_arguments); \ + GMOCK_MOCKER_(7, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(7, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7); \ + } \ + ::testing::MockSpec& \ + gmock_##Method(GMOCK_MATCHER_(tn, F, 1) gmock_a1, \ + GMOCK_MATCHER_(tn, F, 2) gmock_a2, \ + GMOCK_MATCHER_(tn, F, 3) gmock_a3, \ + GMOCK_MATCHER_(tn, F, 4) gmock_a4, \ + GMOCK_MATCHER_(tn, F, 5) gmock_a5, \ + GMOCK_MATCHER_(tn, F, 6) gmock_a6, \ + GMOCK_MATCHER_(tn, F, 7) gmock_a7) constness { \ + return GMOCK_MOCKER_(7, constness, \ + Method).RegisterOwner(this).With(gmock_a1, gmock_a2, gmock_a3, \ + gmock_a4, gmock_a5, gmock_a6, gmock_a7); \ + } \ + mutable ::testing::FunctionMocker GMOCK_MOCKER_(7, constness, Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD8_(tn, constness, ct, Method, F) \ + GMOCK_RESULT_(tn, F) ct Method(GMOCK_ARG_(tn, F, 1) gmock_a1, \ + GMOCK_ARG_(tn, F, 2) gmock_a2, \ + GMOCK_ARG_(tn, F, 3) gmock_a3, \ + GMOCK_ARG_(tn, F, 4) gmock_a4, \ + GMOCK_ARG_(tn, F, 5) gmock_a5, \ + GMOCK_ARG_(tn, F, 6) gmock_a6, \ + GMOCK_ARG_(tn, F, 7) gmock_a7, \ + GMOCK_ARG_(tn, F, 8) gmock_a8) constness { \ + GMOCK_COMPILE_ASSERT_(::std::tr1::tuple_size< \ + tn ::testing::internal::Function::ArgumentTuple>::value == 8, \ + this_method_does_not_take_8_arguments); \ + GMOCK_MOCKER_(8, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(8, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8); \ + } \ + ::testing::MockSpec& \ + gmock_##Method(GMOCK_MATCHER_(tn, F, 1) gmock_a1, \ + GMOCK_MATCHER_(tn, F, 2) gmock_a2, \ + GMOCK_MATCHER_(tn, F, 3) gmock_a3, \ + GMOCK_MATCHER_(tn, F, 4) gmock_a4, \ + GMOCK_MATCHER_(tn, F, 5) gmock_a5, \ + GMOCK_MATCHER_(tn, F, 6) gmock_a6, \ + GMOCK_MATCHER_(tn, F, 7) gmock_a7, \ + GMOCK_MATCHER_(tn, F, 8) gmock_a8) constness { \ + return GMOCK_MOCKER_(8, constness, \ + Method).RegisterOwner(this).With(gmock_a1, gmock_a2, gmock_a3, \ + gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8); \ + } \ + mutable ::testing::FunctionMocker GMOCK_MOCKER_(8, constness, Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD9_(tn, constness, ct, Method, F) \ + GMOCK_RESULT_(tn, F) ct Method(GMOCK_ARG_(tn, F, 1) gmock_a1, \ + GMOCK_ARG_(tn, F, 2) gmock_a2, \ + GMOCK_ARG_(tn, F, 3) gmock_a3, \ + GMOCK_ARG_(tn, F, 4) gmock_a4, \ + GMOCK_ARG_(tn, F, 5) gmock_a5, \ + GMOCK_ARG_(tn, F, 6) gmock_a6, \ + GMOCK_ARG_(tn, F, 7) gmock_a7, \ + GMOCK_ARG_(tn, F, 8) gmock_a8, \ + GMOCK_ARG_(tn, F, 9) gmock_a9) constness { \ + GMOCK_COMPILE_ASSERT_(::std::tr1::tuple_size< \ + tn ::testing::internal::Function::ArgumentTuple>::value == 9, \ + this_method_does_not_take_9_arguments); \ + GMOCK_MOCKER_(9, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(9, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8, \ + gmock_a9); \ + } \ + ::testing::MockSpec& \ + gmock_##Method(GMOCK_MATCHER_(tn, F, 1) gmock_a1, \ + GMOCK_MATCHER_(tn, F, 2) gmock_a2, \ + GMOCK_MATCHER_(tn, F, 3) gmock_a3, \ + GMOCK_MATCHER_(tn, F, 4) gmock_a4, \ + GMOCK_MATCHER_(tn, F, 5) gmock_a5, \ + GMOCK_MATCHER_(tn, F, 6) gmock_a6, \ + GMOCK_MATCHER_(tn, F, 7) gmock_a7, \ + GMOCK_MATCHER_(tn, F, 8) gmock_a8, \ + GMOCK_MATCHER_(tn, F, 9) gmock_a9) constness { \ + return GMOCK_MOCKER_(9, constness, \ + Method).RegisterOwner(this).With(gmock_a1, gmock_a2, gmock_a3, \ + gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8, gmock_a9); \ + } \ + mutable ::testing::FunctionMocker GMOCK_MOCKER_(9, constness, Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD10_(tn, constness, ct, Method, F) \ + GMOCK_RESULT_(tn, F) ct Method(GMOCK_ARG_(tn, F, 1) gmock_a1, \ + GMOCK_ARG_(tn, F, 2) gmock_a2, \ + GMOCK_ARG_(tn, F, 3) gmock_a3, \ + GMOCK_ARG_(tn, F, 4) gmock_a4, \ + GMOCK_ARG_(tn, F, 5) gmock_a5, \ + GMOCK_ARG_(tn, F, 6) gmock_a6, \ + GMOCK_ARG_(tn, F, 7) gmock_a7, \ + GMOCK_ARG_(tn, F, 8) gmock_a8, \ + GMOCK_ARG_(tn, F, 9) gmock_a9, \ + GMOCK_ARG_(tn, F, 10) gmock_a10) constness { \ + GMOCK_COMPILE_ASSERT_(::std::tr1::tuple_size< \ + tn ::testing::internal::Function::ArgumentTuple>::value == 10, \ + this_method_does_not_take_10_arguments); \ + GMOCK_MOCKER_(10, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(10, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8, gmock_a9, \ + gmock_a10); \ + } \ + ::testing::MockSpec& \ + gmock_##Method(GMOCK_MATCHER_(tn, F, 1) gmock_a1, \ + GMOCK_MATCHER_(tn, F, 2) gmock_a2, \ + GMOCK_MATCHER_(tn, F, 3) gmock_a3, \ + GMOCK_MATCHER_(tn, F, 4) gmock_a4, \ + GMOCK_MATCHER_(tn, F, 5) gmock_a5, \ + GMOCK_MATCHER_(tn, F, 6) gmock_a6, \ + GMOCK_MATCHER_(tn, F, 7) gmock_a7, \ + GMOCK_MATCHER_(tn, F, 8) gmock_a8, \ + GMOCK_MATCHER_(tn, F, 9) gmock_a9, \ + GMOCK_MATCHER_(tn, F, 10) gmock_a10) constness { \ + return GMOCK_MOCKER_(10, constness, \ + Method).RegisterOwner(this).With(gmock_a1, gmock_a2, gmock_a3, \ + gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8, gmock_a9, \ + gmock_a10); \ + } \ + mutable ::testing::FunctionMocker GMOCK_MOCKER_(10, constness, Method) + +#define MOCK_METHOD0(m, F) GMOCK_METHOD0_(, , , m, F) +#define MOCK_METHOD1(m, F) GMOCK_METHOD1_(, , , m, F) +#define MOCK_METHOD2(m, F) GMOCK_METHOD2_(, , , m, F) +#define MOCK_METHOD3(m, F) GMOCK_METHOD3_(, , , m, F) +#define MOCK_METHOD4(m, F) GMOCK_METHOD4_(, , , m, F) +#define MOCK_METHOD5(m, F) GMOCK_METHOD5_(, , , m, F) +#define MOCK_METHOD6(m, F) GMOCK_METHOD6_(, , , m, F) +#define MOCK_METHOD7(m, F) GMOCK_METHOD7_(, , , m, F) +#define MOCK_METHOD8(m, F) GMOCK_METHOD8_(, , , m, F) +#define MOCK_METHOD9(m, F) GMOCK_METHOD9_(, , , m, F) +#define MOCK_METHOD10(m, F) GMOCK_METHOD10_(, , , m, F) + +#define MOCK_CONST_METHOD0(m, F) GMOCK_METHOD0_(, const, , m, F) +#define MOCK_CONST_METHOD1(m, F) GMOCK_METHOD1_(, const, , m, F) +#define MOCK_CONST_METHOD2(m, F) GMOCK_METHOD2_(, const, , m, F) +#define MOCK_CONST_METHOD3(m, F) GMOCK_METHOD3_(, const, , m, F) +#define MOCK_CONST_METHOD4(m, F) GMOCK_METHOD4_(, const, , m, F) +#define MOCK_CONST_METHOD5(m, F) GMOCK_METHOD5_(, const, , m, F) +#define MOCK_CONST_METHOD6(m, F) GMOCK_METHOD6_(, const, , m, F) +#define MOCK_CONST_METHOD7(m, F) GMOCK_METHOD7_(, const, , m, F) +#define MOCK_CONST_METHOD8(m, F) GMOCK_METHOD8_(, const, , m, F) +#define MOCK_CONST_METHOD9(m, F) GMOCK_METHOD9_(, const, , m, F) +#define MOCK_CONST_METHOD10(m, F) GMOCK_METHOD10_(, const, , m, F) + +#define MOCK_METHOD0_T(m, F) GMOCK_METHOD0_(typename, , , m, F) +#define MOCK_METHOD1_T(m, F) GMOCK_METHOD1_(typename, , , m, F) +#define MOCK_METHOD2_T(m, F) GMOCK_METHOD2_(typename, , , m, F) +#define MOCK_METHOD3_T(m, F) GMOCK_METHOD3_(typename, , , m, F) +#define MOCK_METHOD4_T(m, F) GMOCK_METHOD4_(typename, , , m, F) +#define MOCK_METHOD5_T(m, F) GMOCK_METHOD5_(typename, , , m, F) +#define MOCK_METHOD6_T(m, F) GMOCK_METHOD6_(typename, , , m, F) +#define MOCK_METHOD7_T(m, F) GMOCK_METHOD7_(typename, , , m, F) +#define MOCK_METHOD8_T(m, F) GMOCK_METHOD8_(typename, , , m, F) +#define MOCK_METHOD9_T(m, F) GMOCK_METHOD9_(typename, , , m, F) +#define MOCK_METHOD10_T(m, F) GMOCK_METHOD10_(typename, , , m, F) + +#define MOCK_CONST_METHOD0_T(m, F) GMOCK_METHOD0_(typename, const, , m, F) +#define MOCK_CONST_METHOD1_T(m, F) GMOCK_METHOD1_(typename, const, , m, F) +#define MOCK_CONST_METHOD2_T(m, F) GMOCK_METHOD2_(typename, const, , m, F) +#define MOCK_CONST_METHOD3_T(m, F) GMOCK_METHOD3_(typename, const, , m, F) +#define MOCK_CONST_METHOD4_T(m, F) GMOCK_METHOD4_(typename, const, , m, F) +#define MOCK_CONST_METHOD5_T(m, F) GMOCK_METHOD5_(typename, const, , m, F) +#define MOCK_CONST_METHOD6_T(m, F) GMOCK_METHOD6_(typename, const, , m, F) +#define MOCK_CONST_METHOD7_T(m, F) GMOCK_METHOD7_(typename, const, , m, F) +#define MOCK_CONST_METHOD8_T(m, F) GMOCK_METHOD8_(typename, const, , m, F) +#define MOCK_CONST_METHOD9_T(m, F) GMOCK_METHOD9_(typename, const, , m, F) +#define MOCK_CONST_METHOD10_T(m, F) GMOCK_METHOD10_(typename, const, , m, F) + +#define MOCK_METHOD0_WITH_CALLTYPE(ct, m, F) GMOCK_METHOD0_(, , ct, m, F) +#define MOCK_METHOD1_WITH_CALLTYPE(ct, m, F) GMOCK_METHOD1_(, , ct, m, F) +#define MOCK_METHOD2_WITH_CALLTYPE(ct, m, F) GMOCK_METHOD2_(, , ct, m, F) +#define MOCK_METHOD3_WITH_CALLTYPE(ct, m, F) GMOCK_METHOD3_(, , ct, m, F) +#define MOCK_METHOD4_WITH_CALLTYPE(ct, m, F) GMOCK_METHOD4_(, , ct, m, F) +#define MOCK_METHOD5_WITH_CALLTYPE(ct, m, F) GMOCK_METHOD5_(, , ct, m, F) +#define MOCK_METHOD6_WITH_CALLTYPE(ct, m, F) GMOCK_METHOD6_(, , ct, m, F) +#define MOCK_METHOD7_WITH_CALLTYPE(ct, m, F) GMOCK_METHOD7_(, , ct, m, F) +#define MOCK_METHOD8_WITH_CALLTYPE(ct, m, F) GMOCK_METHOD8_(, , ct, m, F) +#define MOCK_METHOD9_WITH_CALLTYPE(ct, m, F) GMOCK_METHOD9_(, , ct, m, F) +#define MOCK_METHOD10_WITH_CALLTYPE(ct, m, F) GMOCK_METHOD10_(, , ct, m, F) + +#define MOCK_CONST_METHOD0_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD0_(, const, ct, m, F) +#define MOCK_CONST_METHOD1_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD1_(, const, ct, m, F) +#define MOCK_CONST_METHOD2_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD2_(, const, ct, m, F) +#define MOCK_CONST_METHOD3_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD3_(, const, ct, m, F) +#define MOCK_CONST_METHOD4_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD4_(, const, ct, m, F) +#define MOCK_CONST_METHOD5_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD5_(, const, ct, m, F) +#define MOCK_CONST_METHOD6_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD6_(, const, ct, m, F) +#define MOCK_CONST_METHOD7_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD7_(, const, ct, m, F) +#define MOCK_CONST_METHOD8_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD8_(, const, ct, m, F) +#define MOCK_CONST_METHOD9_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD9_(, const, ct, m, F) +#define MOCK_CONST_METHOD10_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD10_(, const, ct, m, F) + +#define MOCK_METHOD0_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD0_(typename, , ct, m, F) +#define MOCK_METHOD1_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD1_(typename, , ct, m, F) +#define MOCK_METHOD2_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD2_(typename, , ct, m, F) +#define MOCK_METHOD3_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD3_(typename, , ct, m, F) +#define MOCK_METHOD4_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD4_(typename, , ct, m, F) +#define MOCK_METHOD5_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD5_(typename, , ct, m, F) +#define MOCK_METHOD6_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD6_(typename, , ct, m, F) +#define MOCK_METHOD7_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD7_(typename, , ct, m, F) +#define MOCK_METHOD8_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD8_(typename, , ct, m, F) +#define MOCK_METHOD9_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD9_(typename, , ct, m, F) +#define MOCK_METHOD10_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD10_(typename, , ct, m, F) + +#define MOCK_CONST_METHOD0_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD0_(typename, const, ct, m, F) +#define MOCK_CONST_METHOD1_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD1_(typename, const, ct, m, F) +#define MOCK_CONST_METHOD2_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD2_(typename, const, ct, m, F) +#define MOCK_CONST_METHOD3_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD3_(typename, const, ct, m, F) +#define MOCK_CONST_METHOD4_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD4_(typename, const, ct, m, F) +#define MOCK_CONST_METHOD5_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD5_(typename, const, ct, m, F) +#define MOCK_CONST_METHOD6_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD6_(typename, const, ct, m, F) +#define MOCK_CONST_METHOD7_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD7_(typename, const, ct, m, F) +#define MOCK_CONST_METHOD8_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD8_(typename, const, ct, m, F) +#define MOCK_CONST_METHOD9_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD9_(typename, const, ct, m, F) +#define MOCK_CONST_METHOD10_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD10_(typename, const, ct, m, F) + +// A MockFunction class has one mock method whose type is F. It is +// useful when you just want your test code to emit some messages and +// have Google Mock verify the right messages are sent (and perhaps at +// the right times). For example, if you are exercising code: +// +// Foo(1); +// Foo(2); +// Foo(3); +// +// and want to verify that Foo(1) and Foo(3) both invoke +// mock.Bar("a"), but Foo(2) doesn't invoke anything, you can write: +// +// TEST(FooTest, InvokesBarCorrectly) { +// MyMock mock; +// MockFunction check; +// { +// InSequence s; +// +// EXPECT_CALL(mock, Bar("a")); +// EXPECT_CALL(check, Call("1")); +// EXPECT_CALL(check, Call("2")); +// EXPECT_CALL(mock, Bar("a")); +// } +// Foo(1); +// check.Call("1"); +// Foo(2); +// check.Call("2"); +// Foo(3); +// } +// +// The expectation spec says that the first Bar("a") must happen +// before check point "1", the second Bar("a") must happen after check +// point "2", and nothing should happen between the two check +// points. The explicit check points make it easy to tell which +// Bar("a") is called by which call to Foo(). +template +class MockFunction; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD0_T(Call, R()); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD1_T(Call, R(A0)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD2_T(Call, R(A0, A1)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD3_T(Call, R(A0, A1, A2)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD4_T(Call, R(A0, A1, A2, A3)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD5_T(Call, R(A0, A1, A2, A3, A4)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD6_T(Call, R(A0, A1, A2, A3, A4, A5)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD7_T(Call, R(A0, A1, A2, A3, A4, A5, A6)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD8_T(Call, R(A0, A1, A2, A3, A4, A5, A6, A7)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD9_T(Call, R(A0, A1, A2, A3, A4, A5, A6, A7, A8)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD10_T(Call, R(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ diff --git a/3rdparty/gmock/include/gmock/gmock-generated-function-mockers.h.pump b/3rdparty/gmock/include/gmock/gmock-generated-function-mockers.h.pump new file mode 100644 index 00000000..619debd2 --- /dev/null +++ b/3rdparty/gmock/include/gmock/gmock-generated-function-mockers.h.pump @@ -0,0 +1,257 @@ +$$ -*- mode: c++; -*- +$$ This is a Pump source file. Please use Pump to convert it to +$$ gmock-generated-function-mockers.h. +$$ +$var n = 10 $$ The maximum arity we support. +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements function mockers of various arities. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ + +#include +#include + +namespace testing { +namespace internal { + +template +class FunctionMockerBase; + +// Note: class FunctionMocker really belongs to the ::testing +// namespace. However if we define it in ::testing, MSVC will +// complain when classes in ::testing::internal declare it as a +// friend class template. To workaround this compiler bug, we define +// FunctionMocker in ::testing::internal and import it into ::testing. +template +class FunctionMocker; + + +$range i 0..n +$for i [[ +$range j 1..i +$var typename_As = [[$for j [[, typename A$j]]]] +$var As = [[$for j, [[A$j]]]] +$var as = [[$for j, [[a$j]]]] +$var Aas = [[$for j, [[A$j a$j]]]] +$var ms = [[$for j, [[m$j]]]] +$var matchers = [[$for j, [[const Matcher& m$j]]]] +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F($As); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With($matchers) { + +$if i >= 1 [[ + this->current_spec().SetMatchers(::std::tr1::make_tuple($ms)); + +]] + return this->current_spec(); + } + + R Invoke($Aas) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple($as)); + } +}; + + +]] +} // namespace internal + +// The style guide prohibits "using" statements in a namespace scope +// inside a header file. However, the FunctionMocker class template +// is meant to be defined in the ::testing namespace. The following +// line is just a trick for working around a bug in MSVC 8.0, which +// cannot handle it if we define FunctionMocker in ::testing. +using internal::FunctionMocker; + +// The result type of function type F. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_RESULT_(tn, F) tn ::testing::internal::Function::Result + +// The type of argument N of function type F. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_ARG_(tn, F, N) tn ::testing::internal::Function::Argument##N + +// The matcher type for argument N of function type F. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_MATCHER_(tn, F, N) const ::testing::Matcher& + +// The variable for mocking the given method. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_MOCKER_(arity, constness, Method) \ + GMOCK_CONCAT_TOKEN_(gmock##constness##arity##_##Method##_, __LINE__) + + +$for i [[ +$range j 1..i +$var arg_as = [[$for j, \ + [[GMOCK_ARG_(tn, F, $j) gmock_a$j]]]] +$var as = [[$for j, [[gmock_a$j]]]] +$var matcher_as = [[$for j, \ + [[GMOCK_MATCHER_(tn, F, $j) gmock_a$j]]]] +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD$i[[]]_(tn, constness, ct, Method, F) \ + GMOCK_RESULT_(tn, F) ct Method($arg_as) constness { \ + GMOCK_COMPILE_ASSERT_(::std::tr1::tuple_size< \ + tn ::testing::internal::Function::ArgumentTuple>::value == $i, \ + this_method_does_not_take_$i[[]]_argument[[$if i != 1 [[s]]]]); \ + GMOCK_MOCKER_($i, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_($i, constness, Method).Invoke($as); \ + } \ + ::testing::MockSpec& \ + gmock_##Method($matcher_as) constness { \ + return GMOCK_MOCKER_($i, constness, Method).RegisterOwner(this).With($as); \ + } \ + mutable ::testing::FunctionMocker GMOCK_MOCKER_($i, constness, Method) + + +]] +$for i [[ +#define MOCK_METHOD$i(m, F) GMOCK_METHOD$i[[]]_(, , , m, F) + +]] + + +$for i [[ +#define MOCK_CONST_METHOD$i(m, F) GMOCK_METHOD$i[[]]_(, const, , m, F) + +]] + + +$for i [[ +#define MOCK_METHOD$i[[]]_T(m, F) GMOCK_METHOD$i[[]]_(typename, , , m, F) + +]] + + +$for i [[ +#define MOCK_CONST_METHOD$i[[]]_T(m, F) [[]] +GMOCK_METHOD$i[[]]_(typename, const, , m, F) + +]] + + +$for i [[ +#define MOCK_METHOD$i[[]]_WITH_CALLTYPE(ct, m, F) [[]] +GMOCK_METHOD$i[[]]_(, , ct, m, F) + +]] + + +$for i [[ +#define MOCK_CONST_METHOD$i[[]]_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD$i[[]]_(, const, ct, m, F) + +]] + + +$for i [[ +#define MOCK_METHOD$i[[]]_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD$i[[]]_(typename, , ct, m, F) + +]] + + +$for i [[ +#define MOCK_CONST_METHOD$i[[]]_T_WITH_CALLTYPE(ct, m, F) \ + GMOCK_METHOD$i[[]]_(typename, const, ct, m, F) + +]] + +// A MockFunction class has one mock method whose type is F. It is +// useful when you just want your test code to emit some messages and +// have Google Mock verify the right messages are sent (and perhaps at +// the right times). For example, if you are exercising code: +// +// Foo(1); +// Foo(2); +// Foo(3); +// +// and want to verify that Foo(1) and Foo(3) both invoke +// mock.Bar("a"), but Foo(2) doesn't invoke anything, you can write: +// +// TEST(FooTest, InvokesBarCorrectly) { +// MyMock mock; +// MockFunction check; +// { +// InSequence s; +// +// EXPECT_CALL(mock, Bar("a")); +// EXPECT_CALL(check, Call("1")); +// EXPECT_CALL(check, Call("2")); +// EXPECT_CALL(mock, Bar("a")); +// } +// Foo(1); +// check.Call("1"); +// Foo(2); +// check.Call("2"); +// Foo(3); +// } +// +// The expectation spec says that the first Bar("a") must happen +// before check point "1", the second Bar("a") must happen after check +// point "2", and nothing should happen between the two check +// points. The explicit check points make it easy to tell which +// Bar("a") is called by which call to Foo(). +template +class MockFunction; + + +$for i [[ +$range j 0..i-1 +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD$i[[]]_T(Call, R($for j, [[A$j]])); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + + +]] +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ diff --git a/3rdparty/gmock/include/gmock/gmock-generated-matchers.h b/3rdparty/gmock/include/gmock/gmock-generated-matchers.h new file mode 100644 index 00000000..9e5bedea --- /dev/null +++ b/3rdparty/gmock/include/gmock/gmock-generated-matchers.h @@ -0,0 +1,1859 @@ +// This file was GENERATED by command: +// pump.py gmock-generated-matchers.h.pump +// DO NOT EDIT BY HAND!!! + +// Copyright 2008, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used variadic matchers. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ + +#include +#include +#include +#include +#include + +namespace testing { +namespace internal { + +// The type of the i-th (0-based) field of Tuple. +#define GMOCK_FIELD_TYPE_(Tuple, i) \ + typename ::std::tr1::tuple_element::type + +// TupleFields is for selecting fields from a +// tuple of type Tuple. It has two members: +// +// type: a tuple type whose i-th field is the ki-th field of Tuple. +// GetSelectedFields(t): returns fields k0, ..., and kn of t as a tuple. +// +// For example, in class TupleFields, 2, 0>, we have: +// +// type is tuple, and +// GetSelectedFields(make_tuple(true, 'a', 42)) is (42, true). + +template +class TupleFields; + +// This generic version is used when there are 10 selectors. +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t), get(t), get(t), get(t), + get(t), get(t), get(t), get(t), get(t)); + } +}; + +// The following specialization is used for 0 ~ 9 selectors. + +template +class TupleFields { + public: + typedef ::std::tr1::tuple<> type; + static type GetSelectedFields(const Tuple& /* t */) { + using ::std::tr1::get; + return type(); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t), get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t), get(t), get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t), get(t), get(t), get(t), + get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t), get(t), get(t), get(t), + get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t), get(t), get(t), get(t), + get(t), get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t), get(t), get(t), get(t), + get(t), get(t), get(t), get(t)); + } +}; + +#undef GMOCK_FIELD_TYPE_ + +// Implements the Args() matcher. +template +class ArgsMatcherImpl : public MatcherInterface { + public: + // ArgsTuple may have top-level const or reference modifiers. + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(ArgsTuple)) RawArgsTuple; + typedef typename internal::TupleFields::type SelectedArgs; + typedef Matcher MonomorphicInnerMatcher; + + template + explicit ArgsMatcherImpl(const InnerMatcher& inner_matcher) + : inner_matcher_(SafeMatcherCast(inner_matcher)) {} + + virtual bool MatchAndExplain(ArgsTuple args, + MatchResultListener* listener) const { + const SelectedArgs& selected_args = GetSelectedArgs(args); + if (!listener->IsInterested()) + return inner_matcher_.Matches(selected_args); + + PrintIndices(listener->stream()); + *listener << "are " << PrintToString(selected_args); + + StringMatchResultListener inner_listener; + const bool match = inner_matcher_.MatchAndExplain(selected_args, + &inner_listener); + PrintIfNotEmpty(inner_listener.str(), listener->stream()); + return match; + } + + virtual void DescribeTo(::std::ostream* os) const { + *os << "are a tuple "; + PrintIndices(os); + inner_matcher_.DescribeTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "are a tuple "; + PrintIndices(os); + inner_matcher_.DescribeNegationTo(os); + } + + private: + static SelectedArgs GetSelectedArgs(ArgsTuple args) { + return TupleFields::GetSelectedFields(args); + } + + // Prints the indices of the selected fields. + static void PrintIndices(::std::ostream* os) { + *os << "whose fields ("; + const int indices[10] = { k0, k1, k2, k3, k4, k5, k6, k7, k8, k9 }; + for (int i = 0; i < 10; i++) { + if (indices[i] < 0) + break; + + if (i >= 1) + *os << ", "; + + *os << "#" << indices[i]; + } + *os << ") "; + } + + const MonomorphicInnerMatcher inner_matcher_; + + GTEST_DISALLOW_ASSIGN_(ArgsMatcherImpl); +}; + +template +class ArgsMatcher { + public: + explicit ArgsMatcher(const InnerMatcher& inner_matcher) + : inner_matcher_(inner_matcher) {} + + template + operator Matcher() const { + return MakeMatcher(new ArgsMatcherImpl(inner_matcher_)); + } + + private: + const InnerMatcher inner_matcher_; + + GTEST_DISALLOW_ASSIGN_(ArgsMatcher); +}; + +// Implements ElementsAre() of 1-10 arguments. + +template +class ElementsAreMatcher1 { + public: + explicit ElementsAreMatcher1(const T1& e1) : e1_(e1) {} + + template + operator Matcher() const { + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(Container)) + RawContainer; + typedef typename internal::StlContainerView::type::value_type + Element; + + // Nokia's Symbian Compiler has a nasty bug where the object put + // in a one-element local array is not destructed when the array + // goes out of scope. This leads to obvious badness as we've + // added the linked_ptr in it to our other linked_ptrs list. + // Hence we implement ElementsAreMatcher1 specially to avoid using + // a local array. + const Matcher matcher = + MatcherCast(e1_); + return MakeMatcher(new ElementsAreMatcherImpl(&matcher, 1)); + } + + private: + const T1& e1_; + + GTEST_DISALLOW_ASSIGN_(ElementsAreMatcher1); +}; + +template +class ElementsAreMatcher2 { + public: + ElementsAreMatcher2(const T1& e1, const T2& e2) : e1_(e1), e2_(e2) {} + + template + operator Matcher() const { + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(Container)) + RawContainer; + typedef typename internal::StlContainerView::type::value_type + Element; + + const Matcher matchers[] = { + MatcherCast(e1_), + MatcherCast(e2_), + }; + + return MakeMatcher(new ElementsAreMatcherImpl(matchers, 2)); + } + + private: + const T1& e1_; + const T2& e2_; + + GTEST_DISALLOW_ASSIGN_(ElementsAreMatcher2); +}; + +template +class ElementsAreMatcher3 { + public: + ElementsAreMatcher3(const T1& e1, const T2& e2, const T3& e3) : e1_(e1), + e2_(e2), e3_(e3) {} + + template + operator Matcher() const { + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(Container)) + RawContainer; + typedef typename internal::StlContainerView::type::value_type + Element; + + const Matcher matchers[] = { + MatcherCast(e1_), + MatcherCast(e2_), + MatcherCast(e3_), + }; + + return MakeMatcher(new ElementsAreMatcherImpl(matchers, 3)); + } + + private: + const T1& e1_; + const T2& e2_; + const T3& e3_; + + GTEST_DISALLOW_ASSIGN_(ElementsAreMatcher3); +}; + +template +class ElementsAreMatcher4 { + public: + ElementsAreMatcher4(const T1& e1, const T2& e2, const T3& e3, + const T4& e4) : e1_(e1), e2_(e2), e3_(e3), e4_(e4) {} + + template + operator Matcher() const { + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(Container)) + RawContainer; + typedef typename internal::StlContainerView::type::value_type + Element; + + const Matcher matchers[] = { + MatcherCast(e1_), + MatcherCast(e2_), + MatcherCast(e3_), + MatcherCast(e4_), + }; + + return MakeMatcher(new ElementsAreMatcherImpl(matchers, 4)); + } + + private: + const T1& e1_; + const T2& e2_; + const T3& e3_; + const T4& e4_; + + GTEST_DISALLOW_ASSIGN_(ElementsAreMatcher4); +}; + +template +class ElementsAreMatcher5 { + public: + ElementsAreMatcher5(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5) : e1_(e1), e2_(e2), e3_(e3), e4_(e4), e5_(e5) {} + + template + operator Matcher() const { + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(Container)) + RawContainer; + typedef typename internal::StlContainerView::type::value_type + Element; + + const Matcher matchers[] = { + MatcherCast(e1_), + MatcherCast(e2_), + MatcherCast(e3_), + MatcherCast(e4_), + MatcherCast(e5_), + }; + + return MakeMatcher(new ElementsAreMatcherImpl(matchers, 5)); + } + + private: + const T1& e1_; + const T2& e2_; + const T3& e3_; + const T4& e4_; + const T5& e5_; + + GTEST_DISALLOW_ASSIGN_(ElementsAreMatcher5); +}; + +template +class ElementsAreMatcher6 { + public: + ElementsAreMatcher6(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6) : e1_(e1), e2_(e2), e3_(e3), e4_(e4), + e5_(e5), e6_(e6) {} + + template + operator Matcher() const { + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(Container)) + RawContainer; + typedef typename internal::StlContainerView::type::value_type + Element; + + const Matcher matchers[] = { + MatcherCast(e1_), + MatcherCast(e2_), + MatcherCast(e3_), + MatcherCast(e4_), + MatcherCast(e5_), + MatcherCast(e6_), + }; + + return MakeMatcher(new ElementsAreMatcherImpl(matchers, 6)); + } + + private: + const T1& e1_; + const T2& e2_; + const T3& e3_; + const T4& e4_; + const T5& e5_; + const T6& e6_; + + GTEST_DISALLOW_ASSIGN_(ElementsAreMatcher6); +}; + +template +class ElementsAreMatcher7 { + public: + ElementsAreMatcher7(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7) : e1_(e1), e2_(e2), e3_(e3), + e4_(e4), e5_(e5), e6_(e6), e7_(e7) {} + + template + operator Matcher() const { + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(Container)) + RawContainer; + typedef typename internal::StlContainerView::type::value_type + Element; + + const Matcher matchers[] = { + MatcherCast(e1_), + MatcherCast(e2_), + MatcherCast(e3_), + MatcherCast(e4_), + MatcherCast(e5_), + MatcherCast(e6_), + MatcherCast(e7_), + }; + + return MakeMatcher(new ElementsAreMatcherImpl(matchers, 7)); + } + + private: + const T1& e1_; + const T2& e2_; + const T3& e3_; + const T4& e4_; + const T5& e5_; + const T6& e6_; + const T7& e7_; + + GTEST_DISALLOW_ASSIGN_(ElementsAreMatcher7); +}; + +template +class ElementsAreMatcher8 { + public: + ElementsAreMatcher8(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8) : e1_(e1), + e2_(e2), e3_(e3), e4_(e4), e5_(e5), e6_(e6), e7_(e7), e8_(e8) {} + + template + operator Matcher() const { + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(Container)) + RawContainer; + typedef typename internal::StlContainerView::type::value_type + Element; + + const Matcher matchers[] = { + MatcherCast(e1_), + MatcherCast(e2_), + MatcherCast(e3_), + MatcherCast(e4_), + MatcherCast(e5_), + MatcherCast(e6_), + MatcherCast(e7_), + MatcherCast(e8_), + }; + + return MakeMatcher(new ElementsAreMatcherImpl(matchers, 8)); + } + + private: + const T1& e1_; + const T2& e2_; + const T3& e3_; + const T4& e4_; + const T5& e5_; + const T6& e6_; + const T7& e7_; + const T8& e8_; + + GTEST_DISALLOW_ASSIGN_(ElementsAreMatcher8); +}; + +template +class ElementsAreMatcher9 { + public: + ElementsAreMatcher9(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8, + const T9& e9) : e1_(e1), e2_(e2), e3_(e3), e4_(e4), e5_(e5), e6_(e6), + e7_(e7), e8_(e8), e9_(e9) {} + + template + operator Matcher() const { + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(Container)) + RawContainer; + typedef typename internal::StlContainerView::type::value_type + Element; + + const Matcher matchers[] = { + MatcherCast(e1_), + MatcherCast(e2_), + MatcherCast(e3_), + MatcherCast(e4_), + MatcherCast(e5_), + MatcherCast(e6_), + MatcherCast(e7_), + MatcherCast(e8_), + MatcherCast(e9_), + }; + + return MakeMatcher(new ElementsAreMatcherImpl(matchers, 9)); + } + + private: + const T1& e1_; + const T2& e2_; + const T3& e3_; + const T4& e4_; + const T5& e5_; + const T6& e6_; + const T7& e7_; + const T8& e8_; + const T9& e9_; + + GTEST_DISALLOW_ASSIGN_(ElementsAreMatcher9); +}; + +template +class ElementsAreMatcher10 { + public: + ElementsAreMatcher10(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8, const T9& e9, + const T10& e10) : e1_(e1), e2_(e2), e3_(e3), e4_(e4), e5_(e5), e6_(e6), + e7_(e7), e8_(e8), e9_(e9), e10_(e10) {} + + template + operator Matcher() const { + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(Container)) + RawContainer; + typedef typename internal::StlContainerView::type::value_type + Element; + + const Matcher matchers[] = { + MatcherCast(e1_), + MatcherCast(e2_), + MatcherCast(e3_), + MatcherCast(e4_), + MatcherCast(e5_), + MatcherCast(e6_), + MatcherCast(e7_), + MatcherCast(e8_), + MatcherCast(e9_), + MatcherCast(e10_), + }; + + return MakeMatcher(new ElementsAreMatcherImpl(matchers, 10)); + } + + private: + const T1& e1_; + const T2& e2_; + const T3& e3_; + const T4& e4_; + const T5& e5_; + const T6& e6_; + const T7& e7_; + const T8& e8_; + const T9& e9_; + const T10& e10_; + + GTEST_DISALLOW_ASSIGN_(ElementsAreMatcher10); +}; + +} // namespace internal + +// Args(a_matcher) matches a tuple if the selected +// fields of it matches a_matcher. C++ doesn't support default +// arguments for function templates, so we have to overload it. +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +// ElementsAre(e0, e1, ..., e_n) matches an STL-style container with +// (n + 1) elements, where the i-th element in the container must +// match the i-th argument in the list. Each argument of +// ElementsAre() can be either a value or a matcher. We support up to +// 10 arguments. +// +// NOTE: Since ElementsAre() cares about the order of the elements, it +// must not be used with containers whose elements's order is +// undefined (e.g. hash_map). + +inline internal::ElementsAreMatcher0 ElementsAre() { + return internal::ElementsAreMatcher0(); +} + +template +inline internal::ElementsAreMatcher1 ElementsAre(const T1& e1) { + return internal::ElementsAreMatcher1(e1); +} + +template +inline internal::ElementsAreMatcher2 ElementsAre(const T1& e1, + const T2& e2) { + return internal::ElementsAreMatcher2(e1, e2); +} + +template +inline internal::ElementsAreMatcher3 ElementsAre(const T1& e1, + const T2& e2, const T3& e3) { + return internal::ElementsAreMatcher3(e1, e2, e3); +} + +template +inline internal::ElementsAreMatcher4 ElementsAre(const T1& e1, + const T2& e2, const T3& e3, const T4& e4) { + return internal::ElementsAreMatcher4(e1, e2, e3, e4); +} + +template +inline internal::ElementsAreMatcher5 ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5) { + return internal::ElementsAreMatcher5(e1, e2, e3, e4, e5); +} + +template +inline internal::ElementsAreMatcher6 ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6) { + return internal::ElementsAreMatcher6(e1, e2, e3, e4, + e5, e6); +} + +template +inline internal::ElementsAreMatcher7 ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7) { + return internal::ElementsAreMatcher7(e1, e2, e3, + e4, e5, e6, e7); +} + +template +inline internal::ElementsAreMatcher8 ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8) { + return internal::ElementsAreMatcher8(e1, e2, + e3, e4, e5, e6, e7, e8); +} + +template +inline internal::ElementsAreMatcher9 ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8, const T9& e9) { + return internal::ElementsAreMatcher9(e1, + e2, e3, e4, e5, e6, e7, e8, e9); +} + +template +inline internal::ElementsAreMatcher10 ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8, const T9& e9, + const T10& e10) { + return internal::ElementsAreMatcher10(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10); +} + +// ElementsAreArray(array) and ElementAreArray(array, count) are like +// ElementsAre(), except that they take an array of values or +// matchers. The former form infers the size of 'array', which must +// be a static C-style array. In the latter form, 'array' can either +// be a static array or a pointer to a dynamically created array. + +template +inline internal::ElementsAreArrayMatcher ElementsAreArray( + const T* first, size_t count) { + return internal::ElementsAreArrayMatcher(first, count); +} + +template +inline internal::ElementsAreArrayMatcher +ElementsAreArray(const T (&array)[N]) { + return internal::ElementsAreArrayMatcher(array, N); +} + +} // namespace testing + +// The MATCHER* family of macros can be used in a namespace scope to +// define custom matchers easily. +// +// Basic Usage +// =========== +// +// The syntax +// +// MATCHER(name, description_string) { statements; } +// +// defines a matcher with the given name that executes the statements, +// which must return a bool to indicate if the match succeeds. Inside +// the statements, you can refer to the value being matched by 'arg', +// and refer to its type by 'arg_type'. +// +// The description string documents what the matcher does, and is used +// to generate the failure message when the match fails. Since a +// MATCHER() is usually defined in a header file shared by multiple +// C++ source files, we require the description to be a C-string +// literal to avoid possible side effects. It can be empty, in which +// case we'll use the sequence of words in the matcher name as the +// description. +// +// For example: +// +// MATCHER(IsEven, "") { return (arg % 2) == 0; } +// +// allows you to write +// +// // Expects mock_foo.Bar(n) to be called where n is even. +// EXPECT_CALL(mock_foo, Bar(IsEven())); +// +// or, +// +// // Verifies that the value of some_expression is even. +// EXPECT_THAT(some_expression, IsEven()); +// +// If the above assertion fails, it will print something like: +// +// Value of: some_expression +// Expected: is even +// Actual: 7 +// +// where the description "is even" is automatically calculated from the +// matcher name IsEven. +// +// Argument Type +// ============= +// +// Note that the type of the value being matched (arg_type) is +// determined by the context in which you use the matcher and is +// supplied to you by the compiler, so you don't need to worry about +// declaring it (nor can you). This allows the matcher to be +// polymorphic. For example, IsEven() can be used to match any type +// where the value of "(arg % 2) == 0" can be implicitly converted to +// a bool. In the "Bar(IsEven())" example above, if method Bar() +// takes an int, 'arg_type' will be int; if it takes an unsigned long, +// 'arg_type' will be unsigned long; and so on. +// +// Parameterizing Matchers +// ======================= +// +// Sometimes you'll want to parameterize the matcher. For that you +// can use another macro: +// +// MATCHER_P(name, param_name, description_string) { statements; } +// +// For example: +// +// MATCHER_P(HasAbsoluteValue, value, "") { return abs(arg) == value; } +// +// will allow you to write: +// +// EXPECT_THAT(Blah("a"), HasAbsoluteValue(n)); +// +// which may lead to this message (assuming n is 10): +// +// Value of: Blah("a") +// Expected: has absolute value 10 +// Actual: -9 +// +// Note that both the matcher description and its parameter are +// printed, making the message human-friendly. +// +// In the matcher definition body, you can write 'foo_type' to +// reference the type of a parameter named 'foo'. For example, in the +// body of MATCHER_P(HasAbsoluteValue, value) above, you can write +// 'value_type' to refer to the type of 'value'. +// +// We also provide MATCHER_P2, MATCHER_P3, ..., up to MATCHER_P10 to +// support multi-parameter matchers. +// +// Describing Parameterized Matchers +// ================================= +// +// When defining a parameterized matcher, you can use Python-style +// interpolations in the description string to refer to the parameter +// values. We support the following syntax currently: +// +// %% a single '%' character +// %(*)s all parameters of the matcher printed as a tuple +// %(foo)s value of the matcher parameter named 'foo' +// +// For example, +// +// MATCHER_P2(InClosedRange, low, hi, "is in range [%(low)s, %(hi)s]") { +// return low <= arg && arg <= hi; +// } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// +// would generate a failure that contains the message: +// +// Expected: is in range [4, 6] +// +// If you specify "" as the description, the failure message will +// contain the sequence of words in the matcher name followed by the +// parameter values printed as a tuple. For example, +// +// MATCHER_P2(InClosedRange, low, hi, "") { ... } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// +// would generate a failure that contains the text: +// +// Expected: in closed range (4, 6) +// +// Types of Matcher Parameters +// =========================== +// +// For the purpose of typing, you can view +// +// MATCHER_Pk(Foo, p1, ..., pk, description_string) { ... } +// +// as shorthand for +// +// template +// FooMatcherPk +// Foo(p1_type p1, ..., pk_type pk) { ... } +// +// When you write Foo(v1, ..., vk), the compiler infers the types of +// the parameters v1, ..., and vk for you. If you are not happy with +// the result of the type inference, you can specify the types by +// explicitly instantiating the template, as in Foo(5, +// false). As said earlier, you don't get to (or need to) specify +// 'arg_type' as that's determined by the context in which the matcher +// is used. You can assign the result of expression Foo(p1, ..., pk) +// to a variable of type FooMatcherPk. This +// can be useful when composing matchers. +// +// While you can instantiate a matcher template with reference types, +// passing the parameters by pointer usually makes your code more +// readable. If, however, you still want to pass a parameter by +// reference, be aware that in the failure message generated by the +// matcher you will see the value of the referenced object but not its +// address. +// +// Explaining Match Results +// ======================== +// +// Sometimes the matcher description alone isn't enough to explain why +// the match has failed or succeeded. For example, when expecting a +// long string, it can be very helpful to also print the diff between +// the expected string and the actual one. To achieve that, you can +// optionally stream additional information to a special variable +// named result_listener, whose type is a pointer to class +// MatchResultListener: +// +// MATCHER_P(EqualsLongString, str, "") { +// if (arg == str) return true; +// +// *result_listener << "the difference: " +/// << DiffStrings(str, arg); +// return false; +// } +// +// Overloading Matchers +// ==================== +// +// You can overload matchers with different numbers of parameters: +// +// MATCHER_P(Blah, a, description_string1) { ... } +// MATCHER_P2(Blah, a, b, description_string2) { ... } +// +// Caveats +// ======= +// +// When defining a new matcher, you should also consider implementing +// MatcherInterface or using MakePolymorphicMatcher(). These +// approaches require more work than the MATCHER* macros, but also +// give you more control on the types of the value being matched and +// the matcher parameters, which may leads to better compiler error +// messages when the matcher is used wrong. They also allow +// overloading matchers based on parameter types (as opposed to just +// based on the number of parameters). +// +// MATCHER*() can only be used in a namespace scope. The reason is +// that C++ doesn't yet allow function-local types to be used to +// instantiate templates. The up-coming C++0x standard will fix this. +// Once that's done, we'll consider supporting using MATCHER*() inside +// a function. +// +// More Information +// ================ +// +// To learn more about using these macros, please search for 'MATCHER' +// on http://code.google.com/p/googlemock/wiki/CookBook. + +#define MATCHER(name, description)\ + class name##Matcher {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(const ::testing::internal::Interpolations& gmock_interp)\ + : gmock_interp_(gmock_interp) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + const ::testing::internal::Strings& gmock_printed_params = \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple<>());\ + *gmock_os << ::testing::internal::FormatMatcherDescription(\ + #name, description, gmock_interp_, gmock_printed_params);\ + }\ + const ::testing::internal::Interpolations gmock_interp_;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(gmock_interp_));\ + }\ + name##Matcher() {\ + const char* gmock_param_names[] = { NULL };\ + gmock_interp_ = ::testing::internal::ValidateMatcherDescription(\ + gmock_param_names, ("" description ""));\ + }\ + private:\ + ::testing::internal::Interpolations gmock_interp_;\ + GTEST_DISALLOW_ASSIGN_(name##Matcher);\ + };\ + inline name##Matcher name() {\ + return name##Matcher();\ + }\ + template \ + bool name##Matcher::gmock_Impl::MatchAndExplain(\ + arg_type arg,\ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P(name, p0, description)\ + template \ + class name##MatcherP {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + explicit gmock_Impl(p0##_type gmock_p0, \ + const ::testing::internal::Interpolations& gmock_interp)\ + : p0(gmock_p0), gmock_interp_(gmock_interp) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + const ::testing::internal::Strings& gmock_printed_params = \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0));\ + *gmock_os << ::testing::internal::FormatMatcherDescription(\ + #name, description, gmock_interp_, gmock_printed_params);\ + }\ + p0##_type p0;\ + const ::testing::internal::Interpolations gmock_interp_;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, gmock_interp_));\ + }\ + name##MatcherP(p0##_type gmock_p0) : p0(gmock_p0) {\ + const char* gmock_param_names[] = { #p0, NULL };\ + gmock_interp_ = ::testing::internal::ValidateMatcherDescription(\ + gmock_param_names, ("" description ""));\ + }\ + p0##_type p0;\ + private:\ + ::testing::internal::Interpolations gmock_interp_;\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP);\ + };\ + template \ + inline name##MatcherP name(p0##_type p0) {\ + return name##MatcherP(p0);\ + }\ + template \ + template \ + bool name##MatcherP::gmock_Impl::MatchAndExplain(\ + arg_type arg,\ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P2(name, p0, p1, description)\ + template \ + class name##MatcherP2 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, \ + const ::testing::internal::Interpolations& gmock_interp)\ + : p0(gmock_p0), p1(gmock_p1), gmock_interp_(gmock_interp) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + const ::testing::internal::Strings& gmock_printed_params = \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1));\ + *gmock_os << ::testing::internal::FormatMatcherDescription(\ + #name, description, gmock_interp_, gmock_printed_params);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + const ::testing::internal::Interpolations gmock_interp_;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, gmock_interp_));\ + }\ + name##MatcherP2(p0##_type gmock_p0, p1##_type gmock_p1) : p0(gmock_p0), \ + p1(gmock_p1) {\ + const char* gmock_param_names[] = { #p0, #p1, NULL };\ + gmock_interp_ = ::testing::internal::ValidateMatcherDescription(\ + gmock_param_names, ("" description ""));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + private:\ + ::testing::internal::Interpolations gmock_interp_;\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP2);\ + };\ + template \ + inline name##MatcherP2 name(p0##_type p0, \ + p1##_type p1) {\ + return name##MatcherP2(p0, p1);\ + }\ + template \ + template \ + bool name##MatcherP2::gmock_Impl::MatchAndExplain(\ + arg_type arg,\ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P3(name, p0, p1, p2, description)\ + template \ + class name##MatcherP3 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + const ::testing::internal::Interpolations& gmock_interp)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + gmock_interp_(gmock_interp) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + const ::testing::internal::Strings& gmock_printed_params = \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1, \ + p2));\ + *gmock_os << ::testing::internal::FormatMatcherDescription(\ + #name, description, gmock_interp_, gmock_printed_params);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + const ::testing::internal::Interpolations gmock_interp_;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, gmock_interp_));\ + }\ + name##MatcherP3(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2) {\ + const char* gmock_param_names[] = { #p0, #p1, #p2, NULL };\ + gmock_interp_ = ::testing::internal::ValidateMatcherDescription(\ + gmock_param_names, ("" description ""));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + private:\ + ::testing::internal::Interpolations gmock_interp_;\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP3);\ + };\ + template \ + inline name##MatcherP3 name(p0##_type p0, \ + p1##_type p1, p2##_type p2) {\ + return name##MatcherP3(p0, p1, p2);\ + }\ + template \ + template \ + bool name##MatcherP3::gmock_Impl::MatchAndExplain(\ + arg_type arg,\ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P4(name, p0, p1, p2, p3, description)\ + template \ + class name##MatcherP4 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, \ + const ::testing::internal::Interpolations& gmock_interp)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + gmock_interp_(gmock_interp) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + const ::testing::internal::Strings& gmock_printed_params = \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1, p2, p3));\ + *gmock_os << ::testing::internal::FormatMatcherDescription(\ + #name, description, gmock_interp_, gmock_printed_params);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + const ::testing::internal::Interpolations gmock_interp_;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, gmock_interp_));\ + }\ + name##MatcherP4(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3) {\ + const char* gmock_param_names[] = { #p0, #p1, #p2, #p3, NULL };\ + gmock_interp_ = ::testing::internal::ValidateMatcherDescription(\ + gmock_param_names, ("" description ""));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + private:\ + ::testing::internal::Interpolations gmock_interp_;\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP4);\ + };\ + template \ + inline name##MatcherP4 name(p0##_type p0, p1##_type p1, p2##_type p2, \ + p3##_type p3) {\ + return name##MatcherP4(p0, \ + p1, p2, p3);\ + }\ + template \ + template \ + bool name##MatcherP4::gmock_Impl::MatchAndExplain(\ + arg_type arg,\ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P5(name, p0, p1, p2, p3, p4, description)\ + template \ + class name##MatcherP5 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, \ + const ::testing::internal::Interpolations& gmock_interp)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4), gmock_interp_(gmock_interp) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + const ::testing::internal::Strings& gmock_printed_params = \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1, p2, p3, p4));\ + *gmock_os << ::testing::internal::FormatMatcherDescription(\ + #name, description, gmock_interp_, gmock_printed_params);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + const ::testing::internal::Interpolations gmock_interp_;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4, gmock_interp_));\ + }\ + name##MatcherP5(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, \ + p4##_type gmock_p4) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4) {\ + const char* gmock_param_names[] = { #p0, #p1, #p2, #p3, #p4, NULL };\ + gmock_interp_ = ::testing::internal::ValidateMatcherDescription(\ + gmock_param_names, ("" description ""));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + private:\ + ::testing::internal::Interpolations gmock_interp_;\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP5);\ + };\ + template \ + inline name##MatcherP5 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4) {\ + return name##MatcherP5(p0, p1, p2, p3, p4);\ + }\ + template \ + template \ + bool name##MatcherP5::gmock_Impl::MatchAndExplain(\ + arg_type arg,\ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P6(name, p0, p1, p2, p3, p4, p5, description)\ + template \ + class name##MatcherP6 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + const ::testing::internal::Interpolations& gmock_interp)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4), p5(gmock_p5), gmock_interp_(gmock_interp) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + const ::testing::internal::Strings& gmock_printed_params = \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1, p2, p3, p4, p5));\ + *gmock_os << ::testing::internal::FormatMatcherDescription(\ + #name, description, gmock_interp_, gmock_printed_params);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + const ::testing::internal::Interpolations gmock_interp_;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4, p5, gmock_interp_));\ + }\ + name##MatcherP6(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5) {\ + const char* gmock_param_names[] = { #p0, #p1, #p2, #p3, #p4, #p5, NULL };\ + gmock_interp_ = ::testing::internal::ValidateMatcherDescription(\ + gmock_param_names, ("" description ""));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + private:\ + ::testing::internal::Interpolations gmock_interp_;\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP6);\ + };\ + template \ + inline name##MatcherP6 name(p0##_type p0, p1##_type p1, p2##_type p2, \ + p3##_type p3, p4##_type p4, p5##_type p5) {\ + return name##MatcherP6(p0, p1, p2, p3, p4, p5);\ + }\ + template \ + template \ + bool name##MatcherP6::gmock_Impl::MatchAndExplain(\ + arg_type arg,\ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P7(name, p0, p1, p2, p3, p4, p5, p6, description)\ + template \ + class name##MatcherP7 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, \ + const ::testing::internal::Interpolations& gmock_interp)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + gmock_interp_(gmock_interp) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + const ::testing::internal::Strings& gmock_printed_params = \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1, p2, p3, p4, p5, \ + p6));\ + *gmock_os << ::testing::internal::FormatMatcherDescription(\ + #name, description, gmock_interp_, gmock_printed_params);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + const ::testing::internal::Interpolations gmock_interp_;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4, p5, p6, gmock_interp_));\ + }\ + name##MatcherP7(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), \ + p6(gmock_p6) {\ + const char* gmock_param_names[] = { #p0, #p1, #p2, #p3, #p4, #p5, #p6, \ + NULL };\ + gmock_interp_ = ::testing::internal::ValidateMatcherDescription(\ + gmock_param_names, ("" description ""));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + private:\ + ::testing::internal::Interpolations gmock_interp_;\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP7);\ + };\ + template \ + inline name##MatcherP7 name(p0##_type p0, p1##_type p1, \ + p2##_type p2, p3##_type p3, p4##_type p4, p5##_type p5, \ + p6##_type p6) {\ + return name##MatcherP7(p0, p1, p2, p3, p4, p5, p6);\ + }\ + template \ + template \ + bool name##MatcherP7::gmock_Impl::MatchAndExplain(\ + arg_type arg,\ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P8(name, p0, p1, p2, p3, p4, p5, p6, p7, description)\ + template \ + class name##MatcherP8 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, \ + const ::testing::internal::Interpolations& gmock_interp)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + gmock_interp_(gmock_interp) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + const ::testing::internal::Strings& gmock_printed_params = \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1, p2, \ + p3, p4, p5, p6, p7));\ + *gmock_os << ::testing::internal::FormatMatcherDescription(\ + #name, description, gmock_interp_, gmock_printed_params);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + const ::testing::internal::Interpolations gmock_interp_;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4, p5, p6, p7, \ + gmock_interp_));\ + }\ + name##MatcherP8(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, \ + p7##_type gmock_p7) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7) {\ + const char* gmock_param_names[] = { #p0, #p1, #p2, #p3, #p4, #p5, #p6, \ + #p7, NULL };\ + gmock_interp_ = ::testing::internal::ValidateMatcherDescription(\ + gmock_param_names, ("" description ""));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + private:\ + ::testing::internal::Interpolations gmock_interp_;\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP8);\ + };\ + template \ + inline name##MatcherP8 name(p0##_type p0, \ + p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, p5##_type p5, \ + p6##_type p6, p7##_type p7) {\ + return name##MatcherP8(p0, p1, p2, p3, p4, p5, \ + p6, p7);\ + }\ + template \ + template \ + bool name##MatcherP8::gmock_Impl::MatchAndExplain(\ + arg_type arg,\ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P9(name, p0, p1, p2, p3, p4, p5, p6, p7, p8, description)\ + template \ + class name##MatcherP9 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8, \ + const ::testing::internal::Interpolations& gmock_interp)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8), gmock_interp_(gmock_interp) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + const ::testing::internal::Strings& gmock_printed_params = \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1, p2, p3, p4, p5, p6, p7, p8));\ + *gmock_os << ::testing::internal::FormatMatcherDescription(\ + #name, description, gmock_interp_, gmock_printed_params);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + const ::testing::internal::Interpolations gmock_interp_;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4, p5, p6, p7, p8, \ + gmock_interp_));\ + }\ + name##MatcherP9(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8) {\ + const char* gmock_param_names[] = { #p0, #p1, #p2, #p3, #p4, #p5, #p6, \ + #p7, #p8, NULL };\ + gmock_interp_ = ::testing::internal::ValidateMatcherDescription(\ + gmock_param_names, ("" description ""));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + private:\ + ::testing::internal::Interpolations gmock_interp_;\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP9);\ + };\ + template \ + inline name##MatcherP9 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, \ + p8##_type p8) {\ + return name##MatcherP9(p0, p1, p2, \ + p3, p4, p5, p6, p7, p8);\ + }\ + template \ + template \ + bool name##MatcherP9::gmock_Impl::MatchAndExplain(\ + arg_type arg,\ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P10(name, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, description)\ + template \ + class name##MatcherP10 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8, \ + p9##_type gmock_p9, \ + const ::testing::internal::Interpolations& gmock_interp)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8), p9(gmock_p9), gmock_interp_(gmock_interp) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + const ::testing::internal::Strings& gmock_printed_params = \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9));\ + *gmock_os << ::testing::internal::FormatMatcherDescription(\ + #name, description, gmock_interp_, gmock_printed_params);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + p9##_type p9;\ + const ::testing::internal::Interpolations gmock_interp_;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, \ + gmock_interp_));\ + }\ + name##MatcherP10(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8, p9##_type gmock_p9) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7), p8(gmock_p8), p9(gmock_p9) {\ + const char* gmock_param_names[] = { #p0, #p1, #p2, #p3, #p4, #p5, #p6, \ + #p7, #p8, #p9, NULL };\ + gmock_interp_ = ::testing::internal::ValidateMatcherDescription(\ + gmock_param_names, ("" description ""));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + p9##_type p9;\ + private:\ + ::testing::internal::Interpolations gmock_interp_;\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP10);\ + };\ + template \ + inline name##MatcherP10 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8, \ + p9##_type p9) {\ + return name##MatcherP10(p0, \ + p1, p2, p3, p4, p5, p6, p7, p8, p9);\ + }\ + template \ + template \ + bool name##MatcherP10::gmock_Impl::MatchAndExplain(\ + arg_type arg,\ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ diff --git a/3rdparty/gmock/include/gmock/gmock-generated-matchers.h.pump b/3rdparty/gmock/include/gmock/gmock-generated-matchers.h.pump new file mode 100644 index 00000000..07a51a36 --- /dev/null +++ b/3rdparty/gmock/include/gmock/gmock-generated-matchers.h.pump @@ -0,0 +1,599 @@ +$$ -*- mode: c++; -*- +$$ This is a Pump source file. Please use Pump to convert it to +$$ gmock-generated-actions.h. +$$ +$var n = 10 $$ The maximum arity we support. +$$ }} This line fixes auto-indentation of the following code in Emacs. +// Copyright 2008, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used variadic matchers. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ + +#include +#include +#include +#include +#include + +namespace testing { +namespace internal { + +$range i 0..n-1 + +// The type of the i-th (0-based) field of Tuple. +#define GMOCK_FIELD_TYPE_(Tuple, i) \ + typename ::std::tr1::tuple_element::type + +// TupleFields is for selecting fields from a +// tuple of type Tuple. It has two members: +// +// type: a tuple type whose i-th field is the ki-th field of Tuple. +// GetSelectedFields(t): returns fields k0, ..., and kn of t as a tuple. +// +// For example, in class TupleFields, 2, 0>, we have: +// +// type is tuple, and +// GetSelectedFields(make_tuple(true, 'a', 42)) is (42, true). + +template +class TupleFields; + +// This generic version is used when there are $n selectors. +template +class TupleFields { + public: + typedef ::std::tr1::tuple<$for i, [[GMOCK_FIELD_TYPE_(Tuple, k$i)]]> type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type($for i, [[get(t)]]); + } +}; + +// The following specialization is used for 0 ~ $(n-1) selectors. + +$for i [[ +$$ }}} +$range j 0..i-1 +$range k 0..n-1 + +template +class TupleFields { + public: + typedef ::std::tr1::tuple<$for j, [[GMOCK_FIELD_TYPE_(Tuple, k$j)]]> type; + static type GetSelectedFields(const Tuple& $if i==0 [[/* t */]] $else [[t]]) { + using ::std::tr1::get; + return type($for j, [[get(t)]]); + } +}; + +]] + +#undef GMOCK_FIELD_TYPE_ + +// Implements the Args() matcher. + +$var ks = [[$for i, [[k$i]]]] +template +class ArgsMatcherImpl : public MatcherInterface { + public: + // ArgsTuple may have top-level const or reference modifiers. + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(ArgsTuple)) RawArgsTuple; + typedef typename internal::TupleFields::type SelectedArgs; + typedef Matcher MonomorphicInnerMatcher; + + template + explicit ArgsMatcherImpl(const InnerMatcher& inner_matcher) + : inner_matcher_(SafeMatcherCast(inner_matcher)) {} + + virtual bool MatchAndExplain(ArgsTuple args, + MatchResultListener* listener) const { + const SelectedArgs& selected_args = GetSelectedArgs(args); + if (!listener->IsInterested()) + return inner_matcher_.Matches(selected_args); + + PrintIndices(listener->stream()); + *listener << "are " << PrintToString(selected_args); + + StringMatchResultListener inner_listener; + const bool match = inner_matcher_.MatchAndExplain(selected_args, + &inner_listener); + PrintIfNotEmpty(inner_listener.str(), listener->stream()); + return match; + } + + virtual void DescribeTo(::std::ostream* os) const { + *os << "are a tuple "; + PrintIndices(os); + inner_matcher_.DescribeTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "are a tuple "; + PrintIndices(os); + inner_matcher_.DescribeNegationTo(os); + } + + private: + static SelectedArgs GetSelectedArgs(ArgsTuple args) { + return TupleFields::GetSelectedFields(args); + } + + // Prints the indices of the selected fields. + static void PrintIndices(::std::ostream* os) { + *os << "whose fields ("; + const int indices[$n] = { $ks }; + for (int i = 0; i < $n; i++) { + if (indices[i] < 0) + break; + + if (i >= 1) + *os << ", "; + + *os << "#" << indices[i]; + } + *os << ") "; + } + + const MonomorphicInnerMatcher inner_matcher_; + + GTEST_DISALLOW_ASSIGN_(ArgsMatcherImpl); +}; + +template +class ArgsMatcher { + public: + explicit ArgsMatcher(const InnerMatcher& inner_matcher) + : inner_matcher_(inner_matcher) {} + + template + operator Matcher() const { + return MakeMatcher(new ArgsMatcherImpl(inner_matcher_)); + } + + private: + const InnerMatcher inner_matcher_; + + GTEST_DISALLOW_ASSIGN_(ArgsMatcher); +}; + +// Implements ElementsAre() of 1-$n arguments. + + +$range i 1..n +$for i [[ +$range j 1..i +template <$for j, [[typename T$j]]> +class ElementsAreMatcher$i { + public: + $if i==1 [[explicit ]]ElementsAreMatcher$i($for j, [[const T$j& e$j]])$if i > 0 [[ : ]] + $for j, [[e$j[[]]_(e$j)]] {} + + template + operator Matcher() const { + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(Container)) + RawContainer; + typedef typename internal::StlContainerView::type::value_type + Element; + +$if i==1 [[ + + // Nokia's Symbian Compiler has a nasty bug where the object put + // in a one-element local array is not destructed when the array + // goes out of scope. This leads to obvious badness as we've + // added the linked_ptr in it to our other linked_ptrs list. + // Hence we implement ElementsAreMatcher1 specially to avoid using + // a local array. + const Matcher matcher = + MatcherCast(e1_); + return MakeMatcher(new ElementsAreMatcherImpl(&matcher, 1)); +]] $else [[ + + const Matcher matchers[] = { + +$for j [[ + MatcherCast(e$j[[]]_), + +]] + }; + + return MakeMatcher(new ElementsAreMatcherImpl(matchers, $i)); +]] + + } + + private: + +$for j [[ + const T$j& e$j[[]]_; + +]] + + GTEST_DISALLOW_ASSIGN_(ElementsAreMatcher$i); +}; + + +]] +} // namespace internal + +// Args(a_matcher) matches a tuple if the selected +// fields of it matches a_matcher. C++ doesn't support default +// arguments for function templates, so we have to overload it. + +$range i 0..n +$for i [[ +$range j 1..i +template <$for j [[int k$j, ]]typename InnerMatcher> +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + + +]] +// ElementsAre(e0, e1, ..., e_n) matches an STL-style container with +// (n + 1) elements, where the i-th element in the container must +// match the i-th argument in the list. Each argument of +// ElementsAre() can be either a value or a matcher. We support up to +// $n arguments. +// +// NOTE: Since ElementsAre() cares about the order of the elements, it +// must not be used with containers whose elements's order is +// undefined (e.g. hash_map). + +inline internal::ElementsAreMatcher0 ElementsAre() { + return internal::ElementsAreMatcher0(); +} + +$range i 1..n +$for i [[ +$range j 1..i + +template <$for j, [[typename T$j]]> +inline internal::ElementsAreMatcher$i<$for j, [[T$j]]> ElementsAre($for j, [[const T$j& e$j]]) { + return internal::ElementsAreMatcher$i<$for j, [[T$j]]>($for j, [[e$j]]); +} + +]] + +// ElementsAreArray(array) and ElementAreArray(array, count) are like +// ElementsAre(), except that they take an array of values or +// matchers. The former form infers the size of 'array', which must +// be a static C-style array. In the latter form, 'array' can either +// be a static array or a pointer to a dynamically created array. + +template +inline internal::ElementsAreArrayMatcher ElementsAreArray( + const T* first, size_t count) { + return internal::ElementsAreArrayMatcher(first, count); +} + +template +inline internal::ElementsAreArrayMatcher +ElementsAreArray(const T (&array)[N]) { + return internal::ElementsAreArrayMatcher(array, N); +} + +} // namespace testing +$$ } // This Pump meta comment fixes auto-indentation in Emacs. It will not +$$ // show up in the generated code. + + +// The MATCHER* family of macros can be used in a namespace scope to +// define custom matchers easily. +// +// Basic Usage +// =========== +// +// The syntax +// +// MATCHER(name, description_string) { statements; } +// +// defines a matcher with the given name that executes the statements, +// which must return a bool to indicate if the match succeeds. Inside +// the statements, you can refer to the value being matched by 'arg', +// and refer to its type by 'arg_type'. +// +// The description string documents what the matcher does, and is used +// to generate the failure message when the match fails. Since a +// MATCHER() is usually defined in a header file shared by multiple +// C++ source files, we require the description to be a C-string +// literal to avoid possible side effects. It can be empty, in which +// case we'll use the sequence of words in the matcher name as the +// description. +// +// For example: +// +// MATCHER(IsEven, "") { return (arg % 2) == 0; } +// +// allows you to write +// +// // Expects mock_foo.Bar(n) to be called where n is even. +// EXPECT_CALL(mock_foo, Bar(IsEven())); +// +// or, +// +// // Verifies that the value of some_expression is even. +// EXPECT_THAT(some_expression, IsEven()); +// +// If the above assertion fails, it will print something like: +// +// Value of: some_expression +// Expected: is even +// Actual: 7 +// +// where the description "is even" is automatically calculated from the +// matcher name IsEven. +// +// Argument Type +// ============= +// +// Note that the type of the value being matched (arg_type) is +// determined by the context in which you use the matcher and is +// supplied to you by the compiler, so you don't need to worry about +// declaring it (nor can you). This allows the matcher to be +// polymorphic. For example, IsEven() can be used to match any type +// where the value of "(arg % 2) == 0" can be implicitly converted to +// a bool. In the "Bar(IsEven())" example above, if method Bar() +// takes an int, 'arg_type' will be int; if it takes an unsigned long, +// 'arg_type' will be unsigned long; and so on. +// +// Parameterizing Matchers +// ======================= +// +// Sometimes you'll want to parameterize the matcher. For that you +// can use another macro: +// +// MATCHER_P(name, param_name, description_string) { statements; } +// +// For example: +// +// MATCHER_P(HasAbsoluteValue, value, "") { return abs(arg) == value; } +// +// will allow you to write: +// +// EXPECT_THAT(Blah("a"), HasAbsoluteValue(n)); +// +// which may lead to this message (assuming n is 10): +// +// Value of: Blah("a") +// Expected: has absolute value 10 +// Actual: -9 +// +// Note that both the matcher description and its parameter are +// printed, making the message human-friendly. +// +// In the matcher definition body, you can write 'foo_type' to +// reference the type of a parameter named 'foo'. For example, in the +// body of MATCHER_P(HasAbsoluteValue, value) above, you can write +// 'value_type' to refer to the type of 'value'. +// +// We also provide MATCHER_P2, MATCHER_P3, ..., up to MATCHER_P$n to +// support multi-parameter matchers. +// +// Describing Parameterized Matchers +// ================================= +// +// When defining a parameterized matcher, you can use Python-style +// interpolations in the description string to refer to the parameter +// values. We support the following syntax currently: +// +// %% a single '%' character +// %(*)s all parameters of the matcher printed as a tuple +// %(foo)s value of the matcher parameter named 'foo' +// +// For example, +// +// MATCHER_P2(InClosedRange, low, hi, "is in range [%(low)s, %(hi)s]") { +// return low <= arg && arg <= hi; +// } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// +// would generate a failure that contains the message: +// +// Expected: is in range [4, 6] +// +// If you specify "" as the description, the failure message will +// contain the sequence of words in the matcher name followed by the +// parameter values printed as a tuple. For example, +// +// MATCHER_P2(InClosedRange, low, hi, "") { ... } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// +// would generate a failure that contains the text: +// +// Expected: in closed range (4, 6) +// +// Types of Matcher Parameters +// =========================== +// +// For the purpose of typing, you can view +// +// MATCHER_Pk(Foo, p1, ..., pk, description_string) { ... } +// +// as shorthand for +// +// template +// FooMatcherPk +// Foo(p1_type p1, ..., pk_type pk) { ... } +// +// When you write Foo(v1, ..., vk), the compiler infers the types of +// the parameters v1, ..., and vk for you. If you are not happy with +// the result of the type inference, you can specify the types by +// explicitly instantiating the template, as in Foo(5, +// false). As said earlier, you don't get to (or need to) specify +// 'arg_type' as that's determined by the context in which the matcher +// is used. You can assign the result of expression Foo(p1, ..., pk) +// to a variable of type FooMatcherPk. This +// can be useful when composing matchers. +// +// While you can instantiate a matcher template with reference types, +// passing the parameters by pointer usually makes your code more +// readable. If, however, you still want to pass a parameter by +// reference, be aware that in the failure message generated by the +// matcher you will see the value of the referenced object but not its +// address. +// +// Explaining Match Results +// ======================== +// +// Sometimes the matcher description alone isn't enough to explain why +// the match has failed or succeeded. For example, when expecting a +// long string, it can be very helpful to also print the diff between +// the expected string and the actual one. To achieve that, you can +// optionally stream additional information to a special variable +// named result_listener, whose type is a pointer to class +// MatchResultListener: +// +// MATCHER_P(EqualsLongString, str, "") { +// if (arg == str) return true; +// +// *result_listener << "the difference: " +/// << DiffStrings(str, arg); +// return false; +// } +// +// Overloading Matchers +// ==================== +// +// You can overload matchers with different numbers of parameters: +// +// MATCHER_P(Blah, a, description_string1) { ... } +// MATCHER_P2(Blah, a, b, description_string2) { ... } +// +// Caveats +// ======= +// +// When defining a new matcher, you should also consider implementing +// MatcherInterface or using MakePolymorphicMatcher(). These +// approaches require more work than the MATCHER* macros, but also +// give you more control on the types of the value being matched and +// the matcher parameters, which may leads to better compiler error +// messages when the matcher is used wrong. They also allow +// overloading matchers based on parameter types (as opposed to just +// based on the number of parameters). +// +// MATCHER*() can only be used in a namespace scope. The reason is +// that C++ doesn't yet allow function-local types to be used to +// instantiate templates. The up-coming C++0x standard will fix this. +// Once that's done, we'll consider supporting using MATCHER*() inside +// a function. +// +// More Information +// ================ +// +// To learn more about using these macros, please search for 'MATCHER' +// on http://code.google.com/p/googlemock/wiki/CookBook. + +$range i 0..n +$for i + +[[ +$var macro_name = [[$if i==0 [[MATCHER]] $elif i==1 [[MATCHER_P]] + $else [[MATCHER_P$i]]]] +$var class_name = [[name##Matcher[[$if i==0 [[]] $elif i==1 [[P]] + $else [[P$i]]]]]] +$range j 0..i-1 +$var template = [[$if i==0 [[]] $else [[ + + template <$for j, [[typename p$j##_type]]>\ +]]]] +$var ctor_param_list = [[$for j, [[p$j##_type gmock_p$j]]]] +$var impl_ctor_param_list = [[$for j [[p$j##_type gmock_p$j, ]] +const ::testing::internal::Interpolations& gmock_interp]] +$var impl_inits = [[ : $for j [[p$j(gmock_p$j), ]]gmock_interp_(gmock_interp)]] +$var inits = [[$if i==0 [[]] $else [[ : $for j, [[p$j(gmock_p$j)]]]]]] +$var params_and_interp = [[$for j [[p$j, ]]gmock_interp_]] +$var params = [[$for j, [[p$j]]]] +$var param_types = [[$if i==0 [[]] $else [[<$for j, [[p$j##_type]]>]]]] +$var param_types_and_names = [[$for j, [[p$j##_type p$j]]]] +$var param_field_decls = [[$for j +[[ + + p$j##_type p$j;\ +]]]] +$var param_field_decls2 = [[$for j +[[ + + p$j##_type p$j;\ +]]]] + +#define $macro_name(name$for j [[, p$j]], description)\$template + class $class_name {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + [[$if i==1 [[explicit ]]]]gmock_Impl($impl_ctor_param_list)\ + $impl_inits {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + const ::testing::internal::Strings& gmock_printed_params = \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple<$for j, [[p$j##_type]]>($for j, [[p$j]]));\ + *gmock_os << ::testing::internal::FormatMatcherDescription(\ + #name, description, gmock_interp_, gmock_printed_params);\ + }\$param_field_decls + const ::testing::internal::Interpolations gmock_interp_;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl($params_and_interp));\ + }\ + $class_name($ctor_param_list)$inits {\ + const char* gmock_param_names[] = { $for j [[#p$j, ]]NULL };\ + gmock_interp_ = ::testing::internal::ValidateMatcherDescription(\ + gmock_param_names, ("" description ""));\ + }\$param_field_decls2 + private:\ + ::testing::internal::Interpolations gmock_interp_;\ + GTEST_DISALLOW_ASSIGN_($class_name);\ + };\$template + inline $class_name$param_types name($param_types_and_names) {\ + return $class_name$param_types($params);\ + }\$template + template \ + bool $class_name$param_types::gmock_Impl::MatchAndExplain(\ + arg_type arg,\ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const +]] + + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ diff --git a/3rdparty/gmock/include/gmock/gmock-generated-nice-strict.h b/3rdparty/gmock/include/gmock/gmock-generated-nice-strict.h new file mode 100644 index 00000000..435467fa --- /dev/null +++ b/3rdparty/gmock/include/gmock/gmock-generated-nice-strict.h @@ -0,0 +1,274 @@ +// This file was GENERATED by a script. DO NOT EDIT BY HAND!!! + +// Copyright 2008, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Implements class templates NiceMock and StrictMock. +// +// Given a mock class MockFoo that is created using Google Mock, +// NiceMock is a subclass of MockFoo that allows +// uninteresting calls (i.e. calls to mock methods that have no +// EXPECT_CALL specs), and StrictMock is a subclass of +// MockFoo that treats all uninteresting calls as errors. +// +// NiceMock and StrictMock "inherits" the constructors of their +// respective base class, with up-to 10 arguments. Therefore you can +// write NiceMock(5, "a") to construct a nice mock where +// MockFoo has a constructor that accepts (int, const char*), for +// example. +// +// A known limitation is that NiceMock and +// StrictMock only works for mock methods defined using the +// MOCK_METHOD* family of macros DIRECTLY in the MockFoo class. If a +// mock method is defined in a base class of MockFoo, the "nice" or +// "strict" modifier may not affect it, depending on the compiler. In +// particular, nesting NiceMock and StrictMock is NOT supported. +// +// Another known limitation is that the constructors of the base mock +// cannot have arguments passed by non-const reference, which are +// banned by the Google C++ style guide anyway. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ + +#include +#include + +namespace testing { + +template +class NiceMock : public MockClass { + public: + // We don't factor out the constructor body to a common method, as + // we have to avoid a possible clash with members of MockClass. + NiceMock() { + ::testing::Mock::AllowUninterestingCalls( + internal::implicit_cast(this)); + } + + // C++ doesn't (yet) allow inheritance of constructors, so we have + // to define it for each arity. + template + explicit NiceMock(const A1& a1) : MockClass(a1) { + ::testing::Mock::AllowUninterestingCalls( + internal::implicit_cast(this)); + } + template + NiceMock(const A1& a1, const A2& a2) : MockClass(a1, a2) { + ::testing::Mock::AllowUninterestingCalls( + internal::implicit_cast(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3) : MockClass(a1, a2, a3) { + ::testing::Mock::AllowUninterestingCalls( + internal::implicit_cast(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, + const A4& a4) : MockClass(a1, a2, a3, a4) { + ::testing::Mock::AllowUninterestingCalls( + internal::implicit_cast(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5) : MockClass(a1, a2, a3, a4, a5) { + ::testing::Mock::AllowUninterestingCalls( + internal::implicit_cast(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6) : MockClass(a1, a2, a3, a4, a5, a6) { + ::testing::Mock::AllowUninterestingCalls( + internal::implicit_cast(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7) : MockClass(a1, a2, a3, a4, a5, + a6, a7) { + ::testing::Mock::AllowUninterestingCalls( + internal::implicit_cast(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8) : MockClass(a1, + a2, a3, a4, a5, a6, a7, a8) { + ::testing::Mock::AllowUninterestingCalls( + internal::implicit_cast(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8, + const A9& a9) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9) { + ::testing::Mock::AllowUninterestingCalls( + internal::implicit_cast(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9, + const A10& a10) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { + ::testing::Mock::AllowUninterestingCalls( + internal::implicit_cast(this)); + } + + virtual ~NiceMock() { + ::testing::Mock::UnregisterCallReaction( + internal::implicit_cast(this)); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(NiceMock); +}; + +template +class StrictMock : public MockClass { + public: + // We don't factor out the constructor body to a common method, as + // we have to avoid a possible clash with members of MockClass. + StrictMock() { + ::testing::Mock::FailUninterestingCalls( + internal::implicit_cast(this)); + } + + template + explicit StrictMock(const A1& a1) : MockClass(a1) { + ::testing::Mock::FailUninterestingCalls( + internal::implicit_cast(this)); + } + template + StrictMock(const A1& a1, const A2& a2) : MockClass(a1, a2) { + ::testing::Mock::FailUninterestingCalls( + internal::implicit_cast(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3) : MockClass(a1, a2, a3) { + ::testing::Mock::FailUninterestingCalls( + internal::implicit_cast(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, + const A4& a4) : MockClass(a1, a2, a3, a4) { + ::testing::Mock::FailUninterestingCalls( + internal::implicit_cast(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5) : MockClass(a1, a2, a3, a4, a5) { + ::testing::Mock::FailUninterestingCalls( + internal::implicit_cast(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6) : MockClass(a1, a2, a3, a4, a5, a6) { + ::testing::Mock::FailUninterestingCalls( + internal::implicit_cast(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7) : MockClass(a1, a2, a3, a4, a5, + a6, a7) { + ::testing::Mock::FailUninterestingCalls( + internal::implicit_cast(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8) : MockClass(a1, + a2, a3, a4, a5, a6, a7, a8) { + ::testing::Mock::FailUninterestingCalls( + internal::implicit_cast(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8, + const A9& a9) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9) { + ::testing::Mock::FailUninterestingCalls( + internal::implicit_cast(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9, + const A10& a10) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { + ::testing::Mock::FailUninterestingCalls( + internal::implicit_cast(this)); + } + + virtual ~StrictMock() { + ::testing::Mock::UnregisterCallReaction( + internal::implicit_cast(this)); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(StrictMock); +}; + +// The following specializations catch some (relatively more common) +// user errors of nesting nice and strict mocks. They do NOT catch +// all possible errors. + +// These specializations are declared but not defined, as NiceMock and +// StrictMock cannot be nested. +template +class NiceMock >; +template +class NiceMock >; +template +class StrictMock >; +template +class StrictMock >; + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ diff --git a/3rdparty/gmock/include/gmock/gmock-generated-nice-strict.h.pump b/3rdparty/gmock/include/gmock/gmock-generated-nice-strict.h.pump new file mode 100644 index 00000000..96371f57 --- /dev/null +++ b/3rdparty/gmock/include/gmock/gmock-generated-nice-strict.h.pump @@ -0,0 +1,160 @@ +$$ -*- mode: c++; -*- +$$ This is a Pump source file. Please use Pump to convert it to +$$ gmock-generated-nice-strict.h. +$$ +$var n = 10 $$ The maximum arity we support. +// Copyright 2008, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Implements class templates NiceMock and StrictMock. +// +// Given a mock class MockFoo that is created using Google Mock, +// NiceMock is a subclass of MockFoo that allows +// uninteresting calls (i.e. calls to mock methods that have no +// EXPECT_CALL specs), and StrictMock is a subclass of +// MockFoo that treats all uninteresting calls as errors. +// +// NiceMock and StrictMock "inherits" the constructors of their +// respective base class, with up-to $n arguments. Therefore you can +// write NiceMock(5, "a") to construct a nice mock where +// MockFoo has a constructor that accepts (int, const char*), for +// example. +// +// A known limitation is that NiceMock and +// StrictMock only works for mock methods defined using the +// MOCK_METHOD* family of macros DIRECTLY in the MockFoo class. If a +// mock method is defined in a base class of MockFoo, the "nice" or +// "strict" modifier may not affect it, depending on the compiler. In +// particular, nesting NiceMock and StrictMock is NOT supported. +// +// Another known limitation is that the constructors of the base mock +// cannot have arguments passed by non-const reference, which are +// banned by the Google C++ style guide anyway. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ + +#include +#include + +namespace testing { + +template +class NiceMock : public MockClass { + public: + // We don't factor out the constructor body to a common method, as + // we have to avoid a possible clash with members of MockClass. + NiceMock() { + ::testing::Mock::AllowUninterestingCalls( + internal::implicit_cast(this)); + } + + // C++ doesn't (yet) allow inheritance of constructors, so we have + // to define it for each arity. + template + explicit NiceMock(const A1& a1) : MockClass(a1) { + ::testing::Mock::AllowUninterestingCalls( + internal::implicit_cast(this)); + } + +$range i 2..n +$for i [[ +$range j 1..i + template <$for j, [[typename A$j]]> + NiceMock($for j, [[const A$j& a$j]]) : MockClass($for j, [[a$j]]) { + ::testing::Mock::AllowUninterestingCalls( + internal::implicit_cast(this)); + } + + +]] + virtual ~NiceMock() { + ::testing::Mock::UnregisterCallReaction( + internal::implicit_cast(this)); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(NiceMock); +}; + +template +class StrictMock : public MockClass { + public: + // We don't factor out the constructor body to a common method, as + // we have to avoid a possible clash with members of MockClass. + StrictMock() { + ::testing::Mock::FailUninterestingCalls( + internal::implicit_cast(this)); + } + + template + explicit StrictMock(const A1& a1) : MockClass(a1) { + ::testing::Mock::FailUninterestingCalls( + internal::implicit_cast(this)); + } + +$for i [[ +$range j 1..i + template <$for j, [[typename A$j]]> + StrictMock($for j, [[const A$j& a$j]]) : MockClass($for j, [[a$j]]) { + ::testing::Mock::FailUninterestingCalls( + internal::implicit_cast(this)); + } + + +]] + virtual ~StrictMock() { + ::testing::Mock::UnregisterCallReaction( + internal::implicit_cast(this)); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(StrictMock); +}; + +// The following specializations catch some (relatively more common) +// user errors of nesting nice and strict mocks. They do NOT catch +// all possible errors. + +// These specializations are declared but not defined, as NiceMock and +// StrictMock cannot be nested. +template +class NiceMock >; +template +class NiceMock >; +template +class StrictMock >; +template +class StrictMock >; + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ diff --git a/3rdparty/gmock/include/gmock/gmock-matchers.h b/3rdparty/gmock/include/gmock/gmock-matchers.h new file mode 100644 index 00000000..66efecd4 --- /dev/null +++ b/3rdparty/gmock/include/gmock/gmock-matchers.h @@ -0,0 +1,2920 @@ +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used argument matchers. More +// matchers can be defined by the user implementing the +// MatcherInterface interface if necessary. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ + +#include +#include +#include // NOLINT +#include +#include +#include + +#include +#include +#include +#include + +namespace testing { + +// To implement a matcher Foo for type T, define: +// 1. a class FooMatcherImpl that implements the +// MatcherInterface interface, and +// 2. a factory function that creates a Matcher object from a +// FooMatcherImpl*. +// +// The two-level delegation design makes it possible to allow a user +// to write "v" instead of "Eq(v)" where a Matcher is expected, which +// is impossible if we pass matchers by pointers. It also eases +// ownership management as Matcher objects can now be copied like +// plain values. + +// MatchResultListener is an abstract class. Its << operator can be +// used by a matcher to explain why a value matches or doesn't match. +// +// TODO(wan@google.com): add method +// bool InterestedInWhy(bool result) const; +// to indicate whether the listener is interested in why the match +// result is 'result'. +class MatchResultListener { + public: + // Creates a listener object with the given underlying ostream. The + // listener does not own the ostream. + explicit MatchResultListener(::std::ostream* os) : stream_(os) {} + virtual ~MatchResultListener() = 0; // Makes this class abstract. + + // Streams x to the underlying ostream; does nothing if the ostream + // is NULL. + template + MatchResultListener& operator<<(const T& x) { + if (stream_ != NULL) + *stream_ << x; + return *this; + } + + // Returns the underlying ostream. + ::std::ostream* stream() { return stream_; } + + // Returns true iff the listener is interested in an explanation of + // the match result. A matcher's MatchAndExplain() method can use + // this information to avoid generating the explanation when no one + // intends to hear it. + bool IsInterested() const { return stream_ != NULL; } + + private: + ::std::ostream* const stream_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(MatchResultListener); +}; + +inline MatchResultListener::~MatchResultListener() { +} + +// The implementation of a matcher. +template +class MatcherInterface { + public: + virtual ~MatcherInterface() {} + + // Returns true iff the matcher matches x; also explains the match + // result to 'listener', in the form of a non-restrictive relative + // clause ("which ...", "whose ...", etc) that describes x. For + // example, the MatchAndExplain() method of the Pointee(...) matcher + // should generate an explanation like "which points to ...". + // + // You should override this method when defining a new matcher. + // + // It's the responsibility of the caller (Google Mock) to guarantee + // that 'listener' is not NULL. This helps to simplify a matcher's + // implementation when it doesn't care about the performance, as it + // can talk to 'listener' without checking its validity first. + // However, in order to implement dummy listeners efficiently, + // listener->stream() may be NULL. + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const = 0; + + // Describes this matcher to an ostream. The function should print + // a verb phrase that describes the property a value matching this + // matcher should have. The subject of the verb phrase is the value + // being matched. For example, the DescribeTo() method of the Gt(7) + // matcher prints "is greater than 7". + virtual void DescribeTo(::std::ostream* os) const = 0; + + // Describes the negation of this matcher to an ostream. For + // example, if the description of this matcher is "is greater than + // 7", the negated description could be "is not greater than 7". + // You are not required to override this when implementing + // MatcherInterface, but it is highly advised so that your matcher + // can produce good error messages. + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "not ("; + DescribeTo(os); + *os << ")"; + } +}; + +namespace internal { + +// A match result listener that ignores the explanation. +class DummyMatchResultListener : public MatchResultListener { + public: + DummyMatchResultListener() : MatchResultListener(NULL) {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(DummyMatchResultListener); +}; + +// A match result listener that forwards the explanation to a given +// ostream. The difference between this and MatchResultListener is +// that the former is concrete. +class StreamMatchResultListener : public MatchResultListener { + public: + explicit StreamMatchResultListener(::std::ostream* os) + : MatchResultListener(os) {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(StreamMatchResultListener); +}; + +// A match result listener that stores the explanation in a string. +class StringMatchResultListener : public MatchResultListener { + public: + StringMatchResultListener() : MatchResultListener(&ss_) {} + + // Returns the explanation heard so far. + internal::string str() const { return ss_.str(); } + + private: + ::std::stringstream ss_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(StringMatchResultListener); +}; + +// An internal class for implementing Matcher, which will derive +// from it. We put functionalities common to all Matcher +// specializations here to avoid code duplication. +template +class MatcherBase { + public: + // Returns true iff the matcher matches x; also explains the match + // result to 'listener'. + bool MatchAndExplain(T x, MatchResultListener* listener) const { + return impl_->MatchAndExplain(x, listener); + } + + // Returns true iff this matcher matches x. + bool Matches(T x) const { + DummyMatchResultListener dummy; + return MatchAndExplain(x, &dummy); + } + + // Describes this matcher to an ostream. + void DescribeTo(::std::ostream* os) const { impl_->DescribeTo(os); } + + // Describes the negation of this matcher to an ostream. + void DescribeNegationTo(::std::ostream* os) const { + impl_->DescribeNegationTo(os); + } + + // Explains why x matches, or doesn't match, the matcher. + void ExplainMatchResultTo(T x, ::std::ostream* os) const { + StreamMatchResultListener listener(os); + MatchAndExplain(x, &listener); + } + + protected: + MatcherBase() {} + + // Constructs a matcher from its implementation. + explicit MatcherBase(const MatcherInterface* impl) + : impl_(impl) {} + + virtual ~MatcherBase() {} + + private: + // shared_ptr (util/gtl/shared_ptr.h) and linked_ptr have similar + // interfaces. The former dynamically allocates a chunk of memory + // to hold the reference count, while the latter tracks all + // references using a circular linked list without allocating + // memory. It has been observed that linked_ptr performs better in + // typical scenarios. However, shared_ptr can out-perform + // linked_ptr when there are many more uses of the copy constructor + // than the default constructor. + // + // If performance becomes a problem, we should see if using + // shared_ptr helps. + ::testing::internal::linked_ptr > impl_; +}; + +} // namespace internal + +// A Matcher is a copyable and IMMUTABLE (except by assignment) +// object that can check whether a value of type T matches. The +// implementation of Matcher is just a linked_ptr to const +// MatcherInterface, so copying is fairly cheap. Don't inherit +// from Matcher! +template +class Matcher : public internal::MatcherBase { + public: + // Constructs a null matcher. Needed for storing Matcher objects in + // STL containers. + Matcher() {} + + // Constructs a matcher from its implementation. + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + // Implicit constructor here allows people to write + // EXPECT_CALL(foo, Bar(5)) instead of EXPECT_CALL(foo, Bar(Eq(5))) sometimes + Matcher(T value); // NOLINT +}; + +// The following two specializations allow the user to write str +// instead of Eq(str) and "foo" instead of Eq("foo") when a string +// matcher is expected. +template <> +class Matcher + : public internal::MatcherBase { + public: + Matcher() {} + + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a string object. + Matcher(const internal::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT +}; + +template <> +class Matcher + : public internal::MatcherBase { + public: + Matcher() {} + + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a string object. + Matcher(const internal::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT +}; + +// The PolymorphicMatcher class template makes it easy to implement a +// polymorphic matcher (i.e. a matcher that can match values of more +// than one type, e.g. Eq(n) and NotNull()). +// +// To define a polymorphic matcher, a user should provide an Impl +// class that has a DescribeTo() method and a DescribeNegationTo() +// method, and define a member function (or member function template) +// +// bool MatchAndExplain(const Value& value, +// MatchResultListener* listener) const; +// +// See the definition of NotNull() for a complete example. +template +class PolymorphicMatcher { + public: + explicit PolymorphicMatcher(const Impl& an_impl) : impl_(an_impl) {} + + // Returns a mutable reference to the underlying matcher + // implementation object. + Impl& mutable_impl() { return impl_; } + + // Returns an immutable reference to the underlying matcher + // implementation object. + const Impl& impl() const { return impl_; } + + template + operator Matcher() const { + return Matcher(new MonomorphicImpl(impl_)); + } + + private: + template + class MonomorphicImpl : public MatcherInterface { + public: + explicit MonomorphicImpl(const Impl& impl) : impl_(impl) {} + + virtual void DescribeTo(::std::ostream* os) const { + impl_.DescribeTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + impl_.DescribeNegationTo(os); + } + + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const { + return impl_.MatchAndExplain(x, listener); + } + + private: + const Impl impl_; + + GTEST_DISALLOW_ASSIGN_(MonomorphicImpl); + }; + + Impl impl_; + + GTEST_DISALLOW_ASSIGN_(PolymorphicMatcher); +}; + +// Creates a matcher from its implementation. This is easier to use +// than the Matcher constructor as it doesn't require you to +// explicitly write the template argument, e.g. +// +// MakeMatcher(foo); +// vs +// Matcher(foo); +template +inline Matcher MakeMatcher(const MatcherInterface* impl) { + return Matcher(impl); +}; + +// Creates a polymorphic matcher from its implementation. This is +// easier to use than the PolymorphicMatcher constructor as it +// doesn't require you to explicitly write the template argument, e.g. +// +// MakePolymorphicMatcher(foo); +// vs +// PolymorphicMatcher(foo); +template +inline PolymorphicMatcher MakePolymorphicMatcher(const Impl& impl) { + return PolymorphicMatcher(impl); +} + +// In order to be safe and clear, casting between different matcher +// types is done explicitly via MatcherCast(m), which takes a +// matcher m and returns a Matcher. It compiles only when T can be +// statically converted to the argument type of m. +template +Matcher MatcherCast(M m); + +// Implements SafeMatcherCast(). +// +// We use an intermediate class to do the actual safe casting as Nokia's +// Symbian compiler cannot decide between +// template ... (M) and +// template ... (const Matcher&) +// for function templates but can for member function templates. +template +class SafeMatcherCastImpl { + public: + // This overload handles polymorphic matchers only since monomorphic + // matchers are handled by the next one. + template + static inline Matcher Cast(M polymorphic_matcher) { + return Matcher(polymorphic_matcher); + } + + // This overload handles monomorphic matchers. + // + // In general, if type T can be implicitly converted to type U, we can + // safely convert a Matcher to a Matcher (i.e. Matcher is + // contravariant): just keep a copy of the original Matcher, convert the + // argument from type T to U, and then pass it to the underlying Matcher. + // The only exception is when U is a reference and T is not, as the + // underlying Matcher may be interested in the argument's address, which + // is not preserved in the conversion from T to U. + template + static inline Matcher Cast(const Matcher& matcher) { + // Enforce that T can be implicitly converted to U. + GMOCK_COMPILE_ASSERT_((internal::ImplicitlyConvertible::value), + T_must_be_implicitly_convertible_to_U); + // Enforce that we are not converting a non-reference type T to a reference + // type U. + GMOCK_COMPILE_ASSERT_( + internal::is_reference::value || !internal::is_reference::value, + cannot_convert_non_referentce_arg_to_reference); + // In case both T and U are arithmetic types, enforce that the + // conversion is not lossy. + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(T)) RawT; + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(U)) RawU; + const bool kTIsOther = GMOCK_KIND_OF_(RawT) == internal::kOther; + const bool kUIsOther = GMOCK_KIND_OF_(RawU) == internal::kOther; + GMOCK_COMPILE_ASSERT_( + kTIsOther || kUIsOther || + (internal::LosslessArithmeticConvertible::value), + conversion_of_arithmetic_types_must_be_lossless); + return MatcherCast(matcher); + } +}; + +template +inline Matcher SafeMatcherCast(const M& polymorphic_matcher) { + return SafeMatcherCastImpl::Cast(polymorphic_matcher); +} + +// A() returns a matcher that matches any value of type T. +template +Matcher A(); + +// Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION +// and MUST NOT BE USED IN USER CODE!!! +namespace internal { + +// If the explanation is not empty, prints it to the ostream. +inline void PrintIfNotEmpty(const internal::string& explanation, + std::ostream* os) { + if (explanation != "" && os != NULL) { + *os << ", " << explanation; + } +} + +// Matches the value against the given matcher, prints the value and explains +// the match result to the listener. Returns the match result. +// 'listener' must not be NULL. +// Value cannot be passed by const reference, because some matchers take a +// non-const argument. +template +bool MatchPrintAndExplain(Value& value, const Matcher& matcher, + MatchResultListener* listener) { + if (!listener->IsInterested()) { + // If the listener is not interested, we do not need to construct the + // inner explanation. + return matcher.Matches(value); + } + + StringMatchResultListener inner_listener; + const bool match = matcher.MatchAndExplain(value, &inner_listener); + + UniversalPrint(value, listener->stream()); + PrintIfNotEmpty(inner_listener.str(), listener->stream()); + + return match; +} + +// An internal helper class for doing compile-time loop on a tuple's +// fields. +template +class TuplePrefix { + public: + // TuplePrefix::Matches(matcher_tuple, value_tuple) returns true + // iff the first N fields of matcher_tuple matches the first N + // fields of value_tuple, respectively. + template + static bool Matches(const MatcherTuple& matcher_tuple, + const ValueTuple& value_tuple) { + using ::std::tr1::get; + return TuplePrefix::Matches(matcher_tuple, value_tuple) + && get(matcher_tuple).Matches(get(value_tuple)); + } + + // TuplePrefix::ExplainMatchFailuresTo(matchers, values, os) + // describes failures in matching the first N fields of matchers + // against the first N fields of values. If there is no failure, + // nothing will be streamed to os. + template + static void ExplainMatchFailuresTo(const MatcherTuple& matchers, + const ValueTuple& values, + ::std::ostream* os) { + using ::std::tr1::tuple_element; + using ::std::tr1::get; + + // First, describes failures in the first N - 1 fields. + TuplePrefix::ExplainMatchFailuresTo(matchers, values, os); + + // Then describes the failure (if any) in the (N - 1)-th (0-based) + // field. + typename tuple_element::type matcher = + get(matchers); + typedef typename tuple_element::type Value; + Value value = get(values); + StringMatchResultListener listener; + if (!matcher.MatchAndExplain(value, &listener)) { + // TODO(wan): include in the message the name of the parameter + // as used in MOCK_METHOD*() when possible. + *os << " Expected arg #" << N - 1 << ": "; + get(matchers).DescribeTo(os); + *os << "\n Actual: "; + // We remove the reference in type Value to prevent the + // universal printer from printing the address of value, which + // isn't interesting to the user most of the time. The + // matcher's MatchAndExplain() method handles the case when + // the address is interesting. + internal::UniversalPrint(value, os); + PrintIfNotEmpty(listener.str(), os); + *os << "\n"; + } + } +}; + +// The base case. +template <> +class TuplePrefix<0> { + public: + template + static bool Matches(const MatcherTuple& /* matcher_tuple */, + const ValueTuple& /* value_tuple */) { + return true; + } + + template + static void ExplainMatchFailuresTo(const MatcherTuple& /* matchers */, + const ValueTuple& /* values */, + ::std::ostream* /* os */) {} +}; + +// TupleMatches(matcher_tuple, value_tuple) returns true iff all +// matchers in matcher_tuple match the corresponding fields in +// value_tuple. It is a compiler error if matcher_tuple and +// value_tuple have different number of fields or incompatible field +// types. +template +bool TupleMatches(const MatcherTuple& matcher_tuple, + const ValueTuple& value_tuple) { + using ::std::tr1::tuple_size; + // Makes sure that matcher_tuple and value_tuple have the same + // number of fields. + GMOCK_COMPILE_ASSERT_(tuple_size::value == + tuple_size::value, + matcher_and_value_have_different_numbers_of_fields); + return TuplePrefix::value>:: + Matches(matcher_tuple, value_tuple); +} + +// Describes failures in matching matchers against values. If there +// is no failure, nothing will be streamed to os. +template +void ExplainMatchFailureTupleTo(const MatcherTuple& matchers, + const ValueTuple& values, + ::std::ostream* os) { + using ::std::tr1::tuple_size; + TuplePrefix::value>::ExplainMatchFailuresTo( + matchers, values, os); +} + +// The MatcherCastImpl class template is a helper for implementing +// MatcherCast(). We need this helper in order to partially +// specialize the implementation of MatcherCast() (C++ allows +// class/struct templates to be partially specialized, but not +// function templates.). + +// This general version is used when MatcherCast()'s argument is a +// polymorphic matcher (i.e. something that can be converted to a +// Matcher but is not one yet; for example, Eq(value)). +template +class MatcherCastImpl { + public: + static Matcher Cast(M polymorphic_matcher) { + return Matcher(polymorphic_matcher); + } +}; + +// This more specialized version is used when MatcherCast()'s argument +// is already a Matcher. This only compiles when type T can be +// statically converted to type U. +template +class MatcherCastImpl > { + public: + static Matcher Cast(const Matcher& source_matcher) { + return Matcher(new Impl(source_matcher)); + } + + private: + class Impl : public MatcherInterface { + public: + explicit Impl(const Matcher& source_matcher) + : source_matcher_(source_matcher) {} + + // We delegate the matching logic to the source matcher. + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const { + return source_matcher_.MatchAndExplain(static_cast(x), listener); + } + + virtual void DescribeTo(::std::ostream* os) const { + source_matcher_.DescribeTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + source_matcher_.DescribeNegationTo(os); + } + + private: + const Matcher source_matcher_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; +}; + +// This even more specialized version is used for efficiently casting +// a matcher to its own type. +template +class MatcherCastImpl > { + public: + static Matcher Cast(const Matcher& matcher) { return matcher; } +}; + +// Implements A(). +template +class AnyMatcherImpl : public MatcherInterface { + public: + virtual bool MatchAndExplain( + T /* x */, MatchResultListener* /* listener */) const { return true; } + virtual void DescribeTo(::std::ostream* os) const { *os << "is anything"; } + virtual void DescribeNegationTo(::std::ostream* os) const { + // This is mostly for completeness' safe, as it's not very useful + // to write Not(A()). However we cannot completely rule out + // such a possibility, and it doesn't hurt to be prepared. + *os << "never matches"; + } +}; + +// Implements _, a matcher that matches any value of any +// type. This is a polymorphic matcher, so we need a template type +// conversion operator to make it appearing as a Matcher for any +// type T. +class AnythingMatcher { + public: + template + operator Matcher() const { return A(); } +}; + +// Implements a matcher that compares a given value with a +// pre-supplied value using one of the ==, <=, <, etc, operators. The +// two values being compared don't have to have the same type. +// +// The matcher defined here is polymorphic (for example, Eq(5) can be +// used to match an int, a short, a double, etc). Therefore we use +// a template type conversion operator in the implementation. +// +// We define this as a macro in order to eliminate duplicated source +// code. +// +// The following template definition assumes that the Rhs parameter is +// a "bare" type (i.e. neither 'const T' nor 'T&'). +#define GMOCK_IMPLEMENT_COMPARISON_MATCHER_( \ + name, op, relation, negated_relation) \ + template class name##Matcher { \ + public: \ + explicit name##Matcher(const Rhs& rhs) : rhs_(rhs) {} \ + template \ + operator Matcher() const { \ + return MakeMatcher(new Impl(rhs_)); \ + } \ + private: \ + template \ + class Impl : public MatcherInterface { \ + public: \ + explicit Impl(const Rhs& rhs) : rhs_(rhs) {} \ + virtual bool MatchAndExplain(\ + Lhs lhs, MatchResultListener* /* listener */) const { \ + return lhs op rhs_; \ + } \ + virtual void DescribeTo(::std::ostream* os) const { \ + *os << relation " "; \ + UniversalPrinter::Print(rhs_, os); \ + } \ + virtual void DescribeNegationTo(::std::ostream* os) const { \ + *os << negated_relation " "; \ + UniversalPrinter::Print(rhs_, os); \ + } \ + private: \ + Rhs rhs_; \ + GTEST_DISALLOW_ASSIGN_(Impl); \ + }; \ + Rhs rhs_; \ + GTEST_DISALLOW_ASSIGN_(name##Matcher); \ + } + +// Implements Eq(v), Ge(v), Gt(v), Le(v), Lt(v), and Ne(v) +// respectively. +GMOCK_IMPLEMENT_COMPARISON_MATCHER_(Eq, ==, "is equal to", "isn't equal to"); +GMOCK_IMPLEMENT_COMPARISON_MATCHER_(Ge, >=, "is >=", "isn't >="); +GMOCK_IMPLEMENT_COMPARISON_MATCHER_(Gt, >, "is >", "isn't >"); +GMOCK_IMPLEMENT_COMPARISON_MATCHER_(Le, <=, "is <=", "isn't <="); +GMOCK_IMPLEMENT_COMPARISON_MATCHER_(Lt, <, "is <", "isn't <"); +GMOCK_IMPLEMENT_COMPARISON_MATCHER_(Ne, !=, "isn't equal to", "is equal to"); + +#undef GMOCK_IMPLEMENT_COMPARISON_MATCHER_ + +// Implements the polymorphic IsNull() matcher, which matches any raw or smart +// pointer that is NULL. +class IsNullMatcher { + public: + template + bool MatchAndExplain(const Pointer& p, + MatchResultListener* /* listener */) const { + return GetRawPointer(p) == NULL; + } + + void DescribeTo(::std::ostream* os) const { *os << "is NULL"; } + void DescribeNegationTo(::std::ostream* os) const { + *os << "isn't NULL"; + } +}; + +// Implements the polymorphic NotNull() matcher, which matches any raw or smart +// pointer that is not NULL. +class NotNullMatcher { + public: + template + bool MatchAndExplain(const Pointer& p, + MatchResultListener* /* listener */) const { + return GetRawPointer(p) != NULL; + } + + void DescribeTo(::std::ostream* os) const { *os << "isn't NULL"; } + void DescribeNegationTo(::std::ostream* os) const { + *os << "is NULL"; + } +}; + +// Ref(variable) matches any argument that is a reference to +// 'variable'. This matcher is polymorphic as it can match any +// super type of the type of 'variable'. +// +// The RefMatcher template class implements Ref(variable). It can +// only be instantiated with a reference type. This prevents a user +// from mistakenly using Ref(x) to match a non-reference function +// argument. For example, the following will righteously cause a +// compiler error: +// +// int n; +// Matcher m1 = Ref(n); // This won't compile. +// Matcher m2 = Ref(n); // This will compile. +template +class RefMatcher; + +template +class RefMatcher { + // Google Mock is a generic framework and thus needs to support + // mocking any function types, including those that take non-const + // reference arguments. Therefore the template parameter T (and + // Super below) can be instantiated to either a const type or a + // non-const type. + public: + // RefMatcher() takes a T& instead of const T&, as we want the + // compiler to catch using Ref(const_value) as a matcher for a + // non-const reference. + explicit RefMatcher(T& x) : object_(x) {} // NOLINT + + template + operator Matcher() const { + // By passing object_ (type T&) to Impl(), which expects a Super&, + // we make sure that Super is a super type of T. In particular, + // this catches using Ref(const_value) as a matcher for a + // non-const reference, as you cannot implicitly convert a const + // reference to a non-const reference. + return MakeMatcher(new Impl(object_)); + } + + private: + template + class Impl : public MatcherInterface { + public: + explicit Impl(Super& x) : object_(x) {} // NOLINT + + // MatchAndExplain() takes a Super& (as opposed to const Super&) + // in order to match the interface MatcherInterface. + virtual bool MatchAndExplain( + Super& x, MatchResultListener* listener) const { + *listener << "which is located @" << static_cast(&x); + return &x == &object_; + } + + virtual void DescribeTo(::std::ostream* os) const { + *os << "references the variable "; + UniversalPrinter::Print(object_, os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "does not reference the variable "; + UniversalPrinter::Print(object_, os); + } + + private: + const Super& object_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; + + T& object_; + + GTEST_DISALLOW_ASSIGN_(RefMatcher); +}; + +// Polymorphic helper functions for narrow and wide string matchers. +inline bool CaseInsensitiveCStringEquals(const char* lhs, const char* rhs) { + return String::CaseInsensitiveCStringEquals(lhs, rhs); +} + +inline bool CaseInsensitiveCStringEquals(const wchar_t* lhs, + const wchar_t* rhs) { + return String::CaseInsensitiveWideCStringEquals(lhs, rhs); +} + +// String comparison for narrow or wide strings that can have embedded NUL +// characters. +template +bool CaseInsensitiveStringEquals(const StringType& s1, + const StringType& s2) { + // Are the heads equal? + if (!CaseInsensitiveCStringEquals(s1.c_str(), s2.c_str())) { + return false; + } + + // Skip the equal heads. + const typename StringType::value_type nul = 0; + const size_t i1 = s1.find(nul), i2 = s2.find(nul); + + // Are we at the end of either s1 or s2? + if (i1 == StringType::npos || i2 == StringType::npos) { + return i1 == i2; + } + + // Are the tails equal? + return CaseInsensitiveStringEquals(s1.substr(i1 + 1), s2.substr(i2 + 1)); +} + +// String matchers. + +// Implements equality-based string matchers like StrEq, StrCaseNe, and etc. +template +class StrEqualityMatcher { + public: + typedef typename StringType::const_pointer ConstCharPointer; + + StrEqualityMatcher(const StringType& str, bool expect_eq, + bool case_sensitive) + : string_(str), expect_eq_(expect_eq), case_sensitive_(case_sensitive) {} + + // When expect_eq_ is true, returns true iff s is equal to string_; + // otherwise returns true iff s is not equal to string_. + bool MatchAndExplain(ConstCharPointer s, + MatchResultListener* listener) const { + if (s == NULL) { + return !expect_eq_; + } + return MatchAndExplain(StringType(s), listener); + } + + bool MatchAndExplain(const StringType& s, + MatchResultListener* /* listener */) const { + const bool eq = case_sensitive_ ? s == string_ : + CaseInsensitiveStringEquals(s, string_); + return expect_eq_ == eq; + } + + void DescribeTo(::std::ostream* os) const { + DescribeToHelper(expect_eq_, os); + } + + void DescribeNegationTo(::std::ostream* os) const { + DescribeToHelper(!expect_eq_, os); + } + + private: + void DescribeToHelper(bool expect_eq, ::std::ostream* os) const { + *os << (expect_eq ? "is " : "isn't "); + *os << "equal to "; + if (!case_sensitive_) { + *os << "(ignoring case) "; + } + UniversalPrinter::Print(string_, os); + } + + const StringType string_; + const bool expect_eq_; + const bool case_sensitive_; + + GTEST_DISALLOW_ASSIGN_(StrEqualityMatcher); +}; + +// Implements the polymorphic HasSubstr(substring) matcher, which +// can be used as a Matcher as long as T can be converted to a +// string. +template +class HasSubstrMatcher { + public: + typedef typename StringType::const_pointer ConstCharPointer; + + explicit HasSubstrMatcher(const StringType& substring) + : substring_(substring) {} + + // These overloaded methods allow HasSubstr(substring) to be used as a + // Matcher as long as T can be converted to string. Returns true + // iff s contains substring_ as a substring. + bool MatchAndExplain(ConstCharPointer s, + MatchResultListener* listener) const { + return s != NULL && MatchAndExplain(StringType(s), listener); + } + + bool MatchAndExplain(const StringType& s, + MatchResultListener* /* listener */) const { + return s.find(substring_) != StringType::npos; + } + + // Describes what this matcher matches. + void DescribeTo(::std::ostream* os) const { + *os << "has substring "; + UniversalPrinter::Print(substring_, os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "has no substring "; + UniversalPrinter::Print(substring_, os); + } + + private: + const StringType substring_; + + GTEST_DISALLOW_ASSIGN_(HasSubstrMatcher); +}; + +// Implements the polymorphic StartsWith(substring) matcher, which +// can be used as a Matcher as long as T can be converted to a +// string. +template +class StartsWithMatcher { + public: + typedef typename StringType::const_pointer ConstCharPointer; + + explicit StartsWithMatcher(const StringType& prefix) : prefix_(prefix) { + } + + // These overloaded methods allow StartsWith(prefix) to be used as a + // Matcher as long as T can be converted to string. Returns true + // iff s starts with prefix_. + bool MatchAndExplain(ConstCharPointer s, + MatchResultListener* listener) const { + return s != NULL && MatchAndExplain(StringType(s), listener); + } + + bool MatchAndExplain(const StringType& s, + MatchResultListener* /* listener */) const { + return s.length() >= prefix_.length() && + s.substr(0, prefix_.length()) == prefix_; + } + + void DescribeTo(::std::ostream* os) const { + *os << "starts with "; + UniversalPrinter::Print(prefix_, os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "doesn't start with "; + UniversalPrinter::Print(prefix_, os); + } + + private: + const StringType prefix_; + + GTEST_DISALLOW_ASSIGN_(StartsWithMatcher); +}; + +// Implements the polymorphic EndsWith(substring) matcher, which +// can be used as a Matcher as long as T can be converted to a +// string. +template +class EndsWithMatcher { + public: + typedef typename StringType::const_pointer ConstCharPointer; + + explicit EndsWithMatcher(const StringType& suffix) : suffix_(suffix) {} + + // These overloaded methods allow EndsWith(suffix) to be used as a + // Matcher as long as T can be converted to string. Returns true + // iff s ends with suffix_. + bool MatchAndExplain(ConstCharPointer s, + MatchResultListener* listener) const { + return s != NULL && MatchAndExplain(StringType(s), listener); + } + + bool MatchAndExplain(const StringType& s, + MatchResultListener* /* listener */) const { + return s.length() >= suffix_.length() && + s.substr(s.length() - suffix_.length()) == suffix_; + } + + void DescribeTo(::std::ostream* os) const { + *os << "ends with "; + UniversalPrinter::Print(suffix_, os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "doesn't end with "; + UniversalPrinter::Print(suffix_, os); + } + + private: + const StringType suffix_; + + GTEST_DISALLOW_ASSIGN_(EndsWithMatcher); +}; + +// Implements polymorphic matchers MatchesRegex(regex) and +// ContainsRegex(regex), which can be used as a Matcher as long as +// T can be converted to a string. +class MatchesRegexMatcher { + public: + MatchesRegexMatcher(const RE* regex, bool full_match) + : regex_(regex), full_match_(full_match) {} + + // These overloaded methods allow MatchesRegex(regex) to be used as + // a Matcher as long as T can be converted to string. Returns + // true iff s matches regular expression regex. When full_match_ is + // true, a full match is done; otherwise a partial match is done. + bool MatchAndExplain(const char* s, + MatchResultListener* listener) const { + return s != NULL && MatchAndExplain(internal::string(s), listener); + } + + bool MatchAndExplain(const internal::string& s, + MatchResultListener* /* listener */) const { + return full_match_ ? RE::FullMatch(s, *regex_) : + RE::PartialMatch(s, *regex_); + } + + void DescribeTo(::std::ostream* os) const { + *os << (full_match_ ? "matches" : "contains") + << " regular expression "; + UniversalPrinter::Print(regex_->pattern(), os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "doesn't " << (full_match_ ? "match" : "contain") + << " regular expression "; + UniversalPrinter::Print(regex_->pattern(), os); + } + + private: + const internal::linked_ptr regex_; + const bool full_match_; + + GTEST_DISALLOW_ASSIGN_(MatchesRegexMatcher); +}; + +// Implements a matcher that compares the two fields of a 2-tuple +// using one of the ==, <=, <, etc, operators. The two fields being +// compared don't have to have the same type. +// +// The matcher defined here is polymorphic (for example, Eq() can be +// used to match a tuple, a tuple, +// etc). Therefore we use a template type conversion operator in the +// implementation. +// +// We define this as a macro in order to eliminate duplicated source +// code. +#define GMOCK_IMPLEMENT_COMPARISON2_MATCHER_(name, op) \ + class name##2Matcher { \ + public: \ + template \ + operator Matcher&>() const { \ + return MakeMatcher(new Impl); \ + } \ + private: \ + template \ + class Impl : public MatcherInterface&> { \ + public: \ + virtual bool MatchAndExplain( \ + const ::std::tr1::tuple& args, \ + MatchResultListener* /* listener */) const { \ + return ::std::tr1::get<0>(args) op ::std::tr1::get<1>(args); \ + } \ + virtual void DescribeTo(::std::ostream* os) const { \ + *os << "are a pair (x, y) where x " #op " y"; \ + } \ + virtual void DescribeNegationTo(::std::ostream* os) const { \ + *os << "are a pair (x, y) where x " #op " y is false"; \ + } \ + }; \ + } + +// Implements Eq(), Ge(), Gt(), Le(), Lt(), and Ne() respectively. +GMOCK_IMPLEMENT_COMPARISON2_MATCHER_(Eq, ==); +GMOCK_IMPLEMENT_COMPARISON2_MATCHER_(Ge, >=); +GMOCK_IMPLEMENT_COMPARISON2_MATCHER_(Gt, >); +GMOCK_IMPLEMENT_COMPARISON2_MATCHER_(Le, <=); +GMOCK_IMPLEMENT_COMPARISON2_MATCHER_(Lt, <); +GMOCK_IMPLEMENT_COMPARISON2_MATCHER_(Ne, !=); + +#undef GMOCK_IMPLEMENT_COMPARISON2_MATCHER_ + +// Implements the Not(...) matcher for a particular argument type T. +// We do not nest it inside the NotMatcher class template, as that +// will prevent different instantiations of NotMatcher from sharing +// the same NotMatcherImpl class. +template +class NotMatcherImpl : public MatcherInterface { + public: + explicit NotMatcherImpl(const Matcher& matcher) + : matcher_(matcher) {} + + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const { + return !matcher_.MatchAndExplain(x, listener); + } + + virtual void DescribeTo(::std::ostream* os) const { + matcher_.DescribeNegationTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + matcher_.DescribeTo(os); + } + + private: + const Matcher matcher_; + + GTEST_DISALLOW_ASSIGN_(NotMatcherImpl); +}; + +// Implements the Not(m) matcher, which matches a value that doesn't +// match matcher m. +template +class NotMatcher { + public: + explicit NotMatcher(InnerMatcher matcher) : matcher_(matcher) {} + + // This template type conversion operator allows Not(m) to be used + // to match any type m can match. + template + operator Matcher() const { + return Matcher(new NotMatcherImpl(SafeMatcherCast(matcher_))); + } + + private: + InnerMatcher matcher_; + + GTEST_DISALLOW_ASSIGN_(NotMatcher); +}; + +// Implements the AllOf(m1, m2) matcher for a particular argument type +// T. We do not nest it inside the BothOfMatcher class template, as +// that will prevent different instantiations of BothOfMatcher from +// sharing the same BothOfMatcherImpl class. +template +class BothOfMatcherImpl : public MatcherInterface { + public: + BothOfMatcherImpl(const Matcher& matcher1, const Matcher& matcher2) + : matcher1_(matcher1), matcher2_(matcher2) {} + + virtual void DescribeTo(::std::ostream* os) const { + *os << "("; + matcher1_.DescribeTo(os); + *os << ") and ("; + matcher2_.DescribeTo(os); + *os << ")"; + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "("; + matcher1_.DescribeNegationTo(os); + *os << ") or ("; + matcher2_.DescribeNegationTo(os); + *os << ")"; + } + + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const { + // If either matcher1_ or matcher2_ doesn't match x, we only need + // to explain why one of them fails. + StringMatchResultListener listener1; + if (!matcher1_.MatchAndExplain(x, &listener1)) { + *listener << listener1.str(); + return false; + } + + StringMatchResultListener listener2; + if (!matcher2_.MatchAndExplain(x, &listener2)) { + *listener << listener2.str(); + return false; + } + + // Otherwise we need to explain why *both* of them match. + const internal::string s1 = listener1.str(); + const internal::string s2 = listener2.str(); + + if (s1 == "") { + *listener << s2; + } else { + *listener << s1; + if (s2 != "") { + *listener << ", and " << s2; + } + } + return true; + } + + private: + const Matcher matcher1_; + const Matcher matcher2_; + + GTEST_DISALLOW_ASSIGN_(BothOfMatcherImpl); +}; + +// Used for implementing the AllOf(m_1, ..., m_n) matcher, which +// matches a value that matches all of the matchers m_1, ..., and m_n. +template +class BothOfMatcher { + public: + BothOfMatcher(Matcher1 matcher1, Matcher2 matcher2) + : matcher1_(matcher1), matcher2_(matcher2) {} + + // This template type conversion operator allows a + // BothOfMatcher object to match any type that + // both Matcher1 and Matcher2 can match. + template + operator Matcher() const { + return Matcher(new BothOfMatcherImpl(SafeMatcherCast(matcher1_), + SafeMatcherCast(matcher2_))); + } + + private: + Matcher1 matcher1_; + Matcher2 matcher2_; + + GTEST_DISALLOW_ASSIGN_(BothOfMatcher); +}; + +// Implements the AnyOf(m1, m2) matcher for a particular argument type +// T. We do not nest it inside the AnyOfMatcher class template, as +// that will prevent different instantiations of AnyOfMatcher from +// sharing the same EitherOfMatcherImpl class. +template +class EitherOfMatcherImpl : public MatcherInterface { + public: + EitherOfMatcherImpl(const Matcher& matcher1, const Matcher& matcher2) + : matcher1_(matcher1), matcher2_(matcher2) {} + + virtual void DescribeTo(::std::ostream* os) const { + *os << "("; + matcher1_.DescribeTo(os); + *os << ") or ("; + matcher2_.DescribeTo(os); + *os << ")"; + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "("; + matcher1_.DescribeNegationTo(os); + *os << ") and ("; + matcher2_.DescribeNegationTo(os); + *os << ")"; + } + + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const { + // If either matcher1_ or matcher2_ matches x, we just need to + // explain why *one* of them matches. + StringMatchResultListener listener1; + if (matcher1_.MatchAndExplain(x, &listener1)) { + *listener << listener1.str(); + return true; + } + + StringMatchResultListener listener2; + if (matcher2_.MatchAndExplain(x, &listener2)) { + *listener << listener2.str(); + return true; + } + + // Otherwise we need to explain why *both* of them fail. + const internal::string s1 = listener1.str(); + const internal::string s2 = listener2.str(); + + if (s1 == "") { + *listener << s2; + } else { + *listener << s1; + if (s2 != "") { + *listener << ", and " << s2; + } + } + return false; + } + + private: + const Matcher matcher1_; + const Matcher matcher2_; + + GTEST_DISALLOW_ASSIGN_(EitherOfMatcherImpl); +}; + +// Used for implementing the AnyOf(m_1, ..., m_n) matcher, which +// matches a value that matches at least one of the matchers m_1, ..., +// and m_n. +template +class EitherOfMatcher { + public: + EitherOfMatcher(Matcher1 matcher1, Matcher2 matcher2) + : matcher1_(matcher1), matcher2_(matcher2) {} + + // This template type conversion operator allows a + // EitherOfMatcher object to match any type that + // both Matcher1 and Matcher2 can match. + template + operator Matcher() const { + return Matcher(new EitherOfMatcherImpl( + SafeMatcherCast(matcher1_), SafeMatcherCast(matcher2_))); + } + + private: + Matcher1 matcher1_; + Matcher2 matcher2_; + + GTEST_DISALLOW_ASSIGN_(EitherOfMatcher); +}; + +// Used for implementing Truly(pred), which turns a predicate into a +// matcher. +template +class TrulyMatcher { + public: + explicit TrulyMatcher(Predicate pred) : predicate_(pred) {} + + // This method template allows Truly(pred) to be used as a matcher + // for type T where T is the argument type of predicate 'pred'. The + // argument is passed by reference as the predicate may be + // interested in the address of the argument. + template + bool MatchAndExplain(T& x, // NOLINT + MatchResultListener* /* listener */) const { +#if GTEST_OS_WINDOWS + // MSVC warns about converting a value into bool (warning 4800). +#pragma warning(push) // Saves the current warning state. +#pragma warning(disable:4800) // Temporarily disables warning 4800. +#endif // GTEST_OS_WINDOWS + return predicate_(x); +#if GTEST_OS_WINDOWS +#pragma warning(pop) // Restores the warning state. +#endif // GTEST_OS_WINDOWS + } + + void DescribeTo(::std::ostream* os) const { + *os << "satisfies the given predicate"; + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "doesn't satisfy the given predicate"; + } + + private: + Predicate predicate_; + + GTEST_DISALLOW_ASSIGN_(TrulyMatcher); +}; + +// Used for implementing Matches(matcher), which turns a matcher into +// a predicate. +template +class MatcherAsPredicate { + public: + explicit MatcherAsPredicate(M matcher) : matcher_(matcher) {} + + // This template operator() allows Matches(m) to be used as a + // predicate on type T where m is a matcher on type T. + // + // The argument x is passed by reference instead of by value, as + // some matcher may be interested in its address (e.g. as in + // Matches(Ref(n))(x)). + template + bool operator()(const T& x) const { + // We let matcher_ commit to a particular type here instead of + // when the MatcherAsPredicate object was constructed. This + // allows us to write Matches(m) where m is a polymorphic matcher + // (e.g. Eq(5)). + // + // If we write Matcher(matcher_).Matches(x) here, it won't + // compile when matcher_ has type Matcher; if we write + // Matcher(matcher_).Matches(x) here, it won't compile + // when matcher_ has type Matcher; if we just write + // matcher_.Matches(x), it won't compile when matcher_ is + // polymorphic, e.g. Eq(5). + // + // MatcherCast() is necessary for making the code work + // in all of the above situations. + return MatcherCast(matcher_).Matches(x); + } + + private: + M matcher_; + + GTEST_DISALLOW_ASSIGN_(MatcherAsPredicate); +}; + +// For implementing ASSERT_THAT() and EXPECT_THAT(). The template +// argument M must be a type that can be converted to a matcher. +template +class PredicateFormatterFromMatcher { + public: + explicit PredicateFormatterFromMatcher(const M& m) : matcher_(m) {} + + // This template () operator allows a PredicateFormatterFromMatcher + // object to act as a predicate-formatter suitable for using with + // Google Test's EXPECT_PRED_FORMAT1() macro. + template + AssertionResult operator()(const char* value_text, const T& x) const { + // We convert matcher_ to a Matcher *now* instead of + // when the PredicateFormatterFromMatcher object was constructed, + // as matcher_ may be polymorphic (e.g. NotNull()) and we won't + // know which type to instantiate it to until we actually see the + // type of x here. + // + // We write MatcherCast(matcher_) instead of + // Matcher(matcher_), as the latter won't compile when + // matcher_ has type Matcher (e.g. An()). + const Matcher matcher = MatcherCast(matcher_); + StringMatchResultListener listener; + if (MatchPrintAndExplain(x, matcher, &listener)) + return AssertionSuccess(); + + ::std::stringstream ss; + ss << "Value of: " << value_text << "\n" + << "Expected: "; + matcher.DescribeTo(&ss); + ss << "\n Actual: " << listener.str(); + return AssertionFailure() << ss.str(); + } + + private: + const M matcher_; + + GTEST_DISALLOW_ASSIGN_(PredicateFormatterFromMatcher); +}; + +// A helper function for converting a matcher to a predicate-formatter +// without the user needing to explicitly write the type. This is +// used for implementing ASSERT_THAT() and EXPECT_THAT(). +template +inline PredicateFormatterFromMatcher +MakePredicateFormatterFromMatcher(const M& matcher) { + return PredicateFormatterFromMatcher(matcher); +} + +// Implements the polymorphic floating point equality matcher, which +// matches two float values using ULP-based approximation. The +// template is meant to be instantiated with FloatType being either +// float or double. +template +class FloatingEqMatcher { + public: + // Constructor for FloatingEqMatcher. + // The matcher's input will be compared with rhs. The matcher treats two + // NANs as equal if nan_eq_nan is true. Otherwise, under IEEE standards, + // equality comparisons between NANs will always return false. + FloatingEqMatcher(FloatType rhs, bool nan_eq_nan) : + rhs_(rhs), nan_eq_nan_(nan_eq_nan) {} + + // Implements floating point equality matcher as a Matcher. + template + class Impl : public MatcherInterface { + public: + Impl(FloatType rhs, bool nan_eq_nan) : + rhs_(rhs), nan_eq_nan_(nan_eq_nan) {} + + virtual bool MatchAndExplain(T value, + MatchResultListener* /* listener */) const { + const FloatingPoint lhs(value), rhs(rhs_); + + // Compares NaNs first, if nan_eq_nan_ is true. + if (nan_eq_nan_ && lhs.is_nan()) { + return rhs.is_nan(); + } + + return lhs.AlmostEquals(rhs); + } + + virtual void DescribeTo(::std::ostream* os) const { + // os->precision() returns the previously set precision, which we + // store to restore the ostream to its original configuration + // after outputting. + const ::std::streamsize old_precision = os->precision( + ::std::numeric_limits::digits10 + 2); + if (FloatingPoint(rhs_).is_nan()) { + if (nan_eq_nan_) { + *os << "is NaN"; + } else { + *os << "never matches"; + } + } else { + *os << "is approximately " << rhs_; + } + os->precision(old_precision); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + // As before, get original precision. + const ::std::streamsize old_precision = os->precision( + ::std::numeric_limits::digits10 + 2); + if (FloatingPoint(rhs_).is_nan()) { + if (nan_eq_nan_) { + *os << "isn't NaN"; + } else { + *os << "is anything"; + } + } else { + *os << "isn't approximately " << rhs_; + } + // Restore original precision. + os->precision(old_precision); + } + + private: + const FloatType rhs_; + const bool nan_eq_nan_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; + + // The following 3 type conversion operators allow FloatEq(rhs) and + // NanSensitiveFloatEq(rhs) to be used as a Matcher, a + // Matcher, or a Matcher, but nothing else. + // (While Google's C++ coding style doesn't allow arguments passed + // by non-const reference, we may see them in code not conforming to + // the style. Therefore Google Mock needs to support them.) + operator Matcher() const { + return MakeMatcher(new Impl(rhs_, nan_eq_nan_)); + } + + operator Matcher() const { + return MakeMatcher(new Impl(rhs_, nan_eq_nan_)); + } + + operator Matcher() const { + return MakeMatcher(new Impl(rhs_, nan_eq_nan_)); + } + private: + const FloatType rhs_; + const bool nan_eq_nan_; + + GTEST_DISALLOW_ASSIGN_(FloatingEqMatcher); +}; + +// Implements the Pointee(m) matcher for matching a pointer whose +// pointee matches matcher m. The pointer can be either raw or smart. +template +class PointeeMatcher { + public: + explicit PointeeMatcher(const InnerMatcher& matcher) : matcher_(matcher) {} + + // This type conversion operator template allows Pointee(m) to be + // used as a matcher for any pointer type whose pointee type is + // compatible with the inner matcher, where type Pointer can be + // either a raw pointer or a smart pointer. + // + // The reason we do this instead of relying on + // MakePolymorphicMatcher() is that the latter is not flexible + // enough for implementing the DescribeTo() method of Pointee(). + template + operator Matcher() const { + return MakeMatcher(new Impl(matcher_)); + } + + private: + // The monomorphic implementation that works for a particular pointer type. + template + class Impl : public MatcherInterface { + public: + typedef typename PointeeOf::type Pointee; + + explicit Impl(const InnerMatcher& matcher) + : matcher_(MatcherCast(matcher)) {} + + virtual void DescribeTo(::std::ostream* os) const { + *os << "points to a value that "; + matcher_.DescribeTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "does not point to a value that "; + matcher_.DescribeTo(os); + } + + virtual bool MatchAndExplain(Pointer pointer, + MatchResultListener* listener) const { + if (GetRawPointer(pointer) == NULL) + return false; + + *listener << "which points to "; + return MatchPrintAndExplain(*pointer, matcher_, listener); + } + + private: + const Matcher matcher_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; + + const InnerMatcher matcher_; + + GTEST_DISALLOW_ASSIGN_(PointeeMatcher); +}; + +// Implements the Field() matcher for matching a field (i.e. member +// variable) of an object. +template +class FieldMatcher { + public: + FieldMatcher(FieldType Class::*field, + const Matcher& matcher) + : field_(field), matcher_(matcher) {} + + void DescribeTo(::std::ostream* os) const { + *os << "is an object whose given field "; + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "is an object whose given field "; + matcher_.DescribeNegationTo(os); + } + + template + bool MatchAndExplain(const T& value, MatchResultListener* listener) const { + return MatchAndExplainImpl( + typename ::testing::internal:: + is_pointer::type(), + value, listener); + } + + private: + // The first argument of MatchAndExplainImpl() is needed to help + // Symbian's C++ compiler choose which overload to use. Its type is + // true_type iff the Field() matcher is used to match a pointer. + bool MatchAndExplainImpl(false_type /* is_not_pointer */, const Class& obj, + MatchResultListener* listener) const { + *listener << "whose given field is "; + return MatchPrintAndExplain(obj.*field_, matcher_, listener); + } + + bool MatchAndExplainImpl(true_type /* is_pointer */, const Class* p, + MatchResultListener* listener) const { + if (p == NULL) + return false; + + *listener << "which points to an object "; + // Since *p has a field, it must be a class/struct/union type and + // thus cannot be a pointer. Therefore we pass false_type() as + // the first argument. + return MatchAndExplainImpl(false_type(), *p, listener); + } + + const FieldType Class::*field_; + const Matcher matcher_; + + GTEST_DISALLOW_ASSIGN_(FieldMatcher); +}; + +// Implements the Property() matcher for matching a property +// (i.e. return value of a getter method) of an object. +template +class PropertyMatcher { + public: + // The property may have a reference type, so 'const PropertyType&' + // may cause double references and fail to compile. That's why we + // need GMOCK_REFERENCE_TO_CONST, which works regardless of + // PropertyType being a reference or not. + typedef GMOCK_REFERENCE_TO_CONST_(PropertyType) RefToConstProperty; + + PropertyMatcher(PropertyType (Class::*property)() const, + const Matcher& matcher) + : property_(property), matcher_(matcher) {} + + void DescribeTo(::std::ostream* os) const { + *os << "is an object whose given property "; + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "is an object whose given property "; + matcher_.DescribeNegationTo(os); + } + + template + bool MatchAndExplain(const T&value, MatchResultListener* listener) const { + return MatchAndExplainImpl( + typename ::testing::internal:: + is_pointer::type(), + value, listener); + } + + private: + // The first argument of MatchAndExplainImpl() is needed to help + // Symbian's C++ compiler choose which overload to use. Its type is + // true_type iff the Property() matcher is used to match a pointer. + bool MatchAndExplainImpl(false_type /* is_not_pointer */, const Class& obj, + MatchResultListener* listener) const { + *listener << "whose given property is "; + // Cannot pass the return value (for example, int) to MatchPrintAndExplain, + // which takes a non-const reference as argument. + RefToConstProperty result = (obj.*property_)(); + return MatchPrintAndExplain(result, matcher_, listener); + } + + bool MatchAndExplainImpl(true_type /* is_pointer */, const Class* p, + MatchResultListener* listener) const { + if (p == NULL) + return false; + + *listener << "which points to an object "; + // Since *p has a property method, it must be a class/struct/union + // type and thus cannot be a pointer. Therefore we pass + // false_type() as the first argument. + return MatchAndExplainImpl(false_type(), *p, listener); + } + + PropertyType (Class::*property_)() const; + const Matcher matcher_; + + GTEST_DISALLOW_ASSIGN_(PropertyMatcher); +}; + +// Type traits specifying various features of different functors for ResultOf. +// The default template specifies features for functor objects. +// Functor classes have to typedef argument_type and result_type +// to be compatible with ResultOf. +template +struct CallableTraits { + typedef typename Functor::result_type ResultType; + typedef Functor StorageType; + + static void CheckIsValid(Functor /* functor */) {} + template + static ResultType Invoke(Functor f, T arg) { return f(arg); } +}; + +// Specialization for function pointers. +template +struct CallableTraits { + typedef ResType ResultType; + typedef ResType(*StorageType)(ArgType); + + static void CheckIsValid(ResType(*f)(ArgType)) { + GTEST_CHECK_(f != NULL) + << "NULL function pointer is passed into ResultOf()."; + } + template + static ResType Invoke(ResType(*f)(ArgType), T arg) { + return (*f)(arg); + } +}; + +// Implements the ResultOf() matcher for matching a return value of a +// unary function of an object. +template +class ResultOfMatcher { + public: + typedef typename CallableTraits::ResultType ResultType; + + ResultOfMatcher(Callable callable, const Matcher& matcher) + : callable_(callable), matcher_(matcher) { + CallableTraits::CheckIsValid(callable_); + } + + template + operator Matcher() const { + return Matcher(new Impl(callable_, matcher_)); + } + + private: + typedef typename CallableTraits::StorageType CallableStorageType; + + template + class Impl : public MatcherInterface { + public: + Impl(CallableStorageType callable, const Matcher& matcher) + : callable_(callable), matcher_(matcher) {} + + virtual void DescribeTo(::std::ostream* os) const { + *os << "is mapped by the given callable to a value that "; + matcher_.DescribeTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "is mapped by the given callable to a value that "; + matcher_.DescribeNegationTo(os); + } + + virtual bool MatchAndExplain(T obj, MatchResultListener* listener) const { + *listener << "which is mapped by the given callable to "; + // Cannot pass the return value (for example, int) to + // MatchPrintAndExplain, which takes a non-const reference as argument. + ResultType result = + CallableTraits::template Invoke(callable_, obj); + return MatchPrintAndExplain(result, matcher_, listener); + } + + private: + // Functors often define operator() as non-const method even though + // they are actualy stateless. But we need to use them even when + // 'this' is a const pointer. It's the user's responsibility not to + // use stateful callables with ResultOf(), which does't guarantee + // how many times the callable will be invoked. + mutable CallableStorageType callable_; + const Matcher matcher_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; // class Impl + + const CallableStorageType callable_; + const Matcher matcher_; + + GTEST_DISALLOW_ASSIGN_(ResultOfMatcher); +}; + +// Implements an equality matcher for any STL-style container whose elements +// support ==. This matcher is like Eq(), but its failure explanations provide +// more detailed information that is useful when the container is used as a set. +// The failure message reports elements that are in one of the operands but not +// the other. The failure messages do not report duplicate or out-of-order +// elements in the containers (which don't properly matter to sets, but can +// occur if the containers are vectors or lists, for example). +// +// Uses the container's const_iterator, value_type, operator ==, +// begin(), and end(). +template +class ContainerEqMatcher { + public: + typedef internal::StlContainerView View; + typedef typename View::type StlContainer; + typedef typename View::const_reference StlContainerReference; + + // We make a copy of rhs in case the elements in it are modified + // after this matcher is created. + explicit ContainerEqMatcher(const Container& rhs) : rhs_(View::Copy(rhs)) { + // Makes sure the user doesn't instantiate this class template + // with a const or reference type. + testing::StaticAssertTypeEq(); + } + + void DescribeTo(::std::ostream* os) const { + *os << "equals "; + UniversalPrinter::Print(rhs_, os); + } + void DescribeNegationTo(::std::ostream* os) const { + *os << "does not equal "; + UniversalPrinter::Print(rhs_, os); + } + + template + bool MatchAndExplain(const LhsContainer& lhs, + MatchResultListener* listener) const { + // GMOCK_REMOVE_CONST_() is needed to work around an MSVC 8.0 bug + // that causes LhsContainer to be a const type sometimes. + typedef internal::StlContainerView + LhsView; + typedef typename LhsView::type LhsStlContainer; + StlContainerReference lhs_stl_container = LhsView::ConstReference(lhs); + if (lhs_stl_container == rhs_) + return true; + + ::std::ostream* const os = listener->stream(); + if (os != NULL) { + // Something is different. Check for extra values first. + bool printed_header = false; + for (typename LhsStlContainer::const_iterator it = + lhs_stl_container.begin(); + it != lhs_stl_container.end(); ++it) { + if (internal::ArrayAwareFind(rhs_.begin(), rhs_.end(), *it) == + rhs_.end()) { + if (printed_header) { + *os << ", "; + } else { + *os << "which has these unexpected elements: "; + printed_header = true; + } + UniversalPrinter:: + Print(*it, os); + } + } + + // Now check for missing values. + bool printed_header2 = false; + for (typename StlContainer::const_iterator it = rhs_.begin(); + it != rhs_.end(); ++it) { + if (internal::ArrayAwareFind( + lhs_stl_container.begin(), lhs_stl_container.end(), *it) == + lhs_stl_container.end()) { + if (printed_header2) { + *os << ", "; + } else { + *os << (printed_header ? ",\nand" : "which") + << " doesn't have these expected elements: "; + printed_header2 = true; + } + UniversalPrinter::Print(*it, os); + } + } + } + + return false; + } + + private: + const StlContainer rhs_; + + GTEST_DISALLOW_ASSIGN_(ContainerEqMatcher); +}; + +// Implements Contains(element_matcher) for the given argument type Container. +template +class ContainsMatcherImpl : public MatcherInterface { + public: + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(Container)) RawContainer; + typedef StlContainerView View; + typedef typename View::type StlContainer; + typedef typename View::const_reference StlContainerReference; + typedef typename StlContainer::value_type Element; + + template + explicit ContainsMatcherImpl(InnerMatcher inner_matcher) + : inner_matcher_( + testing::SafeMatcherCast(inner_matcher)) {} + + // Describes what this matcher does. + virtual void DescribeTo(::std::ostream* os) const { + *os << "contains at least one element that "; + inner_matcher_.DescribeTo(os); + } + + // Describes what the negation of this matcher does. + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "doesn't contain any element that "; + inner_matcher_.DescribeTo(os); + } + + virtual bool MatchAndExplain(Container container, + MatchResultListener* listener) const { + StlContainerReference stl_container = View::ConstReference(container); + size_t i = 0; + for (typename StlContainer::const_iterator it = stl_container.begin(); + it != stl_container.end(); ++it, ++i) { + StringMatchResultListener inner_listener; + if (inner_matcher_.MatchAndExplain(*it, &inner_listener)) { + *listener << "whose element #" << i << " matches"; + PrintIfNotEmpty(inner_listener.str(), listener->stream()); + return true; + } + } + return false; + } + + private: + const Matcher inner_matcher_; + + GTEST_DISALLOW_ASSIGN_(ContainsMatcherImpl); +}; + +// Implements polymorphic Contains(element_matcher). +template +class ContainsMatcher { + public: + explicit ContainsMatcher(M m) : inner_matcher_(m) {} + + template + operator Matcher() const { + return MakeMatcher(new ContainsMatcherImpl(inner_matcher_)); + } + + private: + const M inner_matcher_; + + GTEST_DISALLOW_ASSIGN_(ContainsMatcher); +}; + +// Implements Key(inner_matcher) for the given argument pair type. +// Key(inner_matcher) matches an std::pair whose 'first' field matches +// inner_matcher. For example, Contains(Key(Ge(5))) can be used to match an +// std::map that contains at least one element whose key is >= 5. +template +class KeyMatcherImpl : public MatcherInterface { + public: + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(PairType)) RawPairType; + typedef typename RawPairType::first_type KeyType; + + template + explicit KeyMatcherImpl(InnerMatcher inner_matcher) + : inner_matcher_( + testing::SafeMatcherCast(inner_matcher)) { + } + + // Returns true iff 'key_value.first' (the key) matches the inner matcher. + virtual bool MatchAndExplain(PairType key_value, + MatchResultListener* listener) const { + StringMatchResultListener inner_listener; + const bool match = inner_matcher_.MatchAndExplain(key_value.first, + &inner_listener); + const internal::string explanation = inner_listener.str(); + if (explanation != "") { + *listener << "whose first field is a value " << explanation; + } + return match; + } + + // Describes what this matcher does. + virtual void DescribeTo(::std::ostream* os) const { + *os << "has a key that "; + inner_matcher_.DescribeTo(os); + } + + // Describes what the negation of this matcher does. + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "doesn't have a key that "; + inner_matcher_.DescribeTo(os); + } + + private: + const Matcher inner_matcher_; + + GTEST_DISALLOW_ASSIGN_(KeyMatcherImpl); +}; + +// Implements polymorphic Key(matcher_for_key). +template +class KeyMatcher { + public: + explicit KeyMatcher(M m) : matcher_for_key_(m) {} + + template + operator Matcher() const { + return MakeMatcher(new KeyMatcherImpl(matcher_for_key_)); + } + + private: + const M matcher_for_key_; + + GTEST_DISALLOW_ASSIGN_(KeyMatcher); +}; + +// Implements Pair(first_matcher, second_matcher) for the given argument pair +// type with its two matchers. See Pair() function below. +template +class PairMatcherImpl : public MatcherInterface { + public: + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(PairType)) RawPairType; + typedef typename RawPairType::first_type FirstType; + typedef typename RawPairType::second_type SecondType; + + template + PairMatcherImpl(FirstMatcher first_matcher, SecondMatcher second_matcher) + : first_matcher_( + testing::SafeMatcherCast(first_matcher)), + second_matcher_( + testing::SafeMatcherCast(second_matcher)) { + } + + // Describes what this matcher does. + virtual void DescribeTo(::std::ostream* os) const { + *os << "has a first field that "; + first_matcher_.DescribeTo(os); + *os << ", and has a second field that "; + second_matcher_.DescribeTo(os); + } + + // Describes what the negation of this matcher does. + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "has a first field that "; + first_matcher_.DescribeNegationTo(os); + *os << ", or has a second field that "; + second_matcher_.DescribeNegationTo(os); + } + + // Returns true iff 'a_pair.first' matches first_matcher and 'a_pair.second' + // matches second_matcher. + virtual bool MatchAndExplain(PairType a_pair, + MatchResultListener* listener) const { + if (!listener->IsInterested()) { + // If the listener is not interested, we don't need to construct the + // explanation. + return first_matcher_.Matches(a_pair.first) && + second_matcher_.Matches(a_pair.second); + } + StringMatchResultListener first_inner_listener; + if (!first_matcher_.MatchAndExplain(a_pair.first, + &first_inner_listener)) { + *listener << "whose first field does not match"; + PrintIfNotEmpty(first_inner_listener.str(), listener->stream()); + return false; + } + StringMatchResultListener second_inner_listener; + if (!second_matcher_.MatchAndExplain(a_pair.second, + &second_inner_listener)) { + *listener << "whose second field does not match"; + PrintIfNotEmpty(second_inner_listener.str(), listener->stream()); + return false; + } + ExplainSuccess(first_inner_listener.str(), second_inner_listener.str(), + listener); + return true; + } + + private: + void ExplainSuccess(const internal::string& first_explanation, + const internal::string& second_explanation, + MatchResultListener* listener) const { + *listener << "whose both fields match"; + if (first_explanation != "") { + *listener << ", where the first field is a value " << first_explanation; + } + if (second_explanation != "") { + *listener << ", "; + if (first_explanation != "") { + *listener << "and "; + } else { + *listener << "where "; + } + *listener << "the second field is a value " << second_explanation; + } + } + + const Matcher first_matcher_; + const Matcher second_matcher_; + + GTEST_DISALLOW_ASSIGN_(PairMatcherImpl); +}; + +// Implements polymorphic Pair(first_matcher, second_matcher). +template +class PairMatcher { + public: + PairMatcher(FirstMatcher first_matcher, SecondMatcher second_matcher) + : first_matcher_(first_matcher), second_matcher_(second_matcher) {} + + template + operator Matcher () const { + return MakeMatcher( + new PairMatcherImpl( + first_matcher_, second_matcher_)); + } + + private: + const FirstMatcher first_matcher_; + const SecondMatcher second_matcher_; + + GTEST_DISALLOW_ASSIGN_(PairMatcher); +}; + +// Implements ElementsAre() and ElementsAreArray(). +template +class ElementsAreMatcherImpl : public MatcherInterface { + public: + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(Container)) RawContainer; + typedef internal::StlContainerView View; + typedef typename View::type StlContainer; + typedef typename View::const_reference StlContainerReference; + typedef typename StlContainer::value_type Element; + + // Constructs the matcher from a sequence of element values or + // element matchers. + template + ElementsAreMatcherImpl(InputIter first, size_t a_count) { + matchers_.reserve(a_count); + InputIter it = first; + for (size_t i = 0; i != a_count; ++i, ++it) { + matchers_.push_back(MatcherCast(*it)); + } + } + + // Describes what this matcher does. + virtual void DescribeTo(::std::ostream* os) const { + if (count() == 0) { + *os << "is empty"; + } else if (count() == 1) { + *os << "has 1 element that "; + matchers_[0].DescribeTo(os); + } else { + *os << "has " << Elements(count()) << " where\n"; + for (size_t i = 0; i != count(); ++i) { + *os << "element #" << i << " "; + matchers_[i].DescribeTo(os); + if (i + 1 < count()) { + *os << ",\n"; + } + } + } + } + + // Describes what the negation of this matcher does. + virtual void DescribeNegationTo(::std::ostream* os) const { + if (count() == 0) { + *os << "isn't empty"; + return; + } + + *os << "doesn't have " << Elements(count()) << ", or\n"; + for (size_t i = 0; i != count(); ++i) { + *os << "element #" << i << " "; + matchers_[i].DescribeNegationTo(os); + if (i + 1 < count()) { + *os << ", or\n"; + } + } + } + + virtual bool MatchAndExplain(Container container, + MatchResultListener* listener) const { + StlContainerReference stl_container = View::ConstReference(container); + const size_t actual_count = stl_container.size(); + if (actual_count != count()) { + // The element count doesn't match. If the container is empty, + // there's no need to explain anything as Google Mock already + // prints the empty container. Otherwise we just need to show + // how many elements there actually are. + if (actual_count != 0) { + *listener << "which has " << Elements(actual_count); + } + return false; + } + + typename StlContainer::const_iterator it = stl_container.begin(); + // explanations[i] is the explanation of the element at index i. + std::vector explanations(count()); + for (size_t i = 0; i != count(); ++it, ++i) { + StringMatchResultListener s; + if (matchers_[i].MatchAndExplain(*it, &s)) { + explanations[i] = s.str(); + } else { + // The container has the right size but the i-th element + // doesn't match its expectation. + *listener << "whose element #" << i << " doesn't match"; + PrintIfNotEmpty(s.str(), listener->stream()); + return false; + } + } + + // Every element matches its expectation. We need to explain why + // (the obvious ones can be skipped). + bool reason_printed = false; + for (size_t i = 0; i != count(); ++i) { + const internal::string& s = explanations[i]; + if (!s.empty()) { + if (reason_printed) { + *listener << ",\nand "; + } + *listener << "whose element #" << i << " matches, " << s; + reason_printed = true; + } + } + + return true; + } + + private: + static Message Elements(size_t count) { + return Message() << count << (count == 1 ? " element" : " elements"); + } + + size_t count() const { return matchers_.size(); } + std::vector > matchers_; + + GTEST_DISALLOW_ASSIGN_(ElementsAreMatcherImpl); +}; + +// Implements ElementsAre() of 0 arguments. +class ElementsAreMatcher0 { + public: + ElementsAreMatcher0() {} + + template + operator Matcher() const { + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(Container)) + RawContainer; + typedef typename internal::StlContainerView::type::value_type + Element; + + const Matcher* const matchers = NULL; + return MakeMatcher(new ElementsAreMatcherImpl(matchers, 0)); + } +}; + +// Implements ElementsAreArray(). +template +class ElementsAreArrayMatcher { + public: + ElementsAreArrayMatcher(const T* first, size_t count) : + first_(first), count_(count) {} + + template + operator Matcher() const { + typedef GMOCK_REMOVE_CONST_(GMOCK_REMOVE_REFERENCE_(Container)) + RawContainer; + typedef typename internal::StlContainerView::type::value_type + Element; + + return MakeMatcher(new ElementsAreMatcherImpl(first_, count_)); + } + + private: + const T* const first_; + const size_t count_; + + GTEST_DISALLOW_ASSIGN_(ElementsAreArrayMatcher); +}; + +// Constants denoting interpolations in a matcher description string. +const int kTupleInterpolation = -1; // "%(*)s" +const int kPercentInterpolation = -2; // "%%" +const int kInvalidInterpolation = -3; // "%" followed by invalid text + +// Records the location and content of an interpolation. +struct Interpolation { + Interpolation(const char* start, const char* end, int param) + : start_pos(start), end_pos(end), param_index(param) {} + + // Points to the start of the interpolation (the '%' character). + const char* start_pos; + // Points to the first character after the interpolation. + const char* end_pos; + // 0-based index of the interpolated matcher parameter; + // kTupleInterpolation for "%(*)s"; kPercentInterpolation for "%%". + int param_index; +}; + +typedef ::std::vector Interpolations; + +// Parses a matcher description string and returns a vector of +// interpolations that appear in the string; generates non-fatal +// failures iff 'description' is an invalid matcher description. +// 'param_names' is a NULL-terminated array of parameter names in the +// order they appear in the MATCHER_P*() parameter list. +Interpolations ValidateMatcherDescription( + const char* param_names[], const char* description); + +// Returns the actual matcher description, given the matcher name, +// user-supplied description template string, interpolations in the +// string, and the printed values of the matcher parameters. +string FormatMatcherDescription( + const char* matcher_name, const char* description, + const Interpolations& interp, const Strings& param_values); + +} // namespace internal + +// Implements MatcherCast(). +template +inline Matcher MatcherCast(M matcher) { + return internal::MatcherCastImpl::Cast(matcher); +} + +// _ is a matcher that matches anything of any type. +// +// This definition is fine as: +// +// 1. The C++ standard permits using the name _ in a namespace that +// is not the global namespace or ::std. +// 2. The AnythingMatcher class has no data member or constructor, +// so it's OK to create global variables of this type. +// 3. c-style has approved of using _ in this case. +const internal::AnythingMatcher _ = {}; +// Creates a matcher that matches any value of the given type T. +template +inline Matcher A() { return MakeMatcher(new internal::AnyMatcherImpl()); } + +// Creates a matcher that matches any value of the given type T. +template +inline Matcher An() { return A(); } + +// Creates a polymorphic matcher that matches anything equal to x. +// Note: if the parameter of Eq() were declared as const T&, Eq("foo") +// wouldn't compile. +template +inline internal::EqMatcher Eq(T x) { return internal::EqMatcher(x); } + +// Constructs a Matcher from a 'value' of type T. The constructed +// matcher matches any value that's equal to 'value'. +template +Matcher::Matcher(T value) { *this = Eq(value); } + +// Creates a monomorphic matcher that matches anything with type Lhs +// and equal to rhs. A user may need to use this instead of Eq(...) +// in order to resolve an overloading ambiguity. +// +// TypedEq(x) is just a convenient short-hand for Matcher(Eq(x)) +// or Matcher(x), but more readable than the latter. +// +// We could define similar monomorphic matchers for other comparison +// operations (e.g. TypedLt, TypedGe, and etc), but decided not to do +// it yet as those are used much less than Eq() in practice. A user +// can always write Matcher(Lt(5)) to be explicit about the type, +// for example. +template +inline Matcher TypedEq(const Rhs& rhs) { return Eq(rhs); } + +// Creates a polymorphic matcher that matches anything >= x. +template +inline internal::GeMatcher Ge(Rhs x) { + return internal::GeMatcher(x); +} + +// Creates a polymorphic matcher that matches anything > x. +template +inline internal::GtMatcher Gt(Rhs x) { + return internal::GtMatcher(x); +} + +// Creates a polymorphic matcher that matches anything <= x. +template +inline internal::LeMatcher Le(Rhs x) { + return internal::LeMatcher(x); +} + +// Creates a polymorphic matcher that matches anything < x. +template +inline internal::LtMatcher Lt(Rhs x) { + return internal::LtMatcher(x); +} + +// Creates a polymorphic matcher that matches anything != x. +template +inline internal::NeMatcher Ne(Rhs x) { + return internal::NeMatcher(x); +} + +// Creates a polymorphic matcher that matches any NULL pointer. +inline PolymorphicMatcher IsNull() { + return MakePolymorphicMatcher(internal::IsNullMatcher()); +} + +// Creates a polymorphic matcher that matches any non-NULL pointer. +// This is convenient as Not(NULL) doesn't compile (the compiler +// thinks that that expression is comparing a pointer with an integer). +inline PolymorphicMatcher NotNull() { + return MakePolymorphicMatcher(internal::NotNullMatcher()); +} + +// Creates a polymorphic matcher that matches any argument that +// references variable x. +template +inline internal::RefMatcher Ref(T& x) { // NOLINT + return internal::RefMatcher(x); +} + +// Creates a matcher that matches any double argument approximately +// equal to rhs, where two NANs are considered unequal. +inline internal::FloatingEqMatcher DoubleEq(double rhs) { + return internal::FloatingEqMatcher(rhs, false); +} + +// Creates a matcher that matches any double argument approximately +// equal to rhs, including NaN values when rhs is NaN. +inline internal::FloatingEqMatcher NanSensitiveDoubleEq(double rhs) { + return internal::FloatingEqMatcher(rhs, true); +} + +// Creates a matcher that matches any float argument approximately +// equal to rhs, where two NANs are considered unequal. +inline internal::FloatingEqMatcher FloatEq(float rhs) { + return internal::FloatingEqMatcher(rhs, false); +} + +// Creates a matcher that matches any double argument approximately +// equal to rhs, including NaN values when rhs is NaN. +inline internal::FloatingEqMatcher NanSensitiveFloatEq(float rhs) { + return internal::FloatingEqMatcher(rhs, true); +} + +// Creates a matcher that matches a pointer (raw or smart) that points +// to a value that matches inner_matcher. +template +inline internal::PointeeMatcher Pointee( + const InnerMatcher& inner_matcher) { + return internal::PointeeMatcher(inner_matcher); +} + +// Creates a matcher that matches an object whose given field matches +// 'matcher'. For example, +// Field(&Foo::number, Ge(5)) +// matches a Foo object x iff x.number >= 5. +template +inline PolymorphicMatcher< + internal::FieldMatcher > Field( + FieldType Class::*field, const FieldMatcher& matcher) { + return MakePolymorphicMatcher( + internal::FieldMatcher( + field, MatcherCast(matcher))); + // The call to MatcherCast() is required for supporting inner + // matchers of compatible types. For example, it allows + // Field(&Foo::bar, m) + // to compile where bar is an int32 and m is a matcher for int64. +} + +// Creates a matcher that matches an object whose given property +// matches 'matcher'. For example, +// Property(&Foo::str, StartsWith("hi")) +// matches a Foo object x iff x.str() starts with "hi". +template +inline PolymorphicMatcher< + internal::PropertyMatcher > Property( + PropertyType (Class::*property)() const, const PropertyMatcher& matcher) { + return MakePolymorphicMatcher( + internal::PropertyMatcher( + property, + MatcherCast(matcher))); + // The call to MatcherCast() is required for supporting inner + // matchers of compatible types. For example, it allows + // Property(&Foo::bar, m) + // to compile where bar() returns an int32 and m is a matcher for int64. +} + +// Creates a matcher that matches an object iff the result of applying +// a callable to x matches 'matcher'. +// For example, +// ResultOf(f, StartsWith("hi")) +// matches a Foo object x iff f(x) starts with "hi". +// callable parameter can be a function, function pointer, or a functor. +// Callable has to satisfy the following conditions: +// * It is required to keep no state affecting the results of +// the calls on it and make no assumptions about how many calls +// will be made. Any state it keeps must be protected from the +// concurrent access. +// * If it is a function object, it has to define type result_type. +// We recommend deriving your functor classes from std::unary_function. +template +internal::ResultOfMatcher ResultOf( + Callable callable, const ResultOfMatcher& matcher) { + return internal::ResultOfMatcher( + callable, + MatcherCast::ResultType>( + matcher)); + // The call to MatcherCast() is required for supporting inner + // matchers of compatible types. For example, it allows + // ResultOf(Function, m) + // to compile where Function() returns an int32 and m is a matcher for int64. +} + +// String matchers. + +// Matches a string equal to str. +inline PolymorphicMatcher > + StrEq(const internal::string& str) { + return MakePolymorphicMatcher(internal::StrEqualityMatcher( + str, true, true)); +} + +// Matches a string not equal to str. +inline PolymorphicMatcher > + StrNe(const internal::string& str) { + return MakePolymorphicMatcher(internal::StrEqualityMatcher( + str, false, true)); +} + +// Matches a string equal to str, ignoring case. +inline PolymorphicMatcher > + StrCaseEq(const internal::string& str) { + return MakePolymorphicMatcher(internal::StrEqualityMatcher( + str, true, false)); +} + +// Matches a string not equal to str, ignoring case. +inline PolymorphicMatcher > + StrCaseNe(const internal::string& str) { + return MakePolymorphicMatcher(internal::StrEqualityMatcher( + str, false, false)); +} + +// Creates a matcher that matches any string, std::string, or C string +// that contains the given substring. +inline PolymorphicMatcher > + HasSubstr(const internal::string& substring) { + return MakePolymorphicMatcher(internal::HasSubstrMatcher( + substring)); +} + +// Matches a string that starts with 'prefix' (case-sensitive). +inline PolymorphicMatcher > + StartsWith(const internal::string& prefix) { + return MakePolymorphicMatcher(internal::StartsWithMatcher( + prefix)); +} + +// Matches a string that ends with 'suffix' (case-sensitive). +inline PolymorphicMatcher > + EndsWith(const internal::string& suffix) { + return MakePolymorphicMatcher(internal::EndsWithMatcher( + suffix)); +} + +// Matches a string that fully matches regular expression 'regex'. +// The matcher takes ownership of 'regex'. +inline PolymorphicMatcher MatchesRegex( + const internal::RE* regex) { + return MakePolymorphicMatcher(internal::MatchesRegexMatcher(regex, true)); +} +inline PolymorphicMatcher MatchesRegex( + const internal::string& regex) { + return MatchesRegex(new internal::RE(regex)); +} + +// Matches a string that contains regular expression 'regex'. +// The matcher takes ownership of 'regex'. +inline PolymorphicMatcher ContainsRegex( + const internal::RE* regex) { + return MakePolymorphicMatcher(internal::MatchesRegexMatcher(regex, false)); +} +inline PolymorphicMatcher ContainsRegex( + const internal::string& regex) { + return ContainsRegex(new internal::RE(regex)); +} + +#if GTEST_HAS_GLOBAL_WSTRING || GTEST_HAS_STD_WSTRING +// Wide string matchers. + +// Matches a string equal to str. +inline PolymorphicMatcher > + StrEq(const internal::wstring& str) { + return MakePolymorphicMatcher(internal::StrEqualityMatcher( + str, true, true)); +} + +// Matches a string not equal to str. +inline PolymorphicMatcher > + StrNe(const internal::wstring& str) { + return MakePolymorphicMatcher(internal::StrEqualityMatcher( + str, false, true)); +} + +// Matches a string equal to str, ignoring case. +inline PolymorphicMatcher > + StrCaseEq(const internal::wstring& str) { + return MakePolymorphicMatcher(internal::StrEqualityMatcher( + str, true, false)); +} + +// Matches a string not equal to str, ignoring case. +inline PolymorphicMatcher > + StrCaseNe(const internal::wstring& str) { + return MakePolymorphicMatcher(internal::StrEqualityMatcher( + str, false, false)); +} + +// Creates a matcher that matches any wstring, std::wstring, or C wide string +// that contains the given substring. +inline PolymorphicMatcher > + HasSubstr(const internal::wstring& substring) { + return MakePolymorphicMatcher(internal::HasSubstrMatcher( + substring)); +} + +// Matches a string that starts with 'prefix' (case-sensitive). +inline PolymorphicMatcher > + StartsWith(const internal::wstring& prefix) { + return MakePolymorphicMatcher(internal::StartsWithMatcher( + prefix)); +} + +// Matches a string that ends with 'suffix' (case-sensitive). +inline PolymorphicMatcher > + EndsWith(const internal::wstring& suffix) { + return MakePolymorphicMatcher(internal::EndsWithMatcher( + suffix)); +} + +#endif // GTEST_HAS_GLOBAL_WSTRING || GTEST_HAS_STD_WSTRING + +// Creates a polymorphic matcher that matches a 2-tuple where the +// first field == the second field. +inline internal::Eq2Matcher Eq() { return internal::Eq2Matcher(); } + +// Creates a polymorphic matcher that matches a 2-tuple where the +// first field >= the second field. +inline internal::Ge2Matcher Ge() { return internal::Ge2Matcher(); } + +// Creates a polymorphic matcher that matches a 2-tuple where the +// first field > the second field. +inline internal::Gt2Matcher Gt() { return internal::Gt2Matcher(); } + +// Creates a polymorphic matcher that matches a 2-tuple where the +// first field <= the second field. +inline internal::Le2Matcher Le() { return internal::Le2Matcher(); } + +// Creates a polymorphic matcher that matches a 2-tuple where the +// first field < the second field. +inline internal::Lt2Matcher Lt() { return internal::Lt2Matcher(); } + +// Creates a polymorphic matcher that matches a 2-tuple where the +// first field != the second field. +inline internal::Ne2Matcher Ne() { return internal::Ne2Matcher(); } + +// Creates a matcher that matches any value of type T that m doesn't +// match. +template +inline internal::NotMatcher Not(InnerMatcher m) { + return internal::NotMatcher(m); +} + +// Creates a matcher that matches any value that matches all of the +// given matchers. +// +// For now we only support up to 5 matchers. Support for more +// matchers can be added as needed, or the user can use nested +// AllOf()s. +template +inline internal::BothOfMatcher +AllOf(Matcher1 m1, Matcher2 m2) { + return internal::BothOfMatcher(m1, m2); +} + +template +inline internal::BothOfMatcher > +AllOf(Matcher1 m1, Matcher2 m2, Matcher3 m3) { + return AllOf(m1, AllOf(m2, m3)); +} + +template +inline internal::BothOfMatcher > > +AllOf(Matcher1 m1, Matcher2 m2, Matcher3 m3, Matcher4 m4) { + return AllOf(m1, AllOf(m2, m3, m4)); +} + +template +inline internal::BothOfMatcher > > > +AllOf(Matcher1 m1, Matcher2 m2, Matcher3 m3, Matcher4 m4, Matcher5 m5) { + return AllOf(m1, AllOf(m2, m3, m4, m5)); +} + +// Creates a matcher that matches any value that matches at least one +// of the given matchers. +// +// For now we only support up to 5 matchers. Support for more +// matchers can be added as needed, or the user can use nested +// AnyOf()s. +template +inline internal::EitherOfMatcher +AnyOf(Matcher1 m1, Matcher2 m2) { + return internal::EitherOfMatcher(m1, m2); +} + +template +inline internal::EitherOfMatcher > +AnyOf(Matcher1 m1, Matcher2 m2, Matcher3 m3) { + return AnyOf(m1, AnyOf(m2, m3)); +} + +template +inline internal::EitherOfMatcher > > +AnyOf(Matcher1 m1, Matcher2 m2, Matcher3 m3, Matcher4 m4) { + return AnyOf(m1, AnyOf(m2, m3, m4)); +} + +template +inline internal::EitherOfMatcher > > > +AnyOf(Matcher1 m1, Matcher2 m2, Matcher3 m3, Matcher4 m4, Matcher5 m5) { + return AnyOf(m1, AnyOf(m2, m3, m4, m5)); +} + +// Returns a matcher that matches anything that satisfies the given +// predicate. The predicate can be any unary function or functor +// whose return type can be implicitly converted to bool. +template +inline PolymorphicMatcher > +Truly(Predicate pred) { + return MakePolymorphicMatcher(internal::TrulyMatcher(pred)); +} + +// Returns a matcher that matches an equal container. +// This matcher behaves like Eq(), but in the event of mismatch lists the +// values that are included in one container but not the other. (Duplicate +// values and order differences are not explained.) +template +inline PolymorphicMatcher > + ContainerEq(const Container& rhs) { + // This following line is for working around a bug in MSVC 8.0, + // which causes Container to be a const type sometimes. + typedef GMOCK_REMOVE_CONST_(Container) RawContainer; + return MakePolymorphicMatcher( + internal::ContainerEqMatcher(rhs)); +} + +// Matches an STL-style container or a native array that contains at +// least one element matching the given value or matcher. +// +// Examples: +// ::std::set page_ids; +// page_ids.insert(3); +// page_ids.insert(1); +// EXPECT_THAT(page_ids, Contains(1)); +// EXPECT_THAT(page_ids, Contains(Gt(2))); +// EXPECT_THAT(page_ids, Not(Contains(4))); +// +// ::std::map page_lengths; +// page_lengths[1] = 100; +// EXPECT_THAT(page_lengths, +// Contains(::std::pair(1, 100))); +// +// const char* user_ids[] = { "joe", "mike", "tom" }; +// EXPECT_THAT(user_ids, Contains(Eq(::std::string("tom")))); +template +inline internal::ContainsMatcher Contains(M matcher) { + return internal::ContainsMatcher(matcher); +} + +// Key(inner_matcher) matches an std::pair whose 'first' field matches +// inner_matcher. For example, Contains(Key(Ge(5))) can be used to match an +// std::map that contains at least one element whose key is >= 5. +template +inline internal::KeyMatcher Key(M inner_matcher) { + return internal::KeyMatcher(inner_matcher); +} + +// Pair(first_matcher, second_matcher) matches a std::pair whose 'first' field +// matches first_matcher and whose 'second' field matches second_matcher. For +// example, EXPECT_THAT(map_type, ElementsAre(Pair(Ge(5), "foo"))) can be used +// to match a std::map that contains exactly one element whose key +// is >= 5 and whose value equals "foo". +template +inline internal::PairMatcher +Pair(FirstMatcher first_matcher, SecondMatcher second_matcher) { + return internal::PairMatcher( + first_matcher, second_matcher); +} + +// Returns a predicate that is satisfied by anything that matches the +// given matcher. +template +inline internal::MatcherAsPredicate Matches(M matcher) { + return internal::MatcherAsPredicate(matcher); +} + +// Returns true iff the value matches the matcher. +template +inline bool Value(const T& value, M matcher) { + return testing::Matches(matcher)(value); +} + +// Matches the value against the given matcher and explains the match +// result to listener. +template +inline bool ExplainMatchResult( + M matcher, const T& value, MatchResultListener* listener) { + return SafeMatcherCast(matcher).MatchAndExplain(value, listener); +} + +// AllArgs(m) is a synonym of m. This is useful in +// +// EXPECT_CALL(foo, Bar(_, _)).With(AllArgs(Eq())); +// +// which is easier to read than +// +// EXPECT_CALL(foo, Bar(_, _)).With(Eq()); +template +inline InnerMatcher AllArgs(const InnerMatcher& matcher) { return matcher; } + +// These macros allow using matchers to check values in Google Test +// tests. ASSERT_THAT(value, matcher) and EXPECT_THAT(value, matcher) +// succeed iff the value matches the matcher. If the assertion fails, +// the value and the description of the matcher will be printed. +#define ASSERT_THAT(value, matcher) ASSERT_PRED_FORMAT1(\ + ::testing::internal::MakePredicateFormatterFromMatcher(matcher), value) +#define EXPECT_THAT(value, matcher) EXPECT_PRED_FORMAT1(\ + ::testing::internal::MakePredicateFormatterFromMatcher(matcher), value) + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ diff --git a/3rdparty/gmock/include/gmock/gmock-more-actions.h b/3rdparty/gmock/include/gmock/gmock-more-actions.h new file mode 100644 index 00000000..6226392d --- /dev/null +++ b/3rdparty/gmock/include/gmock/gmock-more-actions.h @@ -0,0 +1,210 @@ +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some actions that depend on gmock-generated-actions.h. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_MORE_ACTIONS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_MORE_ACTIONS_H_ + +#include + +namespace testing { +namespace internal { + +// Implements the Invoke(f) action. The template argument +// FunctionImpl is the implementation type of f, which can be either a +// function pointer or a functor. Invoke(f) can be used as an +// Action as long as f's type is compatible with F (i.e. f can be +// assigned to a tr1::function). +template +class InvokeAction { + public: + // The c'tor makes a copy of function_impl (either a function + // pointer or a functor). + explicit InvokeAction(FunctionImpl function_impl) + : function_impl_(function_impl) {} + + template + Result Perform(const ArgumentTuple& args) { + return InvokeHelper::Invoke(function_impl_, args); + } + + private: + FunctionImpl function_impl_; + + GTEST_DISALLOW_ASSIGN_(InvokeAction); +}; + +// Implements the Invoke(object_ptr, &Class::Method) action. +template +class InvokeMethodAction { + public: + InvokeMethodAction(Class* obj_ptr, MethodPtr method_ptr) + : obj_ptr_(obj_ptr), method_ptr_(method_ptr) {} + + template + Result Perform(const ArgumentTuple& args) const { + return InvokeHelper::InvokeMethod( + obj_ptr_, method_ptr_, args); + } + + private: + Class* const obj_ptr_; + const MethodPtr method_ptr_; + + GTEST_DISALLOW_ASSIGN_(InvokeMethodAction); +}; + +} // namespace internal + +// Various overloads for Invoke(). + +// Creates an action that invokes 'function_impl' with the mock +// function's arguments. +template +PolymorphicAction > Invoke( + FunctionImpl function_impl) { + return MakePolymorphicAction( + internal::InvokeAction(function_impl)); +} + +// Creates an action that invokes the given method on the given object +// with the mock function's arguments. +template +PolymorphicAction > Invoke( + Class* obj_ptr, MethodPtr method_ptr) { + return MakePolymorphicAction( + internal::InvokeMethodAction(obj_ptr, method_ptr)); +} + +// WithoutArgs(inner_action) can be used in a mock function with a +// non-empty argument list to perform inner_action, which takes no +// argument. In other words, it adapts an action accepting no +// argument to one that accepts (and ignores) arguments. +template +inline internal::WithArgsAction +WithoutArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +// WithArg(an_action) creates an action that passes the k-th +// (0-based) argument of the mock function to an_action and performs +// it. It adapts an action accepting one argument to one that accepts +// multiple arguments. For convenience, we also provide +// WithArgs(an_action) (defined below) as a synonym. +template +inline internal::WithArgsAction +WithArg(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +// The ACTION*() macros trigger warning C4100 (unreferenced formal +// parameter) in MSVC with -W4. Unfortunately they cannot be fixed in +// the macro definition, as the warnings are generated when the macro +// is expanded and macro expansion cannot contain #pragma. Therefore +// we suppress them here. +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4100) +#endif + +// Action ReturnArg() returns the k-th argument of the mock function. +ACTION_TEMPLATE(ReturnArg, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_0_VALUE_PARAMS()) { + return std::tr1::get(args); +} + +// Action SaveArg(pointer) saves the k-th (0-based) argument of the +// mock function to *pointer. +ACTION_TEMPLATE(SaveArg, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_1_VALUE_PARAMS(pointer)) { + *pointer = ::std::tr1::get(args); +} + +// Action SetArgReferee(value) assigns 'value' to the variable +// referenced by the k-th (0-based) argument of the mock function. +ACTION_TEMPLATE(SetArgReferee, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_1_VALUE_PARAMS(value)) { + typedef typename ::std::tr1::tuple_element::type argk_type; + // Ensures that argument #k is a reference. If you get a compiler + // error on the next line, you are using SetArgReferee(value) in + // a mock function whose k-th (0-based) argument is not a reference. + GMOCK_COMPILE_ASSERT_(internal::is_reference::value, + SetArgReferee_must_be_used_with_a_reference_argument); + ::std::tr1::get(args) = value; +} + +// Action SetArrayArgument(first, last) copies the elements in +// source range [first, last) to the array pointed to by the k-th +// (0-based) argument, which can be either a pointer or an +// iterator. The action does not take ownership of the elements in the +// source range. +ACTION_TEMPLATE(SetArrayArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_2_VALUE_PARAMS(first, last)) { + // Microsoft compiler deprecates ::std::copy, so we want to suppress warning + // 4996 (Function call with parameters that may be unsafe) there. +#ifdef _MSC_VER +#pragma warning(push) // Saves the current warning state. +#pragma warning(disable:4996) // Temporarily disables warning 4996. +#endif + ::std::copy(first, last, ::std::tr1::get(args)); +#ifdef _MSC_VER +#pragma warning(pop) // Restores the warning state. +#endif +} + +// Action DeleteArg() deletes the k-th (0-based) argument of the mock +// function. +ACTION_TEMPLATE(DeleteArg, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_0_VALUE_PARAMS()) { + delete ::std::tr1::get(args); +} + +// Action Throw(exception) can be used in a mock function of any type +// to throw the given exception. Any copyable value can be thrown. +#if GTEST_HAS_EXCEPTIONS +ACTION_P(Throw, exception) { throw exception; } +#endif // GTEST_HAS_EXCEPTIONS + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_MORE_ACTIONS_H_ diff --git a/3rdparty/gmock/include/gmock/gmock-printers.h b/3rdparty/gmock/include/gmock/gmock-printers.h new file mode 100644 index 00000000..d1cd03ca --- /dev/null +++ b/3rdparty/gmock/include/gmock/gmock-printers.h @@ -0,0 +1,725 @@ +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements a universal value printer that can print a +// value of any type T: +// +// void ::testing::internal::UniversalPrinter::Print(value, ostream_ptr); +// +// A user can teach this function how to print a class type T by +// defining either operator<<() or PrintTo() in the namespace that +// defines T. More specifically, the FIRST defined function in the +// following list will be used (assuming T is defined in namespace +// foo): +// +// 1. foo::PrintTo(const T&, ostream*) +// 2. operator<<(ostream&, const T&) defined in either foo or the +// global namespace. +// +// If none of the above is defined, it will print the debug string of +// the value if it is a protocol buffer, or print the raw bytes in the +// value otherwise. +// +// To aid debugging: when T is a reference type, the address of the +// value is also printed; when T is a (const) char pointer, both the +// pointer value and the NUL-terminated string it points to are +// printed. +// +// We also provide some convenient wrappers: +// +// // Prints a value to a string. For a (const or not) char +// // pointer, the NUL-terminated string (but not the pointer) is +// // printed. +// std::string ::testing::PrintToString(const T& value); +// +// // Prints a value tersely: for a reference type, the referenced +// // value (but not the address) is printed; for a (const or not) char +// // pointer, the NUL-terminated string (but not the pointer) is +// // printed. +// void ::testing::internal::UniversalTersePrint(const T& value, ostream*); +// +// // Prints value using the type inferred by the compiler. The difference +// // from UniversalTersePrint() is that this function prints both the +// // pointer and the NUL-terminated string for a (const or not) char pointer. +// void ::testing::internal::UniversalPrint(const T& value, ostream*); +// +// // Prints the fields of a tuple tersely to a string vector, one +// // element for each field. +// std::vector UniversalTersePrintTupleFieldsToStrings( +// const Tuple& value); +// +// Known limitation: +// +// The print primitives print the elements of an STL-style container +// using the compiler-inferred type of *iter where iter is a +// const_iterator of the container. When const_iterator is an input +// iterator but not a forward iterator, this inferred type may not +// match value_type, and the print output may be incorrect. In +// practice, this is rarely a problem as for most containers +// const_iterator is a forward iterator. We'll fix this if there's an +// actual need for it. Note that this fix cannot rely on value_type +// being defined as many user-defined container types don't have +// value_type. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_PRINTERS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_PRINTERS_H_ + +#include // NOLINT +#include +#include +#include +#include + +#include +#include +#include + +namespace testing { + +// Definitions in the 'internal' and 'internal2' name spaces are +// subject to change without notice. DO NOT USE THEM IN USER CODE! +namespace internal2 { + +// Prints the given number of bytes in the given object to the given +// ostream. +void PrintBytesInObjectTo(const unsigned char* obj_bytes, + size_t count, + ::std::ostream* os); + +// TypeWithoutFormatter::PrintValue(value, os) is called +// by the universal printer to print a value of type T when neither +// operator<< nor PrintTo() is defined for type T. When T is +// ProtocolMessage, proto2::Message, or a subclass of those, kIsProto +// will be true and the short debug string of the protocol message +// value will be printed; otherwise kIsProto will be false and the +// bytes in the value will be printed. +template +class TypeWithoutFormatter { + public: + static void PrintValue(const T& value, ::std::ostream* os) { + PrintBytesInObjectTo(reinterpret_cast(&value), + sizeof(value), os); + } +}; + +// We print a protobuf using its ShortDebugString() when the string +// doesn't exceed this many characters; otherwise we print it using +// DebugString() for better readability. +const size_t kProtobufOneLinerMaxLength = 50; + +template +class TypeWithoutFormatter { + public: + static void PrintValue(const T& value, ::std::ostream* os) { + const ::testing::internal::string short_str = value.ShortDebugString(); + const ::testing::internal::string pretty_str = + short_str.length() <= kProtobufOneLinerMaxLength ? + short_str : ("\n" + value.DebugString()); + ::std::operator<<(*os, "<" + pretty_str + ">"); + } +}; + +// Prints the given value to the given ostream. If the value is a +// protocol message, its short debug string is printed; otherwise the +// bytes in the value are printed. This is what +// UniversalPrinter::Print() does when it knows nothing about type +// T and T has no << operator. +// +// A user can override this behavior for a class type Foo by defining +// a << operator in the namespace where Foo is defined. +// +// We put this operator in namespace 'internal2' instead of 'internal' +// to simplify the implementation, as much code in 'internal' needs to +// use << in STL, which would conflict with our own << were it defined +// in 'internal'. +// +// Note that this operator<< takes a generic std::basic_ostream type instead of the more restricted std::ostream. If +// we define it to take an std::ostream instead, we'll get an +// "ambiguous overloads" compiler error when trying to print a type +// Foo that supports streaming to std::basic_ostream, as the compiler cannot tell whether +// operator<<(std::ostream&, const T&) or +// operator<<(std::basic_stream, const Foo&) is more +// specific. +template +::std::basic_ostream& operator<<( + ::std::basic_ostream& os, const T& x) { + TypeWithoutFormatter::value>:: + PrintValue(x, &os); + return os; +} + +} // namespace internal2 +} // namespace testing + +// This namespace MUST NOT BE NESTED IN ::testing, or the name look-up +// magic needed for implementing UniversalPrinter won't work. +namespace testing_internal { + +// Used to print a value that is not an STL-style container when the +// user doesn't define PrintTo() for it. +template +void DefaultPrintNonContainerTo(const T& value, ::std::ostream* os) { + // With the following statement, during unqualified name lookup, + // testing::internal2::operator<< appears as if it was declared in + // the nearest enclosing namespace that contains both + // ::testing_internal and ::testing::internal2, i.e. the global + // namespace. For more details, refer to the C++ Standard section + // 7.3.4-1 [namespace.udir]. This allows us to fall back onto + // testing::internal2::operator<< in case T doesn't come with a << + // operator. + // + // We cannot write 'using ::testing::internal2::operator<<;', which + // gcc 3.3 fails to compile due to a compiler bug. + using namespace ::testing::internal2; // NOLINT + + // Assuming T is defined in namespace foo, in the next statement, + // the compiler will consider all of: + // + // 1. foo::operator<< (thanks to Koenig look-up), + // 2. ::operator<< (as the current namespace is enclosed in ::), + // 3. testing::internal2::operator<< (thanks to the using statement above). + // + // The operator<< whose type matches T best will be picked. + // + // We deliberately allow #2 to be a candidate, as sometimes it's + // impossible to define #1 (e.g. when foo is ::std, defining + // anything in it is undefined behavior unless you are a compiler + // vendor.). + *os << value; +} + +} // namespace testing_internal + +namespace testing { +namespace internal { + +// UniversalPrinter::Print(value, ostream_ptr) prints the given +// value to the given ostream. The caller must ensure that +// 'ostream_ptr' is not NULL, or the behavior is undefined. +// +// We define UniversalPrinter as a class template (as opposed to a +// function template), as we need to partially specialize it for +// reference types, which cannot be done with function templates. +template +class UniversalPrinter; + +template +void UniversalPrint(const T& value, ::std::ostream* os); + +// Used to print an STL-style container when the user doesn't define +// a PrintTo() for it. +template +void DefaultPrintTo(IsContainer /* dummy */, + false_type /* is not a pointer */, + const C& container, ::std::ostream* os) { + const size_t kMaxCount = 32; // The maximum number of elements to print. + *os << '{'; + size_t count = 0; + for (typename C::const_iterator it = container.begin(); + it != container.end(); ++it, ++count) { + if (count > 0) { + *os << ','; + if (count == kMaxCount) { // Enough has been printed. + *os << " ..."; + break; + } + } + *os << ' '; + // We cannot call PrintTo(*it, os) here as PrintTo() doesn't + // handle *it being a native array. + internal::UniversalPrint(*it, os); + } + + if (count > 0) { + *os << ' '; + } + *os << '}'; +} + +// Used to print a pointer that is neither a char pointer nor a member +// pointer, when the user doesn't define PrintTo() for it. (A member +// variable pointer or member function pointer doesn't really point to +// a location in the address space. Their representation is +// implementation-defined. Therefore they will be printed as raw +// bytes.) +template +void DefaultPrintTo(IsNotContainer /* dummy */, + true_type /* is a pointer */, + T* p, ::std::ostream* os) { + if (p == NULL) { + *os << "NULL"; + } else { + // We want to print p as a const void*. However, we cannot cast + // it to const void* directly, even using reinterpret_cast, as + // earlier versions of gcc (e.g. 3.4.5) cannot compile the cast + // when p is a function pointer. Casting to UInt64 first solves + // the problem. + *os << reinterpret_cast(reinterpret_cast(p)); + } +} + +// Used to print a non-container, non-pointer value when the user +// doesn't define PrintTo() for it. +template +void DefaultPrintTo(IsNotContainer /* dummy */, + false_type /* is not a pointer */, + const T& value, ::std::ostream* os) { + ::testing_internal::DefaultPrintNonContainerTo(value, os); +} + +// Prints the given value using the << operator if it has one; +// otherwise prints the bytes in it. This is what +// UniversalPrinter::Print() does when PrintTo() is not specialized +// or overloaded for type T. +// +// A user can override this behavior for a class type Foo by defining +// an overload of PrintTo() in the namespace where Foo is defined. We +// give the user this option as sometimes defining a << operator for +// Foo is not desirable (e.g. the coding style may prevent doing it, +// or there is already a << operator but it doesn't do what the user +// wants). +template +void PrintTo(const T& value, ::std::ostream* os) { + // DefaultPrintTo() is overloaded. The type of its first two + // arguments determine which version will be picked. If T is an + // STL-style container, the version for container will be called; if + // T is a pointer, the pointer version will be called; otherwise the + // generic version will be called. + // + // Note that we check for container types here, prior to we check + // for protocol message types in our operator<<. The rationale is: + // + // For protocol messages, we want to give people a chance to + // override Google Mock's format by defining a PrintTo() or + // operator<<. For STL containers, other formats can be + // incompatible with Google Mock's format for the container + // elements; therefore we check for container types here to ensure + // that our format is used. + // + // The second argument of DefaultPrintTo() is needed to bypass a bug + // in Symbian's C++ compiler that prevents it from picking the right + // overload between: + // + // PrintTo(const T& x, ...); + // PrintTo(T* x, ...); + DefaultPrintTo(IsContainerTest(0), is_pointer(), value, os); +} + +// The following list of PrintTo() overloads tells +// UniversalPrinter::Print() how to print standard types (built-in +// types, strings, plain arrays, and pointers). + +// Overloads for various char types. +void PrintCharTo(char c, int char_code, ::std::ostream* os); +inline void PrintTo(unsigned char c, ::std::ostream* os) { + PrintCharTo(c, c, os); +} +inline void PrintTo(signed char c, ::std::ostream* os) { + PrintCharTo(c, c, os); +} +inline void PrintTo(char c, ::std::ostream* os) { + // When printing a plain char, we always treat it as unsigned. This + // way, the output won't be affected by whether the compiler thinks + // char is signed or not. + PrintTo(static_cast(c), os); +} + +// Overloads for other simple built-in types. +inline void PrintTo(bool x, ::std::ostream* os) { + *os << (x ? "true" : "false"); +} + +// Overload for wchar_t type. +// Prints a wchar_t as a symbol if it is printable or as its internal +// code otherwise and also as its decimal code (except for L'\0'). +// The L'\0' char is printed as "L'\\0'". The decimal code is printed +// as signed integer when wchar_t is implemented by the compiler +// as a signed type and is printed as an unsigned integer when wchar_t +// is implemented as an unsigned type. +void PrintTo(wchar_t wc, ::std::ostream* os); + +// Overloads for C strings. +void PrintTo(const char* s, ::std::ostream* os); +inline void PrintTo(char* s, ::std::ostream* os) { + PrintTo(implicit_cast(s), os); +} + +// MSVC can be configured to define wchar_t as a typedef of unsigned +// short. It defines _NATIVE_WCHAR_T_DEFINED when wchar_t is a native +// type. When wchar_t is a typedef, defining an overload for const +// wchar_t* would cause unsigned short* be printed as a wide string, +// possibly causing invalid memory accesses. +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +// Overloads for wide C strings +void PrintTo(const wchar_t* s, ::std::ostream* os); +inline void PrintTo(wchar_t* s, ::std::ostream* os) { + PrintTo(implicit_cast(s), os); +} +#endif + +// Overload for C arrays. Multi-dimensional arrays are printed +// properly. + +// Prints the given number of elements in an array, without printing +// the curly braces. +template +void PrintRawArrayTo(const T a[], size_t count, ::std::ostream* os) { + UniversalPrinter::Print(a[0], os); + for (size_t i = 1; i != count; i++) { + *os << ", "; + UniversalPrinter::Print(a[i], os); + } +} + +// Overloads for ::string and ::std::string. +#if GTEST_HAS_GLOBAL_STRING +void PrintStringTo(const ::string&s, ::std::ostream* os); +inline void PrintTo(const ::string& s, ::std::ostream* os) { + PrintStringTo(s, os); +} +#endif // GTEST_HAS_GLOBAL_STRING + +void PrintStringTo(const ::std::string&s, ::std::ostream* os); +inline void PrintTo(const ::std::string& s, ::std::ostream* os) { + PrintStringTo(s, os); +} + +// Overloads for ::wstring and ::std::wstring. +#if GTEST_HAS_GLOBAL_WSTRING +void PrintWideStringTo(const ::wstring&s, ::std::ostream* os); +inline void PrintTo(const ::wstring& s, ::std::ostream* os) { + PrintWideStringTo(s, os); +} +#endif // GTEST_HAS_GLOBAL_WSTRING + +#if GTEST_HAS_STD_WSTRING +void PrintWideStringTo(const ::std::wstring&s, ::std::ostream* os); +inline void PrintTo(const ::std::wstring& s, ::std::ostream* os) { + PrintWideStringTo(s, os); +} +#endif // GTEST_HAS_STD_WSTRING + +// Overload for ::std::tr1::tuple. Needed for printing function +// arguments, which are packed as tuples. + +// Helper function for printing a tuple. T must be instantiated with +// a tuple type. +template +void PrintTupleTo(const T& t, ::std::ostream* os); + +// Overloaded PrintTo() for tuples of various arities. We support +// tuples of up-to 10 fields. The following implementation works +// regardless of whether tr1::tuple is implemented using the +// non-standard variadic template feature or not. + +inline void PrintTo(const ::std::tr1::tuple<>& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo( + const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +// Overload for std::pair. +template +void PrintTo(const ::std::pair& value, ::std::ostream* os) { + *os << '('; + UniversalPrinter::Print(value.first, os); + *os << ", "; + UniversalPrinter::Print(value.second, os); + *os << ')'; +} + +// Implements printing a non-reference type T by letting the compiler +// pick the right overload of PrintTo() for T. +template +class UniversalPrinter { + public: + // MSVC warns about adding const to a function type, so we want to + // disable the warning. +#ifdef _MSC_VER +#pragma warning(push) // Saves the current warning state. +#pragma warning(disable:4180) // Temporarily disables warning 4180. +#endif // _MSC_VER + + // Note: we deliberately don't call this PrintTo(), as that name + // conflicts with ::testing::internal::PrintTo in the body of the + // function. + static void Print(const T& value, ::std::ostream* os) { + // By default, ::testing::internal::PrintTo() is used for printing + // the value. + // + // Thanks to Koenig look-up, if T is a class and has its own + // PrintTo() function defined in its namespace, that function will + // be visible here. Since it is more specific than the generic ones + // in ::testing::internal, it will be picked by the compiler in the + // following statement - exactly what we want. + PrintTo(value, os); + } + +#ifdef _MSC_VER +#pragma warning(pop) // Restores the warning state. +#endif // _MSC_VER +}; + +// UniversalPrintArray(begin, len, os) prints an array of 'len' +// elements, starting at address 'begin'. +template +void UniversalPrintArray(const T* begin, size_t len, ::std::ostream* os) { + if (len == 0) { + *os << "{}"; + } else { + *os << "{ "; + const size_t kThreshold = 18; + const size_t kChunkSize = 8; + // If the array has more than kThreshold elements, we'll have to + // omit some details by printing only the first and the last + // kChunkSize elements. + // TODO(wan@google.com): let the user control the threshold using a flag. + if (len <= kThreshold) { + PrintRawArrayTo(begin, len, os); + } else { + PrintRawArrayTo(begin, kChunkSize, os); + *os << ", ..., "; + PrintRawArrayTo(begin + len - kChunkSize, kChunkSize, os); + } + *os << " }"; + } +} +// This overload prints a (const) char array compactly. +void UniversalPrintArray(const char* begin, size_t len, ::std::ostream* os); + +// Implements printing an array type T[N]. +template +class UniversalPrinter { + public: + // Prints the given array, omitting some elements when there are too + // many. + static void Print(const T (&a)[N], ::std::ostream* os) { + UniversalPrintArray(a, N, os); + } +}; + +// Implements printing a reference type T&. +template +class UniversalPrinter { + public: + // MSVC warns about adding const to a function type, so we want to + // disable the warning. +#ifdef _MSC_VER +#pragma warning(push) // Saves the current warning state. +#pragma warning(disable:4180) // Temporarily disables warning 4180. +#endif // _MSC_VER + + static void Print(const T& value, ::std::ostream* os) { + // Prints the address of the value. We use reinterpret_cast here + // as static_cast doesn't compile when T is a function type. + *os << "@" << reinterpret_cast(&value) << " "; + + // Then prints the value itself. + UniversalPrinter::Print(value, os); + } + +#ifdef _MSC_VER +#pragma warning(pop) // Restores the warning state. +#endif // _MSC_VER +}; + +// Prints a value tersely: for a reference type, the referenced value +// (but not the address) is printed; for a (const) char pointer, the +// NUL-terminated string (but not the pointer) is printed. +template +void UniversalTersePrint(const T& value, ::std::ostream* os) { + UniversalPrinter::Print(value, os); +} +inline void UniversalTersePrint(const char* str, ::std::ostream* os) { + if (str == NULL) { + *os << "NULL"; + } else { + UniversalPrinter::Print(string(str), os); + } +} +inline void UniversalTersePrint(char* str, ::std::ostream* os) { + UniversalTersePrint(static_cast(str), os); +} + +// Prints a value using the type inferred by the compiler. The +// difference between this and UniversalTersePrint() is that for a +// (const) char pointer, this prints both the pointer and the +// NUL-terminated string. +template +void UniversalPrint(const T& value, ::std::ostream* os) { + UniversalPrinter::Print(value, os); +} + +typedef ::std::vector Strings; + +// This helper template allows PrintTo() for tuples and +// UniversalTersePrintTupleFieldsToStrings() to be defined by +// induction on the number of tuple fields. The idea is that +// TuplePrefixPrinter::PrintPrefixTo(t, os) prints the first N +// fields in tuple t, and can be defined in terms of +// TuplePrefixPrinter. + +// The inductive case. +template +struct TuplePrefixPrinter { + // Prints the first N fields of a tuple. + template + static void PrintPrefixTo(const Tuple& t, ::std::ostream* os) { + TuplePrefixPrinter::PrintPrefixTo(t, os); + *os << ", "; + UniversalPrinter::type> + ::Print(::std::tr1::get(t), os); + } + + // Tersely prints the first N fields of a tuple to a string vector, + // one element for each field. + template + static void TersePrintPrefixToStrings(const Tuple& t, Strings* strings) { + TuplePrefixPrinter::TersePrintPrefixToStrings(t, strings); + ::std::stringstream ss; + UniversalTersePrint(::std::tr1::get(t), &ss); + strings->push_back(ss.str()); + } +}; + +// Base cases. +template <> +struct TuplePrefixPrinter<0> { + template + static void PrintPrefixTo(const Tuple&, ::std::ostream*) {} + + template + static void TersePrintPrefixToStrings(const Tuple&, Strings*) {} +}; +template <> +template +void TuplePrefixPrinter<1>::PrintPrefixTo(const Tuple& t, ::std::ostream* os) { + UniversalPrinter::type>:: + Print(::std::tr1::get<0>(t), os); +} + +// Helper function for printing a tuple. T must be instantiated with +// a tuple type. +template +void PrintTupleTo(const T& t, ::std::ostream* os) { + *os << "("; + TuplePrefixPrinter< ::std::tr1::tuple_size::value>:: + PrintPrefixTo(t, os); + *os << ")"; +} + +// Prints the fields of a tuple tersely to a string vector, one +// element for each field. See the comment before +// UniversalTersePrint() for how we define "tersely". +template +Strings UniversalTersePrintTupleFieldsToStrings(const Tuple& value) { + Strings result; + TuplePrefixPrinter< ::std::tr1::tuple_size::value>:: + TersePrintPrefixToStrings(value, &result); + return result; +} + +} // namespace internal + +template +::std::string PrintToString(const T& value) { + ::std::stringstream ss; + internal::UniversalTersePrint(value, &ss); + return ss.str(); +} + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_PRINTERS_H_ diff --git a/3rdparty/gmock/include/gmock/gmock-spec-builders.h b/3rdparty/gmock/include/gmock/gmock-spec-builders.h new file mode 100644 index 00000000..74a095da --- /dev/null +++ b/3rdparty/gmock/include/gmock/gmock-spec-builders.h @@ -0,0 +1,1853 @@ +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements the ON_CALL() and EXPECT_CALL() macros. +// +// A user can use the ON_CALL() macro to specify the default action of +// a mock method. The syntax is: +// +// ON_CALL(mock_object, Method(argument-matchers)) +// .With(multi-argument-matcher) +// .WillByDefault(action); +// +// where the .With() clause is optional. +// +// A user can use the EXPECT_CALL() macro to specify an expectation on +// a mock method. The syntax is: +// +// EXPECT_CALL(mock_object, Method(argument-matchers)) +// .With(multi-argument-matchers) +// .Times(cardinality) +// .InSequence(sequences) +// .After(expectations) +// .WillOnce(action) +// .WillRepeatedly(action) +// .RetiresOnSaturation(); +// +// where all clauses are optional, and .InSequence()/.After()/ +// .WillOnce() can appear any number of times. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace testing { + +// An abstract handle of an expectation. +class Expectation; + +// A set of expectation handles. +class ExpectationSet; + +// Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION +// and MUST NOT BE USED IN USER CODE!!! +namespace internal { + +// Implements a mock function. +template class FunctionMocker; + +// Base class for expectations. +class ExpectationBase; + +// Implements an expectation. +template class TypedExpectation; + +// Helper class for testing the Expectation class template. +class ExpectationTester; + +// Base class for function mockers. +template class FunctionMockerBase; + +// Protects the mock object registry (in class Mock), all function +// mockers, and all expectations. +// +// The reason we don't use more fine-grained protection is: when a +// mock function Foo() is called, it needs to consult its expectations +// to see which one should be picked. If another thread is allowed to +// call a mock function (either Foo() or a different one) at the same +// time, it could affect the "retired" attributes of Foo()'s +// expectations when InSequence() is used, and thus affect which +// expectation gets picked. Therefore, we sequence all mock function +// calls to ensure the integrity of the mock objects' states. +GTEST_DECLARE_STATIC_MUTEX_(g_gmock_mutex); + +// Abstract base class of FunctionMockerBase. This is the +// type-agnostic part of the function mocker interface. Its pure +// virtual methods are implemented by FunctionMockerBase. +class UntypedFunctionMockerBase { + public: + virtual ~UntypedFunctionMockerBase() {} + + // Verifies that all expectations on this mock function have been + // satisfied. Reports one or more Google Test non-fatal failures + // and returns false if not. + // L >= g_gmock_mutex + virtual bool VerifyAndClearExpectationsLocked() = 0; + + // Clears the ON_CALL()s set on this mock function. + // L >= g_gmock_mutex + virtual void ClearDefaultActionsLocked() = 0; +}; // class UntypedFunctionMockerBase + +// This template class implements a default action spec (i.e. an +// ON_CALL() statement). +template +class DefaultActionSpec { + public: + typedef typename Function::ArgumentTuple ArgumentTuple; + typedef typename Function::ArgumentMatcherTuple ArgumentMatcherTuple; + + // Constructs a DefaultActionSpec object from the information inside + // the parenthesis of an ON_CALL() statement. + DefaultActionSpec(const char* a_file, int a_line, + const ArgumentMatcherTuple& matchers) + : file_(a_file), + line_(a_line), + matchers_(matchers), + // By default, extra_matcher_ should match anything. However, + // we cannot initialize it with _ as that triggers a compiler + // bug in Symbian's C++ compiler (cannot decide between two + // overloaded constructors of Matcher). + extra_matcher_(A()), + last_clause_(kNone) { + } + + // Where in the source file was the default action spec defined? + const char* file() const { return file_; } + int line() const { return line_; } + + // Implements the .With() clause. + DefaultActionSpec& With(const Matcher& m) { + // Makes sure this is called at most once. + ExpectSpecProperty(last_clause_ < kWith, + ".With() cannot appear " + "more than once in an ON_CALL()."); + last_clause_ = kWith; + + extra_matcher_ = m; + return *this; + } + + // Implements the .WillByDefault() clause. + DefaultActionSpec& WillByDefault(const Action& action) { + ExpectSpecProperty(last_clause_ < kWillByDefault, + ".WillByDefault() must appear " + "exactly once in an ON_CALL()."); + last_clause_ = kWillByDefault; + + ExpectSpecProperty(!action.IsDoDefault(), + "DoDefault() cannot be used in ON_CALL()."); + action_ = action; + return *this; + } + + // Returns true iff the given arguments match the matchers. + bool Matches(const ArgumentTuple& args) const { + return TupleMatches(matchers_, args) && extra_matcher_.Matches(args); + } + + // Returns the action specified by the user. + const Action& GetAction() const { + AssertSpecProperty(last_clause_ == kWillByDefault, + ".WillByDefault() must appear exactly " + "once in an ON_CALL()."); + return action_; + } + + private: + // Gives each clause in the ON_CALL() statement a name. + enum Clause { + // Do not change the order of the enum members! The run-time + // syntax checking relies on it. + kNone, + kWith, + kWillByDefault, + }; + + // Asserts that the ON_CALL() statement has a certain property. + void AssertSpecProperty(bool property, const string& failure_message) const { + Assert(property, file_, line_, failure_message); + } + + // Expects that the ON_CALL() statement has a certain property. + void ExpectSpecProperty(bool property, const string& failure_message) const { + Expect(property, file_, line_, failure_message); + } + + // The information in statement + // + // ON_CALL(mock_object, Method(matchers)) + // .With(multi-argument-matcher) + // .WillByDefault(action); + // + // is recorded in the data members like this: + // + // source file that contains the statement => file_ + // line number of the statement => line_ + // matchers => matchers_ + // multi-argument-matcher => extra_matcher_ + // action => action_ + const char* file_; + int line_; + ArgumentMatcherTuple matchers_; + Matcher extra_matcher_; + Action action_; + + // The last clause in the ON_CALL() statement as seen so far. + // Initially kNone and changes as the statement is parsed. + Clause last_clause_; +}; // class DefaultActionSpec + +// Possible reactions on uninteresting calls. TODO(wan@google.com): +// rename the enum values to the kFoo style. +enum CallReaction { + ALLOW, + WARN, + FAIL, +}; + +} // namespace internal + +// Utilities for manipulating mock objects. +class Mock { + public: + // The following public methods can be called concurrently. + + // Tells Google Mock to ignore mock_obj when checking for leaked + // mock objects. + static void AllowLeak(const void* mock_obj); + + // Verifies and clears all expectations on the given mock object. + // If the expectations aren't satisfied, generates one or more + // Google Test non-fatal failures and returns false. + static bool VerifyAndClearExpectations(void* mock_obj); + + // Verifies all expectations on the given mock object and clears its + // default actions and expectations. Returns true iff the + // verification was successful. + static bool VerifyAndClear(void* mock_obj); + private: + // Needed for a function mocker to register itself (so that we know + // how to clear a mock object). + template + friend class internal::FunctionMockerBase; + + template + friend class NiceMock; + + template + friend class StrictMock; + + // Tells Google Mock to allow uninteresting calls on the given mock + // object. + // L < g_gmock_mutex + static void AllowUninterestingCalls(const void* mock_obj); + + // Tells Google Mock to warn the user about uninteresting calls on + // the given mock object. + // L < g_gmock_mutex + static void WarnUninterestingCalls(const void* mock_obj); + + // Tells Google Mock to fail uninteresting calls on the given mock + // object. + // L < g_gmock_mutex + static void FailUninterestingCalls(const void* mock_obj); + + // Tells Google Mock the given mock object is being destroyed and + // its entry in the call-reaction table should be removed. + // L < g_gmock_mutex + static void UnregisterCallReaction(const void* mock_obj); + + // Returns the reaction Google Mock will have on uninteresting calls + // made on the given mock object. + // L < g_gmock_mutex + static internal::CallReaction GetReactionOnUninterestingCalls( + const void* mock_obj); + + // Verifies that all expectations on the given mock object have been + // satisfied. Reports one or more Google Test non-fatal failures + // and returns false if not. + // L >= g_gmock_mutex + static bool VerifyAndClearExpectationsLocked(void* mock_obj); + + // Clears all ON_CALL()s set on the given mock object. + // L >= g_gmock_mutex + static void ClearDefaultActionsLocked(void* mock_obj); + + // Registers a mock object and a mock method it owns. + // L < g_gmock_mutex + static void Register(const void* mock_obj, + internal::UntypedFunctionMockerBase* mocker); + + // Tells Google Mock where in the source code mock_obj is used in an + // ON_CALL or EXPECT_CALL. In case mock_obj is leaked, this + // information helps the user identify which object it is. + // L < g_gmock_mutex + static void RegisterUseByOnCallOrExpectCall( + const void* mock_obj, const char* file, int line); + + // Unregisters a mock method; removes the owning mock object from + // the registry when the last mock method associated with it has + // been unregistered. This is called only in the destructor of + // FunctionMockerBase. + // L >= g_gmock_mutex + static void UnregisterLocked(internal::UntypedFunctionMockerBase* mocker); +}; // class Mock + +// An abstract handle of an expectation. Useful in the .After() +// clause of EXPECT_CALL() for setting the (partial) order of +// expectations. The syntax: +// +// Expectation e1 = EXPECT_CALL(...)...; +// EXPECT_CALL(...).After(e1)...; +// +// sets two expectations where the latter can only be matched after +// the former has been satisfied. +// +// Notes: +// - This class is copyable and has value semantics. +// - Constness is shallow: a const Expectation object itself cannot +// be modified, but the mutable methods of the ExpectationBase +// object it references can be called via expectation_base(). +// - The constructors and destructor are defined out-of-line because +// the Symbian WINSCW compiler wants to otherwise instantiate them +// when it sees this class definition, at which point it doesn't have +// ExpectationBase available yet, leading to incorrect destruction +// in the linked_ptr (or compilation errors if using a checking +// linked_ptr). +class Expectation { + public: + // Constructs a null object that doesn't reference any expectation. + Expectation(); + + ~Expectation(); + + // This single-argument ctor must not be explicit, in order to support the + // Expectation e = EXPECT_CALL(...); + // syntax. + // + // A TypedExpectation object stores its pre-requisites as + // Expectation objects, and needs to call the non-const Retire() + // method on the ExpectationBase objects they reference. Therefore + // Expectation must receive a *non-const* reference to the + // ExpectationBase object. + Expectation(internal::ExpectationBase& exp); // NOLINT + + // The compiler-generated copy ctor and operator= work exactly as + // intended, so we don't need to define our own. + + // Returns true iff rhs references the same expectation as this object does. + bool operator==(const Expectation& rhs) const { + return expectation_base_ == rhs.expectation_base_; + } + + bool operator!=(const Expectation& rhs) const { return !(*this == rhs); } + + private: + friend class ExpectationSet; + friend class Sequence; + friend class ::testing::internal::ExpectationBase; + + template + friend class ::testing::internal::FunctionMockerBase; + + template + friend class ::testing::internal::TypedExpectation; + + // This comparator is needed for putting Expectation objects into a set. + class Less { + public: + bool operator()(const Expectation& lhs, const Expectation& rhs) const { + return lhs.expectation_base_.get() < rhs.expectation_base_.get(); + } + }; + + typedef ::std::set Set; + + Expectation( + const internal::linked_ptr& expectation_base); + + // Returns the expectation this object references. + const internal::linked_ptr& + expectation_base() const { + return expectation_base_; + } + + // A linked_ptr that co-owns the expectation this handle references. + internal::linked_ptr expectation_base_; +}; + +// A set of expectation handles. Useful in the .After() clause of +// EXPECT_CALL() for setting the (partial) order of expectations. The +// syntax: +// +// ExpectationSet es; +// es += EXPECT_CALL(...)...; +// es += EXPECT_CALL(...)...; +// EXPECT_CALL(...).After(es)...; +// +// sets three expectations where the last one can only be matched +// after the first two have both been satisfied. +// +// This class is copyable and has value semantics. +class ExpectationSet { + public: + // A bidirectional iterator that can read a const element in the set. + typedef Expectation::Set::const_iterator const_iterator; + + // An object stored in the set. This is an alias of Expectation. + typedef Expectation::Set::value_type value_type; + + // Constructs an empty set. + ExpectationSet() {} + + // This single-argument ctor must not be explicit, in order to support the + // ExpectationSet es = EXPECT_CALL(...); + // syntax. + ExpectationSet(internal::ExpectationBase& exp) { // NOLINT + *this += Expectation(exp); + } + + // This single-argument ctor implements implicit conversion from + // Expectation and thus must not be explicit. This allows either an + // Expectation or an ExpectationSet to be used in .After(). + ExpectationSet(const Expectation& e) { // NOLINT + *this += e; + } + + // The compiler-generator ctor and operator= works exactly as + // intended, so we don't need to define our own. + + // Returns true iff rhs contains the same set of Expectation objects + // as this does. + bool operator==(const ExpectationSet& rhs) const { + return expectations_ == rhs.expectations_; + } + + bool operator!=(const ExpectationSet& rhs) const { return !(*this == rhs); } + + // Implements the syntax + // expectation_set += EXPECT_CALL(...); + ExpectationSet& operator+=(const Expectation& e) { + expectations_.insert(e); + return *this; + } + + int size() const { return static_cast(expectations_.size()); } + + const_iterator begin() const { return expectations_.begin(); } + const_iterator end() const { return expectations_.end(); } + + private: + Expectation::Set expectations_; +}; + + +// Sequence objects are used by a user to specify the relative order +// in which the expectations should match. They are copyable (we rely +// on the compiler-defined copy constructor and assignment operator). +class Sequence { + public: + // Constructs an empty sequence. + Sequence() : last_expectation_(new Expectation) {} + + // Adds an expectation to this sequence. The caller must ensure + // that no other thread is accessing this Sequence object. + void AddExpectation(const Expectation& expectation) const; + + private: + // The last expectation in this sequence. We use a linked_ptr here + // because Sequence objects are copyable and we want the copies to + // be aliases. The linked_ptr allows the copies to co-own and share + // the same Expectation object. + internal::linked_ptr last_expectation_; +}; // class Sequence + +// An object of this type causes all EXPECT_CALL() statements +// encountered in its scope to be put in an anonymous sequence. The +// work is done in the constructor and destructor. You should only +// create an InSequence object on the stack. +// +// The sole purpose for this class is to support easy definition of +// sequential expectations, e.g. +// +// { +// InSequence dummy; // The name of the object doesn't matter. +// +// // The following expectations must match in the order they appear. +// EXPECT_CALL(a, Bar())...; +// EXPECT_CALL(a, Baz())...; +// ... +// EXPECT_CALL(b, Xyz())...; +// } +// +// You can create InSequence objects in multiple threads, as long as +// they are used to affect different mock objects. The idea is that +// each thread can create and set up its own mocks as if it's the only +// thread. However, for clarity of your tests we recommend you to set +// up mocks in the main thread unless you have a good reason not to do +// so. +class InSequence { + public: + InSequence(); + ~InSequence(); + private: + bool sequence_created_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(InSequence); // NOLINT +} GMOCK_ATTRIBUTE_UNUSED_; + +namespace internal { + +// Points to the implicit sequence introduced by a living InSequence +// object (if any) in the current thread or NULL. +extern ThreadLocal g_gmock_implicit_sequence; + +// Base class for implementing expectations. +// +// There are two reasons for having a type-agnostic base class for +// Expectation: +// +// 1. We need to store collections of expectations of different +// types (e.g. all pre-requisites of a particular expectation, all +// expectations in a sequence). Therefore these expectation objects +// must share a common base class. +// +// 2. We can avoid binary code bloat by moving methods not depending +// on the template argument of Expectation to the base class. +// +// This class is internal and mustn't be used by user code directly. +class ExpectationBase { + public: + // source_text is the EXPECT_CALL(...) source that created this Expectation. + ExpectationBase(const char* file, int line, const string& source_text); + + virtual ~ExpectationBase(); + + // Where in the source file was the expectation spec defined? + const char* file() const { return file_; } + int line() const { return line_; } + const char* source_text() const { return source_text_.c_str(); } + // Returns the cardinality specified in the expectation spec. + const Cardinality& cardinality() const { return cardinality_; } + + // Describes the source file location of this expectation. + void DescribeLocationTo(::std::ostream* os) const { + *os << file() << ":" << line() << ": "; + } + + // Describes how many times a function call matching this + // expectation has occurred. + // L >= g_gmock_mutex + virtual void DescribeCallCountTo(::std::ostream* os) const = 0; + + protected: + friend class ::testing::Expectation; + + enum Clause { + // Don't change the order of the enum members! + kNone, + kWith, + kTimes, + kInSequence, + kAfter, + kWillOnce, + kWillRepeatedly, + kRetiresOnSaturation, + }; + + // Returns an Expectation object that references and co-owns this + // expectation. + virtual Expectation GetHandle() = 0; + + // Asserts that the EXPECT_CALL() statement has the given property. + void AssertSpecProperty(bool property, const string& failure_message) const { + Assert(property, file_, line_, failure_message); + } + + // Expects that the EXPECT_CALL() statement has the given property. + void ExpectSpecProperty(bool property, const string& failure_message) const { + Expect(property, file_, line_, failure_message); + } + + // Explicitly specifies the cardinality of this expectation. Used + // by the subclasses to implement the .Times() clause. + void SpecifyCardinality(const Cardinality& cardinality); + + // Returns true iff the user specified the cardinality explicitly + // using a .Times(). + bool cardinality_specified() const { return cardinality_specified_; } + + // Sets the cardinality of this expectation spec. + void set_cardinality(const Cardinality& a_cardinality) { + cardinality_ = a_cardinality; + } + + // The following group of methods should only be called after the + // EXPECT_CALL() statement, and only when g_gmock_mutex is held by + // the current thread. + + // Retires all pre-requisites of this expectation. + // L >= g_gmock_mutex + void RetireAllPreRequisites(); + + // Returns true iff this expectation is retired. + // L >= g_gmock_mutex + bool is_retired() const { + g_gmock_mutex.AssertHeld(); + return retired_; + } + + // Retires this expectation. + // L >= g_gmock_mutex + void Retire() { + g_gmock_mutex.AssertHeld(); + retired_ = true; + } + + // Returns true iff this expectation is satisfied. + // L >= g_gmock_mutex + bool IsSatisfied() const { + g_gmock_mutex.AssertHeld(); + return cardinality().IsSatisfiedByCallCount(call_count_); + } + + // Returns true iff this expectation is saturated. + // L >= g_gmock_mutex + bool IsSaturated() const { + g_gmock_mutex.AssertHeld(); + return cardinality().IsSaturatedByCallCount(call_count_); + } + + // Returns true iff this expectation is over-saturated. + // L >= g_gmock_mutex + bool IsOverSaturated() const { + g_gmock_mutex.AssertHeld(); + return cardinality().IsOverSaturatedByCallCount(call_count_); + } + + // Returns true iff all pre-requisites of this expectation are satisfied. + // L >= g_gmock_mutex + bool AllPrerequisitesAreSatisfied() const; + + // Adds unsatisfied pre-requisites of this expectation to 'result'. + // L >= g_gmock_mutex + void FindUnsatisfiedPrerequisites(ExpectationSet* result) const; + + // Returns the number this expectation has been invoked. + // L >= g_gmock_mutex + int call_count() const { + g_gmock_mutex.AssertHeld(); + return call_count_; + } + + // Increments the number this expectation has been invoked. + // L >= g_gmock_mutex + void IncrementCallCount() { + g_gmock_mutex.AssertHeld(); + call_count_++; + } + + private: + friend class ::testing::Sequence; + friend class ::testing::internal::ExpectationTester; + + template + friend class TypedExpectation; + + // This group of fields are part of the spec and won't change after + // an EXPECT_CALL() statement finishes. + const char* file_; // The file that contains the expectation. + int line_; // The line number of the expectation. + const string source_text_; // The EXPECT_CALL(...) source text. + // True iff the cardinality is specified explicitly. + bool cardinality_specified_; + Cardinality cardinality_; // The cardinality of the expectation. + // The immediate pre-requisites (i.e. expectations that must be + // satisfied before this expectation can be matched) of this + // expectation. We use linked_ptr in the set because we want an + // Expectation object to be co-owned by its FunctionMocker and its + // successors. This allows multiple mock objects to be deleted at + // different times. + ExpectationSet immediate_prerequisites_; + + // This group of fields are the current state of the expectation, + // and can change as the mock function is called. + int call_count_; // How many times this expectation has been invoked. + bool retired_; // True iff this expectation has retired. + + GTEST_DISALLOW_ASSIGN_(ExpectationBase); +}; // class ExpectationBase + +// Impements an expectation for the given function type. +template +class TypedExpectation : public ExpectationBase { + public: + typedef typename Function::ArgumentTuple ArgumentTuple; + typedef typename Function::ArgumentMatcherTuple ArgumentMatcherTuple; + typedef typename Function::Result Result; + + TypedExpectation(FunctionMockerBase* owner, + const char* a_file, int a_line, const string& a_source_text, + const ArgumentMatcherTuple& m) + : ExpectationBase(a_file, a_line, a_source_text), + owner_(owner), + matchers_(m), + extra_matcher_specified_(false), + // By default, extra_matcher_ should match anything. However, + // we cannot initialize it with _ as that triggers a compiler + // bug in Symbian's C++ compiler (cannot decide between two + // overloaded constructors of Matcher). + extra_matcher_(A()), + repeated_action_specified_(false), + repeated_action_(DoDefault()), + retires_on_saturation_(false), + last_clause_(kNone), + action_count_checked_(false) {} + + virtual ~TypedExpectation() { + // Check the validity of the action count if it hasn't been done + // yet (for example, if the expectation was never used). + CheckActionCountIfNotDone(); + } + + // Implements the .With() clause. + TypedExpectation& With(const Matcher& m) { + if (last_clause_ == kWith) { + ExpectSpecProperty(false, + ".With() cannot appear " + "more than once in an EXPECT_CALL()."); + } else { + ExpectSpecProperty(last_clause_ < kWith, + ".With() must be the first " + "clause in an EXPECT_CALL()."); + } + last_clause_ = kWith; + + extra_matcher_ = m; + extra_matcher_specified_ = true; + return *this; + } + + // Implements the .Times() clause. + TypedExpectation& Times(const Cardinality& a_cardinality) { + if (last_clause_ ==kTimes) { + ExpectSpecProperty(false, + ".Times() cannot appear " + "more than once in an EXPECT_CALL()."); + } else { + ExpectSpecProperty(last_clause_ < kTimes, + ".Times() cannot appear after " + ".InSequence(), .WillOnce(), .WillRepeatedly(), " + "or .RetiresOnSaturation()."); + } + last_clause_ = kTimes; + + ExpectationBase::SpecifyCardinality(a_cardinality); + return *this; + } + + // Implements the .Times() clause. + TypedExpectation& Times(int n) { + return Times(Exactly(n)); + } + + // Implements the .InSequence() clause. + TypedExpectation& InSequence(const Sequence& s) { + ExpectSpecProperty(last_clause_ <= kInSequence, + ".InSequence() cannot appear after .After()," + " .WillOnce(), .WillRepeatedly(), or " + ".RetiresOnSaturation()."); + last_clause_ = kInSequence; + + s.AddExpectation(GetHandle()); + return *this; + } + TypedExpectation& InSequence(const Sequence& s1, const Sequence& s2) { + return InSequence(s1).InSequence(s2); + } + TypedExpectation& InSequence(const Sequence& s1, const Sequence& s2, + const Sequence& s3) { + return InSequence(s1, s2).InSequence(s3); + } + TypedExpectation& InSequence(const Sequence& s1, const Sequence& s2, + const Sequence& s3, const Sequence& s4) { + return InSequence(s1, s2, s3).InSequence(s4); + } + TypedExpectation& InSequence(const Sequence& s1, const Sequence& s2, + const Sequence& s3, const Sequence& s4, + const Sequence& s5) { + return InSequence(s1, s2, s3, s4).InSequence(s5); + } + + // Implements that .After() clause. + TypedExpectation& After(const ExpectationSet& s) { + ExpectSpecProperty(last_clause_ <= kAfter, + ".After() cannot appear after .WillOnce()," + " .WillRepeatedly(), or " + ".RetiresOnSaturation()."); + last_clause_ = kAfter; + + for (ExpectationSet::const_iterator it = s.begin(); it != s.end(); ++it) { + immediate_prerequisites_ += *it; + } + return *this; + } + TypedExpectation& After(const ExpectationSet& s1, const ExpectationSet& s2) { + return After(s1).After(s2); + } + TypedExpectation& After(const ExpectationSet& s1, const ExpectationSet& s2, + const ExpectationSet& s3) { + return After(s1, s2).After(s3); + } + TypedExpectation& After(const ExpectationSet& s1, const ExpectationSet& s2, + const ExpectationSet& s3, const ExpectationSet& s4) { + return After(s1, s2, s3).After(s4); + } + TypedExpectation& After(const ExpectationSet& s1, const ExpectationSet& s2, + const ExpectationSet& s3, const ExpectationSet& s4, + const ExpectationSet& s5) { + return After(s1, s2, s3, s4).After(s5); + } + + // Implements the .WillOnce() clause. + TypedExpectation& WillOnce(const Action& action) { + ExpectSpecProperty(last_clause_ <= kWillOnce, + ".WillOnce() cannot appear after " + ".WillRepeatedly() or .RetiresOnSaturation()."); + last_clause_ = kWillOnce; + + actions_.push_back(action); + if (!cardinality_specified()) { + set_cardinality(Exactly(static_cast(actions_.size()))); + } + return *this; + } + + // Implements the .WillRepeatedly() clause. + TypedExpectation& WillRepeatedly(const Action& action) { + if (last_clause_ == kWillRepeatedly) { + ExpectSpecProperty(false, + ".WillRepeatedly() cannot appear " + "more than once in an EXPECT_CALL()."); + } else { + ExpectSpecProperty(last_clause_ < kWillRepeatedly, + ".WillRepeatedly() cannot appear " + "after .RetiresOnSaturation()."); + } + last_clause_ = kWillRepeatedly; + repeated_action_specified_ = true; + + repeated_action_ = action; + if (!cardinality_specified()) { + set_cardinality(AtLeast(static_cast(actions_.size()))); + } + + // Now that no more action clauses can be specified, we check + // whether their count makes sense. + CheckActionCountIfNotDone(); + return *this; + } + + // Implements the .RetiresOnSaturation() clause. + TypedExpectation& RetiresOnSaturation() { + ExpectSpecProperty(last_clause_ < kRetiresOnSaturation, + ".RetiresOnSaturation() cannot appear " + "more than once."); + last_clause_ = kRetiresOnSaturation; + retires_on_saturation_ = true; + + // Now that no more action clauses can be specified, we check + // whether their count makes sense. + CheckActionCountIfNotDone(); + return *this; + } + + // Returns the matchers for the arguments as specified inside the + // EXPECT_CALL() macro. + const ArgumentMatcherTuple& matchers() const { + return matchers_; + } + + // Returns the matcher specified by the .With() clause. + const Matcher& extra_matcher() const { + return extra_matcher_; + } + + // Returns the sequence of actions specified by the .WillOnce() clause. + const std::vector >& actions() const { return actions_; } + + // Returns the action specified by the .WillRepeatedly() clause. + const Action& repeated_action() const { return repeated_action_; } + + // Returns true iff the .RetiresOnSaturation() clause was specified. + bool retires_on_saturation() const { return retires_on_saturation_; } + + // Describes how many times a function call matching this + // expectation has occurred (implements + // ExpectationBase::DescribeCallCountTo()). + // L >= g_gmock_mutex + virtual void DescribeCallCountTo(::std::ostream* os) const { + g_gmock_mutex.AssertHeld(); + + // Describes how many times the function is expected to be called. + *os << " Expected: to be "; + cardinality().DescribeTo(os); + *os << "\n Actual: "; + Cardinality::DescribeActualCallCountTo(call_count(), os); + + // Describes the state of the expectation (e.g. is it satisfied? + // is it active?). + *os << " - " << (IsOverSaturated() ? "over-saturated" : + IsSaturated() ? "saturated" : + IsSatisfied() ? "satisfied" : "unsatisfied") + << " and " + << (is_retired() ? "retired" : "active"); + } + + void MaybeDescribeExtraMatcherTo(::std::ostream* os) { + if (extra_matcher_specified_) { + *os << " Expected args: "; + extra_matcher_.DescribeTo(os); + *os << "\n"; + } + } + + private: + template + friend class FunctionMockerBase; + + // Returns an Expectation object that references and co-owns this + // expectation. + virtual Expectation GetHandle() { + return owner_->GetHandleOf(this); + } + + // The following methods will be called only after the EXPECT_CALL() + // statement finishes and when the current thread holds + // g_gmock_mutex. + + // Returns true iff this expectation matches the given arguments. + // L >= g_gmock_mutex + bool Matches(const ArgumentTuple& args) const { + g_gmock_mutex.AssertHeld(); + return TupleMatches(matchers_, args) && extra_matcher_.Matches(args); + } + + // Returns true iff this expectation should handle the given arguments. + // L >= g_gmock_mutex + bool ShouldHandleArguments(const ArgumentTuple& args) const { + g_gmock_mutex.AssertHeld(); + + // In case the action count wasn't checked when the expectation + // was defined (e.g. if this expectation has no WillRepeatedly() + // or RetiresOnSaturation() clause), we check it when the + // expectation is used for the first time. + CheckActionCountIfNotDone(); + return !is_retired() && AllPrerequisitesAreSatisfied() && Matches(args); + } + + // Describes the result of matching the arguments against this + // expectation to the given ostream. + // L >= g_gmock_mutex + void ExplainMatchResultTo(const ArgumentTuple& args, + ::std::ostream* os) const { + g_gmock_mutex.AssertHeld(); + + if (is_retired()) { + *os << " Expected: the expectation is active\n" + << " Actual: it is retired\n"; + } else if (!Matches(args)) { + if (!TupleMatches(matchers_, args)) { + ExplainMatchFailureTupleTo(matchers_, args, os); + } + StringMatchResultListener listener; + if (!extra_matcher_.MatchAndExplain(args, &listener)) { + *os << " Expected args: "; + extra_matcher_.DescribeTo(os); + *os << "\n Actual: don't match"; + + internal::PrintIfNotEmpty(listener.str(), os); + *os << "\n"; + } + } else if (!AllPrerequisitesAreSatisfied()) { + *os << " Expected: all pre-requisites are satisfied\n" + << " Actual: the following immediate pre-requisites " + << "are not satisfied:\n"; + ExpectationSet unsatisfied_prereqs; + FindUnsatisfiedPrerequisites(&unsatisfied_prereqs); + int i = 0; + for (ExpectationSet::const_iterator it = unsatisfied_prereqs.begin(); + it != unsatisfied_prereqs.end(); ++it) { + it->expectation_base()->DescribeLocationTo(os); + *os << "pre-requisite #" << i++ << "\n"; + } + *os << " (end of pre-requisites)\n"; + } else { + // This line is here just for completeness' sake. It will never + // be executed as currently the ExplainMatchResultTo() function + // is called only when the mock function call does NOT match the + // expectation. + *os << "The call matches the expectation.\n"; + } + } + + // Returns the action that should be taken for the current invocation. + // L >= g_gmock_mutex + const Action& GetCurrentAction(const FunctionMockerBase* mocker, + const ArgumentTuple& args) const { + g_gmock_mutex.AssertHeld(); + const int count = call_count(); + Assert(count >= 1, __FILE__, __LINE__, + "call_count() is <= 0 when GetCurrentAction() is " + "called - this should never happen."); + + const int action_count = static_cast(actions().size()); + if (action_count > 0 && !repeated_action_specified_ && + count > action_count) { + // If there is at least one WillOnce() and no WillRepeatedly(), + // we warn the user when the WillOnce() clauses ran out. + ::std::stringstream ss; + DescribeLocationTo(&ss); + ss << "Actions ran out in " << source_text() << "...\n" + << "Called " << count << " times, but only " + << action_count << " WillOnce()" + << (action_count == 1 ? " is" : "s are") << " specified - "; + mocker->DescribeDefaultActionTo(args, &ss); + Log(WARNING, ss.str(), 1); + } + + return count <= action_count ? actions()[count - 1] : repeated_action(); + } + + // Given the arguments of a mock function call, if the call will + // over-saturate this expectation, returns the default action; + // otherwise, returns the next action in this expectation. Also + // describes *what* happened to 'what', and explains *why* Google + // Mock does it to 'why'. This method is not const as it calls + // IncrementCallCount(). + // L >= g_gmock_mutex + Action GetActionForArguments(const FunctionMockerBase* mocker, + const ArgumentTuple& args, + ::std::ostream* what, + ::std::ostream* why) { + g_gmock_mutex.AssertHeld(); + if (IsSaturated()) { + // We have an excessive call. + IncrementCallCount(); + *what << "Mock function called more times than expected - "; + mocker->DescribeDefaultActionTo(args, what); + DescribeCallCountTo(why); + + // TODO(wan): allow the user to control whether unexpected calls + // should fail immediately or continue using a flag + // --gmock_unexpected_calls_are_fatal. + return DoDefault(); + } + + IncrementCallCount(); + RetireAllPreRequisites(); + + if (retires_on_saturation() && IsSaturated()) { + Retire(); + } + + // Must be done after IncrementCount()! + *what << "Mock function call matches " << source_text() <<"...\n"; + return GetCurrentAction(mocker, args); + } + + // Checks the action count (i.e. the number of WillOnce() and + // WillRepeatedly() clauses) against the cardinality if this hasn't + // been done before. Prints a warning if there are too many or too + // few actions. + // L < mutex_ + void CheckActionCountIfNotDone() const { + bool should_check = false; + { + MutexLock l(&mutex_); + if (!action_count_checked_) { + action_count_checked_ = true; + should_check = true; + } + } + + if (should_check) { + if (!cardinality_specified_) { + // The cardinality was inferred - no need to check the action + // count against it. + return; + } + + // The cardinality was explicitly specified. + const int action_count = static_cast(actions_.size()); + const int upper_bound = cardinality().ConservativeUpperBound(); + const int lower_bound = cardinality().ConservativeLowerBound(); + bool too_many; // True if there are too many actions, or false + // if there are too few. + if (action_count > upper_bound || + (action_count == upper_bound && repeated_action_specified_)) { + too_many = true; + } else if (0 < action_count && action_count < lower_bound && + !repeated_action_specified_) { + too_many = false; + } else { + return; + } + + ::std::stringstream ss; + DescribeLocationTo(&ss); + ss << "Too " << (too_many ? "many" : "few") + << " actions specified in " << source_text() << "...\n" + << "Expected to be "; + cardinality().DescribeTo(&ss); + ss << ", but has " << (too_many ? "" : "only ") + << action_count << " WillOnce()" + << (action_count == 1 ? "" : "s"); + if (repeated_action_specified_) { + ss << " and a WillRepeatedly()"; + } + ss << "."; + Log(WARNING, ss.str(), -1); // -1 means "don't print stack trace". + } + } + + // All the fields below won't change once the EXPECT_CALL() + // statement finishes. + FunctionMockerBase* const owner_; + ArgumentMatcherTuple matchers_; + bool extra_matcher_specified_; + Matcher extra_matcher_; + std::vector > actions_; + bool repeated_action_specified_; // True if a WillRepeatedly() was specified. + Action repeated_action_; + bool retires_on_saturation_; + Clause last_clause_; + mutable bool action_count_checked_; // Under mutex_. + mutable Mutex mutex_; // Protects action_count_checked_. + + GTEST_DISALLOW_COPY_AND_ASSIGN_(TypedExpectation); +}; // class TypedExpectation + +// A MockSpec object is used by ON_CALL() or EXPECT_CALL() for +// specifying the default behavior of, or expectation on, a mock +// function. + +// Note: class MockSpec really belongs to the ::testing namespace. +// However if we define it in ::testing, MSVC will complain when +// classes in ::testing::internal declare it as a friend class +// template. To workaround this compiler bug, we define MockSpec in +// ::testing::internal and import it into ::testing. + +template +class MockSpec { + public: + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + typedef typename internal::Function::ArgumentMatcherTuple + ArgumentMatcherTuple; + + // Constructs a MockSpec object, given the function mocker object + // that the spec is associated with. + explicit MockSpec(internal::FunctionMockerBase* function_mocker) + : function_mocker_(function_mocker) {} + + // Adds a new default action spec to the function mocker and returns + // the newly created spec. + internal::DefaultActionSpec& InternalDefaultActionSetAt( + const char* file, int line, const char* obj, const char* call) { + LogWithLocation(internal::INFO, file, line, + string("ON_CALL(") + obj + ", " + call + ") invoked"); + return function_mocker_->AddNewDefaultActionSpec(file, line, matchers_); + } + + // Adds a new expectation spec to the function mocker and returns + // the newly created spec. + internal::TypedExpectation& InternalExpectedAt( + const char* file, int line, const char* obj, const char* call) { + const string source_text(string("EXPECT_CALL(") + obj + ", " + call + ")"); + LogWithLocation(internal::INFO, file, line, source_text + " invoked"); + return function_mocker_->AddNewExpectation( + file, line, source_text, matchers_); + } + + private: + template + friend class internal::FunctionMocker; + + void SetMatchers(const ArgumentMatcherTuple& matchers) { + matchers_ = matchers; + } + + // Logs a message including file and line number information. + void LogWithLocation(testing::internal::LogSeverity severity, + const char* file, int line, + const string& message) { + ::std::ostringstream s; + s << file << ":" << line << ": " << message << ::std::endl; + Log(severity, s.str(), 0); + } + + // The function mocker that owns this spec. + internal::FunctionMockerBase* const function_mocker_; + // The argument matchers specified in the spec. + ArgumentMatcherTuple matchers_; + + GTEST_DISALLOW_ASSIGN_(MockSpec); +}; // class MockSpec + +// MSVC warns about using 'this' in base member initializer list, so +// we need to temporarily disable the warning. We have to do it for +// the entire class to suppress the warning, even though it's about +// the constructor only. + +#ifdef _MSC_VER +#pragma warning(push) // Saves the current warning state. +#pragma warning(disable:4355) // Temporarily disables warning 4355. +#endif // _MSV_VER + +// C++ treats the void type specially. For example, you cannot define +// a void-typed variable or pass a void value to a function. +// ActionResultHolder holds a value of type T, where T must be a +// copyable type or void (T doesn't need to be default-constructable). +// It hides the syntactic difference between void and other types, and +// is used to unify the code for invoking both void-returning and +// non-void-returning mock functions. This generic definition is used +// when T is not void. +template +class ActionResultHolder { + public: + explicit ActionResultHolder(T a_value) : value_(a_value) {} + + // The compiler-generated copy constructor and assignment operator + // are exactly what we need, so we don't need to define them. + + T value() const { return value_; } + + // Prints the held value as an action's result to os. + void PrintAsActionResult(::std::ostream* os) const { + *os << "\n Returns: "; + UniversalPrinter::Print(value_, os); + } + + // Performs the given mock function's default action and returns the + // result in a ActionResultHolder. + template + static ActionResultHolder PerformDefaultAction( + const FunctionMockerBase* func_mocker, + const Arguments& args, + const string& call_description) { + return ActionResultHolder( + func_mocker->PerformDefaultAction(args, call_description)); + } + + // Performs the given action and returns the result in a + // ActionResultHolder. + template + static ActionResultHolder PerformAction(const Action& action, + const Arguments& args) { + return ActionResultHolder(action.Perform(args)); + } + + private: + T value_; + + // T could be a reference type, so = isn't supported. + GTEST_DISALLOW_ASSIGN_(ActionResultHolder); +}; + +// Specialization for T = void. +template <> +class ActionResultHolder { + public: + ActionResultHolder() {} + void value() const {} + void PrintAsActionResult(::std::ostream* /* os */) const {} + + template + static ActionResultHolder PerformDefaultAction( + const FunctionMockerBase* func_mocker, + const Arguments& args, + const string& call_description) { + func_mocker->PerformDefaultAction(args, call_description); + return ActionResultHolder(); + } + + template + static ActionResultHolder PerformAction(const Action& action, + const Arguments& args) { + action.Perform(args); + return ActionResultHolder(); + } +}; + +// The base of the function mocker class for the given function type. +// We put the methods in this class instead of its child to avoid code +// bloat. +template +class FunctionMockerBase : public UntypedFunctionMockerBase { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + typedef typename Function::ArgumentMatcherTuple ArgumentMatcherTuple; + + FunctionMockerBase() : mock_obj_(NULL), name_(""), current_spec_(this) {} + + // The destructor verifies that all expectations on this mock + // function have been satisfied. If not, it will report Google Test + // non-fatal failures for the violations. + // L < g_gmock_mutex + virtual ~FunctionMockerBase() { + MutexLock l(&g_gmock_mutex); + VerifyAndClearExpectationsLocked(); + Mock::UnregisterLocked(this); + } + + // Returns the ON_CALL spec that matches this mock function with the + // given arguments; returns NULL if no matching ON_CALL is found. + // L = * + const DefaultActionSpec* FindDefaultActionSpec( + const ArgumentTuple& args) const { + for (typename std::vector >::const_reverse_iterator it + = default_actions_.rbegin(); + it != default_actions_.rend(); ++it) { + const DefaultActionSpec& spec = *it; + if (spec.Matches(args)) + return &spec; + } + + return NULL; + } + + // Performs the default action of this mock function on the given arguments + // and returns the result. Asserts with a helpful call descrption if there is + // no valid return value. This method doesn't depend on the mutable state of + // this object, and thus can be called concurrently without locking. + // L = * + Result PerformDefaultAction(const ArgumentTuple& args, + const string& call_description) const { + const DefaultActionSpec* const spec = FindDefaultActionSpec(args); + if (spec != NULL) { + return spec->GetAction().Perform(args); + } + Assert(DefaultValue::Exists(), "", -1, + call_description + "\n The mock function has no default action " + "set, and its return type has no default value set."); + return DefaultValue::Get(); + } + + // Registers this function mocker and the mock object owning it; + // returns a reference to the function mocker object. This is only + // called by the ON_CALL() and EXPECT_CALL() macros. + // L < g_gmock_mutex + FunctionMocker& RegisterOwner(const void* mock_obj) { + { + MutexLock l(&g_gmock_mutex); + mock_obj_ = mock_obj; + } + Mock::Register(mock_obj, this); + return *::testing::internal::down_cast*>(this); + } + + // The following two functions are from UntypedFunctionMockerBase. + + // Verifies that all expectations on this mock function have been + // satisfied. Reports one or more Google Test non-fatal failures + // and returns false if not. + // L >= g_gmock_mutex + virtual bool VerifyAndClearExpectationsLocked(); + + // Clears the ON_CALL()s set on this mock function. + // L >= g_gmock_mutex + virtual void ClearDefaultActionsLocked() { + g_gmock_mutex.AssertHeld(); + default_actions_.clear(); + } + + // Sets the name of the function being mocked. Will be called upon + // each invocation of this mock function. + // L < g_gmock_mutex + void SetOwnerAndName(const void* mock_obj, const char* name) { + // We protect name_ under g_gmock_mutex in case this mock function + // is called from two threads concurrently. + MutexLock l(&g_gmock_mutex); + mock_obj_ = mock_obj; + name_ = name; + } + + // Returns the address of the mock object this method belongs to. + // Must be called after SetOwnerAndName() has been called. + // L < g_gmock_mutex + const void* MockObject() const { + const void* mock_obj; + { + // We protect mock_obj_ under g_gmock_mutex in case this mock + // function is called from two threads concurrently. + MutexLock l(&g_gmock_mutex); + mock_obj = mock_obj_; + } + return mock_obj; + } + + // Returns the name of the function being mocked. Must be called + // after SetOwnerAndName() has been called. + // L < g_gmock_mutex + const char* Name() const { + const char* name; + { + // We protect name_ under g_gmock_mutex in case this mock + // function is called from two threads concurrently. + MutexLock l(&g_gmock_mutex); + name = name_; + } + return name; + } + + protected: + template + friend class MockSpec; + + // Returns the result of invoking this mock function with the given + // arguments. This function can be safely called from multiple + // threads concurrently. + // L < g_gmock_mutex + Result InvokeWith(const ArgumentTuple& args); + + // Adds and returns a default action spec for this mock function. + // L < g_gmock_mutex + DefaultActionSpec& AddNewDefaultActionSpec( + const char* file, int line, + const ArgumentMatcherTuple& m) { + Mock::RegisterUseByOnCallOrExpectCall(MockObject(), file, line); + default_actions_.push_back(DefaultActionSpec(file, line, m)); + return default_actions_.back(); + } + + // Adds and returns an expectation spec for this mock function. + // L < g_gmock_mutex + TypedExpectation& AddNewExpectation( + const char* file, + int line, + const string& source_text, + const ArgumentMatcherTuple& m) { + Mock::RegisterUseByOnCallOrExpectCall(MockObject(), file, line); + const linked_ptr > expectation( + new TypedExpectation(this, file, line, source_text, m)); + expectations_.push_back(expectation); + + // Adds this expectation into the implicit sequence if there is one. + Sequence* const implicit_sequence = g_gmock_implicit_sequence.get(); + if (implicit_sequence != NULL) { + implicit_sequence->AddExpectation(Expectation(expectation)); + } + + return *expectation; + } + + // The current spec (either default action spec or expectation spec) + // being described on this function mocker. + MockSpec& current_spec() { return current_spec_; } + + private: + template friend class TypedExpectation; + + typedef std::vector > > + TypedExpectations; + + // Returns an Expectation object that references and co-owns exp, + // which must be an expectation on this mock function. + Expectation GetHandleOf(TypedExpectation* exp) { + for (typename TypedExpectations::const_iterator it = expectations_.begin(); + it != expectations_.end(); ++it) { + if (it->get() == exp) { + return Expectation(*it); + } + } + + Assert(false, __FILE__, __LINE__, "Cannot find expectation."); + return Expectation(); + // The above statement is just to make the code compile, and will + // never be executed. + } + + // Some utilities needed for implementing InvokeWith(). + + // Describes what default action will be performed for the given + // arguments. + // L = * + void DescribeDefaultActionTo(const ArgumentTuple& args, + ::std::ostream* os) const { + const DefaultActionSpec* const spec = FindDefaultActionSpec(args); + + if (spec == NULL) { + *os << (internal::type_equals::value ? + "returning directly.\n" : + "returning default value.\n"); + } else { + *os << "taking default action specified at:\n" + << spec->file() << ":" << spec->line() << ":\n"; + } + } + + // Writes a message that the call is uninteresting (i.e. neither + // explicitly expected nor explicitly unexpected) to the given + // ostream. + // L < g_gmock_mutex + void DescribeUninterestingCall(const ArgumentTuple& args, + ::std::ostream* os) const { + *os << "Uninteresting mock function call - "; + DescribeDefaultActionTo(args, os); + *os << " Function call: " << Name(); + UniversalPrinter::Print(args, os); + } + + // Critical section: We must find the matching expectation and the + // corresponding action that needs to be taken in an ATOMIC + // transaction. Otherwise another thread may call this mock + // method in the middle and mess up the state. + // + // However, performing the action has to be left out of the critical + // section. The reason is that we have no control on what the + // action does (it can invoke an arbitrary user function or even a + // mock function) and excessive locking could cause a dead lock. + // L < g_gmock_mutex + bool FindMatchingExpectationAndAction( + const ArgumentTuple& args, TypedExpectation** exp, Action* action, + bool* is_excessive, ::std::ostream* what, ::std::ostream* why) { + MutexLock l(&g_gmock_mutex); + *exp = this->FindMatchingExpectationLocked(args); + if (*exp == NULL) { // A match wasn't found. + *action = DoDefault(); + this->FormatUnexpectedCallMessageLocked(args, what, why); + return false; + } + + // This line must be done before calling GetActionForArguments(), + // which will increment the call count for *exp and thus affect + // its saturation status. + *is_excessive = (*exp)->IsSaturated(); + *action = (*exp)->GetActionForArguments(this, args, what, why); + return true; + } + + // Returns the expectation that matches the arguments, or NULL if no + // expectation matches them. + // L >= g_gmock_mutex + TypedExpectation* FindMatchingExpectationLocked( + const ArgumentTuple& args) const { + g_gmock_mutex.AssertHeld(); + for (typename TypedExpectations::const_reverse_iterator it = + expectations_.rbegin(); + it != expectations_.rend(); ++it) { + TypedExpectation* const exp = it->get(); + if (exp->ShouldHandleArguments(args)) { + return exp; + } + } + return NULL; + } + + // Returns a message that the arguments don't match any expectation. + // L >= g_gmock_mutex + void FormatUnexpectedCallMessageLocked(const ArgumentTuple& args, + ::std::ostream* os, + ::std::ostream* why) const { + g_gmock_mutex.AssertHeld(); + *os << "\nUnexpected mock function call - "; + DescribeDefaultActionTo(args, os); + PrintTriedExpectationsLocked(args, why); + } + + // Prints a list of expectations that have been tried against the + // current mock function call. + // L >= g_gmock_mutex + void PrintTriedExpectationsLocked(const ArgumentTuple& args, + ::std::ostream* why) const { + g_gmock_mutex.AssertHeld(); + const int count = static_cast(expectations_.size()); + *why << "Google Mock tried the following " << count << " " + << (count == 1 ? "expectation, but it didn't match" : + "expectations, but none matched") + << ":\n"; + for (int i = 0; i < count; i++) { + *why << "\n"; + expectations_[i]->DescribeLocationTo(why); + if (count > 1) { + *why << "tried expectation #" << i << ": "; + } + *why << expectations_[i]->source_text() << "...\n"; + expectations_[i]->ExplainMatchResultTo(args, why); + expectations_[i]->DescribeCallCountTo(why); + } + } + + // Address of the mock object this mock method belongs to. Only + // valid after this mock method has been called or + // ON_CALL/EXPECT_CALL has been invoked on it. + const void* mock_obj_; // Protected by g_gmock_mutex. + + // Name of the function being mocked. Only valid after this mock + // method has been called. + const char* name_; // Protected by g_gmock_mutex. + + // The current spec (either default action spec or expectation spec) + // being described on this function mocker. + MockSpec current_spec_; + + // All default action specs for this function mocker. + std::vector > default_actions_; + // All expectations for this function mocker. + TypedExpectations expectations_; + + // There is no generally useful and implementable semantics of + // copying a mock object, so copying a mock is usually a user error. + // Thus we disallow copying function mockers. If the user really + // wants to copy a mock object, he should implement his own copy + // operation, for example: + // + // class MockFoo : public Foo { + // public: + // // Defines a copy constructor explicitly. + // MockFoo(const MockFoo& src) {} + // ... + // }; + GTEST_DISALLOW_COPY_AND_ASSIGN_(FunctionMockerBase); +}; // class FunctionMockerBase + +#ifdef _MSC_VER +#pragma warning(pop) // Restores the warning state. +#endif // _MSV_VER + +// Implements methods of FunctionMockerBase. + +// Verifies that all expectations on this mock function have been +// satisfied. Reports one or more Google Test non-fatal failures and +// returns false if not. +// L >= g_gmock_mutex +template +bool FunctionMockerBase::VerifyAndClearExpectationsLocked() { + g_gmock_mutex.AssertHeld(); + bool expectations_met = true; + for (typename TypedExpectations::const_iterator it = expectations_.begin(); + it != expectations_.end(); ++it) { + TypedExpectation* const exp = it->get(); + + if (exp->IsOverSaturated()) { + // There was an upper-bound violation. Since the error was + // already reported when it occurred, there is no need to do + // anything here. + expectations_met = false; + } else if (!exp->IsSatisfied()) { + expectations_met = false; + ::std::stringstream ss; + ss << "Actual function call count doesn't match " + << exp->source_text() << "...\n"; + // No need to show the source file location of the expectation + // in the description, as the Expect() call that follows already + // takes care of it. + exp->MaybeDescribeExtraMatcherTo(&ss); + exp->DescribeCallCountTo(&ss); + Expect(false, exp->file(), exp->line(), ss.str()); + } + } + expectations_.clear(); + return expectations_met; +} + +// Reports an uninteresting call (whose description is in msg) in the +// manner specified by 'reaction'. +void ReportUninterestingCall(CallReaction reaction, const string& msg); + +// Calculates the result of invoking this mock function with the given +// arguments, prints it, and returns it. +// L < g_gmock_mutex +template +typename Function::Result FunctionMockerBase::InvokeWith( + const typename Function::ArgumentTuple& args) { + typedef ActionResultHolder ResultHolder; + + if (expectations_.size() == 0) { + // No expectation is set on this mock method - we have an + // uninteresting call. + + // We must get Google Mock's reaction on uninteresting calls + // made on this mock object BEFORE performing the action, + // because the action may DELETE the mock object and make the + // following expression meaningless. + const CallReaction reaction = + Mock::GetReactionOnUninterestingCalls(MockObject()); + + // True iff we need to print this call's arguments and return + // value. This definition must be kept in sync with + // the behavior of ReportUninterestingCall(). + const bool need_to_report_uninteresting_call = + // If the user allows this uninteresting call, we print it + // only when he wants informational messages. + reaction == ALLOW ? LogIsVisible(INFO) : + // If the user wants this to be a warning, we print it only + // when he wants to see warnings. + reaction == WARN ? LogIsVisible(WARNING) : + // Otherwise, the user wants this to be an error, and we + // should always print detailed information in the error. + true; + + if (!need_to_report_uninteresting_call) { + // Perform the action without printing the call information. + return PerformDefaultAction(args, ""); + } + + // Warns about the uninteresting call. + ::std::stringstream ss; + DescribeUninterestingCall(args, &ss); + + // Calculates the function result. + const ResultHolder result = + ResultHolder::PerformDefaultAction(this, args, ss.str()); + + // Prints the function result. + result.PrintAsActionResult(&ss); + + ReportUninterestingCall(reaction, ss.str()); + return result.value(); + } + + bool is_excessive = false; + ::std::stringstream ss; + ::std::stringstream why; + ::std::stringstream loc; + Action action; + TypedExpectation* exp; + + // The FindMatchingExpectationAndAction() function acquires and + // releases g_gmock_mutex. + const bool found = FindMatchingExpectationAndAction( + args, &exp, &action, &is_excessive, &ss, &why); + + // True iff we need to print the call's arguments and return value. + // This definition must be kept in sync with the uses of Expect() + // and Log() in this function. + const bool need_to_report_call = !found || is_excessive || LogIsVisible(INFO); + if (!need_to_report_call) { + // Perform the action without printing the call information. + return action.IsDoDefault() ? PerformDefaultAction(args, "") : + action.Perform(args); + } + + ss << " Function call: " << Name(); + UniversalPrinter::Print(args, &ss); + + // In case the action deletes a piece of the expectation, we + // generate the message beforehand. + if (found && !is_excessive) { + exp->DescribeLocationTo(&loc); + } + + const ResultHolder result = action.IsDoDefault() ? + ResultHolder::PerformDefaultAction(this, args, ss.str()) : + ResultHolder::PerformAction(action, args); + result.PrintAsActionResult(&ss); + ss << "\n" << why.str(); + + if (!found) { + // No expectation matches this call - reports a failure. + Expect(false, NULL, -1, ss.str()); + } else if (is_excessive) { + // We had an upper-bound violation and the failure message is in ss. + Expect(false, exp->file(), exp->line(), ss.str()); + } else { + // We had an expected call and the matching expectation is + // described in ss. + Log(INFO, loc.str() + ss.str(), 2); + } + return result.value(); +} + +} // namespace internal + +// The style guide prohibits "using" statements in a namespace scope +// inside a header file. However, the MockSpec class template is +// meant to be defined in the ::testing namespace. The following line +// is just a trick for working around a bug in MSVC 8.0, which cannot +// handle it if we define MockSpec in ::testing. +using internal::MockSpec; + +// Const(x) is a convenient function for obtaining a const reference +// to x. This is useful for setting expectations on an overloaded +// const mock method, e.g. +// +// class MockFoo : public FooInterface { +// public: +// MOCK_METHOD0(Bar, int()); +// MOCK_CONST_METHOD0(Bar, int&()); +// }; +// +// MockFoo foo; +// // Expects a call to non-const MockFoo::Bar(). +// EXPECT_CALL(foo, Bar()); +// // Expects a call to const MockFoo::Bar(). +// EXPECT_CALL(Const(foo), Bar()); +template +inline const T& Const(const T& x) { return x; } + +// Constructs an Expectation object that references and co-owns exp. +inline Expectation::Expectation(internal::ExpectationBase& exp) // NOLINT + : expectation_base_(exp.GetHandle().expectation_base()) {} + +} // namespace testing + +// A separate macro is required to avoid compile errors when the name +// of the method used in call is a result of macro expansion. +// See CompilesWithMethodNameExpandedFromMacro tests in +// internal/gmock-spec-builders_test.cc for more details. +#define GMOCK_ON_CALL_IMPL_(obj, call) \ + ((obj).gmock_##call).InternalDefaultActionSetAt(__FILE__, __LINE__, \ + #obj, #call) +#define ON_CALL(obj, call) GMOCK_ON_CALL_IMPL_(obj, call) + +#define GMOCK_EXPECT_CALL_IMPL_(obj, call) \ + ((obj).gmock_##call).InternalExpectedAt(__FILE__, __LINE__, #obj, #call) +#define EXPECT_CALL(obj, call) GMOCK_EXPECT_CALL_IMPL_(obj, call) + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_ diff --git a/3rdparty/gmock/include/gmock/gmock.h b/3rdparty/gmock/include/gmock/gmock.h new file mode 100644 index 00000000..daf52884 --- /dev/null +++ b/3rdparty/gmock/include/gmock/gmock.h @@ -0,0 +1,94 @@ +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This is the main header file a user should include. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_H_ + +// This file implements the following syntax: +// +// ON_CALL(mock_object.Method(...)) +// .With(...) ? +// .WillByDefault(...); +// +// where With() is optional and WillByDefault() must appear exactly +// once. +// +// EXPECT_CALL(mock_object.Method(...)) +// .With(...) ? +// .Times(...) ? +// .InSequence(...) * +// .WillOnce(...) * +// .WillRepeatedly(...) ? +// .RetiresOnSaturation() ? ; +// +// where all clauses are optional and WillOnce() can be repeated. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace testing { + +// Declares Google Mock flags that we want a user to use programmatically. +GMOCK_DECLARE_bool_(catch_leaked_mocks); +GMOCK_DECLARE_string_(verbose); + +// Initializes Google Mock. This must be called before running the +// tests. In particular, it parses the command line for the flags +// that Google Mock recognizes. Whenever a Google Mock flag is seen, +// it is removed from argv, and *argc is decremented. +// +// No value is returned. Instead, the Google Mock flag variables are +// updated. +// +// Since Google Test is needed for Google Mock to work, this function +// also initializes Google Test and parses its flags, if that hasn't +// been done. +void InitGoogleMock(int* argc, char** argv); + +// This overloaded version can be used in Windows programs compiled in +// UNICODE mode. +void InitGoogleMock(int* argc, wchar_t** argv); + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_H_ diff --git a/3rdparty/gmock/include/gmock/internal/gmock-generated-internal-utils.h b/3rdparty/gmock/include/gmock/internal/gmock-generated-internal-utils.h new file mode 100644 index 00000000..6386b05a --- /dev/null +++ b/3rdparty/gmock/include/gmock/internal/gmock-generated-internal-utils.h @@ -0,0 +1,277 @@ +// This file was GENERATED by a script. DO NOT EDIT BY HAND!!! + +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file contains template meta-programming utility classes needed +// for implementing Google Mock. + +#ifndef GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_GENERATED_INTERNAL_UTILS_H_ +#define GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_GENERATED_INTERNAL_UTILS_H_ + +#include + +namespace testing { + +template +class Matcher; + +namespace internal { + +// An IgnoredValue object can be implicitly constructed from ANY value. +// This is used in implementing the IgnoreResult(a) action. +class IgnoredValue { + public: + // This constructor template allows any value to be implicitly + // converted to IgnoredValue. The object has no data member and + // doesn't try to remember anything about the argument. We + // deliberately omit the 'explicit' keyword in order to allow the + // conversion to be implicit. + template + IgnoredValue(const T&) {} +}; + +// MatcherTuple::type is a tuple type where each field is a Matcher +// for the corresponding field in tuple type T. +template +struct MatcherTuple; + +template <> +struct MatcherTuple< ::std::tr1::tuple<> > { + typedef ::std::tr1::tuple< > type; +}; + +template +struct MatcherTuple< ::std::tr1::tuple > { + typedef ::std::tr1::tuple > type; +}; + +template +struct MatcherTuple< ::std::tr1::tuple > { + typedef ::std::tr1::tuple, Matcher > type; +}; + +template +struct MatcherTuple< ::std::tr1::tuple > { + typedef ::std::tr1::tuple, Matcher, Matcher > type; +}; + +template +struct MatcherTuple< ::std::tr1::tuple > { + typedef ::std::tr1::tuple, Matcher, Matcher, + Matcher > type; +}; + +template +struct MatcherTuple< ::std::tr1::tuple > { + typedef ::std::tr1::tuple, Matcher, Matcher, Matcher, + Matcher > type; +}; + +template +struct MatcherTuple< ::std::tr1::tuple > { + typedef ::std::tr1::tuple, Matcher, Matcher, Matcher, + Matcher, Matcher > type; +}; + +template +struct MatcherTuple< ::std::tr1::tuple > { + typedef ::std::tr1::tuple, Matcher, Matcher, Matcher, + Matcher, Matcher, Matcher > type; +}; + +template +struct MatcherTuple< ::std::tr1::tuple > { + typedef ::std::tr1::tuple, Matcher, Matcher, Matcher, + Matcher, Matcher, Matcher, Matcher > type; +}; + +template +struct MatcherTuple< ::std::tr1::tuple > { + typedef ::std::tr1::tuple, Matcher, Matcher, Matcher, + Matcher, Matcher, Matcher, Matcher, Matcher > type; +}; + +template +struct MatcherTuple< ::std::tr1::tuple > { + typedef ::std::tr1::tuple, Matcher, Matcher, Matcher, + Matcher, Matcher, Matcher, Matcher, Matcher, + Matcher > type; +}; + +// Template struct Function, where F must be a function type, contains +// the following typedefs: +// +// Result: the function's return type. +// ArgumentN: the type of the N-th argument, where N starts with 1. +// ArgumentTuple: the tuple type consisting of all parameters of F. +// ArgumentMatcherTuple: the tuple type consisting of Matchers for all +// parameters of F. +// MakeResultVoid: the function type obtained by substituting void +// for the return type of F. +// MakeResultIgnoredValue: +// the function type obtained by substituting Something +// for the return type of F. +template +struct Function; + +template +struct Function { + typedef R Result; + typedef ::std::tr1::tuple<> ArgumentTuple; + typedef typename MatcherTuple::type ArgumentMatcherTuple; + typedef void MakeResultVoid(); + typedef IgnoredValue MakeResultIgnoredValue(); +}; + +template +struct Function + : Function { + typedef A1 Argument1; + typedef ::std::tr1::tuple ArgumentTuple; + typedef typename MatcherTuple::type ArgumentMatcherTuple; + typedef void MakeResultVoid(A1); + typedef IgnoredValue MakeResultIgnoredValue(A1); +}; + +template +struct Function + : Function { + typedef A2 Argument2; + typedef ::std::tr1::tuple ArgumentTuple; + typedef typename MatcherTuple::type ArgumentMatcherTuple; + typedef void MakeResultVoid(A1, A2); + typedef IgnoredValue MakeResultIgnoredValue(A1, A2); +}; + +template +struct Function + : Function { + typedef A3 Argument3; + typedef ::std::tr1::tuple ArgumentTuple; + typedef typename MatcherTuple::type ArgumentMatcherTuple; + typedef void MakeResultVoid(A1, A2, A3); + typedef IgnoredValue MakeResultIgnoredValue(A1, A2, A3); +}; + +template +struct Function + : Function { + typedef A4 Argument4; + typedef ::std::tr1::tuple ArgumentTuple; + typedef typename MatcherTuple::type ArgumentMatcherTuple; + typedef void MakeResultVoid(A1, A2, A3, A4); + typedef IgnoredValue MakeResultIgnoredValue(A1, A2, A3, A4); +}; + +template +struct Function + : Function { + typedef A5 Argument5; + typedef ::std::tr1::tuple ArgumentTuple; + typedef typename MatcherTuple::type ArgumentMatcherTuple; + typedef void MakeResultVoid(A1, A2, A3, A4, A5); + typedef IgnoredValue MakeResultIgnoredValue(A1, A2, A3, A4, A5); +}; + +template +struct Function + : Function { + typedef A6 Argument6; + typedef ::std::tr1::tuple ArgumentTuple; + typedef typename MatcherTuple::type ArgumentMatcherTuple; + typedef void MakeResultVoid(A1, A2, A3, A4, A5, A6); + typedef IgnoredValue MakeResultIgnoredValue(A1, A2, A3, A4, A5, A6); +}; + +template +struct Function + : Function { + typedef A7 Argument7; + typedef ::std::tr1::tuple ArgumentTuple; + typedef typename MatcherTuple::type ArgumentMatcherTuple; + typedef void MakeResultVoid(A1, A2, A3, A4, A5, A6, A7); + typedef IgnoredValue MakeResultIgnoredValue(A1, A2, A3, A4, A5, A6, A7); +}; + +template +struct Function + : Function { + typedef A8 Argument8; + typedef ::std::tr1::tuple ArgumentTuple; + typedef typename MatcherTuple::type ArgumentMatcherTuple; + typedef void MakeResultVoid(A1, A2, A3, A4, A5, A6, A7, A8); + typedef IgnoredValue MakeResultIgnoredValue(A1, A2, A3, A4, A5, A6, A7, A8); +}; + +template +struct Function + : Function { + typedef A9 Argument9; + typedef ::std::tr1::tuple ArgumentTuple; + typedef typename MatcherTuple::type ArgumentMatcherTuple; + typedef void MakeResultVoid(A1, A2, A3, A4, A5, A6, A7, A8, A9); + typedef IgnoredValue MakeResultIgnoredValue(A1, A2, A3, A4, A5, A6, A7, A8, + A9); +}; + +template +struct Function + : Function { + typedef A10 Argument10; + typedef ::std::tr1::tuple ArgumentTuple; + typedef typename MatcherTuple::type ArgumentMatcherTuple; + typedef void MakeResultVoid(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10); + typedef IgnoredValue MakeResultIgnoredValue(A1, A2, A3, A4, A5, A6, A7, A8, + A9, A10); +}; + +} // namespace internal + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_GENERATED_INTERNAL_UTILS_H_ diff --git a/3rdparty/gmock/include/gmock/internal/gmock-generated-internal-utils.h.pump b/3rdparty/gmock/include/gmock/internal/gmock-generated-internal-utils.h.pump new file mode 100644 index 00000000..f3128b04 --- /dev/null +++ b/3rdparty/gmock/include/gmock/internal/gmock-generated-internal-utils.h.pump @@ -0,0 +1,136 @@ +$$ -*- mode: c++; -*- +$$ This is a Pump source file. Please use Pump to convert it to +$$ gmock-generated-function-mockers.h. +$$ +$var n = 10 $$ The maximum arity we support. +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file contains template meta-programming utility classes needed +// for implementing Google Mock. + +#ifndef GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_GENERATED_INTERNAL_UTILS_H_ +#define GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_GENERATED_INTERNAL_UTILS_H_ + +#include + +namespace testing { + +template +class Matcher; + +namespace internal { + +// An IgnoredValue object can be implicitly constructed from ANY value. +// This is used in implementing the IgnoreResult(a) action. +class IgnoredValue { + public: + // This constructor template allows any value to be implicitly + // converted to IgnoredValue. The object has no data member and + // doesn't try to remember anything about the argument. We + // deliberately omit the 'explicit' keyword in order to allow the + // conversion to be implicit. + template + IgnoredValue(const T&) {} +}; + +// MatcherTuple::type is a tuple type where each field is a Matcher +// for the corresponding field in tuple type T. +template +struct MatcherTuple; + + +$range i 0..n +$for i [[ +$range j 1..i +$var typename_As = [[$for j, [[typename A$j]]]] +$var As = [[$for j, [[A$j]]]] +$var matcher_As = [[$for j, [[Matcher]]]] +template <$typename_As> +struct MatcherTuple< ::std::tr1::tuple<$As> > { + typedef ::std::tr1::tuple<$matcher_As > type; +}; + + +]] +// Template struct Function, where F must be a function type, contains +// the following typedefs: +// +// Result: the function's return type. +// ArgumentN: the type of the N-th argument, where N starts with 1. +// ArgumentTuple: the tuple type consisting of all parameters of F. +// ArgumentMatcherTuple: the tuple type consisting of Matchers for all +// parameters of F. +// MakeResultVoid: the function type obtained by substituting void +// for the return type of F. +// MakeResultIgnoredValue: +// the function type obtained by substituting Something +// for the return type of F. +template +struct Function; + +template +struct Function { + typedef R Result; + typedef ::std::tr1::tuple<> ArgumentTuple; + typedef typename MatcherTuple::type ArgumentMatcherTuple; + typedef void MakeResultVoid(); + typedef IgnoredValue MakeResultIgnoredValue(); +}; + + +$range i 1..n +$for i [[ +$range j 1..i +$var typename_As = [[$for j [[, typename A$j]]]] +$var As = [[$for j, [[A$j]]]] +$var matcher_As = [[$for j, [[Matcher]]]] +$range k 1..i-1 +$var prev_As = [[$for k, [[A$k]]]] +template +struct Function + : Function { + typedef A$i Argument$i; + typedef ::std::tr1::tuple<$As> ArgumentTuple; + typedef typename MatcherTuple::type ArgumentMatcherTuple; + typedef void MakeResultVoid($As); + typedef IgnoredValue MakeResultIgnoredValue($As); +}; + + +]] +} // namespace internal + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_GENERATED_INTERNAL_UTILS_H_ diff --git a/3rdparty/gmock/include/gmock/internal/gmock-internal-utils.h b/3rdparty/gmock/include/gmock/internal/gmock-internal-utils.h new file mode 100644 index 00000000..0c33fdd0 --- /dev/null +++ b/3rdparty/gmock/include/gmock/internal/gmock-internal-utils.h @@ -0,0 +1,763 @@ +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file defines some utilities useful for implementing Google +// Mock. They are subject to change without notice, so please DO NOT +// USE THEM IN USER CODE. + +#ifndef GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_INTERNAL_UTILS_H_ +#define GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_INTERNAL_UTILS_H_ + +#include +#include // NOLINT +#include + +#include +#include +#include + +// Concatenates two pre-processor symbols; works for concatenating +// built-in macros like __FILE__ and __LINE__. +#define GMOCK_CONCAT_TOKEN_IMPL_(foo, bar) foo##bar +#define GMOCK_CONCAT_TOKEN_(foo, bar) GMOCK_CONCAT_TOKEN_IMPL_(foo, bar) + +#ifdef __GNUC__ +#define GMOCK_ATTRIBUTE_UNUSED_ __attribute__ ((unused)) +#else +#define GMOCK_ATTRIBUTE_UNUSED_ +#endif // __GNUC__ + +class ProtocolMessage; +namespace proto2 { class Message; } + +namespace testing { +namespace internal { + +// Converts an identifier name to a space-separated list of lower-case +// words. Each maximum substring of the form [A-Za-z][a-z]*|\d+ is +// treated as one word. For example, both "FooBar123" and +// "foo_bar_123" are converted to "foo bar 123". +string ConvertIdentifierNameToWords(const char* id_name); + +// Defining a variable of type CompileAssertTypesEqual will cause a +// compiler error iff T1 and T2 are different types. +template +struct CompileAssertTypesEqual; + +template +struct CompileAssertTypesEqual { +}; + +// Removes the reference from a type if it is a reference type, +// otherwise leaves it unchanged. This is the same as +// tr1::remove_reference, which is not widely available yet. +template +struct RemoveReference { typedef T type; }; // NOLINT +template +struct RemoveReference { typedef T type; }; // NOLINT + +// A handy wrapper around RemoveReference that works when the argument +// T depends on template parameters. +#define GMOCK_REMOVE_REFERENCE_(T) \ + typename ::testing::internal::RemoveReference::type + +// Removes const from a type if it is a const type, otherwise leaves +// it unchanged. This is the same as tr1::remove_const, which is not +// widely available yet. +template +struct RemoveConst { typedef T type; }; // NOLINT +template +struct RemoveConst { typedef T type; }; // NOLINT + +// MSVC 8.0 has a bug which causes the above definition to fail to +// remove the const in 'const int[3]'. The following specialization +// works around the bug. However, it causes trouble with gcc and thus +// needs to be conditionally compiled. +#ifdef _MSC_VER +template +struct RemoveConst { + typedef typename RemoveConst::type type[N]; +}; +#endif // _MSC_VER + +// A handy wrapper around RemoveConst that works when the argument +// T depends on template parameters. +#define GMOCK_REMOVE_CONST_(T) \ + typename ::testing::internal::RemoveConst::type + +// Adds reference to a type if it is not a reference type, +// otherwise leaves it unchanged. This is the same as +// tr1::add_reference, which is not widely available yet. +template +struct AddReference { typedef T& type; }; // NOLINT +template +struct AddReference { typedef T& type; }; // NOLINT + +// A handy wrapper around AddReference that works when the argument T +// depends on template parameters. +#define GMOCK_ADD_REFERENCE_(T) \ + typename ::testing::internal::AddReference::type + +// Adds a reference to const on top of T as necessary. For example, +// it transforms +// +// char ==> const char& +// const char ==> const char& +// char& ==> const char& +// const char& ==> const char& +// +// The argument T must depend on some template parameters. +#define GMOCK_REFERENCE_TO_CONST_(T) \ + GMOCK_ADD_REFERENCE_(const GMOCK_REMOVE_REFERENCE_(T)) + +// PointeeOf::type is the type of a value pointed to by a +// Pointer, which can be either a smart pointer or a raw pointer. The +// following default implementation is for the case where Pointer is a +// smart pointer. +template +struct PointeeOf { + // Smart pointer classes define type element_type as the type of + // their pointees. + typedef typename Pointer::element_type type; +}; +// This specialization is for the raw pointer case. +template +struct PointeeOf { typedef T type; }; // NOLINT + +// GetRawPointer(p) returns the raw pointer underlying p when p is a +// smart pointer, or returns p itself when p is already a raw pointer. +// The following default implementation is for the smart pointer case. +template +inline typename Pointer::element_type* GetRawPointer(const Pointer& p) { + return p.get(); +} +// This overloaded version is for the raw pointer case. +template +inline Element* GetRawPointer(Element* p) { return p; } + +// This comparator allows linked_ptr to be stored in sets. +template +struct LinkedPtrLessThan { + bool operator()(const ::testing::internal::linked_ptr& lhs, + const ::testing::internal::linked_ptr& rhs) const { + return lhs.get() < rhs.get(); + } +}; + +// ImplicitlyConvertible::value is a compile-time bool +// constant that's true iff type From can be implicitly converted to +// type To. +template +class ImplicitlyConvertible { + private: + // We need the following helper functions only for their types. + // They have no implementations. + + // MakeFrom() is an expression whose type is From. We cannot simply + // use From(), as the type From may not have a public default + // constructor. + static From MakeFrom(); + + // These two functions are overloaded. Given an expression + // Helper(x), the compiler will pick the first version if x can be + // implicitly converted to type To; otherwise it will pick the + // second version. + // + // The first version returns a value of size 1, and the second + // version returns a value of size 2. Therefore, by checking the + // size of Helper(x), which can be done at compile time, we can tell + // which version of Helper() is used, and hence whether x can be + // implicitly converted to type To. + static char Helper(To); + static char (&Helper(...))[2]; // NOLINT + + // We have to put the 'public' section after the 'private' section, + // or MSVC refuses to compile the code. + public: + // MSVC warns about implicitly converting from double to int for + // possible loss of data, so we need to temporarily disable the + // warning. +#ifdef _MSC_VER +#pragma warning(push) // Saves the current warning state. +#pragma warning(disable:4244) // Temporarily disables warning 4244. + static const bool value = + sizeof(Helper(ImplicitlyConvertible::MakeFrom())) == 1; +#pragma warning(pop) // Restores the warning state. +#else + static const bool value = + sizeof(Helper(ImplicitlyConvertible::MakeFrom())) == 1; +#endif // _MSV_VER +}; +template +const bool ImplicitlyConvertible::value; + +// Symbian compilation can be done with wchar_t being either a native +// type or a typedef. Using Google Mock with OpenC without wchar_t +// should require the definition of _STLP_NO_WCHAR_T. +// +// MSVC treats wchar_t as a native type usually, but treats it as the +// same as unsigned short when the compiler option /Zc:wchar_t- is +// specified. It defines _NATIVE_WCHAR_T_DEFINED symbol when wchar_t +// is a native type. +#if (GTEST_OS_SYMBIAN && defined(_STLP_NO_WCHAR_T)) || \ + (defined(_MSC_VER) && !defined(_NATIVE_WCHAR_T_DEFINED)) +// wchar_t is a typedef. +#else +#define GMOCK_WCHAR_T_IS_NATIVE_ 1 +#endif + +// signed wchar_t and unsigned wchar_t are NOT in the C++ standard. +// Using them is a bad practice and not portable. So DON'T use them. +// +// Still, Google Mock is designed to work even if the user uses signed +// wchar_t or unsigned wchar_t (obviously, assuming the compiler +// supports them). +// +// To gcc, +// wchar_t == signed wchar_t != unsigned wchar_t == unsigned int +#ifdef __GNUC__ +#define GMOCK_HAS_SIGNED_WCHAR_T_ 1 // signed/unsigned wchar_t are valid types. +#endif + +// In what follows, we use the term "kind" to indicate whether a type +// is bool, an integer type (excluding bool), a floating-point type, +// or none of them. This categorization is useful for determining +// when a matcher argument type can be safely converted to another +// type in the implementation of SafeMatcherCast. +enum TypeKind { + kBool, kInteger, kFloatingPoint, kOther +}; + +// KindOf::value is the kind of type T. +template struct KindOf { + enum { value = kOther }; // The default kind. +}; + +// This macro declares that the kind of 'type' is 'kind'. +#define GMOCK_DECLARE_KIND_(type, kind) \ + template <> struct KindOf { enum { value = kind }; } + +GMOCK_DECLARE_KIND_(bool, kBool); + +// All standard integer types. +GMOCK_DECLARE_KIND_(char, kInteger); +GMOCK_DECLARE_KIND_(signed char, kInteger); +GMOCK_DECLARE_KIND_(unsigned char, kInteger); +GMOCK_DECLARE_KIND_(short, kInteger); // NOLINT +GMOCK_DECLARE_KIND_(unsigned short, kInteger); // NOLINT +GMOCK_DECLARE_KIND_(int, kInteger); +GMOCK_DECLARE_KIND_(unsigned int, kInteger); +GMOCK_DECLARE_KIND_(long, kInteger); // NOLINT +GMOCK_DECLARE_KIND_(unsigned long, kInteger); // NOLINT + +#if GMOCK_WCHAR_T_IS_NATIVE_ +GMOCK_DECLARE_KIND_(wchar_t, kInteger); +#endif + +// Non-standard integer types. +GMOCK_DECLARE_KIND_(Int64, kInteger); +GMOCK_DECLARE_KIND_(UInt64, kInteger); + +// All standard floating-point types. +GMOCK_DECLARE_KIND_(float, kFloatingPoint); +GMOCK_DECLARE_KIND_(double, kFloatingPoint); +GMOCK_DECLARE_KIND_(long double, kFloatingPoint); + +#undef GMOCK_DECLARE_KIND_ + +// Evaluates to the kind of 'type'. +#define GMOCK_KIND_OF_(type) \ + static_cast< ::testing::internal::TypeKind>( \ + ::testing::internal::KindOf::value) + +// Evaluates to true iff integer type T is signed. +#define GMOCK_IS_SIGNED_(T) (static_cast(-1) < 0) + +// LosslessArithmeticConvertibleImpl::value +// is true iff arithmetic type From can be losslessly converted to +// arithmetic type To. +// +// It's the user's responsibility to ensure that both From and To are +// raw (i.e. has no CV modifier, is not a pointer, and is not a +// reference) built-in arithmetic types, kFromKind is the kind of +// From, and kToKind is the kind of To; the value is +// implementation-defined when the above pre-condition is violated. +template +struct LosslessArithmeticConvertibleImpl : public false_type {}; + +// Converting bool to bool is lossless. +template <> +struct LosslessArithmeticConvertibleImpl + : public true_type {}; // NOLINT + +// Converting bool to any integer type is lossless. +template +struct LosslessArithmeticConvertibleImpl + : public true_type {}; // NOLINT + +// Converting bool to any floating-point type is lossless. +template +struct LosslessArithmeticConvertibleImpl + : public true_type {}; // NOLINT + +// Converting an integer to bool is lossy. +template +struct LosslessArithmeticConvertibleImpl + : public false_type {}; // NOLINT + +// Converting an integer to another non-bool integer is lossless iff +// the target type's range encloses the source type's range. +template +struct LosslessArithmeticConvertibleImpl + : public bool_constant< + // When converting from a smaller size to a larger size, we are + // fine as long as we are not converting from signed to unsigned. + ((sizeof(From) < sizeof(To)) && + (!GMOCK_IS_SIGNED_(From) || GMOCK_IS_SIGNED_(To))) || + // When converting between the same size, the signedness must match. + ((sizeof(From) == sizeof(To)) && + (GMOCK_IS_SIGNED_(From) == GMOCK_IS_SIGNED_(To)))> {}; // NOLINT + +#undef GMOCK_IS_SIGNED_ + +// Converting an integer to a floating-point type may be lossy, since +// the format of a floating-point number is implementation-defined. +template +struct LosslessArithmeticConvertibleImpl + : public false_type {}; // NOLINT + +// Converting a floating-point to bool is lossy. +template +struct LosslessArithmeticConvertibleImpl + : public false_type {}; // NOLINT + +// Converting a floating-point to an integer is lossy. +template +struct LosslessArithmeticConvertibleImpl + : public false_type {}; // NOLINT + +// Converting a floating-point to another floating-point is lossless +// iff the target type is at least as big as the source type. +template +struct LosslessArithmeticConvertibleImpl< + kFloatingPoint, From, kFloatingPoint, To> + : public bool_constant {}; // NOLINT + +// LosslessArithmeticConvertible::value is true iff arithmetic +// type From can be losslessly converted to arithmetic type To. +// +// It's the user's responsibility to ensure that both From and To are +// raw (i.e. has no CV modifier, is not a pointer, and is not a +// reference) built-in arithmetic types; the value is +// implementation-defined when the above pre-condition is violated. +template +struct LosslessArithmeticConvertible + : public LosslessArithmeticConvertibleImpl< + GMOCK_KIND_OF_(From), From, GMOCK_KIND_OF_(To), To> {}; // NOLINT + +// IsAProtocolMessage::value is a compile-time bool constant that's +// true iff T is type ProtocolMessage, proto2::Message, or a subclass +// of those. +template +struct IsAProtocolMessage + : public bool_constant< + ImplicitlyConvertible::value || + ImplicitlyConvertible::value> { +}; + +// When the compiler sees expression IsContainerTest(0), the first +// overload of IsContainerTest will be picked if C is an STL-style +// container class (since C::const_iterator* is a valid type and 0 can +// be converted to it), while the second overload will be picked +// otherwise (since C::const_iterator will be an invalid type in this +// case). Therefore, we can determine whether C is a container class +// by checking the type of IsContainerTest(0). The value of the +// expression is insignificant. +typedef int IsContainer; +template +IsContainer IsContainerTest(typename C::const_iterator*) { return 0; } + +typedef char IsNotContainer; +template +IsNotContainer IsContainerTest(...) { return '\0'; } + +// This interface knows how to report a Google Mock failure (either +// non-fatal or fatal). +class FailureReporterInterface { + public: + // The type of a failure (either non-fatal or fatal). + enum FailureType { + NONFATAL, FATAL + }; + + virtual ~FailureReporterInterface() {} + + // Reports a failure that occurred at the given source file location. + virtual void ReportFailure(FailureType type, const char* file, int line, + const string& message) = 0; +}; + +// Returns the failure reporter used by Google Mock. +FailureReporterInterface* GetFailureReporter(); + +// Asserts that condition is true; aborts the process with the given +// message if condition is false. We cannot use LOG(FATAL) or CHECK() +// as Google Mock might be used to mock the log sink itself. We +// inline this function to prevent it from showing up in the stack +// trace. +inline void Assert(bool condition, const char* file, int line, + const string& msg) { + if (!condition) { + GetFailureReporter()->ReportFailure(FailureReporterInterface::FATAL, + file, line, msg); + } +} +inline void Assert(bool condition, const char* file, int line) { + Assert(condition, file, line, "Assertion failed."); +} + +// Verifies that condition is true; generates a non-fatal failure if +// condition is false. +inline void Expect(bool condition, const char* file, int line, + const string& msg) { + if (!condition) { + GetFailureReporter()->ReportFailure(FailureReporterInterface::NONFATAL, + file, line, msg); + } +} +inline void Expect(bool condition, const char* file, int line) { + Expect(condition, file, line, "Expectation failed."); +} + +// Severity level of a log. +enum LogSeverity { + INFO = 0, + WARNING = 1, +}; + +// Valid values for the --gmock_verbose flag. + +// All logs (informational and warnings) are printed. +const char kInfoVerbosity[] = "info"; +// Only warnings are printed. +const char kWarningVerbosity[] = "warning"; +// No logs are printed. +const char kErrorVerbosity[] = "error"; + +// Returns true iff a log with the given severity is visible according +// to the --gmock_verbose flag. +bool LogIsVisible(LogSeverity severity); + +// Prints the given message to stdout iff 'severity' >= the level +// specified by the --gmock_verbose flag. If stack_frames_to_skip >= +// 0, also prints the stack trace excluding the top +// stack_frames_to_skip frames. In opt mode, any positive +// stack_frames_to_skip is treated as 0, since we don't know which +// function calls will be inlined by the compiler and need to be +// conservative. +void Log(LogSeverity severity, const string& message, int stack_frames_to_skip); + +// TODO(wan@google.com): group all type utilities together. + +// Type traits. + +// is_reference::value is non-zero iff T is a reference type. +template struct is_reference : public false_type {}; +template struct is_reference : public true_type {}; + +// type_equals::value is non-zero iff T1 and T2 are the same type. +template struct type_equals : public false_type {}; +template struct type_equals : public true_type {}; + +// remove_reference::type removes the reference from type T, if any. +template struct remove_reference { typedef T type; }; // NOLINT +template struct remove_reference { typedef T type; }; // NOLINT + +// Invalid() returns an invalid value of type T. This is useful +// when a value of type T is needed for compilation, but the statement +// will not really be executed (or we don't care if the statement +// crashes). +template +inline T Invalid() { + return *static_cast::type*>(NULL); +} +template <> +inline void Invalid() {} + +// Utilities for native arrays. + +// ArrayEq() compares two k-dimensional native arrays using the +// elements' operator==, where k can be any integer >= 0. When k is +// 0, ArrayEq() degenerates into comparing a single pair of values. + +template +bool ArrayEq(const T* lhs, size_t size, const U* rhs); + +// This generic version is used when k is 0. +template +inline bool ArrayEq(const T& lhs, const U& rhs) { return lhs == rhs; } + +// This overload is used when k >= 1. +template +inline bool ArrayEq(const T(&lhs)[N], const U(&rhs)[N]) { + return internal::ArrayEq(lhs, N, rhs); +} + +// This helper reduces code bloat. If we instead put its logic inside +// the previous ArrayEq() function, arrays with different sizes would +// lead to different copies of the template code. +template +bool ArrayEq(const T* lhs, size_t size, const U* rhs) { + for (size_t i = 0; i != size; i++) { + if (!internal::ArrayEq(lhs[i], rhs[i])) + return false; + } + return true; +} + +// Finds the first element in the iterator range [begin, end) that +// equals elem. Element may be a native array type itself. +template +Iter ArrayAwareFind(Iter begin, Iter end, const Element& elem) { + for (Iter it = begin; it != end; ++it) { + if (internal::ArrayEq(*it, elem)) + return it; + } + return end; +} + +// CopyArray() copies a k-dimensional native array using the elements' +// operator=, where k can be any integer >= 0. When k is 0, +// CopyArray() degenerates into copying a single value. + +template +void CopyArray(const T* from, size_t size, U* to); + +// This generic version is used when k is 0. +template +inline void CopyArray(const T& from, U* to) { *to = from; } + +// This overload is used when k >= 1. +template +inline void CopyArray(const T(&from)[N], U(*to)[N]) { + internal::CopyArray(from, N, *to); +} + +// This helper reduces code bloat. If we instead put its logic inside +// the previous CopyArray() function, arrays with different sizes +// would lead to different copies of the template code. +template +void CopyArray(const T* from, size_t size, U* to) { + for (size_t i = 0; i != size; i++) { + internal::CopyArray(from[i], to + i); + } +} + +// The relation between an NativeArray object (see below) and the +// native array it represents. +enum RelationToSource { + kReference, // The NativeArray references the native array. + kCopy // The NativeArray makes a copy of the native array and + // owns the copy. +}; + +// Adapts a native array to a read-only STL-style container. Instead +// of the complete STL container concept, this adaptor only implements +// members useful for Google Mock's container matchers. New members +// should be added as needed. To simplify the implementation, we only +// support Element being a raw type (i.e. having no top-level const or +// reference modifier). It's the client's responsibility to satisfy +// this requirement. Element can be an array type itself (hence +// multi-dimensional arrays are supported). +template +class NativeArray { + public: + // STL-style container typedefs. + typedef Element value_type; + typedef const Element* const_iterator; + + // Constructs from a native array. + NativeArray(const Element* array, size_t count, RelationToSource relation) { + Init(array, count, relation); + } + + // Copy constructor. + NativeArray(const NativeArray& rhs) { + Init(rhs.array_, rhs.size_, rhs.relation_to_source_); + } + + ~NativeArray() { + // Ensures that the user doesn't instantiate NativeArray with a + // const or reference type. + testing::StaticAssertTypeEq(); + if (relation_to_source_ == kCopy) + delete[] array_; + } + + // STL-style container methods. + size_t size() const { return size_; } + const_iterator begin() const { return array_; } + const_iterator end() const { return array_ + size_; } + bool operator==(const NativeArray& rhs) const { + return size() == rhs.size() && + ArrayEq(begin(), size(), rhs.begin()); + } + + private: + // Not implemented as we don't want to support assignment. + void operator=(const NativeArray& rhs); + + // Initializes this object; makes a copy of the input array if + // 'relation' is kCopy. + void Init(const Element* array, size_t a_size, RelationToSource relation) { + if (relation == kReference) { + array_ = array; + } else { + Element* const copy = new Element[a_size]; + CopyArray(array, a_size, copy); + array_ = copy; + } + size_ = a_size; + relation_to_source_ = relation; + } + + const Element* array_; + size_t size_; + RelationToSource relation_to_source_; +}; + +// Given a raw type (i.e. having no top-level reference or const +// modifier) RawContainer that's either an STL-style container or a +// native array, class StlContainerView has the +// following members: +// +// - type is a type that provides an STL-style container view to +// (i.e. implements the STL container concept for) RawContainer; +// - const_reference is a type that provides a reference to a const +// RawContainer; +// - ConstReference(raw_container) returns a const reference to an STL-style +// container view to raw_container, which is a RawContainer. +// - Copy(raw_container) returns an STL-style container view of a +// copy of raw_container, which is a RawContainer. +// +// This generic version is used when RawContainer itself is already an +// STL-style container. +template +class StlContainerView { + public: + typedef RawContainer type; + typedef const type& const_reference; + + static const_reference ConstReference(const RawContainer& container) { + // Ensures that RawContainer is not a const type. + testing::StaticAssertTypeEq(); + return container; + } + static type Copy(const RawContainer& container) { return container; } +}; + +// This specialization is used when RawContainer is a native array type. +template +class StlContainerView { + public: + typedef GMOCK_REMOVE_CONST_(Element) RawElement; + typedef internal::NativeArray type; + // NativeArray can represent a native array either by value or by + // reference (selected by a constructor argument), so 'const type' + // can be used to reference a const native array. We cannot + // 'typedef const type& const_reference' here, as that would mean + // ConstReference() has to return a reference to a local variable. + typedef const type const_reference; + + static const_reference ConstReference(const Element (&array)[N]) { + // Ensures that Element is not a const type. + testing::StaticAssertTypeEq(); +#if GTEST_OS_SYMBIAN + // The Nokia Symbian compiler confuses itself in template instantiation + // for this call without the cast to Element*: + // function call '[testing::internal::NativeArray].NativeArray( + // {lval} const char *[4], long, testing::internal::RelationToSource)' + // does not match + // 'testing::internal::NativeArray::NativeArray( + // char *const *, unsigned int, testing::internal::RelationToSource)' + // (instantiating: 'testing::internal::ContainsMatcherImpl + // ::Matches(const char * (&)[4]) const') + // (instantiating: 'testing::internal::StlContainerView:: + // ConstReference(const char * (&)[4])') + // (and though the N parameter type is mismatched in the above explicit + // conversion of it doesn't help - only the conversion of the array). + return type(const_cast(&array[0]), N, kReference); +#else + return type(array, N, kReference); +#endif // GTEST_OS_SYMBIAN + } + static type Copy(const Element (&array)[N]) { +#if GTEST_OS_SYMBIAN + return type(const_cast(&array[0]), N, kCopy); +#else + return type(array, N, kCopy); +#endif // GTEST_OS_SYMBIAN + } +}; + +// This specialization is used when RawContainer is a native array +// represented as a (pointer, size) tuple. +template +class StlContainerView< ::std::tr1::tuple > { + public: + typedef GMOCK_REMOVE_CONST_( + typename internal::PointeeOf::type) RawElement; + typedef internal::NativeArray type; + typedef const type const_reference; + + static const_reference ConstReference( + const ::std::tr1::tuple& array) { + using ::std::tr1::get; + return type(get<0>(array), get<1>(array), kReference); + } + static type Copy(const ::std::tr1::tuple& array) { + using ::std::tr1::get; + return type(get<0>(array), get<1>(array), kCopy); + } +}; + +// The following specialization prevents the user from instantiating +// StlContainer with a reference type. +template class StlContainerView; + +} // namespace internal +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_INTERNAL_UTILS_H_ diff --git a/3rdparty/gmock/include/gmock/internal/gmock-port.h b/3rdparty/gmock/include/gmock/internal/gmock-port.h new file mode 100644 index 00000000..30115f23 --- /dev/null +++ b/3rdparty/gmock/include/gmock/internal/gmock-port.h @@ -0,0 +1,215 @@ +// Copyright 2008, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: vadimb@google.com (Vadim Berman) +// +// Low-level types and utilities for porting Google Mock to various +// platforms. They are subject to change without notice. DO NOT USE +// THEM IN USER CODE. + +#ifndef GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PORT_H_ +#define GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PORT_H_ + +#include +#include +#include + +// Most of the types needed for porting Google Mock are also required +// for Google Test and are defined in gtest-port.h. +#include +#include + +// To avoid conditional compilation everywhere, we make it +// gmock-port.h's responsibility to #include the header implementing +// tr1/tuple. gmock-port.h does this via gtest-port.h, which is +// guaranteed to pull in the tuple header. + +#if GTEST_OS_LINUX + +#endif // GTEST_OS_LINUX + +namespace testing { +namespace internal { + +// For MS Visual C++, check the compiler version. At least VS 2003 is +// required to compile Google Mock. +#if defined(_MSC_VER) && _MSC_VER < 1310 +#error "At least Visual C++ 2003 (7.1) is required to compile Google Mock." +#endif + +// Use implicit_cast as a safe version of static_cast for upcasting in +// the type hierarchy (e.g. casting a Foo* to a SuperclassOfFoo* or a +// const Foo*). When you use implicit_cast, the compiler checks that +// the cast is safe. Such explicit implicit_casts are necessary in +// surprisingly many situations where C++ demands an exact type match +// instead of an argument type convertable to a target type. +// +// The syntax for using implicit_cast is the same as for static_cast: +// +// implicit_cast(expr) +// +// implicit_cast would have been part of the C++ standard library, +// but the proposal was submitted too late. It will probably make +// its way into the language in the future. +template +inline To implicit_cast(To x) { return x; } + +// When you upcast (that is, cast a pointer from type Foo to type +// SuperclassOfFoo), it's fine to use implicit_cast<>, since upcasts +// always succeed. When you downcast (that is, cast a pointer from +// type Foo to type SubclassOfFoo), static_cast<> isn't safe, because +// how do you know the pointer is really of type SubclassOfFoo? It +// could be a bare Foo, or of type DifferentSubclassOfFoo. Thus, +// when you downcast, you should use this macro. In debug mode, we +// use dynamic_cast<> to double-check the downcast is legal (we die +// if it's not). In normal mode, we do the efficient static_cast<> +// instead. Thus, it's important to test in debug mode to make sure +// the cast is legal! +// This is the only place in the code we should use dynamic_cast<>. +// In particular, you SHOULDN'T be using dynamic_cast<> in order to +// do RTTI (eg code like this: +// if (dynamic_cast(foo)) HandleASubclass1Object(foo); +// if (dynamic_cast(foo)) HandleASubclass2Object(foo); +// You should design the code some other way not to need this. +template // use like this: down_cast(foo); +inline To down_cast(From* f) { // so we only accept pointers + // Ensures that To is a sub-type of From *. This test is here only + // for compile-time type checking, and has no overhead in an + // optimized build at run-time, as it will be optimized away + // completely. + if (false) { + const To to = NULL; + ::testing::internal::implicit_cast(to); + } + +#if GTEST_HAS_RTTI + assert(f == NULL || dynamic_cast(f) != NULL); // RTTI: debug mode only! +#endif + return static_cast(f); +} + +// The GMOCK_COMPILE_ASSERT_ macro can be used to verify that a compile time +// expression is true. For example, you could use it to verify the +// size of a static array: +// +// GMOCK_COMPILE_ASSERT_(ARRAYSIZE(content_type_names) == CONTENT_NUM_TYPES, +// content_type_names_incorrect_size); +// +// or to make sure a struct is smaller than a certain size: +// +// GMOCK_COMPILE_ASSERT_(sizeof(foo) < 128, foo_too_large); +// +// The second argument to the macro is the name of the variable. If +// the expression is false, most compilers will issue a warning/error +// containing the name of the variable. + +template +struct CompileAssert { +}; + +#define GMOCK_COMPILE_ASSERT_(expr, msg) \ + typedef ::testing::internal::CompileAssert<(bool(expr))> \ + msg[bool(expr) ? 1 : -1] + +// Implementation details of GMOCK_COMPILE_ASSERT_: +// +// - GMOCK_COMPILE_ASSERT_ works by defining an array type that has -1 +// elements (and thus is invalid) when the expression is false. +// +// - The simpler definition +// +// #define GMOCK_COMPILE_ASSERT_(expr, msg) typedef char msg[(expr) ? 1 : -1] +// +// does not work, as gcc supports variable-length arrays whose sizes +// are determined at run-time (this is gcc's extension and not part +// of the C++ standard). As a result, gcc fails to reject the +// following code with the simple definition: +// +// int foo; +// GMOCK_COMPILE_ASSERT_(foo, msg); // not supposed to compile as foo is +// // not a compile-time constant. +// +// - By using the type CompileAssert<(bool(expr))>, we ensures that +// expr is a compile-time constant. (Template arguments must be +// determined at compile-time.) +// +// - The outter parentheses in CompileAssert<(bool(expr))> are necessary +// to work around a bug in gcc 3.4.4 and 4.0.1. If we had written +// +// CompileAssert +// +// instead, these compilers will refuse to compile +// +// GMOCK_COMPILE_ASSERT_(5 > 0, some_message); +// +// (They seem to think the ">" in "5 > 0" marks the end of the +// template argument list.) +// +// - The array size is (bool(expr) ? 1 : -1), instead of simply +// +// ((expr) ? 1 : -1). +// +// This is to avoid running into a bug in MS VC 7.1, which +// causes ((0.0) ? 1 : -1) to incorrectly evaluate to 1. + +#if GTEST_HAS_GLOBAL_STRING +typedef ::string string; +#else +typedef ::std::string string; +#endif // GTEST_HAS_GLOBAL_STRING + +#if GTEST_HAS_GLOBAL_WSTRING +typedef ::wstring wstring; +#elif GTEST_HAS_STD_WSTRING +typedef ::std::wstring wstring; +#endif // GTEST_HAS_GLOBAL_WSTRING + +} // namespace internal +} // namespace testing + +// Macro for referencing flags. This is public as we want the user to +// use this syntax to reference Google Mock flags. +#define GMOCK_FLAG(name) FLAGS_gmock_##name + +// Macros for declaring flags. +#define GMOCK_DECLARE_bool_(name) extern bool GMOCK_FLAG(name) +#define GMOCK_DECLARE_int32_(name) \ + extern ::testing::internal::Int32 GMOCK_FLAG(name) +#define GMOCK_DECLARE_string_(name) \ + extern ::testing::internal::String GMOCK_FLAG(name) + +// Macros for defining flags. +#define GMOCK_DEFINE_bool_(name, default_val, doc) \ + bool GMOCK_FLAG(name) = (default_val) +#define GMOCK_DEFINE_int32_(name, default_val, doc) \ + ::testing::internal::Int32 GMOCK_FLAG(name) = (default_val) +#define GMOCK_DEFINE_string_(name, default_val, doc) \ + ::testing::internal::String GMOCK_FLAG(name) = (default_val) + +#endif // GMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PORT_H_ diff --git a/3rdparty/gmock/src/gmock-all.cc b/3rdparty/gmock/src/gmock-all.cc new file mode 100644 index 00000000..c9223fce --- /dev/null +++ b/3rdparty/gmock/src/gmock-all.cc @@ -0,0 +1,48 @@ +// Copyright 2008, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) +// +// Google C++ Mocking Framework (Google Mock) +// +// This file #includes all Google Mock implementation .cc files. The +// purpose is to allow a user to build Google Mock by compiling this +// file alone. + +// This line ensures that gmock.h can be compiled on its own, even +// when it's fused. +#include + +// The following lines pull in the real gmock *.cc files. +#include "src/gmock-cardinalities.cc" +#include "src/gmock-internal-utils.cc" +#include "src/gmock-matchers.cc" +#include "src/gmock-printers.cc" +#include "src/gmock-spec-builders.cc" +#include "src/gmock.cc" diff --git a/3rdparty/gmock/src/gmock-cardinalities.cc b/3rdparty/gmock/src/gmock-cardinalities.cc new file mode 100644 index 00000000..07eed469 --- /dev/null +++ b/3rdparty/gmock/src/gmock-cardinalities.cc @@ -0,0 +1,155 @@ +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements cardinalities. + +#include + +#include +#include // NOLINT +#include +#include +#include +#include + +namespace testing { + +namespace { + +// Implements the Between(m, n) cardinality. +class BetweenCardinalityImpl : public CardinalityInterface { + public: + BetweenCardinalityImpl(int min, int max) + : min_(min >= 0 ? min : 0), + max_(max >= min_ ? max : min_) { + std::stringstream ss; + if (min < 0) { + ss << "The invocation lower bound must be >= 0, " + << "but is actually " << min << "."; + internal::Expect(false, __FILE__, __LINE__, ss.str()); + } else if (max < 0) { + ss << "The invocation upper bound must be >= 0, " + << "but is actually " << max << "."; + internal::Expect(false, __FILE__, __LINE__, ss.str()); + } else if (min > max) { + ss << "The invocation upper bound (" << max + << ") must be >= the invocation lower bound (" << min + << ")."; + internal::Expect(false, __FILE__, __LINE__, ss.str()); + } + } + + // Conservative estimate on the lower/upper bound of the number of + // calls allowed. + virtual int ConservativeLowerBound() const { return min_; } + virtual int ConservativeUpperBound() const { return max_; } + + virtual bool IsSatisfiedByCallCount(int call_count) const { + return min_ <= call_count && call_count <= max_ ; + } + + virtual bool IsSaturatedByCallCount(int call_count) const { + return call_count >= max_; + } + + virtual void DescribeTo(::std::ostream* os) const; + private: + const int min_; + const int max_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(BetweenCardinalityImpl); +}; + +// Formats "n times" in a human-friendly way. +inline internal::string FormatTimes(int n) { + if (n == 1) { + return "once"; + } else if (n == 2) { + return "twice"; + } else { + std::stringstream ss; + ss << n << " times"; + return ss.str(); + } +} + +// Describes the Between(m, n) cardinality in human-friendly text. +void BetweenCardinalityImpl::DescribeTo(::std::ostream* os) const { + if (min_ == 0) { + if (max_ == 0) { + *os << "never called"; + } else if (max_ == INT_MAX) { + *os << "called any number of times"; + } else { + *os << "called at most " << FormatTimes(max_); + } + } else if (min_ == max_) { + *os << "called " << FormatTimes(min_); + } else if (max_ == INT_MAX) { + *os << "called at least " << FormatTimes(min_); + } else { + // 0 < min_ < max_ < INT_MAX + *os << "called between " << min_ << " and " << max_ << " times"; + } +} + +} // Unnamed namespace + +// Describes the given call count to an ostream. +void Cardinality::DescribeActualCallCountTo(int actual_call_count, + ::std::ostream* os) { + if (actual_call_count > 0) { + *os << "called " << FormatTimes(actual_call_count); + } else { + *os << "never called"; + } +} + +// Creates a cardinality that allows at least n calls. +Cardinality AtLeast(int n) { return Between(n, INT_MAX); } + +// Creates a cardinality that allows at most n calls. +Cardinality AtMost(int n) { return Between(0, n); } + +// Creates a cardinality that allows any number of calls. +Cardinality AnyNumber() { return AtLeast(0); } + +// Creates a cardinality that allows between min and max calls. +Cardinality Between(int min, int max) { + return Cardinality(new BetweenCardinalityImpl(min, max)); +} + +// Creates a cardinality that allows exactly n calls. +Cardinality Exactly(int n) { return Between(n, n); } + +} // namespace testing diff --git a/3rdparty/gmock/src/gmock-internal-utils.cc b/3rdparty/gmock/src/gmock-internal-utils.cc new file mode 100644 index 00000000..cc51836b --- /dev/null +++ b/3rdparty/gmock/src/gmock-internal-utils.cc @@ -0,0 +1,173 @@ +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file defines some utilities useful for implementing Google +// Mock. They are subject to change without notice, so please DO NOT +// USE THEM IN USER CODE. + +#include + +#include +#include // NOLINT +#include +#include +#include +#include + +namespace testing { +namespace internal { + +// Converts an identifier name to a space-separated list of lower-case +// words. Each maximum substring of the form [A-Za-z][a-z]*|\d+ is +// treated as one word. For example, both "FooBar123" and +// "foo_bar_123" are converted to "foo bar 123". +string ConvertIdentifierNameToWords(const char* id_name) { + string result; + char prev_char = '\0'; + for (const char* p = id_name; *p != '\0'; prev_char = *(p++)) { + // We don't care about the current locale as the input is + // guaranteed to be a valid C++ identifier name. + const bool starts_new_word = isupper(*p) || + (!isalpha(prev_char) && islower(*p)) || + (!isdigit(prev_char) && isdigit(*p)); + + if (isalnum(*p)) { + if (starts_new_word && result != "") + result += ' '; + result += static_cast(tolower(*p)); + } + } + return result; +} + +// This class reports Google Mock failures as Google Test failures. A +// user can define another class in a similar fashion if he intends to +// use Google Mock with a testing framework other than Google Test. +class GoogleTestFailureReporter : public FailureReporterInterface { + public: + virtual void ReportFailure(FailureType type, const char* file, int line, + const string& message) { + AssertHelper(type == FATAL ? + TestPartResult::kFatalFailure : + TestPartResult::kNonFatalFailure, + file, + line, + message.c_str()) = Message(); + if (type == FATAL) { + posix::Abort(); + } + } +}; + +// Returns the global failure reporter. Will create a +// GoogleTestFailureReporter and return it the first time called. +FailureReporterInterface* GetFailureReporter() { + // Points to the global failure reporter used by Google Mock. gcc + // guarantees that the following use of failure_reporter is + // thread-safe. We may need to add additional synchronization to + // protect failure_reporter if we port Google Mock to other + // compilers. + static FailureReporterInterface* const failure_reporter = + new GoogleTestFailureReporter(); + return failure_reporter; +} + +// Protects global resources (stdout in particular) used by Log(). +static GTEST_DEFINE_STATIC_MUTEX_(g_log_mutex); + +// Returns true iff a log with the given severity is visible according +// to the --gmock_verbose flag. +bool LogIsVisible(LogSeverity severity) { + if (GMOCK_FLAG(verbose) == kInfoVerbosity) { + // Always show the log if --gmock_verbose=info. + return true; + } else if (GMOCK_FLAG(verbose) == kErrorVerbosity) { + // Always hide it if --gmock_verbose=error. + return false; + } else { + // If --gmock_verbose is neither "info" nor "error", we treat it + // as "warning" (its default value). + return severity == WARNING; + } +} + +// Prints the given message to stdout iff 'severity' >= the level +// specified by the --gmock_verbose flag. If stack_frames_to_skip >= +// 0, also prints the stack trace excluding the top +// stack_frames_to_skip frames. In opt mode, any positive +// stack_frames_to_skip is treated as 0, since we don't know which +// function calls will be inlined by the compiler and need to be +// conservative. +void Log(LogSeverity severity, const string& message, + int stack_frames_to_skip) { + if (!LogIsVisible(severity)) + return; + + // Ensures that logs from different threads don't interleave. + MutexLock l(&g_log_mutex); + + // "using ::std::cout;" doesn't work with Symbian's STLport, where cout is a + // macro. + + if (severity == WARNING) { + // Prints a GMOCK WARNING marker to make the warnings easily searchable. + std::cout << "\nGMOCK WARNING:"; + } + // Pre-pends a new-line to message if it doesn't start with one. + if (message.empty() || message[0] != '\n') { + std::cout << "\n"; + } + std::cout << message; + if (stack_frames_to_skip >= 0) { +#ifdef NDEBUG + // In opt mode, we have to be conservative and skip no stack frame. + const int actual_to_skip = 0; +#else + // In dbg mode, we can do what the caller tell us to do (plus one + // for skipping this function's stack frame). + const int actual_to_skip = stack_frames_to_skip + 1; +#endif // NDEBUG + + // Appends a new-line to message if it doesn't end with one. + if (!message.empty() && *message.rbegin() != '\n') { + std::cout << "\n"; + } + std::cout << "Stack trace:\n" + << ::testing::internal::GetCurrentOsStackTraceExceptTop( + ::testing::UnitTest::GetInstance(), actual_to_skip); + } + std::cout << ::std::flush; +} + +} // namespace internal +} // namespace testing diff --git a/3rdparty/gmock/src/gmock-matchers.cc b/3rdparty/gmock/src/gmock-matchers.cc new file mode 100644 index 00000000..0abca708 --- /dev/null +++ b/3rdparty/gmock/src/gmock-matchers.cc @@ -0,0 +1,190 @@ +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements Matcher, Matcher, and +// utilities for defining matchers. + +#include +#include + +#include +#include +#include + +namespace testing { + +// Constructs a matcher that matches a const string& whose value is +// equal to s. +Matcher::Matcher(const internal::string& s) { + *this = Eq(s); +} + +// Constructs a matcher that matches a const string& whose value is +// equal to s. +Matcher::Matcher(const char* s) { + *this = Eq(internal::string(s)); +} + +// Constructs a matcher that matches a string whose value is equal to s. +Matcher::Matcher(const internal::string& s) { *this = Eq(s); } + +// Constructs a matcher that matches a string whose value is equal to s. +Matcher::Matcher(const char* s) { + *this = Eq(internal::string(s)); +} + +namespace internal { + +// Utilities for validating and formatting description strings in the +// MATCHER*() macros. + +// Returns the 0-based index of the given parameter in the +// NULL-terminated parameter array; if the parameter is "*", returns +// kTupleInterpolation; if it's not found in the list, returns +// kInvalidInterpolation. +int GetParamIndex(const char* param_names[], const string& param_name) { + if (param_name == "*") + return kTupleInterpolation; + + for (int i = 0; param_names[i] != NULL; i++) { + if (param_name == param_names[i]) + return i; + } + return kInvalidInterpolation; +} + +// Helper function used by ValidateMatcherDescription() to format +// error messages. +string FormatMatcherDescriptionSyntaxError(const char* description, + const char* error_pos) { + ::std::stringstream ss; + ss << "Syntax error at index " << (error_pos - description) + << " in matcher description \"" << description << "\": "; + return ss.str(); +} + +// Parses a matcher description string and returns a vector of +// interpolations that appear in the string; generates non-fatal +// failures iff 'description' is an invalid matcher description. +// 'param_names' is a NULL-terminated array of parameter names in the +// order they appear in the MATCHER_P*() parameter list. +Interpolations ValidateMatcherDescription( + const char* param_names[], const char* description) { + Interpolations interps; + for (const char* p = description; *p != '\0';) { + if (SkipPrefix("%%", &p)) { + interps.push_back(Interpolation(p - 2, p, kPercentInterpolation)); + } else if (SkipPrefix("%(", &p)) { + const char* const q = strstr(p, ")s"); + if (q == NULL) { + // TODO(wan@google.com): change the source file location in + // the failure to point to where the MATCHER*() macro is used. + ADD_FAILURE() << FormatMatcherDescriptionSyntaxError(description, p - 2) + << "an interpolation must end with \")s\", " + << "but \"" << (p - 2) << "\" does not."; + } else { + const string param_name(p, q); + const int param_index = GetParamIndex(param_names, param_name); + if (param_index == kInvalidInterpolation) { + ADD_FAILURE() << FormatMatcherDescriptionSyntaxError(description, p) + << "\"" << param_name + << "\" is an invalid parameter name."; + } else { + interps.push_back(Interpolation(p - 2, q + 2, param_index)); + p = q + 2; + } + } + } else { + EXPECT_NE(*p, '%') << FormatMatcherDescriptionSyntaxError(description, p) + << "use \"%%\" instead of \"%\" to print \"%\"."; + ++p; + } + } + return interps; +} + +// Joins a vector of strings as if they are fields of a tuple; returns +// the joined string. +string JoinAsTuple(const Strings& fields) { + switch (fields.size()) { + case 0: + return ""; + case 1: + return fields[0]; + default: + string result = "(" + fields[0]; + for (size_t i = 1; i < fields.size(); i++) { + result += ", "; + result += fields[i]; + } + result += ")"; + return result; + } +} + +// Returns the actual matcher description, given the matcher name, +// user-supplied description template string, interpolations in the +// string, and the printed values of the matcher parameters. +string FormatMatcherDescription( + const char* matcher_name, const char* description, + const Interpolations& interp, const Strings& param_values) { + string result; + if (*description == '\0') { + // When the user supplies an empty description, we calculate one + // from the matcher name. + result = ConvertIdentifierNameToWords(matcher_name); + if (param_values.size() >= 1) + result += " " + JoinAsTuple(param_values); + } else { + // The end position of the last interpolation. + const char* last_interp_end = description; + for (size_t i = 0; i < interp.size(); i++) { + result.append(last_interp_end, interp[i].start_pos); + const int param_index = interp[i].param_index; + if (param_index == kTupleInterpolation) { + result += JoinAsTuple(param_values); + } else if (param_index == kPercentInterpolation) { + result += '%'; + } else if (param_index != kInvalidInterpolation) { + result += param_values[param_index]; + } + last_interp_end = interp[i].end_pos; + } + result += last_interp_end; + } + + return result; +} + +} // namespace internal +} // namespace testing diff --git a/3rdparty/gmock/src/gmock-printers.cc b/3rdparty/gmock/src/gmock-printers.cc new file mode 100644 index 00000000..fd7d3055 --- /dev/null +++ b/3rdparty/gmock/src/gmock-printers.cc @@ -0,0 +1,318 @@ +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements a universal value printer that can print a +// value of any type T: +// +// void ::testing::internal::UniversalPrinter::Print(value, ostream_ptr); +// +// It uses the << operator when possible, and prints the bytes in the +// object otherwise. A user can override its behavior for a class +// type Foo by defining either operator<<(::std::ostream&, const Foo&) +// or void PrintTo(const Foo&, ::std::ostream*) in the namespace that +// defines Foo. + +#include +#include +#include +#include // NOLINT +#include +#include + +namespace testing { + +namespace { + +using ::std::ostream; + +#if GTEST_OS_WINDOWS_MOBILE // Windows CE does not define _snprintf_s. +#define snprintf _snprintf +#elif _MSC_VER >= 1400 // VC 8.0 and later deprecate snprintf and _snprintf. +#define snprintf _snprintf_s +#elif _MSC_VER +#define snprintf _snprintf +#endif // GTEST_OS_WINDOWS_MOBILE + +// Prints a segment of bytes in the given object. +void PrintByteSegmentInObjectTo(const unsigned char* obj_bytes, size_t start, + size_t count, ostream* os) { + char text[5] = ""; + for (size_t i = 0; i != count; i++) { + const size_t j = start + i; + if (i != 0) { + // Organizes the bytes into groups of 2 for easy parsing by + // human. + if ((j % 2) == 0) { + *os << " "; + } + } + snprintf(text, sizeof(text), "%02X", obj_bytes[j]); + *os << text; + } +} + +// Prints the bytes in the given value to the given ostream. +void PrintBytesInObjectToImpl(const unsigned char* obj_bytes, size_t count, + ostream* os) { + // Tells the user how big the object is. + *os << count << "-byte object <"; + + const size_t kThreshold = 132; + const size_t kChunkSize = 64; + // If the object size is bigger than kThreshold, we'll have to omit + // some details by printing only the first and the last kChunkSize + // bytes. + // TODO(wan): let the user control the threshold using a flag. + if (count < kThreshold) { + PrintByteSegmentInObjectTo(obj_bytes, 0, count, os); + } else { + PrintByteSegmentInObjectTo(obj_bytes, 0, kChunkSize, os); + *os << " ... "; + // Rounds up to 2-byte boundary. + const size_t resume_pos = (count - kChunkSize + 1)/2*2; + PrintByteSegmentInObjectTo(obj_bytes, resume_pos, count - resume_pos, os); + } + *os << ">"; +} + +} // namespace + +namespace internal2 { + +// Delegates to PrintBytesInObjectToImpl() to print the bytes in the +// given object. The delegation simplifies the implementation, which +// uses the << operator and thus is easier done outside of the +// ::testing::internal namespace, which contains a << operator that +// sometimes conflicts with the one in STL. +void PrintBytesInObjectTo(const unsigned char* obj_bytes, size_t count, + ostream* os) { + PrintBytesInObjectToImpl(obj_bytes, count, os); +} + +} // namespace internal2 + +namespace internal { + +// Prints a wide char as a char literal without the quotes, escaping it +// when necessary. +static void PrintAsWideCharLiteralTo(wchar_t c, ostream* os) { + switch (c) { + case L'\0': + *os << "\\0"; + break; + case L'\'': + *os << "\\'"; + break; + case L'\?': + *os << "\\?"; + break; + case L'\\': + *os << "\\\\"; + break; + case L'\a': + *os << "\\a"; + break; + case L'\b': + *os << "\\b"; + break; + case L'\f': + *os << "\\f"; + break; + case L'\n': + *os << "\\n"; + break; + case L'\r': + *os << "\\r"; + break; + case L'\t': + *os << "\\t"; + break; + case L'\v': + *os << "\\v"; + break; + default: + // Checks whether c is printable or not. Printable characters are in + // the range [0x20,0x7E]. + // We test the value of c directly instead of calling isprint(), as + // isprint() is buggy on Windows mobile. + if (0x20 <= c && c <= 0x7E) { + *os << static_cast(c); + } else { + // Buffer size enough for the maximum number of digits and \0. + char text[2 * sizeof(unsigned long) + 1] = ""; + snprintf(text, sizeof(text), "%lX", static_cast(c)); + *os << "\\x" << text; + } + } +} + +// Prints a char as if it's part of a string literal, escaping it when +// necessary. +static void PrintAsWideStringLiteralTo(wchar_t c, ostream* os) { + switch (c) { + case L'\'': + *os << "'"; + break; + case L'"': + *os << "\\\""; + break; + default: + PrintAsWideCharLiteralTo(c, os); + } +} + +// Prints a char as a char literal without the quotes, escaping it +// when necessary. +static void PrintAsCharLiteralTo(char c, ostream* os) { + PrintAsWideCharLiteralTo(static_cast(c), os); +} + +// Prints a char as if it's part of a string literal, escaping it when +// necessary. +static void PrintAsStringLiteralTo(char c, ostream* os) { + PrintAsWideStringLiteralTo(static_cast(c), os); +} + +// Prints a char and its code. The '\0' char is printed as "'\\0'", +// other unprintable characters are also properly escaped using the +// standard C++ escape sequence. +void PrintCharTo(char c, int char_code, ostream* os) { + *os << "'"; + PrintAsCharLiteralTo(c, os); + *os << "'"; + if (c != '\0') + *os << " (" << char_code << ")"; +} + +// Prints a wchar_t as a symbol if it is printable or as its internal +// code otherwise and also as its decimal code (except for L'\0'). +// The L'\0' char is printed as "L'\\0'". The decimal code is printed +// as signed integer when wchar_t is implemented by the compiler +// as a signed type and is printed as an unsigned integer when wchar_t +// is implemented as an unsigned type. +void PrintTo(wchar_t wc, ostream* os) { + *os << "L'"; + PrintAsWideCharLiteralTo(wc, os); + *os << "'"; + if (wc != L'\0') { + // Type Int64 is used because it provides more storage than wchar_t thus + // when the compiler converts signed or unsigned implementation of wchar_t + // to Int64 it fills higher bits with either zeros or the sign bit + // passing it to operator <<() as either signed or unsigned integer. + *os << " (" << static_cast(wc) << ")"; + } +} + +// Prints the given array of characters to the ostream. +// The array starts at *begin, the length is len, it may include '\0' characters +// and may not be null-terminated. +static void PrintCharsAsStringTo(const char* begin, size_t len, ostream* os) { + *os << "\""; + for (size_t index = 0; index < len; ++index) { + PrintAsStringLiteralTo(begin[index], os); + } + *os << "\""; +} + +// Prints a (const) char array of 'len' elements, starting at address 'begin'. +void UniversalPrintArray(const char* begin, size_t len, ostream* os) { + PrintCharsAsStringTo(begin, len, os); +} + +// Prints the given array of wide characters to the ostream. +// The array starts at *begin, the length is len, it may include L'\0' +// characters and may not be null-terminated. +static void PrintWideCharsAsStringTo(const wchar_t* begin, size_t len, + ostream* os) { + *os << "L\""; + for (size_t index = 0; index < len; ++index) { + PrintAsWideStringLiteralTo(begin[index], os); + } + *os << "\""; +} + +// Prints the given C string to the ostream. +void PrintTo(const char* s, ostream* os) { + if (s == NULL) { + *os << "NULL"; + } else { + *os << implicit_cast(s) << " pointing to "; + PrintCharsAsStringTo(s, strlen(s), os); + } +} + +// MSVC compiler can be configured to define whar_t as a typedef +// of unsigned short. Defining an overload for const wchar_t* in that case +// would cause pointers to unsigned shorts be printed as wide strings, +// possibly accessing more memory than intended and causing invalid +// memory accesses. MSVC defines _NATIVE_WCHAR_T_DEFINED symbol when +// wchar_t is implemented as a native type. +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +// Prints the given wide C string to the ostream. +void PrintTo(const wchar_t* s, ostream* os) { + if (s == NULL) { + *os << "NULL"; + } else { + *os << implicit_cast(s) << " pointing to "; + PrintWideCharsAsStringTo(s, wcslen(s), os); + } +} +#endif // wchar_t is native + +// Prints a ::string object. +#if GTEST_HAS_GLOBAL_STRING +void PrintStringTo(const ::string& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} +#endif // GTEST_HAS_GLOBAL_STRING + +void PrintStringTo(const ::std::string& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} + +// Prints a ::wstring object. +#if GTEST_HAS_GLOBAL_WSTRING +void PrintWideStringTo(const ::wstring& s, ostream* os) { + PrintWideCharsAsStringTo(s.data(), s.size(), os); +} +#endif // GTEST_HAS_GLOBAL_WSTRING + +#if GTEST_HAS_STD_WSTRING +void PrintWideStringTo(const ::std::wstring& s, ostream* os) { + PrintWideCharsAsStringTo(s.data(), s.size(), os); +} +#endif // GTEST_HAS_STD_WSTRING + +} // namespace internal + +} // namespace testing diff --git a/3rdparty/gmock/src/gmock-spec-builders.cc b/3rdparty/gmock/src/gmock-spec-builders.cc new file mode 100644 index 00000000..dab1a2c9 --- /dev/null +++ b/3rdparty/gmock/src/gmock-spec-builders.cc @@ -0,0 +1,465 @@ +// Copyright 2007, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements the spec builder syntax (ON_CALL and +// EXPECT_CALL). + +#include + +#include +#include // NOLINT +#include +#include +#include +#include +#include + +#if GTEST_OS_CYGWIN || GTEST_OS_LINUX || GTEST_OS_MAC +#include // NOLINT +#endif + +namespace testing { +namespace internal { + +// Protects the mock object registry (in class Mock), all function +// mockers, and all expectations. +GTEST_DEFINE_STATIC_MUTEX_(g_gmock_mutex); + +// Constructs an ExpectationBase object. +ExpectationBase::ExpectationBase(const char* a_file, + int a_line, + const string& a_source_text) + : file_(a_file), + line_(a_line), + source_text_(a_source_text), + cardinality_specified_(false), + cardinality_(Exactly(1)), + call_count_(0), + retired_(false) { +} + +// Destructs an ExpectationBase object. +ExpectationBase::~ExpectationBase() {} + +// Explicitly specifies the cardinality of this expectation. Used by +// the subclasses to implement the .Times() clause. +void ExpectationBase::SpecifyCardinality(const Cardinality& a_cardinality) { + cardinality_specified_ = true; + cardinality_ = a_cardinality; +} + +// Retires all pre-requisites of this expectation. +void ExpectationBase::RetireAllPreRequisites() { + if (is_retired()) { + // We can take this short-cut as we never retire an expectation + // until we have retired all its pre-requisites. + return; + } + + for (ExpectationSet::const_iterator it = immediate_prerequisites_.begin(); + it != immediate_prerequisites_.end(); ++it) { + ExpectationBase* const prerequisite = it->expectation_base().get(); + if (!prerequisite->is_retired()) { + prerequisite->RetireAllPreRequisites(); + prerequisite->Retire(); + } + } +} + +// Returns true iff all pre-requisites of this expectation have been +// satisfied. +// L >= g_gmock_mutex +bool ExpectationBase::AllPrerequisitesAreSatisfied() const { + g_gmock_mutex.AssertHeld(); + for (ExpectationSet::const_iterator it = immediate_prerequisites_.begin(); + it != immediate_prerequisites_.end(); ++it) { + if (!(it->expectation_base()->IsSatisfied()) || + !(it->expectation_base()->AllPrerequisitesAreSatisfied())) + return false; + } + return true; +} + +// Adds unsatisfied pre-requisites of this expectation to 'result'. +// L >= g_gmock_mutex +void ExpectationBase::FindUnsatisfiedPrerequisites( + ExpectationSet* result) const { + g_gmock_mutex.AssertHeld(); + for (ExpectationSet::const_iterator it = immediate_prerequisites_.begin(); + it != immediate_prerequisites_.end(); ++it) { + if (it->expectation_base()->IsSatisfied()) { + // If *it is satisfied and has a call count of 0, some of its + // pre-requisites may not be satisfied yet. + if (it->expectation_base()->call_count_ == 0) { + it->expectation_base()->FindUnsatisfiedPrerequisites(result); + } + } else { + // Now that we know *it is unsatisfied, we are not so interested + // in whether its pre-requisites are satisfied. Therefore we + // don't recursively call FindUnsatisfiedPrerequisites() here. + *result += *it; + } + } +} + +// Points to the implicit sequence introduced by a living InSequence +// object (if any) in the current thread or NULL. +ThreadLocal g_gmock_implicit_sequence; + +// Reports an uninteresting call (whose description is in msg) in the +// manner specified by 'reaction'. +void ReportUninterestingCall(CallReaction reaction, const string& msg) { + switch (reaction) { + case ALLOW: + Log(INFO, msg, 3); + break; + case WARN: + Log(WARNING, msg, 3); + break; + default: // FAIL + Expect(false, NULL, -1, msg); + } +} + +} // namespace internal + +// Class Mock. + +namespace { + +typedef std::set FunctionMockers; + +// The current state of a mock object. Such information is needed for +// detecting leaked mock objects and explicitly verifying a mock's +// expectations. +struct MockObjectState { + MockObjectState() + : first_used_file(NULL), first_used_line(-1), leakable(false) {} + + // Where in the source file an ON_CALL or EXPECT_CALL is first + // invoked on this mock object. + const char* first_used_file; + int first_used_line; + ::std::string first_used_test_case; + ::std::string first_used_test; + bool leakable; // true iff it's OK to leak the object. + FunctionMockers function_mockers; // All registered methods of the object. +}; + +// A global registry holding the state of all mock objects that are +// alive. A mock object is added to this registry the first time +// Mock::AllowLeak(), ON_CALL(), or EXPECT_CALL() is called on it. It +// is removed from the registry in the mock object's destructor. +class MockObjectRegistry { + public: + // Maps a mock object (identified by its address) to its state. + typedef std::map StateMap; + + // This destructor will be called when a program exits, after all + // tests in it have been run. By then, there should be no mock + // object alive. Therefore we report any living object as test + // failure, unless the user explicitly asked us to ignore it. + ~MockObjectRegistry() { + + // "using ::std::cout;" doesn't work with Symbian's STLport, where cout is + // a macro. + + if (!GMOCK_FLAG(catch_leaked_mocks)) + return; + + int leaked_count = 0; + for (StateMap::const_iterator it = states_.begin(); it != states_.end(); + ++it) { + if (it->second.leakable) // The user said it's fine to leak this object. + continue; + + // TODO(wan@google.com): Print the type of the leaked object. + // This can help the user identify the leaked object. + std::cout << "\n"; + const MockObjectState& state = it->second; + std::cout << internal::FormatFileLocation(state.first_used_file, + state.first_used_line); + std::cout << " ERROR: this mock object"; + if (state.first_used_test != "") { + std::cout << " (used in test " << state.first_used_test_case << "." + << state.first_used_test << ")"; + } + std::cout << " should be deleted but never is. Its address is @" + << it->first << "."; + leaked_count++; + } + if (leaked_count > 0) { + std::cout << "\nERROR: " << leaked_count + << " leaked mock " << (leaked_count == 1 ? "object" : "objects") + << " found at program exit.\n"; + std::cout.flush(); + ::std::cerr.flush(); + // RUN_ALL_TESTS() has already returned when this destructor is + // called. Therefore we cannot use the normal Google Test + // failure reporting mechanism. + _exit(1); // We cannot call exit() as it is not reentrant and + // may already have been called. + } + } + + StateMap& states() { return states_; } + private: + StateMap states_; +}; + +// Protected by g_gmock_mutex. +MockObjectRegistry g_mock_object_registry; + +// Maps a mock object to the reaction Google Mock should have when an +// uninteresting method is called. Protected by g_gmock_mutex. +std::map g_uninteresting_call_reaction; + +// Sets the reaction Google Mock should have when an uninteresting +// method of the given mock object is called. +// L < g_gmock_mutex +void SetReactionOnUninterestingCalls(const void* mock_obj, + internal::CallReaction reaction) { + internal::MutexLock l(&internal::g_gmock_mutex); + g_uninteresting_call_reaction[mock_obj] = reaction; +} + +} // namespace + +// Tells Google Mock to allow uninteresting calls on the given mock +// object. +// L < g_gmock_mutex +void Mock::AllowUninterestingCalls(const void* mock_obj) { + SetReactionOnUninterestingCalls(mock_obj, internal::ALLOW); +} + +// Tells Google Mock to warn the user about uninteresting calls on the +// given mock object. +// L < g_gmock_mutex +void Mock::WarnUninterestingCalls(const void* mock_obj) { + SetReactionOnUninterestingCalls(mock_obj, internal::WARN); +} + +// Tells Google Mock to fail uninteresting calls on the given mock +// object. +// L < g_gmock_mutex +void Mock::FailUninterestingCalls(const void* mock_obj) { + SetReactionOnUninterestingCalls(mock_obj, internal::FAIL); +} + +// Tells Google Mock the given mock object is being destroyed and its +// entry in the call-reaction table should be removed. +// L < g_gmock_mutex +void Mock::UnregisterCallReaction(const void* mock_obj) { + internal::MutexLock l(&internal::g_gmock_mutex); + g_uninteresting_call_reaction.erase(mock_obj); +} + +// Returns the reaction Google Mock will have on uninteresting calls +// made on the given mock object. +// L < g_gmock_mutex +internal::CallReaction Mock::GetReactionOnUninterestingCalls( + const void* mock_obj) { + internal::MutexLock l(&internal::g_gmock_mutex); + return (g_uninteresting_call_reaction.count(mock_obj) == 0) ? + internal::WARN : g_uninteresting_call_reaction[mock_obj]; +} + +// Tells Google Mock to ignore mock_obj when checking for leaked mock +// objects. +// L < g_gmock_mutex +void Mock::AllowLeak(const void* mock_obj) { + internal::MutexLock l(&internal::g_gmock_mutex); + g_mock_object_registry.states()[mock_obj].leakable = true; +} + +// Verifies and clears all expectations on the given mock object. If +// the expectations aren't satisfied, generates one or more Google +// Test non-fatal failures and returns false. +// L < g_gmock_mutex +bool Mock::VerifyAndClearExpectations(void* mock_obj) { + internal::MutexLock l(&internal::g_gmock_mutex); + return VerifyAndClearExpectationsLocked(mock_obj); +} + +// Verifies all expectations on the given mock object and clears its +// default actions and expectations. Returns true iff the +// verification was successful. +// L < g_gmock_mutex +bool Mock::VerifyAndClear(void* mock_obj) { + internal::MutexLock l(&internal::g_gmock_mutex); + ClearDefaultActionsLocked(mock_obj); + return VerifyAndClearExpectationsLocked(mock_obj); +} + +// Verifies and clears all expectations on the given mock object. If +// the expectations aren't satisfied, generates one or more Google +// Test non-fatal failures and returns false. +// L >= g_gmock_mutex +bool Mock::VerifyAndClearExpectationsLocked(void* mock_obj) { + internal::g_gmock_mutex.AssertHeld(); + if (g_mock_object_registry.states().count(mock_obj) == 0) { + // No EXPECT_CALL() was set on the given mock object. + return true; + } + + // Verifies and clears the expectations on each mock method in the + // given mock object. + bool expectations_met = true; + FunctionMockers& mockers = + g_mock_object_registry.states()[mock_obj].function_mockers; + for (FunctionMockers::const_iterator it = mockers.begin(); + it != mockers.end(); ++it) { + if (!(*it)->VerifyAndClearExpectationsLocked()) { + expectations_met = false; + } + } + + // We don't clear the content of mockers, as they may still be + // needed by ClearDefaultActionsLocked(). + return expectations_met; +} + +// Registers a mock object and a mock method it owns. +// L < g_gmock_mutex +void Mock::Register(const void* mock_obj, + internal::UntypedFunctionMockerBase* mocker) { + internal::MutexLock l(&internal::g_gmock_mutex); + g_mock_object_registry.states()[mock_obj].function_mockers.insert(mocker); +} + +// Tells Google Mock where in the source code mock_obj is used in an +// ON_CALL or EXPECT_CALL. In case mock_obj is leaked, this +// information helps the user identify which object it is. +// L < g_gmock_mutex +void Mock::RegisterUseByOnCallOrExpectCall( + const void* mock_obj, const char* file, int line) { + internal::MutexLock l(&internal::g_gmock_mutex); + MockObjectState& state = g_mock_object_registry.states()[mock_obj]; + if (state.first_used_file == NULL) { + state.first_used_file = file; + state.first_used_line = line; + const TestInfo* const test_info = + UnitTest::GetInstance()->current_test_info(); + if (test_info != NULL) { + // TODO(wan@google.com): record the test case name when the + // ON_CALL or EXPECT_CALL is invoked from SetUpTestCase() or + // TearDownTestCase(). + state.first_used_test_case = test_info->test_case_name(); + state.first_used_test = test_info->name(); + } + } +} + +// Unregisters a mock method; removes the owning mock object from the +// registry when the last mock method associated with it has been +// unregistered. This is called only in the destructor of +// FunctionMockerBase. +// L >= g_gmock_mutex +void Mock::UnregisterLocked(internal::UntypedFunctionMockerBase* mocker) { + internal::g_gmock_mutex.AssertHeld(); + for (MockObjectRegistry::StateMap::iterator it = + g_mock_object_registry.states().begin(); + it != g_mock_object_registry.states().end(); ++it) { + FunctionMockers& mockers = it->second.function_mockers; + if (mockers.erase(mocker) > 0) { + // mocker was in mockers and has been just removed. + if (mockers.empty()) { + g_mock_object_registry.states().erase(it); + } + return; + } + } +} + +// Clears all ON_CALL()s set on the given mock object. +// L >= g_gmock_mutex +void Mock::ClearDefaultActionsLocked(void* mock_obj) { + internal::g_gmock_mutex.AssertHeld(); + + if (g_mock_object_registry.states().count(mock_obj) == 0) { + // No ON_CALL() was set on the given mock object. + return; + } + + // Clears the default actions for each mock method in the given mock + // object. + FunctionMockers& mockers = + g_mock_object_registry.states()[mock_obj].function_mockers; + for (FunctionMockers::const_iterator it = mockers.begin(); + it != mockers.end(); ++it) { + (*it)->ClearDefaultActionsLocked(); + } + + // We don't clear the content of mockers, as they may still be + // needed by VerifyAndClearExpectationsLocked(). +} + +Expectation::Expectation() {} + +Expectation::Expectation( + const internal::linked_ptr& an_expectation_base) + : expectation_base_(an_expectation_base) {} + +Expectation::~Expectation() {} + +// Adds an expectation to a sequence. +void Sequence::AddExpectation(const Expectation& expectation) const { + if (*last_expectation_ != expectation) { + if (last_expectation_->expectation_base() != NULL) { + expectation.expectation_base()->immediate_prerequisites_ + += *last_expectation_; + } + *last_expectation_ = expectation; + } +} + +// Creates the implicit sequence if there isn't one. +InSequence::InSequence() { + if (internal::g_gmock_implicit_sequence.get() == NULL) { + internal::g_gmock_implicit_sequence.set(new Sequence); + sequence_created_ = true; + } else { + sequence_created_ = false; + } +} + +// Deletes the implicit sequence if it was created by the constructor +// of this object. +InSequence::~InSequence() { + if (sequence_created_) { + delete internal::g_gmock_implicit_sequence.get(); + internal::g_gmock_implicit_sequence.set(NULL); + } +} + +} // namespace testing diff --git a/3rdparty/gmock/src/gmock.cc b/3rdparty/gmock/src/gmock.cc new file mode 100644 index 00000000..f487265d --- /dev/null +++ b/3rdparty/gmock/src/gmock.cc @@ -0,0 +1,182 @@ +// Copyright 2008, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +#include +#include + +namespace testing { + +// TODO(wan@google.com): support using environment variables to +// control the flag values, like what Google Test does. + +GMOCK_DEFINE_bool_(catch_leaked_mocks, true, + "true iff Google Mock should report leaked mock objects " + "as failures."); + +GMOCK_DEFINE_string_(verbose, internal::kWarningVerbosity, + "Controls how verbose Google Mock's output is." + " Valid values:\n" + " info - prints all messages.\n" + " warning - prints warnings and errors.\n" + " error - prints errors only."); + +namespace internal { + +// Parses a string as a command line flag. The string should have the +// format "--gmock_flag=value". When def_optional is true, the +// "=value" part can be omitted. +// +// Returns the value of the flag, or NULL if the parsing failed. +static const char* ParseGoogleMockFlagValue(const char* str, + const char* flag, + bool def_optional) { + // str and flag must not be NULL. + if (str == NULL || flag == NULL) return NULL; + + // The flag must start with "--gmock_". + const String flag_str = String::Format("--gmock_%s", flag); + const size_t flag_len = flag_str.length(); + if (strncmp(str, flag_str.c_str(), flag_len) != 0) return NULL; + + // Skips the flag name. + const char* flag_end = str + flag_len; + + // When def_optional is true, it's OK to not have a "=value" part. + if (def_optional && (flag_end[0] == '\0')) { + return flag_end; + } + + // If def_optional is true and there are more characters after the + // flag name, or if def_optional is false, there must be a '=' after + // the flag name. + if (flag_end[0] != '=') return NULL; + + // Returns the string after "=". + return flag_end + 1; +} + +// Parses a string for a Google Mock bool flag, in the form of +// "--gmock_flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +static bool ParseGoogleMockBoolFlag(const char* str, const char* flag, + bool* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseGoogleMockFlagValue(str, flag, true); + + // Aborts if the parsing failed. + if (value_str == NULL) return false; + + // Converts the string value to a bool. + *value = !(*value_str == '0' || *value_str == 'f' || *value_str == 'F'); + return true; +} + +// Parses a string for a Google Mock string flag, in the form of +// "--gmock_flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +static bool ParseGoogleMockStringFlag(const char* str, const char* flag, + String* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseGoogleMockFlagValue(str, flag, false); + + // Aborts if the parsing failed. + if (value_str == NULL) return false; + + // Sets *value to the value of the flag. + *value = value_str; + return true; +} + +// The internal implementation of InitGoogleMock(). +// +// The type parameter CharType can be instantiated to either char or +// wchar_t. +template +void InitGoogleMockImpl(int* argc, CharType** argv) { + // Makes sure Google Test is initialized. InitGoogleTest() is + // idempotent, so it's fine if the user has already called it. + InitGoogleTest(argc, argv); + if (*argc <= 0) return; + + for (int i = 1; i != *argc; i++) { + const String arg_string = StreamableToString(argv[i]); + const char* const arg = arg_string.c_str(); + + // Do we see a Google Mock flag? + if (ParseGoogleMockBoolFlag(arg, "catch_leaked_mocks", + &GMOCK_FLAG(catch_leaked_mocks)) || + ParseGoogleMockStringFlag(arg, "verbose", &GMOCK_FLAG(verbose))) { + // Yes. Shift the remainder of the argv list left by one. Note + // that argv has (*argc + 1) elements, the last one always being + // NULL. The following loop moves the trailing NULL element as + // well. + for (int j = i; j != *argc; j++) { + argv[j] = argv[j + 1]; + } + + // Decrements the argument count. + (*argc)--; + + // We also need to decrement the iterator as we just removed + // an element. + i--; + } + } +} + +} // namespace internal + +// Initializes Google Mock. This must be called before running the +// tests. In particular, it parses a command line for the flags that +// Google Mock recognizes. Whenever a Google Mock flag is seen, it is +// removed from argv, and *argc is decremented. +// +// No value is returned. Instead, the Google Mock flag variables are +// updated. +// +// Since Google Test is needed for Google Mock to work, this function +// also initializes Google Test and parses its flags, if that hasn't +// been done. +void InitGoogleMock(int* argc, char** argv) { + internal::InitGoogleMockImpl(argc, argv); +} + +// This overloaded version can be used in Windows programs compiled in +// UNICODE mode. +void InitGoogleMock(int* argc, wchar_t** argv) { + internal::InitGoogleMockImpl(argc, argv); +} + +} // namespace testing diff --git a/3rdparty/gmock/src/gmock_main.cc b/3rdparty/gmock/src/gmock_main.cc new file mode 100644 index 00000000..0a3071bf --- /dev/null +++ b/3rdparty/gmock/src/gmock_main.cc @@ -0,0 +1,54 @@ +// Copyright 2008, Google Inc. +// 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 name of Google Inc. nor the names of its +// 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 +// OWNER 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. +// +// Author: wan@google.com (Zhanyong Wan) + +#include +#include +#include + +// MS C++ compiler/linker has a bug on Windows (not on Windows CE), which +// causes a link error when _tmain is defined in a static library and UNICODE +// is enabled. For this reason instead of _tmain, main function is used on +// Windows. See the following link to track the current status of this bug: +// http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=394464 // NOLINT +#if GTEST_OS_WINDOWS_MOBILE +#include // NOLINT + +int _tmain(int argc, TCHAR** argv) { +#else +int main(int argc, char** argv) { +#endif // GTEST_OS_WINDOWS_MOBILE + std::cout << "Running main() from gmock_main.cc\n"; + // Since Google Mock depends on Google Test, InitGoogleMock() is + // also responsible for initializing Google Test. Therefore there's + // no need for calling testing::InitGoogleTest() separately. + testing::InitGoogleMock(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/3rdparty/qjson/.travis.yml b/3rdparty/qjson/.travis.yml new file mode 100644 index 00000000..b258582f --- /dev/null +++ b/3rdparty/qjson/.travis.yml @@ -0,0 +1,22 @@ +language: cpp +sudo: false +dist: trusty +env: + - USE_QT4=yes + - USE_QT4=no +compiler: + - gcc +addons: + apt: + packages: + - cmake + - make + - libqt4-dev + - qt5-default +before_script: + - mkdir build + - cd build + - cmake -DQT4_BUILD=$USE_QT4 -DQJSON_BUILD_TESTS=yes .. +script: + - make + - make test diff --git a/3rdparty/qjson/CMakeLists.txt b/3rdparty/qjson/CMakeLists.txt new file mode 100755 index 00000000..c9d45bfa --- /dev/null +++ b/3rdparty/qjson/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 2.8.11) + +set(QJSON-SOURCES + src/parser.cpp + src/qobjecthelper.cpp + src/json_scanner.cpp + src/json_parser.cc + src/parserrunnable.cpp + src/serializer.cpp + src/serializerrunnable.cpp +) + +set(QJSON-MOC-HEADERS + src/parser.h + src/parserrunnable.h + src/qobjecthelper.h + src/serializer.h + src/serializerrunnable.h + src/qjson_export.h +) + +include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) + +QT5_WRAP_CPP(QJSON-SOURCES-MOC ${QJSON-MOC-HEADERS}) + +add_library(qjson STATIC + ${QJSON-SOURCES} + ${QJSON-SOURCES-MOC} + ${QJSON-WIN32-RESOURCES} +) + +set_property(TARGET qjson PROPERTY QT_STATICPLUGIN 1) + +target_link_libraries(qjson + Qt5::Core + ${QJSON_LIBRARIES} +) diff --git a/3rdparty/qjson/CMakeLists.txt.bak b/3rdparty/qjson/CMakeLists.txt.bak new file mode 100755 index 00000000..ae52126f --- /dev/null +++ b/3rdparty/qjson/CMakeLists.txt.bak @@ -0,0 +1,149 @@ +PROJECT(qjson) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII" ) + +# Force cmake 2.8.8 in order to have a decent support of Qt5 +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.8) +CMAKE_POLICY(SET CMP0003 NEW) + +# Do not link against qtmain on Windows +if(POLICY CMP0020) + cmake_policy(SET CMP0020 OLD) +endif() + +set(CMAKE_INSTALL_NAME_DIR ${LIB_INSTALL_DIR}) + +IF("${CMAKE_BUILD_TYPE}" MATCHES "^Rel.*") + ADD_DEFINITIONS("-DQT_NO_DEBUG_OUTPUT") +ENDIF("${CMAKE_BUILD_TYPE}" MATCHES "^Rel.*") + +# Ability to disable verbose debug output +IF(QJSON_VERBOSE_DEBUG_OUTPUT) + ADD_DEFINITIONS("-DQJSON_VERBOSE_DEBUG_OUTPUT") +endif(QJSON_VERBOSE_DEBUG_OUTPUT) + +# On Windows debug library should have 'd' postfix. +IF (WIN32) + SET(CMAKE_DEBUG_POSTFIX "d") +elseif (APPLE) + set(CMAKE_DEBUG_POSTFIX "_debug") +endif (WIN32) + +# BUILD_SHARED_LIBS is cmake variable. Need to change default value. +option(BUILD_SHARED_LIBS "Build shared library" ON) + +OPTION(OSX_FRAMEWORK "Build a Mac OS X Framework") +SET(FRAMEWORK_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/Library/Frameworks" + CACHE PATH "Where to place qjson.framework if OSX_FRAMEWORK is selected") + +option(QT4_BUILD "Force building with Qt4 even if Qt5 is found") +IF (NOT QT4_BUILD) + FIND_PACKAGE( Qt5Core QUIET ) +ENDIF() + +IF (Qt5Core_FOUND) + MESSAGE ("Qt5 found") + + INCLUDE_DIRECTORIES(${Qt5Core_INCLUDE_DIRS}) + ADD_DEFINITIONS(${Qt5Core_DEFINITIONS}) + SET(PC_Requires "Qt5Core") + set(QJSON_SUFFIX "-qt5") + # Tell CMake to run moc when necessary: + set(CMAKE_AUTOMOC ON) + # As moc files are generated in the binary dir, tell CMake + # to always look for includes there: + set(CMAKE_INCLUDE_CURRENT_DIR ON) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_USE_QSTRINGBUILDER" ) + MESSAGE ("Enable QStringBuilder") +ELSE() + MESSAGE ("Qt5 not found, searching for Qt4") + + # Don't use absolute path in qjson-targets-*.cmake + # (This will have no effect with CMake < 2.8) + # Workaround for no stdlib.h error. In this case it must be used with + # -DQT_INCLUDE_DIRS_NO_SYSTEM=ON. So cmake must be invoked with + # -DQT_USE_IMPORTED_TARGETS=OFF -DQT_INCLUDE_DIRS_NO_SYSTEM=ON + # See https://bugzilla.redhat.com/show_bug.cgi?id=1470809 for details + OPTION(QT_USE_IMPORTED_TARGETS "Use imported targets" ON) + + # Find Qt4 + FIND_PACKAGE( Qt4 4.5 REQUIRED QtCore) + # QStringBuilder is supported since Qt 4.8 for both QString and QByteArray + IF (NOT (${QT_VERSION_MINOR} STRLESS "8")) + MESSAGE ("Enable QStringBuilder") + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_USE_QSTRINGBUILDER" ) + ELSE() + MESSAGE ("Disable QStringBuilder") + ENDIF() + + # Ensure to be linked with static Qt library on Widnows + IF(WIN32 AND NOT BUILD_SHARED_LIBS) + STRING(REPLACE "-DQT_DLL" "" QT_DEFINITIONS "${QT_DEFINITIONS}") + SET(QT_DEFINITIONS ${QT_DEFINITIONS} "-DQT_NODLL") + ENDIF() + + # Include the cmake file needed to use qt4 + INCLUDE( ${QT_USE_FILE} ) + SET(PC_Requires "QtCore") +ENDIF() + +IF (NOT WIN32) + SET( QT_DONT_USE_QTGUI TRUE ) +ENDIF() + + +#add extra search paths for libraries and includes +SET (LIB_SUFFIX "" CACHE STRING "Define suffix of directory name (32/64)" ) +SET (LIB_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}" CACHE STRING "Directory where lib will install") +SET (INCLUDE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/include" CACHE PATH "The directory the headers are installed in") +SET (CMAKECONFIG_INSTALL_DIR "${LIB_INSTALL_DIR}/cmake/${CMAKE_PROJECT_NAME}${QJSON_SUFFIX}" CACHE PATH "Directory where to install QJSONConfig.cmake") + +set(QJSON_LIB_MAJOR_VERSION "0") +set(QJSON_LIB_MINOR_VERSION "9") +set(QJSON_LIB_PATCH_VERSION "0") + +set(QJSON_LIB_VERSION_STRING "${QJSON_LIB_MAJOR_VERSION}.${QJSON_LIB_MINOR_VERSION}.${QJSON_LIB_PATCH_VERSION}") + +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" ) + +# pkg-config +IF (NOT WIN32) + CONFIGURE_FILE (${CMAKE_CURRENT_SOURCE_DIR}/QJson.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/QJson${QJSON_SUFFIX}.pc + @ONLY) + INSTALL (FILES ${CMAKE_CURRENT_BINARY_DIR}/QJson${QJSON_SUFFIX}.pc + DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) +ENDIF (NOT WIN32) + +# Subdirs +ADD_SUBDIRECTORY(src) +IF (KDE4_BUILD_TESTS OR QJSON_BUILD_TESTS) + enable_testing() + ADD_SUBDIRECTORY(tests) +ENDIF (KDE4_BUILD_TESTS OR QJSON_BUILD_TESTS) + +CONFIGURE_FILE( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + +INSTALL(EXPORT qjson-export DESTINATION ${CMAKECONFIG_INSTALL_DIR} FILE QJSON${QJSON_SUFFIX}Targets.cmake) + +# figure out the relative path from the installed Config.cmake file to the install prefix (which may be at +# runtime different from the chosen CMAKE_INSTALL_PREFIX if under Windows the package was installed anywhere) +# This relative path will be configured into the QJSONConfig.cmake +file(RELATIVE_PATH relInstallDir ${CMAKE_INSTALL_PREFIX}/${CMAKECONFIG_INSTALL_DIR} ${CMAKE_INSTALL_PREFIX} ) + +# cmake-modules +CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/QJSONConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/QJSON${QJSON_SUFFIX}Config.cmake + @ONLY) +CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/QJSONConfigVersion.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/QJSON${QJSON_SUFFIX}ConfigVersion.cmake + @ONLY) +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/QJSON${QJSON_SUFFIX}Config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/QJSON${QJSON_SUFFIX}ConfigVersion.cmake + DESTINATION "${CMAKECONFIG_INSTALL_DIR}") + +ADD_CUSTOM_TARGET(uninstall + "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") diff --git a/3rdparty/qjson/COPYING.lib b/3rdparty/qjson/COPYING.lib new file mode 100644 index 00000000..08f25cd2 --- /dev/null +++ b/3rdparty/qjson/COPYING.lib @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 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 Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + 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 Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +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 other code 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. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + 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, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser 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 combine 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) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) 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. + + d) 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. + + e) 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 materials to be 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 with +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 Lesser 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 version 2.1, as published by the Free Software Foundation. + + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/3rdparty/qjson/ChangeLog b/3rdparty/qjson/ChangeLog new file mode 100644 index 00000000..65cde20d --- /dev/null +++ b/3rdparty/qjson/ChangeLog @@ -0,0 +1,111 @@ +Tue Nov 29 16:35:04 CET 2016 Flavio Castelli + + * Release 0.9.0: + - Qt5 support + - Added indent to arrays + - Replaced the scanner with a new Flex-based one + - Bison 2.7 compatibility + +Mon Jan 28 23:01:40 CET 2013 Flavio Castelli + + * Fix compilation on BlackBerry 10. + +Tue Nov 27 11:04:12 CET 2012 Flavio Castelli + + * Relase 0.8.1: + - ensure API and ABI compatibility with 0.7.1 + +Thu Nov 22 21:20:11 CET 2012 Flavio Castelli + + * Fix unsafe pointer usage in Serializer::serialize() + +---------------------------------------------------------------------- +Wed Nov 21 22:01:51 CET 2012 Flavio Castelli + + * Version 0.8.0 released + +Tue Nov 20 11:19:49 CET 2012 Flavio Castelli + + * Serializer: handle QVariantHash + +Tue Oct 30 15:50:10 CET 2012 Flavio Castelli + + * Improve error handling inside of Serializer + * Serializer: handle quint16. + +Tue Jan 31 10:15:06 CET 2012 Flavio Castelli + + * Make possible to build qjson as an OS X framework. + +Fri Nov 04 16:50:56 CET 2011 Flavio Castelli + + * Make possible to set double precision during serialization. + +Wed Aug 24 17:58:56 CEST 2011 Flavio Castelli + + * Buildsystem adjustments, fix issues mentioned here: + - http://lists.kde.org/?l=kde-buildsystem&m=130947194605100&w=3 + - http://lists.kde.org/?l=kde-buildsystem&m=128835747626464&w=3 + The biggest difference now is that FindQJSON.cmake is not provided + anymore. Instead, QJSONConfig.cmake and QJSONConfigVersion.cmake are + installed and can be used in find_package(QJSON) calls. Applications + using QJson can write their own FindQJSON.cmake files if they need to. + +Fri Apr 23:04:29 CEST 2011 Flavio Castelli + + * Fixed QVariant de-serialization. QVariant properties were ignored + during QVariant -> QObject conversion. + +Sun Dec 18:59:28 CET 2010 Flavio Castelli + + * It's now possible to indent the output produced by the Serializer. + +Mon Sep 06 18:53:02 CEST 2010 Flavio Castelli + + * 50% performance improvement when parsing numbers. + +Sun Jul 04 15:41:08 CEST 2010 Flavio Castelli + + * fix make install when not installing as root + * provide "make uninstall" + +Tue Jun 15 13:16:57 CEST 2010 Flavio Castelli + + * Allow top level values + +---------------------------------------------------------------------- +Sat Mar 13 23:57:00 CEST 2009 - flavio@castelli.name + + * Merged the symbian branch into master, + +---------------------------------------------------------------------- +Sun Oct 11 19:18:00 CEST 2009 - flavio@castelli.name + + * Updated to 0.6.3: fixed a bug affecting ulonglong numbers serialization. + +------------------------------------------------------------------- +Wed Sep 15 19:21:00 CEST 2009 - flavio@castelli.name + + * Updated to 0.6.2: fixed a bug affecting ulonglong numbers parsing. + +------------------------------------------------------------------- +Wed Sep 09 09:55:00 CEST 2009 - flavio@castelli.name + + * Updated to 0.6.1: relevant bugs fixed. + * Moved the SerializerRunnable class inside QJson namespace. + * Fixed a bug in cmdline_tester. + +------------------------------------------------------------------- +Mon Jul 20 15:24:32 CEST 2009 - prusnak@suse.cz + + * Updated to 0.6.0 (KDE SVN rev 999750). + +------------------------------------------------------------------- +Mon Apr 07 00:00:00 UTC 2009 - flavio@castelli.name + + * Released 0.5.1 - added unicode support. + +------------------------------------------------------------------- +Mon Apr 03 00:00:00 UTC 2009 - flavio@castelli.name + + * First release. diff --git a/3rdparty/qjson/QJSONConfig.cmake.in b/3rdparty/qjson/QJSONConfig.cmake.in new file mode 100644 index 00000000..0c4a1e6e --- /dev/null +++ b/3rdparty/qjson/QJSONConfig.cmake.in @@ -0,0 +1,6 @@ +GET_FILENAME_COMPONENT(myDir ${CMAKE_CURRENT_LIST_FILE} PATH) + +SET(QJSON_LIBRARIES qjson) +SET(QJSON_INCLUDE_DIR "@INCLUDE_INSTALL_DIR@") + +include(${myDir}/QJSON@QJSON_SUFFIX@Targets.cmake) diff --git a/3rdparty/qjson/QJSONConfigVersion.cmake.in b/3rdparty/qjson/QJSONConfigVersion.cmake.in new file mode 100644 index 00000000..6005d2dc --- /dev/null +++ b/3rdparty/qjson/QJSONConfigVersion.cmake.in @@ -0,0 +1,9 @@ +SET(PACKAGE_VERSION "@QJSON_LIB_VERSION_STRING@") +IF (PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) + SET(PACKAGE_VERSION_EXACT TRUE) +ENDIF (PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) +IF (NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) + SET(PACKAGE_VERSION_COMPATIBLE TRUE) +ELSE (NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) + SET(PACKAGE_VERSION_UNSUITABLE TRUE) +ENDIF (NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) diff --git a/3rdparty/qjson/QJson.pc.in b/3rdparty/qjson/QJson.pc.in new file mode 100644 index 00000000..f67d1a15 --- /dev/null +++ b/3rdparty/qjson/QJson.pc.in @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=@LIB_INSTALL_DIR@ +includedir=@INCLUDE_INSTALL_DIR@ + +Name: QJson +Description: QJson is a qt-based library that maps JSON data to QVariant objects +Version: @QJSON_LIB_MAJOR_VERSION@.@QJSON_LIB_MINOR_VERSION@.@QJSON_LIB_PATCH_VERSION@ +Requires: @PC_Requires@ +Libs: -L${libdir} -lqjson@QJSON_SUFFIX@ +Cflags: -I${includedir} diff --git a/3rdparty/qjson/README.license b/3rdparty/qjson/README.license new file mode 100644 index 00000000..3ede3132 --- /dev/null +++ b/3rdparty/qjson/README.license @@ -0,0 +1,89 @@ +Qjson version xxxx, Date + +The following files are licensed under LGPL V2.1: +------------------------------------------------ +src/json_parser.yy +src/json_scanner.cpp +src/json_scanner.h +src/parser.cpp +src/parser.h +src/parser_p.h +src/parserrunnable.cpp +src/parserrunnable.h +src/qjson_debug.h +src/qjson_export.h +src/qobjecthelper.cpp +src/serializer.cpp +src/qobjecthelper.h +src/serializer.h +src/serializerrunnable.cpp +src/serializerrunnable.h +tests/cmdline_tester/cmdline_tester.cpp +tests/cmdline_tester/cmdlineparser.cpp +tests/cmdline_tester/cmdlineparser.h +tests/parser/testparser.cpp +tests/qobjecthelper/person.h +tests/qobjecthelper/testqobjecthelper.cpp +tests/serializer/testserializer.cpp + + +The following files are licensed under GPL V2 with Bison Exception: +-------------------------------------------------------------------- +/src/json_parser.cc +/src/stack.hh +/src/location.hh +/src/position.hh +/src/json_parser.hh + + +Copyrights: +---------- +Copyright (C) 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. +Copyright (C) 2009 Flavio Castelli 2009 Frank Osterfeld +Copyright (C) 2008 Flavio Castelli +Copyright (C) 2009 Till Adam +Copyright (C) 2009 Michael Leupold +Copyright (C) 2009 Flavio Castelli +Copyright (C) 2009 Frank Osterfeld +Copyright (C) 2009 Pino Toscano +Copyright (C) 2010 Flavio Castelli + + +GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999: +------------------------------------------------------------- + +Checkout COPYING.lib + + +GPL V2 with Bison Exception: +---------------------------- +Copyright (C) 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. + diff --git a/3rdparty/qjson/README.md b/3rdparty/qjson/README.md new file mode 100644 index 00000000..e14474ee --- /dev/null +++ b/3rdparty/qjson/README.md @@ -0,0 +1,107 @@ +# QJson [![Build Status](https://travis-ci.org/flavio/qjson.svg?branch=master)](https://travis-ci.org/flavio/qjson) + +JSON (JavaScript Object Notation) is a lightweight data-interchange format. +It can represents integer, real number, string, an ordered sequence of value, and a collection of name/value pairs. + +QJson is a qt-based library that maps JSON data to QVariant objects. +JSON arrays will be mapped to QVariantList instances, while JSON's objects will be mapped to QVariantMap. + +# Install + +QJson requires: + - Qt 4.5 or greater + - cmake 2.6 or greater + +Some possible cmake options: + - `-DCMAKE_BUILD_TYPE=DEBUG`: enables some debug output (other than making + easier to debug the code) + - `-DQJSON_BUILD_TESTS=yes` or `-DKDE4_BUILD_TESTS=yes`: builds the unit tests + - `-DCMAKE_INSTALL_PREFIX=${HOME}/testinstall`: install qjson in a custom directory + - `-DCMAKE_INCLUDE_PATH=${HOME}/testinstall/include`: include a custom include directory + - `-DCMAKE_LIBRARY_PATH=${HOME}/testinstall/lib`: include a custom library directory + - `-DLIB_DESTINATION=lib64`: if you have a 64 bit system with separate + libraries for 64 bit libraries + - `-DQJSON_VERBOSE_DEBUG_OUTPUT:BOOL=ON`: more debugging statements are + generated by the parser. It's useful only if you are trying to fix + the bison grammar. + +For Unix/Linux/Mac: + + mkdir build + cd build + cmake -DCMAKE_INSTALL_PREFIX=_preferred_path_ .. + make + make install + /sbin/ldconfig #if necessary + +# Contribute + +The recommended way to submit your changes is via a pull request. + +Before submitting a patch please ensure: + + * Patched code compiles. + * The patch is fixing a specific issue or implementing a new feature + (it’s not doing multiple things at the same time). + * QJson unit tests have been updated. + * QJson unit tests are passing. + +## Unit testing + +QJson unit tests are located under the `tests` directory. You can enable them +passing the `-DQJSON_BUILD_TESTS=yes` option to `cmake`. + +> Note well: make sure you followed the build instructions. + + +To run all the unit tests move into the build directory and type: + +``` +make tests +``` + +If you want to run the `QJson::Parser` unit tests just type: + +``` +./test/parser/testparser +``` + +If you want to run the `QJson::Serializer` unit tests just type: + +``` +./test/serializer/testserializer +``` + +If you want to run the `QJson::QObjectHelper` tests just type: + +``` +./tests/qobjecthelper/testqobjecthelper +``` + +If you want to test the QJson parser against a specific JSON object you can +use the `cmdline_tester` program. + +This binary is located under the `tests` directory and has a +straightforward syntax: + +``` +./tests/cmdline_tester/cmdline_tester text_file_containing_json_object +``` + +The command will convert the JSON object to a `QVariant` and dump it to stdout. +More options are available via cli options, just checkout the `--help` output. + + +**Note well:** cmdline_tester relies on `qDebug()` to dump the object. `qDebug` +has some limitations, like being unable to print utf8 chars. + +# License + +This library is licensed under the Lesser GNU General Public License version 2.1. +See the COPYING.lib file for more information. + +# Resources + +* [Website](http://qjson.sourceforge.net/) +* [Mailing List](https://lists.sourceforge.net/mailman/listinfo/qjson-devel) +* Project Lead/Maintainer (2008-current): [Flavio Castelli](mailto:flavio@castelli.name). diff --git a/3rdparty/qjson/cmake_uninstall.cmake.in b/3rdparty/qjson/cmake_uninstall.cmake.in new file mode 100644 index 00000000..776e9c96 --- /dev/null +++ b/3rdparty/qjson/cmake_uninstall.cmake.in @@ -0,0 +1,17 @@ +IF(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") + +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}\"") + 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) +ENDFOREACH(file) diff --git a/3rdparty/qjson/doc/Doxyfile b/3rdparty/qjson/doc/Doxyfile new file mode 100644 index 00000000..c21eb01a --- /dev/null +++ b/3rdparty/qjson/doc/Doxyfile @@ -0,0 +1,1851 @@ +# Doxyfile 1.8.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = QJson + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 0.9.0 + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = the easiest way to manage JSON objects with Qt + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = ./ + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all +# comments according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you +# can mix doxygen, HTML, and XML commands with Markdown formatting. +# Disable only in case of backward compatibilities issues. + +MARKDOWN_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = YES + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. The create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ./qjson.dox \ + ../src + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox \ + *.py \ + *.f90 \ + *.f \ + *.vhd \ + *.vhdl \ + *.C \ + *.CC \ + *.C++ \ + *.II \ + *.I++ \ + *.H \ + *.HH \ + *.H++ \ + *.CS \ + *.PHP \ + *.PHP3 \ + *.M \ + *.MM \ + *.PY \ + *.F90 \ + *.F \ + *.VHD \ + *.VHDL + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = ./header.html + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = ./footer.html + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# style sheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of +# entries shown in the various tree structured indices initially; the user +# can expand and collapse entries dynamically later on. Doxygen will expand +# the tree to such a level that at most the specified number of entries are +# visible (unless a fully collapsed tree already exceeds this amount). +# So setting the number of entries 1 will produce a full collapsed tree by +# default. 0 is a special value representing an infinite number of entries +# and will result in a full expanded tree by default. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you may also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. +# However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = NO + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# managable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 1000 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/3rdparty/qjson/doc/footer.html b/3rdparty/qjson/doc/footer.html new file mode 100644 index 00000000..fe2e2a77 --- /dev/null +++ b/3rdparty/qjson/doc/footer.html @@ -0,0 +1,32 @@ +
+ + + + + + + +
+ + SourceForge Logo + + hosts this site. + + + Send comments to:
+ QJson Developers +
+ + + + + + diff --git a/3rdparty/qjson/doc/header.html b/3rdparty/qjson/doc/header.html new file mode 100644 index 00000000..d0ca9936 --- /dev/null +++ b/3rdparty/qjson/doc/header.html @@ -0,0 +1,13 @@ + + + +QJson - a Qt based library for mapping JSON data to QVariant objects + + + + + + + QJson home page + +
diff --git a/3rdparty/qjson/doc/qjson.dox b/3rdparty/qjson/doc/qjson.dox new file mode 100644 index 00000000..ea84e755 --- /dev/null +++ b/3rdparty/qjson/doc/qjson.dox @@ -0,0 +1,87 @@ +/** +\mainpage +\section _intro Introduction + +JSON (JavaScript Object Notation) + is a lightweight data-interchange format. +It can represents integer, real number, string, an ordered sequence of value, and +a collection of name/value pairs. + +QJson is a qt-based library that maps JSON data to QVariant objects. + +JSON arrays will be mapped to QVariantList instances, while JSON's objects will +be mapped to QVariantMap. + +\section _usage Usage +Converting JSON's data to QVariant instance is really simple: +\code +// create a JSonDriver instance +QJson::Parser parser; + +bool ok; + +// json is a QString containing the data to convert +QVariant result = parser.parse (json, &ok); +\endcode + +Suppose you're going to convert this JSON data: +\verbatim +{ + "encoding" : "UTF-8", + "plug-ins" : [ + "python", + "c++", + "ruby" + ], + "indent" : { "length" : 3, "use_space" : true } +} +\endverbatim + +The following code would convert the JSON data and parse it: +\code +QJson::Parser parser; +bool ok; + +QVariantMap result = parser.parse (json, &ok).toMap(); +if (!ok) { + qFatal("An error occured during parsing"); + exit (1); +} + +qDebug() << "encoding:" << result["encoding"].toString(); +qDebug() << "plugins:"; + +foreach (QVariant plugin, result["plug-ins"].toList()) { + qDebug() << "\t-" << plugin.toString(); +} + +QVariantMap nestedMap = result["indent"].toMap(); +qDebug() << "length:" << nestedMap["length"].toInt(); +qDebug() << "use_space:" << nestedMap["use_space"].toBool(); +\endcode +The output would be: +\verbatim +encoding: "UTF-8" +plugins: + - "python" + - "c++" + - "ruby" +length: 3 +use_space: true +\endverbatim + +The QJson::QObjectHelper class permits to serialize QObject instances into JSON. QJson::QObjectHelper also allows to +initialize a QObject using the values stored inside of a JSON object. + +\section _build Build instructions +QJson build system is based on cmake. Download QJson sources, extract them, move inside the sources directory and then: +\code +mkdir build +cd build +cmake .. +make +sudo make install +\endcode + +\author Flavio Castelli +*/ diff --git a/3rdparty/qjson/include/QJson/Parser b/3rdparty/qjson/include/QJson/Parser new file mode 100644 index 00000000..68f06e43 --- /dev/null +++ b/3rdparty/qjson/include/QJson/Parser @@ -0,0 +1 @@ +#include "../../src/parser.h" diff --git a/3rdparty/qjson/include/QJson/QObjectHelper b/3rdparty/qjson/include/QJson/QObjectHelper new file mode 100644 index 00000000..1b72c2e0 --- /dev/null +++ b/3rdparty/qjson/include/QJson/QObjectHelper @@ -0,0 +1 @@ +#include "../../src/qobjecthelper.h" diff --git a/3rdparty/qjson/include/QJson/Serializer b/3rdparty/qjson/include/QJson/Serializer new file mode 100644 index 00000000..2b7fe7a9 --- /dev/null +++ b/3rdparty/qjson/include/QJson/Serializer @@ -0,0 +1 @@ +#include "../../src/serializer.h" diff --git a/3rdparty/qjson/src/.gitignore b/3rdparty/qjson/src/.gitignore new file mode 100644 index 00000000..04ec50a5 --- /dev/null +++ b/3rdparty/qjson/src/.gitignore @@ -0,0 +1,3 @@ +moc_* +*.o +Makefile diff --git a/3rdparty/qjson/src/CMakeLists.txt.bak b/3rdparty/qjson/src/CMakeLists.txt.bak new file mode 100755 index 00000000..0ae7c406 --- /dev/null +++ b/3rdparty/qjson/src/CMakeLists.txt.bak @@ -0,0 +1,73 @@ +# add_custom_command (OUTPUT ${qjson_SOURCE_DIR}/lib/json_parser.cc +# PRE_BUILD +# COMMAND bison -t -o json_parser.cc -d json_parser.yy +# DEPENDS json_parser.yy +# WORKING_DIRECTORY ${qjson_SOURCE_DIR}/lib/ +# ) + +# To regenerate json_scanner.cc use: +# flex json_scanner.yy + +set(qjson_MOC_HDRS + parserrunnable.h + serializerrunnable.h +) + +IF (NOT Qt5Core_FOUND) + qt4_wrap_cpp(qjson_MOC_SRCS ${qjson_MOC_HDRS}) +ENDIF() + +set (qjson_SRCS parser.cpp qobjecthelper.cpp json_scanner.cpp json_parser.cc parserrunnable.cpp serializer.cpp serializerrunnable.cpp) +set (qjson_HEADERS parser.h parserrunnable.h qobjecthelper.h serializer.h serializerrunnable.h qjson_export.h) + +# Required to use the intree copy of FlexLexer.h +INCLUDE_DIRECTORIES(.) + +# Special hack to fix scanner test compilation on Windows +# JsonScanner class is not for export. So need to make static +# lib what can be compiled against scanner test. +if(WIN32 AND QJSON_BUILD_TESTS AND BUILD_SHARED_LIBS) + add_library(qjson_scanner STATIC json_scanner.cpp) +endif() + +add_library (qjson${QJSON_SUFFIX} ${qjson_SRCS} ${qjson_MOC_SRCS} ${qjson_HEADERS}) +IF (Qt5Core_FOUND) + target_link_libraries( qjson${QJSON_SUFFIX} ${Qt5Core_LIBRARIES}) +ELSE() + target_link_libraries( qjson${QJSON_SUFFIX} ${QT_LIBRARIES}) +ENDIF() + +if(NOT ANDROID) + set_target_properties(qjson${QJSON_SUFFIX} PROPERTIES + VERSION ${QJSON_LIB_MAJOR_VERSION}.${QJSON_LIB_MINOR_VERSION}.${QJSON_LIB_PATCH_VERSION} + SOVERSION ${QJSON_LIB_MAJOR_VERSION} + ) +endif() + +if(NOT BUILD_SHARED_LIBS) + set_target_properties( qjson${QJSON_SUFFIX} PROPERTIES COMPILE_DEFINITIONS "QJSON_STATIC") +endif() + +set_target_properties(qjson${QJSON_SUFFIX} PROPERTIES + DEFINE_SYMBOL QJSON_MAKEDLL + PUBLIC_HEADER "${qjson_HEADERS}" + FRAMEWORK ${OSX_FRAMEWORK} + ) + +INSTALL(TARGETS qjson${QJSON_SUFFIX} EXPORT qjson-export + LIBRARY DESTINATION ${LIB_INSTALL_DIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin + ARCHIVE DESTINATION ${LIB_INSTALL_DIR} + FRAMEWORK DESTINATION ${FRAMEWORK_INSTALL_DIR} + PUBLIC_HEADER DESTINATION ${INCLUDE_INSTALL_DIR}/qjson${QJSON_SUFFIX} +) + +if(MSVC) + get_target_property(LOCATION qjson LOCATION_DEBUG) + string(REGEX REPLACE "\\.[^.]*$" ".pdb" LOCATION "${LOCATION}") + install(FILES ${LOCATION} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin CONFIGURATIONS Debug) + + get_target_property(LOCATION qjson LOCATION_RELWITHDEBINFO) + string(REGEX REPLACE "\\.[^.]*$" ".pdb" LOCATION "${LOCATION}") + install(FILES ${LOCATION} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin CONFIGURATIONS RelWithDebInfo) +endif(MSVC) diff --git a/3rdparty/qjson/src/FlexLexer.h b/3rdparty/qjson/src/FlexLexer.h new file mode 100644 index 00000000..bad4ce03 --- /dev/null +++ b/3rdparty/qjson/src/FlexLexer.h @@ -0,0 +1,206 @@ +// -*-C++-*- +// FlexLexer.h -- define interfaces for lexical analyzer classes generated +// by flex + +// Copyright (c) 1993 The Regents of the University of California. +// All rights reserved. +// +// This code is derived from software contributed to Berkeley by +// Kent Williams and Tom Epperly. +// +// 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 above copyright +// notice, this list of conditions and the following disclaimer. +// 2. 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 name of the University nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE. + +// This file defines FlexLexer, an abstract class which specifies the +// external interface provided to flex C++ lexer objects, and yyFlexLexer, +// which defines a particular lexer class. +// +// If you want to create multiple lexer classes, you use the -P flag +// to rename each yyFlexLexer to some other xxFlexLexer. You then +// include in your other sources once per lexer class: +// +// #undef yyFlexLexer +// #define yyFlexLexer xxFlexLexer +// #include +// +// #undef yyFlexLexer +// #define yyFlexLexer zzFlexLexer +// #include +// ... + +#ifndef __FLEX_LEXER_H +// Never included before - need to define base class. +#define __FLEX_LEXER_H + +#include +# ifndef FLEX_STD +# define FLEX_STD std:: +# endif + +extern "C++" { + +struct yy_buffer_state; +typedef int yy_state_type; + +class FlexLexer { +public: + virtual ~FlexLexer() { } + + const char* YYText() const { return yytext; } + int YYLeng() const { return yyleng; } + + virtual void + yy_switch_to_buffer( struct yy_buffer_state* new_buffer ) = 0; + virtual struct yy_buffer_state* + yy_create_buffer( FLEX_STD istream* s, int size ) = 0; + virtual void yy_delete_buffer( struct yy_buffer_state* b ) = 0; + virtual void yyrestart( FLEX_STD istream* s ) = 0; + + virtual int yylex() = 0; + + // Call yylex with new input/output sources. + int yylex( FLEX_STD istream* new_in, FLEX_STD ostream* new_out = 0 ) + { + switch_streams( new_in, new_out ); + return yylex(); + } + + // Switch to new input/output streams. A nil stream pointer + // indicates "keep the current one". + virtual void switch_streams( FLEX_STD istream* new_in = 0, + FLEX_STD ostream* new_out = 0 ) = 0; + + int lineno() const { return yylineno; } + + int debug() const { return yy_flex_debug; } + void set_debug( int flag ) { yy_flex_debug = flag; } + +protected: + char* yytext; + int yyleng; + int yylineno; // only maintained if you use %option yylineno + int yy_flex_debug; // only has effect with -d or "%option debug" +}; + +} +#endif // FLEXLEXER_H + +#if defined(yyFlexLexer) || ! defined(yyFlexLexerOnce) +// Either this is the first time through (yyFlexLexerOnce not defined), +// or this is a repeated include to define a different flavor of +// yyFlexLexer, as discussed in the flex manual. +#define yyFlexLexerOnce + +extern "C++" { + +class yyFlexLexer : public FlexLexer { +public: + // arg_yyin and arg_yyout default to the cin and cout, but we + // only make that assignment when initializing in yylex(). + yyFlexLexer( FLEX_STD istream* arg_yyin = 0, FLEX_STD ostream* arg_yyout = 0 ); + + virtual ~yyFlexLexer(); + + void yy_switch_to_buffer( struct yy_buffer_state* new_buffer ); + struct yy_buffer_state* yy_create_buffer( FLEX_STD istream* s, int size ); + void yy_delete_buffer( struct yy_buffer_state* b ); + void yyrestart( FLEX_STD istream* s ); + + void yypush_buffer_state( struct yy_buffer_state* new_buffer ); + void yypop_buffer_state(); + + virtual int yylex(); + virtual void switch_streams( FLEX_STD istream* new_in, FLEX_STD ostream* new_out = 0 ); + virtual int yywrap(); + +protected: + virtual int LexerInput( char* buf, int max_size ); + virtual void LexerOutput( const char* buf, int size ); + virtual void LexerError( const char* msg ); + + void yyunput( int c, char* buf_ptr ); + int yyinput(); + + void yy_load_buffer_state(); + void yy_init_buffer( struct yy_buffer_state* b, FLEX_STD istream* s ); + void yy_flush_buffer( struct yy_buffer_state* b ); + + int yy_start_stack_ptr; + int yy_start_stack_depth; + int* yy_start_stack; + + void yy_push_state( int new_state ); + void yy_pop_state(); + int yy_top_state(); + + yy_state_type yy_get_previous_state(); + yy_state_type yy_try_NUL_trans( yy_state_type current_state ); + int yy_get_next_buffer(); + + FLEX_STD istream* yyin; // input source for default LexerInput + FLEX_STD ostream* yyout; // output sink for default LexerOutput + + // yy_hold_char holds the character lost when yytext is formed. + char yy_hold_char; + + // Number of characters read into yy_ch_buf. + int yy_n_chars; + + // Points to current character in buffer. + char* yy_c_buf_p; + + int yy_init; // whether we need to initialize + int yy_start; // start state number + + // Flag which is used to allow yywrap()'s to do buffer switches + // instead of setting up a fresh yyin. A bit of a hack ... + int yy_did_buffer_switch_on_eof; + + + size_t yy_buffer_stack_top; /**< index of top of stack. */ + size_t yy_buffer_stack_max; /**< capacity of stack. */ + struct yy_buffer_state ** yy_buffer_stack; /**< Stack as an array. */ + void yyensure_buffer_stack(void); + + // The following are not always needed, but may be depending + // on use of certain flex features (like REJECT or yymore()). + + yy_state_type yy_last_accepting_state; + char* yy_last_accepting_cpos; + + yy_state_type* yy_state_buf; + yy_state_type* yy_state_ptr; + + char* yy_full_match; + int* yy_full_state; + int yy_full_lp; + + int yy_lp; + int yy_looking_for_trail_begin; + + int yy_more_flag; + int yy_more_len; + int yy_more_offset; + int yy_prev_more_offset; +}; + +} + +#endif // yyFlexLexer || ! yyFlexLexerOnce + diff --git a/3rdparty/qjson/src/json_parser.cc b/3rdparty/qjson/src/json_parser.cc new file mode 100644 index 00000000..5c1788f2 --- /dev/null +++ b/3rdparty/qjson/src/json_parser.cc @@ -0,0 +1,1099 @@ +/* A Bison parser, made by GNU Bison 2.7. */ + +/* Skeleton implementation for Bison LALR(1) parsers in C++ + + Copyright (C) 2002-2012 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 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 . */ + +/* 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. */ + + +/* First part of user declarations. */ + +/* Line 279 of lalr1.cc */ +#line 38 "json_parser.cc" + + +#include "json_parser.hh" + +/* User implementation prologue. */ + +/* Line 285 of lalr1.cc */ +#line 46 "json_parser.cc" + + +# ifndef YY_NULL +# if defined __cplusplus && 201103L <= __cplusplus +# define YY_NULL nullptr +# else +# define YY_NULL 0 +# endif +# endif + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* FIXME: INFRINGES ON USER NAME SPACE */ +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +/* 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). */ + +# ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (N) \ + { \ + (Current).begin = YYRHSLOC (Rhs, 1).begin; \ + (Current).end = YYRHSLOC (Rhs, N).end; \ + } \ + else \ + { \ + (Current).begin = (Current).end = YYRHSLOC (Rhs, 0).end; \ + } \ + while (/*CONSTCOND*/ false) +# endif + + +/* Suppress unused-variable warnings by "using" E. */ +#define YYUSE(e) ((void) (e)) + +/* Enable debugging if requested. */ +#if YYDEBUG + +/* A pseudo ostream that takes yydebug_ into account. */ +# define YYCDEBUG if (yydebug_) (*yycdebug_) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug_) \ + { \ + *yycdebug_ << Title << ' '; \ + yy_symbol_print_ ((Type), (Value), (Location)); \ + *yycdebug_ << std::endl; \ + } \ +} while (false) + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug_) \ + yy_reduce_print_ (Rule); \ +} while (false) + +# define YY_STACK_PRINT() \ +do { \ + if (yydebug_) \ + yystack_print_ (); \ +} while (false) + +#else /* !YYDEBUG */ + +# define YYCDEBUG if (false) std::cerr +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) YYUSE(Type) +# define YY_REDUCE_PRINT(Rule) static_cast(0) +# define YY_STACK_PRINT() static_cast(0) + +#endif /* !YYDEBUG */ + +#define yyerrok (yyerrstatus_ = 0) +#define yyclearin (yychar = yyempty_) + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab +#define YYRECOVERING() (!!yyerrstatus_) + + +namespace yy { +/* Line 353 of lalr1.cc */ +#line 141 "json_parser.cc" + + /* Return 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. */ + std::string + json_parser::yytnamerr_ (const char *yystr) + { + if (*yystr == '"') + { + std::string yyr; + 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: + yyr += *yyp; + break; + + case '"': + return yyr; + } + do_not_strip_quotes: ; + } + + return yystr; + } + + + /// Build a parser object. + json_parser::json_parser (QJson::ParserPrivate* driver_yyarg) + : +#if YYDEBUG + yydebug_ (false), + yycdebug_ (&std::cerr), +#endif + driver (driver_yyarg) + { + } + + json_parser::~json_parser () + { + } + +#if YYDEBUG + /*--------------------------------. + | Print this symbol on YYOUTPUT. | + `--------------------------------*/ + + inline void + json_parser::yy_symbol_value_print_ (int yytype, + const semantic_type* yyvaluep, const location_type* yylocationp) + { + YYUSE (yylocationp); + YYUSE (yyvaluep); + std::ostream& yyo = debug_stream (); + std::ostream& yyoutput = yyo; + YYUSE (yyoutput); + switch (yytype) + { + default: + break; + } + } + + + void + json_parser::yy_symbol_print_ (int yytype, + const semantic_type* yyvaluep, const location_type* yylocationp) + { + *yycdebug_ << (yytype < yyntokens_ ? "token" : "nterm") + << ' ' << yytname_[yytype] << " (" + << *yylocationp << ": "; + yy_symbol_value_print_ (yytype, yyvaluep, yylocationp); + *yycdebug_ << ')'; + } +#endif + + void + json_parser::yydestruct_ (const char* yymsg, + int yytype, semantic_type* yyvaluep, location_type* yylocationp) + { + YYUSE (yylocationp); + YYUSE (yymsg); + YYUSE (yyvaluep); + + if (yymsg) + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + + default: + break; + } + } + + void + json_parser::yypop_ (unsigned int n) + { + yystate_stack_.pop (n); + yysemantic_stack_.pop (n); + yylocation_stack_.pop (n); + } + +#if YYDEBUG + std::ostream& + json_parser::debug_stream () const + { + return *yycdebug_; + } + + void + json_parser::set_debug_stream (std::ostream& o) + { + yycdebug_ = &o; + } + + + json_parser::debug_level_type + json_parser::debug_level () const + { + return yydebug_; + } + + void + json_parser::set_debug_level (debug_level_type l) + { + yydebug_ = l; + } +#endif + + inline bool + json_parser::yy_pact_value_is_default_ (int yyvalue) + { + return yyvalue == yypact_ninf_; + } + + inline bool + json_parser::yy_table_value_is_error_ (int yyvalue) + { + return yyvalue == yytable_ninf_; + } + + int + json_parser::parse () + { + /// Lookahead and lookahead in internal form. + int yychar = yyempty_; + int yytoken = 0; + + // State. + int yyn; + int yylen = 0; + int yystate = 0; + + // Error handling. + int yynerrs_ = 0; + int yyerrstatus_ = 0; + + /// Semantic value of the lookahead. + static semantic_type yyval_default; + semantic_type yylval = yyval_default; + /// Location of the lookahead. + location_type yylloc; + /// The locations where the error started and ended. + location_type yyerror_range[3]; + + /// $$. + semantic_type yyval; + /// @$. + location_type yyloc; + + int yyresult; + + // FIXME: This shoud be completely indented. It is not yet to + // avoid gratuitous conflicts when merging into the master branch. + try + { + YYCDEBUG << "Starting parse" << std::endl; + + + /* Initialize the stacks. The initial state will be pushed in + yynewstate, since the latter expects the semantical and the + location values to have been already stored, initialize these + stacks with a primary value. */ + yystate_stack_ = state_stack_type (0); + yysemantic_stack_ = semantic_stack_type (0); + yylocation_stack_ = location_stack_type (0); + yysemantic_stack_.push (yylval); + yylocation_stack_.push (yylloc); + + /* New state. */ + yynewstate: + yystate_stack_.push (yystate); + YYCDEBUG << "Entering state " << yystate << std::endl; + + /* Accept? */ + if (yystate == yyfinal_) + goto yyacceptlab; + + goto yybackup; + + /* Backup. */ + yybackup: + + /* Try to take a decision without lookahead. */ + yyn = yypact_[yystate]; + if (yy_pact_value_is_default_ (yyn)) + goto yydefault; + + /* Read a lookahead token. */ + if (yychar == yyempty_) + { + YYCDEBUG << "Reading a token: "; + yychar = yylex (&yylval, &yylloc, driver); + } + + /* Convert token to internal form. */ + if (yychar <= yyeof_) + { + yychar = yytoken = yyeof_; + YYCDEBUG << "Now at end of input." << std::endl; + } + 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; + + /* Reduce or error. */ + yyn = yytable_[yyn]; + if (yyn <= 0) + { + if (yy_table_value_is_error_ (yyn)) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + /* Shift the lookahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the token being shifted. */ + yychar = yyempty_; + + yysemantic_stack_.push (yylval); + yylocation_stack_.push (yylloc); + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus_) + --yyerrstatus_; + + yystate = yyn; + 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: + yylen = yyr2_[yyn]; + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. Otherwise, use the top of the stack. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. */ + if (yylen) + yyval = yysemantic_stack_[yylen - 1]; + else + yyval = yysemantic_stack_[0]; + + // Compute the default @$. + { + slice slice (yylocation_stack_, yylen); + YYLLOC_DEFAULT (yyloc, slice, yylen); + } + + // Perform the reduction. + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: +/* Line 670 of lalr1.cc */ +#line 82 "json_parser.yy" + { + driver->m_result = (yysemantic_stack_[(1) - (1)]); + qjsonDebug() << "json_parser - parsing finished"; + } + break; + + case 3: +/* Line 670 of lalr1.cc */ +#line 87 "json_parser.yy" + { (yyval) = (yysemantic_stack_[(1) - (1)]); } + break; + + case 4: +/* Line 670 of lalr1.cc */ +#line 89 "json_parser.yy" + { + qCritical()<< "json_parser - syntax error found, " + << "forcing abort, Line" << (yyloc).begin.line << "Column" << (yyloc).begin.column; + YYABORT; + } + break; + + case 5: +/* Line 670 of lalr1.cc */ +#line 95 "json_parser.yy" + { + (yyval) = QVariant(QVariantMap()); + } + break; + + case 6: +/* Line 670 of lalr1.cc */ +#line 98 "json_parser.yy" + { + QVariantMap* map = (yysemantic_stack_[(3) - (2)]).value(); + (yyval) = QVariant(*map); + delete map; + } + break; + + case 7: +/* Line 670 of lalr1.cc */ +#line 104 "json_parser.yy" + { + QVariantMap* pair = new QVariantMap(); + pair->insert((yysemantic_stack_[(3) - (1)]).toString(), (yysemantic_stack_[(3) - (3)])); + (yyval).setValue(pair); + } + break; + + case 8: +/* Line 670 of lalr1.cc */ +#line 109 "json_parser.yy" + { + (yyval).value()->insert((yysemantic_stack_[(5) - (3)]).toString(), (yysemantic_stack_[(5) - (5)])); + } + break; + + case 9: +/* Line 670 of lalr1.cc */ +#line 113 "json_parser.yy" + { + (yyval) = QVariant(QVariantList()); + } + break; + + case 10: +/* Line 670 of lalr1.cc */ +#line 116 "json_parser.yy" + { + QVector* list = (yysemantic_stack_[(3) - (2)]).value* >(); + (yyval) = QVariant(list->toList()); + delete list; + } + break; + + case 11: +/* Line 670 of lalr1.cc */ +#line 122 "json_parser.yy" + { + QVector* list = new QVector(1); + list->replace(0, (yysemantic_stack_[(1) - (1)])); + (yyval).setValue(list); + } + break; + + case 12: +/* Line 670 of lalr1.cc */ +#line 127 "json_parser.yy" + { + (yyval).value* >()->append((yysemantic_stack_[(3) - (3)])); + } + break; + + +/* Line 670 of lalr1.cc */ +#line 549 "json_parser.cc" + default: + break; + } + + /* User semantic actions sometimes alter yychar, and that requires + that yytoken be updated with the new translation. We take the + approach of translating immediately before every use of yytoken. + One alternative is translating here after every semantic action, + but that translation would be missed if the semantic action + invokes YYABORT, YYACCEPT, or YYERROR immediately after altering + yychar. In the case of YYABORT or YYACCEPT, an incorrect + destructor might then be invoked immediately. In the case of + YYERROR, subsequent parser actions might lead to an incorrect + destructor call or verbose syntax error message before the + lookahead is translated. */ + YY_SYMBOL_PRINT ("-> $$ =", yyr1_[yyn], &yyval, &yyloc); + + yypop_ (yylen); + yylen = 0; + YY_STACK_PRINT (); + + yysemantic_stack_.push (yyval); + yylocation_stack_.push (yyloc); + + /* Shift the result of the reduction. */ + yyn = yyr1_[yyn]; + yystate = yypgoto_[yyn - yyntokens_] + yystate_stack_[0]; + if (0 <= yystate && yystate <= yylast_ + && yycheck_[yystate] == yystate_stack_[0]) + yystate = yytable_[yystate]; + else + yystate = yydefgoto_[yyn - yyntokens_]; + goto yynewstate; + + /*------------------------------------. + | yyerrlab -- here on detecting error | + `------------------------------------*/ + yyerrlab: + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = yytranslate_ (yychar); + + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus_) + { + ++yynerrs_; + if (yychar == yyempty_) + yytoken = yyempty_; + error (yylloc, yysyntax_error_ (yystate, yytoken)); + } + + yyerror_range[1] = yylloc; + 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, &yylloc); + 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 (false) + goto yyerrorlab; + + yyerror_range[1] = yylocation_stack_[yylen - 1]; + /* Do not reclaim the symbols of the rule which action triggered + this YYERROR. */ + yypop_ (yylen); + yylen = 0; + yystate = yystate_stack_[0]; + 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 (!yy_pact_value_is_default_ (yyn)) + { + 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 (yystate_stack_.height () == 1) + YYABORT; + + yyerror_range[1] = yylocation_stack_[0]; + yydestruct_ ("Error: popping", + yystos_[yystate], + &yysemantic_stack_[0], &yylocation_stack_[0]); + yypop_ (); + yystate = yystate_stack_[0]; + YY_STACK_PRINT (); + } + + yyerror_range[2] = yylloc; + // Using YYLLOC is tempting, but would change the location of + // the lookahead. YYLOC is available though. + YYLLOC_DEFAULT (yyloc, yyerror_range, 2); + yysemantic_stack_.push (yylval); + yylocation_stack_.push (yyloc); + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos_[yyn], + &yysemantic_stack_[0], &yylocation_stack_[0]); + + yystate = yyn; + goto yynewstate; + + /* Accept. */ + yyacceptlab: + yyresult = 0; + goto yyreturn; + + /* Abort. */ + yyabortlab: + yyresult = 1; + goto yyreturn; + + yyreturn: + if (yychar != yyempty_) + { + /* Make sure we have latest lookahead translation. See comments + at user semantic actions for why this is necessary. */ + yytoken = yytranslate_ (yychar); + yydestruct_ ("Cleanup: discarding lookahead", yytoken, &yylval, + &yylloc); + } + + /* Do not reclaim the symbols of the rule which action triggered + this YYABORT or YYACCEPT. */ + yypop_ (yylen); + while (1 < yystate_stack_.height ()) + { + yydestruct_ ("Cleanup: popping", + yystos_[yystate_stack_[0]], + &yysemantic_stack_[0], + &yylocation_stack_[0]); + yypop_ (); + } + + return yyresult; + } + catch (...) + { + YYCDEBUG << "Exception caught: cleaning lookahead and stack" + << std::endl; + // Do not try to display the values of the reclaimed symbols, + // as their printer might throw an exception. + if (yychar != yyempty_) + { + /* Make sure we have latest lookahead translation. See + comments at user semantic actions for why this is + necessary. */ + yytoken = yytranslate_ (yychar); + yydestruct_ (YY_NULL, yytoken, &yylval, &yylloc); + } + + while (1 < yystate_stack_.height ()) + { + yydestruct_ (YY_NULL, + yystos_[yystate_stack_[0]], + &yysemantic_stack_[0], + &yylocation_stack_[0]); + yypop_ (); + } + throw; + } + } + + // Generate an error message. + std::string + json_parser::yysyntax_error_ (int yystate, int yytoken) + { + std::string yyres; + // Number of reported tokens (one for the "unexpected", one per + // "expected"). + size_t yycount = 0; + // Its maximum. + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + // Arguments of yyformat. + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + + /* There are many possibilities here to consider: + - If this state is a consistent state with a default action, then + the only way this function was invoked is if the default action + is an error action. In that case, don't check for expected + tokens because there are none. + - The only way there can be no lookahead present (in yytoken) is + if this state is a consistent state with a default action. + Thus, detecting the absence of a lookahead is sufficient to + determine that there is no unexpected or expected token to + report. In that case, just report a simple "syntax error". + - Don't assume there isn't a lookahead just because this state is + a consistent state with a default action. There might have + been a previous inconsistent state, consistent state with a + non-default action, or user semantic action that manipulated + yychar. + - Of course, the expected token list depends on states to have + correct lookahead information, and it depends on the parser not + to perform extra reductions after fetching a lookahead from the + scanner and before detecting a syntax error. Thus, state + merging (from LALR or IELR) and default reductions corrupt the + expected token list. However, the list is correct for + canonical LR with one exception: it will still contain any + token that will not be accepted due to an error action in a + later state. + */ + if (yytoken != yyempty_) + { + yyarg[yycount++] = yytname_[yytoken]; + int yyn = yypact_[yystate]; + if (!yy_pact_value_is_default_ (yyn)) + { + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. In other words, skip the first -YYN actions for + this state because they are default actions. */ + 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_; + for (int yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck_[yyx + yyn] == yyx && yyx != yyterror_ + && !yy_table_value_is_error_ (yytable_[yyx + yyn])) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + break; + } + else + yyarg[yycount++] = yytname_[yyx]; + } + } + } + + char const* yyformat = YY_NULL; + switch (yycount) + { +#define YYCASE_(N, S) \ + case N: \ + yyformat = S; \ + break + YYCASE_(0, YY_("syntax error")); + YYCASE_(1, YY_("syntax error, unexpected %s")); + YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); + YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); + YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); + YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); +#undef YYCASE_ + } + + // Argument number. + size_t yyi = 0; + for (char const* yyp = yyformat; *yyp; ++yyp) + if (yyp[0] == '%' && yyp[1] == 's' && yyi < yycount) + { + yyres += yytnamerr_ (yyarg[yyi++]); + ++yyp; + } + else + yyres += *yyp; + return yyres; + } + + + /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ + const signed char json_parser::yypact_ninf_ = -5; + const signed char + json_parser::yypact_[] = + { + 0, -5, 2, 18, -5, -5, -5, -5, -5, 7, + -5, -5, -5, -5, -5, 1, 12, -5, -4, -5, + -5, 29, -5, 4, -5, 29, -5, 26, -5, 29, + -5 + }; + + /* YYDEFACT[S] -- default reduction number in state S. Performed when + YYTABLE doesn't specify something else to do. Zero means the + default is an error. */ + const unsigned char + json_parser::yydefact_[] = + { + 0, 4, 0, 0, 14, 15, 16, 17, 13, 0, + 2, 18, 19, 3, 5, 0, 0, 9, 0, 11, + 1, 0, 6, 0, 10, 0, 7, 0, 12, 0, + 8 + }; + + /* YYPGOTO[NTERM-NUM]. */ + const signed char + json_parser::yypgoto_[] = + { + -5, -5, -5, -5, -5, -5, -5, -3 + }; + + /* YYDEFGOTO[NTERM-NUM]. */ + const signed char + json_parser::yydefgoto_[] = + { + -1, 9, 10, 11, 16, 12, 18, 13 + }; + + /* 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 YYTABLE_NINF_, syntax error. */ + const signed char json_parser::yytable_ninf_ = -1; + const unsigned char + json_parser::yytable_[] = + { + 19, 1, 24, 2, 25, 3, 14, 20, 21, 4, + 5, 6, 7, 8, 0, 15, 22, 27, 26, 0, + 23, 2, 28, 3, 17, 0, 30, 4, 5, 6, + 7, 8, 2, 29, 3, 0, 0, 0, 4, 5, + 6, 7, 8 + }; + + /* YYCHECK. */ + const signed char + json_parser::yycheck_[] = + { + 3, 1, 6, 3, 8, 5, 4, 0, 7, 9, + 10, 11, 12, 13, -1, 13, 4, 13, 21, -1, + 8, 3, 25, 5, 6, -1, 29, 9, 10, 11, + 12, 13, 3, 7, 5, -1, -1, -1, 9, 10, + 11, 12, 13 + }; + + /* STOS_[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ + const unsigned char + json_parser::yystos_[] = + { + 0, 1, 3, 5, 9, 10, 11, 12, 13, 16, + 17, 18, 20, 22, 4, 13, 19, 6, 21, 22, + 0, 7, 4, 8, 6, 8, 22, 13, 22, 7, + 22 + }; + +#if YYDEBUG + /* TOKEN_NUMBER_[YYLEX-NUM] -- Internal symbol number corresponding + to YYLEX-NUM. */ + const unsigned short int + json_parser::yytoken_number_[] = + { + 0, 256, 257, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12 + }; +#endif + + /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ + const unsigned char + json_parser::yyr1_[] = + { + 0, 15, 16, 17, 17, 18, 18, 19, 19, 20, + 20, 21, 21, 22, 22, 22, 22, 22, 22, 22 + }; + + /* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ + const unsigned char + json_parser::yyr2_[] = + { + 0, 2, 1, 1, 1, 2, 3, 3, 5, 2, + 3, 1, 3, 1, 1, 1, 1, 1, 1, 1 + }; + + + /* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at \a yyntokens_, nonterminals. */ + const char* + const json_parser::yytname_[] = + { + "\"end of file\"", "error", "$undefined", "\"{\"", "\"}\"", "\"[\"", + "\"]\"", "\":\"", "\",\"", "\"number\"", "\"true\"", "\"false\"", + "\"null\"", "\"string\"", "\"invalid\"", "$accept", "start", "data", + "object", "members", "array", "values", "value", YY_NULL + }; + +#if YYDEBUG + /* YYRHS -- A `-1'-separated list of the rules' RHS. */ + const json_parser::rhs_number_type + json_parser::yyrhs_[] = + { + 16, 0, -1, 17, -1, 22, -1, 1, -1, 3, + 4, -1, 3, 19, 4, -1, 13, 7, 22, -1, + 19, 8, 13, 7, 22, -1, 5, 6, -1, 5, + 21, 6, -1, 22, -1, 21, 8, 22, -1, 13, + -1, 9, -1, 10, -1, 11, -1, 12, -1, 18, + -1, 20, -1 + }; + + /* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ + const unsigned char + json_parser::yyprhs_[] = + { + 0, 0, 3, 5, 7, 9, 12, 16, 20, 26, + 29, 33, 35, 39, 41, 43, 45, 47, 49, 51 + }; + + /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ + const unsigned char + json_parser::yyrline_[] = + { + 0, 82, 82, 87, 88, 95, 98, 104, 109, 113, + 116, 122, 127, 131, 132, 133, 134, 135, 136, 137 + }; + + // Print the state stack on the debug stream. + void + json_parser::yystack_print_ () + { + *yycdebug_ << "Stack now"; + for (state_stack_type::const_iterator i = yystate_stack_.begin (); + i != yystate_stack_.end (); ++i) + *yycdebug_ << ' ' << *i; + *yycdebug_ << std::endl; + } + + // Report on the debug stream that the rule \a yyrule is going to be reduced. + void + json_parser::yy_reduce_print_ (int yyrule) + { + unsigned int yylno = yyrline_[yyrule]; + int yynrhs = yyr2_[yyrule]; + /* Print the symbols being reduced, and their result. */ + *yycdebug_ << "Reducing stack by rule " << yyrule - 1 + << " (line " << yylno << "):" << std::endl; + /* The symbols being reduced. */ + for (int yyi = 0; yyi < yynrhs; yyi++) + YY_SYMBOL_PRINT (" $" << yyi + 1 << " =", + yyrhs_[yyprhs_[yyrule] + yyi], + &(yysemantic_stack_[(yynrhs) - (yyi + 1)]), + &(yylocation_stack_[(yynrhs) - (yyi + 1)])); + } +#endif // YYDEBUG + + /* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ + json_parser::token_number_type + json_parser::yytranslate_ (int t) + { + static + const token_number_type + translate_table[] = + { + 0, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 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, 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, 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 + }; + if ((unsigned int) t <= yyuser_token_number_max_) + return translate_table[t]; + else + return yyundef_token_; + } + + const int json_parser::yyeof_ = 0; + const int json_parser::yylast_ = 42; + const int json_parser::yynnts_ = 8; + const int json_parser::yyempty_ = -2; + const int json_parser::yyfinal_ = 20; + const int json_parser::yyterror_ = 1; + const int json_parser::yyerrcode_ = 256; + const int json_parser::yyntokens_ = 15; + + const unsigned int json_parser::yyuser_token_number_max_ = 257; + const json_parser::token_number_type json_parser::yyundef_token_ = 2; + + +} // yy +/* Line 1141 of lalr1.cc */ +#line 1075 "json_parser.cc" +/* Line 1142 of lalr1.cc */ +#line 139 "json_parser.yy" + + +int yy::yylex(YYSTYPE *yylval, yy::location *yylloc, QJson::ParserPrivate* driver) +{ + JSonScanner* scanner = driver->m_scanner; + yylval->clear(); + int ret = scanner->yylex(yylval, yylloc); + + qjsonDebug() << "json_parser::yylex - calling scanner yylval==|" + << yylval->toByteArray() << "|, ret==|" << QString::number(ret) << "|"; + + return ret; +} + +void yy::json_parser::error (const yy::location& yyloc, const std::string& error) +{ + /*qjsonDebug() << yyloc.begin.line; + qjsonDebug() << yyloc.begin.column; + qjsonDebug() << yyloc.end.line; + qjsonDebug() << yyloc.end.column;*/ + qjsonDebug() << "json_parser::error [line" << yyloc.end.line << "] -" << error.c_str() ; + driver->setError(QString::fromLatin1(error.c_str()), yyloc.end.line); +} diff --git a/3rdparty/qjson/src/json_parser.hh b/3rdparty/qjson/src/json_parser.hh new file mode 100644 index 00000000..25e00fa1 --- /dev/null +++ b/3rdparty/qjson/src/json_parser.hh @@ -0,0 +1,300 @@ +/* A Bison parser, made by GNU Bison 2.7. */ + +/* Skeleton interface for Bison LALR(1) parsers in C++ + + Copyright (C) 2002-2012 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 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 . */ + +/* 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. */ + +/** + ** \file json_parser.hh + ** Define the yy::parser class. + */ + +/* C++ LALR(1) parser skeleton written by Akim Demaille. */ + +#ifndef YY_YY_JSON_PARSER_HH_INCLUDED +# define YY_YY_JSON_PARSER_HH_INCLUDED + +/* "%code requires" blocks. */ +/* Line 33 of lalr1.cc */ +#line 26 "json_parser.yy" + + #include "parser_p.h" + #include "json_scanner.h" + #include "qjson_debug.h" + + #include + #include + #include + #include + + #include + + class JSonScanner; + + namespace QJson { + class Parser; + } + + #define YYERROR_VERBOSE 1 + + Q_DECLARE_METATYPE(QVector*) + Q_DECLARE_METATYPE(QVariantMap*) + + +/* Line 33 of lalr1.cc */ +#line 72 "json_parser.hh" + + +#include +#include +#include "stack.hh" +#include "location.hh" + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 1 +#endif + + +namespace yy { +/* Line 33 of lalr1.cc */ +#line 88 "json_parser.hh" + + /// A Bison parser. + class json_parser + { + public: + /// Symbol semantic values. +#ifndef YYSTYPE + typedef int semantic_type; +#else + typedef YYSTYPE semantic_type; +#endif + /// Symbol locations. + typedef location location_type; + /// Tokens. + struct token + { + /* Tokens. */ + enum yytokentype { + END = 0, + CURLY_BRACKET_OPEN = 1, + CURLY_BRACKET_CLOSE = 2, + SQUARE_BRACKET_OPEN = 3, + SQUARE_BRACKET_CLOSE = 4, + COLON = 5, + COMMA = 6, + NUMBER = 7, + TRUE_VAL = 8, + FALSE_VAL = 9, + NULL_VAL = 10, + STRING = 11, + INVALID = 12 + }; + + }; + /// Token type. + typedef token::yytokentype token_type; + + /// Build a parser object. + json_parser (QJson::ParserPrivate* driver_yyarg); + virtual ~json_parser (); + + /// Parse. + /// \returns 0 iff parsing succeeded. + virtual int parse (); + +#if YYDEBUG + /// The current debugging stream. + std::ostream& debug_stream () const; + /// Set the current debugging stream. + void set_debug_stream (std::ostream &); + + /// Type for debugging levels. + typedef int debug_level_type; + /// The current debugging level. + debug_level_type debug_level () const; + /// Set the current debugging level. + void set_debug_level (debug_level_type l); +#endif + + private: + /// Report a syntax error. + /// \param loc where the syntax error is found. + /// \param msg a description of the syntax error. + virtual void error (const location_type& loc, const std::string& msg); + + /// Generate an error message. + /// \param state the state where the error occurred. + /// \param tok the lookahead token. + virtual std::string yysyntax_error_ (int yystate, int tok); + +#if YYDEBUG + /// \brief Report a symbol value on the debug stream. + /// \param yytype The token type. + /// \param yyvaluep Its semantic value. + /// \param yylocationp Its location. + virtual void yy_symbol_value_print_ (int yytype, + const semantic_type* yyvaluep, + const location_type* yylocationp); + /// \brief Report a symbol on the debug stream. + /// \param yytype The token type. + /// \param yyvaluep Its semantic value. + /// \param yylocationp Its location. + virtual void yy_symbol_print_ (int yytype, + const semantic_type* yyvaluep, + const location_type* yylocationp); +#endif + + + /// State numbers. + typedef int state_type; + /// State stack type. + typedef stack state_stack_type; + /// Semantic value stack type. + typedef stack semantic_stack_type; + /// location stack type. + typedef stack location_stack_type; + + /// The state stack. + state_stack_type yystate_stack_; + /// The semantic value stack. + semantic_stack_type yysemantic_stack_; + /// The location stack. + location_stack_type yylocation_stack_; + + /// Whether the given \c yypact_ value indicates a defaulted state. + /// \param yyvalue the value to check + static bool yy_pact_value_is_default_ (int yyvalue); + + /// Whether the given \c yytable_ value indicates a syntax error. + /// \param yyvalue the value to check + static bool yy_table_value_is_error_ (int yyvalue); + + /// Internal symbol numbers. + typedef unsigned char token_number_type; + /* Tables. */ + /// For a state, the index in \a yytable_ of its portion. + static const signed char yypact_[]; + static const signed char yypact_ninf_; + + /// For a state, default reduction number. + /// Unless\a yytable_ specifies something else to do. + /// Zero means the default is an error. + static const unsigned char yydefact_[]; + + static const signed char yypgoto_[]; + static const signed char yydefgoto_[]; + + /// What to do in a state. + /// \a yytable_[yypact_[s]]: what to do in state \a s. + /// - if positive, shift that token. + /// - if negative, reduce the rule which number is the opposite. + /// - if zero, do what YYDEFACT says. + static const unsigned char yytable_[]; + static const signed char yytable_ninf_; + + static const signed char yycheck_[]; + + /// For a state, its accessing symbol. + static const unsigned char yystos_[]; + + /// For a rule, its LHS. + static const unsigned char yyr1_[]; + /// For a rule, its RHS length. + static const unsigned char yyr2_[]; + + /// Convert the symbol name \a n to a form suitable for a diagnostic. + static std::string yytnamerr_ (const char *n); + + + /// For a symbol, its name in clear. + static const char* const yytname_[]; +#if YYDEBUG + /// A type to store symbol numbers and -1. + typedef signed char rhs_number_type; + /// A `-1'-separated list of the rules' RHS. + static const rhs_number_type yyrhs_[]; + /// For each rule, the index of the first RHS symbol in \a yyrhs_. + static const unsigned char yyprhs_[]; + /// For each rule, its source line number. + static const unsigned char yyrline_[]; + /// For each scanner token number, its symbol number. + static const unsigned short int yytoken_number_[]; + /// Report on the debug stream that the rule \a r is going to be reduced. + virtual void yy_reduce_print_ (int r); + /// Print the state stack on the debug stream. + virtual void yystack_print_ (); + + /* Debugging. */ + int yydebug_; + std::ostream* yycdebug_; +#endif + + /// Convert a scanner token number \a t to a symbol number. + token_number_type yytranslate_ (int t); + + /// \brief Reclaim the memory associated to a symbol. + /// \param yymsg Why this token is reclaimed. + /// If null, do not display the symbol, just free it. + /// \param yytype The symbol type. + /// \param yyvaluep Its semantic value. + /// \param yylocationp Its location. + inline void yydestruct_ (const char* yymsg, + int yytype, + semantic_type* yyvaluep, + location_type* yylocationp); + + /// Pop \a n symbols the three stacks. + inline void yypop_ (unsigned int n = 1); + + /* Constants. */ + static const int yyeof_; + /* LAST_ -- Last index in TABLE_. */ + static const int yylast_; + static const int yynnts_; + static const int yyempty_; + static const int yyfinal_; + static const int yyterror_; + static const int yyerrcode_; + static const int yyntokens_; + static const unsigned int yyuser_token_number_max_; + static const token_number_type yyundef_token_; + + /* User arguments. */ + QJson::ParserPrivate* driver; + }; + +} // yy +/* Line 33 of lalr1.cc */ +#line 297 "json_parser.hh" + + + +#endif /* !YY_YY_JSON_PARSER_HH_INCLUDED */ diff --git a/3rdparty/qjson/src/json_parser.yy b/3rdparty/qjson/src/json_parser.yy new file mode 100644 index 00000000..79e39aa9 --- /dev/null +++ b/3rdparty/qjson/src/json_parser.yy @@ -0,0 +1,161 @@ +/* This file is part of QJSon + * + * Copyright (C) 2008 Flavio Castelli + * Copyright (C) 2013 Silvio Moioli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +%skeleton "lalr1.cc" +%defines +%define "parser_class_name" "json_parser" + +%code requires{ + #include "parser_p.h" + #include "json_scanner.h" + #include "qjson_debug.h" + + #include + #include + #include + #include + + #include + + class JSonScanner; + + namespace QJson { + class Parser; + } + + #define YYERROR_VERBOSE 1 + + Q_DECLARE_METATYPE(QVector*) + Q_DECLARE_METATYPE(QVariantMap*) +} + +%parse-param { QJson::ParserPrivate* driver } +%lex-param { QJson::ParserPrivate* driver } + +%locations + +%debug +%error-verbose + +%token END 0 "end of file" + +%token CURLY_BRACKET_OPEN 1 "{" +%token CURLY_BRACKET_CLOSE 2 "}" +%token SQUARE_BRACKET_OPEN 3 "[" +%token SQUARE_BRACKET_CLOSE 4 "]" +%token COLON 5 ":" +%token COMMA 6 "," + +%token NUMBER 7 "number" +%token TRUE_VAL 8 "true" +%token FALSE_VAL 9 "false" +%token NULL_VAL 10 "null" +%token STRING 11 "string" + +%token INVALID 12 "invalid" + +// define the initial token +%start start + +%% + +// grammar rules + +start: data { + driver->m_result = $1; + qjsonDebug() << "json_parser - parsing finished"; + }; + +data: value { $$ = $1; } + | error + { + qCritical()<< "json_parser - syntax error found, " + << "forcing abort, Line" << @$.begin.line << "Column" << @$.begin.column; + YYABORT; + }; + +object: CURLY_BRACKET_OPEN CURLY_BRACKET_CLOSE { + $$ = QVariant(QVariantMap()); + } + | CURLY_BRACKET_OPEN members CURLY_BRACKET_CLOSE { + QVariantMap* map = $2.value(); + $$ = QVariant(*map); + delete map; + }; + +members: STRING COLON value { + QVariantMap* pair = new QVariantMap(); + pair->insert($1.toString(), $3); + $$.setValue(pair); + } + | members COMMA STRING COLON value { + $$.value()->insert($3.toString(), $5); + }; + +array: SQUARE_BRACKET_OPEN SQUARE_BRACKET_CLOSE { + $$ = QVariant(QVariantList()); + } + | SQUARE_BRACKET_OPEN values SQUARE_BRACKET_CLOSE { + QVector* list = $2.value* >(); + $$ = QVariant(list->toList()); + delete list; + }; + +values: value { + QVector* list = new QVector(1); + list->replace(0, $1); + $$.setValue(list); + } + | values COMMA value { + $$.value* >()->append($3); + }; + +value: STRING + | NUMBER + | TRUE_VAL + | FALSE_VAL + | NULL_VAL + | object + | array; + +%% + +int yy::yylex(YYSTYPE *yylval, yy::location *yylloc, QJson::ParserPrivate* driver) +{ + JSonScanner* scanner = driver->m_scanner; + yylval->clear(); + int ret = scanner->yylex(yylval, yylloc); + + qjsonDebug() << "json_parser::yylex - calling scanner yylval==|" + << yylval->toByteArray() << "|, ret==|" << QString::number(ret) << "|"; + + return ret; +} + +void yy::json_parser::error (const yy::location& yyloc, const std::string& error) +{ + /*qjsonDebug() << yyloc.begin.line; + qjsonDebug() << yyloc.begin.column; + qjsonDebug() << yyloc.end.line; + qjsonDebug() << yyloc.end.column;*/ + qjsonDebug() << "json_parser::error [line" << yyloc.end.line << "] -" << error.c_str() ; + driver->setError(QString::fromLatin1(error.c_str()), yyloc.end.line); +} diff --git a/3rdparty/qjson/src/json_scanner.cc b/3rdparty/qjson/src/json_scanner.cc new file mode 100644 index 00000000..58f7d000 --- /dev/null +++ b/3rdparty/qjson/src/json_scanner.cc @@ -0,0 +1,4520 @@ +#line 2 "json_scanner.cc" + +#line 4 "json_scanner.cc" + +#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 37 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + + /* The c++ scanner is a mess. The FlexLexer.h header file relies on the + * following macro. This is required in order to pass the c++-multiple-scanners + * test in the regression suite. We get reports that it breaks inheritance. + * We will address this in a future release of flex, or omit the C++ scanner + * altogether. + */ + #define yyFlexLexer yyFlexLexer + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ + +/* 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 */ + +/* begin standard C++ headers. */ +#include +#include +#include +#include +#include +/* end standard C++ headers. */ + +#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) + +/* 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 (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 (((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 yyrestart( yyin ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#define YY_BUF_SIZE 16384 +#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 + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +extern yy_size_t yyleng; + +#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 = (yy_hold_char); \ + YY_RESTORE_YY_MORE_OFFSET \ + (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, (yytext_ptr) ) + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + + std::istream* 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. + */ + yy_size_t 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 yyrestart()), 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 ( (yy_buffer_stack) \ + ? (yy_buffer_stack)[(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 (yy_buffer_stack)[(yy_buffer_stack_top)] + +void *yyalloc (yy_size_t ); +void *yyrealloc (void *,yy_size_t ); +void yyfree (void * ); + +#define yy_new_buffer yy_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +#define YY_SKIP_YYWRAP + +typedef unsigned char YY_CHAR; + +#define yytext_ptr yytext + +#include + +int yyFlexLexer::yywrap() { return 1; } +int yyFlexLexer::yylex() + { + LexerError( "yyFlexLexer::yylex invoked but %option yyclass used" ); + return 0; + } + +#define YY_DECL int JSonScanner::yylex() +static yyconst flex_int16_t yy_nxt[][256] = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 + }, + + { + 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, + 12, 11, 11, 13, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 11, 10, 14, 10, 10, 10, 10, 10, + + 10, 10, 10, 10, 15, 16, 10, 10, 17, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 19, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 20, 10, 21, 10, 10, 10, 10, 10, 10, + 10, 10, 22, 10, 10, 10, 10, 10, 10, 10, + 23, 10, 10, 10, 10, 10, 24, 10, 10, 10, + 10, 10, 10, 25, 10, 26, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10 + }, + + { + 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, + 12, 11, 11, 13, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 11, 10, 14, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 15, 16, 10, 10, 17, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 19, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 20, 10, 21, 10, 10, 10, 10, 10, 10, + 10, 10, 22, 10, 10, 10, 10, 10, 10, 10, + 23, 10, 10, 10, 10, 10, 24, 10, 10, 10, + 10, 10, 10, 25, 10, 26, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10 + }, + + { + 9, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 28, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 29, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27 + }, + + { + 9, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 28, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 29, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27 + + }, + + { + 9, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, 30, 30, + 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, + 31, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 31, 31, 31, + + 31, 31, 31, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30 + }, + + { + 9, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + + 30, 30, 30, 30, 30, 30, 30, 30, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, 30, 30, + 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, + 31, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 31, 31, 31, + 31, 31, 31, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30 + }, + + { + 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, + 12, 11, 11, 13, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 11, 10, 14, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 15, 32, 10, 10, 17, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 19, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 33, 10, 10, 10, 10, 34, 10, + + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 20, 10, 21, 10, 10, 10, 10, 10, 10, + 10, 10, 22, 10, 10, 33, 10, 10, 10, 10, + 35, 10, 10, 10, 10, 10, 24, 10, 10, 10, + 10, 10, 10, 25, 10, 26, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10 + }, + + { + 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, + 12, 11, 11, 13, 10, 10, 10, 10, 10, 10, + + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 11, 10, 14, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 15, 32, 10, 10, 17, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 19, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 33, 10, 10, 10, 10, 34, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 20, 10, 21, 10, 10, 10, 10, 10, 10, + 10, 10, 22, 10, 10, 33, 10, 10, 10, 10, + 35, 10, 10, 10, 10, 10, 24, 10, 10, 10, + + 10, 10, 10, 25, 10, 26, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10 + }, + + { + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9 + + }, + + { + 9, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10 + }, + + { + 9, -11, -11, -11, -11, -11, -11, -11, -11, 36, + -11, 36, 36, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, 36, -11, -11, -11, -11, -11, -11, -11, + + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11 + }, + + { + 9, -12, -12, -12, -12, -12, -12, -12, -12, -12, + 37, -12, -12, 37, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12 + }, + + { + 9, -13, -13, -13, -13, -13, -13, -13, -13, -13, + 37, -13, -13, 37, -13, -13, -13, -13, -13, -13, + + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13 + }, + + { + 9, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14 + + }, + + { + 9, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15 + }, + + { + 9, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + + -16, -16, -16, -16, -16, -16, -16, -16, 38, 39, + 39, 39, 39, 39, 39, 39, 39, 39, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16 + }, + + { + 9, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, 40, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, 41, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, 41, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17 + }, + + { + 9, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, 40, -18, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, 41, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, 41, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18 + }, + + { + 9, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19 + + }, + + { + 9, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20 + }, + + { + 9, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21 + }, + + { + 9, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, 43, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22 + }, + + { + 9, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, 44, -23, -23, + + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23 + }, + + { + 9, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, 45, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24 + + }, + + { + 9, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25 + }, + + { + 9, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26 + }, + + { + 9, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, -27, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, -27, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46 + }, + + { + 9, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28 + }, + + { + 9, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, 47, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, 48, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, 49, -29, -29, -29, -29, -29, 50, -29, + -29, -29, 51, -29, -29, -29, -29, -29, -29, -29, + 52, -29, -29, -29, 53, -29, 54, 55, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29 + + }, + + { + 9, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, + -30, -30, -30, -30, -30, -30 + }, + + { + 9, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + + -31, -31, -31, -31, -31, -31, -31, -31, 56, 56, + 56, 56, 56, 56, 56, 56, 56, 56, -31, -31, + -31, -31, -31, -31, -31, 56, 56, 56, 56, 56, + 56, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, 56, 56, 56, + 56, 56, 56, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31 + }, + + { + 9, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, 38, 39, + 39, 39, 39, 39, 39, 39, 39, 39, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, 57, -32, -32, -32, -32, -32, -32, + + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, 57, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32 + }, + + { + 9, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + 58, -33, -33, -33, -33, -33, -33, -33, -33, -33, + + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33 + }, + + { + 9, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + + -34, -34, -34, -34, -34, 59, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, 59, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34 + + }, + + { + 9, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, 59, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, 59, -35, -35, + + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, 44, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35 + }, + + { + 9, -36, -36, -36, -36, -36, -36, -36, -36, 36, + -36, 36, 36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, 36, -36, -36, -36, -36, -36, -36, -36, + + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36 + }, + + { + 9, -37, -37, -37, -37, -37, -37, -37, -37, -37, + 37, -37, -37, 37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37 + }, + + { + 9, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, 40, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, 41, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, 41, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38 + }, + + { + 9, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, 40, -39, 60, 60, + 60, 60, 60, 60, 60, 60, 60, 60, -39, -39, + + -39, -39, -39, -39, -39, -39, -39, -39, -39, 41, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, 41, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39 + + }, + + { + 9, -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, -40, + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, 61, 61, + 61, 61, 61, 61, 61, 61, 61, 61, -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, -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, -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, -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, + -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, -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, -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, -40, -40 + }, + + { + 9, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + + -41, -41, -41, 62, -41, 62, -41, -41, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41 + }, + + { + 9, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, 40, -42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, 41, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, 41, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42 + }, + + { + 9, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, 64, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43 + }, + + { + 9, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, 65, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44 + + }, + + { + 9, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, 66, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45 + }, + + { + 9, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, -46, 46, 46, 46, 46, 46, + + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, -46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46 + }, + + { + 9, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47 + }, + + { + 9, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48 + }, + + { + 9, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49, -49, -49, -49, -49, + -49, -49, -49, -49, -49, -49 + + }, + + { + 9, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50 + }, + + { + 9, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51 + }, + + { + 9, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52 + }, + + { + 9, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53 + }, + + { + 9, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54, -54, -54, -54, -54, + -54, -54, -54, -54, -54, -54 + + }, + + { + 9, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55 + }, + + { + 9, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + + -56, -56, -56, -56, -56, -56, -56, -56, 67, 67, + 67, 67, 67, 67, 67, 67, 67, 67, -56, -56, + -56, -56, -56, -56, -56, 67, 67, 67, 67, 67, + 67, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56, -56, 67, 67, 67, + 67, 67, 67, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + + -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, -56, -56, -56, -56, -56 + }, + + { + 9, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + 68, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, -57, -57, -57, -57, -57 + }, + + { + 9, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, 69, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58 + }, + + { + 9, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, 70, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + 70, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59 + + }, + + { + 9, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, 40, -60, 60, 60, + 60, 60, 60, 60, 60, 60, 60, 60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, 41, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + + -60, 41, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -60, -60 + }, + + { + 9, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + + -61, -61, -61, -61, -61, -61, -61, -61, 61, 61, + 61, 61, 61, 61, 61, 61, 61, 61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, 41, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, 41, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + + -61, -61, -61, -61, -61, -61, -61, -61, -61, -61, + -61, -61, -61, -61, -61, -61 + }, + + { + 9, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -62, -62, -62, -62 + }, + + { + 9, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63 + }, + + { + 9, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, 71, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, + -64, -64, -64, -64, -64, -64 + + }, + + { + 9, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + + -65, -65, -65, -65, -65, -65, -65, -65, 72, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65, -65, -65, -65, -65, + -65, -65, -65, -65, -65, -65 + }, + + { + 9, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, 73, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + + -66, -66, -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -66 + }, + + { + 9, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, -67, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, -67, -67, + -67, -67, -67, -67, -67, 74, 74, 74, 74, 74, + 74, -67, -67, -67, -67, -67, -67, -67, -67, -67, + + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, 74, 74, 74, + 74, 74, 74, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67, -67, -67, -67, -67, + -67, -67, -67, -67, -67, -67 + }, + + { + 9, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, 75, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68, -68, -68, -68, -68, + -68, -68, -68, -68, -68, -68 + }, + + { + 9, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, 76, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69, -69, -69, -69, -69, + -69, -69, -69, -69, -69, -69 + + }, + + { + 9, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70, -70, -70, -70, -70, + -70, -70, -70, -70, -70, -70 + }, + + { + 9, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, 77, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + + -71, -71, -71, -71, -71, -71, -71, -71, -71, -71, + -71, -71, -71, -71, -71, -71 + }, + + { + 9, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72, -72, -72, -72, -72, + -72, -72, -72, -72, -72, -72 + }, + + { + 9, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73 + }, + + { + 9, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74, -74, -74, -74, -74, + -74, -74, -74, -74, -74, -74 + + }, + + { + 9, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + + -75, -75, -75, -75, -75, 78, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, + -75, -75, -75, -75, -75, -75 + }, + + { + 9, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + 79, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + + -76, -76, -76, -76, -76, -76, -76, -76, -76, -76, + -76, -76, -76, -76, -76, -76 + }, + + { + 9, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77, -77, -77, -77, -77, + -77, -77, -77, -77, -77, -77 + }, + + { + 9, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + 80, -78, -78, -78, -78, -78, -78, -78, -78, -78, + + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78 + }, + + { + 9, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, 81, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -79 + + }, + + { + 9, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + + -80, -80, -80, -80, -80, 82, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, + -80, -80, -80, -80, -80, -80 + }, + + { + 9, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, 83, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + + -81, -81, -81, -81, -81, -81, -81, -81, -81, -81, + -81, -81, -81, -81, -81, -81 + }, + + { + 9, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, 84, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, -82, -82 + }, + + { + 9, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + + -83, 85, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83, -83, -83, -83, -83, + -83, -83, -83, -83, -83, -83 + }, + + { + 9, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, 86, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84 + + }, + + { + 9, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85, -85, -85, -85, -85, + -85, -85, -85, -85, -85, -85 + }, + + { + 9, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + + -86, -86, -86, -86, -86, -86, -86, -86, -86, -86, + -86, -86, -86, -86, -86, -86 + }, + + } ; + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + (yytext_ptr) = yy_bp; \ + yyleng = (size_t) (yy_cp - yy_bp); \ + (yy_hold_char) = *yy_cp; \ + *yy_cp = '\0'; \ + (yy_c_buf_p) = yy_cp; + +#define YY_NUM_RULES 36 +#define YY_END_OF_BUFFER 37 +/* 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[87] = + { 0, + 0, 0, 0, 0, 0, 0, 0, 0, 37, 35, + 1, 2, 2, 11, 27, 35, 6, 6, 26, 28, + 29, 35, 35, 35, 30, 31, 21, 23, 22, 25, + 25, 35, 35, 35, 35, 1, 2, 8, 8, 0, + 0, 7, 0, 0, 0, 21, 12, 14, 13, 15, + 16, 17, 18, 19, 20, 0, 0, 0, 0, 9, + 10, 0, 10, 0, 0, 0, 0, 0, 0, 32, + 0, 5, 3, 24, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 33, 34 + } ; + +static yyconst yy_state_type yy_NUL_trans[87] = + { 0, + 10, 10, 27, 27, 30, 30, 10, 10, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 46, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 46, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 + } ; + +/* 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 "json_scanner.yy" +/* This file is part of QJson + * + * Copyright (C) 2013 Silvio Moioli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110yy::json_parser::token::INVALID301, USA. + */ +/* Flex output settings */ +#define YY_NO_UNISTD_H 1 +#define YY_NO_INPUT 1 +#line 29 "json_scanner.yy" + #include "json_scanner.h" + #include "json_parser.hh" + + #if defined(_WIN32) && !defined(__MINGW32__) + #define strtoll _strtoi64 + #define strtoull _strtoui64 + #endif + + #define YY_USER_INIT if(m_allowSpecialNumbers) { \ + BEGIN(ALLOW_SPECIAL_NUMBERS); \ + } +/* Exclusive subscanners for strings and escaped hex sequences */ + +/* Extra-JSON rules active iff m_allowSpecialNumbers is true */ + +#line 3168 "json_scanner.cc" + +#define INITIAL 0 +#define QUOTMARK_OPEN 1 +#define HEX_OPEN 2 +#define ALLOW_SPECIAL_NUMBERS 3 + +#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 + +#ifndef yytext_ptr +static void yy_flex_strncpy (char *,yyconst char *,int ); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * ); +#endif + +#ifndef YY_NO_INPUT + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#define YY_READ_BUF_SIZE 8192 +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +#define ECHO LexerOutput( yytext, yyleng ) +#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 ( (result = LexerInput( (char *) buf, max_size )) < 0 ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); + +#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) LexerError( msg ) +#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 +#define YY_DECL int yyFlexLexer::yylex() +#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; + +#line 48 "json_scanner.yy" + + + /* Whitespace */ +#line 3275 "json_scanner.cc" + + if ( !(yy_init) ) + { + (yy_init) = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! (yy_start) ) + (yy_start) = 1; /* first start state */ + + if ( ! yyin ) + yyin = & std::cin; + + if ( ! yyout ) + yyout = & std::cout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE ); + } + + yy_load_buffer_state( ); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { + yy_cp = (yy_c_buf_p); + + /* Support of yytext. */ + *yy_cp = (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 = (yy_start); +yy_match: + while ( (yy_current_state = yy_nxt[yy_current_state][ YY_SC_TO_UI(*yy_cp) ]) > 0 ) + { + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + + ++yy_cp; + } + + yy_current_state = -yy_current_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 = (yy_hold_char); + yy_cp = (yy_last_accepting_cpos) + 1; + yy_current_state = (yy_last_accepting_state); + goto yy_find_action; + +case 1: +YY_RULE_SETUP +#line 51 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + } + YY_BREAK +case 2: +/* rule 2 can match eol */ +YY_RULE_SETUP +#line 55 "json_scanner.yy" +{ + m_yylloc->lines(yyleng); + } + YY_BREAK +/* Special values */ +case 3: +YY_RULE_SETUP +#line 61 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + *m_yylval = QVariant(true); + return yy::json_parser::token::TRUE_VAL; + } + YY_BREAK +case 4: +YY_RULE_SETUP +#line 67 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + *m_yylval = QVariant(false); + return yy::json_parser::token::FALSE_VAL; + } + YY_BREAK +case 5: +YY_RULE_SETUP +#line 73 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + *m_yylval = QVariant(); + return yy::json_parser::token::NULL_VAL; + } + YY_BREAK +/* Numbers */ +case 6: +#line 82 "json_scanner.yy" +case 7: +YY_RULE_SETUP +#line 82 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + *m_yylval = QVariant(strtoull(yytext, NULL, 10)); + if (errno == ERANGE) { + qCritical() << "Number is out of range: " << yytext; + return yy::json_parser::token::INVALID; + } + return yy::json_parser::token::NUMBER; + } + YY_BREAK +case 8: +#line 93 "json_scanner.yy" +case 9: +YY_RULE_SETUP +#line 93 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + *m_yylval = QVariant(strtoll(yytext, NULL, 10)); + if (errno == ERANGE) { + qCritical() << "Number is out of range: " << yytext; + return yy::json_parser::token::INVALID; + } + return yy::json_parser::token::NUMBER; + } + YY_BREAK +case 10: +YY_RULE_SETUP +#line 103 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + bool ok; + *m_yylval = QVariant(m_C_locale.toDouble(QLatin1String(yytext),&ok)); + if (!ok) { + qCritical() << "Number is out of range: " << yytext; + return yy::json_parser::token::INVALID; + } + return yy::json_parser::token::NUMBER; + } + YY_BREAK +/* Strings */ +case 11: +YY_RULE_SETUP +#line 115 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + BEGIN(QUOTMARK_OPEN); + } + YY_BREAK + +case 12: +YY_RULE_SETUP +#line 121 "json_scanner.yy" +{ + m_currentString.append(QLatin1String("\"")); + } + YY_BREAK +case 13: +YY_RULE_SETUP +#line 125 "json_scanner.yy" +{ + m_currentString.append(QLatin1String("\\")); + } + YY_BREAK +case 14: +YY_RULE_SETUP +#line 129 "json_scanner.yy" +{ + m_currentString.append(QLatin1String("/")); + } + YY_BREAK +case 15: +YY_RULE_SETUP +#line 133 "json_scanner.yy" +{ + m_currentString.append(QLatin1String("\b")); + } + YY_BREAK +case 16: +YY_RULE_SETUP +#line 137 "json_scanner.yy" +{ + m_currentString.append(QLatin1String("\f")); + } + YY_BREAK +case 17: +YY_RULE_SETUP +#line 141 "json_scanner.yy" +{ + m_currentString.append(QLatin1String("\n")); + } + YY_BREAK +case 18: +YY_RULE_SETUP +#line 145 "json_scanner.yy" +{ + m_currentString.append(QLatin1String("\r")); + } + YY_BREAK +case 19: +YY_RULE_SETUP +#line 149 "json_scanner.yy" +{ + m_currentString.append(QLatin1String("\t")); + } + YY_BREAK +case 20: +YY_RULE_SETUP +#line 153 "json_scanner.yy" +{ + BEGIN(HEX_OPEN); + } + YY_BREAK +case 21: +/* rule 21 can match eol */ +YY_RULE_SETUP +#line 157 "json_scanner.yy" +{ + m_currentString.append(QString::fromUtf8(yytext)); + } + YY_BREAK +case 22: +YY_RULE_SETUP +#line 161 "json_scanner.yy" +{ + // ignore + } + YY_BREAK +case 23: +YY_RULE_SETUP +#line 165 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + *m_yylval = QVariant(m_currentString); + m_currentString.clear(); + BEGIN(INITIAL); + return yy::json_parser::token::STRING; + } + YY_BREAK +case YY_STATE_EOF(QUOTMARK_OPEN): +#line 173 "json_scanner.yy" +{ + qCritical() << "Unterminated string"; + m_yylloc->columns(yyleng); + return yy::json_parser::token::INVALID; + } + YY_BREAK + + +case 24: +YY_RULE_SETUP +#line 182 "json_scanner.yy" +{ + QString hexDigits = QString::fromUtf8(yytext, yyleng); + bool ok; + ushort hexDigit1 = hexDigits.left(2).toShort(&ok, 16); + ushort hexDigit2 = hexDigits.right(2).toShort(&ok, 16); + m_currentString.append(QChar(hexDigit2, hexDigit1)); + BEGIN(QUOTMARK_OPEN); + } + YY_BREAK +case 25: +/* rule 25 can match eol */ +YY_RULE_SETUP +#line 191 "json_scanner.yy" +{ + qCritical() << "Invalid hex string"; + m_yylloc->columns(yyleng); + *m_yylval = QVariant(QLatin1String("")); + BEGIN(QUOTMARK_OPEN); + return yy::json_parser::token::INVALID; + } + YY_BREAK + +/* "Compound type" related tokens */ +case 26: +YY_RULE_SETUP +#line 203 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + return yy::json_parser::token::COLON; + } + YY_BREAK +case 27: +YY_RULE_SETUP +#line 208 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + return yy::json_parser::token::COMMA; + } + YY_BREAK +case 28: +YY_RULE_SETUP +#line 213 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + return yy::json_parser::token::SQUARE_BRACKET_OPEN; + } + YY_BREAK +case 29: +YY_RULE_SETUP +#line 218 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + return yy::json_parser::token::SQUARE_BRACKET_CLOSE; + } + YY_BREAK +case 30: +YY_RULE_SETUP +#line 223 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + return yy::json_parser::token::CURLY_BRACKET_OPEN; + } + YY_BREAK +case 31: +YY_RULE_SETUP +#line 228 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + return yy::json_parser::token::CURLY_BRACKET_CLOSE; + } + YY_BREAK +/* Extra-JSON numbers */ + +case 32: +YY_RULE_SETUP +#line 236 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + *m_yylval = QVariant(std::numeric_limits::quiet_NaN()); + return yy::json_parser::token::NUMBER; + } + YY_BREAK +case 33: +YY_RULE_SETUP +#line 242 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + *m_yylval = QVariant(std::numeric_limits::infinity()); + return yy::json_parser::token::NUMBER; + } + YY_BREAK +case 34: +YY_RULE_SETUP +#line 248 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + *m_yylval = QVariant(-std::numeric_limits::infinity()); + return yy::json_parser::token::NUMBER; + } + YY_BREAK + +/* If all else fails */ +case 35: +YY_RULE_SETUP +#line 256 "json_scanner.yy" +{ + m_yylloc->columns(yyleng); + return yy::json_parser::token::INVALID; + } + YY_BREAK +case YY_STATE_EOF(INITIAL): +case YY_STATE_EOF(HEX_OPEN): +case YY_STATE_EOF(ALLOW_SPECIAL_NUMBERS): +#line 261 "json_scanner.yy" +return yy::json_parser::token::END; + YY_BREAK +case 36: +YY_RULE_SETUP +#line 262 "json_scanner.yy" +ECHO; + YY_BREAK +#line 3667 "json_scanner.cc" + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = (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 + * yylex(). 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. + */ + (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 ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + /* 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 ); + + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++(yy_c_buf_p); + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = (yy_c_buf_p); + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_END_OF_FILE: + { + (yy_did_buffer_switch_on_eof) = 0; + + if ( yywrap( ) ) + { + /* 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. + */ + (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = + (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + (yy_c_buf_p) = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)]; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (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 yylex */ + +/* The contents of this function are C++ specific, so the () macro is not used. + */ +yyFlexLexer::yyFlexLexer( std::istream* arg_yyin, std::ostream* arg_yyout ) +{ + yyin = arg_yyin; + yyout = arg_yyout; + yy_c_buf_p = 0; + yy_init = 0; + yy_start = 0; + yy_flex_debug = 0; + yylineno = 1; // this will only get updated if %option yylineno + + yy_did_buffer_switch_on_eof = 0; + + yy_looking_for_trail_begin = 0; + yy_more_flag = 0; + yy_more_len = 0; + yy_more_offset = yy_prev_more_offset = 0; + + yy_start_stack_ptr = yy_start_stack_depth = 0; + yy_start_stack = NULL; + + yy_buffer_stack = 0; + yy_buffer_stack_top = 0; + yy_buffer_stack_max = 0; + + yy_state_buf = 0; + +} + +/* The contents of this function are C++ specific, so the () macro is not used. + */ +yyFlexLexer::~yyFlexLexer() +{ + delete [] yy_state_buf; + yyfree(yy_start_stack ); + yy_delete_buffer( YY_CURRENT_BUFFER ); + yyfree(yy_buffer_stack ); +} + +/* The contents of this function are C++ specific, so the () macro is not used. + */ +void yyFlexLexer::switch_streams( std::istream* new_in, std::ostream* new_out ) +{ + if ( new_in ) + { + yy_delete_buffer( YY_CURRENT_BUFFER ); + yy_switch_to_buffer( yy_create_buffer( new_in, YY_BUF_SIZE ) ); + } + + if ( new_out ) + yyout = new_out; +} + +#ifdef YY_INTERACTIVE +int yyFlexLexer::LexerInput( char* buf, int /* max_size */ ) +#else +int yyFlexLexer::LexerInput( char* buf, int max_size ) +#endif +{ + if ( yyin->eof() || yyin->fail() ) + return 0; + +#ifdef YY_INTERACTIVE + yyin->get( buf[0] ); + + if ( yyin->eof() ) + return 0; + + if ( yyin->bad() ) + return -1; + + return 1; + +#else + (void) yyin->read( buf, max_size ); + + if ( yyin->bad() ) + return -1; + else + return yyin->gcount(); +#endif +} + +void yyFlexLexer::LexerOutput( const char* buf, int size ) +{ + (void) yyout->write( buf, size ); +} + +/* 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 + */ +int yyFlexLexer::yy_get_next_buffer() +{ + register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + register char *source = (yytext_ptr); + register int number_to_move, i; + int ret_val; + + if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(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 ( (yy_c_buf_p) - (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) ((yy_c_buf_p) - (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 = (yy_n_chars) = 0; + + else + { + yy_size_t 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_LVALUE; + + int yy_c_buf_p_offset = + (int) ((yy_c_buf_p) - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + yy_size_t 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. */ + yyrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ); + } + 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" ); + + (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]), + (yy_n_chars), num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + if ( (yy_n_chars) == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin ); + } + + 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) ((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 = (yy_n_chars) + number_to_move + ((yy_n_chars) >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc((void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf,new_size ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + } + + (yy_n_chars) += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR; + + (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 */ + + yy_state_type yyFlexLexer::yy_get_previous_state() +{ + register yy_state_type yy_current_state; + register char *yy_cp; + + yy_current_state = (yy_start); + + for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) + { + if ( *yy_cp ) + { + yy_current_state = yy_nxt[yy_current_state][YY_SC_TO_UI(*yy_cp)]; + } + else + yy_current_state = yy_NUL_trans[yy_current_state]; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + } + + 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 ); + */ + yy_state_type yyFlexLexer::yy_try_NUL_trans( yy_state_type yy_current_state ) +{ + register int yy_is_jam; + register char *yy_cp = (yy_c_buf_p); + + yy_current_state = yy_NUL_trans[yy_current_state]; + yy_is_jam = (yy_current_state == 0); + + if ( ! yy_is_jam ) + { + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + } + + return yy_is_jam ? 0 : yy_current_state; +} + + void yyFlexLexer::yyunput( int c, register char* yy_bp) +{ + register char *yy_cp; + + yy_cp = (yy_c_buf_p); + + /* undo effects of setting up yytext */ + *yy_cp = (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 yy_size_t number_to_move = (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 = + (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; + + (yytext_ptr) = yy_bp; + (yy_hold_char) = *yy_cp; + (yy_c_buf_p) = yy_cp; +} + + int yyFlexLexer::yyinput() +{ + int c; + + *(yy_c_buf_p) = (yy_hold_char); + + if ( *(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 ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + /* This was really a NUL. */ + *(yy_c_buf_p) = '\0'; + + else + { /* need more input */ + yy_size_t offset = (yy_c_buf_p) - (yytext_ptr); + ++(yy_c_buf_p); + + switch ( yy_get_next_buffer( ) ) + { + 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. */ + yyrestart( yyin ); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( ) ) + return EOF; + + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(); +#else + return input(); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = (yytext_ptr) + offset; + break; + } + } + } + + c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */ + *(yy_c_buf_p) = '\0'; /* preserve yytext */ + (yy_hold_char) = *++(yy_c_buf_p); + + return c; +} + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * + * @note This function does not reset the start condition to @c INITIAL . + */ + void yyFlexLexer::yyrestart( std::istream* input_file ) +{ + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE ); + } + + yy_init_buffer( YY_CURRENT_BUFFER, input_file ); + yy_load_buffer_state( ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * + */ + void yyFlexLexer::yy_switch_to_buffer( YY_BUFFER_STATE new_buffer ) +{ + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + (yy_did_buffer_switch_on_eof) = 1; +} + + void yyFlexLexer::yy_load_buffer_state() +{ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + (yy_hold_char) = *(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. + * + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yyFlexLexer::yy_create_buffer( std::istream* file, int size ) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_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 *) yyalloc(b->yy_buf_size + 2 ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file ); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * + */ + void yyFlexLexer::yy_delete_buffer( YY_BUFFER_STATE b ) +{ + + 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 ) + yyfree((void *) b->yy_ch_buf ); + + yyfree((void *) b ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + void yyFlexLexer::yy_init_buffer( YY_BUFFER_STATE b, std::istream* file ) + +{ + int oerrno = errno; + + yy_flush_buffer( b ); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() 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. + * + */ + void yyFlexLexer::yy_flush_buffer( YY_BUFFER_STATE b ) +{ + 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 ) + yy_load_buffer_state( ); +} + +/** 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. + * + */ +void yyFlexLexer::yypush_buffer_state (YY_BUFFER_STATE new_buffer) +{ + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + (yy_buffer_stack_top)++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * + */ +void yyFlexLexer::yypop_buffer_state (void) +{ + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + if ((yy_buffer_stack_top) > 0) + --(yy_buffer_stack_top); + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +void yyFlexLexer::yyensure_buffer_stack(void) +{ + yy_size_t num_to_alloc; + + if (!(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; + (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + (yy_buffer_stack_max) = num_to_alloc; + (yy_buffer_stack_top) = 0; + return; + } + + if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + int grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = (yy_buffer_stack_max) + grow_size; + (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc + ((yy_buffer_stack), + num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*)); + (yy_buffer_stack_max) = num_to_alloc; + } +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +void yyFlexLexer::LexerError( yyconst char msg[] ) +{ + std::cerr << msg << std::endl; + 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] = (yy_hold_char); \ + (yy_c_buf_p) = yytext + yyless_macro_arg; \ + (yy_hold_char) = *(yy_c_buf_p); \ + *(yy_c_buf_p) = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, yyconst char * s2, int n ) +{ + 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 ) +{ + register int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *yyalloc (yy_size_t size ) +{ + return (void *) malloc( size ); +} + +void *yyrealloc (void * ptr, yy_size_t size ) +{ + /* 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 yyfree (void * ptr ) +{ + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#line 262 "json_scanner.yy" diff --git a/3rdparty/qjson/src/json_scanner.cpp b/3rdparty/qjson/src/json_scanner.cpp new file mode 100644 index 00000000..eb4ec835 --- /dev/null +++ b/3rdparty/qjson/src/json_scanner.cpp @@ -0,0 +1,82 @@ +/* This file is part of QJson + * + * Copyright (C) 2008 Flavio Castelli + * Copyright (C) 2013 Silvio Moioli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; 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 "json_scanner.cc" + +#include "qjson_debug.h" +#include "json_scanner.h" +#include "json_parser.hh" + +#include + +#include +#include + +#include + + +JSonScanner::JSonScanner(QIODevice* io) + : m_allowSpecialNumbers(false), + m_io (io), + m_criticalError(false), + m_C_locale(QLocale::C) +{ + +} + +JSonScanner::~JSonScanner() +{ +} + +void JSonScanner::allowSpecialNumbers(bool allow) { + m_allowSpecialNumbers = allow; +} + +int JSonScanner::yylex(YYSTYPE* yylval, yy::location *yylloc) { + m_yylval = yylval; + m_yylloc = yylloc; + m_yylloc->step(); + int result = yylex(); + + if (m_criticalError) { + return -1; + } + + return result; +} + +int JSonScanner::LexerInput(char* buf, int max_size) { + if (!m_io->isOpen()) { + qCritical() << "JSonScanner::yylex - io device is not open"; + m_criticalError = true; + return 0; + } + + int readBytes = m_io->read(buf, max_size); + if(readBytes < 0) { + qCritical() << "JSonScanner::yylex - error while reading from io device"; + m_criticalError = true; + return 0; + } + + return readBytes; +} + + diff --git a/3rdparty/qjson/src/json_scanner.h b/3rdparty/qjson/src/json_scanner.h new file mode 100644 index 00000000..6a0e97b6 --- /dev/null +++ b/3rdparty/qjson/src/json_scanner.h @@ -0,0 +1,66 @@ +/* This file is part of QJson + * + * Copyright (C) 2008 Flavio Castelli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; 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 _JSON_SCANNER +#define _JSON_SCANNER + +#include +#include +#include + +#define YYSTYPE QVariant + +// Only include FlexLexer.h if it hasn't been already included +#if ! defined(yyFlexLexerOnce) +#include +#endif + +#include "parser_p.h" + + + +namespace yy { + class location; + int yylex(YYSTYPE *yylval, yy::location *yylloc, QJson::ParserPrivate* driver); +} + +class JSonScanner : public yyFlexLexer +{ + public: + explicit JSonScanner(QIODevice* io); + ~JSonScanner(); + + void allowSpecialNumbers(bool allow); + + int yylex(YYSTYPE* yylval, yy::location *yylloc); + int yylex(); + int LexerInput(char* buf, int max_size); + protected: + bool m_allowSpecialNumbers; + QIODevice* m_io; + + YYSTYPE* m_yylval; + yy::location* m_yylloc; + bool m_criticalError; + QString m_currentString; + QLocale m_C_locale; +}; + +#endif diff --git a/3rdparty/qjson/src/json_scanner.yy b/3rdparty/qjson/src/json_scanner.yy new file mode 100644 index 00000000..bc490d4a --- /dev/null +++ b/3rdparty/qjson/src/json_scanner.yy @@ -0,0 +1,261 @@ +/* This file is part of QJson + * + * Copyright (C) 2013 Silvio Moioli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110yy::json_parser::token::INVALID301, USA. + */ + +/* Flex output settings */ +%option 8bit c++ full warn +%option noyywrap nounistd +%option noinput nounput noyy_push_state noyy_pop_state noyy_top_state noyy_scan_buffer noyy_scan_bytes noyy_scan_string noyyget_extra noyyset_extra noyyget_leng noyyget_text noyyget_lineno noyyset_lineno noyyget_in noyyset_in noyyget_out noyyset_out noyyget_lval noyyset_lval noyyget_lloc noyyset_lloc noyyget_debug noyyset_debug +%option yyclass="JSonScanner" +%option outfile="json_scanner.cc" + +%{ + #include "json_scanner.h" + #include "json_parser.hh" + + #if defined(_WIN32) && !defined(__MINGW32__) + #define strtoll _strtoi64 + #define strtoull _strtoui64 + #endif + + #define YY_USER_INIT if(m_allowSpecialNumbers) { \ + BEGIN(ALLOW_SPECIAL_NUMBERS); \ + } +%} + +/* Exclusive subscanners for strings and escaped hex sequences */ +%x QUOTMARK_OPEN HEX_OPEN + +/* Extra-JSON rules active iff m_allowSpecialNumbers is true */ +%s ALLOW_SPECIAL_NUMBERS + +%% + + /* Whitespace */ +[\v\f\t ]+ { + m_yylloc->columns(yyleng); + } + +[\r\n]+ { + m_yylloc->lines(yyleng); + } + + + /* Special values */ +true { + m_yylloc->columns(yyleng); + *m_yylval = QVariant(true); + return yy::json_parser::token::TRUE_VAL; + } + +false { + m_yylloc->columns(yyleng); + *m_yylval = QVariant(false); + return yy::json_parser::token::FALSE_VAL; + } + +null { + m_yylloc->columns(yyleng); + *m_yylval = QVariant(); + return yy::json_parser::token::NULL_VAL; + } + + + /* Numbers */ +[0-9] | +[1-9][0-9]+ { + m_yylloc->columns(yyleng); + *m_yylval = QVariant(strtoull(yytext, NULL, 10)); + if (errno == ERANGE) { + qCritical() << "Number is out of range: " << yytext; + return yy::json_parser::token::INVALID; + } + return yy::json_parser::token::NUMBER; + } + +-[0-9] | +-[1-9][0-9]+ { + m_yylloc->columns(yyleng); + *m_yylval = QVariant(strtoll(yytext, NULL, 10)); + if (errno == ERANGE) { + qCritical() << "Number is out of range: " << yytext; + return yy::json_parser::token::INVALID; + } + return yy::json_parser::token::NUMBER; + } + +-?(([0-9])|([1-9][0-9]+))(\.[0-9]+)?([Ee][+\-]?[0-9]+)? { + m_yylloc->columns(yyleng); + bool ok; + *m_yylval = QVariant(m_C_locale.toDouble(QLatin1String(yytext),&ok)); + if (!ok) { + qCritical() << "Number is out of range: " << yytext; + return yy::json_parser::token::INVALID; + } + return yy::json_parser::token::NUMBER; + } + + /* Strings */ +\" { + m_yylloc->columns(yyleng); + BEGIN(QUOTMARK_OPEN); + } + +{ + \\\" { + m_currentString.append(QLatin1String("\"")); + } + + \\\\ { + m_currentString.append(QLatin1String("\\")); + } + + \\\/ { + m_currentString.append(QLatin1String("/")); + } + + \\b { + m_currentString.append(QLatin1String("\b")); + } + + \\f { + m_currentString.append(QLatin1String("\f")); + } + + \\n { + m_currentString.append(QLatin1String("\n")); + } + + \\r { + m_currentString.append(QLatin1String("\r")); + } + + \\t { + m_currentString.append(QLatin1String("\t")); + } + + \\u { + BEGIN(HEX_OPEN); + } + + [^\"\\]+ { + m_currentString.append(QString::fromUtf8(yytext)); + } + + \\ { + // ignore + } + + \" { + m_yylloc->columns(yyleng); + *m_yylval = QVariant(m_currentString); + m_currentString.clear(); + BEGIN(INITIAL); + return yy::json_parser::token::STRING; + } + + <> { + qCritical() << "Unterminated string"; + m_yylloc->columns(yyleng); + return yy::json_parser::token::INVALID; + } + +} + +{ + [0-9A-Fa-f]{4} { + QString hexDigits = QString::fromUtf8(yytext, yyleng); + bool ok; + ushort hexDigit1 = hexDigits.left(2).toShort(&ok, 16); + ushort hexDigit2 = hexDigits.right(2).toShort(&ok, 16); + m_currentString.append(QChar(hexDigit2, hexDigit1)); + BEGIN(QUOTMARK_OPEN); + } + + .|\n { + qCritical() << "Invalid hex string"; + m_yylloc->columns(yyleng); + *m_yylval = QVariant(QLatin1String("")); + BEGIN(QUOTMARK_OPEN); + return yy::json_parser::token::INVALID; + } +} + + + + /* "Compound type" related tokens */ +: { + m_yylloc->columns(yyleng); + return yy::json_parser::token::COLON; + } + +, { + m_yylloc->columns(yyleng); + return yy::json_parser::token::COMMA; + } + +\[ { + m_yylloc->columns(yyleng); + return yy::json_parser::token::SQUARE_BRACKET_OPEN; + } + +\] { + m_yylloc->columns(yyleng); + return yy::json_parser::token::SQUARE_BRACKET_CLOSE; + } + +\{ { + m_yylloc->columns(yyleng); + return yy::json_parser::token::CURLY_BRACKET_OPEN; + } + +\} { + m_yylloc->columns(yyleng); + return yy::json_parser::token::CURLY_BRACKET_CLOSE; + } + + + /* Extra-JSON numbers */ +{ + (?i:nan) { + m_yylloc->columns(yyleng); + *m_yylval = QVariant(std::numeric_limits::quiet_NaN()); + return yy::json_parser::token::NUMBER; + } + + [Ii]nfinity { + m_yylloc->columns(yyleng); + *m_yylval = QVariant(std::numeric_limits::infinity()); + return yy::json_parser::token::NUMBER; + } + + -[Ii]nfinity { + m_yylloc->columns(yyleng); + *m_yylval = QVariant(-std::numeric_limits::infinity()); + return yy::json_parser::token::NUMBER; + } +} + + /* If all else fails */ +. { + m_yylloc->columns(yyleng); + return yy::json_parser::token::INVALID; + } + +<> return yy::json_parser::token::END; diff --git a/3rdparty/qjson/src/location.hh b/3rdparty/qjson/src/location.hh new file mode 100644 index 00000000..0bf1a74e --- /dev/null +++ b/3rdparty/qjson/src/location.hh @@ -0,0 +1,181 @@ +/* A Bison parser, made by GNU Bison 2.7. */ + +/* Locations for Bison parsers in C++ + + Copyright (C) 2002-2007, 2009-2012 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 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 . */ + +/* 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. */ + +/** + ** \file location.hh + ** Define the yy::location class. + */ + +#ifndef YY_YY_LOCATION_HH_INCLUDED +# define YY_YY_LOCATION_HH_INCLUDED + +# include "position.hh" + + +namespace yy { +/* Line 166 of location.cc */ +#line 47 "location.hh" + + /// Abstract a location. + class location + { + public: + + /// Construct a location from \a b to \a e. + location (const position& b, const position& e) + : begin (b) + , end (e) + { + } + + /// Construct a 0-width location in \a p. + explicit location (const position& p = position ()) + : begin (p) + , end (p) + { + } + + /// Construct a 0-width location in \a f, \a l, \a c. + explicit location (std::string* f, + unsigned int l = 1u, + unsigned int c = 1u) + : begin (f, l, c) + , end (f, l, c) + { + } + + + /// Initialization. + void initialize (std::string* f = YY_NULL, + unsigned int l = 1u, + unsigned int c = 1u) + { + begin.initialize (f, l, c); + end = begin; + } + + /** \name Line and Column related manipulators + ** \{ */ + public: + /// Reset initial location to final location. + void step () + { + begin = end; + } + + /// Extend the current location to the COUNT next columns. + void columns (unsigned int count = 1) + { + end += count; + } + + /// Extend the current location to the COUNT next lines. + void lines (unsigned int count = 1) + { + end.lines (count); + } + /** \} */ + + + public: + /// Beginning of the located region. + position begin; + /// End of the located region. + position end; + }; + + /// Join two location objects to create a location. + inline const location operator+ (const location& begin, const location& end) + { + location res = begin; + res.end = end.end; + return res; + } + + /// Add two location objects. + inline const location operator+ (const location& begin, unsigned int width) + { + location res = begin; + res.columns (width); + return res; + } + + /// Add and assign a location. + inline location& operator+= (location& res, unsigned int width) + { + res.columns (width); + return res; + } + + /// Compare two location objects. + inline bool + operator== (const location& loc1, const location& loc2) + { + return loc1.begin == loc2.begin && loc1.end == loc2.end; + } + + /// Compare two location objects. + inline bool + operator!= (const location& loc1, const location& loc2) + { + return !(loc1 == loc2); + } + + /** \brief Intercept output stream redirection. + ** \param ostr the destination output stream + ** \param loc a reference to the location to redirect + ** + ** Avoid duplicate information. + */ + template + inline std::basic_ostream& + operator<< (std::basic_ostream& ostr, const location& loc) + { + position last = loc.end - 1; + ostr << loc.begin; + if (last.filename + && (!loc.begin.filename + || *loc.begin.filename != *last.filename)) + ostr << '-' << last; + else if (loc.begin.line != last.line) + ostr << '-' << last.line << '.' << last.column; + else if (loc.begin.column != last.column) + ostr << '-' << last.column; + return ostr; + } + + +} // yy +/* Line 296 of location.cc */ +#line 180 "location.hh" + +#endif /* !YY_YY_LOCATION_HH_INCLUDED */ diff --git a/3rdparty/qjson/src/parser.cpp b/3rdparty/qjson/src/parser.cpp new file mode 100644 index 00000000..d6f77c07 --- /dev/null +++ b/3rdparty/qjson/src/parser.cpp @@ -0,0 +1,151 @@ +/* This file is part of QJson + * + * Copyright (C) 2008 Flavio Castelli + * Copyright (C) 2016 Anton Kudryavtsev + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; 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 "parser.h" +#include "parser_p.h" +#include "json_parser.hh" +#include "json_scanner.h" + +#include +#include +#include +#include + +using namespace QJson; + +ParserPrivate::ParserPrivate() : + m_scanner(0), + m_specialNumbersAllowed(false) +{ + reset(); +} + +ParserPrivate::~ParserPrivate() +{ + if (m_scanner) + delete m_scanner; +} + +QVariant ParserPrivate::parse(QIODevice* io, bool* ok) +{ + m_scanner = new JSonScanner (io); + m_scanner->allowSpecialNumbers(m_specialNumbersAllowed); + yy::json_parser parser(this); + parser.parse(); + + delete m_scanner; + m_scanner = 0; + + if (ok != 0) + *ok = !m_error; + + io->close(); + return m_result; +} + +void ParserPrivate::setError(const QString &errorMsg, int errorLine) { + m_error = true; + m_errorMsg = errorMsg; + m_errorLine = errorLine; +} + +void ParserPrivate::reset() +{ + m_error = false; + m_errorLine = 0; + m_errorMsg.clear(); + if (m_scanner) { + delete m_scanner; + m_scanner = 0; + } +} + +Parser::Parser() : + d(new ParserPrivate) +{ +} + +Parser::~Parser() +{ + delete d; +} + +QVariant Parser::parse(QIODevice* io, bool* ok) +{ + d->reset(); + + if (!io->isOpen()) { + if (!io->open(QIODevice::ReadOnly)) { + if (ok != 0) + *ok = false; + qCritical ("Error opening device"); + return QVariant(); + } + } + + if (!io->isReadable()) { + if (ok != 0) + *ok = false; + qCritical ("Device is not readable"); + io->close(); + return QVariant(); + } + + if (io->atEnd()) { + if (ok != 0) + *ok = false; + d->setError(QLatin1String("No data"), 0); + io->close(); + return QVariant(); + } + + QByteArray buffer = io->readAll(); + return parse(buffer, ok); +} + +QVariant Parser::parse(const QByteArray& jsonString, bool* ok) +{ + d->reset(); + + QBuffer buffer; + buffer.open(QBuffer::ReadWrite | QBuffer::Text); + buffer.write(jsonString); + buffer.seek(0); + return d->parse(&buffer, ok); +} + +QString Parser::errorString() const +{ + return d->m_errorMsg; +} + +int Parser::errorLine() const +{ + return d->m_errorLine; +} + +void QJson::Parser::allowSpecialNumbers(bool allowSpecialNumbers) { + d->m_specialNumbersAllowed = allowSpecialNumbers; +} + +bool Parser::specialNumbersAllowed() const { + return d->m_specialNumbersAllowed; +} diff --git a/3rdparty/qjson/src/parser.h b/3rdparty/qjson/src/parser.h new file mode 100644 index 00000000..c3132f50 --- /dev/null +++ b/3rdparty/qjson/src/parser.h @@ -0,0 +1,99 @@ +/* This file is part of QJson + * + * Copyright (C) 2008 Flavio Castelli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; 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 QJSON_PARSER_H +#define QJSON_PARSER_H + +#include "qjson_export.h" + +QT_BEGIN_NAMESPACE +class QIODevice; +class QVariant; +QT_END_NAMESPACE + +/** + * Namespace used by QJson + */ +namespace QJson { + + class ParserPrivate; + + /** + * @brief Main class used to convert JSON data to QVariant objects + */ + class QJSON_EXPORT Parser + { + public: + Parser(); + ~Parser(); + + /** + * Read JSON string from the I/O Device and converts it to a QVariant object + * @param io Input output device + * @param ok if a conversion error occurs, *ok is set to false; otherwise *ok is set to true. + * @returns a QVariant object generated from the JSON string + */ + QVariant parse(QIODevice* io, bool* ok = 0); + + /** + * This is a method provided for convenience. + * @param jsonData data containing the JSON object representation + * @param ok if a conversion error occurs, *ok is set to false; otherwise *ok is set to true. + * @returns a QVariant object generated from the JSON string + * @sa errorString + * @sa errorLine + */ + QVariant parse(const QByteArray& jsonData, bool* ok = 0); + + /** + * This method returns the error message + * @returns a QString object containing the error message of the last parse operation + * @sa errorLine + */ + QString errorString() const; + + /** + * This method returns line number where the error occurred + * @returns the line number where the error occurred + * @sa errorString + */ + int errorLine() const; + + /** + * Sets whether special numbers (Infinity, -Infinity, NaN) are allowed as an extension to + * the standard + * @param allowSpecialNumbers new value of whether special numbers are allowed + * @sa specialNumbersAllowed + */ + void allowSpecialNumbers(bool allowSpecialNumbers); + + /** + * @returns whether special numbers (Infinity, -Infinity, NaN) are allowed + * @sa allowSpecialNumbers + */ + bool specialNumbersAllowed() const; + + private: + Q_DISABLE_COPY(Parser) + ParserPrivate* const d; + }; +} + +#endif // QJSON_PARSER_H diff --git a/3rdparty/qjson/src/parser_p.h b/3rdparty/qjson/src/parser_p.h new file mode 100644 index 00000000..09883d51 --- /dev/null +++ b/3rdparty/qjson/src/parser_p.h @@ -0,0 +1,59 @@ +/* This file is part of QJson + * + * Copyright (C) 2008 Flavio Castelli + * Copyright (C) 2009 Michael Leupold + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; 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 QJSON_PARSER_P_H +#define QJSON_PARSER_P_H + +#include "parser.h" + +#include +#include + +class JSonScanner; + +namespace yy { + class json_parser; +} + +namespace QJson { + + class ParserPrivate + { + public: + ParserPrivate(); + ~ParserPrivate(); + + QVariant parse(QIODevice* io, bool* ok); + + void reset(); + + void setError(const QString &errorMsg, int line); + + JSonScanner* m_scanner; + bool m_error; + int m_errorLine; + QString m_errorMsg; + QVariant m_result; + bool m_specialNumbersAllowed; + }; +} + +#endif // QJSON_PARSER_H diff --git a/3rdparty/qjson/src/parserrunnable.cpp b/3rdparty/qjson/src/parserrunnable.cpp new file mode 100644 index 00000000..88baf4cf --- /dev/null +++ b/3rdparty/qjson/src/parserrunnable.cpp @@ -0,0 +1,68 @@ +/* This file is part of qjson + * + * Copyright (C) 2009 Flavio Castelli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; 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 "parserrunnable.h" + +#include "parser.h" + +#include +#include + +using namespace QJson; + +class QJson::ParserRunnable::Private +{ + public: + QByteArray m_data; +}; + +ParserRunnable::ParserRunnable(QObject* parent) + : QObject(parent), + QRunnable(), + d(new Private) +{ + qRegisterMetaType("QVariant"); +} + +ParserRunnable::~ParserRunnable() +{ + delete d; +} + +void ParserRunnable::setData( const QByteArray& data ) { + d->m_data = data; +} + +void ParserRunnable::run() +{ + qDebug() << Q_FUNC_INFO; + + bool ok; + Parser parser; + QVariant result = parser.parse (d->m_data, &ok); + if (ok) { + qDebug() << "successfully converted json item to QVariant object"; + emit parsingFinished(result, true, QString()); + } else { + const QString errorText = tr("An error occurred while parsing json: %1").arg(parser.errorString()); + qCritical() << errorText; + emit parsingFinished(QVariant(), false, errorText); + } +} diff --git a/3rdparty/qjson/src/parserrunnable.h b/3rdparty/qjson/src/parserrunnable.h new file mode 100644 index 00000000..fddcacd3 --- /dev/null +++ b/3rdparty/qjson/src/parserrunnable.h @@ -0,0 +1,64 @@ +/* This file is part of qjson + * + * Copyright (C) 2009 Flavio Castelli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; 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 PARSERRUNNABLE_H +#define PARSERRUNNABLE_H + +#include "qjson_export.h" + +#include +#include + +QT_BEGIN_NAMESPACE +class QVariant; +QT_END_NAMESPACE + +namespace QJson { + /** + * @brief Convenience class for converting JSON data to QVariant objects using a dedicated thread + */ + class QJSON_EXPORT ParserRunnable : public QObject, public QRunnable + { + Q_OBJECT + public: + explicit ParserRunnable(QObject* parent = 0); + ~ParserRunnable(); + + void setData( const QByteArray& data ); + + void run(); + + Q_SIGNALS: + /** + * This signal is emitted when the parsing process has been completed + * @param json contains the result of the parsing + * @param ok if a parsing error occurs ok is set to false, otherwise it's set to true. + * @param error_msg contains a string explaining the failure reason + **/ + void parsingFinished(const QVariant& json, bool ok, const QString& error_msg); + + private: + Q_DISABLE_COPY(ParserRunnable) + class Private; + Private* const d; + }; +} + +#endif // PARSERRUNNABLE_H diff --git a/3rdparty/qjson/src/position.hh b/3rdparty/qjson/src/position.hh new file mode 100644 index 00000000..3b33c273 --- /dev/null +++ b/3rdparty/qjson/src/position.hh @@ -0,0 +1,172 @@ +/* A Bison parser, made by GNU Bison 2.7. */ + +/* Positions for Bison parsers in C++ + + Copyright (C) 2002-2007, 2009-2012 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 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 . */ + +/* 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. */ + +/** + ** \file position.hh + ** Define the yy::position class. + */ + +#ifndef YY_YY_POSITION_HH_INCLUDED +# define YY_YY_POSITION_HH_INCLUDED + +# include // std::max +# include +# include + +# ifndef YY_NULL +# if defined __cplusplus && 201103L <= __cplusplus +# define YY_NULL nullptr +# else +# define YY_NULL 0 +# endif +# endif + + +namespace yy { +/* Line 36 of location.cc */ +#line 57 "position.hh" + /// Abstract a position. + class position + { + public: + + /// Construct a position. + explicit position (std::string* f = YY_NULL, + unsigned int l = 1u, + unsigned int c = 1u) + : filename (f) + , line (l) + , column (c) + { + } + + + /// Initialization. + void initialize (std::string* fn = YY_NULL, + unsigned int l = 1u, + unsigned int c = 1u) + { + filename = fn; + line = l; + column = c; + } + + /** \name Line and Column related manipulators + ** \{ */ + /// (line related) Advance to the COUNT next lines. + void lines (int count = 1) + { + column = 1u; + line += count; + } + + /// (column related) Advance to the COUNT next columns. + void columns (int count = 1) + { + column = std::max (1u, column + count); + } + /** \} */ + + /// File name to which this position refers. + std::string* filename; + /// Current line number. + unsigned int line; + /// Current column number. + unsigned int column; + }; + + /// Add and assign a position. + inline position& + operator+= (position& res, const int width) + { + res.columns (width); + return res; + } + + /// Add two position objects. + inline const position + operator+ (const position& begin, const int width) + { + position res = begin; + return res += width; + } + + /// Add and assign a position. + inline position& + operator-= (position& res, const int width) + { + return res += -width; + } + + /// Add two position objects. + inline const position + operator- (const position& begin, const int width) + { + return begin + -width; + } + + /// Compare two position objects. + inline bool + operator== (const position& pos1, const position& pos2) + { + return (pos1.line == pos2.line + && pos1.column == pos2.column + && (pos1.filename == pos2.filename + || (pos1.filename && pos2.filename + && *pos1.filename == *pos2.filename))); + } + + /// Compare two position objects. + inline bool + operator!= (const position& pos1, const position& pos2) + { + return !(pos1 == pos2); + } + + /** \brief Intercept output stream redirection. + ** \param ostr the destination output stream + ** \param pos a reference to the position to redirect + */ + template + inline std::basic_ostream& + operator<< (std::basic_ostream& ostr, const position& pos) + { + if (pos.filename) + ostr << *pos.filename << ':'; + return ostr << pos.line << '.' << pos.column; + } + + +} // yy +/* Line 148 of location.cc */ +#line 172 "position.hh" +#endif /* !YY_YY_POSITION_HH_INCLUDED */ diff --git a/3rdparty/qjson/src/qjson_debug.h b/3rdparty/qjson/src/qjson_debug.h new file mode 100644 index 00000000..6036b226 --- /dev/null +++ b/3rdparty/qjson/src/qjson_debug.h @@ -0,0 +1,34 @@ +/* This file is part of qjson + * + * Copyright (C) 2009 Michael Leupold + * Copyright (C) 2013 Silvio Moioli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; 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 QJSON_DEBUG_H +#define QJSON_DEBUG_H + +#include + +// define qjsonDebug() +#ifdef QJSON_VERBOSE_DEBUG_OUTPUT + inline QDebug qjsonDebug() { return QDebug(QtDebugMsg); } +#else + #define qjsonDebug() if(false) QDebug(QtDebugMsg) +#endif + +#endif diff --git a/3rdparty/qjson/src/qjson_export.h b/3rdparty/qjson/src/qjson_export.h new file mode 100644 index 00000000..2c54502d --- /dev/null +++ b/3rdparty/qjson/src/qjson_export.h @@ -0,0 +1,40 @@ +/* This file is part of the KDE project + Copyright (C) 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 version 2.1, as published by the Free Software Foundation. + + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; 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 QJSON_EXPORT_H +#define QJSON_EXPORT_H + +#include + +#ifndef QJSON_STATIC +# ifndef QJSON_EXPORT +# if defined(QJSON_MAKEDLL) + /* We are building this library */ +# define QJSON_EXPORT Q_DECL_EXPORT +# else + /* We are using this library */ +# define QJSON_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif +#ifndef QJSON_EXPORT +# define QJSON_EXPORT +#endif + +#endif diff --git a/3rdparty/qjson/src/qobjecthelper.cpp b/3rdparty/qjson/src/qobjecthelper.cpp new file mode 100644 index 00000000..b69f2024 --- /dev/null +++ b/3rdparty/qjson/src/qobjecthelper.cpp @@ -0,0 +1,86 @@ +/* This file is part of qjson + * + * Copyright (C) 2009 Till Adam + * Copyright (C) 2009 Flavio Castelli + * Copyright (C) 2016 Anton Kudryavtsev + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; 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 "qobjecthelper.h" + +#include +#include +#include + +using namespace QJson; + +class QObjectHelper::QObjectHelperPrivate { +}; + +QObjectHelper::QObjectHelper() + : d (new QObjectHelperPrivate) +{ +} + +QObjectHelper::~QObjectHelper() +{ + delete d; +} + +QVariantMap QObjectHelper::qobject2qvariant( const QObject* object, + const QStringList& ignoredProperties) +{ + QVariantMap result; + const QMetaObject *metaobject = object->metaObject(); + int count = metaobject->propertyCount(); + for (int i=0; iproperty(i); + const char *name = metaproperty.name(); + + if (!metaproperty.isReadable() || ignoredProperties.contains(QLatin1String(name))) + continue; + + QVariant value = object->property(name); + result[QLatin1String(name)] = value; + } + return result; +} + +void QObjectHelper::qvariant2qobject(const QVariantMap& variant, QObject* object) +{ + const QMetaObject *metaobject = object->metaObject(); + + for (QVariantMap::const_iterator iter = variant.constBegin(), + end = variant.constEnd(); iter != end; ++iter) { + int pIdx = metaobject->indexOfProperty( iter.key().toLatin1() ); + + if ( pIdx < 0 ) { + continue; + } + + QMetaProperty metaproperty = metaobject->property( pIdx ); + QVariant::Type type = metaproperty.type(); + QVariant v( iter.value() ); + if ( v.canConvert( type ) ) { + v.convert( type ); + metaproperty.write( object, v ); + } else if (QLatin1String("QVariant") == QLatin1String(metaproperty.typeName())) { + metaproperty.write( object, v ); + } + } +} diff --git a/3rdparty/qjson/src/qobjecthelper.h b/3rdparty/qjson/src/qobjecthelper.h new file mode 100644 index 00000000..e4dfed0c --- /dev/null +++ b/3rdparty/qjson/src/qobjecthelper.h @@ -0,0 +1,147 @@ +/* This file is part of qjson + * + * Copyright (C) 2009 Flavio Castelli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; 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 QOBJECTHELPER_H +#define QOBJECTHELPER_H + +#include "qjson_export.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QObject; +QT_END_NAMESPACE + +namespace QJson { + /** + * @brief Class used to convert QObject into QVariant and vivce-versa. + * During these operations only the class attributes defined as properties will + * be considered. + * Properties marked as 'non-stored' will be ignored. + * + * Suppose the declaration of the Person class looks like this: + * \code + * class Person : public QObject + { + Q_OBJECT + + Q_PROPERTY(QString name READ name WRITE setName) + Q_PROPERTY(int phoneNumber READ phoneNumber WRITE setPhoneNumber) + Q_PROPERTY(Gender gender READ gender WRITE setGender) + Q_PROPERTY(QDate dob READ dob WRITE setDob) + Q_ENUMS(Gender) + + public: + Person(QObject* parent = 0); + ~Person(); + + QString name() const; + void setName(const QString& name); + + int phoneNumber() const; + void setPhoneNumber(const int phoneNumber); + + enum Gender {Male, Female}; + void setGender(Gender gender); + Gender gender() const; + + QDate dob() const; + void setDob(const QDate& dob); + + private: + QString m_name; + int m_phoneNumber; + Gender m_gender; + QDate m_dob; + }; + \endcode + + The following code will serialize an instance of Person to JSON : + + \code + Person person; + person.setName("Flavio"); + person.setPhoneNumber(123456); + person.setGender(Person::Male); + person.setDob(QDate(1982, 7, 12)); + + QVariantMap variant = QObjectHelper::qobject2qvariant(&person); + Serializer serializer; + qDebug() << serializer.serialize( variant); + \endcode + + The generated output will be: + \code + { "dob" : "1982-07-12", "gender" : 0, "name" : "Flavio", "phoneNumber" : 123456 } + \endcode + + It's also possible to initialize a QObject using the values stored inside of + a QVariantMap. + + Suppose you have the following JSON data stored into a QString: + \code + { "dob" : "1982-07-12", "gender" : 0, "name" : "Flavio", "phoneNumber" : 123456 } + \endcode + + The following code will initialize an already allocated instance of Person + using the JSON values: + \code + Parser parser; + QVariant variant = parser.parse(json); + + Person person; + QObjectHelper::qvariant2qobject(variant.toMap(), &person); + \endcode + + \sa Parser + \sa Serializer + */ + class QJSON_EXPORT QObjectHelper { + public: + QObjectHelper(); + ~QObjectHelper(); + + /** + * This method converts a QObject instance into a QVariantMap. + * + * @param object The QObject instance to be converted. + * @param ignoredProperties Properties that won't be converted. + */ + static QVariantMap qobject2qvariant( const QObject* object, + const QStringList& ignoredProperties = QStringList(QString(QLatin1String("objectName")))); + + /** + * This method converts a QVariantMap instance into a QObject + * + * @param variant Attributes to assign to the object. + * @param object The QObject instance to update. + */ + static void qvariant2qobject(const QVariantMap& variant, QObject* object); + + private: + Q_DISABLE_COPY(QObjectHelper) + class QObjectHelperPrivate; + QObjectHelperPrivate* const d; + }; +} + +#endif // QOBJECTHELPER_H diff --git a/3rdparty/qjson/src/serializer.cpp b/3rdparty/qjson/src/serializer.cpp new file mode 100644 index 00000000..ede97bd9 --- /dev/null +++ b/3rdparty/qjson/src/serializer.cpp @@ -0,0 +1,469 @@ +/* This file is part of qjson + * + * Copyright (C) 2009 Till Adam + * Copyright (C) 2009 Flavio Castelli + * Copyright (C) 2016 Anton Kudryavtsev + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; 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 "serializer.h" + +#include +#include +#include + +// cmath does #undef for isnan and isinf macroses what can be defined in math.h +#if defined(Q_OS_SYMBIAN) || defined(Q_OS_ANDROID) || defined(Q_OS_BLACKBERRY) || defined(Q_OS_SOLARIS) +# include +#else +# include +#endif + +#ifdef Q_OS_SOLARIS +# ifndef isinf +# include +# define isinf(x) (!finite((x)) && (x)==(x)) +# endif +#endif + +#ifdef _MSC_VER // using MSVC compiler +#include +#endif + +using namespace QJson; + +class Serializer::SerializerPrivate { + public: + SerializerPrivate() : + specialNumbersAllowed(false), + indentMode(QJson::IndentNone), + doublePrecision(6) { + errorMessage.clear(); + } + QString errorMessage; + bool specialNumbersAllowed; + IndentMode indentMode; + int doublePrecision; + + QByteArray serialize( const QVariant &v, bool *ok, int indentLevel = 0); + + static QByteArray buildIndent(int spaces); + static QByteArray escapeString( const QString& str ); + static QByteArray join( const QList& list, const QByteArray& sep ); + static QByteArray join( const QList& list, char sep ); +}; + +QByteArray Serializer::SerializerPrivate::join( const QList& list, const QByteArray& sep ) { + QByteArray res; + Q_FOREACH( const QByteArray& i, list ) { + if ( !res.isEmpty() ) + res += sep; + res += i; + } + return res; +} + +QByteArray Serializer::SerializerPrivate::join( const QList& list, char sep ) { + QByteArray res; + Q_FOREACH( const QByteArray& i, list ) { + if ( !res.isEmpty() ) + res += sep; + res += i; + } + return res; +} + +QByteArray Serializer::SerializerPrivate::serialize( const QVariant &v, bool *ok, int indentLevel) +{ + QByteArray str; + const QVariant::Type type = v.type(); + + if ( ! v.isValid() ) { // invalid or null? + str = "null"; + } else if (( type == QVariant::List ) || ( type == QVariant::StringList )) { // an array or a stringlist? + const QVariantList list = v.toList(); + QList values; + Q_FOREACH( const QVariant& var, list ) + { + QByteArray serializedValue; + + serializedValue = serialize( var, ok, indentLevel+1); + + if ( !*ok ) { + break; + } + switch(indentMode) { + case QJson::IndentFull : + case QJson::IndentMedium : + case QJson::IndentMinimum : + values << serializedValue; + break; + case QJson::IndentCompact : + case QJson::IndentNone : + default: + values << serializedValue.trimmed(); + break; + } + } + + if (indentMode == QJson::IndentMedium || indentMode == QJson::IndentFull ) { + QByteArray indent = buildIndent(indentLevel); + str = indent + "[\n" + join( values, ",\n" ) + '\n' + indent + ']'; + } + else if (indentMode == QJson::IndentMinimum) { + QByteArray indent = buildIndent(indentLevel); + str = indent + "[\n" + join( values, ",\n" ) + '\n' + indent + ']'; + } + else if (indentMode == QJson::IndentCompact) { + str = '[' + join( values, "," ) + ']'; + } + else { + str = "[ " + join( values, ", " ) + " ]"; + } + + } else if ( type == QVariant::Map ) { // variant is a map? + const QVariantMap vmap = v.toMap(); + + if (indentMode == QJson::IndentMinimum) { + QByteArray indent = buildIndent(indentLevel); + str = indent + "{ "; + } + else if (indentMode == QJson::IndentMedium || indentMode == QJson::IndentFull) { + QByteArray indent = buildIndent(indentLevel); + QByteArray nextindent = buildIndent(indentLevel + 1); + str = indent + "{\n" + nextindent; + } + else if (indentMode == QJson::IndentCompact) { + str = "{"; + } + else { + str = "{ "; + } + + QList pairs; + for (QVariantMap::const_iterator it = vmap.begin(), end = vmap.end(); it != end; ++it) { + indentLevel++; + QByteArray serializedValue = serialize( it.value(), ok, indentLevel); + indentLevel--; + if ( !*ok ) { + break; + } + QByteArray key = escapeString( it.key() ); + QByteArray value = serializedValue.trimmed(); + if (indentMode == QJson::IndentCompact) { + pairs << key + ':' + value; + } else { + pairs << key + " : " + value; + } + } + + if (indentMode == QJson::IndentFull) { + QByteArray indent = buildIndent(indentLevel + 1); + str += join( pairs, ",\n" + indent); + } + else if (indentMode == QJson::IndentCompact) { + str += join( pairs, ',' ); + } + else { + str += join( pairs, ", " ); + } + + if (indentMode == QJson::IndentMedium || indentMode == QJson::IndentFull) { + QByteArray indent = buildIndent(indentLevel); + str += '\n' + indent + '}'; + } + else if (indentMode == QJson::IndentCompact) { + str += '}'; + } + else { + str += " }"; + } + + } else if ( type == QVariant::Hash ) { // variant is a hash? + const QVariantHash vhash = v.toHash(); + + if (indentMode == QJson::IndentMinimum) { + QByteArray indent = buildIndent(indentLevel); + str = indent + "{ "; + } + else if (indentMode == QJson::IndentMedium || indentMode == QJson::IndentFull) { + QByteArray indent = buildIndent(indentLevel); + QByteArray nextindent = buildIndent(indentLevel + 1); + str = indent + "{\n" + nextindent; + } + else if (indentMode == QJson::IndentCompact) { + str = "{"; + } + else { + str = "{ "; + } + + QList pairs; + for (QVariantHash::const_iterator it = vhash.begin(), end = vhash.end(); it != end; ++it) { + QByteArray serializedValue = serialize( it.value(), ok, indentLevel + 1); + + if ( !*ok ) { + break; + } + QByteArray key = escapeString( it.key() ); + QByteArray value = serializedValue.trimmed(); + if (indentMode == QJson::IndentCompact) { + pairs << key + ':' + value; + } else { + pairs << key + " : " + value; + } + } + + if (indentMode == QJson::IndentFull) { + QByteArray indent = buildIndent(indentLevel + 1); + str += join( pairs, ",\n" + indent); + } + else if (indentMode == QJson::IndentCompact) { + str += join( pairs, ',' ); + } + else { + str += join( pairs, ", " ); + } + + if (indentMode == QJson::IndentMedium || indentMode == QJson::IndentFull) { + QByteArray indent = buildIndent(indentLevel); + str += '\n' + indent + '}'; + } + else if (indentMode == QJson::IndentCompact) { + str += '}'; + } + else { + str += " }"; + } + + } else { + // Add indent, we may need to remove it later for some layouts + switch(indentMode) { + case QJson::IndentFull : + case QJson::IndentMedium : + case QJson::IndentMinimum : + str += buildIndent(indentLevel); + break; + case QJson::IndentCompact : + case QJson::IndentNone : + default: + break; + } + + if (( type == QVariant::String ) || ( type == QVariant::ByteArray )) { // a string or a byte array? + str += escapeString( v.toString() ); + } else if (( type == QVariant::Double) || ((QMetaType::Type)type == QMetaType::Float)) { // a double or a float? + const double value = v.toDouble(); + #if defined _WIN32 && !defined(Q_OS_SYMBIAN) + const bool special = _isnan(value) || !_finite(value); + #elif defined(Q_OS_SYMBIAN) || defined(Q_OS_ANDROID) || defined(Q_OS_BLACKBERRY) || defined(Q_OS_SOLARIS) + const bool special = isnan(value) || isinf(value); + #else + const bool special = std::isnan(value) || std::isinf(value); + #endif + if (special) { + if (specialNumbersAllowed) { + #if defined _WIN32 && !defined(Q_OS_SYMBIAN) + if (_isnan(value)) { + #elif defined(Q_OS_SYMBIAN) || defined(Q_OS_ANDROID) || defined(Q_OS_BLACKBERRY) || defined(Q_OS_SOLARIS) + if (isnan(value)) { + #else + if (std::isnan(value)) { + #endif + str += "NaN"; + } else { + if (value<0) { + str += '-'; + } + str += "Infinity"; + } + } else { + errorMessage += QLatin1String("Attempt to write NaN or infinity, which is not supported by json\n"); + *ok = false; + } + } else { + str = QByteArray::number( value , 'g', doublePrecision); + if( !str.contains( '.' ) && !str.contains( 'e' ) ) { + str += ".0"; + } + } + } else if ( type == QVariant::Bool ) { // boolean value? + str += ( v.toBool() ? "true" : "false" ); + } else if ( type == QVariant::ULongLong ) { // large unsigned number? + str += QByteArray::number( v.value() ); + } else if ( type == QVariant::UInt ) { // unsigned int number? + str += QByteArray::number( v.value() ); + } else if ( v.canConvert() ) { // any signed number? + str += QByteArray::number( v.value() ); + } else if ( v.canConvert() ) { // unsigned short number? + str += QByteArray::number( v.value() ); + } else if ( v.canConvert() ){ // can value be converted to string? + // this will catch QDate, QDateTime, QUrl, ... + str += escapeString( v.toString() ); + //TODO: catch other values like QImage, QRect, ... + } else { + *ok = false; + errorMessage += QLatin1String("Cannot serialize "); + errorMessage += v.toString(); + errorMessage += QLatin1String(" because type "); + errorMessage += QLatin1String(v.typeName()); + errorMessage += QLatin1String(" is not supported by QJson\n"); + } + } + if ( *ok ) + { + return str; + } + else + return QByteArray(); +} + +QByteArray Serializer::SerializerPrivate::buildIndent(int spaces) +{ + QByteArray indent; + if (spaces < 0) { + spaces = 0; + } + for (int i = 0; i < spaces; i++ ) { + indent += ' '; + } + return indent; +} + +QByteArray Serializer::SerializerPrivate::escapeString( const QString& str ) +{ + QByteArray result; + result.reserve(str.size() + 2); + result.append('\"'); + for (QString::const_iterator it = str.begin(), end = str.end(); it != end; ++it) { + ushort unicode = it->unicode(); + switch ( unicode ) { + case '\"': + result.append("\\\""); + break; + case '\\': + result.append("\\\\"); + break; + case '\b': + result.append("\\b"); + break; + case '\f': + result.append("\\f"); + break; + case '\n': + result.append("\\n"); + break; + case '\r': + result.append("\\r"); + break; + case '\t': + result.append("\\t"); + break; + default: + if ( unicode > 0x1F && unicode < 128 ) { + result.append(static_cast(unicode)); + } else { + char escaped[7]; + qsnprintf(escaped, sizeof(escaped)/sizeof(char), "\\u%04x", unicode); + result.append(escaped); + } + } + } + result.append('\"'); + return result; +} + +Serializer::Serializer() + : d( new SerializerPrivate ) +{ +} + +Serializer::~Serializer() { + delete d; +} + +void Serializer::serialize( const QVariant& v, QIODevice* io, bool* ok) +{ + Q_ASSERT( io ); + *ok = true; + + if (!io->isOpen()) { + if (!io->open(QIODevice::WriteOnly)) { + d->errorMessage = QLatin1String("Error opening device"); + *ok = false; + return; + } + } + + if (!io->isWritable()) { + d->errorMessage = QLatin1String("Device is not readable"); + io->close(); + *ok = false; + return; + } + + const QByteArray str = serialize( v, ok); + if (*ok && (io->write(str) != str.count())) { + *ok = false; + d->errorMessage = QLatin1String("Something went wrong while writing to IO device"); + } +} + +QByteArray Serializer::serialize( const QVariant &v) +{ + bool ok; + + return serialize(v, &ok); +} + +QByteArray Serializer::serialize( const QVariant &v, bool *ok) +{ + bool _ok = true; + d->errorMessage.clear(); + + if (ok) { + *ok = true; + } else { + ok = &_ok; + } + + return d->serialize(v, ok); +} + +void QJson::Serializer::allowSpecialNumbers(bool allow) { + d->specialNumbersAllowed = allow; +} + +bool QJson::Serializer::specialNumbersAllowed() const { + return d->specialNumbersAllowed; +} + +void QJson::Serializer::setIndentMode(IndentMode mode) { + d->indentMode = mode; +} + +void QJson::Serializer::setDoublePrecision(int precision) { + d->doublePrecision = precision; +} + +IndentMode QJson::Serializer::indentMode() const { + return d->indentMode; +} + +QString QJson::Serializer::errorMessage() const { + return d->errorMessage; +} + diff --git a/3rdparty/qjson/src/serializer.h b/3rdparty/qjson/src/serializer.h new file mode 100644 index 00000000..48dc9ae9 --- /dev/null +++ b/3rdparty/qjson/src/serializer.h @@ -0,0 +1,230 @@ +/* This file is part of qjson + * + * Copyright (C) 2009 Till Adam + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; 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 QJSON_SERIALIZER_H +#define QJSON_SERIALIZER_H + +#include "qjson_export.h" + +QT_BEGIN_NAMESPACE +class QIODevice; +class QString; +class QVariant; +QT_END_NAMESPACE + +namespace QJson { + /** + @brief How the indentation should work. + \verbatim + none (default) : + { "foo" : 0, "foo1" : 1, "foo2" : [ { "bar" : 1, "foo" : 0, "foobar" : 0 }, { "bar" : 1, "foo" : 1, "foobar" : 1 } ], "foo3" : [ 1, 2, 3, 4, 5, 6 ] } + + compact : + {"foo":0,"foo1":1,"foo2":[{"bar":1,"foo":0,"foobar":0},{"bar":1,"foo":1,"foobar":1}],"foo3":[1,2,3,4,5,6]} + + minimum : + { "foo" : 0, "foo1" : 1, "foo2" : [ + { "bar" : 1, "foo" : 0, "foobar" : 0 }, + { "bar" : 1, "foo" : 1, "foobar" : 1 } + ], "foo3" : [ + 1, + 2, + 3, + 4, + 5, + 6 + ] } + + medium : + { + "foo" : 0, "foo1" : 1, "foo2" : [ + { + "bar" : 1, "foo" : 0, "foobar" : 0 + }, + { + "bar" : 1, "foo" : 1, "foobar" : 1 + } + ], "foo3" : [ + 1, + 2, + 3, + 4, + 5, + 6 + ] + } + + full : + { + "foo" : 0, + "foo1" : 1, + "foo2" : [ + { + "bar" : 1, + "foo" : 0, + "foobar" : 0 + }, + { + "bar" : 1, + "foo" : 1, + "foobar" : 1 + } + ], + "foo3" : [ + 1, + 2, + 3, + 4, + 5, + 6 + ] + } + + + \endverbatim + */ + enum IndentMode { + IndentNone, + IndentCompact, + IndentMinimum, + IndentMedium, + IndentFull + }; + /** + * @brief Main class used to convert QVariant objects to JSON data. + * + * QVariant objects are converted to a string containing the JSON data. + * + * + * Usage: + * + * \code + * QVariantList people; + * + * QVariantMap bob; + * bob.insert("Name", "Bob"); + * bob.insert("Phonenumber", 123); + * + * QVariantMap alice; + * alice.insert("Name", "Alice"); + * alice.insert("Phonenumber", 321); + * + * people << bob << alice; + * + * QJson::Serializer serializer; + * bool ok; + * QByteArray json = serializer.serialize(people, &ok); + * + * if (ok) { + * qDebug() << json; + * } else { + * qCritical() << "Something went wrong:" << serializer.errorMessage(); + * } + * \endcode + * + * The output will be: + * + * \code + * "[ { "Name" : "Bob", "Phonenumber" : 123 }, + * { "Name" : "Alice", "Phonenumber" : 321 } ]" + * \endcode + * + * It's possible to tune the indentation level of the resulting string. \sa setIndentMode + */ + class QJSON_EXPORT Serializer { + public: + Serializer(); + ~Serializer(); + + /** + * This method generates a textual JSON representation and outputs it to the + * passed in I/O Device. + * @param variant The JSON document in its in-memory representation as generated by the + * parser. + * @param out Input output device + * @param ok if a conversion error occurs, *ok is set to false; otherwise *ok is set to true + */ + void serialize( const QVariant& variant, QIODevice* out, bool* ok); + + /** + * This is a method provided for convenience. It turns the passed in in-memory + * representation of the JSON document into a textual one, which is returned. + * If the returned string is empty, the document was empty. If it was null, there + * was a parsing error. + * + * @param variant The JSON document in its in-memory representation as generated by the + * parser. + * + * \deprecated This method is going to be removed with the next major release of QJson. + */ + QByteArray serialize( const QVariant& variant); + + /** + * This is a method provided for convenience. It turns the passed in in-memory + * representation of the JSON document into a textual one, which is returned. + * If the returned string is empty, the document was empty. If it was null, there + * was a parsing error. + * + * @param variant The JSON document in its in-memory representation as generated by the + * parser. + * @param ok if a conversion error occurs, *ok is set to false; otherwise *ok is set to true + */ + QByteArray serialize( const QVariant& variant, bool *ok); + + /** + * Allow or disallow writing of NaN and/or Infinity (as an extension to QJson) + */ + void allowSpecialNumbers(bool allow); + + /** + * Is Nan and/or Infinity allowed? + */ + bool specialNumbersAllowed() const; + + /** + * set output indentation mode as defined in QJson::IndentMode + */ + void setIndentMode(IndentMode mode = QJson::IndentNone); + + + /** + * set double precision used while converting Double + * \sa QByteArray::number + */ + void setDoublePrecision(int precision); + + /** + * Returns one of the indentation modes defined in QJson::IndentMode + */ + IndentMode indentMode() const; + + /** + * Returns the error message + */ + QString errorMessage() const; + + private: + Q_DISABLE_COPY(Serializer) + class SerializerPrivate; + SerializerPrivate* const d; + }; +} + +#endif // QJSON_SERIALIZER_H diff --git a/3rdparty/qjson/src/serializerrunnable.cpp b/3rdparty/qjson/src/serializerrunnable.cpp new file mode 100644 index 00000000..b1894a23 --- /dev/null +++ b/3rdparty/qjson/src/serializerrunnable.cpp @@ -0,0 +1,62 @@ +#include "serializerrunnable.h" + +/* This file is part of qjson + * + * Copyright (C) 2009 Flavio Castelli + * 2009 Frank Osterfeld + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; 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 "parserrunnable.h" +#include "serializer.h" + +#include +#include + +using namespace QJson; + +class SerializerRunnable::Private +{ +public: + QVariant json; +}; + +SerializerRunnable::SerializerRunnable(QObject* parent) + : QObject(parent), + QRunnable(), + d(new Private) +{ + qRegisterMetaType("QVariant"); +} + +SerializerRunnable::~SerializerRunnable() +{ + delete d; +} + +void SerializerRunnable::setJsonObject( const QVariant& json ) +{ + d->json = json; +} + +void SerializerRunnable::run() +{ + Serializer serializer; + bool ok; + const QByteArray serialized = serializer.serialize( d->json, &ok); + emit parsingFinished( serialized, ok, serializer.errorMessage() ); +} diff --git a/3rdparty/qjson/src/serializerrunnable.h b/3rdparty/qjson/src/serializerrunnable.h new file mode 100644 index 00000000..1a3df7c1 --- /dev/null +++ b/3rdparty/qjson/src/serializerrunnable.h @@ -0,0 +1,71 @@ +/* This file is part of qjson + * + * Copyright (C) 2009 Frank Osterfeld + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; 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 SERIALIZERRUNNABLE_H +#define SERIALIZERRUNNABLE_H + +#include "qjson_export.h" + +#include +#include + +QT_BEGIN_NAMESPACE +class QByteArray; +class QString; +class QVariant; +QT_END_NAMESPACE + +namespace QJson { + /** + * @brief Convenience class for converting JSON data to QVariant objects using a dedicated thread + */ + class QJSON_EXPORT SerializerRunnable : public QObject, public QRunnable + { + Q_OBJECT + public: + explicit SerializerRunnable(QObject* parent = 0); + ~SerializerRunnable(); + + /** + * Sets the json object to serialize. + * + * @param json QVariant containing the json representation to be serialized + */ + void setJsonObject( const QVariant& json ); + + /* reimp */ void run(); + + Q_SIGNALS: + /** + * This signal is emitted when the serialization process has been completed + * @param serialized contains the result of the serialization + * @param ok if a serialization error occurs ok is set to false, otherwise it's set to true. + * @param error_msg contains a string explaining the failure reason + **/ + void parsingFinished(const QByteArray& serialized, bool ok, const QString& error_msg); + + private: + Q_DISABLE_COPY(SerializerRunnable) + class Private; + Private* const d; + }; +} + +#endif // SERIALIZERRUNNABLE_H diff --git a/3rdparty/qjson/src/stack.hh b/3rdparty/qjson/src/stack.hh new file mode 100644 index 00000000..590accba --- /dev/null +++ b/3rdparty/qjson/src/stack.hh @@ -0,0 +1,133 @@ +/* A Bison parser, made by GNU Bison 2.7. */ + +/* Stack handling for Bison parsers in C++ + + Copyright (C) 2002-2012 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 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 . */ + +/* 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. */ + +/** + ** \file stack.hh + ** Define the yy::stack class. + */ + +#ifndef YY_YY_STACK_HH_INCLUDED +# define YY_YY_STACK_HH_INCLUDED + +# include + + +namespace yy { +/* Line 34 of stack.hh */ +#line 47 "stack.hh" + template > + class stack + { + public: + // Hide our reversed order. + typedef typename S::reverse_iterator iterator; + typedef typename S::const_reverse_iterator const_iterator; + + stack () : seq_ () + { + } + + stack (unsigned int n) : seq_ (n) + { + } + + inline + T& + operator [] (unsigned int i) + { + return seq_[i]; + } + + inline + const T& + operator [] (unsigned int i) const + { + return seq_[i]; + } + + inline + void + push (const T& t) + { + seq_.push_front (t); + } + + inline + void + pop (unsigned int n = 1) + { + for (; n; --n) + seq_.pop_front (); + } + + inline + unsigned int + height () const + { + return seq_.size (); + } + + inline const_iterator begin () const { return seq_.rbegin (); } + inline const_iterator end () const { return seq_.rend (); } + + private: + S seq_; + }; + + /// Present a slice of the top of a stack. + template > + class slice + { + public: + slice (const S& stack, unsigned int range) + : stack_ (stack) + , range_ (range) + { + } + + inline + const T& + operator [] (unsigned int i) const + { + return stack_[range_ - i]; + } + + private: + const S& stack_; + unsigned int range_; + }; + +} // yy +/* Line 116 of stack.hh */ +#line 132 "stack.hh" + +#endif /* !YY_YY_STACK_HH_INCLUDED */ diff --git a/3rdparty/qjson/tests/.gitignore b/3rdparty/qjson/tests/.gitignore new file mode 100644 index 00000000..f3c7a7c5 --- /dev/null +++ b/3rdparty/qjson/tests/.gitignore @@ -0,0 +1 @@ +Makefile diff --git a/3rdparty/qjson/tests/CMakeLists.txt b/3rdparty/qjson/tests/CMakeLists.txt new file mode 100644 index 00000000..dab0cdca --- /dev/null +++ b/3rdparty/qjson/tests/CMakeLists.txt @@ -0,0 +1,15 @@ +IF (Qt5Core_FOUND) + FIND_PACKAGE( Qt5Test REQUIRED ) + + INCLUDE_DIRECTORIES(${Qt5Test_INCLUDE_DIRS}) + ADD_DEFINITIONS(${Qt5Test_DEFINITIONS}) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Test_EXECUTABLE_COMPILE_FLAGS}") + + SET (TEST_LIBRARIES ${Qt5Test_LIBRARIES}) +ENDIF() + +ADD_SUBDIRECTORY(cmdline_tester) +ADD_SUBDIRECTORY(parser) +ADD_SUBDIRECTORY(scanner) +ADD_SUBDIRECTORY(qobjecthelper) +ADD_SUBDIRECTORY(serializer) diff --git a/3rdparty/qjson/tests/benchmarks/CMakeLists.txt b/3rdparty/qjson/tests/benchmarks/CMakeLists.txt new file mode 100644 index 00000000..9132309b --- /dev/null +++ b/3rdparty/qjson/tests/benchmarks/CMakeLists.txt @@ -0,0 +1,38 @@ +##### Probably don't want to edit below this line ##### + +SET( QT_USE_QTTEST TRUE ) + +INCLUDE(AddFileDependencies) + +# Include the library include directories, and the current build directory (moc) +INCLUDE_DIRECTORIES( + ../../include + ${CMAKE_CURRENT_BINARY_DIR} +) + +SET( UNIT_TESTS + parsingbenchmark + qlocalevsstrtod_l +) + +# Build the tests +FOREACH(test ${UNIT_TESTS}) + MESSAGE(STATUS "Building ${test}") + ADD_EXECUTABLE( + ${test} + ${test}.cpp + ) + + TARGET_LINK_LIBRARIES( + ${test} + ${QT_LIBRARIES} + ${TEST_LIBRARIES} + qjson${QJSON_SUFFIX} + ) + if (QJSON_TEST_OUTPUT STREQUAL "xml") + # produce XML output + add_test( ${test} ${test} -xml -o ${test}.tml ) + else (QJSON_TEST_OUTPUT STREQUAL "xml") + add_test( ${test} ${test} ) + endif (QJSON_TEST_OUTPUT STREQUAL "xml") +ENDFOREACH() diff --git a/3rdparty/qjson/tests/benchmarks/parsingbenchmark.cpp b/3rdparty/qjson/tests/benchmarks/parsingbenchmark.cpp new file mode 100644 index 00000000..51cbd860 --- /dev/null +++ b/3rdparty/qjson/tests/benchmarks/parsingbenchmark.cpp @@ -0,0 +1,55 @@ +/* This file is part of QJson + * + * Copyright (C) 2014 Sune Vuorela + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include + +class ParsingBenchmark: public QObject { + Q_OBJECT + private Q_SLOTS: + void benchmark(); +}; + +void ParsingBenchmark::benchmark() { + QString path = QFINDTESTDATA("largefile.json"); + + QVERIFY(QFile::exists(path)); + + QFile f(path); + QVERIFY(f.open(QIODevice::ReadOnly)); + + QByteArray data = f.readAll(); + + QVariant result; + + QJson::Parser parser; + QBENCHMARK { + result = parser.parse(data); + } + + Q_UNUSED(result); +} + + +QTEST_MAIN(ParsingBenchmark) + +#include "parsingbenchmark.moc" diff --git a/3rdparty/qjson/tests/benchmarks/qlocalevsstrtod_l.cpp b/3rdparty/qjson/tests/benchmarks/qlocalevsstrtod_l.cpp new file mode 100644 index 00000000..9bda9d45 --- /dev/null +++ b/3rdparty/qjson/tests/benchmarks/qlocalevsstrtod_l.cpp @@ -0,0 +1,70 @@ +/* This file is part of QJson + * + * Copyright (C) 2014 Sune Vuorela + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + +class QLocaleVsStrtod_l : public QObject { + Q_OBJECT + private Q_SLOTS: + void benchmark(); + void benchmark_data(); + +}; + +void QLocaleVsStrtod_l::benchmark() { + QFETCH(bool, useQLocale); + QList l; + l << strdup("0.123") << strdup("0.947834") << strdup("8.8373") << strdup("884.82921"); + + double result; + + if(useQLocale) { + QLocale c(QLocale::C); + QBENCHMARK { + Q_FOREACH(const char* str, l) { + result = c.toDouble(QString(str)); + } + } + } else { + locale_t c = newlocale(LC_NUMERIC_MASK, "C", NULL); + QBENCHMARK { + Q_FOREACH(const char* str, l) { + result = strtod_l(str, NULL, c); + } + } + } + + + Q_FOREACH(char* str, l) { + free(str); + } +} + +void QLocaleVsStrtod_l::benchmark_data() { + QTest::addColumn("useQLocale"); + + QTest::newRow("using QLocale") << true; + QTest::newRow("using strtod_l") << false; +} + +QTEST_MAIN(QLocaleVsStrtod_l); +#include "qlocalevsstrtod_l.moc" diff --git a/3rdparty/qjson/tests/cmdline_tester/.gitignore b/3rdparty/qjson/tests/cmdline_tester/.gitignore new file mode 100644 index 00000000..1347175f --- /dev/null +++ b/3rdparty/qjson/tests/cmdline_tester/.gitignore @@ -0,0 +1,4 @@ +Makefile +*.o +*.moc +cmdline_tester diff --git a/3rdparty/qjson/tests/cmdline_tester/CMakeLists.txt b/3rdparty/qjson/tests/cmdline_tester/CMakeLists.txt new file mode 100644 index 00000000..12c72cfd --- /dev/null +++ b/3rdparty/qjson/tests/cmdline_tester/CMakeLists.txt @@ -0,0 +1,35 @@ +##### Probably don't want to edit below this line ##### + +IF (WIN32 AND Qt5Core_FOUND) + FIND_PACKAGE( Qt5Widgets REQUIRED ) + + INCLUDE_DIRECTORIES(${Qt5Widgets_INCLUDE_DIRS}) + ADD_DEFINITIONS(${Qt5Widgets_DEFINITIONS}) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}") +ENDIF() + +IF (NOT Qt5Core_FOUND) + # Use it + INCLUDE( ${QT_USE_FILE} ) +ENDIF() + +INCLUDE(AddFileDependencies) + +# Include the library include directories, and the current build directory (moc) +INCLUDE_DIRECTORIES( + ../../include + ${CMAKE_CURRENT_BINARY_DIR} +) + +ADD_EXECUTABLE( + cmdline_tester + cmdline_tester.cpp + cmdlineparser.cpp +) + +TARGET_LINK_LIBRARIES( + cmdline_tester + ${QT_LIBRARIES} + ${Qt5Widgets_LIBRARIES} + qjson${QJSON_SUFFIX} +) diff --git a/3rdparty/qjson/tests/cmdline_tester/cmdline_tester.cpp b/3rdparty/qjson/tests/cmdline_tester/cmdline_tester.cpp new file mode 100644 index 00000000..81289f12 --- /dev/null +++ b/3rdparty/qjson/tests/cmdline_tester/cmdline_tester.cpp @@ -0,0 +1,99 @@ +/* This file is part of QJson + * + * Copyright (C) 2009 Flavio Castelli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "cmdlineparser.h" + +using namespace QJson; + +int main(int argc, char *argv[]) { + QCoreApplication app (argc, argv); + +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) + QTextCodec *codec = QTextCodec::codecForName("UTF-8"); + QTextCodec::setCodecForCStrings(codec); +#endif + + QTime time; + int duration; + + + CmdLineParser cmd (app.arguments()); + CmdLineParser::Result res = cmd.parse(); + if (res == CmdLineParser::Help) + return 0; + else if (res == CmdLineParser::Error) + return -1; + + QString filename = cmd.file(); + if (!QFile::exists ( filename )) { + qCritical ("The file you specified doesn't exist!"); + exit (1); + } + + Parser parser; + bool ok; + + QFile file (filename); + time.start(); + QVariant data = parser.parse (&file, &ok); + duration = time.elapsed(); + if (!ok) { + qCritical("%s:%i - Error: %s", filename.toLatin1().data(), parser.errorLine(), qPrintable(parser.errorString())); + exit (1); + } + else { + qDebug() << "Parsing of" << filename << "took" << duration << "ms"; + if (!cmd.quiet()) + qDebug() << data; + } + + if (cmd.serialize()) { + // serializer tests + qDebug() << "Serializing... "; + QJson::Serializer serializer; + serializer.setIndentMode(cmd.indentationMode()); + time.start(); + QByteArray b = serializer.serialize(data, &ok); + if (!ok) { + qCritical() << "Serialization failed:" << serializer.errorMessage(); + exit(1); + } else { + duration = time.elapsed(); + qDebug() << "Serialization took:" << duration << "ms"; + if (!cmd.quiet()) + qDebug() << b; + } + } + + qDebug() << "JOB DONE, BYE"; + return 0; +} + diff --git a/3rdparty/qjson/tests/cmdline_tester/cmdlineparser.cpp b/3rdparty/qjson/tests/cmdline_tester/cmdlineparser.cpp new file mode 100644 index 00000000..289fe0d4 --- /dev/null +++ b/3rdparty/qjson/tests/cmdline_tester/cmdlineparser.cpp @@ -0,0 +1,170 @@ +/* This file is part of qjson + * + * Copyright (C) 2010 Flavio Castelli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +#include +#ifdef Q_OS_WIN +//using Qt5 +#ifdef QT_WIDGETS_LIB +#include +#else +//using Qt4 +#include +#endif +#endif + +#include "cmdlineparser.h" + +using namespace QJson; + +const QString CmdLineParser::m_helpMessage = QLatin1String( + "Usage: cmdline_tester [options] file\n\n" + "This program converts the json data read from 'file' to a QVariant object.\n" + "--quiet Do not print output generated by parser and serializer.\n" + "--serialize Parses the QVariant object back to json.\n" + "--indent Sets the indentation level used by the 'serialize' option.\n" + " Allowed values:\n" + " - none [default]\n" + " - compact\n" + " - minimum\n" + " - medium\n" + " - full\n" + "--help Displays this help.\n" + ); + + +CmdLineParser::CmdLineParser(const QStringList &arguments) + : m_pos(0), + m_indentationMode(IndentNone), + m_serialize(false), + m_quiet(false) +{ + for (int i = 1; i < arguments.count(); ++i) { + const QString &arg = arguments.at(i); + m_arguments.append(arg); + } +} + +CmdLineParser::Result CmdLineParser::parse() +{ + bool showHelp = false; + + while (m_error.isEmpty() && hasMoreArgs()) { + const QString &arg = nextArg(); + if (arg.toLower() == QLatin1String("--indent")) + handleSetIndentationMode(); + else if (arg.toLower() == QLatin1String("--help")) + showHelp = true; + else if (arg.toLower() == QLatin1String("--serialize")) + m_serialize = true; + else if (arg.toLower() == QLatin1String("--quiet")) + m_quiet = true; + else if (!arg.startsWith(QLatin1String("--"))) + m_file = arg; + else + m_error = QString(QLatin1String("Unknown option: %1")).arg(arg); + } + + if (m_file.isEmpty()) { + m_error = QLatin1String("You have to specify the file containing the json data."); + } + + if (!m_error.isEmpty()) { + showMessage(m_error + QLatin1String("\n\n\n") + m_helpMessage, true); + return Error; + } else if (showHelp) { + showMessage(m_helpMessage, false); + return Help; + } + return Ok; +} + +bool CmdLineParser::hasMoreArgs() const +{ + return m_pos < m_arguments.count(); +} + +const QString &CmdLineParser::nextArg() +{ + Q_ASSERT(hasMoreArgs()); + return m_arguments.at(m_pos++); +} + +void CmdLineParser::handleSetIndentationMode() +{ + if (hasMoreArgs()) { + const QString &indentationMode = nextArg(); + if (indentationMode.compare(QLatin1String("none"), Qt::CaseInsensitive) == 0) + m_indentationMode = IndentNone; + else if (indentationMode.compare(QLatin1String("compact"), Qt::CaseInsensitive) == 0) + m_indentationMode = IndentCompact; + else if (indentationMode.compare(QLatin1String("minimum"), Qt::CaseInsensitive) == 0) + m_indentationMode = IndentMinimum; + else if (indentationMode.compare(QLatin1String("medium"), Qt::CaseInsensitive) == 0) + m_indentationMode = IndentMedium; + else if (indentationMode.compare(QLatin1String("full"), Qt::CaseInsensitive) == 0) + m_indentationMode = IndentFull; + else + m_error = QString(QLatin1String("Unknown indentation mode '%1'.")). + arg(indentationMode); + } else { + m_error = QLatin1String("Missing indentation level."); + } +} + +void CmdLineParser::showMessage(const QString &msg, bool error) +{ +#ifdef Q_OS_WIN + QString message = QLatin1String("
") % msg % QLatin1String("
"); + if (error) + QMessageBox::critical(0, QLatin1String("Error"), message); + else + QMessageBox::information(0, QLatin1String("Notice"), message); +#else + fprintf(error ? stderr : stdout, "%s\n", qPrintable(msg)); +#endif +} + +void CmdLineParser::setIndentationMode(const IndentMode &mode) +{ + m_indentationMode = mode; +} + +IndentMode CmdLineParser::indentationMode() const +{ + return m_indentationMode; +} + +QString CmdLineParser::file() const +{ + return m_file; +} + +bool CmdLineParser::serialize() +{ + return m_serialize; +} + +bool CmdLineParser::quiet() +{ + return m_quiet; +} + diff --git a/3rdparty/qjson/tests/cmdline_tester/cmdlineparser.h b/3rdparty/qjson/tests/cmdline_tester/cmdlineparser.h new file mode 100644 index 00000000..994a2692 --- /dev/null +++ b/3rdparty/qjson/tests/cmdline_tester/cmdlineparser.h @@ -0,0 +1,64 @@ +/* This file is part of qjson + * + * Copyright (C) 2010 Flavio Castelli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; 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 CMDLINEPARSER_H +#define CMDLINEPARSER_H + +#include +#include + +#include + +namespace QJson { + class CmdLineParser + { + public: + enum Result {Ok, Help, Error}; + + CmdLineParser(const QStringList &arguments); + Result parse(); + + void setIndentationMode(const IndentMode &mode); + IndentMode indentationMode() const; + QString helpFile() const; + QString file() const; + bool serialize(); + bool quiet(); + + void showMessage(const QString &msg, bool error); + + private: + bool hasMoreArgs() const; + const QString &nextArg(); + void handleSetIndentationMode(); + + QStringList m_arguments; + int m_pos; + IndentMode m_indentationMode; + QString m_file; + bool m_serialize; + bool m_quiet; + static const QString m_helpMessage; + QString m_error; + }; +} + +#endif + diff --git a/3rdparty/qjson/tests/cmdline_tester/example.txt b/3rdparty/qjson/tests/cmdline_tester/example.txt new file mode 100644 index 00000000..eacfbf5e --- /dev/null +++ b/3rdparty/qjson/tests/cmdline_tester/example.txt @@ -0,0 +1,22 @@ +{ + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": ["GML", "XML"] + }, + "GlossSee": "markup" + } + } + } + } +} diff --git a/3rdparty/qjson/tests/parser/.gitignore b/3rdparty/qjson/tests/parser/.gitignore new file mode 100644 index 00000000..46e5733e --- /dev/null +++ b/3rdparty/qjson/tests/parser/.gitignore @@ -0,0 +1,4 @@ +Makefile +*.o +*.moc +parser diff --git a/3rdparty/qjson/tests/parser/CMakeLists.txt b/3rdparty/qjson/tests/parser/CMakeLists.txt new file mode 100644 index 00000000..b280a383 --- /dev/null +++ b/3rdparty/qjson/tests/parser/CMakeLists.txt @@ -0,0 +1,46 @@ +##### Probably don't want to edit below this line ##### + +SET( QT_USE_QTTEST TRUE ) + +IF (NOT Qt5Core_FOUND) + # Use it + INCLUDE( ${QT_USE_FILE} ) +ENDIF() + +INCLUDE(AddFileDependencies) + +# Include the library include directories, and the current build directory (moc) +INCLUDE_DIRECTORIES( + ../../include + ${CMAKE_CURRENT_BINARY_DIR} +) + +SET( UNIT_TESTS + testparser +) + +# Build the tests +FOREACH(test ${UNIT_TESTS}) + MESSAGE(STATUS "Building ${test}") + IF (NOT Qt5Core_FOUND) + QT4_WRAP_CPP(MOC_SOURCE ${test}.cpp) + ENDIF() + ADD_EXECUTABLE( + ${test} + ${test}.cpp + ) + + ADD_FILE_DEPENDENCIES(${test}.cpp ${MOC_SOURCE}) + TARGET_LINK_LIBRARIES( + ${test} + ${QT_LIBRARIES} + ${TEST_LIBRARIES} + qjson${QJSON_SUFFIX} + ) + if (QJSON_TEST_OUTPUT STREQUAL "xml") + # produce XML output + add_test( ${test} ${test} -xml -o ${test}.tml ) + else (QJSON_TEST_OUTPUT STREQUAL "xml") + add_test( ${test} ${test} ) + endif (QJSON_TEST_OUTPUT STREQUAL "xml") +ENDFOREACH() diff --git a/3rdparty/qjson/tests/parser/testparser.cpp b/3rdparty/qjson/tests/parser/testparser.cpp new file mode 100644 index 00000000..87e128ba --- /dev/null +++ b/3rdparty/qjson/tests/parser/testparser.cpp @@ -0,0 +1,477 @@ +/* This file is part of QJson + * + * Copyright (C) 2008 Flavio Castelli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +#include + +#include + +#include +#include + +#include + +class TestParser: public QObject +{ + Q_OBJECT + private slots: + void parseInvalidEmptyJson(); + void parseInvalidEmptyJson_data(); + void parseNonAsciiString(); + void parseSimpleObject(); + void parseEmptyObject(); + void parseEmptyValue(); + void parseUrl(); + void parseMultipleObject(); + + void parseSimpleArray(); + void parseInvalidObject(); + void parseInvalidObject_data(); + void parseMultipleArray(); + + void reuseSameParser(); + + void testTrueFalseNullValues(); + void testEscapeChars(); + void testNumbers(); + void testNumbers_data(); + void testDoubleParsingWithDifferentLocale(); + void testTopLevelValues(); + void testTopLevelValues_data(); + void testReadWrite(); + void testReadWrite_data(); +}; + +Q_DECLARE_METATYPE(QVariant) +Q_DECLARE_METATYPE(QVariant::Type) + +using namespace QJson; + +void TestParser::parseInvalidEmptyJson() +{ + QFETCH(QByteArray, json); + + Parser parser; + bool ok; + QVariant result = parser.parse(json, &ok); + QVERIFY(!ok); + QVERIFY(!parser.errorString().isEmpty()); +} + +void TestParser::parseInvalidEmptyJson_data() +{ + QTest::addColumn("json"); + + QTest::newRow("empty") << QByteArray(""); + QTest::newRow("empty with spaces") << QByteArray(" \n"); +} + +void TestParser::parseSimpleObject() { + QByteArray json = "{\"foo\":\"bar\"}"; + QVariantMap map; + map.insert (QLatin1String("foo"), QLatin1String("bar")); + QVariant expected(map); + + Parser parser; + bool ok; + QVariant result = parser.parse (json, &ok); + QVERIFY (ok); + QCOMPARE(result, expected); +} + +void TestParser::parseEmptyObject() { + QByteArray json = "{}"; + QVariantMap map; + QVariant expected (map); + + Parser parser; + bool ok; + QVariant result = parser.parse (json, &ok); + QVERIFY (ok); + QCOMPARE(result, expected); +} + +void TestParser::parseEmptyValue() { + QByteArray json = "{\"value\": \"\"}"; + + QVariantMap map; + map.insert (QLatin1String("value"), QString(QLatin1String(""))); + QVariant expected (map); + + Parser parser; + bool ok; + QVariant result = parser.parse (json, &ok); + QVERIFY (ok); + QCOMPARE(result, expected); + QVERIFY (result.toMap().value(QLatin1String("value")).type() == QVariant::String); + + QString value = result.toMap().value(QLatin1String("value")).toString(); + QVERIFY (value.isEmpty()); +} + +void TestParser::parseInvalidObject() { + QFETCH(QByteArray, json); + + Parser parser; + bool ok; + parser.parse (json, &ok); + QVERIFY (!ok); + QVERIFY(!parser.errorString().isEmpty()); +} + +void TestParser::parseInvalidObject_data() { + QTest::addColumn("json"); + + QTest::newRow("unclosed object") << QByteArray("{\"foo\":\"bar\""); + QTest::newRow("infinum (disallow") << QByteArray("Infinum"); + QTest::newRow("Nan (disallow") << QByteArray("NaN"); + QTest::newRow("no data") << QByteArray(""); +} + + +void TestParser::parseNonAsciiString() { + QByteArray json = "{\"artist\":\"Queensr\\u00ffche\"}"; + QVariantMap map; + + QChar unicode_char (0x00ff); + QString unicode_string; + unicode_string.setUnicode(&unicode_char, 1); + unicode_string = QLatin1String("Queensr") + unicode_string + QLatin1String("che"); + + map.insert (QLatin1String("artist"), unicode_string); + QVariant expected (map); + + Parser parser; + bool ok; + QVariant result = parser.parse (json, &ok); + QVERIFY (ok); + QCOMPARE(result, expected); +} + +void TestParser::parseMultipleObject() { + //put also some extra spaces inside the json string + QByteArray json = "{ \"foo\":\"bar\",\n\"number\" : 51.3 , \"array\":[\"item1\", 123]}"; + QVariantMap map; + map.insert (QLatin1String("foo"), QLatin1String("bar")); + map.insert (QLatin1String("number"), 51.3); + QVariantList list; + list.append (QLatin1String("item1")); + list.append (QLatin1String("123")); + map.insert (QLatin1String("array"), list); + QVariant expected (map); + + Parser parser; + bool ok; + QVariant result = parser.parse (json, &ok); + QVERIFY (ok); + QCOMPARE(result, expected); + QVERIFY (result.toMap().value(QLatin1String("number")).canConvert()); + QVERIFY (result.toMap().value(QLatin1String("array")).canConvert()); +} + +void TestParser::parseUrl(){ + //"http:\/\/www.last.fm\/venue\/8926427" + QByteArray json = "[\"http:\\/\\/www.last.fm\\/venue\\/8926427\"]"; + QVariantList list; + list.append (QVariant(QLatin1String("http://www.last.fm/venue/8926427"))); + QVariant expected (list); + + Parser parser; + bool ok; + QVariant result = parser.parse (json, &ok); + QVERIFY (ok); + QCOMPARE(result, expected); +} + + void TestParser::parseSimpleArray() { + QByteArray json = "[\"foo\",\"bar\"]"; + QVariantList list; + list.append (QLatin1String("foo")); + list.append (QLatin1String("bar")); + QVariant expected (list); + + Parser parser; + bool ok; + QVariant result = parser.parse (json, &ok); + QVERIFY (ok); + QCOMPARE(result, expected); +} + +void TestParser::parseMultipleArray() { + //put also some extra spaces inside the json string + QByteArray json = "[ {\"foo\":\"bar\"},\n\"number\",51.3 , [\"item1\", 123]]"; + QVariantMap map; + map.insert (QLatin1String("foo"), QLatin1String("bar")); + + QVariantList array; + array.append (QLatin1String("item1")); + array.append (123); + + QVariantList list; + list.append (map); + list.append (QLatin1String("number")); + list.append (QLatin1String("51.3")); + list.append ((QVariant) array); + + QVariant expected (list); + + Parser parser; + bool ok; + QVariant result = parser.parse (json, &ok); + QVERIFY (ok); + QCOMPARE(result, expected); +} + +void TestParser::testTrueFalseNullValues() { + QByteArray json = "[true,false, null, {\"foo\" : true}]"; + QVariantList list; + list.append (QVariant(true)); + list.append (QVariant(false)); + list.append (QVariant()); + QVariantMap map; + map.insert (QLatin1String("foo"), true); + list.append (map); + QVariant expected (list); + + Parser parser; + bool ok; + QVariant result = parser.parse (json, &ok); + QVERIFY (ok); + QCOMPARE(result, expected); + QCOMPARE (result.toList().at(0).toBool(), true); + QCOMPARE (result.toList().at(1).toBool(), false); + QVERIFY (result.toList().at(2).isNull()); +} + +void TestParser::testEscapeChars() { + QByteArray json = "[\"\\b \\f \\n \\r \\t \", \" \\\\ \\/ \\\\\", \"http:\\/\\/foo.com\"]"; + + QVariantList list; + list.append (QLatin1String("\b \f \n \r \t ")); + list.append (QLatin1String(" \\ / \\")); + list.append (QLatin1String("http://foo.com")); + + QVariant expected (list); + + Parser parser; + bool ok; + QVariant result = parser.parse (json, &ok); + QVERIFY (ok); + QCOMPARE(result.toList().size(), expected.toList().size() ); + QCOMPARE(result, expected); +} + +void TestParser::testNumbers() { + QFETCH(QByteArray, input); + QFETCH(QVariant, expected); + QFETCH(QVariant::Type, type); + + Parser parser; + bool ok; + QVariant result = parser.parse ('[' + input + ']', &ok); + QVERIFY (ok); + + QVariant value = result.toList().at(0); + QCOMPARE(value, expected); + QCOMPARE( value.type(), type); +} + +void TestParser::testNumbers_data() { + QTest::addColumn( "input" ); + QTest::addColumn( "expected" ); + QTest::addColumn( "type" ); + + QByteArray input; + QVariant output; + + // simple ulonglong + input = QByteArray("1"); + output = QVariant(QVariant::ULongLong); + output.setValue(1); + + QTest::newRow("simple ulonglong") << input << output << QVariant::ULongLong; + + // big number + input = QByteArray("12345678901234567890"); + output = QVariant(QVariant::ULongLong); + output.setValue(12345678901234567890ull); + + QTest::newRow("big number") << input << output << QVariant::ULongLong; + + // simple double + input = QByteArray("2.004"); + output = QVariant(QVariant::Double); + output.setValue(2.004); + + QTest::newRow("simple double") << input << output << QVariant::Double; + + // negative int + input = QByteArray("-100"); + output = QVariant(QVariant::LongLong); + output.setValue(-100); + + QTest::newRow("negative int") << input << output << QVariant::LongLong; + + // negative double + input = QByteArray("-3.4"); + output = QVariant(QVariant::Double); + output.setValue(-3.4); + + QTest::newRow("negative double") << input << output << QVariant::Double; +} + +void TestParser::testTopLevelValues() { + QFETCH(QByteArray, input); + QFETCH(QVariant, expected); + QFETCH(QVariant::Type, type); + + Parser parser; + bool ok; + QVariant result = parser.parse (input, &ok); + QVERIFY (ok); + + QCOMPARE(result, expected); + QCOMPARE(result.type(), type); +} + +void TestParser::testTopLevelValues_data() { + QTest::addColumn( "input" ); + QTest::addColumn( "expected" ); + QTest::addColumn( "type" ); + + QByteArray input; + QVariant output; + + // string + input = QByteArray("\"foo bar\""); + output = QVariant(QLatin1String("foo bar")); + QTest::newRow("string") << input << output << QVariant::String; + + // number + input = QByteArray("2.4"); + output = QVariant(QVariant::Double); + output.setValue(2.4); + QTest::newRow("simple double") << input << output << QVariant::Double; + + // boolean + input = QByteArray("true"); + output = QVariant(QVariant::Bool); + output.setValue(true); + QTest::newRow("bool") << input << output << QVariant::Bool; + + // null + input = QByteArray("null"); + output = QVariant(); + QTest::newRow("null") << input << output << QVariant::Invalid; + + // array + input = QByteArray("[1,2,3]"); + QVariantList list; + list << QVariant(1) << QVariant(2) << QVariant(3); + output = QVariant(QVariant::List); + output.setValue(list); + QTest::newRow("array") << input << output << QVariant::List; + + // object + input = QByteArray("{\"foo\" : \"bar\"}"); + QVariantMap map; + map.insert(QLatin1String("foo"), QLatin1String("bar")); + output = QVariant(QVariant::Map); + output.setValue(map); + QTest::newRow("object") << input << output << QVariant::Map; +} + +void TestParser::testDoubleParsingWithDifferentLocale() { + QLocale oldLocale; + QLocale itLocale(QLatin1String("it_IT.utf8")); + + QCOMPARE(itLocale.name(), QLatin1String("it_IT") ); + + // the Italian locale uses ',' as digit separator. + QLocale::setDefault(itLocale); + + Parser parser; + bool ok; + QVariant result = parser.parse ("12.3", &ok); + QVERIFY (ok); + + QCOMPARE(result.toDouble(), 12.3); + + QLocale::setDefault(oldLocale); +} + +void TestParser::testReadWrite() +{ + QFETCH( QVariant, variant ); + Serializer serializer; + bool ok; + + QByteArray json = serializer.serialize(variant, &ok); + QVERIFY(ok); + + Parser parser; + QVariant result = parser.parse( json, &ok ); + QVERIFY(ok); + QCOMPARE( result, variant ); +} + +void TestParser::testReadWrite_data() +{ + QTest::addColumn( "variant" ); + + // array tests + QTest::newRow( "empty array" ) << QVariant(QVariantList()); + + // basic array + QVariantList list; + list << QString(QLatin1String("hello")); + list << 12; + QTest::newRow( "basic array" ) << QVariant(list); + + // simple map + QVariantMap map; + map[QString(QLatin1String("Name"))] = 32; + QTest::newRow( "complicated array" ) << QVariant(map); +} + +void TestParser::reuseSameParser() +{ + Parser parser; + bool ok; + + parser.parse ("12.3", &ok); + QVERIFY (ok); + + parser.parse ("wrong entry", &ok); + QVERIFY (!ok); + + parser.parse ("12.3", &ok); + QVERIFY (ok); +} + +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) +// using Qt4 rather then Qt5 +QTEST_MAIN(TestParser) +#include "moc_testparser.cxx" +#else +QTEST_GUILESS_MAIN(TestParser) +#include "testparser.moc" +#endif diff --git a/3rdparty/qjson/tests/qobjecthelper/.gitignore b/3rdparty/qjson/tests/qobjecthelper/.gitignore new file mode 100644 index 00000000..e64e2324 --- /dev/null +++ b/3rdparty/qjson/tests/qobjecthelper/.gitignore @@ -0,0 +1,5 @@ +Makefile +*.o +*.moc +moc_* +qobjecthelper diff --git a/3rdparty/qjson/tests/qobjecthelper/CMakeLists.txt b/3rdparty/qjson/tests/qobjecthelper/CMakeLists.txt new file mode 100644 index 00000000..e474ffdc --- /dev/null +++ b/3rdparty/qjson/tests/qobjecthelper/CMakeLists.txt @@ -0,0 +1,55 @@ +##### Probably don't want to edit below this line ##### + +SET( QT_USE_QTTEST TRUE ) + +IF (NOT Qt5Core_FOUND) + # Use it + INCLUDE( ${QT_USE_FILE} ) +ENDIF() + +INCLUDE(AddFileDependencies) + +# Include the library include directories, and the current build directory (moc) +INCLUDE_DIRECTORIES( + ../../include + ${CMAKE_CURRENT_BINARY_DIR} +) + +SET (qjson_test_support_SRCS person.cpp) +IF (NOT Qt5Core_FOUND) + QT4_WRAP_CPP(qjson_test_support_MOC_SRCS person.h) +ENDIF() + +ADD_LIBRARY (qjson_test_support STATIC ${qjson_test_support_SRCS} + ${qjson_test_support_MOC_SRCS}) + +SET( UNIT_TESTS + testqobjecthelper +) + +# Build the tests +FOREACH(test ${UNIT_TESTS}) + MESSAGE(STATUS "Building ${test}") + IF (NOT Qt5Core_FOUND) + QT4_WRAP_CPP(MOC_SOURCE ${test}.cpp) + ENDIF() + ADD_EXECUTABLE( + ${test} + ${test}.cpp + ) + + ADD_FILE_DEPENDENCIES(${test}.cpp ${MOC_SOURCE}) + TARGET_LINK_LIBRARIES( + ${test} + ${QT_LIBRARIES} + ${TEST_LIBRARIES} + qjson${QJSON_SUFFIX} + qjson_test_support + ) + if (QJSON_TEST_OUTPUT STREQUAL "xml") + # produce XML output + add_test( ${test} ${test} -xml -o ${test}.tml ) + else (QJSON_TEST_OUTPUT STREQUAL "xml") + add_test( ${test} ${test} ) + endif (QJSON_TEST_OUTPUT STREQUAL "xml") +ENDFOREACH() diff --git a/3rdparty/qjson/tests/qobjecthelper/person.cpp b/3rdparty/qjson/tests/qobjecthelper/person.cpp new file mode 100644 index 00000000..23291798 --- /dev/null +++ b/3rdparty/qjson/tests/qobjecthelper/person.cpp @@ -0,0 +1,75 @@ +#include "person.h" + +Person::Person(QObject* parent) + : QObject(parent), + m_name(), + m_phoneNumber(0), + m_gender(Female), + m_luckyNumber(0) +{ +} + +Person::~Person() +{ +} + +QString Person::name() const +{ + return m_name; +} + +void Person::setName(const QString& name) +{ + m_name = name; +} + +int Person::phoneNumber() const +{ + return m_phoneNumber; +} + +void Person::setPhoneNumber(const int phoneNumber) +{ + m_phoneNumber = phoneNumber; +} + +void Person::setGender(Gender gender) +{ + m_gender = gender; +} + +Person::Gender Person::gender() const +{ + return m_gender; +} + +QDate Person::dob() const +{ + return m_dob; +} + +void Person::setDob(const QDate& dob) +{ + m_dob = dob; +} + +QVariant Person::customField() const +{ + return m_customField; +} + +void Person::setCustomField(const QVariant& customField) +{ + m_customField = customField; +} + +const quint16 Person::luckyNumber() const +{ + return m_luckyNumber; +} + +void Person::setLuckyNumber(const quint16 luckyNumber) +{ + m_luckyNumber = luckyNumber; +} + diff --git a/3rdparty/qjson/tests/qobjecthelper/person.h b/3rdparty/qjson/tests/qobjecthelper/person.h new file mode 100644 index 00000000..3026bf78 --- /dev/null +++ b/3rdparty/qjson/tests/qobjecthelper/person.h @@ -0,0 +1,73 @@ +/* This file is part of qjson + * + * Copyright (C) 2009 Till Adam + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; 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 PERSON_H +#define PERSON_H + +#include +#include +#include +#include + +class Person : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString name READ name WRITE setName) + Q_PROPERTY(int phoneNumber READ phoneNumber WRITE setPhoneNumber) + Q_PROPERTY(Gender gender READ gender WRITE setGender) + Q_PROPERTY(QDate dob READ dob WRITE setDob) + Q_PROPERTY(QVariant customField READ customField WRITE setCustomField) + Q_PROPERTY(quint16 luckyNumber READ luckyNumber WRITE setLuckyNumber) + Q_ENUMS(Gender) + + public: + Person(QObject* parent = 0); + ~Person(); + + QString name() const; + void setName(const QString& name); + + int phoneNumber() const; + void setPhoneNumber(const int phoneNumber); + + enum Gender {Male, Female}; + void setGender(Gender gender); + Gender gender() const; + + QDate dob() const; + void setDob(const QDate& dob); + + QVariant customField() const; + void setCustomField(const QVariant& customField); + + const quint16 luckyNumber() const; + void setLuckyNumber(const quint16 luckyNumber); + + private: + QString m_name; + int m_phoneNumber; + Gender m_gender; + QDate m_dob; + QVariant m_customField; + quint16 m_luckyNumber; +}; + +#endif diff --git a/3rdparty/qjson/tests/qobjecthelper/testqobjecthelper.cpp b/3rdparty/qjson/tests/qobjecthelper/testqobjecthelper.cpp new file mode 100644 index 00000000..2abb9e91 --- /dev/null +++ b/3rdparty/qjson/tests/qobjecthelper/testqobjecthelper.cpp @@ -0,0 +1,126 @@ + +/* This file is part of QJson + * + * Copyright (C) 2009 Flavio Castelli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +#include +#include + +#include + +#include +#include +#include + +#include "person.h" + +class TestQObjectHelper: public QObject +{ + Q_OBJECT + private slots: + void testQObject2QVariant(); + void testQVariant2QObject(); +}; + +using namespace QJson; + +void TestQObjectHelper::testQObject2QVariant() +{ + QString name = QLatin1String("Flavio Castelli"); + int phoneNumber = 123456; + Person::Gender gender = Person::Male; + QDate dob (1982, 7, 12); + QVariantList nicknames; + nicknames << QLatin1String("nickname1") << QLatin1String("nickname2"); + quint16 luckyNumber = 123; + + Person person; + person.setName(name); + person.setPhoneNumber(phoneNumber); + person.setGender(gender); + person.setDob(dob); + person.setCustomField(nicknames); + person.setLuckyNumber(luckyNumber); + + QVariantMap expected; + expected[QLatin1String("name")] = QVariant(name); + expected[QLatin1String("phoneNumber")] = QVariant(phoneNumber); + expected[QLatin1String("gender")] = QVariant(gender); + expected[QLatin1String("dob")] = QVariant(dob); + expected[QLatin1String("customField")] = nicknames; + expected[QLatin1String("luckyNumber")] = luckyNumber; + + QVariantMap result = QObjectHelper::qobject2qvariant(&person); + QCOMPARE(result, expected); +} + +void TestQObjectHelper::testQVariant2QObject() +{ + bool ok; + QString name = QLatin1String("Flavio Castelli"); + int phoneNumber = 123456; + Person::Gender gender = Person::Male; + QDate dob (1982, 7, 12); + QVariantList nicknames; + nicknames << QLatin1String("nickname1") << QLatin1String("nickname2"); + quint16 luckyNumber = 123; + + Person expected_person; + expected_person.setName(name); + expected_person.setPhoneNumber(phoneNumber); + expected_person.setGender(gender); + expected_person.setDob(dob); + expected_person.setCustomField(nicknames); + expected_person.setLuckyNumber(luckyNumber); + + QVariantMap variant = QObjectHelper::qobject2qvariant(&expected_person); + + Serializer serializer; + QByteArray json = serializer.serialize(variant, &ok); + qDebug() << "json is" << json; + QVERIFY(ok); + + Parser parser; + QVariant parsedVariant = parser.parse(json,&ok); + QVERIFY(ok); + qDebug() << parsedVariant; + QVERIFY(parsedVariant.canConvert(QVariant::Map)); + + Person person; + QCOMPARE(Person::Female, person.gender()); + QObjectHelper::qvariant2qobject(parsedVariant.toMap(), &person); + + QCOMPARE(person.name(), name); + QCOMPARE(person.phoneNumber(), phoneNumber); + QCOMPARE(person.gender(), gender); + QCOMPARE(person.dob(), dob); + QCOMPARE(person.customField(), QVariant(nicknames)); + QCOMPARE(person.luckyNumber(), luckyNumber); +} + +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) +// using Qt4 rather then Qt5 +QTEST_MAIN(TestQObjectHelper) +#include "moc_testqobjecthelper.cxx" +#else +QTEST_GUILESS_MAIN(TestQObjectHelper) +#include "testqobjecthelper.moc" +#endif diff --git a/3rdparty/qjson/tests/scanner/CMakeLists.txt b/3rdparty/qjson/tests/scanner/CMakeLists.txt new file mode 100644 index 00000000..e2635775 --- /dev/null +++ b/3rdparty/qjson/tests/scanner/CMakeLists.txt @@ -0,0 +1,52 @@ +##### Probably don't want to edit below this line ##### + +SET( QT_USE_QTTEST TRUE ) + +IF (NOT Qt5Core_FOUND) + # Use it + INCLUDE( ${QT_USE_FILE} ) +ENDIF() + +INCLUDE(AddFileDependencies) + +# Include the library include directories, and the current build directory (moc) +INCLUDE_DIRECTORIES( + ../../src + ../../include + ${CMAKE_CURRENT_BINARY_DIR} +) + +SET( UNIT_TESTS + testscanner +) + +# Build the tests +FOREACH(test ${UNIT_TESTS}) + MESSAGE(STATUS "Building ${test}") + IF (NOT Qt5Core_FOUND) + QT4_WRAP_CPP(MOC_SOURCE ${test}.cpp) + ENDIF() + ADD_EXECUTABLE( + ${test} + ${test}.cpp + ) + + if(WIN32 AND BUILD_SHARED_LIBS) + set(QJSON_SCANNER qjson_scanner) + endif() + + ADD_FILE_DEPENDENCIES(${test}.cpp ${MOC_SOURCE}) + TARGET_LINK_LIBRARIES( + ${test} + ${QT_LIBRARIES} + ${TEST_LIBRARIES} + qjson${QJSON_SUFFIX} + ${QJSON_SCANNER} + ) + if (QJSON_TEST_OUTPUT STREQUAL "xml") + # produce XML output + add_test( ${test} ${test} -xml -o ${test}.tml ) + else (QJSON_TEST_OUTPUT STREQUAL "xml") + add_test( ${test} ${test} ) + endif (QJSON_TEST_OUTPUT STREQUAL "xml") +ENDFOREACH() diff --git a/3rdparty/qjson/tests/scanner/testscanner.cpp b/3rdparty/qjson/tests/scanner/testscanner.cpp new file mode 100644 index 00000000..07de3902 --- /dev/null +++ b/3rdparty/qjson/tests/scanner/testscanner.cpp @@ -0,0 +1,262 @@ +/* This file is part of QJson + * + * Copyright (C) 2013 Silvio Moioli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +// cmath does #undef for isnan and isinf macroses what can be defined in math.h +#if defined(Q_OS_SYMBIAN) || defined(Q_OS_ANDROID) || defined(Q_OS_BLACKBERRY) || defined(Q_OS_SOLARIS) +# include +#else +# include +#endif + +#include "json_scanner.h" +#include "json_parser.hh" +#include "location.hh" + +#define TOKEN(type) (int)yy::json_parser::token::type + +class TestScanner: public QObject +{ + Q_OBJECT + private slots: + void scanClosedDevice(); + void scanTokens(); + void scanTokens_data(); + void scanSpecialNumbers(); + void scanSpecialNumbers_data(); +}; + +Q_DECLARE_METATYPE(QVariant) +Q_DECLARE_METATYPE(QVariant::Type) + +using namespace QJson; + +void TestScanner::scanClosedDevice() { + QBuffer buffer; + int expectedResult = -1; + + JSonScanner scanner(&buffer); + QVariant yylval; + yy::location location; + int result = scanner.yylex(&yylval, &location); + QCOMPARE(result, expectedResult); +} + +void TestScanner::scanTokens() { + QFETCH(QByteArray, input); + QFETCH(bool, allowSpecialNumbers); + QFETCH(bool, skipFirstToken); + QFETCH(int, expectedResult); + QFETCH(QVariant, expectedYylval); + QFETCH(int, expectedLocationBeginLine); + QFETCH(int, expectedLocationBeginColumn); + QFETCH(int, expectedLocationEndLine); + QFETCH(int, expectedLocationEndColumn); + + QBuffer buffer; + buffer.open(QBuffer::ReadWrite); + buffer.write(input); + buffer.seek(0); + JSonScanner scanner(&buffer); + scanner.allowSpecialNumbers(allowSpecialNumbers); + + QVariant yylval; + yy::position position(YY_NULL, 1, 0); + yy::location location(position, position); + int result = scanner.yylex(&yylval, &location); + + if (skipFirstToken) { + result = scanner.yylex(&yylval, &location); + } + + QCOMPARE(result, expectedResult); + QCOMPARE(yylval, expectedYylval); + QCOMPARE(location.begin.line, (uint)expectedLocationBeginLine); + QCOMPARE(location.begin.column, (uint)expectedLocationBeginColumn); + QCOMPARE(location.end.line, (uint)expectedLocationEndLine); + QCOMPARE(location.end.column, (uint)expectedLocationEndColumn); +} + +void TestScanner::scanTokens_data() { + QTest::addColumn("input"); + QTest::addColumn("allowSpecialNumbers"); + QTest::addColumn("skipFirstToken"); + QTest::addColumn("expectedResult"); + QTest::addColumn("expectedYylval"); + QTest::addColumn("expectedLocationBeginLine"); + QTest::addColumn("expectedLocationBeginColumn"); + QTest::addColumn("expectedLocationEndLine"); + QTest::addColumn("expectedLocationEndColumn"); + + QTest::newRow("empty json") << QByteArray("") << true << false << TOKEN(END) << QVariant() << 1 << 0 << 1 << 0; + + QTest::newRow("carriage return") << QByteArray("\r") << true << false << TOKEN(END) << QVariant() << 1 << 0 << 2 << 1; + QTest::newRow("new line") << QByteArray("\n") << true << false << TOKEN(END) << QVariant() << 1 << 0 << 2 << 1; + QTest::newRow("formfeed") << QByteArray("\f") << true << false << TOKEN(END) << QVariant() << 1 << 0 << 1 << 1; + QTest::newRow("vertical tab") << QByteArray("\v") << true << false << TOKEN(END) << QVariant() << 1 << 0 << 1 << 1; + QTest::newRow("space") << QByteArray(" ") << true << false << TOKEN(END) << QVariant() << 1 << 0 << 1 << 1; + QTest::newRow("tab") << QByteArray("\t") << true << false << TOKEN(END) << QVariant() << 1 << 0 << 1 << 1; + QTest::newRow("all spaces") << QByteArray("\r\n\f\v \t") << true << false << TOKEN(END) << QVariant() << 1 << 0 << 3 << 5; + + QTest::newRow("true") << QByteArray("true") << true << false << TOKEN(TRUE_VAL) << QVariant(true) << 1 << 0 << 1 << 4; + QTest::newRow("false") << QByteArray("false") << true << false << TOKEN(FALSE_VAL) << QVariant(false) << 1 << 0 << 1 << 5; + QTest::newRow("null") << QByteArray("null") << true << false << TOKEN(NULL_VAL) << QVariant() << 1 << 0 << 1 << 4; + + QTest::newRow("alphabetic string") << QByteArray("\"abcde\"") << true << false << TOKEN(STRING) << QVariant(QLatin1String("abcde")) << 1 << 0 << 1 << 2; + QTest::newRow("ecaped string") << QByteArray("\"abcde\\b\\f\\n\\r\\t\"") << true << false << TOKEN(STRING) << QVariant(QLatin1String("abcde\b\f\n\r\t")) << 1 << 0 << 1 << 2; + QTest::newRow("invalid ecaped string") << QByteArray("\"\\x\"") << true << false << TOKEN(STRING) << QVariant(QLatin1String("x")) << 1 << 0 << 1 << 2; + QTest::newRow("escaped unicode sequence") << QByteArray("\"\\u005A\"") << true << false << TOKEN(STRING) << QVariant(QLatin1String("Z")) << 1 << 0 << 1 << 2; + QTest::newRow("invalid unicode sequence") << QByteArray("\"\\u005Z\"") << true << false << TOKEN(INVALID) << QVariant(QLatin1String("")) << 1 << 0 << 1 << 2; + QTest::newRow("empty string") << QByteArray("\"\"") << true << false << TOKEN(STRING) << QVariant(QLatin1String("")) << 1 << 0 << 1 << 2; + QTest::newRow("unterminated empty string") << QByteArray("\"") << true << false << TOKEN(INVALID) << QVariant() << 1 << 0 << 1 << 2; + QTest::newRow("unterminated string") << QByteArray("\"abcde") << true << false << TOKEN(INVALID) << QVariant() << 1 << 0 << 1 << 2; + QTest::newRow("unterminated unicode sequence") << QByteArray("\"\\u005A") << true << false << TOKEN(INVALID) << QVariant() << 1 << 0 << 1 << 2; + + QTest::newRow("single digit") << QByteArray("0") << true << false << TOKEN(NUMBER) << QVariant(0u) << 1 << 0 << 1 << 1; + QTest::newRow("multiple digits") << QByteArray("123456789") << true << false << TOKEN(NUMBER) << QVariant(123456789u) << 1 << 0 << 1 << 9; + QTest::newRow("negative single digit") << QByteArray("-0") << true << false << TOKEN(NUMBER) << QVariant(0) << 1 << 0 << 1 << 2; + QTest::newRow("negative multiple digits") << QByteArray("-123456789") << true << false << TOKEN(NUMBER) << QVariant(-123456789) << 1 << 0 << 1 << 10; + QTest::newRow("fractional single digit") << QByteArray("0.1") << true << false << TOKEN(NUMBER) << QVariant(0.1) << 1 << 0 << 1 << 3; + QTest::newRow("fractional multiple digits") << QByteArray("123456789.12") << true << false << TOKEN(NUMBER) << QVariant(123456789.12) << 1 << 0 << 1 << 12; + QTest::newRow("fractional negative single digit") << QByteArray("-0.3") << true << false << TOKEN(NUMBER) << QVariant(-0.3) << 1 << 0 << 1 << 4; + QTest::newRow("fractional negative multiple digits") << QByteArray("-123456789.23") << true << false << TOKEN(NUMBER) << QVariant(-123456789.23) << 1 << 0 << 1 << 13; + QTest::newRow("exponential single digit") << QByteArray("10e2") << true << false << TOKEN(NUMBER) << QVariant(1000) << 1 << 0 << 1 << 4; + QTest::newRow("exponential multiple digits") << QByteArray("10e23") << true << false << TOKEN(NUMBER) << QVariant(10e23) << 1 << 0 << 1 << 5; + QTest::newRow("exponential zero") << QByteArray("0e23") << true << false << TOKEN(NUMBER) << QVariant(0) << 1 << 0 << 1 << 4; + QTest::newRow("exponential fractional") << QByteArray("0.12354e23") << true << false << TOKEN(NUMBER) << QVariant(0.12354e23) << 1 << 0 << 1 << 10; + QTest::newRow("exponential fractional multiple digits") << QByteArray("120.12354e23") << true << false << TOKEN(NUMBER) << QVariant(120.12354e23) << 1 << 0 << 1 << 12; + QTest::newRow("uppercase exponential") << QByteArray("120.12354E23") << true << false << TOKEN(NUMBER) << QVariant(120.12354E23) << 1 << 0 << 1 << 12; + QTest::newRow("negative exponential single digit") << QByteArray("-10e2") << true << false << TOKEN(NUMBER) << QVariant(-1000) << 1 << 0 << 1 << 5; + QTest::newRow("negative exponential multiple digits") << QByteArray("-10e23") << true << false << TOKEN(NUMBER) << QVariant(-10e23) << 1 << 0 << 1 << 6; + QTest::newRow("negative exponential zero") << QByteArray("-0e23") << true << false << TOKEN(NUMBER) << QVariant(0) << 1 << 0 << 1 << 5; + QTest::newRow("negative exponential fractional") << QByteArray("-0.12354e23") << true << false << TOKEN(NUMBER) << QVariant(-0.12354e23) << 1 << 0 << 1 << 11; + QTest::newRow("negative exponential fractional multiple digits") << QByteArray("-120.12354e23") << true << false << TOKEN(NUMBER) << QVariant(-120.12354e23) << 1 << 0 << 1 << 13; + QTest::newRow("negative exponent") << QByteArray("10e-2") << true << false << TOKEN(NUMBER) << QVariant(10e-2) << 1 << 0 << 1 << 5; + QTest::newRow("positive exponent with plus") << QByteArray("10e+2") << true << false << TOKEN(NUMBER) << QVariant(1000) << 1 << 0 << 1 << 5; + + QTest::newRow("invalid multiple digits") << QByteArray("001") << true << false << TOKEN(NUMBER) << QVariant(0) << 1 << 0 << 1 << 1; + QTest::newRow("invalid negative multiple digits") << QByteArray("-001") << true << false << TOKEN(NUMBER) << QVariant(0) << 1 << 0 << 1 << 2; + QTest::newRow("invalid fractional") << QByteArray("12.") << true << true << TOKEN(INVALID) << QVariant(12) << 1 << 2 << 1 << 3; + QTest::newRow("invalid exponential 1") << QByteArray("-5e+") << true << true << TOKEN(INVALID) << QVariant(-5) << 1 << 2 << 1 << 3; + QTest::newRow("invalid exponential 2") << QByteArray("2e") << true << true << TOKEN(INVALID) << QVariant(2) << 1 << 1 << 1 << 2; + QTest::newRow("invalid exponential 3") << QByteArray("3e+") << true << true << TOKEN(INVALID) << QVariant(3) << 1 << 1 << 1 << 2; + QTest::newRow("invalid exponential 4") << QByteArray("4.3E") << true << true << TOKEN(INVALID) << QVariant(4.3) << 1 << 3 << 1 << 4; + QTest::newRow("invalid exponential 5") << QByteArray("5.4E-") << true << true << TOKEN(INVALID) << QVariant(5.4) << 1 << 3 << 1 << 4; + + QTest::newRow("colon") << QByteArray(":") << true << false << TOKEN(COLON) << QVariant() << 1 << 0 << 1 << 1; + QTest::newRow("comma") << QByteArray(",") << true << false << TOKEN(COMMA) << QVariant() << 1 << 0 << 1 << 1; + QTest::newRow("square bracket open") << QByteArray("[") << true << false << TOKEN(SQUARE_BRACKET_OPEN) << QVariant() << 1 << 0 << 1 << 1; + QTest::newRow("square bracket close") << QByteArray("]") << true << false << TOKEN(SQUARE_BRACKET_CLOSE) << QVariant() << 1 << 0 << 1 << 1; + QTest::newRow("curly bracket open") << QByteArray("{") << true << false << TOKEN(CURLY_BRACKET_OPEN) << QVariant() << 1 << 0 << 1 << 1; + QTest::newRow("curly bracket close") << QByteArray("}") << true << false << TOKEN(CURLY_BRACKET_CLOSE) << QVariant() << 1 << 0 << 1 << 1; + + QTest::newRow("too large unsinged number") << QByteArray("18446744073709551616") << false << false << TOKEN(INVALID) << QVariant(ULLONG_MAX) << 1 << 0 << 1 << 20; + QTest::newRow("too large signed number") << QByteArray("-9223372036854775808") << false << false << TOKEN(INVALID) << QVariant(LLONG_MIN) << 1 << 0 << 1 << 20; + QTest::newRow("too large exponential") << QByteArray("1.7976931348623157e309") << false << false << TOKEN(INVALID) << QVariant(0) << 1 << 0 << 1 << 22; + QTest::newRow("not allowed nan") << QByteArray("nan") << false << false << TOKEN(INVALID) << QVariant() << 1 << 0 << 1 << 1; + QTest::newRow("not allowed infinity") << QByteArray("Infinity") << false << false << TOKEN(INVALID) << QVariant() << 1 << 0 << 1 << 1; + QTest::newRow("unknown") << QByteArray("*") << true << false << TOKEN(INVALID) << QVariant() << 1 << 0 << 1 << 1; +} + + +void TestScanner::scanSpecialNumbers() { + QFETCH(QByteArray, input); + QFETCH(bool, isInfinity); + QFETCH(bool, isNegative); + QFETCH(bool, isNan); + QFETCH(int, expectedLocationBeginLine); + QFETCH(int, expectedLocationBeginColumn); + QFETCH(int, expectedLocationEndLine); + QFETCH(int, expectedLocationEndColumn); + + QBuffer buffer; + buffer.open(QBuffer::ReadWrite); + buffer.write(input); + buffer.seek(0); + JSonScanner scanner(&buffer); + scanner.allowSpecialNumbers(true); + + QVariant yylval; + yy::position position(YY_NULL, 1, 0); + yy::location location(position, position); + int result = scanner.yylex(&yylval, &location); + + QCOMPARE(result, TOKEN(NUMBER)); + QVERIFY(yylval.type() == QVariant::Double); + + double doubleResult = yylval.toDouble(); + + #if defined(Q_OS_SYMBIAN) || defined(Q_OS_ANDROID) || defined(Q_OS_BLACKBERRY) + QCOMPARE(bool(isinf(doubleResult)), isInfinity); + #else + // skip this test for MSVC, because there is no "isinf" function. + #ifndef Q_CC_MSVC + QCOMPARE(bool(std::isinf(doubleResult)), isInfinity); + #endif + #endif + + QCOMPARE(doubleResult<0, isNegative); + + #if defined(Q_OS_SYMBIAN) || defined(Q_OS_ANDROID) || defined(Q_OS_BLACKBERRY) + QCOMPARE(bool(isnan(doubleResult)), isNan); + #else + // skip this test for MSVC, because there is no "isinf" function. + #ifndef Q_CC_MSVC + QCOMPARE(bool(std::isnan(doubleResult)), isNan); + #endif + #endif + + QCOMPARE(location.begin.line, (uint)expectedLocationBeginLine); + QCOMPARE(location.begin.column, (uint)expectedLocationBeginColumn); + QCOMPARE(location.end.line, (uint)expectedLocationEndLine); + QCOMPARE(location.end.column, (uint)expectedLocationEndColumn); +} + +void TestScanner::scanSpecialNumbers_data() { + QTest::addColumn("input"); + QTest::addColumn("isInfinity"); + QTest::addColumn("isNegative"); + QTest::addColumn("isNan"); + QTest::addColumn("expectedLocationBeginLine"); + QTest::addColumn("expectedLocationBeginColumn"); + QTest::addColumn("expectedLocationEndLine"); + QTest::addColumn("expectedLocationEndColumn"); + + QTest::newRow("nan") << QByteArray("nan") << false << false << true << 1 << 0 << 1 << 3; + QTest::newRow("NAN") << QByteArray("NAN") << false << false << true << 1 << 0 << 1 << 3; + QTest::newRow("NaN") << QByteArray("NaN") << false << false << true << 1 << 0 << 1 << 3; + + QTest::newRow("infinity") << QByteArray("infinity") << true << false << false << 1 << 0 << 1 << 8; + QTest::newRow("Infinity") << QByteArray("infinity") << true << false << false << 1 << 0 << 1 << 8; + + QTest::newRow("-infinity") << QByteArray("-infinity") << true << true << false << 1 << 0 << 1 << 9; + QTest::newRow("-Infinity") << QByteArray("-Infinity") << true << true << false << 1 << 0 << 1 << 9; +} + +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) +// using Qt4 rather then Qt5 +QTEST_MAIN(TestScanner) +#include "moc_testscanner.cxx" +#else +QTEST_GUILESS_MAIN(TestScanner) +#include "testscanner.moc" +#endif diff --git a/3rdparty/qjson/tests/serializer/.gitignore b/3rdparty/qjson/tests/serializer/.gitignore new file mode 100644 index 00000000..4a080f16 --- /dev/null +++ b/3rdparty/qjson/tests/serializer/.gitignore @@ -0,0 +1,4 @@ +Makefile +*.o +*.moc +serializer diff --git a/3rdparty/qjson/tests/serializer/CMakeLists.txt b/3rdparty/qjson/tests/serializer/CMakeLists.txt new file mode 100644 index 00000000..7222fc7f --- /dev/null +++ b/3rdparty/qjson/tests/serializer/CMakeLists.txt @@ -0,0 +1,46 @@ +##### Probably don't want to edit below this line ##### + +SET( QT_USE_QTTEST TRUE ) + +IF (NOT Qt5Core_FOUND) + # Use it + INCLUDE( ${QT_USE_FILE} ) +ENDIF() + +INCLUDE(AddFileDependencies) + +# Include the library include directories, and the current build directory (moc) +INCLUDE_DIRECTORIES( + ../../include + ${CMAKE_CURRENT_BINARY_DIR} +) + +SET( UNIT_TESTS + testserializer +) + +# Build the tests +FOREACH(test ${UNIT_TESTS}) + MESSAGE(STATUS "Building ${test}") + IF (NOT Qt5Core_FOUND) + QT4_WRAP_CPP(MOC_SOURCE ${test}.cpp) + ENDIF() + ADD_EXECUTABLE( + ${test} + ${test}.cpp + ) + + ADD_FILE_DEPENDENCIES(${test}.cpp ${MOC_SOURCE}) + TARGET_LINK_LIBRARIES( + ${test} + ${QT_LIBRARIES} + ${TEST_LIBRARIES} + qjson${QJSON_SUFFIX} + ) + if (QJSON_TEST_OUTPUT STREQUAL "xml") + # produce XML output + add_test( ${test} ${test} -xml -o ${test}.tml ) + else (QJSON_TEST_OUTPUT STREQUAL "xml") + add_test( ${test} ${test} ) + endif (QJSON_TEST_OUTPUT STREQUAL "xml") +ENDFOREACH() diff --git a/3rdparty/qjson/tests/serializer/testserializer.cpp b/3rdparty/qjson/tests/serializer/testserializer.cpp new file mode 100644 index 00000000..31b2db04 --- /dev/null +++ b/3rdparty/qjson/tests/serializer/testserializer.cpp @@ -0,0 +1,649 @@ +/* This file is part of QJson + * + * Copyright (C) 2009 Flavio Castelli + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +#include + +#include + +#include +#include + +class TestSerializer: public QObject +{ + Q_OBJECT + private slots: + void testReadWriteEmptyDocument(); + void testReadWrite(); + void testReadWrite_data(); + void testValueNull(); + void testValueString(); + void testValueString_data(); + void testValueStringList(); + void testValueStringList_data(); + void testValueHashMap(); + void testValueInteger(); + void testValueInteger_data(); + void testValueDouble(); + void testValueDouble_data(); + void testSetDoublePrecision(); + void testValueFloat(); + void testValueFloat_data(); + void testValueBoolean(); + void testValueBoolean_data(); + void testSpecialNumbers(); + void testSpecialNumbers_data(); + void testIndentation(); + void testIndentation_data(); + void testSerializetoQIODevice(); + void testSerializeWithoutOkParam(); + void testEscapeChars(); + void testEscapeChars_data(); + + private: + void valueTest( const QVariant& value, const QString& expectedRegExp, bool errorExpected = false ); + void valueTest( const QObject* object, const QString& expectedRegExp ); +}; + +Q_DECLARE_METATYPE(QVariant) + +using namespace QJson; + +void TestSerializer::testReadWriteEmptyDocument() +{ + QByteArray json = ""; + Parser parser; + bool ok; + QVariant result = parser.parse( json, &ok ); + QVERIFY(!ok); + QVERIFY( ! result.isValid() ); + Serializer serializer; + const QByteArray serialized = serializer.serialize( result, &ok); + QVERIFY( ok ); + QByteArray expected = "null"; + QCOMPARE(expected, serialized); +} + +void TestSerializer::testReadWrite() +{ + QFETCH( QByteArray, json ); + Parser parser; + bool ok; + QVariant result = parser.parse( json, &ok ); + QVERIFY(ok); + Serializer serializer; + const QByteArray serialized = serializer.serialize( result, &ok); + QVERIFY(ok); + QVariant writtenThenRead = parser.parse( serialized, &ok ); + QVERIFY(ok); + QCOMPARE( result, writtenThenRead ); +} + +void TestSerializer::testReadWrite_data() +{ + QTest::addColumn( "json" ); + + // array tests + QTest::newRow( "empty array" ) << QByteArray("[]"); + QTest::newRow( "basic array" ) << QByteArray("[\"person\",\"bar\"]"); + QTest::newRow( "single int array" ) << QByteArray("[6]"); + QTest::newRow( "int array" ) << QByteArray("[6,5,6,7]"); + const QByteArray json = "[1,2.4, -100, -3.4, -5e+0, 2e0,3e+0,4.3E0,5.4E-0]"; + QTest::newRow( QByteArray("array of various numbers") ) << json; + + // document tests + QTest::newRow( "empty object" ) << QByteArray("{}"); + QTest::newRow( "basic document" ) << QByteArray("{\"person\":\"bar\"}"); + QTest::newRow( "object with ints" ) << QByteArray("{\"person\":6}"); + const QByteArray json2 = "{ \"person\":\"bar\",\n\"number\" : 51.3 , \"array\":[\"item1\", 123]}"; + QTest::newRow( "complicated document" ) << json2; + + // more complex cases + const QByteArray json3 = "[ {\"person\":\"bar\"},\n\"number\",51.3 , [\"item1\", 123]]"; + QTest::newRow( "complicated array" ) << json3; +} + +void TestSerializer::testIndentation() +{ + QFETCH( QByteArray, json ); + QFETCH( QByteArray, expected_compact ); + QFETCH( QByteArray, expected_min ); + QFETCH( QByteArray, expected_med ); + QFETCH( QByteArray, expected_full ); + + // parse + Parser parser; + bool ok; + QVariant parsed = parser.parse( json, &ok ); + QVERIFY(ok); + + Serializer serializer; + QVariant reparsed; + QByteArray serialized; + + // serialize with indent compact and reparse + serializer.setIndentMode(QJson::IndentCompact); + serialized = serializer.serialize( parsed, &ok); + QVERIFY(ok); + QCOMPARE( serialized, expected_compact); + reparsed = parser.parse( serialized, &ok); + QVERIFY(ok); + QCOMPARE( parsed, reparsed); + + // serialize with indent minimum and reparse + serializer.setIndentMode(QJson::IndentMinimum); + serialized = serializer.serialize( parsed, &ok); + QVERIFY(ok); + QCOMPARE( serialized, expected_min); + reparsed = parser.parse( serialized, &ok); + QVERIFY(ok); + QCOMPARE( parsed, reparsed); + + // serialize with indent medium and reparse + serializer.setIndentMode(QJson::IndentMedium); + serialized = serializer.serialize( parsed, &ok); + QVERIFY(ok); + QCOMPARE( serialized, expected_med); + reparsed = parser.parse( serialized, &ok ); + QVERIFY(ok); + QCOMPARE( parsed, reparsed); + + // serialize with indent full and reparse + serializer.setIndentMode(QJson::IndentFull); + serialized = serializer.serialize( parsed, &ok); + QVERIFY(ok); + QCOMPARE( serialized, expected_full); + reparsed = parser.parse( serialized, &ok ); + QVERIFY(ok); + QCOMPARE( parsed, reparsed); +} + +void TestSerializer::testIndentation_data() +{ + QTest::addColumn( "json" ); + QTest::addColumn( "expected_compact" ); + QTest::addColumn( "expected_min" ); + QTest::addColumn( "expected_med" ); + QTest::addColumn( "expected_full" ); + const QByteArray json = " { \"foo\" : 0, \"foo1\" : 1, \"foo2\" : [ { \"bar\" : 1, \"foo\" : 0, \"foobar\" : 0 }, { \"bar\" : 1, \"foo\" : 1, \"foobar\" : 1 } ], \"foo3\" : [ 1, 2, 3, 4, 5, 6 ], \"foobaz\" : [ \"one\", \"two\", \"three\", \"four\" ] }"; + const QByteArray ex_compact = + "{\"foo\":0,\"foo1\":1,\"foo2\":[{\"bar\":1,\"foo\":0,\"foobar\":0},{\"bar\":1,\"foo\":1,\"foobar\":1}],\"foo3\":[1,2,3,4,5,6],\"foobaz\":[\"one\",\"two\",\"three\",\"four\"]}"; + + const QByteArray ex_min = + "{ \"foo\" : 0, \"foo1\" : 1, \"foo2\" : [\n" + " { \"bar\" : 1, \"foo\" : 0, \"foobar\" : 0 },\n" + " { \"bar\" : 1, \"foo\" : 1, \"foobar\" : 1 }\n" + " ], \"foo3\" : [\n" + " 1,\n" + " 2,\n" + " 3,\n" + " 4,\n" + " 5,\n" + " 6\n" + " ], \"foobaz\" : [\n" + " \"one\",\n" + " \"two\",\n" + " \"three\",\n" + " \"four\"\n" + " ] }"; + + const QByteArray ex_med = + "{\n" + " \"foo\" : 0, \"foo1\" : 1, \"foo2\" : [\n" + " {\n" + " \"bar\" : 1, \"foo\" : 0, \"foobar\" : 0\n" + " },\n" + " {\n" + " \"bar\" : 1, \"foo\" : 1, \"foobar\" : 1\n" + " }\n" + " ], \"foo3\" : [\n" + " 1,\n" + " 2,\n" + " 3,\n" + " 4,\n" + " 5,\n" + " 6\n" + " ], \"foobaz\" : [\n" + " \"one\",\n" + " \"two\",\n" + " \"three\",\n" + " \"four\"\n" + " ]\n}"; + + const QByteArray ex_full = + "{\n" + " \"foo\" : 0,\n" + " \"foo1\" : 1,\n" + " \"foo2\" : [\n" + " {\n" + " \"bar\" : 1,\n" + " \"foo\" : 0,\n" + " \"foobar\" : 0\n" + " },\n" + " {\n" + " \"bar\" : 1,\n" + " \"foo\" : 1,\n" + " \"foobar\" : 1\n" + " }\n" + " ],\n" + " \"foo3\" : [\n" + " 1,\n" + " 2,\n" + " 3,\n" + " 4,\n" + " 5,\n" + " 6\n" + " ],\n" + " \"foobaz\" : [\n" + " \"one\",\n" + " \"two\",\n" + " \"three\",\n" + " \"four\"\n" + " ]\n" + "}"; + + QTest::newRow( "test indents" ) << json << ex_compact << ex_min << ex_med << ex_full; +} + +void TestSerializer::valueTest( const QVariant& value, const QString& expectedRegExp, bool errorExpected ) +{ + Serializer serializer; + bool ok; + const QByteArray serialized = serializer.serialize( value, &ok); + QCOMPARE(ok, !errorExpected); + QCOMPARE(serialized.isNull(), errorExpected); + const QString serializedUnicode = QString::fromUtf8( serialized ); + if (!errorExpected) { + QRegExp expected( expectedRegExp ); + QVERIFY( expected.isValid() ); + QVERIFY2( expected.exactMatch( serializedUnicode ), + qPrintable( QString( QLatin1String( "Expected regexp \"%1\" but got \"%2\"." ) ) + .arg( expectedRegExp ).arg( serializedUnicode ) ) ); + } else { + QVERIFY(!serializer.errorMessage().isEmpty()); + } +} + +void TestSerializer::valueTest( const QObject* object, const QString& expectedRegExp ) +{ + Serializer serializer; + bool ok; + const QByteArray serialized = serializer.serialize( object, &ok); + QVERIFY(ok); + const QString serializedUnicode = QString::fromUtf8( serialized ); + QRegExp expected( expectedRegExp ); + QVERIFY( expected.isValid() ); + QVERIFY2( expected.exactMatch( serializedUnicode ), + qPrintable( QString( QLatin1String( "Expected regexp \"%1\" but got \"%2\"." ) ) + .arg( expectedRegExp ).arg( serializedUnicode ) ) ); +} + +void TestSerializer::testValueNull() +{ + valueTest( QVariant(), QLatin1String( "\\s*null\\s*" ) ); + QVariantMap map; + map[QLatin1String("value")] = QVariant(); + valueTest( QVariant(map), QLatin1String( "\\s*\\{\\s*\"value\"\\s*:\\s*null\\s*\\}\\s*" ) ); +} + +void TestSerializer::testValueString() +{ + QFETCH( QVariant, value ); + QFETCH( QString, expected ); + valueTest( value, expected ); + + QVariantMap map; + map[QLatin1String("value")] = value; + valueTest( QVariant(map), QLatin1String( "\\s*\\{\\s*\"value\"\\s*:" ) + expected + QLatin1String( "\\}\\s*" ) ); +} + +void TestSerializer::testValueString_data() +{ + QTest::addColumn( "value" ); + QTest::addColumn( "expected" ); + QTest::newRow( "null string" ) << QVariant( QString() ) << QString( QLatin1String( "\\s*\"\"\\s*" ) ); + QTest::newRow( "empty string" ) << QVariant( QString( QLatin1String( "" ) ) ) << QString( QLatin1String( "\\s*\"\"\\s*" ) ); + QTest::newRow( "Simple String" ) << QVariant( QString( QLatin1String( "simpleString" ) ) ) << QString( QLatin1String( "\\s*\"simpleString\"\\s*" ) ); + QTest::newRow( "string with tab" ) << QVariant( QString( QLatin1String( "string\tstring" ) ) ) << QString( QLatin1String( "\\s*\"string\\\\tstring\"\\s*" ) ); + QTest::newRow( "string with newline" ) << QVariant( QString( QLatin1String( "string\nstring" ) ) ) << QString( QLatin1String( "\\s*\"string\\\\nstring\"\\s*" ) ); + QTest::newRow( "string with bell" ) << QVariant( QString( QLatin1String( "string\bstring" ) ) ) << QString( QLatin1String( "\\s*\"string\\\\bstring\"\\s*" ) ); + QTest::newRow( "string with return" ) << QVariant( QString( QLatin1String( "string\rstring" ) ) ) << QString( QLatin1String( "\\s*\"string\\\\rstring\"\\s*" ) ); + QTest::newRow( "string with double quote" ) << QVariant( QString( QLatin1String( "string\"string" ) ) ) << QString( QLatin1String( "\\s*\"string\\\\\"string\"\\s*" ) ); + QTest::newRow( "string with backslash" ) << QVariant( QString( QLatin1String( "string\\string" ) ) ) << QString( QLatin1String( "\\s*\"string\\\\\\\\string\"\\s*" ) ); + QString testStringWithUnicode = QString( QLatin1String( "string" ) ) + QChar( 0x2665 ) + QLatin1String( "string" ); + QString testEscapedString = QString( QLatin1String( "string" ) ) + QLatin1String("\\\\u2665") + QLatin1String( "string" ); + QTest::newRow( "string with unicode" ) << QVariant( testStringWithUnicode ) << QString( QLatin1String( "\\s*\"" ) + testEscapedString + QLatin1String( "\"\\s*" ) ); +} + +void TestSerializer::testValueStringList() +{ + QFETCH( QVariant, value ); + QFETCH( QString, expected ); + valueTest( value, expected ); + + QVariantMap map; + map[QLatin1String("value")] = value; + valueTest( QVariant(map), QLatin1String( "\\s*\\{\\s*\"value\"\\s*:" ) + expected + QLatin1String( "\\}\\s*" ) ); +} + +void TestSerializer::testValueStringList_data() +{ + QTest::addColumn( "value" ); + QTest::addColumn( "expected" ); + + QStringList stringlist; + QString expected; + + // simple QStringList + stringlist << QLatin1String("hello") << QLatin1String("world"); + expected = QLatin1String( "\\s*\\[\\s*\"hello\"\\s*,\\s*\"world\"\\s*\\]\\s*" ); + QTest::newRow( "simple QStringList" ) << QVariant( stringlist) << expected; +} + +void TestSerializer::testValueInteger() +{ + QFETCH( QVariant, value ); + QFETCH( QString, expected ); + valueTest( value, expected ); + + QVariantMap map; + map[QLatin1String("value")] = value; + valueTest( QVariant(map), QLatin1String( "\\s*\\{\\s*\"value\"\\s*:" ) + expected + QLatin1String( "\\}\\s*" ) ); +} + +void TestSerializer::testValueInteger_data() +{ + QTest::addColumn( "value" ); + QTest::addColumn( "expected" ); + + QTest::newRow( "int 0" ) << QVariant( static_cast( 0 ) ) << QString( QLatin1String( "\\s*0\\s*" ) ); + QTest::newRow( "uint 0" ) << QVariant( static_cast( 0 ) ) << QString( QLatin1String( "\\s*0\\s*" ) ); + QTest::newRow( "int -1" ) << QVariant( static_cast( -1 ) ) << QString( QLatin1String( "\\s*-1\\s*" ) ); + QTest::newRow( "int 2133149800" ) << QVariant( static_cast(2133149800) ) << QString( QLatin1String( "\\s*2133149800\\s*" ) ); + QTest::newRow( "uint 4133149800" ) << QVariant( static_cast(4133149800u) ) << QString( QLatin1String( "\\s*4133149800\\s*" ) ); + QTest::newRow( "uint64 932838457459459" ) << QVariant( Q_UINT64_C(932838457459459) ) << QString( QLatin1String( "\\s*932838457459459\\s*" ) ); + QTest::newRow( "max unsigned long long" ) << QVariant( std::numeric_limits::max() ) << QString( QLatin1String( "\\s*%1\\s*" ) ).arg(std::numeric_limits::max()); +} + +void TestSerializer::testValueDouble() +{ + QFETCH( QVariant, value ); + QFETCH( QString, expected ); + QFETCH( bool, errorExpected ); + valueTest( value, expected, errorExpected ); + + QVariantMap map; + map[QLatin1String("value")] = value; + valueTest( QVariant(map), QLatin1String( "\\s*\\{\\s*\"value\"\\s*:" ) + expected + QLatin1String( "\\}\\s*" ), errorExpected ); +} + +void TestSerializer::testValueDouble_data() +{ + QTest::addColumn( "value" ); + QTest::addColumn( "expected" ); + QTest::addColumn( "errorExpected" ); + + QTest::newRow( "double 0" ) << QVariant( 0.0 ) << QString( QLatin1String( "\\s*0.0\\s*" ) ) << false; + QTest::newRow( "double -1" ) << QVariant( -1.0 ) << QString( QLatin1String( "\\s*-1.0\\s*" ) ) << false; + QTest::newRow( "double 1.5E-20" ) << QVariant( 1.5e-20 ) << QString( QLatin1String( "\\s*1.5[Ee]-20\\s*" ) ) << false; + QTest::newRow( "double -1.5E-20" ) << QVariant( -1.5e-20 ) << QString( QLatin1String( "\\s*-1.5[Ee]-20\\s*" ) ) << false; + QTest::newRow( "double 2.0E-20" ) << QVariant( 2.0e-20 ) << QString( QLatin1String( "\\s*2(?:.0)?[Ee]-20\\s*" ) ) << false; + QTest::newRow( "double infinity" ) << QVariant( std::numeric_limits< double >::infinity() ) << QString( ) << true; + QTest::newRow( "double -infinity" ) << QVariant( -std::numeric_limits< double >::infinity() ) << QString( ) << true; + QTest::newRow( "double NaN" ) << QVariant( std::numeric_limits< double >::quiet_NaN() ) << QString( ) << true; +} + +void TestSerializer::testSetDoublePrecision() +{ + bool ok; + Serializer serializer; + QByteArray actual; + QString expected, actualUnicode; + + double num = 0.12345678; + + // Set 1 as double precision + serializer.setDoublePrecision(1); + expected = QString(QLatin1String("0.1")); + actual = serializer.serialize( QVariant(num), &ok); + QVERIFY(ok); + actualUnicode = QString::fromUtf8(actual); + + QVERIFY2( QString::compare(expected, actualUnicode ) == 0, + qPrintable( QString( QLatin1String( "Expected \"%1\" but got \"%2\"." ) ) + .arg( expected ).arg( actualUnicode ) ) ); + + // Set 2 as double precision + serializer.setDoublePrecision(2); + expected = QString(QLatin1String("0.12")); + actual = serializer.serialize( QVariant(num), &ok); + QVERIFY(ok); + actualUnicode = QString::fromUtf8(actual); + + QVERIFY2( QString::compare(expected, actualUnicode ) == 0, + qPrintable( QString( QLatin1String( "Expected \"%1\" but got \"%2\"." ) ) + .arg( expected ).arg( actualUnicode ) ) ); + + // Set 4 as double precision + serializer.setDoublePrecision(4); + expected = QString(QLatin1String("0.1235")); + actual = serializer.serialize( QVariant(num), &ok); + QVERIFY(ok); + actualUnicode = QString::fromUtf8(actual); + + QVERIFY2( QString::compare(expected, actualUnicode ) == 0, + qPrintable( QString( QLatin1String( "Expected \"%1\" but got \"%2\"." ) ) + .arg( expected ).arg( actualUnicode ) ) ); + + // Set 14 as double precision + serializer.setDoublePrecision(14); + expected = QString(QLatin1String("0.12345678")); + actual = serializer.serialize( QVariant(num), &ok); + QVERIFY(ok); + actualUnicode = QString::fromUtf8(actual); + + QVERIFY2( QString::compare(expected, actualUnicode ) == 0, + qPrintable( QString( QLatin1String( "Expected \"%1\" but got \"%2\"." ) ) + .arg( expected ).arg( actualUnicode ) ) ); +} + +void TestSerializer::testValueFloat() +{ + QFETCH( QVariant, value ); + QFETCH( QString, expected ); + QFETCH( bool, errorExpected ); + valueTest( value, expected, errorExpected ); + + QVariantMap map; + map[QLatin1String("value")] = value; + valueTest( QVariant(map), QLatin1String( "\\s*\\{\\s*\"value\"\\s*:" ) + expected + QLatin1String( "\\}\\s*" ), errorExpected ); +} + +void TestSerializer::testValueFloat_data() +{ + QVariant v; + float value; + + QTest::addColumn( "value" ); + QTest::addColumn( "expected" ); + QTest::addColumn( "errorExpected" ); + + value = 0; + v.setValue(value); + QTest::newRow( "float 0" ) << v << QString( QLatin1String( "\\s*0.0\\s*" ) ) << false; + + value = -1; + v.setValue(value); + QTest::newRow( "float -1" ) << v << QString( QLatin1String( "\\s*-1.0\\s*" ) ) << false; + + value = 1.12f; + v.setValue(value); + QTest::newRow( "float 1.12" ) << v << QString( QLatin1String( "\\s*1.12\\s*" ) ) << false; +} + +void TestSerializer::testValueBoolean() +{ + QFETCH( QVariant, value ); + QFETCH( QString, expected ); + valueTest( value, expected ); + + QVariantMap map; + map[QLatin1String("value")] = value; + valueTest( QVariant(map), QLatin1String( "\\s*\\{\\s*\"value\"\\s*:" ) + expected + QLatin1String( "\\}\\s*" ) ); +} + +void TestSerializer::testValueBoolean_data() +{ + QTest::addColumn( "value" ); + QTest::addColumn( "expected" ); + + QTest::newRow( "bool false" ) << QVariant( false ) << QString( QLatin1String( "\\s*false\\s*" ) ); + QTest::newRow( "bool true" ) << QVariant( true ) << QString( QLatin1String( "\\s*true\\s*" ) ); +} + +void TestSerializer::testSpecialNumbers() { + bool ok; + QFETCH( QVariant, value ); + QFETCH( QString, expected ); + Serializer specialSerializer; + QVERIFY(!specialSerializer.specialNumbersAllowed()); + specialSerializer.allowSpecialNumbers(true); + QVERIFY(specialSerializer.specialNumbersAllowed()); + QByteArray serialized = specialSerializer.serialize(value, &ok); + QVERIFY(ok); + QCOMPARE(QString::fromLocal8Bit(serialized), expected); +} + +void TestSerializer::testSpecialNumbers_data() { + QTest::addColumn( "value" ); + QTest::addColumn( "expected" ); + + QTest::newRow( "Infinity" ) << QVariant( std::numeric_limits< double >::infinity() ) << QString::fromLocal8Bit("Infinity"); + QTest::newRow( "-Infinity" ) << QVariant( -std::numeric_limits< double >::infinity() ) << QString::fromLocal8Bit("-Infinity"); + QTest::newRow( "Infinity" ) << QVariant( std::numeric_limits< double >::quiet_NaN() ) << QString::fromLocal8Bit("NaN"); +} + +void TestSerializer::testSerializetoQIODevice() { + QBuffer buffer; + QVariantList variant; + variant << QVariant(QLatin1String("Hello")); + variant << QVariant(QLatin1String("world!")); + + Serializer serializer; + bool ok; + + serializer.serialize(variant, &buffer, &ok); + + QCOMPARE(QString(QLatin1String(buffer.data())), + QString(QLatin1String("[ \"Hello\", \"world!\" ]"))); + QVERIFY(ok); +} + +void TestSerializer::testSerializeWithoutOkParam() { + QBuffer buffer; + QVariantList variant; + variant << QVariant(QLatin1String("Hello")); + variant << QVariant(QLatin1String("world!")); + + Serializer serializer; + + const QByteArray serialized = serializer.serialize(variant); + const QByteArray expected = "[ \"Hello\", \"world!\" ]"; + QCOMPARE(expected, serialized); + + + // test a serialization which produces an error + QVariant brokenVariant ( std::numeric_limits< double >::quiet_NaN() ); + QVERIFY(serializer.serialize(brokenVariant).isEmpty()); +} + +void TestSerializer::testValueHashMap() +{ + Serializer serializer; + bool ok; + + QVariantHash hash; + hash[QLatin1String("one")] = 1; + hash[QLatin1String("three")] = 3; + hash[QLatin1String("seven")] = 7; + + QByteArray json = serializer.serialize(hash, &ok); + QVERIFY(ok); + + Parser parser; + QVariant var = parser.parse(json, &ok); + QVERIFY(ok); + + QVariantMap vmap = var.toMap(); + QHashIterator hIt( hash ); + while ( hIt.hasNext() ) { + hIt.next(); + QString key = hIt.key(); + QVariant value = hIt.value(); + + QMap::const_iterator mIt = vmap.constFind(key); + QVERIFY(mIt != vmap.constEnd()); + QCOMPARE(mIt.value(), value); + } + +} + +void TestSerializer::testEscapeChars() +{ + QFETCH(QString, input); + QFETCH(QString, escaped); + + Serializer serializer; + bool ok; + + QVariantHash hash; + hash.insert(QLatin1String("key"), input); + QByteArray json = serializer.serialize(hash, &ok); + QVERIFY(ok); + + QString expected = QString(QLatin1String("{ \"key\" : \"%1\" }")).arg(escaped); + QString actual = QString::fromUtf8(json.data(), json.length()); + QCOMPARE(actual, expected); +} + +void TestSerializer::testEscapeChars_data() +{ + QTest::addColumn("input"); + QTest::addColumn("escaped"); + + QTest::newRow("simple ASCII string") << "input" << "input"; + QTest::newRow("ASCII new lines and tabs") << "line1\nline2\rline\t3" << "line1\\nline2\\rline\\t3"; + QTest::newRow("backspace, backslash and quotes") << "one\\two\bthree\"four" << "one\\\\two\\bthree\\\"four"; + + QChar unicodeSnowman(0x2603); + QTest::newRow("non-ASCII unicode char") << QString(unicodeSnowman) << "\\u2603"; + + QTest::newRow("control chars") << QString(QChar(0x06)) << "\\u0006"; +} + +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) +// using Qt4 rather then Qt5 +QTEST_MAIN(TestSerializer) +#include "moc_testserializer.cxx" +#else +QTEST_GUILESS_MAIN(TestSerializer) +#include "testserializer.moc" +#endif diff --git a/3rdparty/qocoa/CMakeLists.txt b/3rdparty/qocoa/CMakeLists.txt new file mode 100644 index 00000000..b3b7fa4a --- /dev/null +++ b/3rdparty/qocoa/CMakeLists.txt @@ -0,0 +1,32 @@ +set(SOURCES) + +set(HEADERS + qsearchfield.h + qbutton.h + qprogressindicatorspinning.h +) + +qt5_wrap_cpp(MOC_SOURCES ${HEADERS}) + +if(APPLE) + list(APPEND SOURCES + qsearchfield_mac.mm + qbutton_mac.mm + qprogressindicatorspinning_mac.mm + ) +else() + list(APPEND SOURCES + qsearchfield_nonmac.cpp + qbutton_nonmac.cpp + qprogressindicatorspinning_nonmac.cpp + ) + set(RESOURCES + qprogressindicatorspinning_nonmac.qrc + ) + qt5_add_resources(RESOURCES_SOURCES ${RESOURCES}) +endif() + +add_library(Qocoa STATIC + ${SOURCES} ${MOC_SOURCES} ${RESOURCES_SOURCES} +) +target_link_libraries(Qocoa ${QT_LIBRARIES}) diff --git a/3rdparty/qocoa/LICENSE.txt b/3rdparty/qocoa/LICENSE.txt new file mode 100644 index 00000000..910eb6d2 --- /dev/null +++ b/3rdparty/qocoa/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (C) 2011 by Mike McQuaid + +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. diff --git a/3rdparty/qocoa/Qocoa.pro b/3rdparty/qocoa/Qocoa.pro new file mode 100644 index 00000000..8b325d19 --- /dev/null +++ b/3rdparty/qocoa/Qocoa.pro @@ -0,0 +1,17 @@ +SOURCES += main.cpp\ + gallery.cpp \ + +HEADERS += gallery.h \ + qocoa_mac.h \ + qsearchfield.h \ + qbutton.h \ + qprogressindicatorspinning.h \ + +mac { + OBJECTIVE_SOURCES += qsearchfield_mac.mm qbutton_mac.mm qprogressindicatorspinning_mac.mm + LIBS += -framework Foundation -framework Appkit + QMAKE_CFLAGS += -mmacosx-version-min=10.6 +} else { + SOURCES += qsearchfield_nonmac.cpp qbutton_nonmac.cpp qprogressindicatorspinning_nonmac.cpp + RESOURCES += qsearchfield_nonmac.qrc qprogressindicatorspinning_nonmac.qrc +} diff --git a/3rdparty/qocoa/README.md b/3rdparty/qocoa/README.md new file mode 100644 index 00000000..fe955c04 --- /dev/null +++ b/3rdparty/qocoa/README.md @@ -0,0 +1,34 @@ +# Qocoa +Qocoa is a collection of Qt wrappers for OSX's Cocoa widgets. + +## Features +- basic fallback to sensible Qt types on non-OSX platforms +- shared class headers which expose no implementation details +- typical Qt signal/slot-based API +- trivial to import into projects (class header/implementation, [single shared global header](https://github.com/mikemcquaid/Qocoa/blob/master/qocoa_mac.h)) + +## Building +``` +git clone git://github.com/mikemcquaid/Qocoa.git +cd Qocoa +qmake # or cmake . +make +``` + +## Status +Qocoa classes are currently provided for NSButton, a spinning NSProgressIndicator and NSSearchField. There is a [TODO list](https://github.com/mikemcquaid/Qocoa/blob/master/TODO.md) for classes I hope to implement. + +## Usage +For each class you want to use copy the [`qocoa_mac.h`](https://github.com/mikemcquaid/Qocoa/blob/master/qocoa_mac.h), `$CLASS.h`, `$CLASS_mac.*` and `$CLASS_nonmac.*` files into your source tree and add them to your buildsystem. Examples are provided for [CMake](https://github.com/mikemcquaid/Qocoa/blob/master/CMakeLists.txt) and [QMake](https://github.com/mikemcquaid/Qocoa/blob/master/Qocoa.pro). + +## Contact +[Mike McQuaid](mailto:mike@mikemcquaid.com) + +## License +Qocoa is licensed under the [MIT License](http://en.wikipedia.org/wiki/MIT_License). +The full license text is available in [LICENSE.txt](https://github.com/mikemcquaid/Qocoa/blob/master/LICENSE.txt). + +The icons are taken from the [Oxygen Project](http://www.oxygen-icons.org/) and are licensed under the [Creative Commons Attribution-ShareAlike 3.0 License](http://creativecommons.org/licenses/by-sa/3.0/). + +## Gallery +![Qocoa Gallery](https://github.com/mikemcquaid/Qocoa/raw/master/gallery.png) diff --git a/3rdparty/qocoa/TODO.md b/3rdparty/qocoa/TODO.md new file mode 100644 index 00000000..45972baf --- /dev/null +++ b/3rdparty/qocoa/TODO.md @@ -0,0 +1,13 @@ +Widgets I hope to implement (or at least investigate): + +- NSTokenField +- NSSegmentedControl +- NSLevelIndicator +- NSPathControl +- NSSlider (Circular) +- NSSplitView +- NSTextFinder +- NSOutlineView in an NSScrollView (Source List) +- NSDrawer +- PDFView +- WebView diff --git a/3rdparty/qocoa/gallery.cpp b/3rdparty/qocoa/gallery.cpp new file mode 100644 index 00000000..d6442a67 --- /dev/null +++ b/3rdparty/qocoa/gallery.cpp @@ -0,0 +1,70 @@ +#include "gallery.h" + +#include + +#include "qsearchfield.h" +#include "qbutton.h" +#include "qprogressindicatorspinning.h" + +Gallery::Gallery(QWidget *parent) : QWidget(parent) +{ + setWindowTitle("Qocoa Gallery"); + QVBoxLayout *layout = new QVBoxLayout(this); + + QSearchField *searchField = new QSearchField(this); + layout->addWidget(searchField); + + QButton *roundedButton = new QButton(this, QButton::Rounded); + roundedButton->setText("Button"); + layout->addWidget(roundedButton); + + QButton *regularSquareButton = new QButton(this, QButton::RegularSquare); + regularSquareButton->setText("Button"); + layout->addWidget(regularSquareButton); + + QButton *disclosureButton = new QButton(this, QButton::Disclosure); + layout->addWidget(disclosureButton); + + QButton *shadowlessSquareButton = new QButton(this, QButton::ShadowlessSquare); + shadowlessSquareButton->setText("Button"); + layout->addWidget(shadowlessSquareButton); + + QButton *circularButton = new QButton(this, QButton::Circular); + layout->addWidget(circularButton); + + QButton *textureSquareButton = new QButton(this, QButton::TexturedSquare); + textureSquareButton->setText("Textured Button"); + layout->addWidget(textureSquareButton); + + QButton *helpButton = new QButton(this, QButton::HelpButton); + layout->addWidget(helpButton); + + QButton *smallSquareButton = new QButton(this, QButton::SmallSquare); + smallSquareButton->setText("Gradient Button"); + layout->addWidget(smallSquareButton); + + QButton *texturedRoundedButton = new QButton(this, QButton::TexturedRounded); + texturedRoundedButton->setText("Round Textured"); + layout->addWidget(texturedRoundedButton); + + QButton *roundedRectangleButton = new QButton(this, QButton::RoundRect); + roundedRectangleButton->setText("Rounded Rect Button"); + layout->addWidget(roundedRectangleButton); + + QButton *recessedButton = new QButton(this, QButton::Recessed); + recessedButton->setText("Recessed Button"); + layout->addWidget(recessedButton); + + QButton *roundedDisclosureButton = new QButton(this, QButton::RoundedDisclosure); + layout->addWidget(roundedDisclosureButton); + +#ifdef MAC_OS_X_VERSION_10_7 + QButton *inlineButton = new QButton(this, QButton::Inline); + inlineButton->setText("Inline Button"); + layout->addWidget(inlineButton); +#endif + + QProgressIndicatorSpinning *progressIndicatorSpinning = new QProgressIndicatorSpinning(this); + progressIndicatorSpinning->animate(); + layout->addWidget(progressIndicatorSpinning); +} diff --git a/3rdparty/qocoa/gallery.h b/3rdparty/qocoa/gallery.h new file mode 100644 index 00000000..1e83bad9 --- /dev/null +++ b/3rdparty/qocoa/gallery.h @@ -0,0 +1,14 @@ +#ifndef GALLERY_H +#define GALLERY_H + +#include + +class Gallery : public QWidget +{ + Q_OBJECT + +public: + explicit Gallery(QWidget *parent = 0); +}; + +#endif // WIDGET_H diff --git a/3rdparty/qocoa/gallery.png b/3rdparty/qocoa/gallery.png new file mode 100644 index 00000000..7a2736ff Binary files /dev/null and b/3rdparty/qocoa/gallery.png differ diff --git a/3rdparty/qocoa/main.cpp b/3rdparty/qocoa/main.cpp new file mode 100644 index 00000000..33e7eb8d --- /dev/null +++ b/3rdparty/qocoa/main.cpp @@ -0,0 +1,12 @@ +#include +#include "gallery.h" + +int main(int argc, char *argv[]) +{ + QApplication application(argc, argv); + + Gallery gallery; + gallery.show(); + + return application.exec(); +} diff --git a/3rdparty/qocoa/qbutton.h b/3rdparty/qocoa/qbutton.h new file mode 100644 index 00000000..0578edf9 --- /dev/null +++ b/3rdparty/qocoa/qbutton.h @@ -0,0 +1,49 @@ +#ifndef QBUTTON_H +#define QBUTTON_H + +#include +#include + +class QButtonPrivate; +class QButton : public QWidget +{ + Q_OBJECT +public: + // Matches NSBezelStyle + enum BezelStyle { + Rounded = 1, + RegularSquare = 2, + Disclosure = 5, + ShadowlessSquare = 6, + Circular = 7, + TexturedSquare = 8, + HelpButton = 9, + SmallSquare = 10, + TexturedRounded = 11, + RoundRect = 12, + Recessed = 13, + RoundedDisclosure = 14, +#ifdef MAC_OS_X_VERSION_10_7 + Inline = 15 +#endif + }; + + explicit QButton(QWidget *parent, BezelStyle bezelStyle = Rounded); + +public slots: + void setText(const QString &text); + void setImage(const QPixmap &image); + void setChecked(bool checked); + +public: + void setCheckable(bool checkable); + bool isChecked(); + +signals: + void clicked(bool checked = false); + +private: + friend class QButtonPrivate; + QPointer pimpl; +}; +#endif // QBUTTON_H diff --git a/3rdparty/qocoa/qbutton_mac.mm b/3rdparty/qocoa/qbutton_mac.mm new file mode 100644 index 00000000..93f7c7c8 --- /dev/null +++ b/3rdparty/qocoa/qbutton_mac.mm @@ -0,0 +1,227 @@ +/* +Copyright (C) 2011 by Mike McQuaid + +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 "qbutton.h" + +#include "qocoa_mac.h" + +#import "Foundation/NSAutoreleasePool.h" +#import "AppKit/NSButton.h" +#import "AppKit/NSFont.h" + +class QButtonPrivate : public QObject +{ +public: + QButtonPrivate(QButton *qButton, NSButton *nsButton, QButton::BezelStyle bezelStyle) + : QObject(qButton), qButton(qButton), nsButton(nsButton) + { + switch(bezelStyle) { + case QButton::Disclosure: + case QButton::Circular: + case QButton::Inline: + case QButton::RoundedDisclosure: + case QButton::HelpButton: + [nsButton setTitle:@""]; + default: + break; + } + + NSFont* font = 0; + switch(bezelStyle) { + case QButton::RoundRect: + font = [NSFont fontWithName:@"Lucida Grande" size:12]; + break; + + case QButton::Recessed: + font = [NSFont fontWithName:@"Lucida Grande Bold" size:12]; + break; + +#ifdef MAC_OS_X_VERSION_10_7 + case QButton::Inline: + font = [NSFont boldSystemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]; + break; +#endif + + default: + font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]; + break; + } + [nsButton setFont:font]; + + switch(bezelStyle) { + case QButton::Rounded: + qButton->setMinimumWidth(40); + qButton->setFixedHeight(24); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + break; + case QButton::RegularSquare: + case QButton::TexturedSquare: + qButton->setMinimumSize(14, 23); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + break; + case QButton::ShadowlessSquare: + qButton->setMinimumSize(5, 25); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + break; + case QButton::SmallSquare: + qButton->setMinimumSize(4, 21); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + break; + case QButton::TexturedRounded: + qButton->setMinimumSize(10, 22); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + break; + case QButton::RoundRect: + case QButton::Recessed: + qButton->setMinimumWidth(16); + qButton->setFixedHeight(18); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + break; + case QButton::Disclosure: + qButton->setMinimumWidth(13); + qButton->setFixedHeight(13); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + break; + case QButton::Circular: + qButton->setMinimumSize(16, 16); + qButton->setMaximumHeight(40); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + break; + case QButton::HelpButton: + case QButton::RoundedDisclosure: + qButton->setMinimumWidth(22); + qButton->setFixedHeight(22); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + break; +#ifdef MAC_OS_X_VERSION_10_7 + case QButton::Inline: + qButton->setMinimumWidth(10); + qButton->setFixedHeight(16); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + break; +#endif + } + + switch(bezelStyle) { + case QButton::Recessed: + [nsButton setButtonType:NSPushOnPushOffButton]; + case QButton::Disclosure: + [nsButton setButtonType:NSOnOffButton]; + default: + [nsButton setButtonType:NSMomentaryPushInButton]; + } + + [nsButton setBezelStyle:bezelStyle]; + } + + void clicked() + { + emit qButton->clicked(qButton->isChecked()); + } + + ~QButtonPrivate() { + [[nsButton target] release]; + [nsButton setTarget:nil]; + } + + QButton *qButton; + NSButton *nsButton; +}; + +@interface QButtonTarget : NSObject +{ +@public + QPointer pimpl; +} +-(void)clicked; +@end + +@implementation QButtonTarget +-(void)clicked { + Q_ASSERT(pimpl); + if (pimpl) + pimpl->clicked(); +} +@end + +QButton::QButton(QWidget *parent, BezelStyle bezelStyle) : QWidget(parent) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSButton *button = [[NSButton alloc] init]; + pimpl = new QButtonPrivate(this, button, bezelStyle); + + QButtonTarget *target = [[QButtonTarget alloc] init]; + target->pimpl = pimpl; + [button setTarget:target]; + + [button setAction:@selector(clicked)]; + + setupLayout(button, this); + + [button release]; + + [pool drain]; +} + +void QButton::setText(const QString &text) +{ + Q_ASSERT(pimpl); + if (!pimpl) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [pimpl->nsButton setTitle:fromQString(text)]; + [pool drain]; +} + +void QButton::setImage(const QPixmap &image) +{ + Q_ASSERT(pimpl); + if (pimpl) + [pimpl->nsButton setImage:fromQPixmap(image)]; +} + +void QButton::setChecked(bool checked) +{ + Q_ASSERT(pimpl); + if (pimpl) + [pimpl->nsButton setState:checked]; +} + +void QButton::setCheckable(bool checkable) +{ + const NSInteger cellMask = checkable ? NSChangeBackgroundCellMask : NSNoCellMask; + + Q_ASSERT(pimpl); + if (pimpl) + [[pimpl->nsButton cell] setShowsStateBy:cellMask]; +} + +bool QButton::isChecked() +{ + Q_ASSERT(pimpl); + if (!pimpl) + return false; + + return [pimpl->nsButton state]; +} diff --git a/3rdparty/qocoa/qbutton_nonmac.cpp b/3rdparty/qocoa/qbutton_nonmac.cpp new file mode 100644 index 00000000..0a79e2ba --- /dev/null +++ b/3rdparty/qocoa/qbutton_nonmac.cpp @@ -0,0 +1,89 @@ +/* +Copyright (C) 2011 by Mike McQuaid + +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 "qbutton.h" + +#include +#include +#include +#include + +class QButtonPrivate : public QObject +{ +public: + QButtonPrivate(QButton *button, QAbstractButton *abstractButton) + : QObject(button), abstractButton(abstractButton) {} + QPointer abstractButton; +}; + +QButton::QButton(QWidget *parent, BezelStyle) : QWidget(parent) +{ + QAbstractButton *button = 0; + if (qobject_cast(parent)) + button = new QToolButton(this); + else + button = new QPushButton(this); + connect(button, SIGNAL(clicked()), + this, SIGNAL(clicked())); + pimpl = new QButtonPrivate(this, button); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->addWidget(button); +} + +void QButton::setText(const QString &text) +{ + Q_ASSERT(pimpl); + if (pimpl) + pimpl->abstractButton->setText(text); +} + +void QButton::setImage(const QPixmap &image) +{ + Q_ASSERT(pimpl); + if (pimpl) + pimpl->abstractButton->setIcon(image); +} + +void QButton::setChecked(bool checked) +{ + Q_ASSERT(pimpl); + if (pimpl) + pimpl->abstractButton->setChecked(checked); +} + +void QButton::setCheckable(bool checkable) +{ + Q_ASSERT(pimpl); + if (pimpl) + pimpl->abstractButton->setCheckable(checkable); +} + +bool QButton::isChecked() +{ + Q_ASSERT(pimpl); + if (!pimpl) + return false; + + return pimpl->abstractButton->isChecked(); +} diff --git a/3rdparty/qocoa/qocoa_mac.h b/3rdparty/qocoa/qocoa_mac.h new file mode 100644 index 00000000..ced43117 --- /dev/null +++ b/3rdparty/qocoa/qocoa_mac.h @@ -0,0 +1,54 @@ +/* +Copyright (C) 2011 by Mike McQuaid + +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 + +static inline NSString* fromQString(const QString &string) +{ + const QByteArray utf8 = string.toUtf8(); + const char* cString = utf8.constData(); + return [[NSString alloc] initWithUTF8String:cString]; +} + +static inline QString toQString(NSString *string) +{ + if (!string) + return QString(); + return QString::fromUtf8([string UTF8String]); +} + +static inline NSImage* fromQPixmap(const QPixmap &pixmap) +{ + CGImageRef cgImage = pixmap.toMacCGImageRef(); + return [[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize]; +} + +static inline void setupLayout(void *cocoaView, QWidget *parent) +{ + parent->setAttribute(Qt::WA_NativeWindow); + QVBoxLayout *layout = new QVBoxLayout(parent); + layout->setMargin(0); + layout->addWidget(new QMacCocoaViewContainer(cocoaView, parent)); +} diff --git a/3rdparty/qocoa/qprogressindicatorspinning.h b/3rdparty/qocoa/qprogressindicatorspinning.h new file mode 100644 index 00000000..ae40a92a --- /dev/null +++ b/3rdparty/qocoa/qprogressindicatorspinning.h @@ -0,0 +1,29 @@ +#ifndef QPROGRESSINDICATORSPINNING_H +#define QPROGRESSINDICATORSPINNING_H + +#include +#include + +class QProgressIndicatorSpinningPrivate; +class QProgressIndicatorSpinning : public QWidget +{ + Q_OBJECT +public: + // Matches NSProgressIndicatorThickness + enum Thickness { + Default = 14, + Small = 10, + Large = 18, + Aqua = 12 + }; + + explicit QProgressIndicatorSpinning(QWidget *parent, + Thickness thickness = Default); +public slots: + void animate(bool animate = true); +private: + friend class QProgressIndicatorSpinningPrivate; + QPointer pimpl; +}; + +#endif // QPROGRESSINDICATORSPINNING_H diff --git a/3rdparty/qocoa/qprogressindicatorspinning_mac.mm b/3rdparty/qocoa/qprogressindicatorspinning_mac.mm new file mode 100644 index 00000000..c67c7c56 --- /dev/null +++ b/3rdparty/qocoa/qprogressindicatorspinning_mac.mm @@ -0,0 +1,70 @@ +/* +Copyright (C) 2011 by Mike McQuaid + +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 "qprogressindicatorspinning.h" + +#include "qocoa_mac.h" + +#import "Foundation/NSAutoreleasePool.h" +#import "AppKit/NSProgressIndicator.h" + +class QProgressIndicatorSpinningPrivate : public QObject +{ +public: + QProgressIndicatorSpinningPrivate(QProgressIndicatorSpinning *qProgressIndicatorSpinning, + NSProgressIndicator *nsProgressIndicator) + : QObject(qProgressIndicatorSpinning), nsProgressIndicator(nsProgressIndicator) {} + + NSProgressIndicator *nsProgressIndicator; +}; + +QProgressIndicatorSpinning::QProgressIndicatorSpinning(QWidget *parent, + Thickness thickness) + : QWidget(parent) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSProgressIndicator *progress = [[NSProgressIndicator alloc] init]; + [progress setStyle:NSProgressIndicatorSpinningStyle]; + + pimpl = new QProgressIndicatorSpinningPrivate(this, progress); + + setupLayout(progress, this); + + setFixedSize(thickness, thickness); + + [progress release]; + + [pool drain]; +} + +void QProgressIndicatorSpinning::animate(bool animate) +{ + Q_ASSERT(pimpl); + if (!pimpl) + return; + + if (animate) + [pimpl->nsProgressIndicator startAnimation:nil]; + else + [pimpl->nsProgressIndicator stopAnimation:nil]; +} diff --git a/3rdparty/qocoa/qprogressindicatorspinning_nonmac.cpp b/3rdparty/qocoa/qprogressindicatorspinning_nonmac.cpp new file mode 100644 index 00000000..6cbded6c --- /dev/null +++ b/3rdparty/qocoa/qprogressindicatorspinning_nonmac.cpp @@ -0,0 +1,72 @@ +/* +Copyright (C) 2011 by Mike McQuaid + +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 "qprogressindicatorspinning.h" + +#include +#include +#include + +class QProgressIndicatorSpinningPrivate : public QObject +{ +public: + QProgressIndicatorSpinningPrivate(QProgressIndicatorSpinning *qProgressIndicatorSpinning, + QMovie *movie) + : QObject(qProgressIndicatorSpinning), movie(movie) {} + + QPointer movie; +}; + +QProgressIndicatorSpinning::QProgressIndicatorSpinning(QWidget *parent, + Thickness thickness) + : QWidget(parent) +{ + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setMargin(0); + + QSize size(thickness, thickness); + QMovie *movie = new QMovie(this); + movie->setFileName(":/Qocoa/qprogressindicatorspinning_nonmac.gif"); + movie->setScaledSize(size); + // Roughly match OSX speed. + movie->setSpeed(200); + pimpl = new QProgressIndicatorSpinningPrivate(this, movie); + + QLabel *label = new QLabel(this); + label->setMovie(movie); + + layout->addWidget(label); + setFixedSize(size); +} + + +void QProgressIndicatorSpinning::animate(bool animate) +{ + Q_ASSERT(pimpl && pimpl->movie); + if (!(pimpl && pimpl->movie)) + return; + + if (animate) + pimpl->movie->start(); + else + pimpl->movie->stop(); +} diff --git a/3rdparty/qocoa/qprogressindicatorspinning_nonmac.gif b/3rdparty/qocoa/qprogressindicatorspinning_nonmac.gif new file mode 100644 index 00000000..3288d103 Binary files /dev/null and b/3rdparty/qocoa/qprogressindicatorspinning_nonmac.gif differ diff --git a/3rdparty/qocoa/qprogressindicatorspinning_nonmac.qrc b/3rdparty/qocoa/qprogressindicatorspinning_nonmac.qrc new file mode 100644 index 00000000..108c78ec --- /dev/null +++ b/3rdparty/qocoa/qprogressindicatorspinning_nonmac.qrc @@ -0,0 +1,5 @@ + + + qprogressindicatorspinning_nonmac.gif + + diff --git a/3rdparty/qocoa/qsearchfield.h b/3rdparty/qocoa/qsearchfield.h new file mode 100644 index 00000000..f2a0561e --- /dev/null +++ b/3rdparty/qocoa/qsearchfield.h @@ -0,0 +1,41 @@ +#ifndef QSEARCHFIELD_H +#define QSEARCHFIELD_H + +#include +#include + +class QSearchFieldPrivate; +class QSearchField : public QWidget +{ + Q_OBJECT +public: + explicit QSearchField(QWidget *parent); + + QString text() const; + QString placeholderText() const; + void setFocus(Qt::FocusReason reason); + +public slots: + void setText(const QString &text); + void setPlaceholderText(const QString &text); + void clear(); + void selectAll(); + void setFocus(); + +signals: + void textChanged(const QString &text); + void editingFinished(); + void returnPressed(); + +protected: + void resizeEvent(QResizeEvent*); + bool eventFilter(QObject*, QEvent*); + +private: + friend class QSearchFieldPrivate; + QPointer pimpl; + + Q_PROPERTY(QString placeholderText READ placeholderText WRITE setPlaceholderText); +}; + +#endif // QSEARCHFIELD_H diff --git a/3rdparty/qocoa/qsearchfield_mac.mm b/3rdparty/qocoa/qsearchfield_mac.mm new file mode 100644 index 00000000..29b5b06b --- /dev/null +++ b/3rdparty/qocoa/qsearchfield_mac.mm @@ -0,0 +1,285 @@ +/* +Copyright (C) 2011 by Mike McQuaid + +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 "qsearchfield.h" + +#include "qocoa_mac.h" + +#import "Foundation/NSAutoreleasePool.h" +#import "Foundation/NSNotification.h" +#import "AppKit/NSSearchField.h" + +#include +#include +#include + +class QSearchFieldPrivate : public QObject +{ +public: + QSearchFieldPrivate(QSearchField *qSearchField, NSSearchField *nsSearchField) + : QObject(qSearchField), qSearchField(qSearchField), nsSearchField(nsSearchField) {} + + void textDidChange(const QString &text) + { + if (qSearchField) + emit qSearchField->textChanged(text); + } + + void textDidEndEditing() + { + if (qSearchField) + emit qSearchField->editingFinished(); + } + + void returnPressed() + { + if (qSearchField) { + emit qSearchField->returnPressed(); + QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier); + QApplication::postEvent(qSearchField, event); + } + } + + void keyDownPressed() + { + if (qSearchField) { + QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier); + QApplication::postEvent(qSearchField, event); + } + } + + void keyUpPressed() + { + if (qSearchField) { + QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier); + QApplication::postEvent(qSearchField, event); + } + } + + QPointer qSearchField; + NSSearchField *nsSearchField; +}; + +@interface QSearchFieldDelegate : NSObject +{ +@public + QPointer pimpl; +} +-(void)controlTextDidChange:(NSNotification*)notification; +-(void)controlTextDidEndEditing:(NSNotification*)notification; +@end + +@implementation QSearchFieldDelegate +-(void)controlTextDidChange:(NSNotification*)notification { + Q_ASSERT(pimpl); + if (pimpl) + pimpl->textDidChange(toQString([[notification object] stringValue])); +} + +-(BOOL)control: (NSControl *)control textView: + (NSTextView *)textView doCommandBySelector: + (SEL)commandSelector { + Q_ASSERT(pimpl); + if (!pimpl) return NO; + + if (commandSelector == @selector(moveDown:)) { + pimpl->keyDownPressed(); + return YES; + } else if (commandSelector == @selector(moveUp:)) { + pimpl->keyUpPressed(); + return YES; + } + return NO; +} + +-(void)controlTextDidEndEditing:(NSNotification*)notification { + // No Q_ASSERT here as it is called on destruction. + if (!pimpl) return; + + pimpl->textDidEndEditing(); + + if ([[[notification userInfo] objectForKey:@"NSTextMovement"] intValue] == NSReturnTextMovement) + pimpl->returnPressed(); +} + +@end + +@interface QocoaSearchField : NSSearchField +-(BOOL)performKeyEquivalent:(NSEvent*)event; +@end + +@implementation QocoaSearchField +-(BOOL)performKeyEquivalent:(NSEvent*)event { + + // First, check if we have the focus. + // If no, it probably means this event isn't for us. + NSResponder* firstResponder = [[NSApp keyWindow] firstResponder]; + if ([firstResponder isKindOfClass:[NSText class]] && + [(NSText*)firstResponder delegate] == self) { + + if ([event type] == NSKeyDown && [event modifierFlags] & NSCommandKeyMask) + { + QString keyString = toQString([event characters]); + if (keyString == "a") // Cmd+a + { + [self performSelector:@selector(selectText:)]; + return YES; + } + else if (keyString == "c") // Cmd+c + { + [[self currentEditor] copy: nil]; + return YES; + } + else if (keyString == "v") // Cmd+v + { + [[self currentEditor] paste: nil]; + return YES; + } + else if (keyString == "x") // Cmd+x + { + [[self currentEditor] cut: nil]; + return YES; + } + } + } + + return NO; +} +@end + +QSearchField::QSearchField(QWidget *parent) : QWidget(parent) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSSearchField *search = [[QocoaSearchField alloc] init]; + + QSearchFieldDelegate *delegate = [[QSearchFieldDelegate alloc] init]; + pimpl = delegate->pimpl = new QSearchFieldPrivate(this, search); + [search setDelegate:delegate]; + + setupLayout(search, this); + + setFixedHeight(24); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + [search release]; + + [pool drain]; +} + +void QSearchField::setText(const QString &text) +{ + Q_ASSERT(pimpl); + if (!pimpl) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [pimpl->nsSearchField setStringValue:fromQString(text)]; + if (!text.isEmpty()) { + [pimpl->nsSearchField selectText:pimpl->nsSearchField]; + [[pimpl->nsSearchField currentEditor] setSelectedRange:NSMakeRange([[pimpl->nsSearchField stringValue] length], 0)]; + } + [pool drain]; +} + +void QSearchField::setPlaceholderText(const QString &text) +{ + Q_ASSERT(pimpl); + if (!pimpl) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[pimpl->nsSearchField cell] setPlaceholderString:fromQString(text)]; + [pool drain]; +} + +QString QSearchField::placeholderText() const { + Q_ASSERT(pimpl); + NSString* placeholder = [[pimpl->nsSearchField cell] placeholderString]; + return toQString(placeholder); +} + +void QSearchField::setFocus(Qt::FocusReason reason) +{ +/* Do nothing: we were previously using makeFirstResponder on search field, but + * that resulted in having the text being selected (and I didn't find any way to + * deselect it) which would result in the user erasing the first letter he just + * typed, after using setText (e.g. if the user typed a letter while having + * focus on the playlist, which means we call setText and give focus to the + * search bar). + * Instead now the focus will take place when calling selectText in setText. + * This obviously breaks the purpose of this function, but we never call only + * setFocus on a search box in Clementine (i.e. without a call to setText + * shortly after). + */ + +// Q_ASSERT(pimpl); +// if (!pimpl) +// return; + +// if ([pimpl->nsSearchField acceptsFirstResponder]) { +// [[pimpl->nsSearchField window] makeFirstResponder: pimpl->nsSearchField]; +// } +} + +void QSearchField::setFocus() +{ + setFocus(Qt::OtherFocusReason); +} + +void QSearchField::clear() +{ + Q_ASSERT(pimpl); + if (!pimpl) + return; + + [pimpl->nsSearchField setStringValue:@""]; + emit textChanged(QString()); +} + +void QSearchField::selectAll() +{ + Q_ASSERT(pimpl); + if (!pimpl) + return; + + [pimpl->nsSearchField performSelector:@selector(selectText:)]; +} + +QString QSearchField::text() const +{ + Q_ASSERT(pimpl); + if (!pimpl) + return QString(); + + return toQString([pimpl->nsSearchField stringValue]); +} + +void QSearchField::resizeEvent(QResizeEvent *resizeEvent) +{ + QWidget::resizeEvent(resizeEvent); +} + +bool QSearchField::eventFilter(QObject *o, QEvent *e) +{ + return QWidget::eventFilter(o, e); +} diff --git a/3rdparty/qocoa/qsearchfield_nonmac.cpp b/3rdparty/qocoa/qsearchfield_nonmac.cpp new file mode 100644 index 00000000..c5556d22 --- /dev/null +++ b/3rdparty/qocoa/qsearchfield_nonmac.cpp @@ -0,0 +1,184 @@ +/* +Copyright (C) 2011 by Mike McQuaid + +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 "qsearchfield.h" +#include "../../src/core/iconloader.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +class QSearchFieldPrivate : public QObject +{ +public: + QSearchFieldPrivate(QSearchField *searchField, QLineEdit *lineEdit, QToolButton *clearButton) + : QObject(searchField), lineEdit(lineEdit), clearButton(clearButton) {} + int lineEditFrameWidth() const { + return lineEdit->style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + } + int clearButtonPaddedWidth() const { + return clearButton->width() + lineEditFrameWidth() * 2; + } + int clearButtonPaddedHeight() const { + return clearButton->height() + lineEditFrameWidth() * 2; + } + QPointer lineEdit; + QPointer clearButton; +}; + +QSearchField::QSearchField(QWidget *parent) : QWidget(parent) +{ + QLineEdit *lineEdit = new QLineEdit(this); + connect(lineEdit, SIGNAL(textChanged(QString)), + this, SIGNAL(textChanged(QString))); + connect(lineEdit, SIGNAL(editingFinished()), + this, SIGNAL(editingFinished())); + connect(lineEdit, SIGNAL(returnPressed()), + this, SIGNAL(returnPressed())); + connect(lineEdit, SIGNAL(textChanged(QString)), + this, SLOT(setText(QString))); + + QIcon clearIcon(IconLoader::Load("edit-clear-locationbar-ltr")); + + QToolButton *clearButton = new QToolButton(this); + clearButton->setIcon(clearIcon); + clearButton->setIconSize(QSize(16, 16)); + clearButton->setStyleSheet("border: none; padding: 0px;"); + clearButton->resize(clearButton->sizeHint()); + connect(clearButton, SIGNAL(clicked()), this, SLOT(clear())); + + pimpl = new QSearchFieldPrivate(this, lineEdit, clearButton); + + const int frame_width = lineEdit->style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + + lineEdit->setStyleSheet(QString("QLineEdit { padding-left: %1px; } ").arg(clearButton->width())); + const int width = frame_width + qMax(lineEdit->minimumSizeHint().width(), pimpl->clearButtonPaddedWidth()); + const int height = frame_width + qMax(lineEdit->minimumSizeHint().height(), pimpl->clearButtonPaddedHeight()); + lineEdit->setMinimumSize(width, height); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->addWidget(lineEdit); + + lineEdit->installEventFilter(this); +} + +void QSearchField::setText(const QString &text) +{ + Q_ASSERT(pimpl && pimpl->clearButton && pimpl->lineEdit); + if (!(pimpl && pimpl->clearButton && pimpl->lineEdit)) + return; + + if (text != this->text()) + pimpl->lineEdit->setText(text); +} + +void QSearchField::setPlaceholderText(const QString &text) +{ + Q_ASSERT(pimpl && pimpl->lineEdit); + if (!(pimpl && pimpl->lineEdit)) + return; + +#if QT_VERSION >= 0x040700 + pimpl->lineEdit->setPlaceholderText(text); +#endif +} + +QString QSearchField::placeholderText() const { +#if QT_VERSION >= 0x040700 + return pimpl->lineEdit->placeholderText(); +#else + return QString(); +#endif +} + +void QSearchField::setFocus(Qt::FocusReason reason) +{ + Q_ASSERT(pimpl && pimpl->lineEdit); + if (pimpl && pimpl->lineEdit) + pimpl->lineEdit->setFocus(reason); +} + +void QSearchField::setFocus() +{ + setFocus(Qt::OtherFocusReason); +} + +void QSearchField::clear() +{ + Q_ASSERT(pimpl && pimpl->lineEdit); + if (!(pimpl && pimpl->lineEdit)) + return; + + pimpl->lineEdit->clear(); +} + +void QSearchField::selectAll() +{ + Q_ASSERT(pimpl && pimpl->lineEdit); + if (!(pimpl && pimpl->lineEdit)) + return; + + pimpl->lineEdit->selectAll(); +} + +QString QSearchField::text() const +{ + Q_ASSERT(pimpl && pimpl->lineEdit); + if (!(pimpl && pimpl->lineEdit)) + return QString(); + + return pimpl->lineEdit->text(); +} + +void QSearchField::resizeEvent(QResizeEvent *resizeEvent) +{ + Q_ASSERT(pimpl && pimpl->clearButton && pimpl->lineEdit); + if (!(pimpl && pimpl->clearButton && pimpl->lineEdit)) + return; + + QWidget::resizeEvent(resizeEvent); + const int x = pimpl->lineEditFrameWidth(); + const int y = (height() - pimpl->clearButton->height())/2; + pimpl->clearButton->move(x, y); +} + +bool QSearchField::eventFilter(QObject *o, QEvent *e) +{ + if (pimpl && pimpl->lineEdit && o == pimpl->lineEdit) { + // Forward some lineEdit events to QSearchField (only those we need for + // now, but some might be added later if needed) + switch (e->type()) { + case QEvent::FocusIn: + case QEvent::FocusOut: + QApplication::sendEvent(this, e); + break; + } + } + return QWidget::eventFilter(o, e); +} diff --git a/3rdparty/qsqlite/CMakeLists.txt b/3rdparty/qsqlite/CMakeLists.txt new file mode 100644 index 00000000..5288882d --- /dev/null +++ b/3rdparty/qsqlite/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 2.8.11) + +add_definitions(-DQT_STATICPLUGIN) + +# Source files +set(SQLITE-SOURCES + qsql_sqlite.cpp + sqlcachedresult.cpp + smain.cpp +) + +# Header files that have Q_OBJECT in +set(SQLITE-MOC-HEADERS + qsql_sqlite.h + smain.h +) + +set(SQLITE-WIN32-RESOURCES qsqlite_resource.rc) + +qt5_wrap_cpp(SQLITE-SOURCES-MOC ${SQLITE-MOC-HEADERS}) + +include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) + +add_definitions(-DQT_PLUGIN -DQT_NO_DEBUG) + +find_path(SQLITE_INCLUDE_DIRS sqlite3.h) +find_library(SQLITE_LIBRARIES sqlite3) + +if (SQLITE_INCLUDE_DIRS AND SQLITE_LIBRARIES) + set(SQLITE_FOUND true) +endif() + +if (NOT SQLITE_FOUND) + message(SEND_ERROR "Could not find sqlite3") +endif() + +include_directories(${SQLITE_INCLUDE_DIRS}) + +add_library(qsqlite STATIC + ${SQLITE-SOURCES} + ${SQLITE-SOURCES-MOC} + ${SQLITE-WIN32-RESOURCES} +) + +set_property(TARGET qsqlite PROPERTY QT_STATICPLUGIN 1) + +target_link_libraries(qsqlite + Qt5::Core Qt5::Sql + ${SQLITE_LIBRARIES} +) diff --git a/3rdparty/qsqlite/LICENSE.LGPL b/3rdparty/qsqlite/LICENSE.LGPL new file mode 100644 index 00000000..170f02d4 --- /dev/null +++ b/3rdparty/qsqlite/LICENSE.LGPL @@ -0,0 +1,514 @@ + GNU LESSER GENERAL PUBLIC LICENSE + + The Qt GUI Toolkit is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + Contact: Nokia Corporation (qt-info@nokia.com) + + You may use, distribute and copy the Qt GUI Toolkit under the terms of + GNU Lesser General Public License version 2.1, which is displayed below. + +------------------------------------------------------------------------- + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 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 Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + 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 Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +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 other code 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. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + 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, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser 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 combine 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) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) 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. + + d) 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. + + e) 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 materials to be 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 with +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 Lesser 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.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 + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/3rdparty/qsqlite/README b/3rdparty/qsqlite/README new file mode 100644 index 00000000..3f6b5eea --- /dev/null +++ b/3rdparty/qsqlite/README @@ -0,0 +1,6 @@ +This is the qsqlite plugin from the Qt SDK. It's built statically on Windows +and linked with libclementine. This is so librarybackend.cpp can use QLibrary +to load the symbols from sqlite (like sqlite3_create_function) which by +default aren't exported from the .dll on windows. + +See the individual files for licensing information. diff --git a/3rdparty/qsqlite/qsql_sqlite.cpp b/3rdparty/qsqlite/qsql_sqlite.cpp new file mode 100644 index 00000000..33d02284 --- /dev/null +++ b/3rdparty/qsqlite/qsql_sqlite.cpp @@ -0,0 +1,762 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_sqlite.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined Q_OS_WIN +# include +#else +# include +#endif + +#include + +Q_DECLARE_OPAQUE_POINTER(sqlite3*) +Q_DECLARE_METATYPE(sqlite3*) + +Q_DECLARE_OPAQUE_POINTER(sqlite3_stmt*) +Q_DECLARE_METATYPE(sqlite3_stmt*) + +QT_BEGIN_NAMESPACE + +static QString _q_escapeIdentifier(const QString &identifier) +{ + QString res = identifier; + if(!identifier.isEmpty() && identifier.left(1) != QString(QLatin1Char('"')) && identifier.right(1) != QString(QLatin1Char('"')) ) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +static QVariant::Type qGetColumnType(const QString &tpName) +{ + const QString typeName = tpName.toLower(); + + if (typeName == QLatin1String("integer") + || typeName == QLatin1String("int")) + return QVariant::Int; + if (typeName == QLatin1String("double") + || typeName == QLatin1String("float") + || typeName == QLatin1String("real") + || typeName.startsWith(QLatin1String("numeric"))) + return QVariant::Double; + if (typeName == QLatin1String("blob")) + return QVariant::ByteArray; + if (typeName == QLatin1String("boolean") + || typeName == QLatin1String("bool")) + return QVariant::Bool; + return QVariant::String; +} + +static QSqlError qMakeError(sqlite3 *access, const QString &descr, QSqlError::ErrorType type, + int errorCode = -1) +{ + return QSqlError(descr, + QString(reinterpret_cast(sqlite3_errmsg16(access))), + type, errorCode); +} + +class QSQLiteDriverPrivate +{ +public: + inline QSQLiteDriverPrivate() : access(0) {} + sqlite3 *access; + QList results; +}; + + +class QSQLiteResultPrivate +{ +public: + QSQLiteResultPrivate(QSQLiteResult *res); + void cleanup(); + bool fetchNext(ClementineSqlCachedResult::ValueCache &values, int idx, bool initialFetch); + // initializes the recordInfo and the cache + void initColumns(bool emptyResultset); + void finalize(); + + QSQLiteResult* q; + sqlite3 *access; + + sqlite3_stmt *stmt; + + bool skippedStatus; // the status of the fetchNext() that's skipped + bool skipRow; // skip the next fetchNext()? + QSqlRecord rInf; + QVector firstRow; +}; + +QSQLiteResultPrivate::QSQLiteResultPrivate(QSQLiteResult* res) : q(res), access(0), + stmt(0), skippedStatus(false), skipRow(false) +{ +} + +void QSQLiteResultPrivate::cleanup() +{ + finalize(); + rInf.clear(); + skippedStatus = false; + skipRow = false; + q->setAt(QSql::BeforeFirstRow); + q->setActive(false); + q->cleanup(); +} + +void QSQLiteResultPrivate::finalize() +{ + if (!stmt) + return; + + sqlite3_finalize(stmt); + stmt = 0; +} + +void QSQLiteResultPrivate::initColumns(bool emptyResultset) +{ + int nCols = sqlite3_column_count(stmt); + if (nCols <= 0) + return; + + q->init(nCols); + + for (int i = 0; i < nCols; ++i) { + QString colName = QString(reinterpret_cast( + sqlite3_column_name16(stmt, i)) + ).remove(QLatin1Char('"')); + + // must use typeName for resolving the type to match QSqliteDriver::record + QString typeName = QString(reinterpret_cast( + sqlite3_column_decltype16(stmt, i))); + // sqlite3_column_type is documented to have undefined behavior if the result set is empty + int stp = emptyResultset ? -1 : sqlite3_column_type(stmt, i); + + QVariant::Type fieldType; + + if (!typeName.isEmpty()) { + fieldType = qGetColumnType(typeName); + } else { + // Get the proper type for the field based on stp value + switch (stp) { + case SQLITE_INTEGER: + fieldType = QVariant::Int; + break; + case SQLITE_FLOAT: + fieldType = QVariant::Double; + break; + case SQLITE_BLOB: + fieldType = QVariant::ByteArray; + break; + case SQLITE_TEXT: + fieldType = QVariant::String; + break; + case SQLITE_NULL: + default: + fieldType = QVariant::Invalid; + break; + } + } + + int dotIdx = colName.lastIndexOf(QLatin1Char('.')); + QSqlField fld(colName.mid(dotIdx == -1 ? 0 : dotIdx + 1), fieldType); + fld.setSqlType(stp); + rInf.append(fld); + } +} + +bool QSQLiteResultPrivate::fetchNext(ClementineSqlCachedResult::ValueCache &values, int idx, bool initialFetch) +{ + int res; + int i; + + if (skipRow) { + // already fetched + Q_ASSERT(!initialFetch); + skipRow = false; + for(int i=0;isetLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", "Unable to fetch row"), + QCoreApplication::translate("QSQLiteResult", "No query"), QSqlError::ConnectionError)); + q->setAt(QSql::AfterLastRow); + return false; + } + res = sqlite3_step(stmt); + + switch(res) { + case SQLITE_ROW: + // check to see if should fill out columns + if (rInf.isEmpty()) + // must be first call. + initColumns(false); + if (idx < 0 && !initialFetch) + return true; + for (i = 0; i < rInf.count(); ++i) { + switch (sqlite3_column_type(stmt, i)) { + case SQLITE_BLOB: + values[i + idx] = QByteArray(static_cast( + sqlite3_column_blob(stmt, i)), + sqlite3_column_bytes(stmt, i)); + break; + case SQLITE_INTEGER: + values[i + idx] = sqlite3_column_int64(stmt, i); + break; + case SQLITE_FLOAT: + switch(q->numericalPrecisionPolicy()) { + case QSql::LowPrecisionInt32: + values[i + idx] = sqlite3_column_int(stmt, i); + break; + case QSql::LowPrecisionInt64: + values[i + idx] = sqlite3_column_int64(stmt, i); + break; + case QSql::LowPrecisionDouble: + case QSql::HighPrecision: + default: + values[i + idx] = sqlite3_column_double(stmt, i); + break; + }; + break; + case SQLITE_NULL: + values[i + idx] = QVariant(QVariant::String); + break; + default: + values[i + idx] = QString(reinterpret_cast( + sqlite3_column_text16(stmt, i)), + sqlite3_column_bytes16(stmt, i) / sizeof(QChar)); + break; + } + } + return true; + case SQLITE_DONE: + if (rInf.isEmpty()) + // must be first call. + initColumns(true); + q->setAt(QSql::AfterLastRow); + sqlite3_reset(stmt); + return false; + case SQLITE_CONSTRAINT: + case SQLITE_ERROR: + // SQLITE_ERROR is a generic error code and we must call sqlite3_reset() + // to get the specific error message. + res = sqlite3_reset(stmt); + q->setLastError(qMakeError(access, QCoreApplication::translate("QSQLiteResult", + "Unable to fetch row"), QSqlError::ConnectionError, res)); + q->setAt(QSql::AfterLastRow); + return false; + case SQLITE_MISUSE: + case SQLITE_BUSY: + default: + // something wrong, don't get col info, but still return false + q->setLastError(qMakeError(access, QCoreApplication::translate("QSQLiteResult", + "Unable to fetch row"), QSqlError::ConnectionError, res)); + sqlite3_reset(stmt); + q->setAt(QSql::AfterLastRow); + return false; + } + return false; +} + +QSQLiteResult::QSQLiteResult(const QSQLiteDriver* db) + : ClementineSqlCachedResult(db) +{ + d = new QSQLiteResultPrivate(this); + d->access = db->d->access; + db->d->results.append(this); +} + +QSQLiteResult::~QSQLiteResult() +{ + const QSqlDriver *sqlDriver = driver(); + if (sqlDriver) + qobject_cast(sqlDriver)->d->results.removeOne(this); + d->cleanup(); + delete d; +} + +void QSQLiteResult::virtual_hook(int id, void *data) +{ + ClementineSqlCachedResult::virtual_hook(id, data); +} + +bool QSQLiteResult::reset(const QString &query) +{ + if (!prepare(query)) + return false; + return exec(); +} + +bool QSQLiteResult::prepare(const QString &query) +{ + if (!driver() || !driver()->isOpen() || driver()->isOpenError()) + return false; + + d->cleanup(); + + setSelect(false); + + const void *pzTail = NULL; + +#if (SQLITE_VERSION_NUMBER >= 3003011) + int res = sqlite3_prepare16_v2(d->access, query.constData(), (query.size() + 1) * sizeof(QChar), + &d->stmt, &pzTail); +#else + int res = sqlite3_prepare16(d->access, query.constData(), (query.size() + 1) * sizeof(QChar), + &d->stmt, &pzTail); +#endif + + if (res != SQLITE_OK) { + setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult", + "Unable to execute statement"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } else if (pzTail && !QString(reinterpret_cast(pzTail)).trimmed().isEmpty()) { + setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult", + "Unable to execute multiple statements at a time"), QSqlError::StatementError, SQLITE_MISUSE)); + d->finalize(); + return false; + } + return true; +} + +bool QSQLiteResult::exec() +{ + const QVector values = boundValues(); + + d->skippedStatus = false; + d->skipRow = false; + d->rInf.clear(); + clearValues(); + setLastError(QSqlError()); + + int res = sqlite3_reset(d->stmt); + if (res != SQLITE_OK) { + setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult", + "Unable to reset statement"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } + int paramCount = sqlite3_bind_parameter_count(d->stmt); + if (paramCount == values.count()) { + for (int i = 0; i < paramCount; ++i) { + res = SQLITE_OK; + const QVariant value = values.at(i); + + if (value.isNull()) { + res = sqlite3_bind_null(d->stmt, i + 1); + } else { + switch (value.type()) { + case QVariant::ByteArray: { + const QByteArray *ba = static_cast(value.constData()); + res = sqlite3_bind_blob(d->stmt, i + 1, ba->constData(), + ba->size(), SQLITE_STATIC); + break; } + case QVariant::Int: + case QVariant::Bool: + res = sqlite3_bind_int(d->stmt, i + 1, value.toInt()); + break; + case QVariant::Double: + res = sqlite3_bind_double(d->stmt, i + 1, value.toDouble()); + break; + case QVariant::UInt: + case QVariant::LongLong: + res = sqlite3_bind_int64(d->stmt, i + 1, value.toLongLong()); + break; + case QVariant::String: { + // lifetime of string == lifetime of its qvariant + const QString *str = static_cast(value.constData()); + res = sqlite3_bind_text16(d->stmt, i + 1, str->utf16(), + (str->size()) * sizeof(QChar), SQLITE_STATIC); + break; } + default: { + QString str = value.toString(); + // SQLITE_TRANSIENT makes sure that sqlite buffers the data + res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + (str.size()) * sizeof(QChar), SQLITE_TRANSIENT); + break; } + } + } + if (res != SQLITE_OK) { + setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult", + "Unable to bind parameters"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } + } + } else { + setLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", + "Parameter count mismatch") + QString::number(paramCount, 10) + "/" + QString::number(values.count(), 10), QString(), QSqlError::StatementError)); + return false; + } + d->skippedStatus = d->fetchNext(d->firstRow, 0, true); + if (lastError().isValid()) { + setSelect(false); + setActive(false); + return false; + } + setSelect(!d->rInf.isEmpty()); + setActive(true); + return true; +} + +bool QSQLiteResult::gotoNext(ClementineSqlCachedResult::ValueCache& row, int idx) +{ + return d->fetchNext(row, idx, false); +} + +int QSQLiteResult::size() +{ + return -1; +} + +int QSQLiteResult::numRowsAffected() +{ + return sqlite3_changes(d->access); +} + +QVariant QSQLiteResult::lastInsertId() const +{ + if (isActive()) { + qint64 id = sqlite3_last_insert_rowid(d->access); + if (id) + return id; + } + return QVariant(); +} + +QSqlRecord QSQLiteResult::record() const +{ + if (!isActive() || !isSelect()) + return QSqlRecord(); + return d->rInf; +} + +void QSQLiteResult::detachFromResultSet() +{ + if (d->stmt) + sqlite3_reset(d->stmt); +} + +QVariant QSQLiteResult::handle() const +{ + return QVariant::fromValue(d->stmt); +} + +///////////////////////////////////////////////////////// + +QSQLiteDriver::QSQLiteDriver(QObject * parent) + : QSqlDriver(parent) +{ + d = new QSQLiteDriverPrivate(); +} + +QSQLiteDriver::QSQLiteDriver(sqlite3 *connection, QObject *parent) + : QSqlDriver(parent) +{ + d = new QSQLiteDriverPrivate(); + d->access = connection; + setOpen(true); + setOpenError(false); +} + + +QSQLiteDriver::~QSQLiteDriver() +{ + delete d; +} + +bool QSQLiteDriver::hasFeature(DriverFeature f) const +{ + switch (f) { + case BLOB: + case Transactions: + case Unicode: + case LastInsertId: + case PreparedQueries: + case PositionalPlaceholders: + case SimpleLocking: + case FinishQuery: + case LowPrecisionNumbers: + return true; + case QuerySize: + case NamedPlaceholders: + case BatchOperations: + case EventNotifications: + case MultipleResultSets: + return false; + } + return false; +} + +/* + SQLite dbs have no user name, passwords, hosts or ports. + just file names. +*/ +bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, const QString &, int, const QString &conOpts) +{ + if (isOpen()) + close(); + + if (db.isEmpty()) + return false; + bool sharedCache = false; + int openMode = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, timeOut=5000; + QStringList opts=QString(conOpts).remove(QLatin1Char(' ')).split(QLatin1Char(';')); + foreach(const QString &option, opts) { + if (option.startsWith(QLatin1String("QSQLITE_BUSY_TIMEOUT="))) { + bool ok; + int nt = option.mid(21).toInt(&ok); + if (ok) + timeOut = nt; + } + if (option == QLatin1String("QSQLITE_OPEN_READONLY")) + openMode = SQLITE_OPEN_READONLY; + if (option == QLatin1String("QSQLITE_ENABLE_SHARED_CACHE")) + sharedCache = true; + } + + sqlite3_enable_shared_cache(sharedCache); + + if (sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, NULL) == SQLITE_OK) { + sqlite3_busy_timeout(d->access, timeOut); + setOpen(true); + setOpenError(false); + return true; + } else { + if (d->access) { + sqlite3_close(d->access); + d->access = 0; + } + + setLastError(qMakeError(d->access, tr("Error opening database"), + QSqlError::ConnectionError)); + setOpenError(true); + return false; + } +} + +void QSQLiteDriver::close() +{ + if (isOpen()) { + foreach (QSQLiteResult *result, d->results) { + result->d->finalize(); + } + + if (sqlite3_close(d->access) != SQLITE_OK) + setLastError(qMakeError(d->access, tr("Error closing database"), + QSqlError::ConnectionError)); + d->access = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QSQLiteDriver::createResult() const +{ + return new QSQLiteResult(this); +} + +bool QSQLiteDriver::beginTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("BEGIN"))) { + setLastError(QSqlError(tr("Unable to begin transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +bool QSQLiteDriver::commitTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("COMMIT"))) { + setLastError(QSqlError(tr("Unable to commit transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +bool QSQLiteDriver::rollbackTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("ROLLBACK"))) { + setLastError(QSqlError(tr("Unable to rollback transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +QStringList QSQLiteDriver::tables(QSql::TableType type) const +{ + QStringList res; + if (!isOpen()) + return res; + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + + QString sql = QLatin1String("SELECT name FROM sqlite_master WHERE %1 " + "UNION ALL SELECT name FROM sqlite_temp_master WHERE %1"); + if ((type & QSql::Tables) && (type & QSql::Views)) + sql = sql.arg(QLatin1String("type='table' OR type='view'")); + else if (type & QSql::Tables) + sql = sql.arg(QLatin1String("type='table'")); + else if (type & QSql::Views) + sql = sql.arg(QLatin1String("type='view'")); + else + sql.clear(); + + if (!sql.isEmpty() && q.exec(sql)) { + while(q.next()) + res.append(q.value(0).toString()); + } + + if (type & QSql::SystemTables) { + // there are no internal tables beside this one: + res.append(QLatin1String("sqlite_master")); + } + + return res; +} + +static QSqlIndex qGetTableInfo(QSqlQuery &q, const QString &tableName, bool onlyPIndex = false) +{ + QString schema; + QString table(tableName); + int indexOfSeparator = tableName.indexOf(QLatin1Char('.')); + if (indexOfSeparator > -1) { + schema = tableName.left(indexOfSeparator).append(QLatin1Char('.')); + table = tableName.mid(indexOfSeparator + 1); + } + q.exec(QLatin1String("PRAGMA ") + schema + QLatin1String("table_info (") + _q_escapeIdentifier(table) + QLatin1String(")")); + + QSqlIndex ind; + while (q.next()) { + bool isPk = q.value(5).toInt(); + if (onlyPIndex && !isPk) + continue; + QString typeName = q.value(2).toString().toLower(); + QSqlField fld(q.value(1).toString(), qGetColumnType(typeName)); + if (isPk && (typeName == QLatin1String("integer"))) + // INTEGER PRIMARY KEY fields are auto-generated in sqlite + // INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY! + fld.setAutoValue(true); + fld.setRequired(q.value(3).toInt() != 0); + fld.setDefaultValue(q.value(4)); + ind.append(fld); + } + return ind; +} + +QSqlIndex QSQLiteDriver::primaryIndex(const QString &tblname) const +{ + if (!isOpen()) + return QSqlIndex(); + + QString table = tblname; + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + return qGetTableInfo(q, table, true); +} + +QSqlRecord QSQLiteDriver::record(const QString &tbl) const +{ + if (!isOpen()) + return QSqlRecord(); + + QString table = tbl; + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + return qGetTableInfo(q, table); +} + +QVariant QSQLiteDriver::handle() const +{ + return QVariant::fromValue(d->access); +} + +QString QSQLiteDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const +{ + Q_UNUSED(type); + return _q_escapeIdentifier(identifier); +} + +QT_END_NAMESPACE diff --git a/3rdparty/qsqlite/qsql_sqlite.h b/3rdparty/qsqlite/qsql_sqlite.h new file mode 100644 index 00000000..6a4eb157 --- /dev/null +++ b/3rdparty/qsqlite/qsql_sqlite.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_SQLITE_H +#define QSQL_SQLITE_H + +#include +#include +#include "sqlcachedresult.h" + +struct sqlite3; + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_SQLITE +#else +#define Q_EXPORT_SQLDRIVER_SQLITE Q_SQL_EXPORT +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE +class QSQLiteDriverPrivate; +class QSQLiteResultPrivate; +class QSQLiteDriver; + +class QSQLiteResult : public ClementineSqlCachedResult +{ + friend class QSQLiteDriver; + friend class QSQLiteResultPrivate; +public: + explicit QSQLiteResult(const QSQLiteDriver* db); + ~QSQLiteResult(); + QVariant handle() const; + +protected: + bool gotoNext(ClementineSqlCachedResult::ValueCache& row, int idx); + bool reset(const QString &query); + bool prepare(const QString &query); + bool exec(); + int size(); + int numRowsAffected(); + QVariant lastInsertId() const; + QSqlRecord record() const; + void detachFromResultSet(); + void virtual_hook(int id, void *data); + +private: + QSQLiteResultPrivate* d; +}; + +class Q_EXPORT_SQLDRIVER_SQLITE QSQLiteDriver : public QSqlDriver +{ + Q_OBJECT + friend class QSQLiteResult; +public: + explicit QSQLiteDriver(QObject *parent = 0); + explicit QSQLiteDriver(sqlite3 *connection, QObject *parent = 0); + ~QSQLiteDriver(); + bool hasFeature(DriverFeature f) const; + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString & connOpts); + void close(); + QSqlResult *createResult() const; + bool beginTransaction(); + bool commitTransaction(); + bool rollbackTransaction(); + QStringList tables(QSql::TableType) const; + + QSqlRecord record(const QString& tablename) const; + QSqlIndex primaryIndex(const QString &table) const; + QVariant handle() const; + QString escapeIdentifier(const QString &identifier, IdentifierType) const; + +private: + QSQLiteDriverPrivate* d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQL_SQLITE_H diff --git a/3rdparty/qsqlite/qsqlite.json b/3rdparty/qsqlite/qsqlite.json new file mode 100644 index 00000000..0c105ead --- /dev/null +++ b/3rdparty/qsqlite/qsqlite.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "QSQLITE" ] +} diff --git a/3rdparty/qsqlite/smain.cpp b/3rdparty/qsqlite/smain.cpp new file mode 100644 index 00000000..35b6b5d7 --- /dev/null +++ b/3rdparty/qsqlite/smain.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** 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 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "smain.h" + +QT_BEGIN_NAMESPACE + +QSQLiteDriverPlugin::QSQLiteDriverPlugin() + : QSqlDriverPlugin() +{ +} + +QSqlDriver* QSQLiteDriverPlugin::create(const QString &name) +{ + if (name == QLatin1String("QSQLITE")) { + QSQLiteDriver* driver = new QSQLiteDriver(); + return driver; + } + return 0; +} + +QT_END_NAMESPACE diff --git a/3rdparty/qsqlite/smain.h b/3rdparty/qsqlite/smain.h new file mode 100644 index 00000000..1d91a454 --- /dev/null +++ b/3rdparty/qsqlite/smain.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** 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 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include "qsql_sqlite.h" + +QT_BEGIN_NAMESPACE + +class QSQLiteDriverPlugin : public QSqlDriverPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QSqlDriverFactoryInterface" FILE "qsqlite.json") + +public: + QSQLiteDriverPlugin(); + + QSqlDriver* create(const QString &); +}; + +QT_END_NAMESPACE diff --git a/3rdparty/qsqlite/sqlcachedresult.cpp b/3rdparty/qsqlite/sqlcachedresult.cpp new file mode 100644 index 00000000..22ddfd37 --- /dev/null +++ b/3rdparty/qsqlite/sqlcachedresult.cpp @@ -0,0 +1,322 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +#include "sqlcachedresult.h" + +QT_BEGIN_NAMESPACE + +/* + ClementineSqlCachedResult is a convenience class for databases that only allow + forward only fetching. It will cache all the results so we can iterate + backwards over the results again. + + All you need to do is to inherit from ClementineSqlCachedResult and reimplement + gotoNext(). gotoNext() will have a reference to the internal cache and + will give you an index where you can start filling in your data. Special + case: If the user actually wants a forward-only query, idx will be -1 + to indicate that we are not interested in the actual values. +*/ + +static const uint initial_cache_size = 128; + +class ClementineSqlCachedResultPrivate +{ +public: + ClementineSqlCachedResultPrivate(); + bool canSeek(int i) const; + inline int cacheCount() const; + void init(int count, bool fo); + void cleanup(); + int nextIndex(); + void revertLast(); + + ClementineSqlCachedResult::ValueCache cache; + int rowCacheEnd; + int colCount; + bool forwardOnly; + bool atEnd; +}; + +ClementineSqlCachedResultPrivate::ClementineSqlCachedResultPrivate(): + rowCacheEnd(0), colCount(0), forwardOnly(false), atEnd(false) +{ +} + +void ClementineSqlCachedResultPrivate::cleanup() +{ + cache.clear(); + forwardOnly = false; + atEnd = false; + colCount = 0; + rowCacheEnd = 0; +} + +void ClementineSqlCachedResultPrivate::init(int count, bool fo) +{ + Q_ASSERT(count); + cleanup(); + forwardOnly = fo; + colCount = count; + if (fo) { + cache.resize(count); + rowCacheEnd = count; + } else { + cache.resize(initial_cache_size * count); + } +} + +int ClementineSqlCachedResultPrivate::nextIndex() +{ + if (forwardOnly) + return 0; + int newIdx = rowCacheEnd; + if (newIdx + colCount > cache.size()) + cache.resize(qMin(cache.size() * 2, cache.size() + 10000)); + rowCacheEnd += colCount; + + return newIdx; +} + +bool ClementineSqlCachedResultPrivate::canSeek(int i) const +{ + if (forwardOnly || i < 0) + return false; + return rowCacheEnd >= (i + 1) * colCount; +} + +void ClementineSqlCachedResultPrivate::revertLast() +{ + if (forwardOnly) + return; + rowCacheEnd -= colCount; +} + +inline int ClementineSqlCachedResultPrivate::cacheCount() const +{ + Q_ASSERT(!forwardOnly); + Q_ASSERT(colCount); + return rowCacheEnd / colCount; +} + +////////////// + +ClementineSqlCachedResult::ClementineSqlCachedResult(const QSqlDriver * db): QSqlResult (db) +{ + d = new ClementineSqlCachedResultPrivate(); +} + +ClementineSqlCachedResult::~ClementineSqlCachedResult() +{ + delete d; +} + +void ClementineSqlCachedResult::init(int colCount) +{ + d->init(colCount, isForwardOnly()); +} + +bool ClementineSqlCachedResult::fetch(int i) +{ + if ((!isActive()) || (i < 0)) + return false; + if (at() == i) + return true; + if (d->forwardOnly) { + // speed hack - do not copy values if not needed + if (at() > i || at() == QSql::AfterLastRow) + return false; + while(at() < i - 1) { + if (!gotoNext(d->cache, -1)) + return false; + setAt(at() + 1); + } + if (!gotoNext(d->cache, 0)) + return false; + setAt(at() + 1); + return true; + } + if (d->canSeek(i)) { + setAt(i); + return true; + } + if (d->rowCacheEnd > 0) + setAt(d->cacheCount()); + while (at() < i + 1) { + if (!cacheNext()) { + if (d->canSeek(i)) + break; + return false; + } + } + setAt(i); + + return true; +} + +bool ClementineSqlCachedResult::fetchNext() +{ + if (d->canSeek(at() + 1)) { + setAt(at() + 1); + return true; + } + return cacheNext(); +} + +bool ClementineSqlCachedResult::fetchPrevious() +{ + return fetch(at() - 1); +} + +bool ClementineSqlCachedResult::fetchFirst() +{ + if (d->forwardOnly && at() != QSql::BeforeFirstRow) { + return false; + } + if (d->canSeek(0)) { + setAt(0); + return true; + } + return cacheNext(); +} + +bool ClementineSqlCachedResult::fetchLast() +{ + if (d->atEnd) { + if (d->forwardOnly) + return false; + else + return fetch(d->cacheCount() - 1); + } + + int i = at(); + while (fetchNext()) + ++i; /* brute force */ + if (d->forwardOnly && at() == QSql::AfterLastRow) { + setAt(i); + return true; + } else { + return fetch(i); + } +} + +QVariant ClementineSqlCachedResult::data(int i) +{ + int idx = d->forwardOnly ? i : at() * d->colCount + i; + if (i >= d->colCount || i < 0 || at() < 0 || idx >= d->rowCacheEnd) + return QVariant(); + + return d->cache.at(idx); +} + +bool ClementineSqlCachedResult::isNull(int i) +{ + int idx = d->forwardOnly ? i : at() * d->colCount + i; + if (i > d->colCount || i < 0 || at() < 0 || idx >= d->rowCacheEnd) + return true; + + return d->cache.at(idx).isNull(); +} + +void ClementineSqlCachedResult::cleanup() +{ + setAt(QSql::BeforeFirstRow); + setActive(false); + d->cleanup(); +} + +void ClementineSqlCachedResult::clearValues() +{ + setAt(QSql::BeforeFirstRow); + d->rowCacheEnd = 0; + d->atEnd = false; +} + +bool ClementineSqlCachedResult::cacheNext() +{ + if (d->atEnd) + return false; + + if(isForwardOnly()) { + d->cache.clear(); + d->cache.resize(d->colCount); + } + + if (!gotoNext(d->cache, d->nextIndex())) { + d->revertLast(); + d->atEnd = true; + return false; + } + setAt(at() + 1); + return true; +} + +int ClementineSqlCachedResult::colCount() const +{ + return d->colCount; +} + +ClementineSqlCachedResult::ValueCache &ClementineSqlCachedResult::cache() +{ + return d->cache; +} + +void ClementineSqlCachedResult::virtual_hook(int id, void *data) +{ + QSqlResult::virtual_hook(id, data); +} + +void ClementineSqlCachedResult::detachFromResultSet() +{ + cleanup(); +} + +void ClementineSqlCachedResult::setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy policy) +{ + QSqlResult::setNumericalPrecisionPolicy(policy); + cleanup(); +} + + +QT_END_NAMESPACE diff --git a/3rdparty/qsqlite/sqlcachedresult.h b/3rdparty/qsqlite/sqlcachedresult.h new file mode 100644 index 00000000..c170270a --- /dev/null +++ b/3rdparty/qsqlite/sqlcachedresult.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLCACHEDRESULT_P_H +#define QSQLCACHEDRESULT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +QT_BEGIN_NAMESPACE + +class QVariant; +template class QVector; + +class ClementineSqlCachedResultPrivate; + +class ClementineSqlCachedResult: public QSqlResult +{ +public: + virtual ~ClementineSqlCachedResult(); + + typedef QVector ValueCache; + +protected: + ClementineSqlCachedResult(const QSqlDriver * db); + + void init(int colCount); + void cleanup(); + void clearValues(); + + virtual bool gotoNext(ValueCache &values, int index) = 0; + + QVariant data(int i); + bool isNull(int i); + bool fetch(int i); + bool fetchNext(); + bool fetchPrevious(); + bool fetchFirst(); + bool fetchLast(); + + int colCount() const; + ValueCache &cache(); + + void virtual_hook(int id, void *data); + void detachFromResultSet(); + void setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy policy); +private: + bool cacheNext(); + ClementineSqlCachedResultPrivate *d; +}; + +QT_END_NAMESPACE + +#endif // QSQLCACHEDRESULT_P_H diff --git a/3rdparty/qtsingleapplication/CMakeLists.txt b/3rdparty/qtsingleapplication/CMakeLists.txt new file mode 100644 index 00000000..75d098d6 --- /dev/null +++ b/3rdparty/qtsingleapplication/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 2.8.11) + +set(SINGLEAPP-SOURCES + qtlocalpeer.cpp + qtsingleapplication.cpp + qtsinglecoreapplication.cpp +) + +set(SINGLEAPP-MOC-HEADERS + qtlocalpeer.h + qtsingleapplication.h + qtsinglecoreapplication.h +) + +if(WIN32) + set(SINGLEAPP-SOURCES ${SINGLEAPP-SOURCES} qtlockedfile_win.cpp) +elseif(WIN32) + set(SINGLEAPP-SOURCES ${SINGLEAPP-SOURCES} qtlockedfile_unix.cpp) +endif(WIN32) + +QT5_WRAP_CPP(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS}) + +ADD_LIBRARY(qtsingleapplication STATIC + ${SINGLEAPP-SOURCES} + ${SINGLEAPP-SOURCES-MOC} +) + +QT5_USE_MODULES(qtsingleapplication Core Widgets Network) diff --git a/3rdparty/qtsingleapplication/LICENSE.LGPL b/3rdparty/qtsingleapplication/LICENSE.LGPL new file mode 100644 index 00000000..5ab7695a --- /dev/null +++ b/3rdparty/qtsingleapplication/LICENSE.LGPL @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 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 Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + 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 Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +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 other code 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. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + 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, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser 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 combine 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) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) 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. + + d) 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. + + e) 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 materials to be 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 with +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 Lesser 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.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 + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/3rdparty/qtsingleapplication/qtlocalpeer.cpp b/3rdparty/qtsingleapplication/qtlocalpeer.cpp new file mode 100644 index 00000000..837752b6 --- /dev/null +++ b/3rdparty/qtsingleapplication/qtlocalpeer.cpp @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** Copyright (C) 2010 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. +** +** You may use this file under the terms of the BSD license as follows: +** +** "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 name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its 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 +** OWNER 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." +** +****************************************************************************/ + + +#include "qtlocalpeer.h" +#include +#include +#include +#include + +#if defined(Q_OS_WIN) +#include +#include +typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); +static PProcessIdToSessionId pProcessIdToSessionId = 0; +#endif +#if defined(Q_OS_UNIX) +#include +#endif + +namespace QtLP_Private { +#include "qtlockedfile.cpp" +#if defined(Q_OS_WIN) +#include "qtlockedfile_win.cpp" +#else +#include "qtlockedfile_unix.cpp" +#endif +} + +const char* QtLocalPeer::ack = "ack"; + +QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId) + : QObject(parent), id(appId) +{ + QString prefix = id; + if (id.isEmpty()) { + id = QCoreApplication::applicationFilePath(); +#if defined(Q_OS_WIN) + id = id.toLower(); +#endif + prefix = id.section(QLatin1Char('/'), -1); + } + prefix.remove(QRegExp("[^a-zA-Z]")); + prefix.truncate(6); + + QByteArray idc = id.toUtf8(); + quint16 idNum = qChecksum(idc.constData(), idc.size()); + socketName = QLatin1String("qtsingleapp-") + prefix + + QLatin1Char('-') + QString::number(idNum, 16); + +#if defined(Q_OS_WIN) + if (!pProcessIdToSessionId) { + QLibrary lib("kernel32"); + pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId"); + } + if (pProcessIdToSessionId) { + DWORD sessionId = 0; + pProcessIdToSessionId(GetCurrentProcessId(), &sessionId); + socketName += QLatin1Char('-') + QString::number(sessionId, 16); + } +#else + socketName += QLatin1Char('-') + QString::number(::getuid(), 16); +#endif + + server = new QLocalServer(this); + QString lockName = QDir(QDir::tempPath()).absolutePath() + + QLatin1Char('/') + socketName + + QLatin1String("-lockfile"); + lockFile.setFileName(lockName); + lockFile.open(QIODevice::ReadWrite); +} + + + +bool QtLocalPeer::isClient() +{ + if (lockFile.isLocked()) + return false; + + if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false)) + return true; + + bool res = server->listen(socketName); +#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0)) + // ### Workaround + if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { + QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName); + res = server->listen(socketName); + } +#endif + if (!res) + qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString())); + QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection())); + return false; +} + + +bool QtLocalPeer::sendMessage(const QString &message, int timeout) +{ + if (!isClient()) + return false; + + QLocalSocket socket; + bool connOk = false; + for(int i = 0; i < 2; i++) { + // Try twice, in case the other instance is just starting up + socket.connectToServer(socketName); + connOk = socket.waitForConnected(timeout/2); + if (connOk || i) + break; + int ms = 250; +#if defined(Q_OS_WIN) + Sleep(DWORD(ms)); +#else + struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; + nanosleep(&ts, NULL); +#endif + } + if (!connOk) + return false; + + QByteArray uMsg(message.toUtf8()); + QDataStream ds(&socket); + ds.writeBytes(uMsg.constData(), uMsg.size()); + bool res = socket.waitForBytesWritten(timeout); + if (res) { + res &= socket.waitForReadyRead(timeout); // wait for ack + if (res) + res &= (socket.read(qstrlen(ack)) == ack); + } + return res; +} + + +void QtLocalPeer::receiveConnection() +{ + QLocalSocket* socket = server->nextPendingConnection(); + if (!socket) + return; + + while (socket->bytesAvailable() < (int)sizeof(quint32)) + socket->waitForReadyRead(); + QDataStream ds(socket); + QByteArray uMsg; + quint32 remaining; + ds >> remaining; + uMsg.resize(remaining); + int got = 0; + char* uMsgBuf = uMsg.data(); + do { + got = ds.readRawData(uMsgBuf, remaining); + remaining -= got; + uMsgBuf += got; + } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); + if (got < 0) { + qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData()); + delete socket; + return; + } + QString message(QString::fromUtf8(uMsg)); + socket->write(ack, qstrlen(ack)); + socket->waitForBytesWritten(1000); + delete socket; + emit messageReceived(message); //### (might take a long time to return) +} diff --git a/3rdparty/qtsingleapplication/qtlocalpeer.h b/3rdparty/qtsingleapplication/qtlocalpeer.h new file mode 100644 index 00000000..7b3fa815 --- /dev/null +++ b/3rdparty/qtsingleapplication/qtlocalpeer.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2010 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. +** +** You may use this file under the terms of the BSD license as follows: +** +** "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 name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its 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 +** OWNER 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." +** +****************************************************************************/ + +#ifndef QTLOCALPEER_H +#define QTLOCALPEER_H + +#include +#include +#include + +#include "qtlockedfile.h" + +class QtLocalPeer : public QObject +{ + Q_OBJECT + +public: + QtLocalPeer(QObject *parent = 0, const QString &appId = QString()); + bool isClient(); + bool sendMessage(const QString &message, int timeout); + QString applicationId() const + { return id; } + +Q_SIGNALS: + void messageReceived(const QString &message); + +protected Q_SLOTS: + void receiveConnection(); + +protected: + QString id; + QString socketName; + QLocalServer* server; + QtLP_Private::QtLockedFile lockFile; + +private: + static const char* ack; +}; + +#endif // QTLOCALPEER_H diff --git a/3rdparty/qtsingleapplication/qtlockedfile.cpp b/3rdparty/qtsingleapplication/qtlockedfile.cpp new file mode 100644 index 00000000..3e73ba65 --- /dev/null +++ b/3rdparty/qtsingleapplication/qtlockedfile.cpp @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** Copyright (C) 2010 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. +** +** You may use this file under the terms of the BSD license as follows: +** +** "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 name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its 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 +** OWNER 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." +** +****************************************************************************/ + +#include "qtlockedfile.h" + +/*! + \class QtLockedFile + + \brief The QtLockedFile class extends QFile with advisory locking + functions. + + A file may be locked in read or write mode. Multiple instances of + \e QtLockedFile, created in multiple processes running on the same + machine, may have a file locked in read mode. Exactly one instance + may have it locked in write mode. A read and a write lock cannot + exist simultaneously on the same file. + + The file locks are advisory. This means that nothing prevents + another process from manipulating a locked file using QFile or + file system functions offered by the OS. Serialization is only + guaranteed if all processes that access the file use + QLockedFile. Also, while holding a lock on a file, a process + must not open the same file again (through any API), or locks + can be unexpectedly lost. + + The lock provided by an instance of \e QtLockedFile is released + whenever the program terminates. This is true even when the + program crashes and no destructors are called. +*/ + +/*! \enum QtLockedFile::LockMode + + This enum describes the available lock modes. + + \value ReadLock A read lock. + \value WriteLock A write lock. + \value NoLock Neither a read lock nor a write lock. +*/ + +/*! + Constructs an unlocked \e QtLockedFile object. This constructor + behaves in the same way as \e QFile::QFile(). + + \sa QFile::QFile() +*/ +QtLockedFile::QtLockedFile() + : QFile() +{ +#ifdef Q_OS_WIN + wmutex = 0; + rmutex = 0; +#endif + m_lock_mode = NoLock; +} + +/*! + Constructs an unlocked QtLockedFile object with file \a name. This + constructor behaves in the same way as \e QFile::QFile(const + QString&). + + \sa QFile::QFile() +*/ +QtLockedFile::QtLockedFile(const QString &name) + : QFile(name) +{ +#ifdef Q_OS_WIN + wmutex = 0; + rmutex = 0; +#endif + m_lock_mode = NoLock; +} + +/*! + Opens the file in OpenMode \a mode. + + This is identical to QFile::open(), with the one exception that the + Truncate mode flag is disallowed. Truncation would conflict with the + advisory file locking, since the file would be modified before the + write lock is obtained. If truncation is required, use resize(0) + after obtaining the write lock. + + Returns true if successful; otherwise false. + + \sa QFile::open(), QFile::resize() +*/ +bool QtLockedFile::open(OpenMode mode) +{ + if (mode & QIODevice::Truncate) { + qWarning("QtLockedFile::open(): Truncate mode not allowed."); + return false; + } + return QFile::open(mode); +} + +/*! + Returns \e true if this object has a in read or write lock; + otherwise returns \e false. + + \sa lockMode() +*/ +bool QtLockedFile::isLocked() const +{ + return m_lock_mode != NoLock; +} + +/*! + Returns the type of lock currently held by this object, or \e + QtLockedFile::NoLock. + + \sa isLocked() +*/ +QtLockedFile::LockMode QtLockedFile::lockMode() const +{ + return m_lock_mode; +} + +/*! + \fn bool QtLockedFile::lock(LockMode mode, bool block = true) + + Obtains a lock of type \a mode. The file must be opened before it + can be locked. + + If \a block is true, this function will block until the lock is + aquired. If \a block is false, this function returns \e false + immediately if the lock cannot be aquired. + + If this object already has a lock of type \a mode, this function + returns \e true immediately. If this object has a lock of a + different type than \a mode, the lock is first released and then a + new lock is obtained. + + This function returns \e true if, after it executes, the file is + locked by this object, and \e false otherwise. + + \sa unlock(), isLocked(), lockMode() +*/ + +/*! + \fn bool QtLockedFile::unlock() + + Releases a lock. + + If the object has no lock, this function returns immediately. + + This function returns \e true if, after it executes, the file is + not locked by this object, and \e false otherwise. + + \sa lock(), isLocked(), lockMode() +*/ + +/*! + \fn QtLockedFile::~QtLockedFile() + + Destroys the \e QtLockedFile object. If any locks were held, they + are released. +*/ diff --git a/3rdparty/qtsingleapplication/qtlockedfile.h b/3rdparty/qtsingleapplication/qtlockedfile.h new file mode 100644 index 00000000..2af3e3ea --- /dev/null +++ b/3rdparty/qtsingleapplication/qtlockedfile.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2010 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. +** +** You may use this file under the terms of the BSD license as follows: +** +** "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 name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its 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 +** OWNER 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." +** +****************************************************************************/ + +#ifndef QTLOCKEDFILE_H +#define QTLOCKEDFILE_H + +#include +#ifdef Q_OS_WIN +#include +#endif + +#if defined(Q_WS_WIN) || defined(Q_OS_WIN) +# if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT) +# define QT_QTLOCKEDFILE_EXPORT +# elif defined(QT_QTLOCKEDFILE_IMPORT) +# if defined(QT_QTLOCKEDFILE_EXPORT) +# undef QT_QTLOCKEDFILE_EXPORT +# endif +# define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport) +# elif defined(QT_QTLOCKEDFILE_EXPORT) +# undef QT_QTLOCKEDFILE_EXPORT +# define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport) +# endif +#else +# define QT_QTLOCKEDFILE_EXPORT +#endif + +namespace QtLP_Private { + +class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile +{ +public: + enum LockMode { NoLock = 0, ReadLock, WriteLock }; + + QtLockedFile(); + QtLockedFile(const QString &name); + ~QtLockedFile(); + + bool open(OpenMode mode); + + bool lock(LockMode mode, bool block = true); + bool unlock(); + bool isLocked() const; + LockMode lockMode() const; + +private: +#ifdef Q_OS_WIN + Qt::HANDLE wmutex; + Qt::HANDLE rmutex; + QVector rmutexes; + QString mutexname; + + Qt::HANDLE getMutexHandle(int idx, bool doCreate); + bool waitMutex(Qt::HANDLE mutex, bool doBlock); + +#endif + LockMode m_lock_mode; +}; +} +#endif diff --git a/3rdparty/qtsingleapplication/qtlockedfile_unix.cpp b/3rdparty/qtsingleapplication/qtlockedfile_unix.cpp new file mode 100644 index 00000000..715c7d9b --- /dev/null +++ b/3rdparty/qtsingleapplication/qtlockedfile_unix.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2010 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. +** +** You may use this file under the terms of the BSD license as follows: +** +** "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 name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its 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 +** OWNER 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." +** +****************************************************************************/ + +#include +#include +#include +#include + +#include "qtlockedfile.h" + +bool QtLockedFile::lock(LockMode mode, bool block) +{ + if (!isOpen()) { + qWarning("QtLockedFile::lock(): file is not opened"); + return false; + } + + if (mode == NoLock) + return unlock(); + + if (mode == m_lock_mode) + return true; + + if (m_lock_mode != NoLock) + unlock(); + + struct flock fl; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK; + int cmd = block ? F_SETLKW : F_SETLK; + int ret = fcntl(handle(), cmd, &fl); + + if (ret == -1) { + if (errno != EINTR && errno != EAGAIN) + qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); + return false; + } + + + m_lock_mode = mode; + return true; +} + + +bool QtLockedFile::unlock() +{ + if (!isOpen()) { + qWarning("QtLockedFile::unlock(): file is not opened"); + return false; + } + + if (!isLocked()) + return true; + + struct flock fl; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_UNLCK; + int ret = fcntl(handle(), F_SETLKW, &fl); + + if (ret == -1) { + qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); + return false; + } + + m_lock_mode = NoLock; + return true; +} + +QtLockedFile::~QtLockedFile() +{ + if (isOpen()) + unlock(); +} + diff --git a/3rdparty/qtsingleapplication/qtlockedfile_win.cpp b/3rdparty/qtsingleapplication/qtlockedfile_win.cpp new file mode 100644 index 00000000..ed2995f3 --- /dev/null +++ b/3rdparty/qtsingleapplication/qtlockedfile_win.cpp @@ -0,0 +1,214 @@ +/**************************************************************************** +** +** Copyright (C) 2010 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. +** +** You may use this file under the terms of the BSD license as follows: +** +** "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 name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its 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 +** OWNER 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." +** +****************************************************************************/ + +#include "qtlockedfile.h" +#include +#include + +#define MUTEX_PREFIX "QtLockedFile mutex " +// Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS +#define MAX_READERS MAXIMUM_WAIT_OBJECTS + +Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate) +{ + if (mutexname.isEmpty()) { + QFileInfo fi(*this); + mutexname = QString::fromLatin1(MUTEX_PREFIX) + + fi.absoluteFilePath().toLower(); + } + QString mname(mutexname); + if (idx >= 0) + mname += QString::number(idx); + + Qt::HANDLE mutex; + if (doCreate) { +#if (QT_VERSION < 0x050000) + QT_WA( { mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); }, + { mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } ); +#else + mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); +#endif + if (!mutex) { + qErrnoWarning("QtLockedFile::lock(): CreateMutex failed"); + return 0; + } + } + else { +#if (QT_VERSION < 0x050000) + QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); }, + { mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } ); +#else + mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); +#endif + if (!mutex) { + if (GetLastError() != ERROR_FILE_NOT_FOUND) + qErrnoWarning("QtLockedFile::lock(): OpenMutex failed"); + return 0; + } + } + return mutex; +} + +bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock) +{ + Q_ASSERT(mutex); + DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0); + switch (res) { + case WAIT_OBJECT_0: + case WAIT_ABANDONED: + return true; + break; + case WAIT_TIMEOUT: + break; + default: + qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed"); + } + return false; +} + + + +bool QtLockedFile::lock(LockMode mode, bool block) +{ + if (!isOpen()) { + qWarning("QtLockedFile::lock(): file is not opened"); + return false; + } + + if (mode == NoLock) + return unlock(); + + if (mode == m_lock_mode) + return true; + + if (m_lock_mode != NoLock) + unlock(); + + if (!wmutex && !(wmutex = getMutexHandle(-1, true))) + return false; + + if (!waitMutex(wmutex, block)) + return false; + + if (mode == ReadLock) { + int idx = 0; + for (; idx < MAX_READERS; idx++) { + rmutex = getMutexHandle(idx, false); + if (!rmutex || waitMutex(rmutex, false)) + break; + CloseHandle(rmutex); + } + bool ok = true; + if (idx >= MAX_READERS) { + qWarning("QtLockedFile::lock(): too many readers"); + rmutex = 0; + ok = false; + } + else if (!rmutex) { + rmutex = getMutexHandle(idx, true); + if (!rmutex || !waitMutex(rmutex, false)) + ok = false; + } + if (!ok && rmutex) { + CloseHandle(rmutex); + rmutex = 0; + } + ReleaseMutex(wmutex); + if (!ok) + return false; + } + else { + Q_ASSERT(rmutexes.isEmpty()); + for (int i = 0; i < MAX_READERS; i++) { + Qt::HANDLE mutex = getMutexHandle(i, false); + if (mutex) + rmutexes.append(mutex); + } + if (rmutexes.size()) { + DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(), + TRUE, block ? INFINITE : 0); + if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) { + if (res != WAIT_TIMEOUT) + qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed"); + m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky + unlock(); + return false; + } + } + } + + m_lock_mode = mode; + return true; +} + +bool QtLockedFile::unlock() +{ + if (!isOpen()) { + qWarning("QtLockedFile::unlock(): file is not opened"); + return false; + } + + if (!isLocked()) + return true; + + if (m_lock_mode == ReadLock) { + ReleaseMutex(rmutex); + CloseHandle(rmutex); + rmutex = 0; + } + else { + foreach(Qt::HANDLE mutex, rmutexes) { + ReleaseMutex(mutex); + CloseHandle(mutex); + } + rmutexes.clear(); + ReleaseMutex(wmutex); + } + + m_lock_mode = QtLockedFile::NoLock; + return true; +} + +QtLockedFile::~QtLockedFile() +{ + if (isOpen()) + unlock(); + if (wmutex) + CloseHandle(wmutex); +} diff --git a/3rdparty/qtsingleapplication/qtsingleapplication.cpp b/3rdparty/qtsingleapplication/qtsingleapplication.cpp new file mode 100644 index 00000000..48c2e45f --- /dev/null +++ b/3rdparty/qtsingleapplication/qtsingleapplication.cpp @@ -0,0 +1,331 @@ +/**************************************************************************** +** +** Copyright (C) 2010 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. +** +** You may use this file under the terms of the BSD license as follows: +** +** "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 name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its 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 +** OWNER 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." +** +****************************************************************************/ + + +#include "qtsingleapplication.h" +#include "qtlocalpeer.h" +#include + +/*! + \class QtSingleApplication qtsingleapplication.h + \brief The QtSingleApplication class provides an API to detect and + communicate with running instances of an application. + + This class allows you to create applications where only one + instance should be running at a time. I.e., if the user tries to + launch another instance, the already running instance will be + activated instead. Another usecase is a client-server system, + where the first started instance will assume the role of server, + and the later instances will act as clients of that server. + + By default, the full path of the executable file is used to + determine whether two processes are instances of the same + application. You can also provide an explicit identifier string + that will be compared instead. + + The application should create the QtSingleApplication object early + in the startup phase, and call isRunning() to find out if another + instance of this application is already running. If isRunning() + returns false, it means that no other instance is running, and + this instance has assumed the role as the running instance. In + this case, the application should continue with the initialization + of the application user interface before entering the event loop + with exec(), as normal. + + The messageReceived() signal will be emitted when the running + application receives messages from another instance of the same + application. When a message is received it might be helpful to the + user to raise the application so that it becomes visible. To + facilitate this, QtSingleApplication provides the + setActivationWindow() function and the activateWindow() slot. + + If isRunning() returns true, another instance is already + running. It may be alerted to the fact that another instance has + started by using the sendMessage() function. Also data such as + startup parameters (e.g. the name of the file the user wanted this + new instance to open) can be passed to the running instance with + this function. Then, the application should terminate (or enter + client mode). + + If isRunning() returns true, but sendMessage() fails, that is an + indication that the running instance is frozen. + + Here's an example that shows how to convert an existing + application to use QtSingleApplication. It is very simple and does + not make use of all QtSingleApplication's functionality (see the + examples for that). + + \code + // Original + int main(int argc, char **argv) + { + QApplication app(argc, argv); + + MyMainWidget mmw; + mmw.show(); + return app.exec(); + } + + // Single instance + int main(int argc, char **argv) + { + QtSingleApplication app(argc, argv); + + if (app.isRunning()) + return !app.sendMessage(someDataString); + + MyMainWidget mmw; + app.setActivationWindow(&mmw); + mmw.show(); + return app.exec(); + } + \endcode + + Once this QtSingleApplication instance is destroyed (normally when + the process exits or crashes), when the user next attempts to run the + application this instance will not, of course, be encountered. The + next instance to call isRunning() or sendMessage() will assume the + role as the new running instance. + + For console (non-GUI) applications, QtSingleCoreApplication may be + used instead of this class, to avoid the dependency on the QtGui + library. + + \sa QtSingleCoreApplication +*/ + + +void QtSingleApplication::sysInit(const QString &appId) +{ + actWin = 0; + peer = new QtLocalPeer(this, appId); + connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); +} + + +/*! + Creates a QtSingleApplication object. The application identifier + will be QCoreApplication::applicationFilePath(). \a argc, \a + argv, and \a GUIenabled are passed on to the QAppliation constructor. + + If you are creating a console application (i.e. setting \a + GUIenabled to false), you may consider using + QtSingleCoreApplication instead. +*/ + +QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled) + : QApplication(argc, argv, GUIenabled) +{ + sysInit(); +} + + +/*! + Creates a QtSingleApplication object with the application + identifier \a appId. \a argc and \a argv are passed on to the + QAppliation constructor. +*/ + +QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv) + : QApplication(argc, argv) +{ + sysInit(appId); +} + + +#if defined(Q_WS_X11) +/*! + Special constructor for X11, ref. the documentation of + QApplication's corresponding constructor. The application identifier + will be QCoreApplication::applicationFilePath(). \a dpy, \a visual, + and \a cmap are passed on to the QApplication constructor. +*/ +QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap) + : QApplication(dpy, visual, cmap) +{ + sysInit(); +} + +/*! + Special constructor for X11, ref. the documentation of + QApplication's corresponding constructor. The application identifier + will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a + argv, \a visual, and \a cmap are passed on to the QApplication + constructor. +*/ +QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) + : QApplication(dpy, argc, argv, visual, cmap) +{ + sysInit(); +} + +/*! + Special constructor for X11, ref. the documentation of + QApplication's corresponding constructor. The application identifier + will be \a appId. \a dpy, \a argc, \a + argv, \a visual, and \a cmap are passed on to the QApplication + constructor. +*/ +QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) + : QApplication(dpy, argc, argv, visual, cmap) +{ + sysInit(appId); +} +#endif + + +/*! + Returns true if another instance of this application is running; + otherwise false. + + This function does not find instances of this application that are + being run by a different user (on Windows: that are running in + another session). + + \sa sendMessage() +*/ + +bool QtSingleApplication::isRunning() +{ + return peer->isClient(); +} + + +/*! + Tries to send the text \a message to the currently running + instance. The QtSingleApplication object in the running instance + will emit the messageReceived() signal when it receives the + message. + + This function returns true if the message has been sent to, and + processed by, the current instance. If there is no instance + currently running, or if the running instance fails to process the + message within \a timeout milliseconds, this function return false. + + \sa isRunning(), messageReceived() +*/ +bool QtSingleApplication::sendMessage(const QString &message, int timeout) +{ + return peer->sendMessage(message, timeout); +} + + +/*! + Returns the application identifier. Two processes with the same + identifier will be regarded as instances of the same application. +*/ +QString QtSingleApplication::id() const +{ + return peer->applicationId(); +} + + +/*! + Sets the activation window of this application to \a aw. The + activation window is the widget that will be activated by + activateWindow(). This is typically the application's main window. + + If \a activateOnMessage is true (the default), the window will be + activated automatically every time a message is received, just prior + to the messageReceived() signal being emitted. + + \sa activateWindow(), messageReceived() +*/ + +void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage) +{ + actWin = aw; + if (activateOnMessage) + connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); + else + disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); +} + + +/*! + Returns the applications activation window if one has been set by + calling setActivationWindow(), otherwise returns 0. + + \sa setActivationWindow() +*/ +QWidget* QtSingleApplication::activationWindow() const +{ + return actWin; +} + + +/*! + De-minimizes, raises, and activates this application's activation window. + This function does nothing if no activation window has been set. + + This is a convenience function to show the user that this + application instance has been activated when he has tried to start + another instance. + + This function should typically be called in response to the + messageReceived() signal. By default, that will happen + automatically, if an activation window has been set. + + \sa setActivationWindow(), messageReceived(), initialize() +*/ +void QtSingleApplication::activateWindow() +{ + if (actWin) { + actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized); + actWin->raise(); + actWin->activateWindow(); + } +} + + +/*! + \fn void QtSingleApplication::messageReceived(const QString& message) + + This signal is emitted when the current instance receives a \a + message from another instance of this application. + + \sa sendMessage(), setActivationWindow(), activateWindow() +*/ + + +/*! + \fn void QtSingleApplication::initialize(bool dummy = true) + + \obsolete +*/ diff --git a/3rdparty/qtsingleapplication/qtsingleapplication.h b/3rdparty/qtsingleapplication/qtsingleapplication.h new file mode 100644 index 00000000..42e97728 --- /dev/null +++ b/3rdparty/qtsingleapplication/qtsingleapplication.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2010 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. +** +** You may use this file under the terms of the BSD license as follows: +** +** "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 name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its 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 +** OWNER 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." +** +****************************************************************************/ + +#ifndef QTSINGLEAPPLICATION_H +#define QTSINGLEAPPLICATION_H + +#include + +class QtLocalPeer; + +#if defined(Q_WS_WIN) || defined(Q_OS_WIN32) +# if !defined(QT_QTSINGLEAPPLICATION_EXPORT) && !defined(QT_QTSINGLEAPPLICATION_IMPORT) +# define QT_QTSINGLEAPPLICATION_EXPORT +# elif defined(QT_QTSINGLEAPPLICATION_IMPORT) +# if defined(QT_QTSINGLEAPPLICATION_EXPORT) +# undef QT_QTSINGLEAPPLICATION_EXPORT +# endif +# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllimport) +# elif defined(QT_QTSINGLEAPPLICATION_EXPORT) +# undef QT_QTSINGLEAPPLICATION_EXPORT +# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllexport) +# endif +#else +# define QT_QTSINGLEAPPLICATION_EXPORT +#endif + +class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication +{ + Q_OBJECT + +public: + QtSingleApplication(int &argc, char **argv, bool GUIenabled = true); + QtSingleApplication(const QString &id, int &argc, char **argv); +#if defined(Q_WS_X11) + QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); + QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0); + QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); +#endif + + bool isRunning(); + QString id() const; + + void setActivationWindow(QWidget* aw, bool activateOnMessage = true); + QWidget* activationWindow() const; + + // Obsolete: + void initialize(bool dummy = true) + { isRunning(); Q_UNUSED(dummy) } + +public Q_SLOTS: + bool sendMessage(const QString &message, int timeout = 5000); + void activateWindow(); + + +Q_SIGNALS: + void messageReceived(const QString &message); + + +private: + void sysInit(const QString &appId = QString()); + QtLocalPeer *peer; + QWidget *actWin; +}; + +#endif // QTSINGLEAPPLICATION_H diff --git a/3rdparty/qtsingleapplication/qtsinglecoreapplication.cpp b/3rdparty/qtsingleapplication/qtsinglecoreapplication.cpp new file mode 100644 index 00000000..cf607710 --- /dev/null +++ b/3rdparty/qtsingleapplication/qtsinglecoreapplication.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2010 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. +** +** You may use this file under the terms of the BSD license as follows: +** +** "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 name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its 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 +** OWNER 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." +** +****************************************************************************/ + + +#include "qtsinglecoreapplication.h" +#include "qtlocalpeer.h" + +/*! + \class QtSingleCoreApplication qtsinglecoreapplication.h + \brief A variant of the QtSingleApplication class for non-GUI applications. + + This class is a variant of QtSingleApplication suited for use in + console (non-GUI) applications. It is an extension of + QCoreApplication (instead of QApplication). It does not require + the QtGui library. + + The API and usage is identical to QtSingleApplication, except that + functions relating to the "activation window" are not present, for + obvious reasons. Please refer to the QtSingleApplication + documentation for explanation of the usage. + + A QtSingleCoreApplication instance can communicate to a + QtSingleApplication instance if they share the same application + id. Hence, this class can be used to create a light-weight + command-line tool that sends commands to a GUI application. + + \sa QtSingleApplication +*/ + +/*! + Creates a QtSingleCoreApplication object. The application identifier + will be QCoreApplication::applicationFilePath(). \a argc and \a + argv are passed on to the QCoreAppliation constructor. +*/ + +QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv) + : QCoreApplication(argc, argv) +{ + peer = new QtLocalPeer(this); + connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); +} + + +/*! + Creates a QtSingleCoreApplication object with the application + identifier \a appId. \a argc and \a argv are passed on to the + QCoreAppliation constructor. +*/ +QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv) + : QCoreApplication(argc, argv) +{ + peer = new QtLocalPeer(this, appId); + connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); +} + + +/*! + Returns true if another instance of this application is running; + otherwise false. + + This function does not find instances of this application that are + being run by a different user (on Windows: that are running in + another session). + + \sa sendMessage() +*/ + +bool QtSingleCoreApplication::isRunning() +{ + return peer->isClient(); +} + + +/*! + Tries to send the text \a message to the currently running + instance. The QtSingleCoreApplication object in the running instance + will emit the messageReceived() signal when it receives the + message. + + This function returns true if the message has been sent to, and + processed by, the current instance. If there is no instance + currently running, or if the running instance fails to process the + message within \a timeout milliseconds, this function return false. + + \sa isRunning(), messageReceived() +*/ + +bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout) +{ + return peer->sendMessage(message, timeout); +} + + +/*! + Returns the application identifier. Two processes with the same + identifier will be regarded as instances of the same application. +*/ + +QString QtSingleCoreApplication::id() const +{ + return peer->applicationId(); +} + + +/*! + \fn void QtSingleCoreApplication::messageReceived(const QString& message) + + This signal is emitted when the current instance receives a \a + message from another instance of this application. + + \sa sendMessage() +*/ diff --git a/3rdparty/qtsingleapplication/qtsinglecoreapplication.h b/3rdparty/qtsingleapplication/qtsinglecoreapplication.h new file mode 100644 index 00000000..549d49f5 --- /dev/null +++ b/3rdparty/qtsingleapplication/qtsinglecoreapplication.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2010 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. +** +** You may use this file under the terms of the BSD license as follows: +** +** "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 name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its 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 +** OWNER 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." +** +****************************************************************************/ + +#ifndef QTSINGLECOREAPPLICATION_H +#define QTSINGLECOREAPPLICATION_H + +#include + +class QtLocalPeer; + +class QtSingleCoreApplication : public QCoreApplication +{ + Q_OBJECT + +public: + QtSingleCoreApplication(int &argc, char **argv); + QtSingleCoreApplication(const QString &id, int &argc, char **argv); + + bool isRunning(); + QString id() const; + +public Q_SLOTS: + bool sendMessage(const QString &message, int timeout = 5000); + + +Q_SIGNALS: + void messageReceived(const QString &message); + + +private: + QtLocalPeer* peer; +}; + +#endif // QTSINGLECOREAPPLICATION_H diff --git a/3rdparty/qtwin/CMakeLists.txt b/3rdparty/qtwin/CMakeLists.txt new file mode 100644 index 00000000..9122be35 --- /dev/null +++ b/3rdparty/qtwin/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 2.8.11) + +set(QTWIN-SOURCES + qtwin.cpp +) + +ADD_LIBRARY(qtwin STATIC + ${QTWIN-SOURCES} +) + +target_link_libraries(qtwin + Qt5::Widgets +) diff --git a/3rdparty/qtwin/qtwin.cpp b/3rdparty/qtwin/qtwin.cpp new file mode 100644 index 00000000..69a24a9d --- /dev/null +++ b/3rdparty/qtwin/qtwin.cpp @@ -0,0 +1,229 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Use, modification and distribution is allowed without limitation, +** warranty, liability or support of any kind. +** +****************************************************************************/ + +#include "qtwin.h" +#include +#include +#include +#include +#include + +#ifdef Q_WS_WIN + +#include + +// Blur behind data structures +#define DWM_BB_ENABLE 0x00000001 // fEnable has been specified +#define DWM_BB_BLURREGION 0x00000002 // hRgnBlur has been specified +#define DWM_BB_TRANSITIONONMAXIMIZED 0x00000004 // fTransitionOnMaximized has been specified +#define WM_DWMCOMPOSITIONCHANGED 0x031E // Composition changed window message + +typedef struct _DWM_BLURBEHIND +{ + DWORD dwFlags; + BOOL fEnable; + HRGN hRgnBlur; + BOOL fTransitionOnMaximized; +} DWM_BLURBEHIND, *PDWM_BLURBEHIND; + +typedef struct _MARGINS +{ + int cxLeftWidth; + int cxRightWidth; + int cyTopHeight; + int cyBottomHeight; +} MARGINS, *PMARGINS; + +typedef HRESULT (WINAPI *PtrDwmIsCompositionEnabled)(BOOL* pfEnabled); +typedef HRESULT (WINAPI *PtrDwmExtendFrameIntoClientArea)(HWND hWnd, const MARGINS* pMarInset); +typedef HRESULT (WINAPI *PtrDwmEnableBlurBehindWindow)(HWND hWnd, const DWM_BLURBEHIND* pBlurBehind); +typedef HRESULT (WINAPI *PtrDwmGetColorizationColor)(DWORD *pcrColorization, BOOL *pfOpaqueBlend); + +static PtrDwmIsCompositionEnabled pDwmIsCompositionEnabled= 0; +static PtrDwmEnableBlurBehindWindow pDwmEnableBlurBehindWindow = 0; +static PtrDwmExtendFrameIntoClientArea pDwmExtendFrameIntoClientArea = 0; +static PtrDwmGetColorizationColor pDwmGetColorizationColor = 0; + + +/* + * Internal helper class that notifies windows if the + * DWM compositing state changes and updates the widget + * flags correspondingly. + */ +class WindowNotifier : public QWidget +{ +public: + WindowNotifier() { winId(); } + void addWidget(QWidget *widget) { widgets.append(widget); } + void removeWidget(QWidget *widget) { widgets.removeAll(widget); } + bool winEvent(MSG *message, long *result); + +private: + QWidgetList widgets; +}; + +static bool resolveLibs() +{ + if (!pDwmIsCompositionEnabled) { + QLibrary dwmLib(QString::fromAscii("dwmapi")); + pDwmIsCompositionEnabled =(PtrDwmIsCompositionEnabled)dwmLib.resolve("DwmIsCompositionEnabled"); + pDwmExtendFrameIntoClientArea = (PtrDwmExtendFrameIntoClientArea)dwmLib.resolve("DwmExtendFrameIntoClientArea"); + pDwmEnableBlurBehindWindow = (PtrDwmEnableBlurBehindWindow)dwmLib.resolve("DwmEnableBlurBehindWindow"); + pDwmGetColorizationColor = (PtrDwmGetColorizationColor)dwmLib.resolve("DwmGetColorizationColor"); + } + return pDwmIsCompositionEnabled != 0; +} + +#endif + +/*! + * Chekcs and returns true if Windows DWM composition + * is currently enabled on the system. + * + * To get live notification on the availability of + * this feature, you will currently have to + * reimplement winEvent() on your widget and listen + * for the WM_DWMCOMPOSITIONCHANGED event to occur. + * + */ +bool QtWin::isCompositionEnabled() +{ +#ifdef Q_WS_WIN + if (resolveLibs()) { + HRESULT hr = S_OK; + BOOL isEnabled = false; + hr = pDwmIsCompositionEnabled(&isEnabled); + if (SUCCEEDED(hr)) + return isEnabled; + } +#endif + return false; +} + +/*! + * Enables Blur behind on a Widget. + * + * \a enable tells if the blur should be enabled or not + */ +bool QtWin::enableBlurBehindWindow(QWidget *widget, bool enable, + const QRegion ®ion) +{ + Q_ASSERT(widget); + bool result = false; +#ifdef Q_WS_WIN + if (resolveLibs()) { + DWM_BLURBEHIND bb = {0}; + HRESULT hr = S_OK; + bb.fEnable = enable; + bb.dwFlags = DWM_BB_ENABLE; + bb.hRgnBlur = NULL; + + if (!region.isEmpty()) { + bb.dwFlags |= DWM_BB_BLURREGION; + bb.hRgnBlur = region.handle(); + } + + widget->setAttribute(Qt::WA_TranslucentBackground, enable); + widget->setAttribute(Qt::WA_NoSystemBackground, enable); + hr = pDwmEnableBlurBehindWindow(widget->winId(), &bb); + if (SUCCEEDED(hr)) { + result = true; + windowNotifier()->addWidget(widget); + } + } +#endif + return result; +} + +/*! + * ExtendFrameIntoClientArea. + * + * This controls the rendering of the frame inside the window. + * Note that passing margins of -1 (the default value) will completely + * remove the frame from the window. + * + * \note you should not call enableBlurBehindWindow before calling + * this functions + * + * \a enable tells if the blur should be enabled or not + */ +bool QtWin::extendFrameIntoClientArea(QWidget *widget, int left, int top, int right, int bottom) +{ + + Q_ASSERT(widget); + Q_UNUSED(left); + Q_UNUSED(top); + Q_UNUSED(right); + Q_UNUSED(bottom); + + bool result = false; +#ifdef Q_WS_WIN + if (resolveLibs()) { + QLibrary dwmLib(QString::fromAscii("dwmapi")); + HRESULT hr = S_OK; + MARGINS m = {left, top, right, bottom}; + hr = pDwmExtendFrameIntoClientArea(widget->winId(), &m); + if (SUCCEEDED(hr)) { + result = true; + windowNotifier()->addWidget(widget); + } + widget->setAttribute(Qt::WA_TranslucentBackground, result); + } +#endif + return result; +} + +/*! + * Returns the current colorizationColor for the window. + * + * \a enable tells if the blur should be enabled or not + */ +QColor QtWin::colorizatinColor() +{ + QColor resultColor = QApplication::palette().window().color(); + +#ifdef Q_WS_WIN + if (resolveLibs()) { + DWORD color = 0; + BOOL opaque = FALSE; + QLibrary dwmLib(QString::fromAscii("dwmapi")); + HRESULT hr = S_OK; + hr = pDwmGetColorizationColor(&color, &opaque); + if (SUCCEEDED(hr)) + resultColor = QColor(color); + } +#endif + return resultColor; +} + +#ifdef Q_WS_WIN +WindowNotifier *QtWin::windowNotifier() +{ + static WindowNotifier *windowNotifierInstance = 0; + if (!windowNotifierInstance) + windowNotifierInstance = new WindowNotifier; + return windowNotifierInstance; +} + + +/* Notify all enabled windows that the DWM state changed */ +bool WindowNotifier::winEvent(MSG *message, long *result) +{ + if (message && message->message == WM_DWMCOMPOSITIONCHANGED) { + bool compositionEnabled = QtWin::isCompositionEnabled(); + foreach(QWidget * widget, widgets) { + if (widget) { + widget->setAttribute(Qt::WA_NoSystemBackground, compositionEnabled); + } + widget->update(); + } + } + return QWidget::winEvent(message, result); +} +#endif diff --git a/3rdparty/qtwin/qtwin.h b/3rdparty/qtwin/qtwin.h new file mode 100644 index 00000000..a4018585 --- /dev/null +++ b/3rdparty/qtwin/qtwin.h @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Use, modification and distribution is allowed without limitation, +** warranty, liability or support of any kind. +** +****************************************************************************/ + +#ifndef QTWIN_H +#define QTWIN_H + +#include +#include +/** + * This is a helper class for using the Desktop Window Manager + * functionality on Windows 7 and Windows Vista. On other platforms + * these functions will simply not do anything. + */ + +class WindowNotifier; + +class QtWin +{ +public: + static bool enableBlurBehindWindow(QWidget *widget, bool enable = true, + const QRegion& region = QRegion()); + static bool extendFrameIntoClientArea(QWidget *widget, + int left = -1, int top = -1, + int right = -1, int bottom = -1); + static bool isCompositionEnabled(); + static QColor colorizatinColor(); + +private: + static WindowNotifier *windowNotifier(); +}; + +#endif // QTWIN_H diff --git a/3rdparty/qxt/CMakeLists.txt b/3rdparty/qxt/CMakeLists.txt new file mode 100644 index 00000000..75fa686b --- /dev/null +++ b/3rdparty/qxt/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 2.8.11) + +set(QXT-SOURCES + qxtglobal.cpp + qxtglobalshortcut.cpp +) + +set(QXT-MOC-HEADERS + qxtglobalshortcut.h +) + +find_package(X11) +include_directories(${X11_INCLUDE_DIR}) +include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) + +if(WIN32) + set(QXT-SOURCES ${QXT-SOURCES} qxtglobalshortcut_win.cpp) +elseif(APPLE) + set(QXT-SOURCES ${QXT-SOURCES} qxtglobalshortcut_mac.cpp) +else(WIN32) + set(QXT-SOURCES ${QXT-SOURCES} qxtglobalshortcut_x11.cpp) +endif(WIN32) + +QT5_WRAP_CPP(QXT-SOURCES-MOC ${QXT-MOC-HEADERS}) + +ADD_LIBRARY(qxt STATIC + ${QXT-SOURCES} + ${QXT-SOURCES-MOC} +) + +if(WIN32) + QT5_USE_MODULES(qxt Core Widgets) +else(WIN32) + QT5_USE_MODULES(qxt Core Widgets X11Extras) +endif(WIN32) diff --git a/3rdparty/qxt/LICENSE b/3rdparty/qxt/LICENSE new file mode 100644 index 00000000..91a9970f --- /dev/null +++ b/3rdparty/qxt/LICENSE @@ -0,0 +1,89 @@ +Qt Extension Library +Copyright (C) 2007 Qxt Foundation + +------------------- Disclaimer ------------------------------------------------ + +Until the Qxt Foundation is legally established, copyright for the +source code falls back to the original contributor. For information about the +status of the Qxt Foundation, or about the copyright status of any part of Qxt, +contact the Qxt project maintainers at + +Once the Qxt Foundation has been legally established, all contributors must +transfer all copyright interest to the Qxt Foundation before their submissions +will be added to the project. + +------------------- License --------------------------------------------------- + +This library is free software; you can redistribute it and/or modify it +under the terms of the Common Public License, version 1.0, as published by IBM +or under the terms of the GNU Lesser General Public License, version 2.1, +as published by the Free Software Foundation + +This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY +WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR +FITNESS FOR A PARTICULAR PURPOSE. + +You should have received a copy of the CPL along with this file. +See the LICENSE file and the cpl1.0.txt file included with the source +distribution for more information. If you did not receive a copy of the +license, contact the Qxt Foundation. + +You should have received a copy of the LGPL along with this file. +See the LICENSE file and the lgpl-2.1.txt file included with the source +distribution for more information. If you did not receive a copy of the +license, contact the Qxt Foundation. + +Parts of Qxt depend on Qt 4 and/or other libraries that have their own +licenses. Qxt is independent of these licenses; however, use of these other +libraries is subject to their respective license agreements. + +------------------- Intent ---------------------------------------------------- + +The following section describes the opinions and intent of the Qxt Foundation +with regards to the licensing and use of the Qxt source code and library. In +the event that the CPL is found to be illegal or invalid, or if any application +or clause of the license is subjected to question or abuse, this section is a +general statement of the desired interpretation. + +This section has no legal standing and the statements made here are strictly +subject to the text of the CPL; that is, if this section and the CPL are in +disagreement, the text of the CPL takes precedence. In no way does this +intent grant you any additional rights or impose any additional restrictions. + +If you have questions about licensing, contact the maintainers. + +Qxt is built and supported by open-source enthusiasts. +- Please respect the open-source background of the contributors. The code is + provided for everyone's use; you may not restrict the rights of anyone to + use it. + - No individual may claim ownership of any part of the code. It belongs + to the community. + - You may modify the source code to suit your needs, but these changes + must be made free. If you distribute a modified form of Qxt, you must + also distribute the entire source code of the modified form. + - Digital Rights Management (DRM) puts unfair, unfree restrictions on + users and developers. It is the opposite of Free Software. We can't + stop you from using it, but please don't use the Qxt name for software + restricted by DRM. +- Please respect the time and effort put into the project by the developers. + - If you find Qxt useful, it would be appreciated if you would include + text in your application (for instance, in the About dialog) giving + acknowledgement to Qxt. + - If you make modifications to the source code, you must not call the + modified version "Qxt." It's okay to include "Qxt" in the name, but + anyone who receives the modified version needs to know that it's not + the same as the version distributed by the Qxt Foundation. +- We want everyone to be able to use Qxt without restrictions. + - If you distribute Qxt in compiled binary form, please ensure that + everyone who receives it can get the source code used to create it. + - You are free to use Qxt in closed-source applications as long as you + distribute Qxt in an open-source fashion. This does not require you + to make your entire application open-source. +- The Qxt Foundation is a non-profit, non-political organization. + - Please don't use the Qxt name in any political or semi-political + propaganda or publication. We don't like it. +- Qxt is distributed "as-is," with no warranty. + - If it makes your program crash, your computer blow up, or tiny demons + fly out of your nose, please don't sue us. + diff --git a/3rdparty/qxt/keymapper_x11.h b/3rdparty/qxt/keymapper_x11.h new file mode 100644 index 00000000..f9a68b51 --- /dev/null +++ b/3rdparty/qxt/keymapper_x11.h @@ -0,0 +1,372 @@ +#ifndef KEYMAPPER_X11_H +#define KEYMAPPER_X11_H + +// (davidsansome) Nicked from qkeymapper_x11.cpp + +#include + +#define XK_MISCELLANY +#define XK_LATIN1 +#define XK_KOREAN +#define XK_XKB_KEYS +#include + +// +// Keyboard event translation +// + +#ifndef XK_ISO_Left_Tab +#define XK_ISO_Left_Tab 0xFE20 +#endif + +#ifndef XK_dead_hook +#define XK_dead_hook 0xFE61 +#endif + +#ifndef XK_dead_horn +#define XK_dead_horn 0xFE62 +#endif + +#ifndef XK_Codeinput +#define XK_Codeinput 0xFF37 +#endif + +#ifndef XK_Kanji_Bangou +#define XK_Kanji_Bangou 0xFF37 /* same as codeinput */ +#endif + +// Fix old X libraries +#ifndef XK_KP_Home +#define XK_KP_Home 0xFF95 +#endif +#ifndef XK_KP_Left +#define XK_KP_Left 0xFF96 +#endif +#ifndef XK_KP_Up +#define XK_KP_Up 0xFF97 +#endif +#ifndef XK_KP_Right +#define XK_KP_Right 0xFF98 +#endif +#ifndef XK_KP_Down +#define XK_KP_Down 0xFF99 +#endif +#ifndef XK_KP_Prior +#define XK_KP_Prior 0xFF9A +#endif +#ifndef XK_KP_Next +#define XK_KP_Next 0xFF9B +#endif +#ifndef XK_KP_End +#define XK_KP_End 0xFF9C +#endif +#ifndef XK_KP_Insert +#define XK_KP_Insert 0xFF9E +#endif +#ifndef XK_KP_Delete +#define XK_KP_Delete 0xFF9F +#endif + +// the next lines are taken from XFree > 4.0 (X11/XF86keysyms.h), defining some special +// multimedia keys. They are included here as not every system has them. +#define XF86XK_Standby 0x1008FF10 +#define XF86XK_AudioLowerVolume 0x1008FF11 +#define XF86XK_AudioMute 0x1008FF12 +#define XF86XK_AudioRaiseVolume 0x1008FF13 +#define XF86XK_AudioPlay 0x1008FF14 +#define XF86XK_AudioStop 0x1008FF15 +#define XF86XK_AudioPrev 0x1008FF16 +#define XF86XK_AudioNext 0x1008FF17 +#define XF86XK_HomePage 0x1008FF18 +#define XF86XK_Calculator 0x1008FF1D +#define XF86XK_Mail 0x1008FF19 +#define XF86XK_Start 0x1008FF1A +#define XF86XK_Search 0x1008FF1B +#define XF86XK_AudioRecord 0x1008FF1C +#define XF86XK_Back 0x1008FF26 +#define XF86XK_Forward 0x1008FF27 +#define XF86XK_Stop 0x1008FF28 +#define XF86XK_Refresh 0x1008FF29 +#define XF86XK_Favorites 0x1008FF30 +#define XF86XK_AudioPause 0x1008FF31 +#define XF86XK_AudioMedia 0x1008FF32 +#define XF86XK_MyComputer 0x1008FF33 +#define XF86XK_OpenURL 0x1008FF38 +#define XF86XK_Launch0 0x1008FF40 +#define XF86XK_Launch1 0x1008FF41 +#define XF86XK_Launch2 0x1008FF42 +#define XF86XK_Launch3 0x1008FF43 +#define XF86XK_Launch4 0x1008FF44 +#define XF86XK_Launch5 0x1008FF45 +#define XF86XK_Launch6 0x1008FF46 +#define XF86XK_Launch7 0x1008FF47 +#define XF86XK_Launch8 0x1008FF48 +#define XF86XK_Launch9 0x1008FF49 +#define XF86XK_LaunchA 0x1008FF4A +#define XF86XK_LaunchB 0x1008FF4B +#define XF86XK_LaunchC 0x1008FF4C +#define XF86XK_LaunchD 0x1008FF4D +#define XF86XK_LaunchE 0x1008FF4E +#define XF86XK_LaunchF 0x1008FF4F +// end of XF86keysyms.h + +// Special keys used by Qtopia, mapped into the X11 private keypad range. +#define QTOPIAXK_Select 0x11000601 +#define QTOPIAXK_Yes 0x11000602 +#define QTOPIAXK_No 0x11000603 +#define QTOPIAXK_Cancel 0x11000604 +#define QTOPIAXK_Printer 0x11000605 +#define QTOPIAXK_Execute 0x11000606 +#define QTOPIAXK_Sleep 0x11000607 +#define QTOPIAXK_Play 0x11000608 +#define QTOPIAXK_Zoom 0x11000609 +#define QTOPIAXK_Context1 0x1100060A +#define QTOPIAXK_Context2 0x1100060B +#define QTOPIAXK_Context3 0x1100060C +#define QTOPIAXK_Context4 0x1100060D +#define QTOPIAXK_Call 0x1100060E +#define QTOPIAXK_Hangup 0x1100060F +#define QTOPIAXK_Flip 0x11000610 + +// keyboard mapping table +static const unsigned int KeyTbl[] = { + + // misc keys + + XK_Escape, Qt::Key_Escape, + XK_Tab, Qt::Key_Tab, + XK_ISO_Left_Tab, Qt::Key_Backtab, + XK_BackSpace, Qt::Key_Backspace, + XK_Return, Qt::Key_Return, + XK_Insert, Qt::Key_Insert, + XK_Delete, Qt::Key_Delete, + XK_Clear, Qt::Key_Delete, + XK_Pause, Qt::Key_Pause, + XK_Print, Qt::Key_Print, + 0x1005FF60, Qt::Key_SysReq, // hardcoded Sun SysReq + 0x1007ff00, Qt::Key_SysReq, // hardcoded X386 SysReq + + // cursor movement + + XK_Home, Qt::Key_Home, + XK_End, Qt::Key_End, + XK_Left, Qt::Key_Left, + XK_Up, Qt::Key_Up, + XK_Right, Qt::Key_Right, + XK_Down, Qt::Key_Down, + XK_Prior, Qt::Key_PageUp, + XK_Next, Qt::Key_PageDown, + + // modifiers + + XK_Shift_L, Qt::Key_Shift, + XK_Shift_R, Qt::Key_Shift, + XK_Shift_Lock, Qt::Key_Shift, + XK_Control_L, Qt::Key_Control, + XK_Control_R, Qt::Key_Control, + XK_Meta_L, Qt::Key_Meta, + XK_Meta_R, Qt::Key_Meta, + XK_Alt_L, Qt::Key_Alt, + XK_Alt_R, Qt::Key_Alt, + XK_Caps_Lock, Qt::Key_CapsLock, + XK_Num_Lock, Qt::Key_NumLock, + XK_Scroll_Lock, Qt::Key_ScrollLock, + XK_Super_L, Qt::Key_Super_L, + XK_Super_R, Qt::Key_Super_R, + XK_Menu, Qt::Key_Menu, + XK_Hyper_L, Qt::Key_Hyper_L, + XK_Hyper_R, Qt::Key_Hyper_R, + XK_Help, Qt::Key_Help, + 0x1000FF74, Qt::Key_Backtab, // hardcoded HP backtab + 0x1005FF10, Qt::Key_F11, // hardcoded Sun F36 (labeled F11) + 0x1005FF11, Qt::Key_F12, // hardcoded Sun F37 (labeled F12) + + // numeric and function keypad keys + + XK_KP_Enter, Qt::Key_Enter, + + // special and additional keys + + XK_Clear, Qt::Key_Clear, + XK_Delete, Qt::Key_Delete, + XK_space, Qt::Key_Space, + XK_exclam, Qt::Key_Exclam, + XK_quotedbl, Qt::Key_QuoteDbl, + XK_numbersign, Qt::Key_NumberSign, + XK_dollar, Qt::Key_Dollar, + XK_percent, Qt::Key_Percent, + XK_ampersand, Qt::Key_Ampersand, + XK_apostrophe, Qt::Key_Apostrophe, + XK_parenleft, Qt::Key_ParenLeft, + XK_parenright, Qt::Key_ParenRight, + XK_asterisk, Qt::Key_Asterisk, + XK_plus, Qt::Key_Plus, + XK_comma, Qt::Key_Comma, + XK_minus, Qt::Key_Minus, + XK_period, Qt::Key_Period, + XK_slash, Qt::Key_Slash, + XK_colon, Qt::Key_Colon, + XK_semicolon, Qt::Key_Semicolon, + XK_less, Qt::Key_Less, + XK_equal, Qt::Key_Equal, + XK_greater, Qt::Key_Greater, + XK_question, Qt::Key_Question, + XK_bracketleft, Qt::Key_BracketLeft, + XK_backslash, Qt::Key_Backslash, + XK_bracketright, Qt::Key_BracketRight, + XK_asciicircum, Qt::Key_AsciiCircum, + XK_underscore, Qt::Key_Underscore, + + // International input method support keys + + // International & multi-key character composition + XK_ISO_Level3_Shift, Qt::Key_AltGr, + XK_Multi_key, Qt::Key_Multi_key, + XK_Codeinput, Qt::Key_Codeinput, + XK_SingleCandidate, Qt::Key_SingleCandidate, + XK_MultipleCandidate, Qt::Key_MultipleCandidate, + XK_PreviousCandidate, Qt::Key_PreviousCandidate, + + // Misc Functions + XK_Mode_switch, Qt::Key_Mode_switch, + XK_script_switch, Qt::Key_Mode_switch, + + // Japanese keyboard support + XK_Kanji, Qt::Key_Kanji, + XK_Muhenkan, Qt::Key_Muhenkan, + //XK_Henkan_Mode, Qt::Key_Henkan_Mode, + XK_Henkan_Mode, Qt::Key_Henkan, + XK_Henkan, Qt::Key_Henkan, + XK_Romaji, Qt::Key_Romaji, + XK_Hiragana, Qt::Key_Hiragana, + XK_Katakana, Qt::Key_Katakana, + XK_Hiragana_Katakana, Qt::Key_Hiragana_Katakana, + XK_Zenkaku, Qt::Key_Zenkaku, + XK_Hankaku, Qt::Key_Hankaku, + XK_Zenkaku_Hankaku, Qt::Key_Zenkaku_Hankaku, + XK_Touroku, Qt::Key_Touroku, + XK_Massyo, Qt::Key_Massyo, + XK_Kana_Lock, Qt::Key_Kana_Lock, + XK_Kana_Shift, Qt::Key_Kana_Shift, + XK_Eisu_Shift, Qt::Key_Eisu_Shift, + XK_Eisu_toggle, Qt::Key_Eisu_toggle, + //XK_Kanji_Bangou, Qt::Key_Kanji_Bangou, + //XK_Zen_Koho, Qt::Key_Zen_Koho, + //XK_Mae_Koho, Qt::Key_Mae_Koho, + XK_Kanji_Bangou, Qt::Key_Codeinput, + XK_Zen_Koho, Qt::Key_MultipleCandidate, + XK_Mae_Koho, Qt::Key_PreviousCandidate, + +#ifdef XK_KOREAN + // Korean keyboard support + XK_Hangul, Qt::Key_Hangul, + XK_Hangul_Start, Qt::Key_Hangul_Start, + XK_Hangul_End, Qt::Key_Hangul_End, + XK_Hangul_Hanja, Qt::Key_Hangul_Hanja, + XK_Hangul_Jamo, Qt::Key_Hangul_Jamo, + XK_Hangul_Romaja, Qt::Key_Hangul_Romaja, + //XK_Hangul_Codeinput, Qt::Key_Hangul_Codeinput, + XK_Hangul_Codeinput, Qt::Key_Codeinput, + XK_Hangul_Jeonja, Qt::Key_Hangul_Jeonja, + XK_Hangul_Banja, Qt::Key_Hangul_Banja, + XK_Hangul_PreHanja, Qt::Key_Hangul_PreHanja, + XK_Hangul_PostHanja, Qt::Key_Hangul_PostHanja, + //XK_Hangul_SingleCandidate,Qt::Key_Hangul_SingleCandidate, + //XK_Hangul_MultipleCandidate,Qt::Key_Hangul_MultipleCandidate, + //XK_Hangul_PreviousCandidate,Qt::Key_Hangul_PreviousCandidate, + XK_Hangul_SingleCandidate, Qt::Key_SingleCandidate, + XK_Hangul_MultipleCandidate,Qt::Key_MultipleCandidate, + XK_Hangul_PreviousCandidate,Qt::Key_PreviousCandidate, + XK_Hangul_Special, Qt::Key_Hangul_Special, + //XK_Hangul_switch, Qt::Key_Hangul_switch, + XK_Hangul_switch, Qt::Key_Mode_switch, +#endif // XK_KOREAN + + // dead keys + XK_dead_grave, Qt::Key_Dead_Grave, + XK_dead_acute, Qt::Key_Dead_Acute, + XK_dead_circumflex, Qt::Key_Dead_Circumflex, + XK_dead_tilde, Qt::Key_Dead_Tilde, + XK_dead_macron, Qt::Key_Dead_Macron, + XK_dead_breve, Qt::Key_Dead_Breve, + XK_dead_abovedot, Qt::Key_Dead_Abovedot, + XK_dead_diaeresis, Qt::Key_Dead_Diaeresis, + XK_dead_abovering, Qt::Key_Dead_Abovering, + XK_dead_doubleacute, Qt::Key_Dead_Doubleacute, + XK_dead_caron, Qt::Key_Dead_Caron, + XK_dead_cedilla, Qt::Key_Dead_Cedilla, + XK_dead_ogonek, Qt::Key_Dead_Ogonek, + XK_dead_iota, Qt::Key_Dead_Iota, + XK_dead_voiced_sound, Qt::Key_Dead_Voiced_Sound, + XK_dead_semivoiced_sound, Qt::Key_Dead_Semivoiced_Sound, + XK_dead_belowdot, Qt::Key_Dead_Belowdot, + XK_dead_hook, Qt::Key_Dead_Hook, + XK_dead_horn, Qt::Key_Dead_Horn, + + // Special multimedia keys + // currently only tested with MS internet keyboard + + // browsing keys + XF86XK_Back, Qt::Key_Back, + XF86XK_Forward, Qt::Key_Forward, + XF86XK_Stop, Qt::Key_Stop, + XF86XK_Refresh, Qt::Key_Refresh, + XF86XK_Favorites, Qt::Key_Favorites, + XF86XK_AudioMedia, Qt::Key_LaunchMedia, + XF86XK_OpenURL, Qt::Key_OpenUrl, + XF86XK_HomePage, Qt::Key_HomePage, + XF86XK_Search, Qt::Key_Search, + + // media keys + XF86XK_AudioLowerVolume, Qt::Key_VolumeDown, + XF86XK_AudioMute, Qt::Key_VolumeMute, + XF86XK_AudioRaiseVolume, Qt::Key_VolumeUp, + XF86XK_AudioPlay, Qt::Key_MediaPlay, + XF86XK_AudioStop, Qt::Key_MediaStop, + XF86XK_AudioPrev, Qt::Key_MediaPrevious, + XF86XK_AudioNext, Qt::Key_MediaNext, + XF86XK_AudioRecord, Qt::Key_MediaRecord, + + // launch keys + XF86XK_Mail, Qt::Key_LaunchMail, + XF86XK_MyComputer, Qt::Key_Launch0, + XF86XK_Calculator, Qt::Key_Launch1, + XF86XK_Standby, Qt::Key_Standby, + + XF86XK_Launch0, Qt::Key_Launch2, + XF86XK_Launch1, Qt::Key_Launch3, + XF86XK_Launch2, Qt::Key_Launch4, + XF86XK_Launch3, Qt::Key_Launch5, + XF86XK_Launch4, Qt::Key_Launch6, + XF86XK_Launch5, Qt::Key_Launch7, + XF86XK_Launch6, Qt::Key_Launch8, + XF86XK_Launch7, Qt::Key_Launch9, + XF86XK_Launch8, Qt::Key_LaunchA, + XF86XK_Launch9, Qt::Key_LaunchB, + XF86XK_LaunchA, Qt::Key_LaunchC, + XF86XK_LaunchB, Qt::Key_LaunchD, + XF86XK_LaunchC, Qt::Key_LaunchE, + XF86XK_LaunchD, Qt::Key_LaunchF, + + // Qtopia keys + QTOPIAXK_Select, Qt::Key_Select, + QTOPIAXK_Yes, Qt::Key_Yes, + QTOPIAXK_No, Qt::Key_No, + QTOPIAXK_Cancel, Qt::Key_Cancel, + QTOPIAXK_Printer, Qt::Key_Printer, + QTOPIAXK_Execute, Qt::Key_Execute, + QTOPIAXK_Sleep, Qt::Key_Sleep, + QTOPIAXK_Play, Qt::Key_Play, + QTOPIAXK_Zoom, Qt::Key_Zoom, + QTOPIAXK_Context1, Qt::Key_Context1, + QTOPIAXK_Context2, Qt::Key_Context2, + QTOPIAXK_Context3, Qt::Key_Context3, + QTOPIAXK_Context4, Qt::Key_Context4, + QTOPIAXK_Call, Qt::Key_Call, + QTOPIAXK_Hangup, Qt::Key_Hangup, + QTOPIAXK_Flip, Qt::Key_Flip, + + 0, 0 +}; + +#endif // KEYMAPPER_X11_H diff --git a/3rdparty/qxt/qxtglobal.cpp b/3rdparty/qxt/qxtglobal.cpp new file mode 100644 index 00000000..3da47c14 --- /dev/null +++ b/3rdparty/qxt/qxtglobal.cpp @@ -0,0 +1,251 @@ + +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** 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 name of the LibQxt project nor the +** names of its 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 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. +** +** +*****************************************************************************/ + +#include "qxtglobal.h" + +/*! + \headerfile + \title Global Qxt Declarations + \inmodule QxtCore + + \brief The header provides basic declarations and + is included by all other Qxt headers. + */ + +/*! + \macro QXT_VERSION + \relates + + This macro expands a numeric value of the form 0xMMNNPP (MM = + major, NN = minor, PP = patch) that specifies Qxt's version + number. For example, if you compile your application against Qxt + 0.4.0, the QXT_VERSION macro will expand to 0x000400. + + You can use QXT_VERSION to use the latest Qt features where + available. For example: + \code + #if QXT_VERSION >= 0x000400 + qxtTabWidget->setTabMovementMode(QxtTabWidget::InPlaceMovement); + #endif + \endcode + + \sa QXT_VERSION_STR, qxtVersion() + */ + +/*! + \macro QXT_VERSION_STR + \relates + + This macro expands to a string that specifies Qxt's version number + (for example, "0.4.0"). This is the version against which the + application is compiled. + + \sa qxtVersion(), QXT_VERSION + */ + +/*! + \relates + + Returns the version number of Qxt at run-time as a string (for + example, "0.4.0"). This may be a different version than the + version the application was compiled against. + + \sa QXT_VERSION_STR + */ +const char * qxtVersion() +{ + return QXT_VERSION_STR; +} + +/*! +\headerfile +\title The Qxt private implementation +\inmodule QxtCore + +\brief The header provides tools for hiding +details of a class. + +Application code generally doesn't have to be concerned about hiding its +implementation details, but when writing library code it is important to +maintain a constant interface, both source and binary. Maintaining a constant +source interface is easy enough, but keeping the binary interface constant +means moving implementation details into a private class. The PIMPL, or +d-pointer, idiom is a common method of implementing this separation. QxtPimpl +offers a convenient way to connect the public and private sides of your class. + +\section1 Getting Started +Before you declare the public class, you need to make a forward declaration +of the private class. The private class must have the same name as the public +class, followed by the word Private. For example, a class named MyTest would +declare the private class with: +\code +class MyTestPrivate; +\endcode + +\section1 The Public Class +Generally, you shouldn't keep any data members in the public class without a +good reason. Functions that are part of the public interface should be declared +in the public class, and functions that need to be available to subclasses (for +calling or overriding) should be in the protected section of the public class. +To connect the private class to the public class, include the +QXT_DECLARE_PRIVATE macro in the private section of the public class. In the +example above, the private class is connected as follows: +\code +private: + QXT_DECLARE_PRIVATE(MyTest) +\endcode + +Additionally, you must include the QXT_INIT_PRIVATE macro in the public class's +constructor. Continuing with the MyTest example, your constructor might look +like this: +\code +MyTest::MyTest() { + // initialization + QXT_INIT_PRIVATE(MyTest); +} +\endcode + +\section1 The Private Class +As mentioned above, data members should usually be kept in the private class. +This allows the memory layout of the private class to change without breaking +binary compatibility for the public class. Functions that exist only as +implementation details, or functions that need access to private data members, +should be implemented here. + +To define the private class, inherit from the template QxtPrivate class, and +include the QXT_DECLARE_PUBLIC macro in its public section. The template +parameter should be the name of the public class. For example: +\code +class MyTestPrivate : public QxtPrivate { +public: + MyTestPrivate(); + QXT_DECLARE_PUBLIC(MyTest) +}; +\endcode + +\section1 Accessing Private Members +Use the qxt_d() function (actually a function-like object) from functions in +the public class to access the private class. Similarly, functions in the +private class can invoke functions in the public class by using the qxt_p() +function (this one's actually a function). + +For example, assume that MyTest has methods named getFoobar and doBaz(), +and MyTestPrivate has a member named foobar and a method named doQuux(). +The code might resemble this example: +\code +int MyTest::getFoobar() { + return qxt_d().foobar; +} + +void MyTestPrivate::doQuux() { + qxt_p().doBaz(foobar); +} +\endcode +*/ + +/*! + * \macro QXT_DECLARE_PRIVATE(PUB) + * \relates + * Declares that a public class has a related private class. + * + * This shuold be put in the private section of the public class. The + * parameter \a PUB must be the name of the public class. + */ + +/*! + * \macro QXT_DECLARE_PUBLIC(PUB) + * \relates + * Declares that a private class has a related public class named \a PUB. + * + * This may be put anywhere in the declaration of the private class. The parameter is the name of the public class. + */ + +/*! + * \macro QXT_INIT_PRIVATE(PUB) + * \relates + * Initializes resources owned by the private class. + * + * This should be called from the public class's constructor, + * before qxt_d() is used for the first time. The parameter \a PUB must be + * the name of the public class. + */ + +/*! + * \macro QXT_D(PUB) + * \relates + * Returns a reference in the current scope named "d" to the private class + * associated with the public class \a PUB. + * + * This function is only available in a class using QXT_DECLARE_PRIVATE(). + */ + +/*! + * \macro QXT_P(PUB) + * \relates + * Creates a reference in the current scope named "q" to the public class + * named \a PUB. + * + * This macro only works in a class using QXT_DECLARE_PUBLIC(). + */ + +/*! + * \fn QxtPrivate& PUB::qxt_d() + * \relates + * Returns a reference to the private class. + * + * This function is only available in a class using \a QXT_DECLARE_PRIVATE. + */ + +/*! + * \fn const QxtPrivate& PUB::qxt_d() const + * \relates + * Returns a const reference to the private class. + * + * This function is only available in a class using \a QXT_DECLARE_PRIVATE. + * This overload will be automatically used in const functions. + */ + +/*! + * \fn PUB& QxtPrivate::qxt_p() + * \relates + * Returns a reference to the public class. + * + * This function is only available in a class using QXT_DECLARE_PUBLIC(). + */ + +/*! + * \fn const PUB& QxtPrivate::qxt_p() const + * \relates + * Returns a const reference to the public class. + * + * This function is only available in a class using QXT_DECLARE_PUBLIC(). + * This overload will be automatically used in const functions. + */ diff --git a/3rdparty/qxt/qxtglobal.h b/3rdparty/qxt/qxtglobal.h new file mode 100644 index 00000000..7d5abfbe --- /dev/null +++ b/3rdparty/qxt/qxtglobal.h @@ -0,0 +1,233 @@ + +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** 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 name of the LibQxt project nor the +** names of its 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 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. +** +** +*****************************************************************************/ + +#ifndef QXTGLOBAL_H +#define QXTGLOBAL_H + +#include + +#define QXT_VERSION 0x000700 +#define QXT_VERSION_STR "0.7.0" + +//--------------------------global macros------------------------------ + +#ifndef QXT_NO_MACROS + +#ifndef _countof +#define _countof(x) (sizeof(x)/sizeof(*x)) +#endif + +#endif // QXT_NO_MACROS + +//--------------------------export macros------------------------------ + +#define QXT_DLLEXPORT DO_NOT_USE_THIS_ANYMORE + +#if !defined(QXT_STATIC) && !defined(QXT_DOXYGEN_RUN) +# if defined(BUILD_QXT_CORE) +# define QXT_CORE_EXPORT Q_DECL_EXPORT +# else +# define QXT_CORE_EXPORT Q_DECL_IMPORT +# endif +#else +# define QXT_CORE_EXPORT +#endif // BUILD_QXT_CORE + +#if !defined(QXT_STATIC) && !defined(QXT_DOXYGEN_RUN) +# if defined(BUILD_QXT_GUI) +# define QXT_GUI_EXPORT Q_DECL_EXPORT +# else +# define QXT_GUI_EXPORT Q_DECL_IMPORT +# endif +#else +# define QXT_GUI_EXPORT +#endif // BUILD_QXT_GUI + +#if !defined(QXT_STATIC) && !defined(QXT_DOXYGEN_RUN) +# if defined(BUILD_QXT_NETWORK) +# define QXT_NETWORK_EXPORT Q_DECL_EXPORT +# else +# define QXT_NETWORK_EXPORT Q_DECL_IMPORT +# endif +#else +# define QXT_NETWORK_EXPORT +#endif // BUILD_QXT_NETWORK + +#if !defined(QXT_STATIC) && !defined(QXT_DOXYGEN_RUN) +# if defined(BUILD_QXT_SQL) +# define QXT_SQL_EXPORT Q_DECL_EXPORT +# else +# define QXT_SQL_EXPORT Q_DECL_IMPORT +# endif +#else +# define QXT_SQL_EXPORT +#endif // BUILD_QXT_SQL + +#if !defined(QXT_STATIC) && !defined(QXT_DOXYGEN_RUN) +# if defined(BUILD_QXT_WEB) +# define QXT_WEB_EXPORT Q_DECL_EXPORT +# else +# define QXT_WEB_EXPORT Q_DECL_IMPORT +# endif +#else +# define QXT_WEB_EXPORT +#endif // BUILD_QXT_WEB + +#if !defined(QXT_STATIC) && !defined(QXT_DOXYGEN_RUN) +# if defined(BUILD_QXT_BERKELEY) +# define QXT_BERKELEY_EXPORT Q_DECL_EXPORT +# else +# define QXT_BERKELEY_EXPORT Q_DECL_IMPORT +# endif +#else +# define QXT_BERKELEY_EXPORT +#endif // BUILD_QXT_BERKELEY + +#if !defined(QXT_STATIC) && !defined(QXT_DOXYGEN_RUN) +# if defined(BUILD_QXT_ZEROCONF) +# define QXT_ZEROCONF_EXPORT Q_DECL_EXPORT +# else +# define QXT_ZEROCONF_EXPORT Q_DECL_IMPORT +# endif +#else +# define QXT_ZEROCONF_EXPORT +#endif // QXT_ZEROCONF_EXPORT + +#if defined(BUILD_QXT_CORE) || defined(BUILD_QXT_GUI) || defined(BUILD_QXT_SQL) || defined(BUILD_QXT_NETWORK) || defined(BUILD_QXT_WEB) || defined(BUILD_QXT_BERKELEY) || defined(BUILD_QXT_ZEROCONF) +# define BUILD_QXT +#endif + +QXT_CORE_EXPORT const char* qxtVersion(); + +#ifndef QT_BEGIN_NAMESPACE +#define QT_BEGIN_NAMESPACE +#endif + +#ifndef QT_END_NAMESPACE +#define QT_END_NAMESPACE +#endif + +#ifndef QT_FORWARD_DECLARE_CLASS +#define QT_FORWARD_DECLARE_CLASS(Class) class Class; +#endif + +/**************************************************************************** +** This file is derived from code bearing the following notice: +** The sole author of this file, Adam Higerd, has explicitly disclaimed all +** copyright interest and protection for the content within. This file has +** been placed in the public domain according to United States copyright +** statute and case law. In jurisdictions where this public domain dedication +** is not legally recognized, anyone who receives a copy of this file is +** permitted to use, modify, duplicate, and redistribute this file, in whole +** or in part, with no restrictions or conditions. In these jurisdictions, +** this file shall be copyright (C) 2006-2008 by Adam Higerd. +****************************************************************************/ + +#define QXT_DECLARE_PRIVATE(PUB) friend class PUB##Private; QxtPrivateInterface qxt_d; +#define QXT_DECLARE_PUBLIC(PUB) friend class PUB; +#define QXT_INIT_PRIVATE(PUB) qxt_d.setPublic(this); +#define QXT_D(PUB) PUB##Private& d = qxt_d() +#define QXT_P(PUB) PUB& p = qxt_p() + +template +class QxtPrivate +{ +public: + virtual ~QxtPrivate() + {} + inline void QXT_setPublic(PUB* pub) + { + qxt_p_ptr = pub; + } + +protected: + inline PUB& qxt_p() + { + return *qxt_p_ptr; + } + inline const PUB& qxt_p() const + { + return *qxt_p_ptr; + } + inline PUB* qxt_ptr() + { + return qxt_p_ptr; + } + inline const PUB* qxt_ptr() const + { + return qxt_p_ptr; + } + +private: + PUB* qxt_p_ptr; +}; + +template +class QxtPrivateInterface +{ + friend class QxtPrivate; +public: + QxtPrivateInterface() + { + pvt = new PVT; + } + ~QxtPrivateInterface() + { + delete pvt; + } + + inline void setPublic(PUB* pub) + { + pvt->QXT_setPublic(pub); + } + inline PVT& operator()() + { + return *static_cast(pvt); + } + inline const PVT& operator()() const + { + return *static_cast(pvt); + } + inline PVT * operator->() + { + return static_cast(pvt); + } + inline const PVT * operator->() const + { + return static_cast(pvt); + } +private: + QxtPrivateInterface(const QxtPrivateInterface&) { } + QxtPrivateInterface& operator=(const QxtPrivateInterface&) { } + QxtPrivate* pvt; +}; + +#endif // QXT_GLOBAL diff --git a/3rdparty/qxt/qxtglobalshortcut.cpp b/3rdparty/qxt/qxtglobalshortcut.cpp new file mode 100644 index 00000000..6ea380ca --- /dev/null +++ b/3rdparty/qxt/qxtglobalshortcut.cpp @@ -0,0 +1,218 @@ +#include "qxtglobalshortcut.h" +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** 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 name of the LibQxt project nor the +** names of its 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 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. +** +** +*****************************************************************************/ + +#include "qxtglobalshortcut_p.h" +#include +#include + +bool QxtGlobalShortcutPrivate::error = false; +#ifndef Q_WS_MAC +int QxtGlobalShortcutPrivate::ref = 0; +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) +QAbstractEventDispatcher::EventFilter QxtGlobalShortcutPrivate::prevEventFilter = 0; +#endif +#endif // Q_WS_MAC +QHash, QxtGlobalShortcut*> QxtGlobalShortcutPrivate::shortcuts; + +QxtGlobalShortcutPrivate::QxtGlobalShortcutPrivate() : enabled(true), key(Qt::Key(0)), mods(Qt::NoModifier) +{ +#ifndef Q_WS_MAC + if (!ref++) +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) + prevEventFilter = QAbstractEventDispatcher::instance()->setEventFilter(eventFilter); +#else + QAbstractEventDispatcher::instance()->installNativeEventFilter(this); +#endif +#endif // Q_WS_MAC +} + +QxtGlobalShortcutPrivate::~QxtGlobalShortcutPrivate() +{ +#ifndef Q_WS_MAC + if (!--ref) +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) + QAbstractEventDispatcher::instance()->setEventFilter(prevEventFilter); +#else + QAbstractEventDispatcher::instance()->removeNativeEventFilter(this); +#endif +#endif // Q_WS_MAC +} + +bool QxtGlobalShortcutPrivate::setShortcut(const QKeySequence& shortcut) +{ + Qt::KeyboardModifiers allMods = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier; + key = shortcut.isEmpty() ? Qt::Key(0) : Qt::Key((shortcut[0] ^ allMods) & shortcut[0]); + mods = shortcut.isEmpty() ? Qt::KeyboardModifiers(0) : Qt::KeyboardModifiers(shortcut[0] & allMods); + const quint32 nativeKey = nativeKeycode(key); + const quint32 nativeMods = nativeModifiers(mods); + const bool res = registerShortcut(nativeKey, nativeMods); + if (res) + shortcuts.insert(qMakePair(nativeKey, nativeMods), &qxt_p()); + else + qWarning() << "QxtGlobalShortcut failed to register:" << QKeySequence(key + mods).toString(); + return res; +} + +bool QxtGlobalShortcutPrivate::unsetShortcut() +{ + bool res = false; + const quint32 nativeKey = nativeKeycode(key); + const quint32 nativeMods = nativeModifiers(mods); + if (shortcuts.value(qMakePair(nativeKey, nativeMods)) == &qxt_p()) + res = unregisterShortcut(nativeKey, nativeMods); + if (res) + shortcuts.remove(qMakePair(nativeKey, nativeMods)); + else + qWarning() << "QxtGlobalShortcut failed to unregister:" << QKeySequence(key + mods).toString(); + key = Qt::Key(0); + mods = Qt::KeyboardModifiers(0); + return res; +} + +void QxtGlobalShortcutPrivate::activateShortcut(quint32 nativeKey, quint32 nativeMods) +{ + QxtGlobalShortcut* shortcut = shortcuts.value(qMakePair(nativeKey, nativeMods)); + if (shortcut && shortcut->isEnabled()) + emit shortcut->activated(); +} + +/*! + \class QxtGlobalShortcut + \inmodule QxtWidgets + \brief The QxtGlobalShortcut class provides a global shortcut aka "hotkey". + + A global shortcut triggers even if the application is not active. This + makes it easy to implement applications that react to certain shortcuts + still if some other application is active or if the application is for + example minimized to the system tray. + + Example usage: + \code + QxtGlobalShortcut* shortcut = new QxtGlobalShortcut(window); + connect(shortcut, SIGNAL(activated()), window, SLOT(toggleVisibility())); + shortcut->setShortcut(QKeySequence("Ctrl+Shift+F12")); + \endcode + + \bold {Note:} Since Qxt 0.6 QxtGlobalShortcut no more requires QxtApplication. + */ + +/*! + \fn QxtGlobalShortcut::activated() + + This signal is emitted when the user types the shortcut's key sequence. + + \sa shortcut + */ + +/*! + Constructs a new QxtGlobalShortcut with \a parent. + */ +QxtGlobalShortcut::QxtGlobalShortcut(QObject* parent) + : QObject(parent) +{ + QXT_INIT_PRIVATE(QxtGlobalShortcut); +} + +/*! + Constructs a new QxtGlobalShortcut with \a shortcut and \a parent. + */ +QxtGlobalShortcut::QxtGlobalShortcut(const QKeySequence& shortcut, QObject* parent) + : QObject(parent) +{ + QXT_INIT_PRIVATE(QxtGlobalShortcut); + setShortcut(shortcut); +} + +/*! + Destructs the QxtGlobalShortcut. + */ +QxtGlobalShortcut::~QxtGlobalShortcut() +{ + if (qxt_d().key != 0) + qxt_d().unsetShortcut(); +} + +/*! + \property QxtGlobalShortcut::shortcut + \brief the shortcut key sequence + + \bold {Note:} Notice that corresponding key press and release events are not + delivered for registered global shortcuts even if they are disabled. + Also, comma separated key sequences are not supported. + Only the first part is used: + + \code + qxtShortcut->setShortcut(QKeySequence("Ctrl+Alt+A,Ctrl+Alt+B")); + Q_ASSERT(qxtShortcut->shortcut() == QKeySequence("Ctrl+Alt+A")); + \endcode + */ +QKeySequence QxtGlobalShortcut::shortcut() const +{ + return QKeySequence(qxt_d().key | qxt_d().mods); +} + +bool QxtGlobalShortcut::setShortcut(const QKeySequence& shortcut) +{ + if (qxt_d().key != 0) + qxt_d().unsetShortcut(); + return qxt_d().setShortcut(shortcut); +} + +/*! + \property QxtGlobalShortcut::enabled + \brief whether the shortcut is enabled + + A disabled shortcut does not get activated. + + The default value is \c true. + + \sa setDisabled() + */ +bool QxtGlobalShortcut::isEnabled() const +{ + return qxt_d().enabled; +} + +void QxtGlobalShortcut::setEnabled(bool enabled) +{ + qxt_d().enabled = enabled; +} + +/*! + Sets the shortcut \a disabled. + + \sa enabled + */ +void QxtGlobalShortcut::setDisabled(bool disabled) +{ + qxt_d().enabled = !disabled; +} + diff --git a/3rdparty/qxt/qxtglobalshortcut.h b/3rdparty/qxt/qxtglobalshortcut.h new file mode 100644 index 00000000..907e04c5 --- /dev/null +++ b/3rdparty/qxt/qxtglobalshortcut.h @@ -0,0 +1,65 @@ +#ifndef QXTGLOBALSHORTCUT_H +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** 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 name of the LibQxt project nor the +** names of its 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 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. +** +** +*****************************************************************************/ + +#define QXTGLOBALSHORTCUT_H + +#include "qxtglobal.h" +#include +#include +class QxtGlobalShortcutPrivate; + +class QXT_GUI_EXPORT QxtGlobalShortcut : public QObject +{ + Q_OBJECT + QXT_DECLARE_PRIVATE(QxtGlobalShortcut) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) + Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut) + +public: + explicit QxtGlobalShortcut(QObject* parent = 0); + explicit QxtGlobalShortcut(const QKeySequence& shortcut, QObject* parent = 0); + virtual ~QxtGlobalShortcut(); + + QKeySequence shortcut() const; + bool setShortcut(const QKeySequence& shortcut); + + bool isEnabled() const; + +public Q_SLOTS: + void setEnabled(bool enabled = true); + void setDisabled(bool disabled = true); + +Q_SIGNALS: + void activated(); +}; + +#endif // QXTGLOBALSHORTCUT_H + diff --git a/3rdparty/qxt/qxtglobalshortcut_mac.cpp b/3rdparty/qxt/qxtglobalshortcut_mac.cpp new file mode 100644 index 00000000..58b9a904 --- /dev/null +++ b/3rdparty/qxt/qxtglobalshortcut_mac.cpp @@ -0,0 +1,258 @@ +#include +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** 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 name of the LibQxt project nor the +** names of its 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 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. +** +** +*****************************************************************************/ + +#include "qxtglobalshortcut_p.h" +#include +#include +#include +#include + +typedef QPair Identifier; +static QMap keyRefs; +static QHash keyIDs; +static quint32 hotKeySerial = 0; +static bool qxt_mac_handler_installed = false; + +OSStatus qxt_mac_handle_hot_key(EventHandlerCallRef nextHandler, EventRef event, void* data) +{ + Q_UNUSED(nextHandler); + Q_UNUSED(data); + if (GetEventClass(event) == kEventClassKeyboard && GetEventKind(event) == kEventHotKeyPressed) + { + EventHotKeyID keyID; + GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(keyID), NULL, &keyID); + Identifier id = keyIDs.key(keyID.id); + QxtGlobalShortcutPrivate::activateShortcut(id.second, id.first); + } + return noErr; +} + +quint32 QxtGlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifiers) +{ + quint32 native = 0; + if (modifiers & Qt::ShiftModifier) + native |= shiftKey; + if (modifiers & Qt::ControlModifier) + native |= cmdKey; + if (modifiers & Qt::AltModifier) + native |= optionKey; + if (modifiers & Qt::MetaModifier) + native |= controlKey; + if (modifiers & Qt::KeypadModifier) + native |= kEventKeyModifierNumLockMask; + return native; +} + +quint32 QxtGlobalShortcutPrivate::nativeKeycode(Qt::Key key) +{ + UTF16Char ch; + // Constants found in NSEvent.h from AppKit.framework + switch (key) + { + case Qt::Key_Return: + return kVK_Return; + case Qt::Key_Enter: + return kVK_ANSI_KeypadEnter; + case Qt::Key_Tab: + return kVK_Tab; + case Qt::Key_Space: + return kVK_Space; + case Qt::Key_Backspace: + return kVK_Delete; + case Qt::Key_Control: + return kVK_Command; + case Qt::Key_Shift: + return kVK_Shift; + case Qt::Key_CapsLock: + return kVK_CapsLock; + case Qt::Key_Option: + return kVK_Option; + case Qt::Key_Meta: + return kVK_Control; + case Qt::Key_F17: + return kVK_F17; + case Qt::Key_VolumeUp: + return kVK_VolumeUp; + case Qt::Key_VolumeDown: + return kVK_VolumeDown; + case Qt::Key_F18: + return kVK_F18; + case Qt::Key_F19: + return kVK_F19; + case Qt::Key_F20: + return kVK_F20; + case Qt::Key_F5: + return kVK_F5; + case Qt::Key_F6: + return kVK_F6; + case Qt::Key_F7: + return kVK_F7; + case Qt::Key_F3: + return kVK_F3; + case Qt::Key_F8: + return kVK_F8; + case Qt::Key_F9: + return kVK_F9; + case Qt::Key_F11: + return kVK_F11; + case Qt::Key_F13: + return kVK_F13; + case Qt::Key_F16: + return kVK_F16; + case Qt::Key_F14: + return kVK_F14; + case Qt::Key_F10: + return kVK_F10; + case Qt::Key_F12: + return kVK_F12; + case Qt::Key_F15: + return kVK_F15; + case Qt::Key_Help: + return kVK_Help; + case Qt::Key_Home: + return kVK_Home; + case Qt::Key_PageUp: + return kVK_PageUp; + case Qt::Key_Delete: + return kVK_ForwardDelete; + case Qt::Key_F4: + return kVK_F4; + case Qt::Key_End: + return kVK_End; + case Qt::Key_F2: + return kVK_F2; + case Qt::Key_PageDown: + return kVK_PageDown; + case Qt::Key_F1: + return kVK_F1; + case Qt::Key_Left: + return kVK_LeftArrow; + case Qt::Key_Right: + return kVK_RightArrow; + case Qt::Key_Down: + return kVK_DownArrow; + case Qt::Key_Up: + return kVK_UpArrow; + default: + ; + } + + if (key == Qt::Key_Escape) ch = 27; + else if (key == Qt::Key_Return) ch = 13; + else if (key == Qt::Key_Enter) ch = 3; + else if (key == Qt::Key_Tab) ch = 9; + else ch = key; + + CFDataRef currentLayoutData; + TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); + + if (currentKeyboard == NULL) + return 0; + + currentLayoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); + CFRelease(currentKeyboard); + if (currentLayoutData == NULL) + return 0; + + UCKeyboardLayout* header = (UCKeyboardLayout*)CFDataGetBytePtr(currentLayoutData); + UCKeyboardTypeHeader* table = header->keyboardTypeList; + + uint8_t *data = (uint8_t*)header; + // God, would a little documentation for this shit kill you... + for (quint32 i=0; i < header->keyboardTypeCount; i++) + { + UCKeyStateRecordsIndex* stateRec = 0; + if (table[i].keyStateRecordsIndexOffset != 0) + { + stateRec = reinterpret_cast(data + table[i].keyStateRecordsIndexOffset); + if (stateRec->keyStateRecordsIndexFormat != kUCKeyStateRecordsIndexFormat) stateRec = 0; + } + + UCKeyToCharTableIndex* charTable = reinterpret_cast(data + table[i].keyToCharTableIndexOffset); + if (charTable->keyToCharTableIndexFormat != kUCKeyToCharTableIndexFormat) continue; + + for (quint32 j=0; j < charTable->keyToCharTableCount; j++) + { + UCKeyOutput* keyToChar = reinterpret_cast(data + charTable->keyToCharTableOffsets[j]); + for (quint32 k=0; k < charTable->keyToCharTableSize; k++) + { + if (keyToChar[k] & kUCKeyOutputTestForIndexMask) + { + long idx = keyToChar[k] & kUCKeyOutputGetIndexMask; + if (stateRec && idx < stateRec->keyStateRecordCount) + { + UCKeyStateRecord* rec = reinterpret_cast(data + stateRec->keyStateRecordOffsets[idx]); + if (rec->stateZeroCharData == ch) return k; + } + } + else if (!(keyToChar[k] & kUCKeyOutputSequenceIndexMask) && keyToChar[k] < 0xFFFE) + { + if (keyToChar[k] == ch) return k; + } + } // for k + } // for j + } // for i + return 0; +} + +bool QxtGlobalShortcutPrivate::registerShortcut(quint32 nativeKey, quint32 nativeMods) +{ + if (!qxt_mac_handler_installed) + { + EventTypeSpec t; + t.eventClass = kEventClassKeyboard; + t.eventKind = kEventHotKeyPressed; + InstallApplicationEventHandler(&qxt_mac_handle_hot_key, 1, &t, NULL, NULL); + } + + EventHotKeyID keyID; + keyID.signature = 'cute'; + keyID.id = ++hotKeySerial; + + EventHotKeyRef ref = 0; + bool rv = !RegisterEventHotKey(nativeKey, nativeMods, keyID, GetApplicationEventTarget(), 0, &ref); + if (rv) + { + keyIDs.insert(Identifier(nativeMods, nativeKey), keyID.id); + keyRefs.insert(keyID.id, ref); + } + return rv; +} + +bool QxtGlobalShortcutPrivate::unregisterShortcut(quint32 nativeKey, quint32 nativeMods) +{ + Identifier id(nativeMods, nativeKey); + if (!keyIDs.contains(id)) return false; + + EventHotKeyRef ref = keyRefs.take(keyIDs[id]); + keyIDs.remove(id); + return !UnregisterEventHotKey(ref); +} diff --git a/3rdparty/qxt/qxtglobalshortcut_p.h b/3rdparty/qxt/qxtglobalshortcut_p.h new file mode 100644 index 00000000..1a788852 --- /dev/null +++ b/3rdparty/qxt/qxtglobalshortcut_p.h @@ -0,0 +1,84 @@ +#ifndef QXTGLOBALSHORTCUT_P_H +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** 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 name of the LibQxt project nor the +** names of its 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 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. +** +** +*****************************************************************************/ + +#define QXTGLOBALSHORTCUT_P_H + +#include "qxtglobalshortcut.h" +#include +#include +#include + +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) +#include +#endif + + +class QxtGlobalShortcutPrivate : public QxtPrivate +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + ,public QAbstractNativeEventFilter +#endif +{ +public: + QXT_DECLARE_PUBLIC(QxtGlobalShortcut) + QxtGlobalShortcutPrivate(); + ~QxtGlobalShortcutPrivate(); + + bool enabled; + Qt::Key key; + Qt::KeyboardModifiers mods; + + bool setShortcut(const QKeySequence& shortcut); + bool unsetShortcut(); + + static bool error; +#ifndef Q_WS_MAC + static int ref; +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) + static QAbstractEventDispatcher::EventFilter prevEventFilter; + static bool eventFilter(void* message); +#else + virtual bool nativeEventFilter(const QByteArray & eventType, void * message, long * result); +#endif // QT_VERSION < QT_VERSION_CHECK(5,0,0) +#endif // Q_WS_MAC + + static void activateShortcut(quint32 nativeKey, quint32 nativeMods); + +private: + static quint32 nativeKeycode(Qt::Key keycode); + static quint32 nativeModifiers(Qt::KeyboardModifiers modifiers); + + static bool registerShortcut(quint32 nativeKey, quint32 nativeMods); + static bool unregisterShortcut(quint32 nativeKey, quint32 nativeMods); + + static QHash, QxtGlobalShortcut*> shortcuts; +}; + +#endif // QXTGLOBALSHORTCUT_P_H diff --git a/3rdparty/qxt/qxtglobalshortcut_win.cpp b/3rdparty/qxt/qxtglobalshortcut_win.cpp new file mode 100644 index 00000000..1f4b611d --- /dev/null +++ b/3rdparty/qxt/qxtglobalshortcut_win.cpp @@ -0,0 +1,247 @@ +#include "qxtglobalshortcut_p.h" +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** 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 name of the LibQxt project nor the +** names of its 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 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. +** +** +*****************************************************************************/ + +#include + + +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) +bool QxtGlobalShortcutPrivate::eventFilter(void* message) +{ +#else +bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray & eventType, + void * message, long * result) +{ + Q_UNUSED(eventType); + Q_UNUSED(result); +#endif + MSG* msg = static_cast(message); + if (msg->message == WM_HOTKEY) + { + const quint32 keycode = HIWORD(msg->lParam); + const quint32 modifiers = LOWORD(msg->lParam); + activateShortcut(keycode, modifiers); + } + return false; +} + + +quint32 QxtGlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifiers) +{ + // MOD_ALT, MOD_CONTROL, (MOD_KEYUP), MOD_SHIFT, MOD_WIN + quint32 native = 0; + if (modifiers & Qt::ShiftModifier) + native |= MOD_SHIFT; + if (modifiers & Qt::ControlModifier) + native |= MOD_CONTROL; + if (modifiers & Qt::AltModifier) + native |= MOD_ALT; + if (modifiers & Qt::MetaModifier) + native |= MOD_WIN; + // TODO: resolve these? + //if (modifiers & Qt::KeypadModifier) + //if (modifiers & Qt::GroupSwitchModifier) + return native; +} + +quint32 QxtGlobalShortcutPrivate::nativeKeycode(Qt::Key key) +{ + switch (key) + { + case Qt::Key_Escape: + return VK_ESCAPE; + case Qt::Key_Tab: + case Qt::Key_Backtab: + return VK_TAB; + case Qt::Key_Backspace: + return VK_BACK; + case Qt::Key_Return: + case Qt::Key_Enter: + return VK_RETURN; + case Qt::Key_Insert: + return VK_INSERT; + case Qt::Key_Delete: + return VK_DELETE; + case Qt::Key_Pause: + return VK_PAUSE; + case Qt::Key_Print: + return VK_PRINT; + case Qt::Key_Clear: + return VK_CLEAR; + case Qt::Key_Home: + return VK_HOME; + case Qt::Key_End: + return VK_END; + case Qt::Key_Left: + return VK_LEFT; + case Qt::Key_Up: + return VK_UP; + case Qt::Key_Right: + return VK_RIGHT; + case Qt::Key_Down: + return VK_DOWN; + case Qt::Key_PageUp: + return VK_PRIOR; + case Qt::Key_PageDown: + return VK_NEXT; + case Qt::Key_F1: + return VK_F1; + case Qt::Key_F2: + return VK_F2; + case Qt::Key_F3: + return VK_F3; + case Qt::Key_F4: + return VK_F4; + case Qt::Key_F5: + return VK_F5; + case Qt::Key_F6: + return VK_F6; + case Qt::Key_F7: + return VK_F7; + case Qt::Key_F8: + return VK_F8; + case Qt::Key_F9: + return VK_F9; + case Qt::Key_F10: + return VK_F10; + case Qt::Key_F11: + return VK_F11; + case Qt::Key_F12: + return VK_F12; + case Qt::Key_F13: + return VK_F13; + case Qt::Key_F14: + return VK_F14; + case Qt::Key_F15: + return VK_F15; + case Qt::Key_F16: + return VK_F16; + case Qt::Key_F17: + return VK_F17; + case Qt::Key_F18: + return VK_F18; + case Qt::Key_F19: + return VK_F19; + case Qt::Key_F20: + return VK_F20; + case Qt::Key_F21: + return VK_F21; + case Qt::Key_F22: + return VK_F22; + case Qt::Key_F23: + return VK_F23; + case Qt::Key_F24: + return VK_F24; + case Qt::Key_Space: + return VK_SPACE; + case Qt::Key_Asterisk: + return VK_MULTIPLY; + case Qt::Key_Plus: + return VK_ADD; + case Qt::Key_Comma: + return VK_SEPARATOR; + case Qt::Key_Minus: + return VK_SUBTRACT; + case Qt::Key_Slash: + return VK_DIVIDE; + case Qt::Key_MediaNext: + return VK_MEDIA_NEXT_TRACK; + case Qt::Key_MediaPrevious: + return VK_MEDIA_PREV_TRACK; + case Qt::Key_MediaPlay: + return VK_MEDIA_PLAY_PAUSE; + case Qt::Key_MediaStop: + return VK_MEDIA_STOP; + // couldn't find those in VK_* + //case Qt::Key_MediaLast: + //case Qt::Key_MediaRecord: + case Qt::Key_VolumeDown: + return VK_VOLUME_DOWN; + case Qt::Key_VolumeUp: + return VK_VOLUME_UP; + case Qt::Key_VolumeMute: + return VK_VOLUME_MUTE; + + // numbers + case Qt::Key_0: + case Qt::Key_1: + case Qt::Key_2: + case Qt::Key_3: + case Qt::Key_4: + case Qt::Key_5: + case Qt::Key_6: + case Qt::Key_7: + case Qt::Key_8: + case Qt::Key_9: + return key; + + // letters + case Qt::Key_A: + case Qt::Key_B: + case Qt::Key_C: + case Qt::Key_D: + case Qt::Key_E: + case Qt::Key_F: + case Qt::Key_G: + case Qt::Key_H: + case Qt::Key_I: + case Qt::Key_J: + case Qt::Key_K: + case Qt::Key_L: + case Qt::Key_M: + case Qt::Key_N: + case Qt::Key_O: + case Qt::Key_P: + case Qt::Key_Q: + case Qt::Key_R: + case Qt::Key_S: + case Qt::Key_T: + case Qt::Key_U: + case Qt::Key_V: + case Qt::Key_W: + case Qt::Key_X: + case Qt::Key_Y: + case Qt::Key_Z: + return key; + + default: + return 0; + } +} + +bool QxtGlobalShortcutPrivate::registerShortcut(quint32 nativeKey, quint32 nativeMods) +{ + return RegisterHotKey(0, nativeMods ^ nativeKey, nativeMods, nativeKey); +} + +bool QxtGlobalShortcutPrivate::unregisterShortcut(quint32 nativeKey, quint32 nativeMods) +{ + return UnregisterHotKey(0, nativeMods ^ nativeKey); +} diff --git a/3rdparty/qxt/qxtglobalshortcut_x11.cpp b/3rdparty/qxt/qxtglobalshortcut_x11.cpp new file mode 100644 index 00000000..9ddb1e08 --- /dev/null +++ b/3rdparty/qxt/qxtglobalshortcut_x11.cpp @@ -0,0 +1,254 @@ +#include "qxtglobalshortcut_p.h" +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** 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 name of the LibQxt project nor the +** names of its 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 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 QT_VERSION < QT_VERSION_CHECK(5,0,0) +# include +#else +# include +# include +# include +#endif +#include +#include +#include "keymapper_x11.h" + +namespace { + +const QVector maskModifiers = QVector() + << 0 << Mod2Mask << LockMask << (Mod2Mask | LockMask); + +typedef int (*X11ErrorHandler)(Display *display, XErrorEvent *event); + +class QxtX11ErrorHandler { +public: + static bool error; + + static int qxtX11ErrorHandler(Display *display, XErrorEvent *event) + { + Q_UNUSED(display); + switch (event->error_code) + { + case BadAccess: + case BadValue: + case BadWindow: + if (event->request_code == 33 /* X_GrabKey */ || + event->request_code == 34 /* X_UngrabKey */) + { + error = true; + //TODO: + //char errstr[256]; + //XGetErrorText(dpy, err->error_code, errstr, 256); + } + } + return 0; + } + + QxtX11ErrorHandler() + { + error = false; + m_previousErrorHandler = XSetErrorHandler(qxtX11ErrorHandler); + } + + ~QxtX11ErrorHandler() + { + XSetErrorHandler(m_previousErrorHandler); + } + +private: + X11ErrorHandler m_previousErrorHandler; +}; + +bool QxtX11ErrorHandler::error = false; + +class QxtX11Data { +public: + QxtX11Data() + { +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) + m_display = QX11Info::display(); +#else + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + void *display = native->nativeResourceForScreen(QByteArray("display"), + QGuiApplication::primaryScreen()); + m_display = reinterpret_cast(display); +#endif + } + + bool isValid() + { + return m_display != 0; + } + + Display *display() + { + Q_ASSERT(isValid()); + return m_display; + } + + Window rootWindow() + { + return DefaultRootWindow(display()); + } + + bool grabKey(quint32 keycode, quint32 modifiers, Window window) + { + QxtX11ErrorHandler errorHandler; + + for (int i = 0; !errorHandler.error && i < maskModifiers.size(); ++i) { + XGrabKey(display(), keycode, modifiers | maskModifiers[i], window, True, + GrabModeAsync, GrabModeAsync); + } + + if (errorHandler.error) { + ungrabKey(keycode, modifiers, window); + return false; + } + + return true; + } + + bool ungrabKey(quint32 keycode, quint32 modifiers, Window window) + { + QxtX11ErrorHandler errorHandler; + + foreach (quint32 maskMods, maskModifiers) { + XUngrabKey(display(), keycode, modifiers | maskMods, window); + } + + return !errorHandler.error; + } + +private: + Display *m_display; +}; + +} // namespace + +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) +bool QxtGlobalShortcutPrivate::eventFilter(void *message) +{ + XEvent *event = static_cast(message); + if (event->type == KeyPress) + { + XKeyEvent *key = reinterpret_cast(event); + unsigned int keycode = key->keycode; + unsigned int keystate = key->state; +#else +bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray & eventType, + void *message, long *result) +{ + Q_UNUSED(result); + + xcb_key_press_event_t *kev = 0; + if (eventType == "xcb_generic_event_t") { + xcb_generic_event_t *ev = static_cast(message); + if ((ev->response_type & 127) == XCB_KEY_PRESS) + kev = static_cast(message); + } + + if (kev != 0) { + unsigned int keycode = kev->detail; + unsigned int keystate = 0; + if(kev->state & XCB_MOD_MASK_1) + keystate |= Mod1Mask; + if(kev->state & XCB_MOD_MASK_CONTROL) + keystate |= ControlMask; + if(kev->state & XCB_MOD_MASK_4) + keystate |= Mod4Mask; + if(kev->state & XCB_MOD_MASK_SHIFT) + keystate |= ShiftMask; +#endif + activateShortcut(keycode, + // Mod1Mask == Alt, Mod4Mask == Meta + keystate & (ShiftMask | ControlMask | Mod1Mask | Mod4Mask)); + } + return false; +} + +quint32 QxtGlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifiers) +{ + // ShiftMask, LockMask, ControlMask, Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, and Mod5Mask + quint32 native = 0; + if (modifiers & Qt::ShiftModifier) + native |= ShiftMask; + if (modifiers & Qt::ControlModifier) + native |= ControlMask; + if (modifiers & Qt::AltModifier) + native |= Mod1Mask; + if (modifiers & Qt::MetaModifier) + native |= Mod4Mask; + + // TODO: resolve these? + //if (modifiers & Qt::MetaModifier) + //if (modifiers & Qt::KeypadModifier) + //if (modifiers & Qt::GroupSwitchModifier) + return native; +} + +quint32 QxtGlobalShortcutPrivate::nativeKeycode(Qt::Key key) +{ + // (davidsansome) Try the table from QKeyMapper first - this seems to be + // the only way to get Keysyms for the media keys. + unsigned int keysym = 0; + int i = 0; + while (KeyTbl[i]) { + if (KeyTbl[i+1] == static_cast(key)) { + keysym = KeyTbl[i]; + break; + } + i += 2; + } + + // If that didn't work then fall back on XStringToKeysym + if (!keysym) { + keysym = XStringToKeysym(QKeySequence(key).toString().toLatin1().data()); + if (keysym == NoSymbol) + keysym = static_cast(key); + } + + QxtX11Data x11; + if (!x11.isValid()) + return 0; + + return XKeysymToKeycode(x11.display(), keysym); +} + +bool QxtGlobalShortcutPrivate::registerShortcut(quint32 nativeKey, quint32 nativeMods) +{ + QxtX11Data x11; + return x11.isValid() && x11.grabKey(nativeKey, nativeMods, x11.rootWindow()); +} + +bool QxtGlobalShortcutPrivate::unregisterShortcut(quint32 nativeKey, quint32 nativeMods) +{ + QxtX11Data x11; + return x11.isValid() && x11.ungrabKey(nativeKey, nativeMods, x11.rootWindow()); +} diff --git a/3rdparty/sha2/CMakeLists.txt b/3rdparty/sha2/CMakeLists.txt new file mode 100644 index 00000000..6b86048d --- /dev/null +++ b/3rdparty/sha2/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 2.8.11) + +add_library(sha2 STATIC sha2.cpp) diff --git a/3rdparty/sha2/sha2.cpp b/3rdparty/sha2/sha2.cpp new file mode 100644 index 00000000..64848d52 --- /dev/null +++ b/3rdparty/sha2/sha2.cpp @@ -0,0 +1,588 @@ +/* + * FILE: sha2.c + * AUTHOR: Aaron D. Gifford - http://www.aarongifford.com/ + * + * Copyright (c) 2000-2001, Aaron D. Gifford + * All rights reserved. + * + * 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 above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of the copyright holder nor the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 OR CONTRIBUTOR(S) 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. + * + */ + +#include /* memcpy()/memset() or bcopy()/bzero() */ +#include /* assert() */ +#include "sha2.h" + +/* + * ASSERT NOTE: + * Some sanity checking code is included using assert(). On my FreeBSD + * system, this additional code can be removed by compiling with NDEBUG + * defined. Check your own systems manpage on assert() to see how to + * compile WITHOUT the sanity checking code on your system. + * + * UNROLLED TRANSFORM LOOP NOTE: + * You can define SHA2_UNROLL_TRANSFORM to use the unrolled transform + * loop version for the hash transform rounds (defined using macros + * later in this file). Either define on the command line, for example: + * + * cc -DSHA2_UNROLL_TRANSFORM -o sha2 sha2.c sha2prog.c + * + * or define below: + * + * #define SHA2_UNROLL_TRANSFORM + * + */ + + +/*** SHA-256/384/512 Machine Architecture Definitions *****************/ +/* + * BYTE_ORDER NOTE: + * + * Please make sure that your system defines BYTE_ORDER. If your + * architecture is little-endian, make sure it also defines + * LITTLE_ENDIAN and that the two (BYTE_ORDER and LITTLE_ENDIAN) are + * equivilent. + * + * If your system does not define the above, then you can do so by + * hand like this: + * + * #define LITTLE_ENDIAN 1234 + * #define BIG_ENDIAN 4321 + * + * And for little-endian machines, add: + * + * #define BYTE_ORDER LITTLE_ENDIAN + * + * Or for big-endian machines: + * + * #define BYTE_ORDER BIG_ENDIAN + * + * The FreeBSD machine this was written on defines BYTE_ORDER + * appropriately by including (which in turn includes + * where the appropriate definitions are actually + * made). + */ +#ifdef __MINGW32__ +#include +#endif + +#if !defined(BYTE_ORDER) || (BYTE_ORDER != LITTLE_ENDIAN && BYTE_ORDER != BIG_ENDIAN) +#error Define BYTE_ORDER to be equal to either LITTLE_ENDIAN or BIG_ENDIAN +#endif + +namespace strawberry_sha2 { + +/* + * Define the followingsha2_* types to types of the correct length on + * the native archtecture. Most BSD systems and Linux define u_intXX_t + * types. Machines with very recent ANSI C headers, can use the + * uintXX_t definintions from inttypes.h by defining SHA2_USE_INTTYPES_H + * during compile or in the sha.h header file. + * + * Machines that support neither u_intXX_t nor inttypes.h's uintXX_t + * will need to define these three typedefs below (and the appropriate + * ones in sha.h too) by hand according to their system architecture. + * + * Thank you, Jun-ichiro itojun Hagino, for suggesting using u_intXX_t + * types and pointing out recent ANSI C support for uintXX_t in inttypes.h. + */ +#ifdef SHA2_USE_INTTYPES_H + +typedef uint8_t sha2_byte; /* Exactly 1 byte */ +typedef uint32_t sha2_word32; /* Exactly 4 bytes */ +typedef uint64_t sha2_word64; /* Exactly 8 bytes */ + +#else /* SHA2_USE_INTTYPES_H */ + +typedef u_int8_t sha2_byte; /* Exactly 1 byte */ +typedef u_int32_t sha2_word32; /* Exactly 4 bytes */ +typedef u_int64_t sha2_word64; /* Exactly 8 bytes */ + +#endif /* SHA2_USE_INTTYPES_H */ + + +/*** SHA-256/384/512 Various Length Definitions ***********************/ +/* NOTE: Most of these are in sha2.h */ +#define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8) + + +/*** ENDIAN REVERSAL MACROS *******************************************/ +#if BYTE_ORDER == LITTLE_ENDIAN +#define REVERSE32(w,x) { \ + sha2_word32 tmp = (w); \ + tmp = (tmp >> 16) | (tmp << 16); \ + (x) = ((tmp & 0xff00ff00UL) >> 8) | ((tmp & 0x00ff00ffUL) << 8); \ +} +#define REVERSE64(w,x) { \ + sha2_word64 tmp = (w); \ + tmp = (tmp >> 32) | (tmp << 32); \ + tmp = ((tmp & 0xff00ff00ff00ff00ULL) >> 8) | \ + ((tmp & 0x00ff00ff00ff00ffULL) << 8); \ + (x) = ((tmp & 0xffff0000ffff0000ULL) >> 16) | \ + ((tmp & 0x0000ffff0000ffffULL) << 16); \ +} +#endif /* BYTE_ORDER == LITTLE_ENDIAN */ + +/* + * Macro for incrementally adding the unsigned 64-bit integer n to the + * unsigned 128-bit integer (represented using a two-element array of + * 64-bit words): + */ +#define ADDINC128(w,n) { \ + (w)[0] += (sha2_word64)(n); \ + if ((w)[0] < (n)) { \ + (w)[1]++; \ + } \ +} + +/* + * Macros for copying blocks of memory and for zeroing out ranges + * of memory. Using these macros makes it easy to switch from + * using memset()/memcpy() and using bzero()/bcopy(). + * + * Please define either SHA2_USE_MEMSET_MEMCPY or define + * SHA2_USE_BZERO_BCOPY depending on which function set you + * choose to use: + */ +#if !defined(SHA2_USE_MEMSET_MEMCPY) && !defined(SHA2_USE_BZERO_BCOPY) +/* Default to memset()/memcpy() if no option is specified */ +#define SHA2_USE_MEMSET_MEMCPY 1 +#endif +#if defined(SHA2_USE_MEMSET_MEMCPY) && defined(SHA2_USE_BZERO_BCOPY) +/* Abort with an error if BOTH options are defined */ +#error Define either SHA2_USE_MEMSET_MEMCPY or SHA2_USE_BZERO_BCOPY, not both! +#endif + +#ifdef SHA2_USE_MEMSET_MEMCPY +#define MEMSET_BZERO(p,l) memset((p), 0, (l)) +#define MEMCPY_BCOPY(d,s,l) memcpy((d), (s), (l)) +#endif +#ifdef SHA2_USE_BZERO_BCOPY +#define MEMSET_BZERO(p,l) bzero((p), (l)) +#define MEMCPY_BCOPY(d,s,l) bcopy((s), (d), (l)) +#endif + + +/*** THE SIX LOGICAL FUNCTIONS ****************************************/ +/* + * Bit shifting and rotation (used by the six SHA-XYZ logical functions: + * + * NOTE: The naming of R and S appears backwards here (R is a SHIFT and + * S is a ROTATION) because the SHA-256/384/512 description document + * (see http://csrc.nist.gov/cryptval/shs/sha256-384-512.pdf) uses this + * same "backwards" definition. + */ +/* Shift-right (used in SHA-256, SHA-384, and SHA-512): */ +#define R(b,x) ((x) >> (b)) +/* 32-bit Rotate-right (used in SHA-256): */ +#define S32(b,x) (((x) >> (b)) | ((x) << (32 - (b)))) + +/* Two of six logical functions used in SHA-256, SHA-384, and SHA-512: */ +#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z))) +#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) + +/* Four of six logical functions used in SHA-256: */ +#define Sigma0_256(x) (S32(2, (x)) ^ S32(13, (x)) ^ S32(22, (x))) +#define Sigma1_256(x) (S32(6, (x)) ^ S32(11, (x)) ^ S32(25, (x))) +#define sigma0_256(x) (S32(7, (x)) ^ S32(18, (x)) ^ R(3 , (x))) +#define sigma1_256(x) (S32(17, (x)) ^ S32(19, (x)) ^ R(10, (x))) + +/*** INTERNAL FUNCTION PROTOTYPES *************************************/ +/* NOTE: These should not be accessed directly from outside this + * library -- they are intended for private internal visibility/use + * only. + */ +void SHA256_Transform(SHA256_CTX*, const sha2_word32*); + + +/*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/ +/* Hash constant words K for SHA-256: */ +const static sha2_word32 K256[64] = { + 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, + 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, + 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, + 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, + 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, + 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, + 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, + 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL, + 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, + 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, + 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, + 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, + 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, + 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL, + 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, + 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL +}; + +/* Initial hash value H for SHA-256: */ +const static sha2_word32 sha256_initial_hash_value[8] = { + 0x6a09e667UL, + 0xbb67ae85UL, + 0x3c6ef372UL, + 0xa54ff53aUL, + 0x510e527fUL, + 0x9b05688cUL, + 0x1f83d9abUL, + 0x5be0cd19UL +}; + + +/* + * Constant used by SHA256/384/512_End() functions for converting the + * digest to a readable hexadecimal character string: + */ +static const char *sha2_hex_digits = "0123456789abcdef"; + + +/*** SHA-256: *********************************************************/ +void SHA256_Init(SHA256_CTX* context) { + if (context == (SHA256_CTX*)0) { + return; + } + MEMCPY_BCOPY(context->state, sha256_initial_hash_value, SHA256_DIGEST_LENGTH); + MEMSET_BZERO(context->buffer, SHA256_BLOCK_LENGTH); + context->bitcount = 0; +} + +#ifdef SHA2_UNROLL_TRANSFORM + +/* Unrolled SHA-256 round macros: */ + +#if BYTE_ORDER == LITTLE_ENDIAN + +#define ROUND256_0_TO_15(a,b,c,d,e,f,g,h) \ + REVERSE32(*data++, W256[j]); \ + T1 = (h) + Sigma1_256(e) + Ch((e), (f), (g)) + \ + K256[j] + W256[j]; \ + (d) += T1; \ + (h) = T1 + Sigma0_256(a) + Maj((a), (b), (c)); \ + j++ + + +#else /* BYTE_ORDER == LITTLE_ENDIAN */ + +#define ROUND256_0_TO_15(a,b,c,d,e,f,g,h) \ + T1 = (h) + Sigma1_256(e) + Ch((e), (f), (g)) + \ + K256[j] + (W256[j] = *data++); \ + (d) += T1; \ + (h) = T1 + Sigma0_256(a) + Maj((a), (b), (c)); \ + j++ + +#endif /* BYTE_ORDER == LITTLE_ENDIAN */ + +#define ROUND256(a,b,c,d,e,f,g,h) \ + s0 = W256[(j+1)&0x0f]; \ + s0 = sigma0_256(s0); \ + s1 = W256[(j+14)&0x0f]; \ + s1 = sigma1_256(s1); \ + T1 = (h) + Sigma1_256(e) + Ch((e), (f), (g)) + K256[j] + \ + (W256[j&0x0f] += s1 + W256[(j+9)&0x0f] + s0); \ + (d) += T1; \ + (h) = T1 + Sigma0_256(a) + Maj((a), (b), (c)); \ + j++ + +void SHA256_Transform(SHA256_CTX* context, const sha2_word32* data) { + sha2_word32 a, b, c, d, e, f, g, h, s0, s1; + sha2_word32 T1, *W256; + int j; + + W256 = (sha2_word32*)context->buffer; + + /* Initialize registers with the prev. intermediate value */ + a = context->state[0]; + b = context->state[1]; + c = context->state[2]; + d = context->state[3]; + e = context->state[4]; + f = context->state[5]; + g = context->state[6]; + h = context->state[7]; + + j = 0; + do { + /* Rounds 0 to 15 (unrolled): */ + ROUND256_0_TO_15(a,b,c,d,e,f,g,h); + ROUND256_0_TO_15(h,a,b,c,d,e,f,g); + ROUND256_0_TO_15(g,h,a,b,c,d,e,f); + ROUND256_0_TO_15(f,g,h,a,b,c,d,e); + ROUND256_0_TO_15(e,f,g,h,a,b,c,d); + ROUND256_0_TO_15(d,e,f,g,h,a,b,c); + ROUND256_0_TO_15(c,d,e,f,g,h,a,b); + ROUND256_0_TO_15(b,c,d,e,f,g,h,a); + } while (j < 16); + + /* Now for the remaining rounds to 64: */ + do { + ROUND256(a,b,c,d,e,f,g,h); + ROUND256(h,a,b,c,d,e,f,g); + ROUND256(g,h,a,b,c,d,e,f); + ROUND256(f,g,h,a,b,c,d,e); + ROUND256(e,f,g,h,a,b,c,d); + ROUND256(d,e,f,g,h,a,b,c); + ROUND256(c,d,e,f,g,h,a,b); + ROUND256(b,c,d,e,f,g,h,a); + } while (j < 64); + + /* Compute the current intermediate hash value */ + context->state[0] += a; + context->state[1] += b; + context->state[2] += c; + context->state[3] += d; + context->state[4] += e; + context->state[5] += f; + context->state[6] += g; + context->state[7] += h; + + /* Clean up */ + a = b = c = d = e = f = g = h = T1 = 0; +} + +#else /* SHA2_UNROLL_TRANSFORM */ + +void SHA256_Transform(SHA256_CTX* context, const sha2_word32* data) { + sha2_word32 a, b, c, d, e, f, g, h, s0, s1; + sha2_word32 T1, T2, *W256; + int j; + + W256 = (sha2_word32*)context->buffer; + + /* Initialize registers with the prev. intermediate value */ + a = context->state[0]; + b = context->state[1]; + c = context->state[2]; + d = context->state[3]; + e = context->state[4]; + f = context->state[5]; + g = context->state[6]; + h = context->state[7]; + + j = 0; + do { +#if BYTE_ORDER == LITTLE_ENDIAN + /* Copy data while converting to host byte order */ + REVERSE32(*data++,W256[j]); + /* Apply the SHA-256 compression function to update a..h */ + T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[j] + W256[j]; +#else /* BYTE_ORDER == LITTLE_ENDIAN */ + /* Apply the SHA-256 compression function to update a..h with copy */ + T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[j] + (W256[j] = *data++); +#endif /* BYTE_ORDER == LITTLE_ENDIAN */ + T2 = Sigma0_256(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + + j++; + } while (j < 16); + + do { + /* Part of the message block expansion: */ + s0 = W256[(j+1)&0x0f]; + s0 = sigma0_256(s0); + s1 = W256[(j+14)&0x0f]; + s1 = sigma1_256(s1); + + /* Apply the SHA-256 compression function to update a..h */ + T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[j] + + (W256[j&0x0f] += s1 + W256[(j+9)&0x0f] + s0); + T2 = Sigma0_256(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + + j++; + } while (j < 64); + + /* Compute the current intermediate hash value */ + context->state[0] += a; + context->state[1] += b; + context->state[2] += c; + context->state[3] += d; + context->state[4] += e; + context->state[5] += f; + context->state[6] += g; + context->state[7] += h; + + /* Clean up */ + a = b = c = d = e = f = g = h = T1 = T2 = 0; +} + +#endif /* SHA2_UNROLL_TRANSFORM */ + +void SHA256_Update(SHA256_CTX* context, const sha2_byte *data, size_t len) { + unsigned int freespace, usedspace; + + if (len == 0) { + /* Calling with no data is valid - we do nothing */ + return; + } + + /* Sanity check: */ + assert(context != (SHA256_CTX*)0 && data != (sha2_byte*)0); + + usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH; + if (usedspace > 0) { + /* Calculate how much free space is available in the buffer */ + freespace = SHA256_BLOCK_LENGTH - usedspace; + + if (len >= freespace) { + /* Fill the buffer completely and process it */ + MEMCPY_BCOPY(&context->buffer[usedspace], data, freespace); + context->bitcount += freespace << 3; + len -= freespace; + data += freespace; + SHA256_Transform(context, (sha2_word32*)context->buffer); + } else { + /* The buffer is not yet full */ + MEMCPY_BCOPY(&context->buffer[usedspace], data, len); + context->bitcount += len << 3; + /* Clean up: */ + usedspace = freespace = 0; + return; + } + } + while (len >= SHA256_BLOCK_LENGTH) { + /* Process as many complete blocks as we can */ + SHA256_Transform(context, (sha2_word32*)data); + context->bitcount += SHA256_BLOCK_LENGTH << 3; + len -= SHA256_BLOCK_LENGTH; + data += SHA256_BLOCK_LENGTH; + } + if (len > 0) { + /* There's left-overs, so save 'em */ + MEMCPY_BCOPY(context->buffer, data, len); + context->bitcount += len << 3; + } + /* Clean up: */ + usedspace = freespace = 0; +} + +void SHA256_Final(sha2_byte digest[], SHA256_CTX* context) { + sha2_word32 *d = (sha2_word32*)digest; + unsigned int usedspace; + + /* Sanity check: */ + assert(context != (SHA256_CTX*)0); + + /* If no digest buffer is passed, we don't bother doing this: */ + if (digest != (sha2_byte*)0) { + usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH; +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert FROM host byte order */ + REVERSE64(context->bitcount,context->bitcount); +#endif + if (usedspace > 0) { + /* Begin padding with a 1 bit: */ + context->buffer[usedspace++] = 0x80; + + if (usedspace <= SHA256_SHORT_BLOCK_LENGTH) { + /* Set-up for the last transform: */ + MEMSET_BZERO(&context->buffer[usedspace], SHA256_SHORT_BLOCK_LENGTH - usedspace); + } else { + if (usedspace < SHA256_BLOCK_LENGTH) { + MEMSET_BZERO(&context->buffer[usedspace], SHA256_BLOCK_LENGTH - usedspace); + } + /* Do second-to-last transform: */ + SHA256_Transform(context, (sha2_word32*)context->buffer); + + /* And set-up for the last transform: */ + MEMSET_BZERO(context->buffer, SHA256_SHORT_BLOCK_LENGTH); + } + } else { + /* Set-up for the last transform: */ + MEMSET_BZERO(context->buffer, SHA256_SHORT_BLOCK_LENGTH); + + /* Begin padding with a 1 bit: */ + *context->buffer = 0x80; + } + /* Set the bit count: */ + *(sha2_word64*)&context->buffer[SHA256_SHORT_BLOCK_LENGTH] = context->bitcount; + + /* Final transform: */ + SHA256_Transform(context, (sha2_word32*)context->buffer); + +#if BYTE_ORDER == LITTLE_ENDIAN + { + /* Convert TO host byte order */ + int j; + for (j = 0; j < 8; j++) { + REVERSE32(context->state[j],context->state[j]); + *d++ = context->state[j]; + } + } +#else + MEMCPY_BCOPY(d, context->state, SHA256_DIGEST_LENGTH); +#endif + } + + /* Clean up state data: */ + MEMSET_BZERO(context, sizeof(SHA256_CTX)); + usedspace = 0; +} + +char *SHA256_End(SHA256_CTX* context, char buffer[]) { + sha2_byte digest[SHA256_DIGEST_LENGTH], *d = digest; + int i; + + /* Sanity check: */ + assert(context != (SHA256_CTX*)0); + + if (buffer != (char*)0) { + SHA256_Final(digest, context); + + for (i = 0; i < SHA256_DIGEST_LENGTH; i++) { + *buffer++ = sha2_hex_digits[(*d & 0xf0) >> 4]; + *buffer++ = sha2_hex_digits[*d & 0x0f]; + d++; + } + *buffer = (char)0; + } else { + MEMSET_BZERO(context, sizeof(SHA256_CTX)); + } + MEMSET_BZERO(digest, SHA256_DIGEST_LENGTH); + return buffer; +} + +char* SHA256_Data(const sha2_byte* data, size_t len, char digest[SHA256_DIGEST_STRING_LENGTH]) { + SHA256_CTX context; + + SHA256_Init(&context); + SHA256_Update(&context, data, len); + return SHA256_End(&context, digest); +} + +} // namespace strawberry_sha2 diff --git a/3rdparty/sha2/sha2.h b/3rdparty/sha2/sha2.h new file mode 100644 index 00000000..ae266be2 --- /dev/null +++ b/3rdparty/sha2/sha2.h @@ -0,0 +1,79 @@ +/* + * FILE: sha2.h + * AUTHOR: Aaron D. Gifford - http://www.aarongifford.com/ + * + * Copyright (c) 2000-2001, Aaron D. Gifford + * All rights reserved. + * + * 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 above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of the copyright holder nor the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 OR CONTRIBUTOR(S) 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. + * + * $Id: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $ + */ + +#ifndef __STRAWBERRY_SHA2_H__ +#define __STRAWBERRY_SHA2_H__ + +/* + * Import u_intXX_t size_t type definitions from system headers. You + * may need to change this, or define these things yourself in this + * file. + */ +#include + +namespace strawberry_sha2 { + +/*** SHA-256/384/512 Various Length Definitions ***********************/ +static const int SHA256_BLOCK_LENGTH = 64; +static const int SHA256_DIGEST_LENGTH = 32; +static const int SHA256_DIGEST_STRING_LENGTH = (SHA256_DIGEST_LENGTH * 2 + 1); + + +/*** SHA-256/384/512 Context Structures *******************************/ +/* NOTE: If your architecture does not define either u_intXX_t types or + * uintXX_t (from inttypes.h), you may need to define things by hand + * for your system: + */ +#ifdef __MINGW32__ +typedef unsigned char u_int8_t; /* 1-byte (8-bits) */ +typedef unsigned int u_int32_t; /* 4-bytes (32-bits) */ +typedef unsigned long long u_int64_t; /* 8-bytes (64-bits) */ +#endif + +typedef struct _SHA256_CTX { + u_int32_t state[8]; + u_int64_t bitcount; + u_int8_t buffer[SHA256_BLOCK_LENGTH]; +} SHA256_CTX; + + +void SHA256_Init(SHA256_CTX *); +void SHA256_Update(SHA256_CTX*, const u_int8_t*, size_t); +void SHA256_Final(u_int8_t[SHA256_DIGEST_LENGTH], SHA256_CTX*); +char* SHA256_End(SHA256_CTX*, char[SHA256_DIGEST_STRING_LENGTH]); +char* SHA256_Data(const u_int8_t*, size_t, char[SHA256_DIGEST_STRING_LENGTH]); + +} // namespace strawberry_sha2 + +#endif /* __STRAWBERRY_SHA2_H__ */ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..b063c389 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,402 @@ +# Strawberry Music Player +# Copyright 2013, Jonas Kvinge +# This file was part of Clementine. +# Copyright 2010, David Sansome +# +# Strawberry is free software: you can 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. +# +# Strawberry is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Strawberry. If not, see . + +project(strawberry) +cmake_minimum_required(VERSION 2.8.11) +cmake_policy(SET CMP0011 OLD) + +#aux_source_directory(. SRC_LIST) + +include(CheckCXXCompilerFlag) +include(CheckIncludeFiles) +include(FindPkgConfig) +include(cmake/C++11Compat.cmake) +include(cmake/Summary.cmake) +include(cmake/Version.cmake) +include(cmake/Rpm.cmake) +include(cmake/OptionalSource.cmake) +include(cmake/Format.cmake) + +#set(CMAKE_BUILD_TYPE Debug) +set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) + +if (CMAKE_CXX_COMPILER MATCHES ".*clang") + set(CMAKE_COMPILER_IS_CLANGXX 1) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-uninitialized") +endif () + +if (APPLE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --stdlib=libc++") +endif () + +find_program(CCACHE_EXECUTABLE NAMES ccache) +if (CCACHE_EXECUTABLE) + message(STATUS "ccache found: will be used for compilation and linkage") + SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_EXECUTABLE}) + SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_EXECUTABLE}) +endif () + +if (UNIX AND NOT APPLE) + set(LINUX 1) +endif (UNIX AND NOT APPLE) + +set(QT_MIN_VERSION 5.6.0) + +find_package(X11) +find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Core OpenGL Sql Network Xml Widgets Concurrent Test) + +if(X11_FOUND) + find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS X11Extras) +endif() + +if(NOT APPLE) + find_package(Qt5 COMPONENTS WebKitWidgets) +endif(NOT APPLE) + +find_program(QT_LCONVERT_EXECUTABLE NAMES lconvert lconvert-qt5 PATHS ${QT_BINARY_DIR} NO_DEFAULT_PATH) +find_program(QT_LCONVERT_EXECUTABLE NAMES lconvert lconvert-qt5) + +if(APPLE) + if(NOT QT_MAC_USE_COCOA) + message(FATAL_ERROR "Cocoa support is required") + endif(NOT QT_MAC_USE_COCOA) +endif(APPLE) + +find_package(OpenGL REQUIRED) +find_package(Boost REQUIRED) +find_package(Gettext REQUIRED) +find_package(PkgConfig REQUIRED) +find_package(Protobuf REQUIRED) +find_package(FFTW3) +find_package(Threads) +if(LINUX) + find_package(ALSA REQUIRED) +endif(LINUX) + +pkg_check_modules(GLIB REQUIRED glib-2.0) +pkg_check_modules(GIO REQUIRED gio-2.0) +pkg_check_modules(GOBJECT REQUIRED gobject-2.0) +pkg_check_modules(CDIO libcdio) +pkg_check_modules(CHROMAPRINT REQUIRED libchromaprint) +pkg_check_modules(GSTREAMER gstreamer-1.0) +pkg_check_modules(GSTREAMER_BASE gstreamer-base-1.0) +pkg_check_modules(GSTREAMER_APP gstreamer-app-1.0) +pkg_check_modules(GSTREAMER_AUDIO gstreamer-audio-1.0) +pkg_check_modules(GSTREAMER_TAG gstreamer-tag-1.0) +pkg_check_modules(GSTREAMER_PBUTILS gstreamer-pbutils-1.0) +pkg_check_modules(LIBXINE libxine) +pkg_check_modules(LIBVLC libvlc) +pkg_check_modules(PHONON phonon4qt5) +pkg_check_modules(LIBGPOD libgpod-1.0>=0.7.92) +pkg_check_modules(LIBMTP libmtp>=1.0) +pkg_check_modules(LIBPULSE libpulse) +pkg_check_modules(LIBXML libxml-2.0) +#pkg_check_modules(QCA qca2) +pkg_check_modules(TAGLIB REQUIRED taglib>=1.8) +pkg_check_modules(SQLITE REQUIRED sqlite3>=3.7) +#pkg_check_modules(QJSON qjson-qt5) +find_library(PROTOBUF_STATIC_LIBRARY libprotobuf.a libprotobuf) + +#find_library(QJSON_LIBRARIES qjson-qt5) + +if (WIN32) + find_package(ZLIB REQUIRED) + find_library(QTSPARKLE_LIBRARIES qtsparkle-qt5) +endif (WIN32) + +# LASTFM +find_library(LASTFM5_LIBRARIES lastfm5) +find_path(LASTFM5_INCLUDE_DIRS lastfm5/ws.h) +find_path(LASTFM51_INCLUDE_DIRS lastfm5/Track.h) +#CHECK_INCLUDE_FILES(lastfm/Track.h HAVE_LASTFM_TRACK_H) + +# GSTREAMER +CHECK_INCLUDE_FILES(gst/audio/gstaudiocdsrc.h GST_AUDIO_GSTAUDIOCDSRC_H) + +# TAGLIB +set(CMAKE_REQUIRED_INCLUDES "${TAGLIB_INCLUDE_DIRS}") +set(CMAKE_REQUIRED_LIBRARIES "${TAGLIB_LIBRARIES}") +check_cxx_source_compiles("#include + int main() { char *s; TagLib::Ogg::Opus::File opusfile(s); return 0;}" TAGLIB_HAS_OPUS) +set(CMAKE_REQUIRED_INCLUDES) +set(CMAKE_REQUIRED_LIBRARIES) +CHECK_INCLUDE_FILES(taglib/xiphcomment.h HAVE_XIPCOMMENT_H) + +# LASTFM +if(LASTFM5_INCLUDE_DIRS AND LASTFM51_INCLUDE_DIRS) + set(HAVE_LIBLASTFM1 ON) +endif() + +# QJSON +CHECK_INCLUDE_FILES(qjson/parser.h QJSON_PARSER_H) + +# CHROMAPRINT +CHECK_INCLUDE_FILES(chromaprint.h CHROMAPRINT_H) + +if (APPLE) + find_library(SPARKLE Sparkle) + add_subdirectory(3rdparty/SPMediaKeyTap) + set(SPMEDIAKEYTAP_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/SPMediaKeyTap) + set(SPMEDIAKEYTAP_LIBRARIES SPMediaKeyTap) +endif (APPLE) + +if(${CMAKE_BUILD_TYPE} MATCHES "Release") + add_definitions(-DNDEBUG) + add_definitions(-DQT_NO_DEBUG_OUTPUT) + #add_definitions(-DQT_NO_WARNING_OUTPUT) +endif(${CMAKE_BUILD_TYPE} MATCHES "Release") + +# Set up definitions and paths +add_definitions(${QT_DEFINITIONS}) +link_directories(${TAGLIB_LIBRARY_DIRS}) +link_directories(${QJSON_LIBRARY_DIRS}) +link_directories(${GSTREAMER_LIBRARY_DIRS}) + +# Don't try to use webkit if their include directories couldn't be found. +if (NOT QT_QTWEBKIT_INCLUDE_DIR) + set (QT_USE_QTWEBKIT 0) +endif (NOT QT_QTWEBKIT_INCLUDE_DIR) + +include_directories(${GLIB_INCLUDE_DIRS}) +include_directories(${GLIBCONFIG_INCLUDE_DIRS}) +include_directories(${Boost_INCLUDE_DIRS}) +include_directories(${LIBXML_INCLUDE_DIRS}) +include_directories(${TAGLIB_INCLUDE_DIRS}) +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(${GSTREAMER_APP_INCLUDE_DIRS}) +include_directories(${GSTREAMER_AUDIO_INCLUDE_DIRS}) +include_directories(${GSTREAMER_BASE_INCLUDE_DIRS}) +include_directories(${GSTREAMER_TAG_INCLUDE_DIRS}) +include_directories(${GSTREAMER_PBUTILS_INCLUDE_DIRS}) +#include_directories(${QJSON_INCLUDE_DIRS}) + +if (WIN32) + # RC compiler + string(REPLACE "gcc" "windres" CMAKE_RC_COMPILER_INIT ${CMAKE_C_COMPILER}) + enable_language(RC) + SET(CMAKE_RC_COMPILE_OBJECT " -O coff -o -I ${CMAKE_SOURCE_DIR}/dist/windows") +endif(WIN32) + +add_definitions(-DQT_NO_CAST_TO_ASCII -DQT_STRICT_ITERATORS) + +# Translations stuff +find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext PATHS /target/bin) +if(NOT GETTEXT_XGETTEXT_EXECUTABLE) + message(FATAL_ERROR "Could not find xgettext executable") +endif(NOT GETTEXT_XGETTEXT_EXECUTABLE) +find_program(GETTEXT_MSGMERGE_EXECUTABLE msgmerge PATHS /target/bin) +if(NOT GETTEXT_MSGMERGE_EXECUTABLE) + message(FATAL_ERROR "Could not find msgmerge executable") +endif(NOT GETTEXT_MSGMERGE_EXECUTABLE) +find_program(GETTEXT_MSGFMT_EXECUTABLE msgfmt PATHS /target/bin) +if(NOT GETTEXT_MSGFMT_EXECUTABLE) + message(FATAL_ERROR "Could not find msgfmt executable") +endif(NOT GETTEXT_MSGFMT_EXECUTABLE) + +# Optional bits +if(WIN32) + option(ENABLE_WIN32_CONSOLE "Show the windows console even outside Debug mode" OFF) +endif(WIN32) + +optional_component(AUDIOCD ON "Devices: Audio CD support" + DEPENDS "libcdio" CDIO_FOUND +) + +optional_component(LIBGPOD ON "Devices: iPod classic support" + DEPENDS "libgpod" LIBGPOD_FOUND +) + +optional_component(GIO ON "Devices: GIO device backend" + DEPENDS "libgio" GIO_FOUND + DEPENDS "Linux or Windows" "NOT APPLE" +) + +optional_component(IMOBILEDEVICE ON "Devices: iPod Touch, iPhone, iPad support" + DEPENDS "libimobiledevice" IMOBILEDEVICE_FOUND + DEPENDS "libplist" PLIST_FOUND + DEPENDS "libusbmuxd" USBMUXD_FOUND + DEPENDS "iPod classic support" HAVE_LIBGPOD +) + +optional_component(LIBMTP ON "Devices: MTP support" + DEPENDS "libmtp" LIBMTP_FOUND +) + +optional_component(LIBLASTFM ON "Last.fm support" + DEPENDS "liblastfm" LASTFM5_LIBRARIES LASTFM5_INCLUDE_DIRS +) + +optional_component(DBUS ON "D-Bus support" + DEPENDS "Linux" LINUX +) + +optional_component(DEVICEKIT ON "Devices: DeviceKit backend" + DEPENDS "D-Bus support" HAVE_DBUS +) + +optional_component(UDISKS2 ON "Devices: UDisks2 backend" + DEPENDS "D-Bus support" HAVE_DBUS +) + +optional_component(SPARKLE ON "Sparkle integration" + DEPENDS "Mac OS X" APPLE + DEPENDS "Sparkle" SPARKLE +) + +optional_component(LIBPULSE ON "Pulse audio integration" + DEPENDS "libpulse" LIBPULSE_FOUND +) + + +# Find DBus if it's enabled +if (HAVE_DBUS) + find_package(Qt5 COMPONENTS DBus) + get_target_property(QT_DBUSXML2CPP_EXECUTABLE Qt5::qdbusxml2cpp LOCATION) +endif () + +# We can include the Qt definitions now +#include(${QT_USE_FILE}) +if(WIN32) + set(QT_LIBRARIES Qt5::Core Qt5::OpenGL Qt5::Sql Qt5::Network Qt5::Xml Qt5::Widgets Qt5::Concurrent) +else(WIN32) + set(QT_LIBRARIES Qt5::Core Qt5::OpenGL Qt5::Sql Qt5::Network Qt5::Xml Qt5::Widgets Qt5::Concurrent Qt5::X11Extras Qt5::DBus) +endif(WIN32) + +# Remove GLU and GL from the link line - they're not really required +# and don't exist on my mingw toolchain +list(REMOVE_ITEM QT_LIBRARIES "-lGLU -lGL") + +# SQLITE +#find_path(SQLITE_INCLUDE_DIRS sqlite.h) +#find_library(SQLITE_LIBRARIES sqlite) +#if(SQLITE_LIBRARIES AND SQLITE_INCLUDE_DIRS) +# message(STATUS "Using system sqlite library") +# set(USE_SYSTEM_SQLITE ON) +#endif () + +# Build our copy of QSqlLiteDriver. +# We do this because we can't guarantee that the driver shipped with Qt exposes the +# raw sqlite3_ functions required for FTS support. This way we know that those symbols +# exist at compile-time and that our code links to the same sqlite library as the +# Qt driver. +add_subdirectory(3rdparty/qsqlite) +include_directories("3rdparty/qsqlite") + +# When/if upstream accepts our patches then these options can be used to link +# to system installed qtsingleapplication instead. +option(USE_SYSTEM_QTSINGLEAPPLICATION "Don't set this option unless your system QtSingleApplication library has been compiled with the Strawberry patches in 3rdparty" OFF) +if(USE_SYSTEM_QTSINGLEAPPLICATION) + find_path(QTSINGLEAPPLICATION_INCLUDE_DIRS qtsingleapplication.h PATH_SUFFIXES QtSolutions) + find_library(QTSINGLEAPPLICATION_LIBRARIES QtSolutions_SingleApplication-2.6) + find_library(QTSINGLECOREAPPLICATION_LIBRARIES QtSolutions_SingleCoreApplication-2.6) +else(USE_SYSTEM_QTSINGLEAPPLICATION) + add_subdirectory(3rdparty/qtsingleapplication) + set(QTSINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/qtsingleapplication) + set(QTSINGLEAPPLICATION_LIBRARIES qtsingleapplication) +endif(USE_SYSTEM_QTSINGLEAPPLICATION) + +# QtIoCompressor isn't patched, so we can use a system version if it's available +#find_path(QTIOCOMPRESSOR_INCLUDE_DIRS qtiocompressor.h PATH_SUFFIXES QtSolutions) +#find_library(QTIOCOMPRESSOR_LIBRARIES QtSolutions_IOCompressor-2.3) +#if(NOT QTIOCOMPRESSOR_INCLUDE_DIRS OR NOT QTIOCOMPRESSOR_LIBRARIES) +# add_subdirectory(3rdparty/qtiocompressor) +# set(QTIOCOMPRESSOR_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/qtiocompressor) +# set(QTIOCOMPRESSOR_LIBRARIES qtiocompressor) +#endif(NOT QTIOCOMPRESSOR_INCLUDE_DIRS OR NOT QTIOCOMPRESSOR_LIBRARIES) + +# When/if upstream accepts our or reimplement our patches then these options can be +# used to link to system installed qxt instead. +option(USE_SYSTEM_QXT "Don't set this option unless your system Qxt library has been compiled with the Strawberry patches in 3rdparty" OFF) +if (USE_SYSTEM_QXT) + find_path(QXTCORE_INCLUDE_DIRS qxtglobal.h PATH_SUFFIXES QxtCore) + find_path(QXTGUI_INCLUDE_DIRS qxtglobalshortcut.h PATH_SUFFIXES QxtGui) + set(QXT_INCLUDE_DIRS ${QXTCORE_INCLUDE_DIRS} ${QXTGUI_INCLUDE_DIRS}) + # We only need its header. We don't need to link to QxtCore. + find_library(QXT_LIBRARIES QxtGui) +else (USE_SYSTEM_QXT) + add_definitions(-DQXT_STATIC -DBUILD_QXT_GUI -DBUILD_QXT_CORE) + set(QXT_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/qxt) + set(QXT_LIBRARIES qxt) + if (NOT APPLE) + add_subdirectory(3rdparty/qxt) + endif (NOT APPLE) +endif (USE_SYSTEM_QXT) + +# Use system gmock if it's available +# We need to look for both gmock and gtest +find_path(GMOCK_INCLUDE_DIRS gmock/gmock.h) +find_library(GMOCK_LIBRARIES gmock) +if (GMOCK_INCLUDE_DIRS) + message(STATUS "Using builtin gmock library") + find_path(GTEST_INCLUDE_DIRS gtest/gtest.h) + find_library(GTEST_LIBRARIES gtest) + if(GTEST_INCLUDE_DIRS) + set(USE_SYSTEM_GMOCK 1) + set(GMOCK_LIBRARIES ${GMOCK_LIBRARIES} ${GTEST_LIBRARIES}) + endif(GTEST_INCLUDE_DIRS) +endif(GMOCK_INCLUDE_DIRS) + +# Use system sha2 if it's available +find_path(SHA2_INCLUDE_DIRS sha2.h) +find_library(SHA2_LIBRARIES sha2) +if (SHA2_LIBRARIES AND SHA2_INCLUDE_DIRS) + message(STATUS "Using system sha2 library") + set(USE_SYSTEM_SHA2 ON) +else() + message(STATUS "Using builtin sha2 library") + set(USE_SYSTEM_SHA2 OFF) + add_subdirectory(3rdparty/sha2) + set(SHA2_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/sha2) + set(SHA2_LIBRARIES sha2) +endif() + +# Qocoa +add_subdirectory(3rdparty/qocoa) + +# QJSON +add_subdirectory(3rdparty/qjson) +set(QJSON_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/qjson ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/qjson/include) + +# Subdirectories +add_subdirectory(src) +if (WIN32) + add_subdirectory(3rdparty/qtwin) + add_subdirectory(3rdparty/tinysvcmdns) +endif (WIN32) +#add_subdirectory(tests) +add_subdirectory(dist) +add_subdirectory(ext/libstrawberry-common) +add_subdirectory(ext/libstrawberry-tagreader) +add_subdirectory(ext/strawberry-tagreader) + +option(WITH_DEBIAN OFF) +if(WITH_DEBIAN) + add_subdirectory(debian) +endif(WITH_DEBIAN) + +# Uninstall support +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + +add_custom_target(uninstall + "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") + +# Show a summary of what we have enabled +summary_show() diff --git a/CMakeLists.txt.user b/CMakeLists.txt.user new file mode 100644 index 00000000..2aa44753 --- /dev/null +++ b/CMakeLists.txt.user @@ -0,0 +1,1266 @@ + + + + + + EnvironmentId + {6b0aa776-3cc6-474f-842a-549e3f71d442} + + + ProjectExplorer.Project.ActiveTarget + 1 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + true + false + 0 + true + true + 0 + 8 + true + 1 + true + true + true + false + + + + ProjectExplorer.Project.PluginSettings + + + + ProjectExplorer.Project.Target.0 + + Qt 5.6.2 (qt5) + Qt 5.6.2 (qt5) + {af2d4c87-1843-4050-9e00-6e038cc612ad} + 0 + 0 + 0 + + + /home/jonas/Projects/strawberry/build + + + + + all + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Build + + ProjectExplorer.BuildSteps.Build + + + + + + clean + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Build + Build + CMakeProjectManager.CMakeBuildConfiguration + + 1 + + + 0 + Deploy + + ProjectExplorer.BuildSteps.Deploy + + 1 + Deploy locally + + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + + + %{buildDir} + Custom Executable + + ProjectExplorer.CustomExecutableRunConfiguration + 3768 + false + true + false + false + true + + 1 + + + + ProjectExplorer.Project.Target.1 + + Desktop + Desktop + {80173244-0390-41e7-b1fe-e499d6813389} + 1 + 0 + 0 + + + /home/jonas/Projects/strawberry/build-strawberry-Desktop-Default + + + + + all + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Build + + ProjectExplorer.BuildSteps.Build + + + + + + clean + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Default + Default + CMakeProjectManager.CMakeBuildConfiguration + + + + CMAKE_BUILD_TYPE:STRING=Debug + + /home/jonas/Projects/strawberry/build-strawberry-Desktop-Debug + + + + + all + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Build + + ProjectExplorer.BuildSteps.Build + + + + + + clean + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Debug + Debug + CMakeProjectManager.CMakeBuildConfiguration + + + + CMAKE_BUILD_TYPE:STRING=Release + + /home/jonas/Projects/strawberry/build-strawberry-Desktop-Release + + + + + all + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Build + + ProjectExplorer.BuildSteps.Build + + + + + + clean + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release + Release + CMakeProjectManager.CMakeBuildConfiguration + + + + CMAKE_BUILD_TYPE:STRING=RelWithDebInfo + + /home/jonas/Projects/strawberry/build-strawberry-Desktop-Release with Debug Information + + + + + all + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Build + + ProjectExplorer.BuildSteps.Build + + + + + + clean + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release with Debug Information + Release with Debug Information + CMakeProjectManager.CMakeBuildConfiguration + + + + CMAKE_BUILD_TYPE:STRING=MinSizeRel + + /home/jonas/Projects/strawberry/build-strawberry-Desktop-Minimum Size Release + + + + + all + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Build + + ProjectExplorer.BuildSteps.Build + + + + + + clean + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Minimum Size Release + Minimum Size Release + CMakeProjectManager.CMakeBuildConfiguration + + 5 + + + 0 + Deploy + + ProjectExplorer.BuildSteps.Deploy + + 1 + Deploy locally + + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + strawberry + + + .. + 2 + + strawberry + + CMakeProjectManager.CMakeRunConfiguration.strawberry + 3768 + false + true + false + false + true + + + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + strawberry-tagreader + + + /home/jonas/Projects/strawberry/build-strawberry-Desktop-Debug + 2 + + strawberry-tagreader + + CMakeProjectManager.CMakeRunConfiguration.strawberry-tagreader + 3768 + false + true + false + false + true + + 2 + + + + ProjectExplorer.Project.Target.2 + + Qt 4.8.6 in PATH (System) + Qt 4.8.6 in PATH (System) + {02b71e75-dd34-48bd-8535-e5c32e47198c} + 0 + 0 + 0 + + + /home/jonas/Projects/strawberry/build-strawberry-Qt_4_8_6_in_PATH_System_02b71e-Default + + + + + all + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Build + + ProjectExplorer.BuildSteps.Build + + + + + + clean + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Default + Default + CMakeProjectManager.CMakeBuildConfiguration + + + + CMAKE_BUILD_TYPE:STRING=Debug + + /home/jonas/Projects/strawberry/build-strawberry-Qt_4_8_6_in_PATH_System_02b71e-Debug + + + + + all + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Build + + ProjectExplorer.BuildSteps.Build + + + + + + clean + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Debug + Debug + CMakeProjectManager.CMakeBuildConfiguration + + + + CMAKE_BUILD_TYPE:STRING=Release + + /home/jonas/Projects/strawberry/build-strawberry-Qt_4_8_6_in_PATH_System_02b71e-Release + + + + + all + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Build + + ProjectExplorer.BuildSteps.Build + + + + + + clean + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release + Release + CMakeProjectManager.CMakeBuildConfiguration + + + + CMAKE_BUILD_TYPE:STRING=RelWithDebInfo + + /home/jonas/Projects/strawberry/build-strawberry-Qt_4_8_6_in_PATH_System_02b71e-Release with Debug Information + + + + + all + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Build + + ProjectExplorer.BuildSteps.Build + + + + + + clean + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release with Debug Information + Release with Debug Information + CMakeProjectManager.CMakeBuildConfiguration + + + + CMAKE_BUILD_TYPE:STRING=MinSizeRel + + /home/jonas/Projects/strawberry/build-strawberry-Qt_4_8_6_in_PATH_System_02b71e-Minimum Size Release + + + + + all + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Build + + ProjectExplorer.BuildSteps.Build + + + + + + clean + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Minimum Size Release + Minimum Size Release + CMakeProjectManager.CMakeBuildConfiguration + + 5 + + + 0 + Deploy + + ProjectExplorer.BuildSteps.Deploy + + 1 + Deploy locally + + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + strawberry + + + .. + 2 + + strawberry + + CMakeProjectManager.CMakeRunConfiguration.strawberry + 3768 + false + true + false + false + true + + + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + strawberry-tagreader + + + /tmp/QtCreator-LC1Phe/qtc-cmake-XXq5fNoN + -1 + + strawberry-tagreader + + CMakeProjectManager.CMakeRunConfiguration.strawberry-tagreader + 3768 + false + true + false + false + true + + 2 + + + + ProjectExplorer.Project.Target.3 + + Qt 4.8.6 in PATH (System) + Qt 4.8.6 in PATH (System) + {feba4b14-1974-4db7-94b6-65ce15367edf} + 0 + 0 + 0 + + + /home/jonas/Projects/strawberry/build-strawberry-Qt_4_8_6_in_PATH_System_feba4b-Default + + + + + all + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Build + + ProjectExplorer.BuildSteps.Build + + + + + + clean + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Default + Default + CMakeProjectManager.CMakeBuildConfiguration + + + + CMAKE_BUILD_TYPE:STRING=Debug + + /home/jonas/Projects/strawberry/build-strawberry-Qt_4_8_6_in_PATH_System_feba4b-Debug + + + + + all + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Build + + ProjectExplorer.BuildSteps.Build + + + + + + clean + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Debug + Debug + CMakeProjectManager.CMakeBuildConfiguration + + + + CMAKE_BUILD_TYPE:STRING=Release + + /home/jonas/Projects/strawberry/build-strawberry-Qt_4_8_6_in_PATH_System_feba4b-Release + + + + + all + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Build + + ProjectExplorer.BuildSteps.Build + + + + + + clean + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release + Release + CMakeProjectManager.CMakeBuildConfiguration + + + + CMAKE_BUILD_TYPE:STRING=RelWithDebInfo + + /home/jonas/Projects/strawberry/build-strawberry-Qt_4_8_6_in_PATH_System_feba4b-Release with Debug Information + + + + + all + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Build + + ProjectExplorer.BuildSteps.Build + + + + + + clean + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release with Debug Information + Release with Debug Information + CMakeProjectManager.CMakeBuildConfiguration + + + + CMAKE_BUILD_TYPE:STRING=MinSizeRel + + /home/jonas/Projects/strawberry/build-strawberry-Qt_4_8_6_in_PATH_System_feba4b-Minimum Size Release + + + + + all + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Build + + ProjectExplorer.BuildSteps.Build + + + + + + clean + + true + CMake Build + + CMakeProjectManager.MakeStep + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Minimum Size Release + Minimum Size Release + CMakeProjectManager.CMakeBuildConfiguration + + 5 + + + 0 + Deploy + + ProjectExplorer.BuildSteps.Deploy + + 1 + Deploy locally + + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + strawberry + + + .. + 2 + + strawberry + + CMakeProjectManager.CMakeRunConfiguration.strawberry + 3768 + false + true + false + false + true + + + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + strawberry-tagreader + + + /tmp/QtCreator-LC1Phe/qtc-cmake-XXlPl0g1 + -1 + + strawberry-tagreader + + CMakeProjectManager.CMakeRunConfiguration.strawberry-tagreader + 3768 + false + true + false + false + true + + 2 + + + + ProjectExplorer.Project.TargetCount + 4 + + + ProjectExplorer.Project.Updater.FileVersion + 18 + + + Version + 18 + + diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + 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 +. diff --git a/Changelog b/Changelog new file mode 100644 index 00000000..39c0d594 --- /dev/null +++ b/Changelog @@ -0,0 +1,3 @@ +Strawberry Music Player +======================= +ChangeLog diff --git a/README b/README index 0fd2974f..c04e5380 100644 --- a/README +++ b/README @@ -1,2 +1,34 @@ Strawberry Music Player -Fork of Clementine Music Player +======================= +README + +Strawberry is a fork of Clementine for Linux created in 2013, it's written in C++ and Qt5. +The main goal was to create a player for playing local music files that looked a bit more like Amarok 1.4 amd with advanced soundcard options. +You will find that Strawberry is lacking internet services and some other features found in Clementine. + +Some differences between Strawberry and Clementine are: + +- Status widget similar to context in Amarok 1.4 +- Settings have been reorganized +- Advanced backend settings with support for several backends and advanced options +- No Smart playlists, visualizations or cd ripping support +- No LastFM, podcast or internet features except for fetching album covers + +There are no plans to add internet streaming features, but if we would add something it has to be a service providing high quality audio and not low audio quality like Spotify. + +You can obtain and view the sourcecode on github at: https://github.com/jonaski/strawberry + +Compiling from source +--------------------- + +Get the code: + + git clone https://github.com/jonaski/strawberry.git + +Compile and install: + + mkdir strawberry-build + cd strawberry-build + cmake ../strawberry + make + sudo make install diff --git a/cmake/AddEngine.cmake b/cmake/AddEngine.cmake new file mode 100644 index 00000000..6e29ba9c --- /dev/null +++ b/cmake/AddEngine.cmake @@ -0,0 +1,88 @@ +# Strawberry Music Player +# Copyright 2013, Jonas Kvinge +# +# Strawberry is free software: you can 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. +# +# Strawberry is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Strawberry. If not, see . + +macro(add_engine engine_lower engine_upper lib_list src_list inc_list enabled) + + #message(STATUS "ADD ENGINE: ${engine_lower} ${engine_upper} ${lib_list} ${src_list} ${inc_list} ${enabled}") + + #set(ENGINE_LIBRARIES "") + + # recreate list + set(lib_list ${lib_list}) + #list(GET lib_list 0 name) + + # add a user selectable build option + option(ENGINE_${engine_upper}_ENABLED "enable engine ${engine_upper}" ${enabled}) + + # check if engine is enabled and needed librares are available + if(ENGINE_${engine_upper}_ENABLED) + + # check for all needed libraries + foreach(lib ${lib_list}) + #pkg_check_modules(${lib} ${lib}) + if (NOT ${lib}_FOUND MATCHES 1) + set(ENGINE_${engine_upper}_LIB_MISSING TRUE) + endif(NOT ${lib}_FOUND MATCHES 1) + endforeach(lib ${lib_list}) + + if(ENGINE_${engine_upper}_LIB_MISSING) + set(ENGINES_MISSING "${ENGINES_MISSING} ${engine_lower}") + #set("HAVE_${engine_upper}" 0 CACHE INTERNAL ${engine_upper}) + set("HAVE_${engine_upper}" OFF) + else(ENGINE_${engine_upper}_LIB_MISSING) + # add define -DHAVE_ so we can clutter the code with #ifdefs + #set("HAVE_${engine_upper}" 1 CACHE INTERNAL ${engine_upper}) + set("HAVE_${engine_upper}" ON) + # add sources and headers + list(APPEND SOURCES ${src_list}) + list(APPEND HEADERS ${inc_list}) + # add libraries to link against + foreach(lib ${lib_list}) + #set(ENGINE_LIBRARIES ${ENGINE_LIBRARIES} ${${lib}_LIBRARIES} CACHE INTERNAL libraries) + set(ENGINE_LIBRARIES ${ENGINE_LIBRARIES} ${${lib}_LIBRARIES}) + endforeach(lib ${lib_list}) + # add to list of enabled engines + set(ENGINES_ENABLED "${ENGINES_ENABLED} ${engine_lower}") + endif(ENGINE_${engine_upper}_LIB_MISSING) + else(ENGINE_${engine_upper}_ENABLED) + set(ENGINES_DISABLED "${ENGINES_DISABLED} ${engine_lower}") + #set("HAVE_${engine_upper}" 0 CACHE INTERNAL ${engine_upper}) + set("HAVE_${engine_upper}" OFF) + endif(ENGINE_${engine_upper}_ENABLED) + +endmacro(add_engine engine_lower engine_upper lib_list src_list inc_list enabled) + +# print engines to be built +macro(print_engines) + + if(ENGINES_ENABLED) + message(STATUS "Building engines:${ENGINES_ENABLED}") + endif(ENGINES_ENABLED) + if(ENGINES_DISABLED) + message(STATUS "Disabled engines:${ENGINES_DISABLED}") + endif(ENGINES_DISABLED) + if(ENGINES_MISSING) + message(STATUS "Missing engines:${ENGINES_MISSING}") + endif(ENGINES_MISSING) + + #message(STATUS "Engine libraries:${ENGINE_LIBRARIES}") + + # need at least 1 engine + if(NOT ENGINES_ENABLED) + message(FATAL_ERROR "No engine enabled!") + endif(NOT ENGINES_ENABLED) + +endmacro(print_engines) diff --git a/cmake/C++11Compat.cmake b/cmake/C++11Compat.cmake new file mode 100644 index 00000000..5c4d5151 --- /dev/null +++ b/cmake/C++11Compat.cmake @@ -0,0 +1,9 @@ +# Hacky stuff to make C++11 features work with old compilers. + +if (CMAKE_COMPILER_IS_GNUCC) + execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion + OUTPUT_VARIABLE GCC_VERSION) + if (GCC_VERSION VERSION_LESS 4.7) + add_definitions(-Doverride=) + endif() +endif() diff --git a/cmake/Deb.cmake b/cmake/Deb.cmake new file mode 100644 index 00000000..672392c5 --- /dev/null +++ b/cmake/Deb.cmake @@ -0,0 +1,9 @@ +set(DEB_ARCH amd64 CACHE STRING "Architecture of the deb file") +set(DEB_DIST "unstable" CACHE STRING "Distribution to set in the .deb changelog") + +add_custom_target(deb + COMMAND dpkg-buildpackage -b -d -uc -us + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/../strawberry_${STRAWBERRY_VERSION_DEB}~${DEB_DIST}_${DEB_ARCH}.deb + ${CMAKE_BINARY_DIR}/strawberry_${STRAWBERRY_VERSION_DEB}~${DEB_DIST}_${DEB_ARCH}.deb + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} +) diff --git a/cmake/FindFFTW3.cmake b/cmake/FindFFTW3.cmake new file mode 100644 index 00000000..e711dd3a --- /dev/null +++ b/cmake/FindFFTW3.cmake @@ -0,0 +1,133 @@ +# +# Try to find FFTW3 library +# (see www.fftw.org) +# Once run this will define: +# +# FFTW3_FOUND +# FFTW3_INCLUDE_DIR +# FFTW3_LIBRARIES +# FFTW3_LINK_DIRECTORIES +# +# You may set one of these options before including this file: +# FFTW3_USE_SSE2 +# +# TODO: _F_ versions. +# +# Jan Woetzel 05/2004 +# www.mip.informatik.uni-kiel.de +# -------------------------------- + + FIND_PATH(FFTW3_INCLUDE_DIR fftw3.h + ${FFTW3_DIR}/include + ${FFTW3_HOME}/include + ${FFTW3_DIR} + ${FFTW3_HOME} + $ENV{FFTW3_DIR}/include + $ENV{FFTW3_HOME}/include + $ENV{FFTW3_DIR} + $ENV{FFTW3_HOME} + /usr/include + /usr/local/include + $ENV{SOURCE_DIR}/fftw3 + $ENV{SOURCE_DIR}/fftw3/include + $ENV{SOURCE_DIR}/fftw + $ENV{SOURCE_DIR}/fftw/include + ) +#MESSAGE("DBG FFTW3_INCLUDE_DIR=${FFTW3_INCLUDE_DIR}") + + +SET(FFTW3_POSSIBLE_LIBRARY_PATH + ${FFTW3_DIR}/lib + ${FFTW3_HOME}/lib + ${FFTW3_DIR} + ${FFTW3_HOME} + $ENV{FFTW3_DIR}/lib + $ENV{FFTW3_HOME}/lib + $ENV{FFTW3_DIR} + $ENV{FFTW3_HOME} + /usr/lib + /usr/local/lib + $ENV{SOURCE_DIR}/fftw3 + $ENV{SOURCE_DIR}/fftw3/lib + $ENV{SOURCE_DIR}/fftw + $ENV{SOURCE_DIR}/fftw/lib +) + + +# the lib prefix is containe din filename onf W32, unfortuantely. JW +# teh "general" lib: +FIND_LIBRARY(FFTW3_FFTW_LIBRARY + NAMES fftw3 libfftw libfftw3 libfftw3-3 + PATHS + ${FFTW3_POSSIBLE_LIBRARY_PATH} + ) +#MESSAGE("DBG FFTW3_FFTW_LIBRARY=${FFTW3_FFTW_LIBRARY}") + +FIND_LIBRARY(FFTW3_FFTWF_LIBRARY + NAMES fftwf3 fftw3f fftwf libfftwf libfftwf3 libfftw3f-3 + PATHS + ${FFTW3_POSSIBLE_LIBRARY_PATH} + ) +#MESSAGE("DBG FFTW3_FFTWF_LIBRARY=${FFTW3_FFTWF_LIBRARY}") + +FIND_LIBRARY(FFTW3_FFTWL_LIBRARY + NAMES fftwl3 fftw3l fftwl libfftwl libfftwl3 libfftw3l-3 + PATHS + ${FFTW3_POSSIBLE_LIBRARY_PATH} + ) +#MESSAGE("DBG FFTW3_FFTWF_LIBRARY=${FFTW3_FFTWL_LIBRARY}") + + +FIND_LIBRARY(FFTW3_FFTW_SSE2_LIBRARY + NAMES fftw_sse2 fftw3_sse2 libfftw_sse2 libfftw3_sse2 + PATHS + ${FFTW3_POSSIBLE_LIBRARY_PATH} + ) +#MESSAGE("DBG FFTW3_FFTW_SSE2_LIBRARY=${FFTW3_FFTW_SSE2_LIBRARY}") + +FIND_LIBRARY(FFTW3_FFTWF_SSE_LIBRARY + NAMES fftwf_sse fftwf3_sse libfftwf_sse libfftwf3_sse + PATHS + ${FFTW3_POSSIBLE_LIBRARY_PATH} + ) +#MESSAGE("DBG FFTW3_FFTWF_SSE_LIBRARY=${FFTW3_FFTWF_SSE_LIBRARY}") + + +# -------------------------------- +# select one of the above +# default: +IF (FFTW3_FFTW_LIBRARY) + SET(FFTW3_LIBRARIES ${FFTW3_FFTW_LIBRARY}) +ENDIF (FFTW3_FFTW_LIBRARY) +# specialized: +IF (FFTW3_USE_SSE2 AND FFTW3_FFTW_SSE2_LIBRARY) + SET(FFTW3_LIBRARIES ${FFTW3_FFTW_SSE2_LIBRARY}) +ENDIF (FFTW3_USE_SSE2 AND FFTW3_FFTW_SSE2_LIBRARY) + +# -------------------------------- + +IF(FFTW3_LIBRARIES) + IF (FFTW3_INCLUDE_DIR) + + # OK, found all we need + SET(FFTW3_FOUND TRUE) + GET_FILENAME_COMPONENT(FFTW3_LINK_DIRECTORIES ${FFTW3_LIBRARIES} PATH) + + ELSE (FFTW3_INCLUDE_DIR) + MESSAGE("FFTW3 include dir not found. Set FFTW3_DIR to find it.") + ENDIF(FFTW3_INCLUDE_DIR) +ELSE(FFTW3_LIBRARIES) + MESSAGE("FFTW3 lib not found. Set FFTW3_DIR to find it.") +ENDIF(FFTW3_LIBRARIES) + + +MARK_AS_ADVANCED( + FFTW3_INCLUDE_DIR + FFTW3_LIBRARIES + FFTW3_FFTW_LIBRARY + FFTW3_FFTW_SSE2_LIBRARY + FFTW3_FFTWF_LIBRARY + FFTW3_FFTWF_SSE_LIBRARY + FFTW3_FFTWL_LIBRARY + FFTW3_LINK_DIRECTORIES +) diff --git a/cmake/Format.cmake b/cmake/Format.cmake new file mode 100644 index 00000000..1f7dba02 --- /dev/null +++ b/cmake/Format.cmake @@ -0,0 +1,6 @@ +add_custom_target(format-diff + #COMMAND python ${CMAKE_SOURCE_DIR}/dist/format.py) + COMMAND python2 ${CMAKE_SOURCE_DIR}/dist/format.py) +add_custom_target(format + #COMMAND python ${CMAKE_SOURCE_DIR}/dist/format.py -i) + COMMAND python2 ${CMAKE_SOURCE_DIR}/dist/format.py -i) diff --git a/cmake/OptionalSource.cmake b/cmake/OptionalSource.cmake new file mode 100644 index 00000000..d543e21f --- /dev/null +++ b/cmake/OptionalSource.cmake @@ -0,0 +1,22 @@ +macro(optional_source TOGGLE) + parse_arguments(OPTIONAL_SOURCE + "SOURCES;HEADERS;UI;INCLUDE_DIRECTORIES" + "" + ${ARGN} + ) + + if(${TOGGLE}) + list(APPEND SOURCES ${OPTIONAL_SOURCE_SOURCES}) + list(APPEND HEADERS ${OPTIONAL_SOURCE_HEADERS}) + list(APPEND UI ${OPTIONAL_SOURCE_UI}) + include_directories(${OPTIONAL_SOURCE_INCLUDE_DIRECTORIES}) + else(${TOGGLE}) + list(APPEND OTHER_SOURCES ${OPTIONAL_SOURCE_SOURCES}) + list(APPEND OTHER_SOURCES ${OPTIONAL_SOURCE_HEADERS}) + + set(_uic_sources) + qt5_wrap_ui(_uic_sources ${OPTIONAL_SOURCE_UI}) + list(APPEND OTHER_SOURCES ${_uic_sources}) + list(APPEND OTHER_UIC_SOURCES ${_uic_sources}) + endif(${TOGGLE}) +endmacro(optional_source) diff --git a/cmake/ParseArguments.cmake b/cmake/ParseArguments.cmake new file mode 100644 index 00000000..0abf43b2 --- /dev/null +++ b/cmake/ParseArguments.cmake @@ -0,0 +1,34 @@ +# From http://www.cmake.org/Wiki/CMakeMacroParseArguments + +cmake_minimum_required(VERSION 2.6) + +MACRO(PARSE_ARGUMENTS prefix arg_names option_names) + SET(DEFAULT_ARGS) + FOREACH(arg_name ${arg_names}) + SET(${prefix}_${arg_name}) + ENDFOREACH(arg_name) + FOREACH(option ${option_names}) + SET(${prefix}_${option} FALSE) + ENDFOREACH(option) + + SET(current_arg_name DEFAULT_ARGS) + SET(current_arg_list) + FOREACH(arg ${ARGN}) + SET(larg_names ${arg_names}) + LIST(FIND larg_names "${arg}" is_arg_name) + IF (is_arg_name GREATER -1) + SET(${prefix}_${current_arg_name} ${current_arg_list}) + SET(current_arg_name ${arg}) + SET(current_arg_list) + ELSE (is_arg_name GREATER -1) + SET(loption_names ${option_names}) + LIST(FIND loption_names "${arg}" is_option) + IF (is_option GREATER -1) + SET(${prefix}_${arg} TRUE) + ELSE (is_option GREATER -1) + SET(current_arg_list ${current_arg_list} ${arg}) + ENDIF (is_option GREATER -1) + ENDIF (is_arg_name GREATER -1) + ENDFOREACH(arg) + SET(${prefix}_${current_arg_name} ${current_arg_list}) +ENDMACRO(PARSE_ARGUMENTS) diff --git a/cmake/Rpm.cmake b/cmake/Rpm.cmake new file mode 100644 index 00000000..f22874b9 --- /dev/null +++ b/cmake/Rpm.cmake @@ -0,0 +1,19 @@ +set(RPMBUILD_DIR ~/rpmbuild CACHE STRING "Rpmbuild directory, for the rpm target") +set(MOCK_COMMAND mock CACHE STRING "Command to use for running mock") +set(MOCK_CHROOT fedora-13-x86_64 CACHE STRING "Chroot to use when building an rpm with mock") +set(RPM_DISTRO fc13 CACHE STRING "Suffix of the rpm file") +set(RPM_ARCH x86_64 CACHE STRING "Architecture of the rpm file") + +add_custom_target(rpm + COMMAND ${CMAKE_SOURCE_DIR}/dist/maketarball.sh + COMMAND ${CMAKE_COMMAND} -E copy strawberry-${STRAWBERRY_VERSION_SPARKLE}.tar.gz ${RPMBUILD_DIR}/SOURCES/ + COMMAND rpmbuild -bs ${CMAKE_SOURCE_DIR}/dist/strawberry.spec + COMMAND ${MOCK_COMMAND} + --verbose + --root=${MOCK_CHROOT} + --resultdir=${CMAKE_BINARY_DIR}/mock_result/ + ${RPMBUILD_DIR}/SRPMS/strawberry-${STRAWBERRY_VERSION_RPM_V}-${STRAWBERRY_VERSION_RPM_R}.${RPM_DISTRO}.src.rpm + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_BINARY_DIR}/mock_result/strawberry-${STRAWBERRY_VERSION_RPM_V}-${STRAWBERRY_VERSION_RPM_R}.${RPM_DISTRO}.${RPM_ARCH}.rpm + ${CMAKE_BINARY_DIR}/strawberry-${STRAWBERRY_VERSION_RPM_V}-${STRAWBERRY_VERSION_RPM_R}.${RPM_DISTRO}.${RPM_ARCH}.rpm +) diff --git a/cmake/Summary.cmake b/cmake/Summary.cmake new file mode 100644 index 00000000..6faf83eb --- /dev/null +++ b/cmake/Summary.cmake @@ -0,0 +1,89 @@ +cmake_policy(SET CMP0012 NEW) + +set(summary_willbuild "") +set(summary_willnotbuild "") + +macro(summary_add name test) + if (${test}) + list(APPEND summary_willbuild ${name}) + else (${test}) + list(APPEND summary_willnotbuild "${name}") + endif (${test}) +endmacro(summary_add) + +macro(summary_show_part variable title) + list(LENGTH ${variable} _len) + if (_len) + message("") + message(${title}) + foreach (_item ${${variable}}) + message(" ${_item}") + endforeach (_item) + endif (_len) +endmacro(summary_show_part) + +macro(summary_show) + list(SORT summary_willbuild) + list(SORT summary_willnotbuild) + message("") + message("Building strawberry version: ${STRAWBERRY_VERSION_DISPLAY}") + summary_show_part(summary_willbuild "The following components will be built:") + summary_show_part(summary_willnotbuild "The following components WILL NOT be built:") + message("") +endmacro(summary_show) + +function(optional_component name default description) + set(option_variable "ENABLE_${name}") + set(have_variable "HAVE_${name}") + set(${have_variable} OFF) + + # Create the option + option(${option_variable} "${description}" ${default}) + + # Was the option set? + if(NOT ${option_variable}) + set(summary_willnotbuild "${summary_willnotbuild};${description} (disabled in CMake config)" PARENT_SCOPE) + return() + endif() + + # Check each of the dependencies + set(next_arg_is_dep_name FALSE) + set(testing_deps TRUE) + set(current_dep_name) + set(missing_deps) + + foreach(arg ${ARGN}) + if(${next_arg_is_dep_name}) + set(current_dep_name "${arg}") + set(next_arg_is_dep_name FALSE) + elseif(arg STREQUAL "DEPENDS") + set(next_arg_is_dep_name TRUE) + set(testing_deps TRUE) + elseif(${testing_deps}) + string(REPLACE " " ";" arglist "${arg}") + if(${arglist}) + # We have to do this instead of if(NOT ${arg}) so that tests may contain + # "NOT" themselves. + else() + list(APPEND missing_deps "${current_dep_name}") + set(testing_deps FALSE) + endif() + endif() + endforeach() + + if(missing_deps) + foreach(dep ${missing_deps}) + if(deplist_text) + set(deplist_text "${deplist_text}, ${dep}") + else() + set(deplist_text "${dep}") + endif() + endforeach() + set(text "${description} (missing ${deplist_text})") + + set(summary_willnotbuild "${summary_willnotbuild};${text}" PARENT_SCOPE) + else() + set(${have_variable} ON PARENT_SCOPE) + set(summary_willbuild "${summary_willbuild};${description}" PARENT_SCOPE) + endif() +endfunction() diff --git a/cmake/Version.cmake b/cmake/Version.cmake new file mode 100644 index 00000000..20da4683 --- /dev/null +++ b/cmake/Version.cmake @@ -0,0 +1,169 @@ +# Change this file when releasing a new version. + +# Version numbers. +set(STRAWBERRY_VERSION_MAJOR 0) +set(STRAWBERRY_VERSION_MINOR 1) +set(STRAWBERRY_VERSION_PATCH 1) +#set(STRAWBERRY_VERSION_PRERELEASE rc1) + +# This should be set to OFF in a release branch +set(INCLUDE_GIT_REVISION ON) + +# Rules about version number comparison on different platforms: +# Debian: +# Two stages are repeated until there are no more characters to compare: +# one block of consecutive digits (\d+) is compared numerically, then one +# block of consecutive NON-digits (\D+) is compared lexigraphically, +# with the exception that ~ sorts before everything else. +# +# The "upstream version" and "debian revision" are separated by the last +# dash in the version number. +# +# Algorithm is in "man deb-version", test comparisons with +# dpkg --compare-versions. +# +# These are in sorted order: +# 1.0~rc1 +# 1.0~rc2 +# 1.0 +# 1.0-1-g044287b +# 1.0-506-g044287b +# 1.0.1 +# 1.0.2 +# 1.0.a +# +# Rpm: +# The string is split on non-alphanumeric characters. Numeric sections are +# compared numerically and non-numeric sections are compared lexigraphically. +# If one sections is numeric and the other sections is non-numeric, the +# numeric sections is always NEWER. +# +# The "version" and "release" fields are compared with the same algorithm - +# if the versions are equal the releases are compared to determine which +# package is newer. +# +# Algorithm is described in: +# http://fedoraproject.org/wiki/Packaging:NamingGuidelines#Package_Versioning +# Test comparisons with: +# import rpm +# rpm.labelCompare((epoch, version, release), (epoch, version, release)) +# +# These are in sorted order: +# 1.0-0.rc1 +# 1.0-0.rc2 +# 1.0-1 +# 1.0-2.506-g044287b +# 1.0.1-1 +# 1.0.2-1 +# +# Sparkle (mac) and QtSparkle (windows): +# The strings are split into sections of characters that are all of the same +# "type" - where a "type" is period, digit, or other. Sections are then +# compared against each other - digits are compared numerically and other +# are compared lexigraphically. When two sections are of different types, +# the numeric section is always NEWER. +# +# If the common parts of both strings are equal, but one string has more +# sections, the type of the first extra section is used to determine which +# version is newer. +# If the extra section is a string, the shorter result is NEWER, otherwise +# the shorter section is OLDER. That means that 1.0 is NEWER than 1.0rc1, +# but 1.0 is OLDER than 1.0.1. +# +# See compareversions.cpp in QtSparkle. + +# Version numbers in Strawberry: +# Deb: +# With git: $tagname-$commitcount-g$sha1 +# Without git: $major.$minor.$patch[~$prerelease] +# +# Rpm: Version Release +# Prerelease: $major.$minor.$patch 0.$prerelease +# Without git: $major.$minor.$patch 1 +# With git: $tagname 2.$commitcount.g$sha1 +# +# QtSparkle (Windows): +# With git: $tagname-$commitcount-g$sha1 +# Without git: $major.$minor.$patch[$prerelease] +# +# Mac info.plist: CFBundleVersion +# Prerelease: 4096.$major.$minor.$patch.0 +# Without git: 4096.$major.$minor.$patch.1 +# With git: 4096.$tagname.2.$commitcount +# The 4096. prefix is because the previous versioning scheme used svn revision +# numbers, which got up to 3000+. + + +set(majorminorpatch "${STRAWBERRY_VERSION_MAJOR}.${STRAWBERRY_VERSION_MINOR}.${STRAWBERRY_VERSION_PATCH}") + +set(STRAWBERRY_VERSION_DISPLAY "${majorminorpatch}") +set(STRAWBERRY_VERSION_DEB "${majorminorpatch}") +set(STRAWBERRY_VERSION_RPM_V "${majorminorpatch}") +set(STRAWBERRY_VERSION_RPM_R "1") +set(STRAWBERRY_VERSION_SPARKLE "${majorminorpatch}") +set(STRAWBERRY_VERSION_PLIST "4096.${majorminorpatch}") + +if(${STRAWBERRY_VERSION_PATCH} EQUAL "0") + set(STRAWBERRY_VERSION_DISPLAY "${STRAWBERRY_VERSION_MAJOR}.${STRAWBERRY_VERSION_MINOR}") +endif(${STRAWBERRY_VERSION_PATCH} EQUAL "0") + +# Add prerelease +if(STRAWBERRY_VERSION_PRERELEASE) + set(STRAWBERRY_VERSION_DISPLAY "${STRAWBERRY_VERSION_DISPLAY} ${STRAWBERRY_VERSION_PRERELEASE}") + set(STRAWBERRY_VERSION_DEB "${STRAWBERRY_VERSION_DEB}~${STRAWBERRY_VERSION_PRERELEASE}") + set(STRAWBERRY_VERSION_RPM_R "0.${STRAWBERRY_VERSION_PRERELEASE}") + set(STRAWBERRY_VERSION_SPARKLE "${STRAWBERRY_VERSION_SPARKLE}${STRAWBERRY_VERSION_PRERELEASE}") + set(STRAWBERRY_VERSION_PLIST "${STRAWBERRY_VERSION_PLIST}.0") +else(STRAWBERRY_VERSION_PRERELEASE) + set(STRAWBERRY_VERSION_PLIST "${STRAWBERRY_VERSION_PLIST}.1") +endif(STRAWBERRY_VERSION_PRERELEASE) + +# Add git revision +if(FORCE_GIT_REVISION) + set(GIT_REV ${FORCE_GIT_REVISION}) + set(GIT_INFO_RESULT 0) +else(FORCE_GIT_REVISION) + find_program(GIT_EXECUTABLE git) + + if(NOT GIT_EXECUTABLE-NOTFOUND) + execute_process(COMMAND ${GIT_EXECUTABLE} describe + RESULT_VARIABLE GIT_INFO_RESULT + OUTPUT_VARIABLE GIT_REV + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + endif() +endif() + +if(${GIT_INFO_RESULT} EQUAL 0) + string(REGEX REPLACE "^(.+)-([0-9]+)-(g[a-f0-9]+)$" "\\1;\\2;\\3" + GIT_PARTS ${GIT_REV}) + + if(NOT GIT_PARTS) + message(FATAL_ERROR "Failed to parse git revision string '${GIT_REV}'") + endif(NOT GIT_PARTS) + + list(LENGTH GIT_PARTS GIT_PARTS_LENGTH) + if(GIT_PARTS_LENGTH EQUAL 3) + list(GET GIT_PARTS 0 GIT_TAGNAME) + list(GET GIT_PARTS 1 GIT_COMMITCOUNT) + list(GET GIT_PARTS 2 GIT_SHA1) + set(HAS_GET_REVISION ON) + endif(GIT_PARTS_LENGTH EQUAL 3) +endif(${GIT_INFO_RESULT} EQUAL 0) + +if(INCLUDE_GIT_REVISION AND HAS_GET_REVISION) + set(STRAWBERRY_VERSION_DISPLAY "${GIT_REV}") + set(STRAWBERRY_VERSION_DEB "${GIT_REV}") + set(STRAWBERRY_VERSION_RPM_V "${GIT_TAGNAME}") + set(STRAWBERRY_VERSION_RPM_R "2.${GIT_COMMITCOUNT}.${GIT_SHA1}") + set(STRAWBERRY_VERSION_SPARKLE "${GIT_REV}") + set(STRAWBERRY_VERSION_PLIST "4096.${GIT_TAGNAME}.2.${GIT_COMMITCOUNT}") +endif(INCLUDE_GIT_REVISION AND HAS_GET_REVISION) + +if(0) + message(STATUS "Display: ${STRAWBERRY_VERSION_DISPLAY}") + message(STATUS "Deb: ${STRAWBERRY_VERSION_DEB}") + message(STATUS "Rpm: ${STRAWBERRY_VERSION_RPM_V}-${STRAWBERRY_VERSION_RPM_R}") + message(STATUS "Sparkle: ${STRAWBERRY_VERSION_SPARKLE}") + message(STATUS "Plist: ${STRAWBERRY_VERSION_PLIST}") +endif(0) diff --git a/cmake_uninstall.cmake.in b/cmake_uninstall.cmake.in new file mode 100644 index 00000000..290dc787 --- /dev/null +++ b/cmake_uninstall.cmake.in @@ -0,0 +1,23 @@ +IF(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") + +FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +STRING(REGEX REPLACE "\n" ";" files "${files}") +FOREACH(file ${files}) + MESSAGE(STATUS "Uninstalling \"${file}\"") + IF(EXISTS "${file}") + EXEC_PROGRAM( + "@CMAKE_COMMAND@" ARGS "-E remove \"${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + IF("${rm_retval}" STREQUAL 0) + ELSE("${rm_retval}" STREQUAL 0) + MESSAGE(FATAL_ERROR "Problem when removing \"${file}\"") + ENDIF("${rm_retval}" STREQUAL 0) + ELSE(EXISTS "${file}") + MESSAGE(STATUS "File \"${file}\" does not exist.") + ENDIF(EXISTS "${file}") +ENDFOREACH(file) + diff --git a/data/data.qrc b/data/data.qrc new file mode 100644 index 00000000..4492ddf2 --- /dev/null +++ b/data/data.qrc @@ -0,0 +1,584 @@ + + + + schema/schema.sql + schema/device-schema.sql + + style/mainwindow.css + style/statusview.css + + misc/blank.ttf + misc/playing_tooltip.txt + + pictures/strawberry.png + pictures/strawberry-background.png + pictures/strawbs-transparent.png + + pictures/noalbumart.png + pictures/nomusic.png + pictures/musicbrainz.png + pictures/tiny-play.png + pictures/tiny-pause.png + pictures/spinner.gif + + pictures/volumeslider-gradient.png + pictures/volumeslider-handle_glow.png + pictures/volumeslider-handle.png + pictures/volumeslider-inset.png + + pictures/currenttrack_play.png + pictures/currenttrack_pause.png + pictures/currenttrack_bar_left.png + pictures/currenttrack_bar_mid.png + pictures/currenttrack_bar_right.png + + pictures/osd_background.png + pictures/osd_shadow_corner.png + pictures/osd_shadow_edge.png + +icons/full/albums.png +icons/full/alsa.png +icons/full/application-exit.png +icons/full/applications-internet.png +icons/full/bluetooth.png +icons/full/cdcase.png +icons/full/cd.png +icons/full/configure.png +icons/full/device-ipod-nano.png +icons/full/device-ipod.png +icons/full/device-phone.png +icons/full/device.png +icons/full/device-usb-drive.png +icons/full/device-usb-flash.png +icons/full/dialog-error.png +icons/full/dialog-information.png +icons/full/dialog-ok-apply.png +icons/full/dialog-password.png +icons/full/dialog-warning.png +icons/full/document-download.png +icons/full/document-new.png +icons/full/document-open-folder.png +icons/full/document-open.png +icons/full/document-save.png +icons/full/document-search.png +icons/full/download.png +icons/full/edit-clear-list.png +icons/full/edit-clear-locationbar-ltr.png +icons/full/edit-copy.png +icons/full/edit-delete.png +icons/full/edit-find.png +icons/full/edit-redo.png +icons/full/edit-rename.png +icons/full/edit-undo.png +icons/full/electrocompaniet.png +icons/full/equalizer.png +icons/full/folder-new.png +icons/full/folder.png +icons/full/folder-sound.png +icons/full/footsteps.png +icons/full/go-down.png +icons/full/go-home.png +icons/full/go-jump.png +icons/full/go-next.png +icons/full/go-previous.png +icons/full/go-up.png +icons/full/gstreamer.png +icons/full/guitar.png +icons/full/headset.png +icons/full/help-hint.png +icons/full/intel.png +icons/full/jack.png +icons/full/keyboard.png +icons/full/list-add.png +icons/full/list-remove.png +icons/full/mcintosh-player.png +icons/full/mcintosh.png +icons/full/mcintosh-text.png +icons/full/media-eject.png +icons/full/media-forward.png +icons/full/media-pause.png +icons/full/media-playlist-repeat.png +icons/full/media-playlist-shuffle.png +icons/full/media-play.png +icons/full/media-rewind.png +icons/full/media-stop.png +icons/full/nvidia.png +icons/full/play2.png +icons/full/pulseaudio.png +icons/full/realtek.png +icons/full/search.png +icons/full/soundcard2.png +icons/full/soundcard.png +icons/full/speaker.png +icons/full/star-grey.png +icons/full/star.png +icons/full/strawberry-panel-grey.png +icons/full/strawberry-panel.png +icons/full/strawberry.png +icons/full/strawberry.svg +icons/full/tools-wizard.png +icons/full/view-choose.png +icons/full/view-fullscreen.png +icons/full/view-media-lyrics.png +icons/full/view-media-playlist.png +icons/full/view-media-visualization.png +icons/full/view-refresh.png +icons/full/vinyl.png +icons/full/vlc.png +icons/full/xine.png +icons/full/zoom-in.png +icons/full/zoom-out.png +icons/128x128/albums.png +icons/128x128/alsa.png +icons/128x128/application-exit.png +icons/128x128/applications-internet.png +icons/128x128/bluetooth.png +icons/128x128/cdcase.png +icons/128x128/cd.png +icons/128x128/configure.png +icons/128x128/device-ipod-nano.png +icons/128x128/device-ipod.png +icons/128x128/device-phone.png +icons/128x128/device.png +icons/128x128/device-usb-drive.png +icons/128x128/device-usb-flash.png +icons/128x128/dialog-error.png +icons/128x128/dialog-information.png +icons/128x128/dialog-ok-apply.png +icons/128x128/dialog-password.png +icons/128x128/dialog-warning.png +icons/128x128/document-download.png +icons/128x128/document-new.png +icons/128x128/document-open-folder.png +icons/128x128/document-open.png +icons/128x128/document-save.png +icons/128x128/document-search.png +icons/128x128/download.png +icons/128x128/edit-clear-list.png +icons/128x128/edit-clear-locationbar-ltr.png +icons/128x128/edit-copy.png +icons/128x128/edit-delete.png +icons/128x128/edit-find.png +icons/128x128/edit-redo.png +icons/128x128/edit-rename.png +icons/128x128/edit-undo.png +icons/128x128/electrocompaniet.png +icons/128x128/equalizer.png +icons/128x128/folder-new.png +icons/128x128/folder.png +icons/128x128/folder-sound.png +icons/128x128/footsteps.png +icons/128x128/go-down.png +icons/128x128/go-home.png +icons/128x128/go-jump.png +icons/128x128/go-next.png +icons/128x128/go-previous.png +icons/128x128/go-up.png +icons/128x128/gstreamer.png +icons/128x128/guitar.png +icons/128x128/headset.png +icons/128x128/help-hint.png +icons/128x128/intel.png +icons/128x128/jack.png +icons/128x128/keyboard.png +icons/128x128/list-add.png +icons/128x128/list-remove.png +icons/128x128/mcintosh-player.png +icons/128x128/mcintosh-text.png +icons/128x128/media-eject.png +icons/128x128/media-forward.png +icons/128x128/media-pause.png +icons/128x128/media-play.png +icons/128x128/media-rewind.png +icons/128x128/media-stop.png +icons/128x128/nvidia.png +icons/128x128/play2.png +icons/128x128/realtek.png +icons/128x128/search.png +icons/128x128/soundcard2.png +icons/128x128/soundcard.png +icons/128x128/speaker.png +icons/128x128/star-grey.png +icons/128x128/star.png +icons/128x128/strawberry-panel-grey.png +icons/128x128/strawberry-panel.png +icons/128x128/strawberry.png +icons/128x128/strawberry.svg +icons/128x128/tools-wizard.png +icons/128x128/view-choose.png +icons/128x128/view-fullscreen.png +icons/128x128/view-media-lyrics.png +icons/128x128/view-media-playlist.png +icons/128x128/view-media-visualization.png +icons/128x128/view-refresh.png +icons/128x128/vinyl.png +icons/128x128/vlc.png +icons/128x128/xine.png +icons/128x128/zoom-in.png +icons/128x128/zoom-out.png +icons/64x64/albums.png +icons/64x64/alsa.png +icons/64x64/application-exit.png +icons/64x64/applications-internet.png +icons/64x64/bluetooth.png +icons/64x64/cdcase.png +icons/64x64/cd.png +icons/64x64/configure.png +icons/64x64/device-ipod-nano.png +icons/64x64/device-ipod.png +icons/64x64/device-phone.png +icons/64x64/device.png +icons/64x64/device-usb-drive.png +icons/64x64/device-usb-flash.png +icons/64x64/dialog-error.png +icons/64x64/dialog-information.png +icons/64x64/dialog-ok-apply.png +icons/64x64/dialog-password.png +icons/64x64/dialog-warning.png +icons/64x64/document-download.png +icons/64x64/document-new.png +icons/64x64/document-open-folder.png +icons/64x64/document-open.png +icons/64x64/document-save.png +icons/64x64/document-search.png +icons/64x64/download.png +icons/64x64/edit-clear-list.png +icons/64x64/edit-clear-locationbar-ltr.png +icons/64x64/edit-copy.png +icons/64x64/edit-delete.png +icons/64x64/edit-find.png +icons/64x64/edit-redo.png +icons/64x64/edit-rename.png +icons/64x64/edit-undo.png +icons/64x64/electrocompaniet.png +icons/64x64/equalizer.png +icons/64x64/folder-new.png +icons/64x64/folder.png +icons/64x64/folder-sound.png +icons/64x64/footsteps.png +icons/64x64/go-down.png +icons/64x64/go-home.png +icons/64x64/go-jump.png +icons/64x64/go-next.png +icons/64x64/go-previous.png +icons/64x64/go-up.png +icons/64x64/gstreamer.png +icons/64x64/guitar.png +icons/64x64/headset.png +icons/64x64/help-hint.png +icons/64x64/intel.png +icons/64x64/jack.png +icons/64x64/keyboard.png +icons/64x64/list-add.png +icons/64x64/list-remove.png +icons/64x64/mcintosh-player.png +icons/64x64/mcintosh-text.png +icons/64x64/media-eject.png +icons/64x64/media-forward.png +icons/64x64/media-pause.png +icons/64x64/media-play.png +icons/64x64/media-rewind.png +icons/64x64/media-stop.png +icons/64x64/nvidia.png +icons/64x64/play2.png +icons/64x64/pulseaudio.png +icons/64x64/realtek.png +icons/64x64/search.png +icons/64x64/soundcard2.png +icons/64x64/soundcard.png +icons/64x64/speaker.png +icons/64x64/star-grey.png +icons/64x64/star.png +icons/64x64/strawberry-panel-grey.png +icons/64x64/strawberry-panel.png +icons/64x64/strawberry.png +icons/64x64/tools-wizard.png +icons/64x64/view-choose.png +icons/64x64/view-fullscreen.png +icons/64x64/view-media-lyrics.png +icons/64x64/view-media-playlist.png +icons/64x64/view-media-visualization.png +icons/64x64/view-refresh.png +icons/64x64/vinyl.png +icons/64x64/vlc.png +icons/64x64/xine.png +icons/64x64/zoom-in.png +icons/64x64/zoom-out.png +icons/48x48/albums.png +icons/48x48/alsa.png +icons/48x48/application-exit.png +icons/48x48/applications-internet.png +icons/48x48/bluetooth.png +icons/48x48/cdcase.png +icons/48x48/cd.png +icons/48x48/configure.png +icons/48x48/device-ipod-nano.png +icons/48x48/device-ipod.png +icons/48x48/device-phone.png +icons/48x48/device.png +icons/48x48/device-usb-drive.png +icons/48x48/device-usb-flash.png +icons/48x48/dialog-error.png +icons/48x48/dialog-information.png +icons/48x48/dialog-ok-apply.png +icons/48x48/dialog-password.png +icons/48x48/dialog-warning.png +icons/48x48/document-download.png +icons/48x48/document-new.png +icons/48x48/document-open-folder.png +icons/48x48/document-open.png +icons/48x48/document-save.png +icons/48x48/document-search.png +icons/48x48/download.png +icons/48x48/edit-clear-list.png +icons/48x48/edit-clear-locationbar-ltr.png +icons/48x48/edit-copy.png +icons/48x48/edit-delete.png +icons/48x48/edit-find.png +icons/48x48/edit-redo.png +icons/48x48/edit-rename.png +icons/48x48/edit-undo.png +icons/48x48/electrocompaniet.png +icons/48x48/equalizer.png +icons/48x48/folder-new.png +icons/48x48/folder.png +icons/48x48/folder-sound.png +icons/48x48/footsteps.png +icons/48x48/go-down.png +icons/48x48/go-home.png +icons/48x48/go-jump.png +icons/48x48/go-next.png +icons/48x48/go-previous.png +icons/48x48/go-up.png +icons/48x48/gstreamer.png +icons/48x48/guitar.png +icons/48x48/headset.png +icons/48x48/help-hint.png +icons/48x48/intel.png +icons/48x48/jack.png +icons/48x48/keyboard.png +icons/48x48/list-add.png +icons/48x48/list-remove.png +icons/48x48/mcintosh-player.png +icons/48x48/mcintosh.png +icons/48x48/mcintosh-text.png +icons/48x48/media-eject.png +icons/48x48/media-forward.png +icons/48x48/media-pause.png +icons/48x48/media-playlist-repeat.png +icons/48x48/media-playlist-shuffle.png +icons/48x48/media-play.png +icons/48x48/media-rewind.png +icons/48x48/media-stop.png +icons/48x48/nvidia.png +icons/48x48/play2.png +icons/48x48/pulseaudio.png +icons/48x48/realtek.png +icons/48x48/search.png +icons/48x48/soundcard2.png +icons/48x48/soundcard.png +icons/48x48/speaker.png +icons/48x48/star-grey.png +icons/48x48/star.png +icons/48x48/strawberry-panel-grey.png +icons/48x48/strawberry-panel.png +icons/48x48/strawberry.png +icons/48x48/tools-wizard.png +icons/48x48/view-choose.png +icons/48x48/view-fullscreen.png +icons/48x48/view-media-lyrics.png +icons/48x48/view-media-playlist.png +icons/48x48/view-media-visualization.png +icons/48x48/view-refresh.png +icons/48x48/vinyl.png +icons/48x48/vlc.png +icons/48x48/xine.png +icons/48x48/zoom-in.png +icons/48x48/zoom-out.png +icons/32x32/albums.png +icons/32x32/alsa.png +icons/32x32/application-exit.png +icons/32x32/applications-internet.png +icons/32x32/bluetooth.png +icons/32x32/cdcase.png +icons/32x32/cd.png +icons/32x32/configure.png +icons/32x32/device-ipod-nano.png +icons/32x32/device-ipod.png +icons/32x32/device-phone.png +icons/32x32/device.png +icons/32x32/device-usb-drive.png +icons/32x32/device-usb-flash.png +icons/32x32/dialog-error.png +icons/32x32/dialog-information.png +icons/32x32/dialog-ok-apply.png +icons/32x32/dialog-password.png +icons/32x32/dialog-warning.png +icons/32x32/document-download.png +icons/32x32/document-new.png +icons/32x32/document-open-folder.png +icons/32x32/document-open.png +icons/32x32/document-save.png +icons/32x32/document-search.png +icons/32x32/download.png +icons/32x32/edit-clear-list.png +icons/32x32/edit-clear-locationbar-ltr.png +icons/32x32/edit-copy.png +icons/32x32/edit-delete.png +icons/32x32/edit-find.png +icons/32x32/edit-redo.png +icons/32x32/edit-rename.png +icons/32x32/edit-undo.png +icons/32x32/electrocompaniet.png +icons/32x32/equalizer.png +icons/32x32/folder-new.png +icons/32x32/folder.png +icons/32x32/folder-sound.png +icons/32x32/footsteps.png +icons/32x32/go-down.png +icons/32x32/go-home.png +icons/32x32/go-jump.png +icons/32x32/go-next.png +icons/32x32/go-previous.png +icons/32x32/go-up.png +icons/32x32/gstreamer.png +icons/32x32/guitar.png +icons/32x32/headset.png +icons/32x32/help-hint.png +icons/32x32/intel.png +icons/32x32/jack.png +icons/32x32/keyboard.png +icons/32x32/list-add.png +icons/32x32/list-remove.png +icons/32x32/mcintosh-player.png +icons/32x32/mcintosh.png +icons/32x32/mcintosh-text.png +icons/32x32/media-eject.png +icons/32x32/media-forward.png +icons/32x32/media-pause.png +icons/32x32/media-playlist-repeat.png +icons/32x32/media-playlist-shuffle.png +icons/32x32/media-play.png +icons/32x32/media-rewind.png +icons/32x32/media-stop.png +icons/32x32/nvidia.png +icons/32x32/play2.png +icons/32x32/pulseaudio.png +icons/32x32/realtek.png +icons/32x32/search.png +icons/32x32/soundcard2.png +icons/32x32/soundcard.png +icons/32x32/speaker.png +icons/32x32/star-grey.png +icons/32x32/star.png +icons/32x32/strawberry-panel-grey.png +icons/32x32/strawberry-panel.png +icons/32x32/strawberry.png +icons/32x32/strawberry.svg +icons/32x32/tools-wizard.png +icons/32x32/view-choose.png +icons/32x32/view-fullscreen.png +icons/32x32/view-media-lyrics.png +icons/32x32/view-media-playlist.png +icons/32x32/view-media-visualization.png +icons/32x32/view-refresh.png +icons/32x32/vinyl.png +icons/32x32/vlc.png +icons/32x32/xine.png +icons/32x32/zoom-in.png +icons/32x32/zoom-out.png +icons/22x22/albums.png +icons/22x22/alsa.png +icons/22x22/application-exit.png +icons/22x22/applications-internet.png +icons/22x22/bluetooth.png +icons/22x22/cdcase.png +icons/22x22/cd.png +icons/22x22/configure.png +icons/22x22/device-ipod-nano.png +icons/22x22/device-ipod.png +icons/22x22/device-phone.png +icons/22x22/device.png +icons/22x22/device-usb-drive.png +icons/22x22/device-usb-flash.png +icons/22x22/dialog-error.png +icons/22x22/dialog-information.png +icons/22x22/dialog-ok-apply.png +icons/22x22/dialog-password.png +icons/22x22/dialog-warning.png +icons/22x22/document-download.png +icons/22x22/document-new.png +icons/22x22/document-open-folder.png +icons/22x22/document-open.png +icons/22x22/document-save.png +icons/22x22/document-search.png +icons/22x22/download.png +icons/22x22/edit-clear-list.png +icons/22x22/edit-clear-locationbar-ltr.png +icons/22x22/edit-copy.png +icons/22x22/edit-delete.png +icons/22x22/edit-find.png +icons/22x22/edit-redo.png +icons/22x22/edit-rename.png +icons/22x22/edit-undo.png +icons/22x22/electrocompaniet.png +icons/22x22/equalizer.png +icons/22x22/folder-new.png +icons/22x22/folder.png +icons/22x22/folder-sound.png +icons/22x22/footsteps.png +icons/22x22/go-down.png +icons/22x22/go-home.png +icons/22x22/go-jump.png +icons/22x22/go-next.png +icons/22x22/go-previous.png +icons/22x22/go-up.png +icons/22x22/gstreamer.png +icons/22x22/guitar.png +icons/22x22/headset.png +icons/22x22/help-hint.png +icons/22x22/intel.png +icons/22x22/jack.png +icons/22x22/keyboard.png +icons/22x22/list-add.png +icons/22x22/list-remove.png +icons/22x22/mcintosh-player.png +icons/22x22/mcintosh.png +icons/22x22/mcintosh-text.png +icons/22x22/media-eject.png +icons/22x22/media-forward.png +icons/22x22/media-pause.png +icons/22x22/media-playlist-repeat.png +icons/22x22/media-playlist-shuffle.png +icons/22x22/media-play.png +icons/22x22/media-rewind.png +icons/22x22/media-stop.png +icons/22x22/nvidia.png +icons/22x22/play2.png +icons/22x22/pulseaudio.png +icons/22x22/realtek.png +icons/22x22/search.png +icons/22x22/soundcard2.png +icons/22x22/soundcard.png +icons/22x22/speaker.png +icons/22x22/star-grey.png +icons/22x22/star.png +icons/22x22/strawberry-panel-grey.png +icons/22x22/strawberry-panel.png +icons/22x22/strawberry.png +icons/22x22/strawberry.svg +icons/22x22/tools-wizard.png +icons/22x22/view-choose.png +icons/22x22/view-fullscreen.png +icons/22x22/view-media-lyrics.png +icons/22x22/view-media-playlist.png +icons/22x22/view-media-visualization.png +icons/22x22/view-refresh.png +icons/22x22/vinyl.png +icons/22x22/vlc.png +icons/22x22/xine.png +icons/22x22/zoom-in.png +icons/22x22/zoom-out.png + + + diff --git a/data/icons.qrc b/data/icons.qrc new file mode 100644 index 00000000..63ea2638 --- /dev/null +++ b/data/icons.qrc @@ -0,0 +1,4 @@ + + + + diff --git a/data/icons/128x128/albums.png b/data/icons/128x128/albums.png new file mode 100644 index 00000000..4ba09f4c Binary files /dev/null and b/data/icons/128x128/albums.png differ diff --git a/data/icons/128x128/alsa.png b/data/icons/128x128/alsa.png new file mode 100644 index 00000000..6a06452e Binary files /dev/null and b/data/icons/128x128/alsa.png differ diff --git a/data/icons/128x128/application-exit.png b/data/icons/128x128/application-exit.png new file mode 100644 index 00000000..df24a881 Binary files /dev/null and b/data/icons/128x128/application-exit.png differ diff --git a/data/icons/128x128/applications-internet.png b/data/icons/128x128/applications-internet.png new file mode 100644 index 00000000..3e82aa46 Binary files /dev/null and b/data/icons/128x128/applications-internet.png differ diff --git a/data/icons/128x128/bluetooth.png b/data/icons/128x128/bluetooth.png new file mode 100644 index 00000000..44758ec5 Binary files /dev/null and b/data/icons/128x128/bluetooth.png differ diff --git a/data/icons/128x128/cd.png b/data/icons/128x128/cd.png new file mode 100644 index 00000000..27f23286 Binary files /dev/null and b/data/icons/128x128/cd.png differ diff --git a/data/icons/128x128/cdcase.png b/data/icons/128x128/cdcase.png new file mode 100644 index 00000000..90a24fc7 Binary files /dev/null and b/data/icons/128x128/cdcase.png differ diff --git a/data/icons/128x128/configure.png b/data/icons/128x128/configure.png new file mode 100644 index 00000000..82359c54 Binary files /dev/null and b/data/icons/128x128/configure.png differ diff --git a/data/icons/128x128/device-ipod-nano.png b/data/icons/128x128/device-ipod-nano.png new file mode 100644 index 00000000..9413b71a Binary files /dev/null and b/data/icons/128x128/device-ipod-nano.png differ diff --git a/data/icons/128x128/device-ipod.png b/data/icons/128x128/device-ipod.png new file mode 100644 index 00000000..83616f8d Binary files /dev/null and b/data/icons/128x128/device-ipod.png differ diff --git a/data/icons/128x128/device-phone.png b/data/icons/128x128/device-phone.png new file mode 100644 index 00000000..8f00f1ac Binary files /dev/null and b/data/icons/128x128/device-phone.png differ diff --git a/data/icons/128x128/device-usb-drive.png b/data/icons/128x128/device-usb-drive.png new file mode 100644 index 00000000..6dab4ba2 Binary files /dev/null and b/data/icons/128x128/device-usb-drive.png differ diff --git a/data/icons/128x128/device-usb-flash.png b/data/icons/128x128/device-usb-flash.png new file mode 100644 index 00000000..f057ffff Binary files /dev/null and b/data/icons/128x128/device-usb-flash.png differ diff --git a/data/icons/128x128/device.png b/data/icons/128x128/device.png new file mode 100644 index 00000000..4921296d Binary files /dev/null and b/data/icons/128x128/device.png differ diff --git a/data/icons/128x128/dialog-error.png b/data/icons/128x128/dialog-error.png new file mode 100644 index 00000000..43e16862 Binary files /dev/null and b/data/icons/128x128/dialog-error.png differ diff --git a/data/icons/128x128/dialog-information.png b/data/icons/128x128/dialog-information.png new file mode 100644 index 00000000..2637bab3 Binary files /dev/null and b/data/icons/128x128/dialog-information.png differ diff --git a/data/icons/128x128/dialog-ok-apply.png b/data/icons/128x128/dialog-ok-apply.png new file mode 100644 index 00000000..85adb8e6 Binary files /dev/null and b/data/icons/128x128/dialog-ok-apply.png differ diff --git a/data/icons/128x128/dialog-password.png b/data/icons/128x128/dialog-password.png new file mode 100644 index 00000000..9f5c846f Binary files /dev/null and b/data/icons/128x128/dialog-password.png differ diff --git a/data/icons/128x128/dialog-warning.png b/data/icons/128x128/dialog-warning.png new file mode 100644 index 00000000..69b7d18e Binary files /dev/null and b/data/icons/128x128/dialog-warning.png differ diff --git a/data/icons/128x128/document-download.png b/data/icons/128x128/document-download.png new file mode 100644 index 00000000..740ad06d Binary files /dev/null and b/data/icons/128x128/document-download.png differ diff --git a/data/icons/128x128/document-new.png b/data/icons/128x128/document-new.png new file mode 100644 index 00000000..b8337a92 Binary files /dev/null and b/data/icons/128x128/document-new.png differ diff --git a/data/icons/128x128/document-open-folder.png b/data/icons/128x128/document-open-folder.png new file mode 100644 index 00000000..17e788c8 Binary files /dev/null and b/data/icons/128x128/document-open-folder.png differ diff --git a/data/icons/128x128/document-open.png b/data/icons/128x128/document-open.png new file mode 100644 index 00000000..6fc3659f Binary files /dev/null and b/data/icons/128x128/document-open.png differ diff --git a/data/icons/128x128/document-save.png b/data/icons/128x128/document-save.png new file mode 100644 index 00000000..f4c31002 Binary files /dev/null and b/data/icons/128x128/document-save.png differ diff --git a/data/icons/128x128/document-search.png b/data/icons/128x128/document-search.png new file mode 100644 index 00000000..eaf84f25 Binary files /dev/null and b/data/icons/128x128/document-search.png differ diff --git a/data/icons/128x128/download.png b/data/icons/128x128/download.png new file mode 100644 index 00000000..b85f1476 Binary files /dev/null and b/data/icons/128x128/download.png differ diff --git a/data/icons/128x128/edit-clear-list.png b/data/icons/128x128/edit-clear-list.png new file mode 100644 index 00000000..75dfded9 Binary files /dev/null and b/data/icons/128x128/edit-clear-list.png differ diff --git a/data/icons/128x128/edit-clear-locationbar-ltr.png b/data/icons/128x128/edit-clear-locationbar-ltr.png new file mode 100644 index 00000000..a574405c Binary files /dev/null and b/data/icons/128x128/edit-clear-locationbar-ltr.png differ diff --git a/data/icons/128x128/edit-copy.png b/data/icons/128x128/edit-copy.png new file mode 100644 index 00000000..ca3efe10 Binary files /dev/null and b/data/icons/128x128/edit-copy.png differ diff --git a/data/icons/128x128/edit-delete.png b/data/icons/128x128/edit-delete.png new file mode 100644 index 00000000..6aa5c8d5 Binary files /dev/null and b/data/icons/128x128/edit-delete.png differ diff --git a/data/icons/128x128/edit-find.png b/data/icons/128x128/edit-find.png new file mode 100644 index 00000000..14cb77cb Binary files /dev/null and b/data/icons/128x128/edit-find.png differ diff --git a/data/icons/128x128/edit-redo.png b/data/icons/128x128/edit-redo.png new file mode 100644 index 00000000..b5f28816 Binary files /dev/null and b/data/icons/128x128/edit-redo.png differ diff --git a/data/icons/128x128/edit-rename.png b/data/icons/128x128/edit-rename.png new file mode 100644 index 00000000..7288dc95 Binary files /dev/null and b/data/icons/128x128/edit-rename.png differ diff --git a/data/icons/128x128/edit-undo.png b/data/icons/128x128/edit-undo.png new file mode 100644 index 00000000..3091ed97 Binary files /dev/null and b/data/icons/128x128/edit-undo.png differ diff --git a/data/icons/128x128/electrocompaniet.png b/data/icons/128x128/electrocompaniet.png new file mode 100644 index 00000000..042b1f4e Binary files /dev/null and b/data/icons/128x128/electrocompaniet.png differ diff --git a/data/icons/128x128/equalizer.png b/data/icons/128x128/equalizer.png new file mode 100644 index 00000000..879954f5 Binary files /dev/null and b/data/icons/128x128/equalizer.png differ diff --git a/data/icons/128x128/folder-new.png b/data/icons/128x128/folder-new.png new file mode 100644 index 00000000..2dc039a8 Binary files /dev/null and b/data/icons/128x128/folder-new.png differ diff --git a/data/icons/128x128/folder-sound.png b/data/icons/128x128/folder-sound.png new file mode 100644 index 00000000..382b0071 Binary files /dev/null and b/data/icons/128x128/folder-sound.png differ diff --git a/data/icons/128x128/folder.png b/data/icons/128x128/folder.png new file mode 100644 index 00000000..ec965e24 Binary files /dev/null and b/data/icons/128x128/folder.png differ diff --git a/data/icons/128x128/footsteps.png b/data/icons/128x128/footsteps.png new file mode 100644 index 00000000..6594fab9 Binary files /dev/null and b/data/icons/128x128/footsteps.png differ diff --git a/data/icons/128x128/go-down.png b/data/icons/128x128/go-down.png new file mode 100644 index 00000000..0f8d1dfc Binary files /dev/null and b/data/icons/128x128/go-down.png differ diff --git a/data/icons/128x128/go-home.png b/data/icons/128x128/go-home.png new file mode 100644 index 00000000..7c395132 Binary files /dev/null and b/data/icons/128x128/go-home.png differ diff --git a/data/icons/128x128/go-jump.png b/data/icons/128x128/go-jump.png new file mode 100644 index 00000000..d0afd299 Binary files /dev/null and b/data/icons/128x128/go-jump.png differ diff --git a/data/icons/128x128/go-next.png b/data/icons/128x128/go-next.png new file mode 100644 index 00000000..971c4191 Binary files /dev/null and b/data/icons/128x128/go-next.png differ diff --git a/data/icons/128x128/go-previous.png b/data/icons/128x128/go-previous.png new file mode 100644 index 00000000..ce2bd626 Binary files /dev/null and b/data/icons/128x128/go-previous.png differ diff --git a/data/icons/128x128/go-up.png b/data/icons/128x128/go-up.png new file mode 100644 index 00000000..e799a7f5 Binary files /dev/null and b/data/icons/128x128/go-up.png differ diff --git a/data/icons/128x128/gstreamer.png b/data/icons/128x128/gstreamer.png new file mode 100644 index 00000000..20b40c9d Binary files /dev/null and b/data/icons/128x128/gstreamer.png differ diff --git a/data/icons/128x128/guitar.png b/data/icons/128x128/guitar.png new file mode 100644 index 00000000..0844bbf9 Binary files /dev/null and b/data/icons/128x128/guitar.png differ diff --git a/data/icons/128x128/headset.png b/data/icons/128x128/headset.png new file mode 100644 index 00000000..11a269a5 Binary files /dev/null and b/data/icons/128x128/headset.png differ diff --git a/data/icons/128x128/help-hint.png b/data/icons/128x128/help-hint.png new file mode 100644 index 00000000..97f43470 Binary files /dev/null and b/data/icons/128x128/help-hint.png differ diff --git a/data/icons/128x128/intel.png b/data/icons/128x128/intel.png new file mode 100644 index 00000000..75ed434f Binary files /dev/null and b/data/icons/128x128/intel.png differ diff --git a/data/icons/128x128/jack.png b/data/icons/128x128/jack.png new file mode 100644 index 00000000..ed6e524c Binary files /dev/null and b/data/icons/128x128/jack.png differ diff --git a/data/icons/128x128/keyboard.png b/data/icons/128x128/keyboard.png new file mode 100644 index 00000000..c3157fa3 Binary files /dev/null and b/data/icons/128x128/keyboard.png differ diff --git a/data/icons/128x128/list-add.png b/data/icons/128x128/list-add.png new file mode 100644 index 00000000..7bb8669d Binary files /dev/null and b/data/icons/128x128/list-add.png differ diff --git a/data/icons/128x128/list-remove.png b/data/icons/128x128/list-remove.png new file mode 100644 index 00000000..4ce541a9 Binary files /dev/null and b/data/icons/128x128/list-remove.png differ diff --git a/data/icons/128x128/mcintosh-player.png b/data/icons/128x128/mcintosh-player.png new file mode 100644 index 00000000..50202f6a Binary files /dev/null and b/data/icons/128x128/mcintosh-player.png differ diff --git a/data/icons/128x128/mcintosh-text.png b/data/icons/128x128/mcintosh-text.png new file mode 100644 index 00000000..0086aab6 Binary files /dev/null and b/data/icons/128x128/mcintosh-text.png differ diff --git a/data/icons/128x128/media-eject.png b/data/icons/128x128/media-eject.png new file mode 100644 index 00000000..8cca48a4 Binary files /dev/null and b/data/icons/128x128/media-eject.png differ diff --git a/data/icons/128x128/media-forward.png b/data/icons/128x128/media-forward.png new file mode 100644 index 00000000..01b50df0 Binary files /dev/null and b/data/icons/128x128/media-forward.png differ diff --git a/data/icons/128x128/media-pause.png b/data/icons/128x128/media-pause.png new file mode 100644 index 00000000..8af21c05 Binary files /dev/null and b/data/icons/128x128/media-pause.png differ diff --git a/data/icons/128x128/media-play.png b/data/icons/128x128/media-play.png new file mode 100644 index 00000000..56635626 Binary files /dev/null and b/data/icons/128x128/media-play.png differ diff --git a/data/icons/128x128/media-rewind.png b/data/icons/128x128/media-rewind.png new file mode 100644 index 00000000..2f3e9347 Binary files /dev/null and b/data/icons/128x128/media-rewind.png differ diff --git a/data/icons/128x128/media-stop.png b/data/icons/128x128/media-stop.png new file mode 100644 index 00000000..8a655398 Binary files /dev/null and b/data/icons/128x128/media-stop.png differ diff --git a/data/icons/128x128/nvidia.png b/data/icons/128x128/nvidia.png new file mode 100644 index 00000000..9efba2d9 Binary files /dev/null and b/data/icons/128x128/nvidia.png differ diff --git a/data/icons/128x128/play2.png b/data/icons/128x128/play2.png new file mode 100644 index 00000000..56635626 Binary files /dev/null and b/data/icons/128x128/play2.png differ diff --git a/data/icons/128x128/realtek.png b/data/icons/128x128/realtek.png new file mode 100644 index 00000000..f7bb315f Binary files /dev/null and b/data/icons/128x128/realtek.png differ diff --git a/data/icons/128x128/search.png b/data/icons/128x128/search.png new file mode 100644 index 00000000..c6f61d85 Binary files /dev/null and b/data/icons/128x128/search.png differ diff --git a/data/icons/128x128/soundcard.png b/data/icons/128x128/soundcard.png new file mode 100644 index 00000000..1c761f90 Binary files /dev/null and b/data/icons/128x128/soundcard.png differ diff --git a/data/icons/128x128/soundcard2.png b/data/icons/128x128/soundcard2.png new file mode 100644 index 00000000..fc99ab83 Binary files /dev/null and b/data/icons/128x128/soundcard2.png differ diff --git a/data/icons/128x128/speaker.png b/data/icons/128x128/speaker.png new file mode 100644 index 00000000..eb5c2cf3 Binary files /dev/null and b/data/icons/128x128/speaker.png differ diff --git a/data/icons/128x128/star-grey.png b/data/icons/128x128/star-grey.png new file mode 100644 index 00000000..991733d0 Binary files /dev/null and b/data/icons/128x128/star-grey.png differ diff --git a/data/icons/128x128/star.png b/data/icons/128x128/star.png new file mode 100644 index 00000000..804e8c0a Binary files /dev/null and b/data/icons/128x128/star.png differ diff --git a/data/icons/128x128/strawberry-panel-grey.png b/data/icons/128x128/strawberry-panel-grey.png new file mode 100644 index 00000000..cac3b262 Binary files /dev/null and b/data/icons/128x128/strawberry-panel-grey.png differ diff --git a/data/icons/128x128/strawberry-panel.png b/data/icons/128x128/strawberry-panel.png new file mode 100644 index 00000000..4d575b1a Binary files /dev/null and b/data/icons/128x128/strawberry-panel.png differ diff --git a/data/icons/128x128/strawberry.png b/data/icons/128x128/strawberry.png new file mode 100644 index 00000000..f3312ba3 Binary files /dev/null and b/data/icons/128x128/strawberry.png differ diff --git a/data/icons/128x128/strawberry.svg b/data/icons/128x128/strawberry.svg new file mode 100644 index 00000000..b48ca3f2 --- /dev/null +++ b/data/icons/128x128/strawberry.svg @@ -0,0 +1,454 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/data/icons/128x128/tools-wizard.png b/data/icons/128x128/tools-wizard.png new file mode 100644 index 00000000..aa671fb9 Binary files /dev/null and b/data/icons/128x128/tools-wizard.png differ diff --git a/data/icons/128x128/view-choose.png b/data/icons/128x128/view-choose.png new file mode 100644 index 00000000..edc91e1d Binary files /dev/null and b/data/icons/128x128/view-choose.png differ diff --git a/data/icons/128x128/view-fullscreen.png b/data/icons/128x128/view-fullscreen.png new file mode 100644 index 00000000..918298bc Binary files /dev/null and b/data/icons/128x128/view-fullscreen.png differ diff --git a/data/icons/128x128/view-media-lyrics.png b/data/icons/128x128/view-media-lyrics.png new file mode 100644 index 00000000..1abb4858 Binary files /dev/null and b/data/icons/128x128/view-media-lyrics.png differ diff --git a/data/icons/128x128/view-media-playlist.png b/data/icons/128x128/view-media-playlist.png new file mode 100644 index 00000000..b4ac9133 Binary files /dev/null and b/data/icons/128x128/view-media-playlist.png differ diff --git a/data/icons/128x128/view-media-visualization.png b/data/icons/128x128/view-media-visualization.png new file mode 100644 index 00000000..caabe002 Binary files /dev/null and b/data/icons/128x128/view-media-visualization.png differ diff --git a/data/icons/128x128/view-refresh.png b/data/icons/128x128/view-refresh.png new file mode 100644 index 00000000..788edaf9 Binary files /dev/null and b/data/icons/128x128/view-refresh.png differ diff --git a/data/icons/128x128/vinyl.png b/data/icons/128x128/vinyl.png new file mode 100644 index 00000000..d6a84880 Binary files /dev/null and b/data/icons/128x128/vinyl.png differ diff --git a/data/icons/128x128/vlc.png b/data/icons/128x128/vlc.png new file mode 100644 index 00000000..abad2e98 Binary files /dev/null and b/data/icons/128x128/vlc.png differ diff --git a/data/icons/128x128/xine.png b/data/icons/128x128/xine.png new file mode 100644 index 00000000..600a57e9 Binary files /dev/null and b/data/icons/128x128/xine.png differ diff --git a/data/icons/128x128/zoom-in.png b/data/icons/128x128/zoom-in.png new file mode 100644 index 00000000..3bbb3605 Binary files /dev/null and b/data/icons/128x128/zoom-in.png differ diff --git a/data/icons/128x128/zoom-out.png b/data/icons/128x128/zoom-out.png new file mode 100644 index 00000000..ba5013d3 Binary files /dev/null and b/data/icons/128x128/zoom-out.png differ diff --git a/data/icons/22x22/albums.png b/data/icons/22x22/albums.png new file mode 100644 index 00000000..aa919c32 Binary files /dev/null and b/data/icons/22x22/albums.png differ diff --git a/data/icons/22x22/alsa.png b/data/icons/22x22/alsa.png new file mode 100644 index 00000000..9864f089 Binary files /dev/null and b/data/icons/22x22/alsa.png differ diff --git a/data/icons/22x22/application-exit.png b/data/icons/22x22/application-exit.png new file mode 100644 index 00000000..ed5f8b25 Binary files /dev/null and b/data/icons/22x22/application-exit.png differ diff --git a/data/icons/22x22/applications-internet.png b/data/icons/22x22/applications-internet.png new file mode 100644 index 00000000..8903fc34 Binary files /dev/null and b/data/icons/22x22/applications-internet.png differ diff --git a/data/icons/22x22/bluetooth.png b/data/icons/22x22/bluetooth.png new file mode 100644 index 00000000..d1a50650 Binary files /dev/null and b/data/icons/22x22/bluetooth.png differ diff --git a/data/icons/22x22/cd.png b/data/icons/22x22/cd.png new file mode 100644 index 00000000..dd620e09 Binary files /dev/null and b/data/icons/22x22/cd.png differ diff --git a/data/icons/22x22/cdcase.png b/data/icons/22x22/cdcase.png new file mode 100644 index 00000000..0c7f4e6f Binary files /dev/null and b/data/icons/22x22/cdcase.png differ diff --git a/data/icons/22x22/configure.png b/data/icons/22x22/configure.png new file mode 100644 index 00000000..45b8fae8 Binary files /dev/null and b/data/icons/22x22/configure.png differ diff --git a/data/icons/22x22/device-ipod-nano.png b/data/icons/22x22/device-ipod-nano.png new file mode 100644 index 00000000..0f2320f2 Binary files /dev/null and b/data/icons/22x22/device-ipod-nano.png differ diff --git a/data/icons/22x22/device-ipod.png b/data/icons/22x22/device-ipod.png new file mode 100644 index 00000000..4387a621 Binary files /dev/null and b/data/icons/22x22/device-ipod.png differ diff --git a/data/icons/22x22/device-phone.png b/data/icons/22x22/device-phone.png new file mode 100644 index 00000000..89a8b75d Binary files /dev/null and b/data/icons/22x22/device-phone.png differ diff --git a/data/icons/22x22/device-usb-drive.png b/data/icons/22x22/device-usb-drive.png new file mode 100644 index 00000000..b4a1274d Binary files /dev/null and b/data/icons/22x22/device-usb-drive.png differ diff --git a/data/icons/22x22/device-usb-flash.png b/data/icons/22x22/device-usb-flash.png new file mode 100644 index 00000000..a5036042 Binary files /dev/null and b/data/icons/22x22/device-usb-flash.png differ diff --git a/data/icons/22x22/device.png b/data/icons/22x22/device.png new file mode 100644 index 00000000..a081cfb8 Binary files /dev/null and b/data/icons/22x22/device.png differ diff --git a/data/icons/22x22/dialog-error.png b/data/icons/22x22/dialog-error.png new file mode 100644 index 00000000..b9de536b Binary files /dev/null and b/data/icons/22x22/dialog-error.png differ diff --git a/data/icons/22x22/dialog-information.png b/data/icons/22x22/dialog-information.png new file mode 100644 index 00000000..896f8511 Binary files /dev/null and b/data/icons/22x22/dialog-information.png differ diff --git a/data/icons/22x22/dialog-ok-apply.png b/data/icons/22x22/dialog-ok-apply.png new file mode 100644 index 00000000..d35ce53a Binary files /dev/null and b/data/icons/22x22/dialog-ok-apply.png differ diff --git a/data/icons/22x22/dialog-password.png b/data/icons/22x22/dialog-password.png new file mode 100644 index 00000000..11b6344c Binary files /dev/null and b/data/icons/22x22/dialog-password.png differ diff --git a/data/icons/22x22/dialog-warning.png b/data/icons/22x22/dialog-warning.png new file mode 100644 index 00000000..01ffffe8 Binary files /dev/null and b/data/icons/22x22/dialog-warning.png differ diff --git a/data/icons/22x22/document-download.png b/data/icons/22x22/document-download.png new file mode 100644 index 00000000..ee2a60c6 Binary files /dev/null and b/data/icons/22x22/document-download.png differ diff --git a/data/icons/22x22/document-new.png b/data/icons/22x22/document-new.png new file mode 100644 index 00000000..fbb4ce05 Binary files /dev/null and b/data/icons/22x22/document-new.png differ diff --git a/data/icons/22x22/document-open-folder.png b/data/icons/22x22/document-open-folder.png new file mode 100644 index 00000000..0f0e1828 Binary files /dev/null and b/data/icons/22x22/document-open-folder.png differ diff --git a/data/icons/22x22/document-open.png b/data/icons/22x22/document-open.png new file mode 100644 index 00000000..bc8cface Binary files /dev/null and b/data/icons/22x22/document-open.png differ diff --git a/data/icons/22x22/document-save.png b/data/icons/22x22/document-save.png new file mode 100644 index 00000000..a81e70d4 Binary files /dev/null and b/data/icons/22x22/document-save.png differ diff --git a/data/icons/22x22/document-search.png b/data/icons/22x22/document-search.png new file mode 100644 index 00000000..a16fc86f Binary files /dev/null and b/data/icons/22x22/document-search.png differ diff --git a/data/icons/22x22/download.png b/data/icons/22x22/download.png new file mode 100644 index 00000000..4b6f76be Binary files /dev/null and b/data/icons/22x22/download.png differ diff --git a/data/icons/22x22/edit-clear-list.png b/data/icons/22x22/edit-clear-list.png new file mode 100644 index 00000000..3588e9bf Binary files /dev/null and b/data/icons/22x22/edit-clear-list.png differ diff --git a/data/icons/22x22/edit-clear-locationbar-ltr.png b/data/icons/22x22/edit-clear-locationbar-ltr.png new file mode 100644 index 00000000..8d902d57 Binary files /dev/null and b/data/icons/22x22/edit-clear-locationbar-ltr.png differ diff --git a/data/icons/22x22/edit-copy.png b/data/icons/22x22/edit-copy.png new file mode 100644 index 00000000..d34cdcd3 Binary files /dev/null and b/data/icons/22x22/edit-copy.png differ diff --git a/data/icons/22x22/edit-delete.png b/data/icons/22x22/edit-delete.png new file mode 100644 index 00000000..b0de61d2 Binary files /dev/null and b/data/icons/22x22/edit-delete.png differ diff --git a/data/icons/22x22/edit-find.png b/data/icons/22x22/edit-find.png new file mode 100644 index 00000000..1b7a2528 Binary files /dev/null and b/data/icons/22x22/edit-find.png differ diff --git a/data/icons/22x22/edit-redo.png b/data/icons/22x22/edit-redo.png new file mode 100644 index 00000000..45f04502 Binary files /dev/null and b/data/icons/22x22/edit-redo.png differ diff --git a/data/icons/22x22/edit-rename.png b/data/icons/22x22/edit-rename.png new file mode 100644 index 00000000..ea8872fe Binary files /dev/null and b/data/icons/22x22/edit-rename.png differ diff --git a/data/icons/22x22/edit-undo.png b/data/icons/22x22/edit-undo.png new file mode 100644 index 00000000..57abbe17 Binary files /dev/null and b/data/icons/22x22/edit-undo.png differ diff --git a/data/icons/22x22/electrocompaniet.png b/data/icons/22x22/electrocompaniet.png new file mode 100644 index 00000000..10175028 Binary files /dev/null and b/data/icons/22x22/electrocompaniet.png differ diff --git a/data/icons/22x22/equalizer.png b/data/icons/22x22/equalizer.png new file mode 100644 index 00000000..0dfcd9a4 Binary files /dev/null and b/data/icons/22x22/equalizer.png differ diff --git a/data/icons/22x22/folder-new.png b/data/icons/22x22/folder-new.png new file mode 100644 index 00000000..bd34174a Binary files /dev/null and b/data/icons/22x22/folder-new.png differ diff --git a/data/icons/22x22/folder-sound.png b/data/icons/22x22/folder-sound.png new file mode 100644 index 00000000..45028810 Binary files /dev/null and b/data/icons/22x22/folder-sound.png differ diff --git a/data/icons/22x22/folder.png b/data/icons/22x22/folder.png new file mode 100644 index 00000000..d984a133 Binary files /dev/null and b/data/icons/22x22/folder.png differ diff --git a/data/icons/22x22/footsteps.png b/data/icons/22x22/footsteps.png new file mode 100644 index 00000000..a92583ea Binary files /dev/null and b/data/icons/22x22/footsteps.png differ diff --git a/data/icons/22x22/go-down.png b/data/icons/22x22/go-down.png new file mode 100644 index 00000000..63331a57 Binary files /dev/null and b/data/icons/22x22/go-down.png differ diff --git a/data/icons/22x22/go-home.png b/data/icons/22x22/go-home.png new file mode 100644 index 00000000..aab6a883 Binary files /dev/null and b/data/icons/22x22/go-home.png differ diff --git a/data/icons/22x22/go-jump.png b/data/icons/22x22/go-jump.png new file mode 100644 index 00000000..74e837d5 Binary files /dev/null and b/data/icons/22x22/go-jump.png differ diff --git a/data/icons/22x22/go-next.png b/data/icons/22x22/go-next.png new file mode 100644 index 00000000..aa7cbb91 Binary files /dev/null and b/data/icons/22x22/go-next.png differ diff --git a/data/icons/22x22/go-previous.png b/data/icons/22x22/go-previous.png new file mode 100644 index 00000000..8230340b Binary files /dev/null and b/data/icons/22x22/go-previous.png differ diff --git a/data/icons/22x22/go-up.png b/data/icons/22x22/go-up.png new file mode 100644 index 00000000..4459024e Binary files /dev/null and b/data/icons/22x22/go-up.png differ diff --git a/data/icons/22x22/gstreamer.png b/data/icons/22x22/gstreamer.png new file mode 100644 index 00000000..08604acf Binary files /dev/null and b/data/icons/22x22/gstreamer.png differ diff --git a/data/icons/22x22/guitar.png b/data/icons/22x22/guitar.png new file mode 100644 index 00000000..626cfdfe Binary files /dev/null and b/data/icons/22x22/guitar.png differ diff --git a/data/icons/22x22/headset.png b/data/icons/22x22/headset.png new file mode 100644 index 00000000..e7022294 Binary files /dev/null and b/data/icons/22x22/headset.png differ diff --git a/data/icons/22x22/help-hint.png b/data/icons/22x22/help-hint.png new file mode 100644 index 00000000..63c368cb Binary files /dev/null and b/data/icons/22x22/help-hint.png differ diff --git a/data/icons/22x22/intel.png b/data/icons/22x22/intel.png new file mode 100644 index 00000000..096125b4 Binary files /dev/null and b/data/icons/22x22/intel.png differ diff --git a/data/icons/22x22/jack.png b/data/icons/22x22/jack.png new file mode 100644 index 00000000..92634cb2 Binary files /dev/null and b/data/icons/22x22/jack.png differ diff --git a/data/icons/22x22/keyboard.png b/data/icons/22x22/keyboard.png new file mode 100644 index 00000000..3439c76b Binary files /dev/null and b/data/icons/22x22/keyboard.png differ diff --git a/data/icons/22x22/list-add.png b/data/icons/22x22/list-add.png new file mode 100644 index 00000000..e029787c Binary files /dev/null and b/data/icons/22x22/list-add.png differ diff --git a/data/icons/22x22/list-remove.png b/data/icons/22x22/list-remove.png new file mode 100644 index 00000000..2bb1a598 Binary files /dev/null and b/data/icons/22x22/list-remove.png differ diff --git a/data/icons/22x22/mcintosh-player.png b/data/icons/22x22/mcintosh-player.png new file mode 100644 index 00000000..821e877f Binary files /dev/null and b/data/icons/22x22/mcintosh-player.png differ diff --git a/data/icons/22x22/mcintosh-text.png b/data/icons/22x22/mcintosh-text.png new file mode 100644 index 00000000..2200cf22 Binary files /dev/null and b/data/icons/22x22/mcintosh-text.png differ diff --git a/data/icons/22x22/mcintosh.png b/data/icons/22x22/mcintosh.png new file mode 100644 index 00000000..019790f1 Binary files /dev/null and b/data/icons/22x22/mcintosh.png differ diff --git a/data/icons/22x22/media-eject.png b/data/icons/22x22/media-eject.png new file mode 100644 index 00000000..06c4c537 Binary files /dev/null and b/data/icons/22x22/media-eject.png differ diff --git a/data/icons/22x22/media-forward.png b/data/icons/22x22/media-forward.png new file mode 100644 index 00000000..c11ee5d3 Binary files /dev/null and b/data/icons/22x22/media-forward.png differ diff --git a/data/icons/22x22/media-pause.png b/data/icons/22x22/media-pause.png new file mode 100644 index 00000000..0b85f80c Binary files /dev/null and b/data/icons/22x22/media-pause.png differ diff --git a/data/icons/22x22/media-play.png b/data/icons/22x22/media-play.png new file mode 100644 index 00000000..2ea18a21 Binary files /dev/null and b/data/icons/22x22/media-play.png differ diff --git a/data/icons/22x22/media-playlist-repeat.png b/data/icons/22x22/media-playlist-repeat.png new file mode 100644 index 00000000..b8b48b84 Binary files /dev/null and b/data/icons/22x22/media-playlist-repeat.png differ diff --git a/data/icons/22x22/media-playlist-shuffle.png b/data/icons/22x22/media-playlist-shuffle.png new file mode 100644 index 00000000..7a5ef060 Binary files /dev/null and b/data/icons/22x22/media-playlist-shuffle.png differ diff --git a/data/icons/22x22/media-rewind.png b/data/icons/22x22/media-rewind.png new file mode 100644 index 00000000..6e1976b4 Binary files /dev/null and b/data/icons/22x22/media-rewind.png differ diff --git a/data/icons/22x22/media-stop.png b/data/icons/22x22/media-stop.png new file mode 100644 index 00000000..65364ea5 Binary files /dev/null and b/data/icons/22x22/media-stop.png differ diff --git a/data/icons/22x22/nvidia.png b/data/icons/22x22/nvidia.png new file mode 100644 index 00000000..0fce68e9 Binary files /dev/null and b/data/icons/22x22/nvidia.png differ diff --git a/data/icons/22x22/play2.png b/data/icons/22x22/play2.png new file mode 100644 index 00000000..2ea18a21 Binary files /dev/null and b/data/icons/22x22/play2.png differ diff --git a/data/icons/22x22/pulseaudio.png b/data/icons/22x22/pulseaudio.png new file mode 100644 index 00000000..eb2249ea Binary files /dev/null and b/data/icons/22x22/pulseaudio.png differ diff --git a/data/icons/22x22/realtek.png b/data/icons/22x22/realtek.png new file mode 100644 index 00000000..47935ba4 Binary files /dev/null and b/data/icons/22x22/realtek.png differ diff --git a/data/icons/22x22/search.png b/data/icons/22x22/search.png new file mode 100644 index 00000000..9b794aaa Binary files /dev/null and b/data/icons/22x22/search.png differ diff --git a/data/icons/22x22/soundcard.png b/data/icons/22x22/soundcard.png new file mode 100644 index 00000000..d5a7ad47 Binary files /dev/null and b/data/icons/22x22/soundcard.png differ diff --git a/data/icons/22x22/soundcard2.png b/data/icons/22x22/soundcard2.png new file mode 100644 index 00000000..6f65e560 Binary files /dev/null and b/data/icons/22x22/soundcard2.png differ diff --git a/data/icons/22x22/speaker.png b/data/icons/22x22/speaker.png new file mode 100644 index 00000000..23bf9679 Binary files /dev/null and b/data/icons/22x22/speaker.png differ diff --git a/data/icons/22x22/star-grey.png b/data/icons/22x22/star-grey.png new file mode 100644 index 00000000..1698dc3d Binary files /dev/null and b/data/icons/22x22/star-grey.png differ diff --git a/data/icons/22x22/star.png b/data/icons/22x22/star.png new file mode 100644 index 00000000..92c57f2b Binary files /dev/null and b/data/icons/22x22/star.png differ diff --git a/data/icons/22x22/strawberry-panel-grey.png b/data/icons/22x22/strawberry-panel-grey.png new file mode 100644 index 00000000..8ad482b4 Binary files /dev/null and b/data/icons/22x22/strawberry-panel-grey.png differ diff --git a/data/icons/22x22/strawberry-panel.png b/data/icons/22x22/strawberry-panel.png new file mode 100644 index 00000000..004df73e Binary files /dev/null and b/data/icons/22x22/strawberry-panel.png differ diff --git a/data/icons/22x22/strawberry.png b/data/icons/22x22/strawberry.png new file mode 100644 index 00000000..82b743dc Binary files /dev/null and b/data/icons/22x22/strawberry.png differ diff --git a/data/icons/22x22/strawberry.svg b/data/icons/22x22/strawberry.svg new file mode 100644 index 00000000..17eba06c --- /dev/null +++ b/data/icons/22x22/strawberry.svg @@ -0,0 +1,489 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/icons/22x22/tools-wizard.png b/data/icons/22x22/tools-wizard.png new file mode 100644 index 00000000..b969f46d Binary files /dev/null and b/data/icons/22x22/tools-wizard.png differ diff --git a/data/icons/22x22/view-choose.png b/data/icons/22x22/view-choose.png new file mode 100644 index 00000000..c0fa58ec Binary files /dev/null and b/data/icons/22x22/view-choose.png differ diff --git a/data/icons/22x22/view-fullscreen.png b/data/icons/22x22/view-fullscreen.png new file mode 100644 index 00000000..ceda8189 Binary files /dev/null and b/data/icons/22x22/view-fullscreen.png differ diff --git a/data/icons/22x22/view-media-lyrics.png b/data/icons/22x22/view-media-lyrics.png new file mode 100644 index 00000000..9c173d77 Binary files /dev/null and b/data/icons/22x22/view-media-lyrics.png differ diff --git a/data/icons/22x22/view-media-playlist.png b/data/icons/22x22/view-media-playlist.png new file mode 100644 index 00000000..181958ba Binary files /dev/null and b/data/icons/22x22/view-media-playlist.png differ diff --git a/data/icons/22x22/view-media-visualization.png b/data/icons/22x22/view-media-visualization.png new file mode 100644 index 00000000..e31f00e9 Binary files /dev/null and b/data/icons/22x22/view-media-visualization.png differ diff --git a/data/icons/22x22/view-refresh.png b/data/icons/22x22/view-refresh.png new file mode 100644 index 00000000..45b5535c Binary files /dev/null and b/data/icons/22x22/view-refresh.png differ diff --git a/data/icons/22x22/vinyl.png b/data/icons/22x22/vinyl.png new file mode 100644 index 00000000..2691fab4 Binary files /dev/null and b/data/icons/22x22/vinyl.png differ diff --git a/data/icons/22x22/vlc.png b/data/icons/22x22/vlc.png new file mode 100644 index 00000000..43b20c3a Binary files /dev/null and b/data/icons/22x22/vlc.png differ diff --git a/data/icons/22x22/xine.png b/data/icons/22x22/xine.png new file mode 100644 index 00000000..150c72c1 Binary files /dev/null and b/data/icons/22x22/xine.png differ diff --git a/data/icons/22x22/zoom-in.png b/data/icons/22x22/zoom-in.png new file mode 100644 index 00000000..8393e281 Binary files /dev/null and b/data/icons/22x22/zoom-in.png differ diff --git a/data/icons/22x22/zoom-out.png b/data/icons/22x22/zoom-out.png new file mode 100644 index 00000000..d39d4860 Binary files /dev/null and b/data/icons/22x22/zoom-out.png differ diff --git a/data/icons/32x32/albums.png b/data/icons/32x32/albums.png new file mode 100644 index 00000000..747013f1 Binary files /dev/null and b/data/icons/32x32/albums.png differ diff --git a/data/icons/32x32/alsa.png b/data/icons/32x32/alsa.png new file mode 100644 index 00000000..2924f721 Binary files /dev/null and b/data/icons/32x32/alsa.png differ diff --git a/data/icons/32x32/application-exit.png b/data/icons/32x32/application-exit.png new file mode 100644 index 00000000..1aeeb4d8 Binary files /dev/null and b/data/icons/32x32/application-exit.png differ diff --git a/data/icons/32x32/applications-internet.png b/data/icons/32x32/applications-internet.png new file mode 100644 index 00000000..0fcd3114 Binary files /dev/null and b/data/icons/32x32/applications-internet.png differ diff --git a/data/icons/32x32/bluetooth.png b/data/icons/32x32/bluetooth.png new file mode 100644 index 00000000..39e8061e Binary files /dev/null and b/data/icons/32x32/bluetooth.png differ diff --git a/data/icons/32x32/cd.png b/data/icons/32x32/cd.png new file mode 100644 index 00000000..2cc20df7 Binary files /dev/null and b/data/icons/32x32/cd.png differ diff --git a/data/icons/32x32/cdcase.png b/data/icons/32x32/cdcase.png new file mode 100644 index 00000000..2f5eac8e Binary files /dev/null and b/data/icons/32x32/cdcase.png differ diff --git a/data/icons/32x32/configure.png b/data/icons/32x32/configure.png new file mode 100644 index 00000000..7c259158 Binary files /dev/null and b/data/icons/32x32/configure.png differ diff --git a/data/icons/32x32/device-ipod-nano.png b/data/icons/32x32/device-ipod-nano.png new file mode 100644 index 00000000..7d54e381 Binary files /dev/null and b/data/icons/32x32/device-ipod-nano.png differ diff --git a/data/icons/32x32/device-ipod.png b/data/icons/32x32/device-ipod.png new file mode 100644 index 00000000..4e632086 Binary files /dev/null and b/data/icons/32x32/device-ipod.png differ diff --git a/data/icons/32x32/device-phone.png b/data/icons/32x32/device-phone.png new file mode 100644 index 00000000..f50e7a14 Binary files /dev/null and b/data/icons/32x32/device-phone.png differ diff --git a/data/icons/32x32/device-usb-drive.png b/data/icons/32x32/device-usb-drive.png new file mode 100644 index 00000000..7580ffe3 Binary files /dev/null and b/data/icons/32x32/device-usb-drive.png differ diff --git a/data/icons/32x32/device-usb-flash.png b/data/icons/32x32/device-usb-flash.png new file mode 100644 index 00000000..ad12f6dd Binary files /dev/null and b/data/icons/32x32/device-usb-flash.png differ diff --git a/data/icons/32x32/device.png b/data/icons/32x32/device.png new file mode 100644 index 00000000..562ed1da Binary files /dev/null and b/data/icons/32x32/device.png differ diff --git a/data/icons/32x32/dialog-error.png b/data/icons/32x32/dialog-error.png new file mode 100644 index 00000000..7ce9ac6f Binary files /dev/null and b/data/icons/32x32/dialog-error.png differ diff --git a/data/icons/32x32/dialog-information.png b/data/icons/32x32/dialog-information.png new file mode 100644 index 00000000..b88fe18f Binary files /dev/null and b/data/icons/32x32/dialog-information.png differ diff --git a/data/icons/32x32/dialog-ok-apply.png b/data/icons/32x32/dialog-ok-apply.png new file mode 100644 index 00000000..2d4ce5ee Binary files /dev/null and b/data/icons/32x32/dialog-ok-apply.png differ diff --git a/data/icons/32x32/dialog-password.png b/data/icons/32x32/dialog-password.png new file mode 100644 index 00000000..0caa2585 Binary files /dev/null and b/data/icons/32x32/dialog-password.png differ diff --git a/data/icons/32x32/dialog-warning.png b/data/icons/32x32/dialog-warning.png new file mode 100644 index 00000000..6f577e06 Binary files /dev/null and b/data/icons/32x32/dialog-warning.png differ diff --git a/data/icons/32x32/document-download.png b/data/icons/32x32/document-download.png new file mode 100644 index 00000000..e8845a17 Binary files /dev/null and b/data/icons/32x32/document-download.png differ diff --git a/data/icons/32x32/document-new.png b/data/icons/32x32/document-new.png new file mode 100644 index 00000000..3d0f5cc1 Binary files /dev/null and b/data/icons/32x32/document-new.png differ diff --git a/data/icons/32x32/document-open-folder.png b/data/icons/32x32/document-open-folder.png new file mode 100644 index 00000000..9cbc9978 Binary files /dev/null and b/data/icons/32x32/document-open-folder.png differ diff --git a/data/icons/32x32/document-open.png b/data/icons/32x32/document-open.png new file mode 100644 index 00000000..4fab85a5 Binary files /dev/null and b/data/icons/32x32/document-open.png differ diff --git a/data/icons/32x32/document-save.png b/data/icons/32x32/document-save.png new file mode 100644 index 00000000..a7cfd14c Binary files /dev/null and b/data/icons/32x32/document-save.png differ diff --git a/data/icons/32x32/document-search.png b/data/icons/32x32/document-search.png new file mode 100644 index 00000000..58702d7e Binary files /dev/null and b/data/icons/32x32/document-search.png differ diff --git a/data/icons/32x32/download.png b/data/icons/32x32/download.png new file mode 100644 index 00000000..5a7c043a Binary files /dev/null and b/data/icons/32x32/download.png differ diff --git a/data/icons/32x32/edit-clear-list.png b/data/icons/32x32/edit-clear-list.png new file mode 100644 index 00000000..c8e3f73d Binary files /dev/null and b/data/icons/32x32/edit-clear-list.png differ diff --git a/data/icons/32x32/edit-clear-locationbar-ltr.png b/data/icons/32x32/edit-clear-locationbar-ltr.png new file mode 100644 index 00000000..8fb0649a Binary files /dev/null and b/data/icons/32x32/edit-clear-locationbar-ltr.png differ diff --git a/data/icons/32x32/edit-copy.png b/data/icons/32x32/edit-copy.png new file mode 100644 index 00000000..dd29e8b7 Binary files /dev/null and b/data/icons/32x32/edit-copy.png differ diff --git a/data/icons/32x32/edit-delete.png b/data/icons/32x32/edit-delete.png new file mode 100644 index 00000000..e39fb028 Binary files /dev/null and b/data/icons/32x32/edit-delete.png differ diff --git a/data/icons/32x32/edit-find.png b/data/icons/32x32/edit-find.png new file mode 100644 index 00000000..79733de4 Binary files /dev/null and b/data/icons/32x32/edit-find.png differ diff --git a/data/icons/32x32/edit-redo.png b/data/icons/32x32/edit-redo.png new file mode 100644 index 00000000..69ad2689 Binary files /dev/null and b/data/icons/32x32/edit-redo.png differ diff --git a/data/icons/32x32/edit-rename.png b/data/icons/32x32/edit-rename.png new file mode 100644 index 00000000..826a7c86 Binary files /dev/null and b/data/icons/32x32/edit-rename.png differ diff --git a/data/icons/32x32/edit-undo.png b/data/icons/32x32/edit-undo.png new file mode 100644 index 00000000..c4b88d7d Binary files /dev/null and b/data/icons/32x32/edit-undo.png differ diff --git a/data/icons/32x32/electrocompaniet.png b/data/icons/32x32/electrocompaniet.png new file mode 100644 index 00000000..0089b6f0 Binary files /dev/null and b/data/icons/32x32/electrocompaniet.png differ diff --git a/data/icons/32x32/equalizer.png b/data/icons/32x32/equalizer.png new file mode 100644 index 00000000..d6b9f934 Binary files /dev/null and b/data/icons/32x32/equalizer.png differ diff --git a/data/icons/32x32/folder-new.png b/data/icons/32x32/folder-new.png new file mode 100644 index 00000000..94109d02 Binary files /dev/null and b/data/icons/32x32/folder-new.png differ diff --git a/data/icons/32x32/folder-sound.png b/data/icons/32x32/folder-sound.png new file mode 100644 index 00000000..1e90a87a Binary files /dev/null and b/data/icons/32x32/folder-sound.png differ diff --git a/data/icons/32x32/folder.png b/data/icons/32x32/folder.png new file mode 100644 index 00000000..05a420bd Binary files /dev/null and b/data/icons/32x32/folder.png differ diff --git a/data/icons/32x32/footsteps.png b/data/icons/32x32/footsteps.png new file mode 100644 index 00000000..bb4a56d6 Binary files /dev/null and b/data/icons/32x32/footsteps.png differ diff --git a/data/icons/32x32/go-down.png b/data/icons/32x32/go-down.png new file mode 100644 index 00000000..e7df9d02 Binary files /dev/null and b/data/icons/32x32/go-down.png differ diff --git a/data/icons/32x32/go-home.png b/data/icons/32x32/go-home.png new file mode 100644 index 00000000..6e01de76 Binary files /dev/null and b/data/icons/32x32/go-home.png differ diff --git a/data/icons/32x32/go-jump.png b/data/icons/32x32/go-jump.png new file mode 100644 index 00000000..baa52572 Binary files /dev/null and b/data/icons/32x32/go-jump.png differ diff --git a/data/icons/32x32/go-next.png b/data/icons/32x32/go-next.png new file mode 100644 index 00000000..c88f1760 Binary files /dev/null and b/data/icons/32x32/go-next.png differ diff --git a/data/icons/32x32/go-previous.png b/data/icons/32x32/go-previous.png new file mode 100644 index 00000000..1add104d Binary files /dev/null and b/data/icons/32x32/go-previous.png differ diff --git a/data/icons/32x32/go-up.png b/data/icons/32x32/go-up.png new file mode 100644 index 00000000..fead6a1c Binary files /dev/null and b/data/icons/32x32/go-up.png differ diff --git a/data/icons/32x32/gstreamer.png b/data/icons/32x32/gstreamer.png new file mode 100644 index 00000000..d489a7ce Binary files /dev/null and b/data/icons/32x32/gstreamer.png differ diff --git a/data/icons/32x32/guitar.png b/data/icons/32x32/guitar.png new file mode 100644 index 00000000..e854d638 Binary files /dev/null and b/data/icons/32x32/guitar.png differ diff --git a/data/icons/32x32/headset.png b/data/icons/32x32/headset.png new file mode 100644 index 00000000..d4d89f2f Binary files /dev/null and b/data/icons/32x32/headset.png differ diff --git a/data/icons/32x32/help-hint.png b/data/icons/32x32/help-hint.png new file mode 100644 index 00000000..209f1d28 Binary files /dev/null and b/data/icons/32x32/help-hint.png differ diff --git a/data/icons/32x32/intel.png b/data/icons/32x32/intel.png new file mode 100644 index 00000000..afdc21c6 Binary files /dev/null and b/data/icons/32x32/intel.png differ diff --git a/data/icons/32x32/jack.png b/data/icons/32x32/jack.png new file mode 100644 index 00000000..717b6229 Binary files /dev/null and b/data/icons/32x32/jack.png differ diff --git a/data/icons/32x32/keyboard.png b/data/icons/32x32/keyboard.png new file mode 100644 index 00000000..53c6db36 Binary files /dev/null and b/data/icons/32x32/keyboard.png differ diff --git a/data/icons/32x32/list-add.png b/data/icons/32x32/list-add.png new file mode 100644 index 00000000..5724694a Binary files /dev/null and b/data/icons/32x32/list-add.png differ diff --git a/data/icons/32x32/list-remove.png b/data/icons/32x32/list-remove.png new file mode 100644 index 00000000..53506b02 Binary files /dev/null and b/data/icons/32x32/list-remove.png differ diff --git a/data/icons/32x32/mcintosh-player.png b/data/icons/32x32/mcintosh-player.png new file mode 100644 index 00000000..064febe3 Binary files /dev/null and b/data/icons/32x32/mcintosh-player.png differ diff --git a/data/icons/32x32/mcintosh-text.png b/data/icons/32x32/mcintosh-text.png new file mode 100644 index 00000000..db345af3 Binary files /dev/null and b/data/icons/32x32/mcintosh-text.png differ diff --git a/data/icons/32x32/mcintosh.png b/data/icons/32x32/mcintosh.png new file mode 100644 index 00000000..5bf4a5e0 Binary files /dev/null and b/data/icons/32x32/mcintosh.png differ diff --git a/data/icons/32x32/media-eject.png b/data/icons/32x32/media-eject.png new file mode 100644 index 00000000..afdb4bb7 Binary files /dev/null and b/data/icons/32x32/media-eject.png differ diff --git a/data/icons/32x32/media-forward.png b/data/icons/32x32/media-forward.png new file mode 100644 index 00000000..567d8380 Binary files /dev/null and b/data/icons/32x32/media-forward.png differ diff --git a/data/icons/32x32/media-pause.png b/data/icons/32x32/media-pause.png new file mode 100644 index 00000000..3e454522 Binary files /dev/null and b/data/icons/32x32/media-pause.png differ diff --git a/data/icons/32x32/media-play.png b/data/icons/32x32/media-play.png new file mode 100644 index 00000000..38a3159a Binary files /dev/null and b/data/icons/32x32/media-play.png differ diff --git a/data/icons/32x32/media-playlist-repeat.png b/data/icons/32x32/media-playlist-repeat.png new file mode 100644 index 00000000..79dad726 Binary files /dev/null and b/data/icons/32x32/media-playlist-repeat.png differ diff --git a/data/icons/32x32/media-playlist-shuffle.png b/data/icons/32x32/media-playlist-shuffle.png new file mode 100644 index 00000000..b85db42a Binary files /dev/null and b/data/icons/32x32/media-playlist-shuffle.png differ diff --git a/data/icons/32x32/media-rewind.png b/data/icons/32x32/media-rewind.png new file mode 100644 index 00000000..c714221b Binary files /dev/null and b/data/icons/32x32/media-rewind.png differ diff --git a/data/icons/32x32/media-stop.png b/data/icons/32x32/media-stop.png new file mode 100644 index 00000000..74069ec3 Binary files /dev/null and b/data/icons/32x32/media-stop.png differ diff --git a/data/icons/32x32/nvidia.png b/data/icons/32x32/nvidia.png new file mode 100644 index 00000000..eab4c3fc Binary files /dev/null and b/data/icons/32x32/nvidia.png differ diff --git a/data/icons/32x32/play2.png b/data/icons/32x32/play2.png new file mode 100644 index 00000000..38a3159a Binary files /dev/null and b/data/icons/32x32/play2.png differ diff --git a/data/icons/32x32/pulseaudio.png b/data/icons/32x32/pulseaudio.png new file mode 100644 index 00000000..c5217978 Binary files /dev/null and b/data/icons/32x32/pulseaudio.png differ diff --git a/data/icons/32x32/realtek.png b/data/icons/32x32/realtek.png new file mode 100644 index 00000000..4369ae86 Binary files /dev/null and b/data/icons/32x32/realtek.png differ diff --git a/data/icons/32x32/search.png b/data/icons/32x32/search.png new file mode 100644 index 00000000..96803910 Binary files /dev/null and b/data/icons/32x32/search.png differ diff --git a/data/icons/32x32/soundcard.png b/data/icons/32x32/soundcard.png new file mode 100644 index 00000000..1ee154c0 Binary files /dev/null and b/data/icons/32x32/soundcard.png differ diff --git a/data/icons/32x32/soundcard2.png b/data/icons/32x32/soundcard2.png new file mode 100644 index 00000000..981d5978 Binary files /dev/null and b/data/icons/32x32/soundcard2.png differ diff --git a/data/icons/32x32/speaker.png b/data/icons/32x32/speaker.png new file mode 100644 index 00000000..36eb4486 Binary files /dev/null and b/data/icons/32x32/speaker.png differ diff --git a/data/icons/32x32/star-grey.png b/data/icons/32x32/star-grey.png new file mode 100644 index 00000000..9d741ab9 Binary files /dev/null and b/data/icons/32x32/star-grey.png differ diff --git a/data/icons/32x32/star.png b/data/icons/32x32/star.png new file mode 100644 index 00000000..8ab71823 Binary files /dev/null and b/data/icons/32x32/star.png differ diff --git a/data/icons/32x32/strawberry-panel-grey.png b/data/icons/32x32/strawberry-panel-grey.png new file mode 100644 index 00000000..5787faf9 Binary files /dev/null and b/data/icons/32x32/strawberry-panel-grey.png differ diff --git a/data/icons/32x32/strawberry-panel.png b/data/icons/32x32/strawberry-panel.png new file mode 100644 index 00000000..961d0cd6 Binary files /dev/null and b/data/icons/32x32/strawberry-panel.png differ diff --git a/data/icons/32x32/strawberry.png b/data/icons/32x32/strawberry.png new file mode 100644 index 00000000..ca1f1cd5 Binary files /dev/null and b/data/icons/32x32/strawberry.png differ diff --git a/data/icons/32x32/strawberry.svg b/data/icons/32x32/strawberry.svg new file mode 100644 index 00000000..b152a908 --- /dev/null +++ b/data/icons/32x32/strawberry.svg @@ -0,0 +1,1029 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/icons/32x32/tools-wizard.png b/data/icons/32x32/tools-wizard.png new file mode 100644 index 00000000..34e4a79b Binary files /dev/null and b/data/icons/32x32/tools-wizard.png differ diff --git a/data/icons/32x32/view-choose.png b/data/icons/32x32/view-choose.png new file mode 100644 index 00000000..fb20af27 Binary files /dev/null and b/data/icons/32x32/view-choose.png differ diff --git a/data/icons/32x32/view-fullscreen.png b/data/icons/32x32/view-fullscreen.png new file mode 100644 index 00000000..66f95206 Binary files /dev/null and b/data/icons/32x32/view-fullscreen.png differ diff --git a/data/icons/32x32/view-media-lyrics.png b/data/icons/32x32/view-media-lyrics.png new file mode 100644 index 00000000..f63a5cb6 Binary files /dev/null and b/data/icons/32x32/view-media-lyrics.png differ diff --git a/data/icons/32x32/view-media-playlist.png b/data/icons/32x32/view-media-playlist.png new file mode 100644 index 00000000..336fd0e8 Binary files /dev/null and b/data/icons/32x32/view-media-playlist.png differ diff --git a/data/icons/32x32/view-media-visualization.png b/data/icons/32x32/view-media-visualization.png new file mode 100644 index 00000000..af5421e3 Binary files /dev/null and b/data/icons/32x32/view-media-visualization.png differ diff --git a/data/icons/32x32/view-refresh.png b/data/icons/32x32/view-refresh.png new file mode 100644 index 00000000..afa2a9d7 Binary files /dev/null and b/data/icons/32x32/view-refresh.png differ diff --git a/data/icons/32x32/vinyl.png b/data/icons/32x32/vinyl.png new file mode 100644 index 00000000..0e250ffb Binary files /dev/null and b/data/icons/32x32/vinyl.png differ diff --git a/data/icons/32x32/vlc.png b/data/icons/32x32/vlc.png new file mode 100644 index 00000000..54adc4bf Binary files /dev/null and b/data/icons/32x32/vlc.png differ diff --git a/data/icons/32x32/xine.png b/data/icons/32x32/xine.png new file mode 100644 index 00000000..6339a257 Binary files /dev/null and b/data/icons/32x32/xine.png differ diff --git a/data/icons/32x32/zoom-in.png b/data/icons/32x32/zoom-in.png new file mode 100644 index 00000000..63b3bd03 Binary files /dev/null and b/data/icons/32x32/zoom-in.png differ diff --git a/data/icons/32x32/zoom-out.png b/data/icons/32x32/zoom-out.png new file mode 100644 index 00000000..fe4ddca4 Binary files /dev/null and b/data/icons/32x32/zoom-out.png differ diff --git a/data/icons/48x48/albums.png b/data/icons/48x48/albums.png new file mode 100644 index 00000000..ceaf4121 Binary files /dev/null and b/data/icons/48x48/albums.png differ diff --git a/data/icons/48x48/alsa.png b/data/icons/48x48/alsa.png new file mode 100644 index 00000000..983769ac Binary files /dev/null and b/data/icons/48x48/alsa.png differ diff --git a/data/icons/48x48/application-exit.png b/data/icons/48x48/application-exit.png new file mode 100644 index 00000000..ca93d07d Binary files /dev/null and b/data/icons/48x48/application-exit.png differ diff --git a/data/icons/48x48/applications-internet.png b/data/icons/48x48/applications-internet.png new file mode 100644 index 00000000..81720c85 Binary files /dev/null and b/data/icons/48x48/applications-internet.png differ diff --git a/data/icons/48x48/bluetooth.png b/data/icons/48x48/bluetooth.png new file mode 100644 index 00000000..00a4515c Binary files /dev/null and b/data/icons/48x48/bluetooth.png differ diff --git a/data/icons/48x48/cd.png b/data/icons/48x48/cd.png new file mode 100644 index 00000000..009ae0f4 Binary files /dev/null and b/data/icons/48x48/cd.png differ diff --git a/data/icons/48x48/cdcase.png b/data/icons/48x48/cdcase.png new file mode 100644 index 00000000..2e96382b Binary files /dev/null and b/data/icons/48x48/cdcase.png differ diff --git a/data/icons/48x48/configure.png b/data/icons/48x48/configure.png new file mode 100644 index 00000000..2bc9afed Binary files /dev/null and b/data/icons/48x48/configure.png differ diff --git a/data/icons/48x48/device-ipod-nano.png b/data/icons/48x48/device-ipod-nano.png new file mode 100644 index 00000000..24d953e7 Binary files /dev/null and b/data/icons/48x48/device-ipod-nano.png differ diff --git a/data/icons/48x48/device-ipod.png b/data/icons/48x48/device-ipod.png new file mode 100644 index 00000000..64ea434b Binary files /dev/null and b/data/icons/48x48/device-ipod.png differ diff --git a/data/icons/48x48/device-phone.png b/data/icons/48x48/device-phone.png new file mode 100644 index 00000000..863cd34d Binary files /dev/null and b/data/icons/48x48/device-phone.png differ diff --git a/data/icons/48x48/device-usb-drive.png b/data/icons/48x48/device-usb-drive.png new file mode 100644 index 00000000..01635a59 Binary files /dev/null and b/data/icons/48x48/device-usb-drive.png differ diff --git a/data/icons/48x48/device-usb-flash.png b/data/icons/48x48/device-usb-flash.png new file mode 100644 index 00000000..d68e8ed8 Binary files /dev/null and b/data/icons/48x48/device-usb-flash.png differ diff --git a/data/icons/48x48/device.png b/data/icons/48x48/device.png new file mode 100644 index 00000000..17095bd4 Binary files /dev/null and b/data/icons/48x48/device.png differ diff --git a/data/icons/48x48/dialog-error.png b/data/icons/48x48/dialog-error.png new file mode 100644 index 00000000..5e900923 Binary files /dev/null and b/data/icons/48x48/dialog-error.png differ diff --git a/data/icons/48x48/dialog-information.png b/data/icons/48x48/dialog-information.png new file mode 100644 index 00000000..d08d9591 Binary files /dev/null and b/data/icons/48x48/dialog-information.png differ diff --git a/data/icons/48x48/dialog-ok-apply.png b/data/icons/48x48/dialog-ok-apply.png new file mode 100644 index 00000000..096b56ce Binary files /dev/null and b/data/icons/48x48/dialog-ok-apply.png differ diff --git a/data/icons/48x48/dialog-password.png b/data/icons/48x48/dialog-password.png new file mode 100644 index 00000000..5e9e1a1f Binary files /dev/null and b/data/icons/48x48/dialog-password.png differ diff --git a/data/icons/48x48/dialog-warning.png b/data/icons/48x48/dialog-warning.png new file mode 100644 index 00000000..2e72d58f Binary files /dev/null and b/data/icons/48x48/dialog-warning.png differ diff --git a/data/icons/48x48/document-download.png b/data/icons/48x48/document-download.png new file mode 100644 index 00000000..fcb8afbd Binary files /dev/null and b/data/icons/48x48/document-download.png differ diff --git a/data/icons/48x48/document-new.png b/data/icons/48x48/document-new.png new file mode 100644 index 00000000..50a0b5f1 Binary files /dev/null and b/data/icons/48x48/document-new.png differ diff --git a/data/icons/48x48/document-open-folder.png b/data/icons/48x48/document-open-folder.png new file mode 100644 index 00000000..5c894994 Binary files /dev/null and b/data/icons/48x48/document-open-folder.png differ diff --git a/data/icons/48x48/document-open.png b/data/icons/48x48/document-open.png new file mode 100644 index 00000000..7f7ffa52 Binary files /dev/null and b/data/icons/48x48/document-open.png differ diff --git a/data/icons/48x48/document-save.png b/data/icons/48x48/document-save.png new file mode 100644 index 00000000..7ef3be76 Binary files /dev/null and b/data/icons/48x48/document-save.png differ diff --git a/data/icons/48x48/document-search.png b/data/icons/48x48/document-search.png new file mode 100644 index 00000000..66523f2a Binary files /dev/null and b/data/icons/48x48/document-search.png differ diff --git a/data/icons/48x48/download.png b/data/icons/48x48/download.png new file mode 100644 index 00000000..a542419f Binary files /dev/null and b/data/icons/48x48/download.png differ diff --git a/data/icons/48x48/edit-clear-list.png b/data/icons/48x48/edit-clear-list.png new file mode 100644 index 00000000..b223f29d Binary files /dev/null and b/data/icons/48x48/edit-clear-list.png differ diff --git a/data/icons/48x48/edit-clear-locationbar-ltr.png b/data/icons/48x48/edit-clear-locationbar-ltr.png new file mode 100644 index 00000000..f648128e Binary files /dev/null and b/data/icons/48x48/edit-clear-locationbar-ltr.png differ diff --git a/data/icons/48x48/edit-copy.png b/data/icons/48x48/edit-copy.png new file mode 100644 index 00000000..88fb0d61 Binary files /dev/null and b/data/icons/48x48/edit-copy.png differ diff --git a/data/icons/48x48/edit-delete.png b/data/icons/48x48/edit-delete.png new file mode 100644 index 00000000..b50e6bd7 Binary files /dev/null and b/data/icons/48x48/edit-delete.png differ diff --git a/data/icons/48x48/edit-find.png b/data/icons/48x48/edit-find.png new file mode 100644 index 00000000..dd461018 Binary files /dev/null and b/data/icons/48x48/edit-find.png differ diff --git a/data/icons/48x48/edit-redo.png b/data/icons/48x48/edit-redo.png new file mode 100644 index 00000000..0e30ff7f Binary files /dev/null and b/data/icons/48x48/edit-redo.png differ diff --git a/data/icons/48x48/edit-rename.png b/data/icons/48x48/edit-rename.png new file mode 100644 index 00000000..98c7cfac Binary files /dev/null and b/data/icons/48x48/edit-rename.png differ diff --git a/data/icons/48x48/edit-undo.png b/data/icons/48x48/edit-undo.png new file mode 100644 index 00000000..bc6819ba Binary files /dev/null and b/data/icons/48x48/edit-undo.png differ diff --git a/data/icons/48x48/electrocompaniet.png b/data/icons/48x48/electrocompaniet.png new file mode 100644 index 00000000..336a95c3 Binary files /dev/null and b/data/icons/48x48/electrocompaniet.png differ diff --git a/data/icons/48x48/equalizer.png b/data/icons/48x48/equalizer.png new file mode 100644 index 00000000..bd8a0dd6 Binary files /dev/null and b/data/icons/48x48/equalizer.png differ diff --git a/data/icons/48x48/folder-new.png b/data/icons/48x48/folder-new.png new file mode 100644 index 00000000..c84b4a7c Binary files /dev/null and b/data/icons/48x48/folder-new.png differ diff --git a/data/icons/48x48/folder-sound.png b/data/icons/48x48/folder-sound.png new file mode 100644 index 00000000..4673732a Binary files /dev/null and b/data/icons/48x48/folder-sound.png differ diff --git a/data/icons/48x48/folder.png b/data/icons/48x48/folder.png new file mode 100644 index 00000000..2a07196c Binary files /dev/null and b/data/icons/48x48/folder.png differ diff --git a/data/icons/48x48/footsteps.png b/data/icons/48x48/footsteps.png new file mode 100644 index 00000000..36392596 Binary files /dev/null and b/data/icons/48x48/footsteps.png differ diff --git a/data/icons/48x48/go-down.png b/data/icons/48x48/go-down.png new file mode 100644 index 00000000..423ce570 Binary files /dev/null and b/data/icons/48x48/go-down.png differ diff --git a/data/icons/48x48/go-home.png b/data/icons/48x48/go-home.png new file mode 100644 index 00000000..deb9690a Binary files /dev/null and b/data/icons/48x48/go-home.png differ diff --git a/data/icons/48x48/go-jump.png b/data/icons/48x48/go-jump.png new file mode 100644 index 00000000..7100f0a1 Binary files /dev/null and b/data/icons/48x48/go-jump.png differ diff --git a/data/icons/48x48/go-next.png b/data/icons/48x48/go-next.png new file mode 100644 index 00000000..7ae58881 Binary files /dev/null and b/data/icons/48x48/go-next.png differ diff --git a/data/icons/48x48/go-previous.png b/data/icons/48x48/go-previous.png new file mode 100644 index 00000000..606b4b6a Binary files /dev/null and b/data/icons/48x48/go-previous.png differ diff --git a/data/icons/48x48/go-up.png b/data/icons/48x48/go-up.png new file mode 100644 index 00000000..f456dec8 Binary files /dev/null and b/data/icons/48x48/go-up.png differ diff --git a/data/icons/48x48/gstreamer.png b/data/icons/48x48/gstreamer.png new file mode 100644 index 00000000..a187ae02 Binary files /dev/null and b/data/icons/48x48/gstreamer.png differ diff --git a/data/icons/48x48/guitar.png b/data/icons/48x48/guitar.png new file mode 100644 index 00000000..dc5f35cb Binary files /dev/null and b/data/icons/48x48/guitar.png differ diff --git a/data/icons/48x48/headset.png b/data/icons/48x48/headset.png new file mode 100644 index 00000000..c47e7a92 Binary files /dev/null and b/data/icons/48x48/headset.png differ diff --git a/data/icons/48x48/help-hint.png b/data/icons/48x48/help-hint.png new file mode 100644 index 00000000..98bcc93a Binary files /dev/null and b/data/icons/48x48/help-hint.png differ diff --git a/data/icons/48x48/intel.png b/data/icons/48x48/intel.png new file mode 100644 index 00000000..c29e2921 Binary files /dev/null and b/data/icons/48x48/intel.png differ diff --git a/data/icons/48x48/jack.png b/data/icons/48x48/jack.png new file mode 100644 index 00000000..df4da471 Binary files /dev/null and b/data/icons/48x48/jack.png differ diff --git a/data/icons/48x48/keyboard.png b/data/icons/48x48/keyboard.png new file mode 100644 index 00000000..72749e8e Binary files /dev/null and b/data/icons/48x48/keyboard.png differ diff --git a/data/icons/48x48/list-add.png b/data/icons/48x48/list-add.png new file mode 100644 index 00000000..28d5e8f9 Binary files /dev/null and b/data/icons/48x48/list-add.png differ diff --git a/data/icons/48x48/list-remove.png b/data/icons/48x48/list-remove.png new file mode 100644 index 00000000..678b34df Binary files /dev/null and b/data/icons/48x48/list-remove.png differ diff --git a/data/icons/48x48/mcintosh-player.png b/data/icons/48x48/mcintosh-player.png new file mode 100644 index 00000000..05750f98 Binary files /dev/null and b/data/icons/48x48/mcintosh-player.png differ diff --git a/data/icons/48x48/mcintosh-text.png b/data/icons/48x48/mcintosh-text.png new file mode 100644 index 00000000..a41743fe Binary files /dev/null and b/data/icons/48x48/mcintosh-text.png differ diff --git a/data/icons/48x48/mcintosh.png b/data/icons/48x48/mcintosh.png new file mode 100644 index 00000000..ea35f7b5 Binary files /dev/null and b/data/icons/48x48/mcintosh.png differ diff --git a/data/icons/48x48/media-eject.png b/data/icons/48x48/media-eject.png new file mode 100644 index 00000000..cf7c2b0a Binary files /dev/null and b/data/icons/48x48/media-eject.png differ diff --git a/data/icons/48x48/media-forward.png b/data/icons/48x48/media-forward.png new file mode 100644 index 00000000..82187e2b Binary files /dev/null and b/data/icons/48x48/media-forward.png differ diff --git a/data/icons/48x48/media-pause.png b/data/icons/48x48/media-pause.png new file mode 100644 index 00000000..5a657c51 Binary files /dev/null and b/data/icons/48x48/media-pause.png differ diff --git a/data/icons/48x48/media-play.png b/data/icons/48x48/media-play.png new file mode 100644 index 00000000..2fbb5e10 Binary files /dev/null and b/data/icons/48x48/media-play.png differ diff --git a/data/icons/48x48/media-playlist-repeat.png b/data/icons/48x48/media-playlist-repeat.png new file mode 100644 index 00000000..089a4615 Binary files /dev/null and b/data/icons/48x48/media-playlist-repeat.png differ diff --git a/data/icons/48x48/media-playlist-shuffle.png b/data/icons/48x48/media-playlist-shuffle.png new file mode 100644 index 00000000..99de62ff Binary files /dev/null and b/data/icons/48x48/media-playlist-shuffle.png differ diff --git a/data/icons/48x48/media-rewind.png b/data/icons/48x48/media-rewind.png new file mode 100644 index 00000000..db81a881 Binary files /dev/null and b/data/icons/48x48/media-rewind.png differ diff --git a/data/icons/48x48/media-stop.png b/data/icons/48x48/media-stop.png new file mode 100644 index 00000000..180b001b Binary files /dev/null and b/data/icons/48x48/media-stop.png differ diff --git a/data/icons/48x48/nvidia.png b/data/icons/48x48/nvidia.png new file mode 100644 index 00000000..f68f1b08 Binary files /dev/null and b/data/icons/48x48/nvidia.png differ diff --git a/data/icons/48x48/play2.png b/data/icons/48x48/play2.png new file mode 100644 index 00000000..2fbb5e10 Binary files /dev/null and b/data/icons/48x48/play2.png differ diff --git a/data/icons/48x48/pulseaudio.png b/data/icons/48x48/pulseaudio.png new file mode 100644 index 00000000..6e0dc445 Binary files /dev/null and b/data/icons/48x48/pulseaudio.png differ diff --git a/data/icons/48x48/realtek.png b/data/icons/48x48/realtek.png new file mode 100644 index 00000000..59f28bad Binary files /dev/null and b/data/icons/48x48/realtek.png differ diff --git a/data/icons/48x48/search.png b/data/icons/48x48/search.png new file mode 100644 index 00000000..4b08dac8 Binary files /dev/null and b/data/icons/48x48/search.png differ diff --git a/data/icons/48x48/soundcard.png b/data/icons/48x48/soundcard.png new file mode 100644 index 00000000..3bf4c8a0 Binary files /dev/null and b/data/icons/48x48/soundcard.png differ diff --git a/data/icons/48x48/soundcard2.png b/data/icons/48x48/soundcard2.png new file mode 100644 index 00000000..3c12d6ad Binary files /dev/null and b/data/icons/48x48/soundcard2.png differ diff --git a/data/icons/48x48/speaker.png b/data/icons/48x48/speaker.png new file mode 100644 index 00000000..27b6c9ce Binary files /dev/null and b/data/icons/48x48/speaker.png differ diff --git a/data/icons/48x48/star-grey.png b/data/icons/48x48/star-grey.png new file mode 100644 index 00000000..c1574b84 Binary files /dev/null and b/data/icons/48x48/star-grey.png differ diff --git a/data/icons/48x48/star.png b/data/icons/48x48/star.png new file mode 100644 index 00000000..e6553cc0 Binary files /dev/null and b/data/icons/48x48/star.png differ diff --git a/data/icons/48x48/strawberry-panel-grey.png b/data/icons/48x48/strawberry-panel-grey.png new file mode 100644 index 00000000..8d11609a Binary files /dev/null and b/data/icons/48x48/strawberry-panel-grey.png differ diff --git a/data/icons/48x48/strawberry-panel.png b/data/icons/48x48/strawberry-panel.png new file mode 100644 index 00000000..fb87d0a1 Binary files /dev/null and b/data/icons/48x48/strawberry-panel.png differ diff --git a/data/icons/48x48/strawberry.png b/data/icons/48x48/strawberry.png new file mode 100644 index 00000000..8f9dd80b Binary files /dev/null and b/data/icons/48x48/strawberry.png differ diff --git a/data/icons/48x48/tools-wizard.png b/data/icons/48x48/tools-wizard.png new file mode 100644 index 00000000..2a91d206 Binary files /dev/null and b/data/icons/48x48/tools-wizard.png differ diff --git a/data/icons/48x48/view-choose.png b/data/icons/48x48/view-choose.png new file mode 100644 index 00000000..a943ac9d Binary files /dev/null and b/data/icons/48x48/view-choose.png differ diff --git a/data/icons/48x48/view-fullscreen.png b/data/icons/48x48/view-fullscreen.png new file mode 100644 index 00000000..bf323857 Binary files /dev/null and b/data/icons/48x48/view-fullscreen.png differ diff --git a/data/icons/48x48/view-media-lyrics.png b/data/icons/48x48/view-media-lyrics.png new file mode 100644 index 00000000..fcc5b27c Binary files /dev/null and b/data/icons/48x48/view-media-lyrics.png differ diff --git a/data/icons/48x48/view-media-playlist.png b/data/icons/48x48/view-media-playlist.png new file mode 100644 index 00000000..6ea4710c Binary files /dev/null and b/data/icons/48x48/view-media-playlist.png differ diff --git a/data/icons/48x48/view-media-visualization.png b/data/icons/48x48/view-media-visualization.png new file mode 100644 index 00000000..39c6c674 Binary files /dev/null and b/data/icons/48x48/view-media-visualization.png differ diff --git a/data/icons/48x48/view-refresh.png b/data/icons/48x48/view-refresh.png new file mode 100644 index 00000000..5081d418 Binary files /dev/null and b/data/icons/48x48/view-refresh.png differ diff --git a/data/icons/48x48/vinyl.png b/data/icons/48x48/vinyl.png new file mode 100644 index 00000000..95c54585 Binary files /dev/null and b/data/icons/48x48/vinyl.png differ diff --git a/data/icons/48x48/vlc.png b/data/icons/48x48/vlc.png new file mode 100644 index 00000000..7d002110 Binary files /dev/null and b/data/icons/48x48/vlc.png differ diff --git a/data/icons/48x48/xine.png b/data/icons/48x48/xine.png new file mode 100644 index 00000000..58aa2971 Binary files /dev/null and b/data/icons/48x48/xine.png differ diff --git a/data/icons/48x48/zoom-in.png b/data/icons/48x48/zoom-in.png new file mode 100644 index 00000000..b5994e9a Binary files /dev/null and b/data/icons/48x48/zoom-in.png differ diff --git a/data/icons/48x48/zoom-out.png b/data/icons/48x48/zoom-out.png new file mode 100644 index 00000000..99cf491f Binary files /dev/null and b/data/icons/48x48/zoom-out.png differ diff --git a/data/icons/64x64/albums.png b/data/icons/64x64/albums.png new file mode 100644 index 00000000..4c7bd2fd Binary files /dev/null and b/data/icons/64x64/albums.png differ diff --git a/data/icons/64x64/alsa.png b/data/icons/64x64/alsa.png new file mode 100644 index 00000000..a95bf521 Binary files /dev/null and b/data/icons/64x64/alsa.png differ diff --git a/data/icons/64x64/application-exit.png b/data/icons/64x64/application-exit.png new file mode 100644 index 00000000..f9754846 Binary files /dev/null and b/data/icons/64x64/application-exit.png differ diff --git a/data/icons/64x64/applications-internet.png b/data/icons/64x64/applications-internet.png new file mode 100644 index 00000000..3e68dc8a Binary files /dev/null and b/data/icons/64x64/applications-internet.png differ diff --git a/data/icons/64x64/bluetooth.png b/data/icons/64x64/bluetooth.png new file mode 100644 index 00000000..b931b955 Binary files /dev/null and b/data/icons/64x64/bluetooth.png differ diff --git a/data/icons/64x64/cd.png b/data/icons/64x64/cd.png new file mode 100644 index 00000000..91f9b402 Binary files /dev/null and b/data/icons/64x64/cd.png differ diff --git a/data/icons/64x64/cdcase.png b/data/icons/64x64/cdcase.png new file mode 100644 index 00000000..8076d229 Binary files /dev/null and b/data/icons/64x64/cdcase.png differ diff --git a/data/icons/64x64/configure.png b/data/icons/64x64/configure.png new file mode 100644 index 00000000..c4b7ca86 Binary files /dev/null and b/data/icons/64x64/configure.png differ diff --git a/data/icons/64x64/device-ipod-nano.png b/data/icons/64x64/device-ipod-nano.png new file mode 100644 index 00000000..c8d47ea2 Binary files /dev/null and b/data/icons/64x64/device-ipod-nano.png differ diff --git a/data/icons/64x64/device-ipod.png b/data/icons/64x64/device-ipod.png new file mode 100644 index 00000000..bc071c04 Binary files /dev/null and b/data/icons/64x64/device-ipod.png differ diff --git a/data/icons/64x64/device-phone.png b/data/icons/64x64/device-phone.png new file mode 100644 index 00000000..d3383e8d Binary files /dev/null and b/data/icons/64x64/device-phone.png differ diff --git a/data/icons/64x64/device-usb-drive.png b/data/icons/64x64/device-usb-drive.png new file mode 100644 index 00000000..53c7ee80 Binary files /dev/null and b/data/icons/64x64/device-usb-drive.png differ diff --git a/data/icons/64x64/device-usb-flash.png b/data/icons/64x64/device-usb-flash.png new file mode 100644 index 00000000..7802b1e6 Binary files /dev/null and b/data/icons/64x64/device-usb-flash.png differ diff --git a/data/icons/64x64/device.png b/data/icons/64x64/device.png new file mode 100644 index 00000000..6eb1cafe Binary files /dev/null and b/data/icons/64x64/device.png differ diff --git a/data/icons/64x64/dialog-error.png b/data/icons/64x64/dialog-error.png new file mode 100644 index 00000000..d80b3d65 Binary files /dev/null and b/data/icons/64x64/dialog-error.png differ diff --git a/data/icons/64x64/dialog-information.png b/data/icons/64x64/dialog-information.png new file mode 100644 index 00000000..eb35d0b9 Binary files /dev/null and b/data/icons/64x64/dialog-information.png differ diff --git a/data/icons/64x64/dialog-ok-apply.png b/data/icons/64x64/dialog-ok-apply.png new file mode 100644 index 00000000..44df8fd3 Binary files /dev/null and b/data/icons/64x64/dialog-ok-apply.png differ diff --git a/data/icons/64x64/dialog-password.png b/data/icons/64x64/dialog-password.png new file mode 100644 index 00000000..afa02f71 Binary files /dev/null and b/data/icons/64x64/dialog-password.png differ diff --git a/data/icons/64x64/dialog-warning.png b/data/icons/64x64/dialog-warning.png new file mode 100644 index 00000000..b12d7a5f Binary files /dev/null and b/data/icons/64x64/dialog-warning.png differ diff --git a/data/icons/64x64/document-download.png b/data/icons/64x64/document-download.png new file mode 100644 index 00000000..12e7052c Binary files /dev/null and b/data/icons/64x64/document-download.png differ diff --git a/data/icons/64x64/document-new.png b/data/icons/64x64/document-new.png new file mode 100644 index 00000000..2817dbcb Binary files /dev/null and b/data/icons/64x64/document-new.png differ diff --git a/data/icons/64x64/document-open-folder.png b/data/icons/64x64/document-open-folder.png new file mode 100644 index 00000000..a6ef2feb Binary files /dev/null and b/data/icons/64x64/document-open-folder.png differ diff --git a/data/icons/64x64/document-open.png b/data/icons/64x64/document-open.png new file mode 100644 index 00000000..74af0676 Binary files /dev/null and b/data/icons/64x64/document-open.png differ diff --git a/data/icons/64x64/document-save.png b/data/icons/64x64/document-save.png new file mode 100644 index 00000000..04d90070 Binary files /dev/null and b/data/icons/64x64/document-save.png differ diff --git a/data/icons/64x64/document-search.png b/data/icons/64x64/document-search.png new file mode 100644 index 00000000..02c6558a Binary files /dev/null and b/data/icons/64x64/document-search.png differ diff --git a/data/icons/64x64/download.png b/data/icons/64x64/download.png new file mode 100644 index 00000000..abbd163a Binary files /dev/null and b/data/icons/64x64/download.png differ diff --git a/data/icons/64x64/edit-clear-list.png b/data/icons/64x64/edit-clear-list.png new file mode 100644 index 00000000..f1381623 Binary files /dev/null and b/data/icons/64x64/edit-clear-list.png differ diff --git a/data/icons/64x64/edit-clear-locationbar-ltr.png b/data/icons/64x64/edit-clear-locationbar-ltr.png new file mode 100644 index 00000000..6f0eeb37 Binary files /dev/null and b/data/icons/64x64/edit-clear-locationbar-ltr.png differ diff --git a/data/icons/64x64/edit-copy.png b/data/icons/64x64/edit-copy.png new file mode 100644 index 00000000..4f7fcfd0 Binary files /dev/null and b/data/icons/64x64/edit-copy.png differ diff --git a/data/icons/64x64/edit-delete.png b/data/icons/64x64/edit-delete.png new file mode 100644 index 00000000..c3ad8d9d Binary files /dev/null and b/data/icons/64x64/edit-delete.png differ diff --git a/data/icons/64x64/edit-find.png b/data/icons/64x64/edit-find.png new file mode 100644 index 00000000..28e4be2a Binary files /dev/null and b/data/icons/64x64/edit-find.png differ diff --git a/data/icons/64x64/edit-redo.png b/data/icons/64x64/edit-redo.png new file mode 100644 index 00000000..113a7aa7 Binary files /dev/null and b/data/icons/64x64/edit-redo.png differ diff --git a/data/icons/64x64/edit-rename.png b/data/icons/64x64/edit-rename.png new file mode 100644 index 00000000..c579a716 Binary files /dev/null and b/data/icons/64x64/edit-rename.png differ diff --git a/data/icons/64x64/edit-undo.png b/data/icons/64x64/edit-undo.png new file mode 100644 index 00000000..5131ea83 Binary files /dev/null and b/data/icons/64x64/edit-undo.png differ diff --git a/data/icons/64x64/electrocompaniet.png b/data/icons/64x64/electrocompaniet.png new file mode 100644 index 00000000..0fba8270 Binary files /dev/null and b/data/icons/64x64/electrocompaniet.png differ diff --git a/data/icons/64x64/equalizer.png b/data/icons/64x64/equalizer.png new file mode 100644 index 00000000..dcd1c72f Binary files /dev/null and b/data/icons/64x64/equalizer.png differ diff --git a/data/icons/64x64/folder-new.png b/data/icons/64x64/folder-new.png new file mode 100644 index 00000000..9ca40a71 Binary files /dev/null and b/data/icons/64x64/folder-new.png differ diff --git a/data/icons/64x64/folder-sound.png b/data/icons/64x64/folder-sound.png new file mode 100644 index 00000000..73982f00 Binary files /dev/null and b/data/icons/64x64/folder-sound.png differ diff --git a/data/icons/64x64/folder.png b/data/icons/64x64/folder.png new file mode 100644 index 00000000..24fa149a Binary files /dev/null and b/data/icons/64x64/folder.png differ diff --git a/data/icons/64x64/footsteps.png b/data/icons/64x64/footsteps.png new file mode 100644 index 00000000..470553d4 Binary files /dev/null and b/data/icons/64x64/footsteps.png differ diff --git a/data/icons/64x64/go-down.png b/data/icons/64x64/go-down.png new file mode 100644 index 00000000..e81c9676 Binary files /dev/null and b/data/icons/64x64/go-down.png differ diff --git a/data/icons/64x64/go-home.png b/data/icons/64x64/go-home.png new file mode 100644 index 00000000..2107d3fc Binary files /dev/null and b/data/icons/64x64/go-home.png differ diff --git a/data/icons/64x64/go-jump.png b/data/icons/64x64/go-jump.png new file mode 100644 index 00000000..10555fea Binary files /dev/null and b/data/icons/64x64/go-jump.png differ diff --git a/data/icons/64x64/go-next.png b/data/icons/64x64/go-next.png new file mode 100644 index 00000000..cb44cea3 Binary files /dev/null and b/data/icons/64x64/go-next.png differ diff --git a/data/icons/64x64/go-previous.png b/data/icons/64x64/go-previous.png new file mode 100644 index 00000000..f6a95902 Binary files /dev/null and b/data/icons/64x64/go-previous.png differ diff --git a/data/icons/64x64/go-up.png b/data/icons/64x64/go-up.png new file mode 100644 index 00000000..32f2b446 Binary files /dev/null and b/data/icons/64x64/go-up.png differ diff --git a/data/icons/64x64/gstreamer.png b/data/icons/64x64/gstreamer.png new file mode 100644 index 00000000..9082e670 Binary files /dev/null and b/data/icons/64x64/gstreamer.png differ diff --git a/data/icons/64x64/guitar.png b/data/icons/64x64/guitar.png new file mode 100644 index 00000000..12d87b76 Binary files /dev/null and b/data/icons/64x64/guitar.png differ diff --git a/data/icons/64x64/headset.png b/data/icons/64x64/headset.png new file mode 100644 index 00000000..f0a89cb9 Binary files /dev/null and b/data/icons/64x64/headset.png differ diff --git a/data/icons/64x64/help-hint.png b/data/icons/64x64/help-hint.png new file mode 100644 index 00000000..970a4630 Binary files /dev/null and b/data/icons/64x64/help-hint.png differ diff --git a/data/icons/64x64/intel.png b/data/icons/64x64/intel.png new file mode 100644 index 00000000..ae56e106 Binary files /dev/null and b/data/icons/64x64/intel.png differ diff --git a/data/icons/64x64/jack.png b/data/icons/64x64/jack.png new file mode 100644 index 00000000..81d20e58 Binary files /dev/null and b/data/icons/64x64/jack.png differ diff --git a/data/icons/64x64/keyboard.png b/data/icons/64x64/keyboard.png new file mode 100644 index 00000000..9da070d7 Binary files /dev/null and b/data/icons/64x64/keyboard.png differ diff --git a/data/icons/64x64/list-add.png b/data/icons/64x64/list-add.png new file mode 100644 index 00000000..d4d76697 Binary files /dev/null and b/data/icons/64x64/list-add.png differ diff --git a/data/icons/64x64/list-remove.png b/data/icons/64x64/list-remove.png new file mode 100644 index 00000000..acfe18d8 Binary files /dev/null and b/data/icons/64x64/list-remove.png differ diff --git a/data/icons/64x64/mcintosh-player.png b/data/icons/64x64/mcintosh-player.png new file mode 100644 index 00000000..a4ccc4d5 Binary files /dev/null and b/data/icons/64x64/mcintosh-player.png differ diff --git a/data/icons/64x64/mcintosh-text.png b/data/icons/64x64/mcintosh-text.png new file mode 100644 index 00000000..2c6b2702 Binary files /dev/null and b/data/icons/64x64/mcintosh-text.png differ diff --git a/data/icons/64x64/media-eject.png b/data/icons/64x64/media-eject.png new file mode 100644 index 00000000..eeecbd90 Binary files /dev/null and b/data/icons/64x64/media-eject.png differ diff --git a/data/icons/64x64/media-forward.png b/data/icons/64x64/media-forward.png new file mode 100644 index 00000000..7e5a3f45 Binary files /dev/null and b/data/icons/64x64/media-forward.png differ diff --git a/data/icons/64x64/media-pause.png b/data/icons/64x64/media-pause.png new file mode 100644 index 00000000..c1470bf0 Binary files /dev/null and b/data/icons/64x64/media-pause.png differ diff --git a/data/icons/64x64/media-play.png b/data/icons/64x64/media-play.png new file mode 100644 index 00000000..bbc09e14 Binary files /dev/null and b/data/icons/64x64/media-play.png differ diff --git a/data/icons/64x64/media-rewind.png b/data/icons/64x64/media-rewind.png new file mode 100644 index 00000000..e7348957 Binary files /dev/null and b/data/icons/64x64/media-rewind.png differ diff --git a/data/icons/64x64/media-stop.png b/data/icons/64x64/media-stop.png new file mode 100644 index 00000000..00b19924 Binary files /dev/null and b/data/icons/64x64/media-stop.png differ diff --git a/data/icons/64x64/nvidia.png b/data/icons/64x64/nvidia.png new file mode 100644 index 00000000..defd810e Binary files /dev/null and b/data/icons/64x64/nvidia.png differ diff --git a/data/icons/64x64/play2.png b/data/icons/64x64/play2.png new file mode 100644 index 00000000..bbc09e14 Binary files /dev/null and b/data/icons/64x64/play2.png differ diff --git a/data/icons/64x64/pulseaudio.png b/data/icons/64x64/pulseaudio.png new file mode 100644 index 00000000..1c826970 Binary files /dev/null and b/data/icons/64x64/pulseaudio.png differ diff --git a/data/icons/64x64/realtek.png b/data/icons/64x64/realtek.png new file mode 100644 index 00000000..a4f877d0 Binary files /dev/null and b/data/icons/64x64/realtek.png differ diff --git a/data/icons/64x64/search.png b/data/icons/64x64/search.png new file mode 100644 index 00000000..6b1886aa Binary files /dev/null and b/data/icons/64x64/search.png differ diff --git a/data/icons/64x64/soundcard.png b/data/icons/64x64/soundcard.png new file mode 100644 index 00000000..bf7445d7 Binary files /dev/null and b/data/icons/64x64/soundcard.png differ diff --git a/data/icons/64x64/soundcard2.png b/data/icons/64x64/soundcard2.png new file mode 100644 index 00000000..50f41654 Binary files /dev/null and b/data/icons/64x64/soundcard2.png differ diff --git a/data/icons/64x64/speaker.png b/data/icons/64x64/speaker.png new file mode 100644 index 00000000..8da2def5 Binary files /dev/null and b/data/icons/64x64/speaker.png differ diff --git a/data/icons/64x64/star-grey.png b/data/icons/64x64/star-grey.png new file mode 100644 index 00000000..14975366 Binary files /dev/null and b/data/icons/64x64/star-grey.png differ diff --git a/data/icons/64x64/star.png b/data/icons/64x64/star.png new file mode 100644 index 00000000..d314d603 Binary files /dev/null and b/data/icons/64x64/star.png differ diff --git a/data/icons/64x64/strawberry-panel-grey.png b/data/icons/64x64/strawberry-panel-grey.png new file mode 100644 index 00000000..e1477099 Binary files /dev/null and b/data/icons/64x64/strawberry-panel-grey.png differ diff --git a/data/icons/64x64/strawberry-panel.png b/data/icons/64x64/strawberry-panel.png new file mode 100644 index 00000000..cabce039 Binary files /dev/null and b/data/icons/64x64/strawberry-panel.png differ diff --git a/data/icons/64x64/strawberry.png b/data/icons/64x64/strawberry.png new file mode 100644 index 00000000..3d0afa0a Binary files /dev/null and b/data/icons/64x64/strawberry.png differ diff --git a/data/icons/64x64/tools-wizard.png b/data/icons/64x64/tools-wizard.png new file mode 100644 index 00000000..9fe9a333 Binary files /dev/null and b/data/icons/64x64/tools-wizard.png differ diff --git a/data/icons/64x64/view-choose.png b/data/icons/64x64/view-choose.png new file mode 100644 index 00000000..e287f989 Binary files /dev/null and b/data/icons/64x64/view-choose.png differ diff --git a/data/icons/64x64/view-fullscreen.png b/data/icons/64x64/view-fullscreen.png new file mode 100644 index 00000000..06b77fec Binary files /dev/null and b/data/icons/64x64/view-fullscreen.png differ diff --git a/data/icons/64x64/view-media-lyrics.png b/data/icons/64x64/view-media-lyrics.png new file mode 100644 index 00000000..954e4179 Binary files /dev/null and b/data/icons/64x64/view-media-lyrics.png differ diff --git a/data/icons/64x64/view-media-playlist.png b/data/icons/64x64/view-media-playlist.png new file mode 100644 index 00000000..efcb6c8c Binary files /dev/null and b/data/icons/64x64/view-media-playlist.png differ diff --git a/data/icons/64x64/view-media-visualization.png b/data/icons/64x64/view-media-visualization.png new file mode 100644 index 00000000..775cade6 Binary files /dev/null and b/data/icons/64x64/view-media-visualization.png differ diff --git a/data/icons/64x64/view-refresh.png b/data/icons/64x64/view-refresh.png new file mode 100644 index 00000000..96d1bed5 Binary files /dev/null and b/data/icons/64x64/view-refresh.png differ diff --git a/data/icons/64x64/vinyl.png b/data/icons/64x64/vinyl.png new file mode 100644 index 00000000..98be243c Binary files /dev/null and b/data/icons/64x64/vinyl.png differ diff --git a/data/icons/64x64/vlc.png b/data/icons/64x64/vlc.png new file mode 100644 index 00000000..f8e9cc81 Binary files /dev/null and b/data/icons/64x64/vlc.png differ diff --git a/data/icons/64x64/xine.png b/data/icons/64x64/xine.png new file mode 100644 index 00000000..2d409d08 Binary files /dev/null and b/data/icons/64x64/xine.png differ diff --git a/data/icons/64x64/zoom-in.png b/data/icons/64x64/zoom-in.png new file mode 100644 index 00000000..e5bc6a77 Binary files /dev/null and b/data/icons/64x64/zoom-in.png differ diff --git a/data/icons/64x64/zoom-out.png b/data/icons/64x64/zoom-out.png new file mode 100644 index 00000000..baca92a2 Binary files /dev/null and b/data/icons/64x64/zoom-out.png differ diff --git a/data/icons/full/.png b/data/icons/full/.png new file mode 100644 index 00000000..1f9124d7 Binary files /dev/null and b/data/icons/full/.png differ diff --git a/data/icons/full/albums.png b/data/icons/full/albums.png new file mode 100644 index 00000000..d43a6181 Binary files /dev/null and b/data/icons/full/albums.png differ diff --git a/data/icons/full/alsa.png b/data/icons/full/alsa.png new file mode 100644 index 00000000..b8a13853 Binary files /dev/null and b/data/icons/full/alsa.png differ diff --git a/data/icons/full/application-exit.png b/data/icons/full/application-exit.png new file mode 100644 index 00000000..8adbcba9 Binary files /dev/null and b/data/icons/full/application-exit.png differ diff --git a/data/icons/full/applications-internet.png b/data/icons/full/applications-internet.png new file mode 100644 index 00000000..df5c07e9 Binary files /dev/null and b/data/icons/full/applications-internet.png differ diff --git a/data/icons/full/bluetooth.png b/data/icons/full/bluetooth.png new file mode 100644 index 00000000..2ca4bcf6 Binary files /dev/null and b/data/icons/full/bluetooth.png differ diff --git a/data/icons/full/cd.png b/data/icons/full/cd.png new file mode 100644 index 00000000..a4b0a0d9 Binary files /dev/null and b/data/icons/full/cd.png differ diff --git a/data/icons/full/cdcase.png b/data/icons/full/cdcase.png new file mode 100644 index 00000000..c9257638 Binary files /dev/null and b/data/icons/full/cdcase.png differ diff --git a/data/icons/full/configure.png b/data/icons/full/configure.png new file mode 100644 index 00000000..2a819572 Binary files /dev/null and b/data/icons/full/configure.png differ diff --git a/data/icons/full/device-ipod-nano.png b/data/icons/full/device-ipod-nano.png new file mode 100644 index 00000000..5fe855bd Binary files /dev/null and b/data/icons/full/device-ipod-nano.png differ diff --git a/data/icons/full/device-ipod.png b/data/icons/full/device-ipod.png new file mode 100644 index 00000000..8de7fe1f Binary files /dev/null and b/data/icons/full/device-ipod.png differ diff --git a/data/icons/full/device-phone.png b/data/icons/full/device-phone.png new file mode 100644 index 00000000..ff3d1af6 Binary files /dev/null and b/data/icons/full/device-phone.png differ diff --git a/data/icons/full/device-usb-drive.png b/data/icons/full/device-usb-drive.png new file mode 100644 index 00000000..387f096c Binary files /dev/null and b/data/icons/full/device-usb-drive.png differ diff --git a/data/icons/full/device-usb-flash.png b/data/icons/full/device-usb-flash.png new file mode 100644 index 00000000..f057ffff Binary files /dev/null and b/data/icons/full/device-usb-flash.png differ diff --git a/data/icons/full/device.png b/data/icons/full/device.png new file mode 100644 index 00000000..6e46c191 Binary files /dev/null and b/data/icons/full/device.png differ diff --git a/data/icons/full/dialog-error.png b/data/icons/full/dialog-error.png new file mode 100644 index 00000000..e4ddd42a Binary files /dev/null and b/data/icons/full/dialog-error.png differ diff --git a/data/icons/full/dialog-information.png b/data/icons/full/dialog-information.png new file mode 100644 index 00000000..fe7dd00e Binary files /dev/null and b/data/icons/full/dialog-information.png differ diff --git a/data/icons/full/dialog-ok-apply.png b/data/icons/full/dialog-ok-apply.png new file mode 100644 index 00000000..3260f604 Binary files /dev/null and b/data/icons/full/dialog-ok-apply.png differ diff --git a/data/icons/full/dialog-password.png b/data/icons/full/dialog-password.png new file mode 100644 index 00000000..d34ab706 Binary files /dev/null and b/data/icons/full/dialog-password.png differ diff --git a/data/icons/full/dialog-warning.png b/data/icons/full/dialog-warning.png new file mode 100644 index 00000000..b3196908 Binary files /dev/null and b/data/icons/full/dialog-warning.png differ diff --git a/data/icons/full/document-download.png b/data/icons/full/document-download.png new file mode 100644 index 00000000..8ce01699 Binary files /dev/null and b/data/icons/full/document-download.png differ diff --git a/data/icons/full/document-new.png b/data/icons/full/document-new.png new file mode 100644 index 00000000..e4d8b474 Binary files /dev/null and b/data/icons/full/document-new.png differ diff --git a/data/icons/full/document-open-folder.png b/data/icons/full/document-open-folder.png new file mode 100644 index 00000000..63380e48 Binary files /dev/null and b/data/icons/full/document-open-folder.png differ diff --git a/data/icons/full/document-open.png b/data/icons/full/document-open.png new file mode 100644 index 00000000..5dea408d Binary files /dev/null and b/data/icons/full/document-open.png differ diff --git a/data/icons/full/document-save.png b/data/icons/full/document-save.png new file mode 100644 index 00000000..aee3e22a Binary files /dev/null and b/data/icons/full/document-save.png differ diff --git a/data/icons/full/document-search.png b/data/icons/full/document-search.png new file mode 100644 index 00000000..470b7f07 Binary files /dev/null and b/data/icons/full/document-search.png differ diff --git a/data/icons/full/download.png b/data/icons/full/download.png new file mode 100644 index 00000000..fa021813 Binary files /dev/null and b/data/icons/full/download.png differ diff --git a/data/icons/full/edit-clear-list.png b/data/icons/full/edit-clear-list.png new file mode 100644 index 00000000..8fc3e086 Binary files /dev/null and b/data/icons/full/edit-clear-list.png differ diff --git a/data/icons/full/edit-clear-locationbar-ltr.png b/data/icons/full/edit-clear-locationbar-ltr.png new file mode 100644 index 00000000..41b1a56d Binary files /dev/null and b/data/icons/full/edit-clear-locationbar-ltr.png differ diff --git a/data/icons/full/edit-copy.png b/data/icons/full/edit-copy.png new file mode 100644 index 00000000..a8178caa Binary files /dev/null and b/data/icons/full/edit-copy.png differ diff --git a/data/icons/full/edit-delete.png b/data/icons/full/edit-delete.png new file mode 100644 index 00000000..38f11cd5 Binary files /dev/null and b/data/icons/full/edit-delete.png differ diff --git a/data/icons/full/edit-find.png b/data/icons/full/edit-find.png new file mode 100644 index 00000000..140e5810 Binary files /dev/null and b/data/icons/full/edit-find.png differ diff --git a/data/icons/full/edit-redo.png b/data/icons/full/edit-redo.png new file mode 100644 index 00000000..353a6c0d Binary files /dev/null and b/data/icons/full/edit-redo.png differ diff --git a/data/icons/full/edit-rename.png b/data/icons/full/edit-rename.png new file mode 100644 index 00000000..4b47837b Binary files /dev/null and b/data/icons/full/edit-rename.png differ diff --git a/data/icons/full/edit-undo.png b/data/icons/full/edit-undo.png new file mode 100644 index 00000000..a6774095 Binary files /dev/null and b/data/icons/full/edit-undo.png differ diff --git a/data/icons/full/electrocompaniet.png b/data/icons/full/electrocompaniet.png new file mode 100644 index 00000000..4ae5e582 Binary files /dev/null and b/data/icons/full/electrocompaniet.png differ diff --git a/data/icons/full/equalizer.png b/data/icons/full/equalizer.png new file mode 100644 index 00000000..80fd9d2b Binary files /dev/null and b/data/icons/full/equalizer.png differ diff --git a/data/icons/full/folder-new.png b/data/icons/full/folder-new.png new file mode 100644 index 00000000..40596460 Binary files /dev/null and b/data/icons/full/folder-new.png differ diff --git a/data/icons/full/folder-sound.png b/data/icons/full/folder-sound.png new file mode 100644 index 00000000..45fa5f9a Binary files /dev/null and b/data/icons/full/folder-sound.png differ diff --git a/data/icons/full/folder.png b/data/icons/full/folder.png new file mode 100644 index 00000000..9eacb377 Binary files /dev/null and b/data/icons/full/folder.png differ diff --git a/data/icons/full/footsteps.png b/data/icons/full/footsteps.png new file mode 100644 index 00000000..5741820d Binary files /dev/null and b/data/icons/full/footsteps.png differ diff --git a/data/icons/full/go-down.png b/data/icons/full/go-down.png new file mode 100644 index 00000000..680ac2d8 Binary files /dev/null and b/data/icons/full/go-down.png differ diff --git a/data/icons/full/go-home.png b/data/icons/full/go-home.png new file mode 100644 index 00000000..0c07adf1 Binary files /dev/null and b/data/icons/full/go-home.png differ diff --git a/data/icons/full/go-jump.png b/data/icons/full/go-jump.png new file mode 100644 index 00000000..7437eff2 Binary files /dev/null and b/data/icons/full/go-jump.png differ diff --git a/data/icons/full/go-next.png b/data/icons/full/go-next.png new file mode 100644 index 00000000..cacea05d Binary files /dev/null and b/data/icons/full/go-next.png differ diff --git a/data/icons/full/go-previous.png b/data/icons/full/go-previous.png new file mode 100644 index 00000000..b4b00f5a Binary files /dev/null and b/data/icons/full/go-previous.png differ diff --git a/data/icons/full/go-up.png b/data/icons/full/go-up.png new file mode 100644 index 00000000..5e8f7dd5 Binary files /dev/null and b/data/icons/full/go-up.png differ diff --git a/data/icons/full/gstreamer.png b/data/icons/full/gstreamer.png new file mode 100644 index 00000000..9082e670 Binary files /dev/null and b/data/icons/full/gstreamer.png differ diff --git a/data/icons/full/guitar.png b/data/icons/full/guitar.png new file mode 100644 index 00000000..a283de39 Binary files /dev/null and b/data/icons/full/guitar.png differ diff --git a/data/icons/full/headset.png b/data/icons/full/headset.png new file mode 100644 index 00000000..1f9124d7 Binary files /dev/null and b/data/icons/full/headset.png differ diff --git a/data/icons/full/help-hint.png b/data/icons/full/help-hint.png new file mode 100644 index 00000000..8fc8447d Binary files /dev/null and b/data/icons/full/help-hint.png differ diff --git a/data/icons/full/intel.png b/data/icons/full/intel.png new file mode 100644 index 00000000..5de26d1b Binary files /dev/null and b/data/icons/full/intel.png differ diff --git a/data/icons/full/jack.png b/data/icons/full/jack.png new file mode 100644 index 00000000..9d53dc25 Binary files /dev/null and b/data/icons/full/jack.png differ diff --git a/data/icons/full/keyboard.png b/data/icons/full/keyboard.png new file mode 100644 index 00000000..4340f0e5 Binary files /dev/null and b/data/icons/full/keyboard.png differ diff --git a/data/icons/full/list-add.png b/data/icons/full/list-add.png new file mode 100644 index 00000000..a15dd10a Binary files /dev/null and b/data/icons/full/list-add.png differ diff --git a/data/icons/full/list-remove.png b/data/icons/full/list-remove.png new file mode 100644 index 00000000..fffb2484 Binary files /dev/null and b/data/icons/full/list-remove.png differ diff --git a/data/icons/full/mcintosh-player.png b/data/icons/full/mcintosh-player.png new file mode 100644 index 00000000..b817ddc3 Binary files /dev/null and b/data/icons/full/mcintosh-player.png differ diff --git a/data/icons/full/mcintosh-text.png b/data/icons/full/mcintosh-text.png new file mode 100644 index 00000000..3207da77 Binary files /dev/null and b/data/icons/full/mcintosh-text.png differ diff --git a/data/icons/full/mcintosh.png b/data/icons/full/mcintosh.png new file mode 100644 index 00000000..ea35f7b5 Binary files /dev/null and b/data/icons/full/mcintosh.png differ diff --git a/data/icons/full/media-eject.png b/data/icons/full/media-eject.png new file mode 100644 index 00000000..536fb9fe Binary files /dev/null and b/data/icons/full/media-eject.png differ diff --git a/data/icons/full/media-forward.png b/data/icons/full/media-forward.png new file mode 100644 index 00000000..328e47e4 Binary files /dev/null and b/data/icons/full/media-forward.png differ diff --git a/data/icons/full/media-pause.png b/data/icons/full/media-pause.png new file mode 100644 index 00000000..0d73fe2f Binary files /dev/null and b/data/icons/full/media-pause.png differ diff --git a/data/icons/full/media-play.png b/data/icons/full/media-play.png new file mode 100644 index 00000000..5c8c97a2 Binary files /dev/null and b/data/icons/full/media-play.png differ diff --git a/data/icons/full/media-playlist-repeat.png b/data/icons/full/media-playlist-repeat.png new file mode 100644 index 00000000..089a4615 Binary files /dev/null and b/data/icons/full/media-playlist-repeat.png differ diff --git a/data/icons/full/media-playlist-shuffle.png b/data/icons/full/media-playlist-shuffle.png new file mode 100644 index 00000000..99de62ff Binary files /dev/null and b/data/icons/full/media-playlist-shuffle.png differ diff --git a/data/icons/full/media-rewind.png b/data/icons/full/media-rewind.png new file mode 100644 index 00000000..d92c7755 Binary files /dev/null and b/data/icons/full/media-rewind.png differ diff --git a/data/icons/full/media-stop.png b/data/icons/full/media-stop.png new file mode 100644 index 00000000..d2e9d103 Binary files /dev/null and b/data/icons/full/media-stop.png differ diff --git a/data/icons/full/nvidia.png b/data/icons/full/nvidia.png new file mode 100644 index 00000000..154c7b6a Binary files /dev/null and b/data/icons/full/nvidia.png differ diff --git a/data/icons/full/play2.png b/data/icons/full/play2.png new file mode 100644 index 00000000..5c8c97a2 Binary files /dev/null and b/data/icons/full/play2.png differ diff --git a/data/icons/full/pulseaudio.png b/data/icons/full/pulseaudio.png new file mode 100644 index 00000000..a9ddd787 Binary files /dev/null and b/data/icons/full/pulseaudio.png differ diff --git a/data/icons/full/realtek.png b/data/icons/full/realtek.png new file mode 100644 index 00000000..3c80da73 Binary files /dev/null and b/data/icons/full/realtek.png differ diff --git a/data/icons/full/search.png b/data/icons/full/search.png new file mode 100644 index 00000000..84b5c16a Binary files /dev/null and b/data/icons/full/search.png differ diff --git a/data/icons/full/soundcard.png b/data/icons/full/soundcard.png new file mode 100644 index 00000000..955b89b8 Binary files /dev/null and b/data/icons/full/soundcard.png differ diff --git a/data/icons/full/soundcard2.png b/data/icons/full/soundcard2.png new file mode 100644 index 00000000..c23d4f28 Binary files /dev/null and b/data/icons/full/soundcard2.png differ diff --git a/data/icons/full/speaker.png b/data/icons/full/speaker.png new file mode 100644 index 00000000..80715c9e Binary files /dev/null and b/data/icons/full/speaker.png differ diff --git a/data/icons/full/star-grey.png b/data/icons/full/star-grey.png new file mode 100644 index 00000000..bb47f5f3 Binary files /dev/null and b/data/icons/full/star-grey.png differ diff --git a/data/icons/full/star.png b/data/icons/full/star.png new file mode 100644 index 00000000..ecced10c Binary files /dev/null and b/data/icons/full/star.png differ diff --git a/data/icons/full/strawberry-panel-grey.png b/data/icons/full/strawberry-panel-grey.png new file mode 100644 index 00000000..75d8a1e5 Binary files /dev/null and b/data/icons/full/strawberry-panel-grey.png differ diff --git a/data/icons/full/strawberry-panel.png b/data/icons/full/strawberry-panel.png new file mode 100644 index 00000000..e651e1b3 Binary files /dev/null and b/data/icons/full/strawberry-panel.png differ diff --git a/data/icons/full/strawberry.png b/data/icons/full/strawberry.png new file mode 100644 index 00000000..b0c9db07 Binary files /dev/null and b/data/icons/full/strawberry.png differ diff --git a/data/icons/full/strawberry.svg b/data/icons/full/strawberry.svg new file mode 100644 index 00000000..b48ca3f2 --- /dev/null +++ b/data/icons/full/strawberry.svg @@ -0,0 +1,454 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/data/icons/full/tools-wizard.png b/data/icons/full/tools-wizard.png new file mode 100644 index 00000000..0101c36f Binary files /dev/null and b/data/icons/full/tools-wizard.png differ diff --git a/data/icons/full/view-choose.png b/data/icons/full/view-choose.png new file mode 100644 index 00000000..942bc77b Binary files /dev/null and b/data/icons/full/view-choose.png differ diff --git a/data/icons/full/view-fullscreen.png b/data/icons/full/view-fullscreen.png new file mode 100644 index 00000000..4a7c152c Binary files /dev/null and b/data/icons/full/view-fullscreen.png differ diff --git a/data/icons/full/view-media-lyrics.png b/data/icons/full/view-media-lyrics.png new file mode 100644 index 00000000..d95431fd Binary files /dev/null and b/data/icons/full/view-media-lyrics.png differ diff --git a/data/icons/full/view-media-playlist.png b/data/icons/full/view-media-playlist.png new file mode 100644 index 00000000..39853ca6 Binary files /dev/null and b/data/icons/full/view-media-playlist.png differ diff --git a/data/icons/full/view-media-visualization.png b/data/icons/full/view-media-visualization.png new file mode 100644 index 00000000..caabe002 Binary files /dev/null and b/data/icons/full/view-media-visualization.png differ diff --git a/data/icons/full/view-refresh.png b/data/icons/full/view-refresh.png new file mode 100644 index 00000000..66f0cebe Binary files /dev/null and b/data/icons/full/view-refresh.png differ diff --git a/data/icons/full/vinyl.png b/data/icons/full/vinyl.png new file mode 100644 index 00000000..7fd8fc4f Binary files /dev/null and b/data/icons/full/vinyl.png differ diff --git a/data/icons/full/vlc.png b/data/icons/full/vlc.png new file mode 100644 index 00000000..0bb9ef91 Binary files /dev/null and b/data/icons/full/vlc.png differ diff --git a/data/icons/full/xine.png b/data/icons/full/xine.png new file mode 100644 index 00000000..600a57e9 Binary files /dev/null and b/data/icons/full/xine.png differ diff --git a/data/icons/full/zoom-in.png b/data/icons/full/zoom-in.png new file mode 100644 index 00000000..9a97124a Binary files /dev/null and b/data/icons/full/zoom-in.png differ diff --git a/data/icons/full/zoom-out.png b/data/icons/full/zoom-out.png new file mode 100644 index 00000000..6e4e03f5 Binary files /dev/null and b/data/icons/full/zoom-out.png differ diff --git a/data/icons/generate-icons.sh b/data/icons/generate-icons.sh new file mode 100755 index 00000000..357dd215 --- /dev/null +++ b/data/icons/generate-icons.sh @@ -0,0 +1,114 @@ +#!/bin/sh + +sizes="128x128 64x64 48x48 32x32 22x22" + +# + +for i in full/* +do + source=$i + file=`basename $i` + + id=`identify "$i"` || exit 1 + if [ "$id" = "" ] ; then + echo "ERROR: Cannot dermine format and geometry for image: \"$i\"." + continue + fi + g=`echo $id | awk '{print $3}'` || exit 1 + if [ "$g" = "" ] ; then + echo "ERROR: Cannot dermine geometry for image: \"$i\"." + continue + fi + + # Geometry can be 563x144+0+0 or 75x98 + # we need to get rid of the plus (+) and the x characters: + w=`echo $g | sed 's/[^0-9]/ /g' | awk '{print $1}'` || exit 1 + if [ "$w" = "" ] ; then + echo "ERROR: Cannot dermine width for image: \"$x\"." + continue + fi + h=`echo $g | sed 's/[^0-9]/ /g' | awk '{print $2}'` || exit 1 + if [ "$h" = "" ] ; then + echo "ERROR: Cannot dermine height for image: \"$x\"." + continue + fi + + for x in $sizes + do + + dest="$x/$file" + if [ -f $dest ]; then + continue + fi + + x_w=$(echo $x | cut -d 'x' -f1) + x_h=$(echo $x | cut -d 'x' -f2) + + if [ "$w" -lt "$x_w" ] || [ "$h" -lt "$x_h" ]; then + continue + fi + + echo "convert -verbose -resize $x $source $dest" + convert -verbose -resize $x $source $dest + + done +done + + +for i in $sizes +do + for x in $i/* + do + file=`basename $x` + if ! [ -f "full/$file" ]; then + echo "Warning: full/$file does not exist, but $x exists." + fi + + id=`identify "$x"` || exit 1 + if [ "$id" = "" ] ; then + echo "ERROR: Cannot dermine format and geometry for image: \"$x\"." + continue + fi + g=`echo $id | awk '{print $3}'` || exit 1 + if [ "$g" = "" ] ; then + echo "ERROR: Cannot dermine geometry for image: \"$x\"." + continue + fi + + # Geometry can be 563x144+0+0 or 75x98 + # we need to get rid of the plus (+) and the x characters: + w=`echo $g | sed 's/[^0-9]/ /g' | awk '{print $1}'` || exit 1 + if [ "$w" = "" ] ; then + echo "ERROR: Cannot dermine width for image: \"$x\"." + continue + fi + h=`echo $g | sed 's/[^0-9]/ /g' | awk '{print $2}'` || exit 1 + if [ "$h" = "" ] ; then + echo "ERROR: Cannot dermine height for image: \"$x\"." + continue + fi + + if ! [ "${h}x${w}" = "$i" ]; then + echo "Warning: $x is not $i, but ${h}x${w}!" + fi + + done +done + +file="../icons.qrc" +rm -rf "$file" +echo "" >>$file +echo "" >>$file + +for i in full $sizes +do + for x in $i/* + do + f=`basename $x` + echo " icons/$i/$f" >>$file + done +done + +echo "" >>$file +echo "" >>$file + diff --git a/data/misc/blank.ttf b/data/misc/blank.ttf new file mode 100644 index 00000000..bba4ca06 Binary files /dev/null and b/data/misc/blank.ttf differ diff --git a/data/misc/playing_tooltip.txt b/data/misc/playing_tooltip.txt new file mode 100644 index 00000000..5a3de69f --- /dev/null +++ b/data/misc/playing_tooltip.txt @@ -0,0 +1,41 @@ + + + + + + %image + + +
+

%appName

+
+ + + + + + + + + + + + + + + + + + + +
+

%titleKey

+
%titleValue
+

%artistKey

+
%artistValue
+

%albumKey

+
%albumValue
+
+

%lengthKey

+
%lengthValue
+
diff --git a/data/pictures/.icon_large.png-autosave.kra b/data/pictures/.icon_large.png-autosave.kra new file mode 100644 index 00000000..d3919ce6 Binary files /dev/null and b/data/pictures/.icon_large.png-autosave.kra differ diff --git a/data/pictures/currenttrack_bar_left.png b/data/pictures/currenttrack_bar_left.png new file mode 100644 index 00000000..e0827d7e Binary files /dev/null and b/data/pictures/currenttrack_bar_left.png differ diff --git a/data/pictures/currenttrack_bar_mid.png b/data/pictures/currenttrack_bar_mid.png new file mode 100644 index 00000000..ef843770 Binary files /dev/null and b/data/pictures/currenttrack_bar_mid.png differ diff --git a/data/pictures/currenttrack_bar_right.png b/data/pictures/currenttrack_bar_right.png new file mode 100644 index 00000000..5dfd7dcc Binary files /dev/null and b/data/pictures/currenttrack_bar_right.png differ diff --git a/data/pictures/currenttrack_pause.png b/data/pictures/currenttrack_pause.png new file mode 100644 index 00000000..f4897310 Binary files /dev/null and b/data/pictures/currenttrack_pause.png differ diff --git a/data/pictures/currenttrack_play.png b/data/pictures/currenttrack_play.png new file mode 100644 index 00000000..ba4c061d Binary files /dev/null and b/data/pictures/currenttrack_play.png differ diff --git a/data/pictures/musicbrainz.png b/data/pictures/musicbrainz.png new file mode 100644 index 00000000..66c8e958 Binary files /dev/null and b/data/pictures/musicbrainz.png differ diff --git a/data/pictures/noalbumart.jpg b/data/pictures/noalbumart.jpg new file mode 100644 index 00000000..287005a7 Binary files /dev/null and b/data/pictures/noalbumart.jpg differ diff --git a/data/pictures/noalbumart.png b/data/pictures/noalbumart.png new file mode 100644 index 00000000..18920fc8 Binary files /dev/null and b/data/pictures/noalbumart.png differ diff --git a/data/pictures/nomusic.png b/data/pictures/nomusic.png new file mode 100644 index 00000000..044b634a Binary files /dev/null and b/data/pictures/nomusic.png differ diff --git a/data/pictures/osd_background.png b/data/pictures/osd_background.png new file mode 100644 index 00000000..f11404b9 Binary files /dev/null and b/data/pictures/osd_background.png differ diff --git a/data/pictures/osd_shadow_corner.png b/data/pictures/osd_shadow_corner.png new file mode 100644 index 00000000..d236ed28 Binary files /dev/null and b/data/pictures/osd_shadow_corner.png differ diff --git a/data/pictures/osd_shadow_edge.png b/data/pictures/osd_shadow_edge.png new file mode 100644 index 00000000..d2e22c79 Binary files /dev/null and b/data/pictures/osd_shadow_edge.png differ diff --git a/data/pictures/pigeon1.png b/data/pictures/pigeon1.png new file mode 100644 index 00000000..323d3398 Binary files /dev/null and b/data/pictures/pigeon1.png differ diff --git a/data/pictures/pigeon2.png b/data/pictures/pigeon2.png new file mode 100644 index 00000000..7f54637a Binary files /dev/null and b/data/pictures/pigeon2.png differ diff --git a/data/pictures/spinner.gif b/data/pictures/spinner.gif new file mode 100644 index 00000000..d0bce154 Binary files /dev/null and b/data/pictures/spinner.gif differ diff --git a/data/pictures/strawberry-background.gif b/data/pictures/strawberry-background.gif new file mode 100644 index 00000000..ad1d9fea Binary files /dev/null and b/data/pictures/strawberry-background.gif differ diff --git a/data/pictures/strawberry-background.png b/data/pictures/strawberry-background.png new file mode 100644 index 00000000..645a926e Binary files /dev/null and b/data/pictures/strawberry-background.png differ diff --git a/data/pictures/strawberry.png b/data/pictures/strawberry.png new file mode 100644 index 00000000..4ea988f1 Binary files /dev/null and b/data/pictures/strawberry.png differ diff --git a/data/pictures/strawbs-transparent.png b/data/pictures/strawbs-transparent.png new file mode 100644 index 00000000..99ff4326 Binary files /dev/null and b/data/pictures/strawbs-transparent.png differ diff --git a/data/pictures/strawbs.jpg b/data/pictures/strawbs.jpg new file mode 100644 index 00000000..f33de34f Binary files /dev/null and b/data/pictures/strawbs.jpg differ diff --git a/data/pictures/tiny-pause.png b/data/pictures/tiny-pause.png new file mode 100644 index 00000000..44b65526 Binary files /dev/null and b/data/pictures/tiny-pause.png differ diff --git a/data/pictures/tiny-play.png b/data/pictures/tiny-play.png new file mode 100644 index 00000000..49d2a93f Binary files /dev/null and b/data/pictures/tiny-play.png differ diff --git a/data/pictures/volumeslider-gradient.png b/data/pictures/volumeslider-gradient.png new file mode 100644 index 00000000..d6dfab83 Binary files /dev/null and b/data/pictures/volumeslider-gradient.png differ diff --git a/data/pictures/volumeslider-handle.png b/data/pictures/volumeslider-handle.png new file mode 100644 index 00000000..7a8d2971 Binary files /dev/null and b/data/pictures/volumeslider-handle.png differ diff --git a/data/pictures/volumeslider-handle_glow.png b/data/pictures/volumeslider-handle_glow.png new file mode 100644 index 00000000..3684e225 Binary files /dev/null and b/data/pictures/volumeslider-handle_glow.png differ diff --git a/data/pictures/volumeslider-inset.png b/data/pictures/volumeslider-inset.png new file mode 100644 index 00000000..ca111093 Binary files /dev/null and b/data/pictures/volumeslider-inset.png differ diff --git a/data/schema/device-schema.sql b/data/schema/device-schema.sql new file mode 100644 index 00000000..804faf5a --- /dev/null +++ b/data/schema/device-schema.sql @@ -0,0 +1,79 @@ +CREATE TABLE device_%deviceid_directories ( + path TEXT NOT NULL, + subdirs INTEGER NOT NULL +); + +CREATE TABLE device_%deviceid_subdirectories ( + directory_id INTEGER NOT NULL, + path TEXT NOT NULL, + mtime INTEGER NOT NULL +); + +CREATE TABLE device_%deviceid_songs ( + + /* Metadata from taglib */ + + title TEXT NOT NULL, + album TEXT NOT NULL, + artist TEXT NOT NULL, + albumartist TEXT NOT NULL, + track INTEGER NOT NULL DEFAULT -1, + disc INTEGER NOT NULL DEFAULT -1, + year INTEGER NOT NULL DEFAULT -1, + originalyear INTEGER NOT NULL DEFAULT 0, + genre TEXT NOT NULL, + compilation INTEGER NOT NULL DEFAULT -1, + composer TEXT NOT NULL, + performer TEXT NOT NULL, + grouping TEXT NOT NULL, + comment TEXT NOT NULL, + + beginning INTEGER NOT NULL DEFAULT 0, + length INTEGER NOT NULL DEFAULT 0, + + bitrate INTEGER NOT NULL DEFAULT 0, + samplerate INTEGER NOT NULL DEFAULT 0, + bitdepth INTEGER NOT NULL DEFAULT 0, + + /* Information about the file on disk */ + + directory_id INTEGER NOT NULL, + filename TEXT NOT NULL, + filetype INTEGER NOT NULL DEFAULT 0, + filesize INTEGER NOT NULL, + mtime INTEGER NOT NULL, + ctime INTEGER NOT NULL, + unavailable INTEGER DEFAULT 0, + + /* Other */ + + playcount INTEGER NOT NULL DEFAULT 0, + skipcount INTEGER NOT NULL DEFAULT 0, + lastplayed INTEGER NOT NULL DEFAULT 0, + + compilation_detected INTEGER DEFAULT 0, + compilation_on INTEGER NOT NULL DEFAULT 0, + compilation_off INTEGER NOT NULL DEFAULT 0, + compilation_effective INTEGER NOT NULL DEFAULT 0, + + art_automatic TEXT, + art_manual TEXT, + + effective_albumartist TEXT, + effective_originalyear INTEGER NOT NULL DEFAULT 0, + + cue_path TEXT + +); + +CREATE INDEX idx_device_%deviceid_songs_album ON device_%deviceid_songs (album); + +CREATE INDEX idx_device_%deviceid_songs_comp_artist ON device_%deviceid_songs (compilation_effective, artist); + +CREATE VIRTUAL TABLE device_%deviceid_fts USING fts3( + ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment, + tokenize=unicode +); + +UPDATE devices SET schema_version=0 WHERE ROWID=%deviceid; + diff --git a/data/schema/schema.sql b/data/schema/schema.sql new file mode 100644 index 00000000..fd588c91 --- /dev/null +++ b/data/schema/schema.sql @@ -0,0 +1,218 @@ +CREATE TABLE schema_version ( + version INTEGER NOT NULL +); + +INSERT INTO schema_version (version) VALUES (0); + +CREATE TABLE directories ( + path TEXT NOT NULL, + subdirs INTEGER NOT NULL +); + +CREATE TABLE subdirectories ( + directory_id INTEGER NOT NULL, + path TEXT NOT NULL, + mtime INTEGER NOT NULL +); + +CREATE TABLE songs ( + + /* Metadata from taglib */ + + title TEXT NOT NULL, + album TEXT NOT NULL, + artist TEXT NOT NULL, + albumartist TEXT NOT NULL, + track INTEGER NOT NULL DEFAULT -1, + disc INTEGER NOT NULL DEFAULT -1, + year INTEGER NOT NULL DEFAULT -1, + originalyear INTEGER NOT NULL DEFAULT 0, + genre TEXT NOT NULL, + compilation INTEGER NOT NULL DEFAULT -1, + composer TEXT NOT NULL, + performer TEXT NOT NULL, + grouping TEXT NOT NULL, + comment TEXT NOT NULL, + + beginning INTEGER NOT NULL DEFAULT 0, + length INTEGER NOT NULL DEFAULT 0, + + bitrate INTEGER NOT NULL DEFAULT 0, + samplerate INTEGER NOT NULL DEFAULT 0, + bitdepth INTEGER NOT NULL DEFAULT 0, + + /* Information about the file on disk */ + + directory_id INTEGER NOT NULL, + filename TEXT NOT NULL, + filetype INTEGER NOT NULL DEFAULT 0, + filesize INTEGER NOT NULL, + mtime INTEGER NOT NULL, + ctime INTEGER NOT NULL, + unavailable INTEGER DEFAULT 0, + + /* Other */ + + playcount INTEGER NOT NULL DEFAULT 0, + skipcount INTEGER NOT NULL DEFAULT 0, + lastplayed INTEGER NOT NULL DEFAULT 0, + + compilation_detected INTEGER DEFAULT 0, + compilation_on INTEGER NOT NULL DEFAULT 0, + compilation_off INTEGER NOT NULL DEFAULT 0, + compilation_effective INTEGER NOT NULL DEFAULT 0, + + art_automatic TEXT, + art_manual TEXT, + + effective_albumartist TEXT, + effective_originalyear INTEGER NOT NULL DEFAULT 0, + + cue_path TEXT + +); + +CREATE TABLE playlists ( + + name TEXT NOT NULL, + last_played INTEGER NOT NULL DEFAULT -1, + ui_order INTEGER NOT NULL DEFAULT 0, + special_type TEXT, + ui_path TEXT, + is_favorite INTEGER NOT NULL DEFAULT 0 + +); + +CREATE TABLE playlist_items ( + + playlist INTEGER NOT NULL, + type TEXT NOT NULL, + collection_id INTEGER, + url TEXT, + + /* Metadata from taglib */ + + title TEXT NOT NULL, + album TEXT NOT NULL, + artist TEXT NOT NULL, + albumartist TEXT NOT NULL, + track INTEGER NOT NULL DEFAULT -1, + disc INTEGER NOT NULL DEFAULT -1, + year INTEGER NOT NULL DEFAULT -1, + originalyear INTEGER NOT NULL DEFAULT 0, + genre TEXT NOT NULL, + compilation INTEGER NOT NULL DEFAULT -1, + composer TEXT NOT NULL, + performer TEXT NOT NULL, + grouping TEXT NOT NULL, + comment TEXT NOT NULL, + + beginning INTEGER NOT NULL DEFAULT 0, + length INTEGER NOT NULL DEFAULT 0, + + bitrate INTEGER NOT NULL DEFAULT 0, + samplerate INTEGER NOT NULL DEFAULT 0, + bitdepth INTEGER NOT NULL DEFAULT 0, + + /* Information about the file on disk */ + + directory_id INTEGER, + filename TEXT, + filetype INTEGER NOT NULL DEFAULT 0, + filesize INTEGER, + mtime INTEGER, + ctime INTEGER, + unavailable INTEGER DEFAULT 0, + + /* Other */ + + playcount INTEGER NOT NULL DEFAULT 0, + skipcount INTEGER NOT NULL DEFAULT 0, + lastplayed INTEGER NOT NULL DEFAULT 0, + + compilation_detected INTEGER DEFAULT 0, + compilation_on INTEGER NOT NULL DEFAULT 0, + compilation_off INTEGER NOT NULL DEFAULT 0, + compilation_effective INTEGER NOT NULL DEFAULT 0, + + art_automatic TEXT, + art_manual TEXT, + + effective_albumartist TEXT, + effective_originalyear INTEGER NOT NULL DEFAULT 0, + + cue_path TEXT + +); + +CREATE TABLE devices ( + unique_id TEXT NOT NULL, + friendly_name TEXT, + size INTEGER, + icon TEXT, + schema_version INTEGER NOT NULL DEFAULT 0, + transcode_mode NOT NULL DEFAULT 3, + transcode_format NOT NULL DEFAULT 5 +); + +CREATE INDEX idx_filename ON songs (filename); + +CREATE INDEX idx_comp_artist ON songs (compilation_effective, artist); + +CREATE INDEX idx_album ON songs (album); + +CREATE INDEX idx_title ON songs (title); + +CREATE VIEW duplicated_songs as select artist dup_artist, album dup_album, title dup_title from songs as inner_songs where artist != '' and album != '' and title != '' and unavailable = 0 group by artist, album , title having count(*) > 1; + +CREATE VIRTUAL TABLE songs_fts USING fts3( + + ftstitle, + ftsalbum, + ftsartist, + ftsalbumartist, + ftscomposer, + ftsperformer, + ftsgrouping, + ftsgenre, + ftscomment, + tokenize=unicode + +); + +CREATE VIRTUAL TABLE playlist_items_fts_ USING fts3( + + ftstitle, + ftsalbum, + ftsartist, + ftsalbumartist, + ftscomposer, + ftsperformer, + ftsgrouping, + ftsgenre, + ftscomment, + tokenize=unicode + +); + +CREATE VIRTUAL TABLE %allsongstables_fts USING fts3( + + ftstitle, + ftsalbum, + ftsartist, + ftsalbumartist, + ftscomposer, + ftsperformer, + ftsgrouping, + ftsgenre, + ftscomment, + tokenize=unicode + +); + + +INSERT INTO songs_fts (ROWID, ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment) +SELECT ROWID, title, album, artist, albumartist, composer, performer, grouping, genre, comment FROM songs; + +INSERT INTO %allsongstables_fts (ROWID, ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment) +SELECT ROWID, title, album, artist, albumartist, composer, performer, grouping, genre, comment FROM %allsongstables; diff --git a/data/style/mainwindow.css b/data/style/mainwindow.css new file mode 100644 index 00000000..0a6712ea --- /dev/null +++ b/data/style/mainwindow.css @@ -0,0 +1,54 @@ +#playlist { + background-color: %palette-base; + alternate-background-color: %palette-alternate-base; + +} + +#playlist[default_background_enabled = "true"] { + background-image: url(:/pictures/strawbs-transparent.png); + background-attachment: fixed; + background-position: bottom right; + background-repeat: none; + background-clip: content; +} + +QToolButton { + border: 2px solid transparent; + border-radius: 3px; + padding: 1px; +} + +QToolButton::menu-button { + width: 16px; + border: none; +} + +QToolButton[popupMode="1"] { + padding-right: 16px; +} + +QToolButton:hover { + border: 2px solid %palette-highlight; + background-color: %palette-highlight-lighter; +} + +QToolButton:hover[popupMode="1"] { + padding-right: 16px; +} + +QToolButton:pressed { + border: 2px solid %palette-highlight-darker; + background-color: %palette-highlight-lighter; +} + +QToolButton:pressed[popupMode="1"] { + padding-right: 16px; +} + +darwin { + font-size: 11pt; +} + +darwin QMenu { + font-size: 13pt; +} diff --git a/data/style/statusview.css b/data/style/statusview.css new file mode 100644 index 00000000..6203a100 --- /dev/null +++ b/data/style/statusview.css @@ -0,0 +1,11 @@ +StatusView { + background: white; + background-color: white; +} +QVBoxLayout { + background: white; + background-color: white; +} +QScrollArea { + background: qpalette(base); +} diff --git a/dist/CMakeLists.txt b/dist/CMakeLists.txt new file mode 100644 index 00000000..cd5076c1 --- /dev/null +++ b/dist/CMakeLists.txt @@ -0,0 +1,25 @@ +execute_process(COMMAND date "+%a %b %d %Y" + OUTPUT_VARIABLE RPM_DATE OUTPUT_STRIP_TRAILING_WHITESPACE) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/strawberry.spec.in + ${CMAKE_CURRENT_SOURCE_DIR}/strawberry.spec @ONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/maketarball.sh.in + ${CMAKE_CURRENT_SOURCE_DIR}/maketarball.sh @ONLY) + +if (NOT APPLE) + install(FILES ../data/icons/48x48/strawberry.png + DESTINATION share/icons/hicolor/48x48/apps/ + ) + install(FILES ../data/icons/64x64/strawberry.png + DESTINATION share/icons/hicolor/64x64/apps/ + ) + install(FILES ../data/icons/128x128/strawberry.png + DESTINATION share/icons/hicolor/128x128/apps/ + ) + install(FILES ../data/icons/128x128/strawberry.svg + DESTINATION share/icons/hicolor/scalable/apps/ + ) + install(FILES strawberry.desktop + DESTINATION share/applications + ) +endif (NOT APPLE) diff --git a/dist/maketarball.sh b/dist/maketarball.sh new file mode 100755 index 00000000..3edca956 --- /dev/null +++ b/dist/maketarball.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +name=strawberry +version="0.1.1" +deb_dist="unstable" +root=$(cd "${0%/*}/.." && echo $PWD/${0##*/}) +root=`dirname "$root"` +rootnoslash=`echo $root | sed "s/^\///"` + +echo "Creating $name-$version.tar.xz..." + +tar -cJf $name-$version.tar.xz \ + --transform "s,^$rootnoslash,$name-$version," \ + --exclude-vcs \ + --exclude "$root/dist/*.tar" \ + --exclude "$root/dist/*.tar.*" \ + --exclude "$root/CMakeLists.txt.user" \ + "$root" + +echo "Also creating ${name}_${version}~${deb_dist}.orig.tar.xz..." +cp "$name-$version.tar.xz" "${name}_${version}~${deb_dist}.orig.tar.xz" diff --git a/dist/maketarball.sh.in b/dist/maketarball.sh.in new file mode 100755 index 00000000..fc7ad225 --- /dev/null +++ b/dist/maketarball.sh.in @@ -0,0 +1,21 @@ +#!/bin/bash + +name=strawberry +version="@STRAWBERRY_VERSION_SPARKLE@" +deb_dist="@DEB_DIST@" +root=$(cd "${0%/*}/.." && echo $PWD/${0##*/}) +root=`dirname "$root"` +rootnoslash=`echo $root | sed "s/^\///"` + +echo "Creating $name-$version.tar.xz..." + +tar -cJf $name-$version.tar.xz \ + --transform "s,^$rootnoslash,$name-$version," \ + --exclude-vcs \ + --exclude "$root/dist/*.tar" \ + --exclude "$root/dist/*.tar.*" \ + --exclude "$root/CMakeLists.txt.user" \ + "$root" + +echo "Also creating ${name}_${version}~${deb_dist}.orig.tar.xz..." +cp "$name-$version.tar.xz" "${name}_${version}~${deb_dist}.orig.tar.xz" diff --git a/dist/strawberry.desktop b/dist/strawberry.desktop new file mode 100755 index 00000000..b22174a5 --- /dev/null +++ b/dist/strawberry.desktop @@ -0,0 +1,39 @@ +[Desktop Entry] +Version=0.1.1 +Type=Application +Name=Strawberry +GenericName=Strawberry Music Player +Comment=Plays music +Exec=strawberry %U +TryExec=strawberry +Icon=strawberry +Terminal=false +Categories=AudioVideo;Player;Qt;Audio; +StartupNotify=false +MimeType=application/ogg;application/x-ogg;application/x-ogm-audio;audio/aac;audio/mp4;audio/mpeg;audio/mpegurl;audio/ogg;audio/vnd.rn-realaudio;audio/vorbis;audio/x-flac;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-ms-wma;audio/x-musepack;audio/x-oggflac;audio/x-pn-realaudio;audio/x-scpls;audio/x-speex;audio/x-vorbis;audio/x-vorbis+ogg;audio/x-wav;video/x-ms-asf;x-content/audio-player;x-scheme-handler/zune;x-scheme-handler/itpc;x-scheme-handler/itms;x-scheme-handler/feed; +X-Ayatana-Desktop-Shortcuts=Play;Pause;Stop;Previous;Next; + +[Play Shortcut Group] +Name=Play +Exec=strawberry --play +TargetEnvironment=Unity + +[Pause Shortcut Group] +Name=Pause +Exec=strawberry --pause +TargetEnvironment=Unity + +[Stop Shortcut Group] +Name=Stop +Exec=strawberry --stop +TargetEnvironment=Unity + +[Previous Shortcut Group] +Name=Previous +Exec=strawberry --previous +TargetEnvironment=Unity + +[Next Shortcut Group] +Name=Next +Exec=strawberry --next +TargetEnvironment=Unity diff --git a/dist/strawberry.spec b/dist/strawberry.spec new file mode 100644 index 00000000..15f741a0 --- /dev/null +++ b/dist/strawberry.spec @@ -0,0 +1,87 @@ +Name: strawberry +Version: 0.1.1 +Release: 1.fc13 +Summary: A audio player and music collection organiser + +Group: Applications/Multimedia +License: GPLv3 +URL: http://www.strawbs.org/ +Source0: %{name}-0.1.1.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +BuildRequires: desktop-file-utils liblastfm-devel taglib-devel gettext +BuildRequires: qt5-devel boost-devel gcc-c++ glew-devel libgpod-devel +BuildRequires: cmake gstreamer-devel gstreamer-plugins-base-devel +BuildRequires: libimobiledevice-devel libplist-devel usbmuxd-devel +BuildRequires: libmtp-devel protobuf-devel protobuf-compiler libcdio-devel +BuildRequires: qjson-devel qca2-devel fftw-devel sparsehash-devel +BuildRequires: libchromaprint-devel + +Requires: libgpod protobuf-lite libcdio qjson qca-ossl sqlite + +# GStreamer codec dependencies +Requires: gstreamer-plugins-ugly + +%ifarch x86_64 +Requires: gstreamer1.0(decoder-audio/x-vorbis)()(64bit) +Requires: gstreamer1.0(decoder-audio/x-flac)()(64bit) +Requires: gstreamer1.0(decoder-audio/x-speex)()(64bit) +Requires: gstreamer1.0(decoder-audio/x-wav)()(64bit) +%else +Requires: gstreamer1.0(decoder-audio/x-vorbis) +Requires: gstreamer1.0(decoder-audio/x-flac) +Requires: gstreamer1.0(decoder-audio/x-speex) +Requires: gstreamer1.0(decoder-audio/x-wav) +%endif + +%description +Strawberry is a modern audio player and music collection organiser. +It is a fork of Clementine. The name is inspired by the band Strawbs. + +Features include: + + * Organize and play your music collection + * Edit tags on your music + * Download album cover art from Last.fm, musicbrainz, Discogs and Amazon + * Native desktop notifications + * Import and export playlists in multiple formats + * Copy music to your iPod, iPhone, MTP or mass-storage USB player + * Support for multiple backends + +%prep +%setup -q -n %{name}-0.1.1 + + +%build +cd bin +%{cmake} .. -DUSE_INSTALL_PREFIX=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON +make %{?_smp_mflags} + +%install +cd bin +make install DESTDIR=$RPM_BUILD_ROOT +rm -f $RPM_BUILD_ROOT/usr/share/icons/ubuntu-mono-{dark,light}/apps/24/strawberry-panel*.png + +%clean +cd bin +make clean + + +%files +%defattr(-,root,root,-) +%doc +%{_bindir}/strawberry +%{_bindir}/strawberry-tagreader +%{_datadir}/applications/strawberry.desktop +%{_datadir}/strawberry/projectm-presets +%{_datadir}/kde4/services/strawberry-itms.protocol +%{_datadir}/kde4/services/strawberry-itpc.protocol +%{_datadir}/kde4/services/strawberry-feed.protocol +%{_datadir}/kde4/services/strawberry-zune.protocol +%{_datadir}/icons/hicolor/64x64/apps/strawberry.png +%{_datadir}/icons/hicolor/128x128/apps/strawberry.png +%{_datadir}/icons/hicolor/scalable/apps/strawberry.svg + +%changelog +* ma. feb. 26 2018 0.1.1 +- Version 0.1.1 diff --git a/dist/strawberry.spec.in b/dist/strawberry.spec.in new file mode 100644 index 00000000..4519a89a --- /dev/null +++ b/dist/strawberry.spec.in @@ -0,0 +1,87 @@ +Name: strawberry +Version: @STRAWBERRY_VERSION_RPM_V@ +Release: @STRAWBERRY_VERSION_RPM_R@.@RPM_DISTRO@ +Summary: A audio player and music collection organiser + +Group: Applications/Multimedia +License: GPLv3 +URL: http://www.strawbs.org/ +Source0: %{name}-@STRAWBERRY_VERSION_SPARKLE@.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +BuildRequires: desktop-file-utils liblastfm-devel taglib-devel gettext +BuildRequires: qt5-devel boost-devel gcc-c++ glew-devel libgpod-devel +BuildRequires: cmake gstreamer-devel gstreamer-plugins-base-devel +BuildRequires: libimobiledevice-devel libplist-devel usbmuxd-devel +BuildRequires: libmtp-devel protobuf-devel protobuf-compiler libcdio-devel +BuildRequires: qjson-devel qca2-devel fftw-devel sparsehash-devel +BuildRequires: libchromaprint-devel + +Requires: libgpod protobuf-lite libcdio qjson qca-ossl sqlite + +# GStreamer codec dependencies +Requires: gstreamer-plugins-ugly + +%ifarch x86_64 +Requires: gstreamer1.0(decoder-audio/x-vorbis)()(64bit) +Requires: gstreamer1.0(decoder-audio/x-flac)()(64bit) +Requires: gstreamer1.0(decoder-audio/x-speex)()(64bit) +Requires: gstreamer1.0(decoder-audio/x-wav)()(64bit) +%else +Requires: gstreamer1.0(decoder-audio/x-vorbis) +Requires: gstreamer1.0(decoder-audio/x-flac) +Requires: gstreamer1.0(decoder-audio/x-speex) +Requires: gstreamer1.0(decoder-audio/x-wav) +%endif + +%description +Strawberry is a modern audio player and music collection organiser. +It is a fork of Clementine. The name is inspired by the band Strawbs. + +Features include: + + * Organize and play your music collection + * Edit tags on your music + * Download album cover art from Last.fm, musicbrainz, Discogs and Amazon + * Native desktop notifications + * Import and export playlists in multiple formats + * Copy music to your iPod, iPhone, MTP or mass-storage USB player + * Support for multiple backends + +%prep +%setup -q -n %{name}-@STRAWBERRY_VERSION_SPARKLE@ + + +%build +cd bin +%{cmake} .. -DUSE_INSTALL_PREFIX=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON +make %{?_smp_mflags} + +%install +cd bin +make install DESTDIR=$RPM_BUILD_ROOT +rm -f $RPM_BUILD_ROOT/usr/share/icons/ubuntu-mono-{dark,light}/apps/24/strawberry-panel*.png + +%clean +cd bin +make clean + + +%files +%defattr(-,root,root,-) +%doc +%{_bindir}/strawberry +%{_bindir}/strawberry-tagreader +%{_datadir}/applications/strawberry.desktop +%{_datadir}/strawberry/projectm-presets +%{_datadir}/kde4/services/strawberry-itms.protocol +%{_datadir}/kde4/services/strawberry-itpc.protocol +%{_datadir}/kde4/services/strawberry-feed.protocol +%{_datadir}/kde4/services/strawberry-zune.protocol +%{_datadir}/icons/hicolor/64x64/apps/strawberry.png +%{_datadir}/icons/hicolor/128x128/apps/strawberry.png +%{_datadir}/icons/hicolor/scalable/apps/strawberry.svg + +%changelog +* @RPM_DATE@ @STRAWBERRY_VERSION_RPM_V@ +- Version @STRAWBERRY_VERSION_DISPLAY@ diff --git a/ext/libstrawberry-common/CMakeLists.txt b/ext/libstrawberry-common/CMakeLists.txt new file mode 100644 index 00000000..6134c16e --- /dev/null +++ b/ext/libstrawberry-common/CMakeLists.txt @@ -0,0 +1,41 @@ +include_directories(${PROTOBUF_INCLUDE_DIRS}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${CMAKE_SOURCE_DIR}/src) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++0x") + +set(SOURCES + core/closure.cpp + core/logging.cpp + core/messagehandler.cpp + core/messagereply.cpp + core/waitforsignal.cpp + core/workerpool.cpp +) + +set(HEADERS + core/closure.h + core/messagehandler.h + core/messagereply.h + core/workerpool.h +) + +if(APPLE) + list(APPEND SOURCES core/scoped_nsautorelease_pool.mm) +endif(APPLE) + +qt5_wrap_cpp(MOC ${HEADERS}) + +add_library(libstrawberry-common STATIC + ${SOURCES} + ${MOC} +) + +target_link_libraries(libstrawberry-common + #${PROTOBUF_LIBRARY} + ${TAGLIB_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} +) + +QT5_USE_MODULES(libstrawberry-common Core Network) diff --git a/ext/libstrawberry-common/core/arraysize.h b/ext/libstrawberry-common/core/arraysize.h new file mode 100644 index 00000000..ff43e200 --- /dev/null +++ b/ext/libstrawberry-common/core/arraysize.h @@ -0,0 +1,34 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// From Chromium src/base/macros.h + +#include // For size_t. + +// The arraysize(arr) macro returns the # of elements in an array arr. +// The expression is a compile-time constant, and therefore can be +// used in defining new arrays, for example. If you use arraysize on +// a pointer by mistake, you will get a compile-time error. +// +// One caveat is that arraysize() doesn't accept any array of an +// anonymous type or a type defined inside a function. In these rare +// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is +// due to a limitation in C++'s template system. The limitation might +// eventually be removed, but it hasn't happened yet. + +// This template function declaration is used in defining arraysize. +// Note that the function doesn't need an implementation, as we only +// use its type. +template +char (&ArraySizeHelper(T (&array)[N]))[N]; + +// That gcc wants both of these prototypes seems mysterious. VC, for +// its part, can't decide which to use (another mystery). Matching of +// template overloads: the final frontier. +#ifndef _MSC_VER +template +char (&ArraySizeHelper(const T (&array)[N]))[N]; +#endif + +#define arraysize(array) (sizeof(ArraySizeHelper(array))) diff --git a/ext/libstrawberry-common/core/closure.cpp b/ext/libstrawberry-common/core/closure.cpp new file mode 100644 index 00000000..79c56853 --- /dev/null +++ b/ext/libstrawberry-common/core/closure.cpp @@ -0,0 +1,71 @@ +/* This file is part of Strawberry. + Copyright 2011, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#include "closure.h" + +#include "core/timeconstants.h" + +namespace _detail { + +ClosureBase::ClosureBase(ObjectHelper *helper) + : helper_(helper) { +} + +ClosureBase::~ClosureBase() { +} + +CallbackClosure::CallbackClosure(QObject *sender, const char *signal, std::function callback) + : ClosureBase(new ObjectHelper(sender, signal, this)), + callback_(callback) { +} + +void CallbackClosure::Invoke() { + callback_(); +} + +ObjectHelper* ClosureBase::helper() const { + return helper_; +} + +ObjectHelper::ObjectHelper(QObject *sender, const char *signal, ClosureBase *closure) : closure_(closure) { + + connect(sender, signal, SLOT(Invoked())); + connect(sender, SIGNAL(destroyed()), SLOT(deleteLater())); + +} + +void ObjectHelper::Invoked() { + closure_->Invoke(); + deleteLater(); +} + +void Unpack(QList*) {} + +} // namespace _detail + +_detail::ClosureBase* NewClosure(QObject *sender, const char *signal, std::function callback) { + return new _detail::CallbackClosure(sender, signal, callback); +} + +void DoAfter(QObject *receiver, const char *slot, int msec) { + QTimer::singleShot(msec, receiver, slot); +} + +void DoInAMinuteOrSo(QObject *receiver, const char *slot) { + int msec = (60 + (qrand() % 60)) * kMsecPerSec; + DoAfter(receiver, slot, msec); +} diff --git a/ext/libstrawberry-common/core/closure.h b/ext/libstrawberry-common/core/closure.h new file mode 100644 index 00000000..b9f1cef0 --- /dev/null +++ b/ext/libstrawberry-common/core/closure.h @@ -0,0 +1,248 @@ +/* This file is part of Strawberry. + Copyright 2011, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#ifndef CLOSURE_H +#define CLOSURE_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace _detail { + +class ObjectHelper; + +// Interface for ObjectHelper to call on signal emission. +class ClosureBase { + public: + virtual ~ClosureBase(); + virtual void Invoke() = 0; + + // Tests only. + ObjectHelper* helper() const; + + protected: + explicit ClosureBase(ObjectHelper*); + ObjectHelper* helper_; + + private: + Q_DISABLE_COPY(ClosureBase); +}; + +// QObject helper as templated QObjects do not work. +// Connects to the given signal and invokes the closure when called. +// Deletes itself and the Closure after being invoked. +class ObjectHelper : public QObject { + Q_OBJECT + public: + ObjectHelper(QObject* parent, const char* signal, ClosureBase* closure); + + private slots: + void Invoked(); + + private: + std::unique_ptr closure_; + Q_DISABLE_COPY(ObjectHelper); +}; + +// Helpers for unpacking a variadic template list. + +// Base case of no arguments. +void Unpack(QList*); + +template +void Unpack(QList* list, const Arg& arg) { + list->append(Q_ARG(Arg, arg)); +} + +template +void Unpack(QList* list, const Head& head, const Tail&... tail) { + Unpack(list, head); + Unpack(list, tail...); +} + +template +class Closure : public ClosureBase { + public: + Closure( + QObject* sender, + const char* signal, + QObject* receiver, + const char* slot, + const Args&... args) + : ClosureBase(new ObjectHelper(sender, signal, this)), + // std::bind is the easiest way to store an argument list. + function_(std::bind(&Closure::Call, this, args...)), + receiver_(receiver) { + const QMetaObject* meta_receiver = receiver->metaObject(); + QByteArray normalised_slot = QMetaObject::normalizedSignature(slot + 1); + const int index = meta_receiver->indexOfSlot(normalised_slot.constData()); + Q_ASSERT(index != -1); + slot_ = meta_receiver->method(index); + QObject::connect(receiver_, SIGNAL(destroyed()), helper_, SLOT(deleteLater())); + } + + virtual void Invoke() { + function_(); + } + + private: + void Call(const Args&... args) { + QList arg_list; + Unpack(&arg_list, args...); + + slot_.invoke( + receiver_, + arg_list.size() > 0 ? arg_list[0] : QGenericArgument(), + arg_list.size() > 1 ? arg_list[1] : QGenericArgument(), + arg_list.size() > 2 ? arg_list[2] : QGenericArgument(), + arg_list.size() > 3 ? arg_list[3] : QGenericArgument(), + arg_list.size() > 4 ? arg_list[4] : QGenericArgument(), + arg_list.size() > 5 ? arg_list[5] : QGenericArgument(), + arg_list.size() > 6 ? arg_list[6] : QGenericArgument(), + arg_list.size() > 7 ? arg_list[7] : QGenericArgument(), + arg_list.size() > 8 ? arg_list[8] : QGenericArgument(), + arg_list.size() > 9 ? arg_list[9] : QGenericArgument()); + } + + std::function function_; + QObject* receiver_; + QMetaMethod slot_; +}; + +template +class SharedClosure : public Closure { + public: + SharedClosure( + QSharedPointer sender, + const char* signal, + QObject* receiver, + const char* slot, + const Args&... args) + : Closure( + sender.data(), + signal, + receiver, + slot, + args...), + data_(sender) { + } + + private: + QSharedPointer data_; +}; + +class CallbackClosure : public ClosureBase { + public: + CallbackClosure(QObject* sender, const char* signal, + std::function callback); + + virtual void Invoke(); + + private: + std::function callback_; +}; + +} // namespace _detail + +template +_detail::ClosureBase* NewClosure( + QObject* sender, + const char* signal, + QObject* receiver, + const char* slot, + const Args&... args) { + return new _detail::Closure( + sender, signal, receiver, slot, args...); +} + +// QSharedPointer variant +template +_detail::ClosureBase* NewClosure( + QSharedPointer sender, + const char* signal, + QObject* receiver, + const char* slot, + const Args&... args) { + return new _detail::SharedClosure( + sender, signal, receiver, slot, args...); +} + +_detail::ClosureBase* NewClosure(QObject* sender, const char* signal, std::function callback); + +template +_detail::ClosureBase* NewClosure(QObject* sender, const char* signal, std::function callback, const Args&... args) { + return NewClosure(sender, signal, std::bind(callback, args...)); +} + +template +_detail::ClosureBase* NewClosure( + QObject* sender, + const char* signal, + void (*callback)(Args...), + const Args&... args) { + return NewClosure(sender, signal, std::bind(callback, args...)); +} + +template +_detail::ClosureBase* NewClosure( + QObject* sender, + const char* signal, + T* receiver, Unused (T::*callback)(Args...), + const Args&... args) { + return NewClosure(sender, signal, std::bind(callback, receiver, args...)); +} + +template +_detail::ClosureBase* NewClosure(QFuture future, QObject* receiver, const char* slot, const Args&... args) { + QFutureWatcher* watcher = new QFutureWatcher; + watcher->setFuture(future); + QObject::connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); + return NewClosure(watcher, SIGNAL(finished()), receiver, slot, args...); +} + +template +_detail::ClosureBase* NewClosure(QFuture future, const F& callback, const Args&... args) { + QFutureWatcher* watcher = new QFutureWatcher; + watcher->setFuture(future); + QObject::connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); + return NewClosure(watcher, SIGNAL(finished()), callback, args...); +} + +void DoAfter(QObject* receiver, const char* slot, int msec); +void DoAfter(std::function callback, std::chrono::milliseconds msec); +void DoInAMinuteOrSo(QObject* receiver, const char* slot); + +template +void DoAfter(std::function callback, std::chrono::duration duration) { + QTimer* timer = new QTimer; + timer->setSingleShot(true); + NewClosure(timer, SIGNAL(timeout()), callback); + QObject::connect(timer, SIGNAL(timeout()), timer, SLOT(deleteLater())); + std::chrono::milliseconds msec = std::chrono::duration_cast(duration); + timer->start(msec.count()); +} + +#endif // CLOSURE_H + diff --git a/ext/libstrawberry-common/core/concurrentrun.h b/ext/libstrawberry-common/core/concurrentrun.h new file mode 100644 index 00000000..189b3b61 --- /dev/null +++ b/ext/libstrawberry-common/core/concurrentrun.h @@ -0,0 +1,137 @@ +/* This file is part of Strawberry. + Copyright 2012, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#ifndef CONCURRENTRUN_H +#define CONCURRENTRUN_H + +#include + +#include +#include +#include + +/* + The aim of ThreadFunctor classes and ConcurrentRun::Run() functions is to + complete QtConcurrentRun, which lack support for using a particular + QThreadPool, as it always uses QThreadPool::globalInstance(). + + This is problematic when we do not want to share the same thread pool over + all the application, but want to keep the convenient QtConcurrent::run() + functor syntax. + With ConcurrentRun::Run(), time critical changes can be performed in their + own pool, which is not empty by other actions (as it happens when using + QtConcurrentRun::run()). + + ThreadFunctor classes are used to store a functor and its arguments, and + Run() functions are used for convenience: to directly create a new + ThreadFunctor object and start it. +*/ + +/* + Base abstract classes ThreadFunctorBase and ThreadFunctor (for void and + non-void result): +*/ +template +class ThreadFunctorBase : public QFutureInterface, public QRunnable { + public: + ThreadFunctorBase() {} + + QFuture Start(QThreadPool* thread_pool) { + this->setRunnable(this); + this->reportStarted(); + Q_ASSERT(thread_pool); + QFuture future = this->future(); + thread_pool->start(this, 0 /* priority: currently we do not support + changing the priority. Might be added later + if needed */); + return future; + } + + virtual void run() = 0; +}; + +template +class ThreadFunctor : public ThreadFunctorBase { + public: + ThreadFunctor(std::function function, + Args... args) + : function_(std::bind(function, args...)) { + } + + virtual void run() { + this->reportResult(function_()); + this->reportFinished(); + } + + private: + std::function function_; +}; + +// Partial specialisation for void return type. +template +class ThreadFunctor : public ThreadFunctorBase { + public: + ThreadFunctor(std::function function, + Args... args) + : function_(std::bind(function, args...)) { + } + + virtual void run() { + function_(); + this->reportFinished(); + } + + private: + std::function function_; +}; + + +/* + Run functions +*/ +namespace ConcurrentRun { + + // Empty argument form. + template + QFuture Run( + QThreadPool* threadpool, + std::function function) { + return (new ThreadFunctor(function))->Start(threadpool); + } + + // Function object with arguments form. + template + QFuture Run( + QThreadPool* threadpool, + std::function function, + const Args&... args) { + return (new ThreadFunctor( + function, args...))->Start(threadpool); + } + + // Support passing C function pointers instead of function objects. + template + QFuture Run( + QThreadPool* threadpool, + ReturnType (*function) (Args...), + const Args&... args) { + return Run( + threadpool, std::function(function), args...); + } +} + +#endif // CONCURRENTRUN_H diff --git a/ext/libstrawberry-common/core/lazy.h b/ext/libstrawberry-common/core/lazy.h new file mode 100644 index 00000000..228e51e5 --- /dev/null +++ b/ext/libstrawberry-common/core/lazy.h @@ -0,0 +1,66 @@ +/* This file is part of Clementine. + Copyright 2016, John Maguire + + 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 LAZY_H +#define LAZY_H + +#include +#include + +// Helper for lazy initialisation of objects. +// Usage: +// Lazy my_lazy_object([]() { return new Foo; }); + +template +class Lazy { + public: + explicit Lazy(std::function init) : init_(init) {} + + // Convenience constructor that will lazily default construct the object. + Lazy() : init_([]() { return new T; }) {} + + T* get() const { + CheckInitialised(); + return ptr_.get(); + } + + typename std::add_lvalue_reference::type operator*() const { + CheckInitialised(); + return *ptr_; + } + + T* operator->() const { return get(); } + + // Returns true if the object is not yet initialised. + explicit operator bool() const { return ptr_; } + + // Deletes the underlying object and will re-run the initialisation function + // if the object is requested again. + void reset() { ptr_.reset(nullptr); } + + private: + void CheckInitialised() const { + if (!ptr_) { + ptr_.reset(init_()); + } + } + + const std::function init_; + mutable std::unique_ptr ptr_; +}; + +#endif // LAZY_H diff --git a/ext/libstrawberry-common/core/logging.cpp b/ext/libstrawberry-common/core/logging.cpp new file mode 100644 index 00000000..2a0c0ba7 --- /dev/null +++ b/ext/libstrawberry-common/core/logging.cpp @@ -0,0 +1,292 @@ +/* This file is part of Strawberry. + Copyright 2011, David Sansome + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include +#include + +#include + +#include +#ifdef Q_OS_UNIX + #include +#endif + +#include +#include +#include +#include + +#include "logging.h" + +namespace logging { + +static Level sDefaultLevel = Level_Debug; +static QMap* sClassLevels = nullptr; +static QIODevice *sNullDevice = nullptr; + +const char* kDefaultLogLevels = "GstEnginePipeline:2,*:3"; + +static const char *kMessageHandlerMagic = "__logging_message__"; +static const int kMessageHandlerMagicLength = strlen(kMessageHandlerMagic); +static QtMessageHandler sOriginalMessageHandler = nullptr; + +void GLog(const char *domain, int level, const char *message, void *user_data) { + + switch (level) { + case G_LOG_FLAG_RECURSION: + case G_LOG_FLAG_FATAL: + case G_LOG_LEVEL_ERROR: + case G_LOG_LEVEL_CRITICAL: qLog(Error) << message; break; + case G_LOG_LEVEL_WARNING: qLog(Warning) << message; break; + case G_LOG_LEVEL_MESSAGE: + case G_LOG_LEVEL_INFO: qLog(Info) << message; break; + case G_LOG_LEVEL_DEBUG: + default: qLog(Debug) << message; break; + } + +} + +static void MessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message) { + + if (strncmp(kMessageHandlerMagic, message.toLocal8Bit().data(), kMessageHandlerMagicLength) == 0) { + fprintf(stderr, "%s\n", message.toLocal8Bit().data() + kMessageHandlerMagicLength); + return; + } + + Level level = Level_Debug; + switch (type) { + case QtFatalMsg: + case QtCriticalMsg: level = Level_Error; break; + case QtWarningMsg: level = Level_Warning; break; + case QtDebugMsg: + default: level = Level_Debug; break; + } + + for (const QString& line : message.split('\n')) { + CreateLogger(level, "unknown", -1) << line.toLocal8Bit().constData(); + } + + if (type == QtFatalMsg) { + abort(); + } +} + +void Init() { + + delete sClassLevels; + delete sNullDevice; + + sClassLevels = new QMap(); + sNullDevice = new NullDevice; + sNullDevice->open(QIODevice::ReadWrite); + + // Catch other messages from Qt + if (!sOriginalMessageHandler) { + sOriginalMessageHandler = qInstallMessageHandler(MessageHandler); + } +} + +void SetLevels(const QString &levels) { + + if (!sClassLevels) return; + + for (const QString& item : levels.split(',')) { + const QStringList class_level = item.split(':'); + + QString class_name; + bool ok = false; + int level = Level_Error; + + if (class_level.count() == 1) { + level = class_level.last().toInt(&ok); + } + else if (class_level.count() == 2) { + class_name = class_level.first(); + level = class_level.last().toInt(&ok); + } + + if (!ok || level < Level_Error || level > Level_Debug) { + continue; + } + + if (class_name.isEmpty() || class_name == "*") { + sDefaultLevel = (Level) level; + } + else { + sClassLevels->insert(class_name, (Level) level); + } + } + +} + +QString ParsePrettyFunction(const char *pretty_function) { + + // Get the class name out of the function name. + QString class_name = pretty_function; + const int paren = class_name.indexOf('('); + if (paren != -1) { + const int colons = class_name.lastIndexOf("::", paren); + if (colons != -1) { + class_name = class_name.left(colons); + } + else { + class_name = class_name.left(paren); + } + } + + const int space = class_name.lastIndexOf(' '); + if (space != -1) { + class_name = class_name.mid(space+1); + } + + return class_name; +} + +QDebug CreateLogger(Level level, const QString &class_name, int line) { + + // Map the level to a string + const char *level_name = nullptr; + switch (level) { + case Level_Debug: level_name = " DEBUG "; break; + case Level_Info: level_name = " INFO "; break; + case Level_Warning: level_name = " WARN "; break; + case Level_Error: level_name = " ERROR "; break; + case Level_Fatal: level_name = " FATAL "; break; + } + + // Check the settings to see if we're meant to show or hide this message. + Level threshold_level = sDefaultLevel; + if (sClassLevels && sClassLevels->contains(class_name)) { + threshold_level = sClassLevels->value(class_name); + } + + if (level > threshold_level) { + return QDebug(sNullDevice); + } + + QString function_line = class_name; + if (line != -1) { + function_line += ":" + QString::number(line); + } + + QtMsgType type = QtDebugMsg; + if (level == Level_Fatal) { + type = QtFatalMsg; + } + + QDebug ret(type); + ret.nospace() << kMessageHandlerMagic + << QDateTime::currentDateTime() + .toString("hh:mm:ss.zzz") + .toLatin1() + .constData() << level_name + << function_line.leftJustified(32).toLatin1().constData(); + + return ret.space(); +} + +QString CXXDemangle(const QString &mangled_function) { + + int status; + char* demangled_function = abi::__cxa_demangle(mangled_function.toLatin1().constData(), nullptr, nullptr, &status); + if (status == 0) { + QString ret = QString::fromLatin1(demangled_function); + free(demangled_function); + return ret; + } + return mangled_function; // Probably not a C++ function. + +} + +QString DarwinDemangle(const QString &symbol) { + + QStringList split = symbol.split(' ', QString::SkipEmptyParts); + QString mangled_function = split[3]; + return CXXDemangle(mangled_function); + +} + +QString LinuxDemangle(const QString &symbol) { + + QRegExp regex("\\(([^+]+)"); + if (!symbol.contains(regex)) { + return symbol; + } + QString mangled_function = regex.cap(1); + return CXXDemangle(mangled_function); + +} + +QString DemangleSymbol(const QString &symbol) { +#ifdef Q_OS_DARWIN + return DarwinDemangle(symbol); +#elif defined(Q_OS_LINUX) + return LinuxDemangle(symbol); +#else + return symbol; +#endif +} + +void DumpStackTrace() { +#ifdef Q_OS_UNIX + void* callstack[128]; + int callstack_size = backtrace(reinterpret_cast(&callstack), sizeof(callstack)); + char** symbols = backtrace_symbols(reinterpret_cast(&callstack), callstack_size); + // Start from 1 to skip ourself. + for (int i = 1; i < callstack_size; ++i) { + std::cerr << DemangleSymbol(QString::fromLatin1(symbols[i])).toStdString() << std::endl; + } + free(symbols); +#else + qLog(Debug) << "FIXME: Implement printing stack traces on this platform"; +#endif +} + +#if 0 +QDebug CreateLoggerFatal(int line, const char *class_name) { return qCreateLogger(line, class_name, Fatal); } +QDebug CreateLoggerError(int line, const char *class_name) { return qCreateLogger(line, class_name, Error); } + +#ifdef QT_NO_WARNING_OUTPUT +QNoDebug CreateLoggerWarning(int, const char*) { return QNoDebug(); } +#else +QDebug CreateLoggerWarning(int line, const char *class_name) { return qCreateLogger(line, class_name, Warning); } +#endif // QT_NO_WARNING_OUTPUT + +#ifdef QT_NO_DEBUG_OUTPUT +QNoDebug CreateLoggerInfo(int, const char*) { return QNoDebug(); } +QNoDebug CreateLoggerDebug(int, const char*) { return QNoDebug(); } +#else +QDebug CreateLoggerInfo(int line, const char *class_name) { return qCreateLogger(line, class_name, Info); } +QDebug CreateLoggerDebug(int line, const char *class_name) { return qCreateLogger(line, class_name, Debug); } +#endif // QT_NO_DEBUG_OUTPUT +#endif + +} // namespace logging + +namespace { + +template +QString print_duration(T duration, const std::string& unit) { + return QString("%1%2").arg(duration.count()).arg(unit.c_str()); +} + +} // namespace + +QDebug operator<<(QDebug dbg, std::chrono::seconds secs) { + dbg.nospace() << print_duration(secs, "s"); + return dbg.space(); +} + diff --git a/ext/libstrawberry-common/core/logging.h b/ext/libstrawberry-common/core/logging.h new file mode 100644 index 00000000..2827f68c --- /dev/null +++ b/ext/libstrawberry-common/core/logging.h @@ -0,0 +1,93 @@ +/* This file is part of Strawberry. + Copyright 2011, David Sansome + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef LOGGING_H +#define LOGGING_H + +#include +#include + +#include + +#ifdef QT_NO_DEBUG_STREAM +# define qLog(level) while (false) QNoDebug() +#else +#define qLog(level) \ + logging::CreateLogger(logging::Level_##level, \ + logging::ParsePrettyFunction(__PRETTY_FUNCTION__), \ + __LINE__) +#endif // QT_NO_DEBUG_STREAM + +#if 0 +#define qLog(level) \ + logging::CreateLogger##level(__LINE__, __PRETTY_FUNCTION__) + +#define qCreateLogger(line, class_name, level) \ + logging::CreateLogger(logging::Level_##level, \ + logging::ParsePrettyFunction(class_name), \ + line) +#endif + +namespace logging { + class NullDevice : public QIODevice { + protected: + qint64 readData(char*, qint64) { return -1; } + qint64 writeData(const char*, qint64 len) { return len; } +}; + +enum Level { + Level_Fatal = -1, + Level_Error = 0, + Level_Warning, + Level_Info, + Level_Debug, +}; + + void Init(); + void SetLevels(const QString& levels); + + void DumpStackTrace(); + + QString ParsePrettyFunction(const char* pretty_function); + QDebug CreateLogger(Level level, const QString &class_name, int line); + +QDebug CreateLoggerFatal(int line, const char* class_name); +QDebug CreateLoggerError(int line, const char* class_name); + +#ifdef QT_NO_WARNING_OUTPUT +QNoDebug CreateLoggerWarning(int, const char*); +#else +QDebug CreateLoggerWarning(int line, const char* class_name); +#endif // QT_NO_WARNING_OUTPUT + +#ifdef QT_NO_DEBUG_OUTPUT +QNoDebug CreateLoggerInfo(int, const char*); +QNoDebug CreateLoggerDebug(int, const char*); +#else +QDebug CreateLoggerInfo(int line, const char* class_name); +QDebug CreateLoggerDebug(int line, const char* class_name); +#endif // QT_NO_DEBUG_OUTPUT + + +void GLog(const char* domain, int level, const char* message, void* user_data); + +extern const char* kDefaultLogLevels; +} + +QDebug operator<<(QDebug debug, std::chrono::seconds secs); + +#endif // LOGGING_H + diff --git a/ext/libstrawberry-common/core/messagehandler.cpp b/ext/libstrawberry-common/core/messagehandler.cpp new file mode 100644 index 00000000..4b1a4b6f --- /dev/null +++ b/ext/libstrawberry-common/core/messagehandler.cpp @@ -0,0 +1,112 @@ +/* This file is part of Strawberry. + Copyright 2011, David Sansome + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "messagehandler.h" +#include "core/logging.h" + +#include +#include +#include + +_MessageHandlerBase::_MessageHandlerBase(QIODevice *device, QObject *parent) + : QObject(parent), + device_(nullptr), + flush_abstract_socket_(nullptr), + flush_local_socket_(nullptr), + reading_protobuf_(false), + expected_length_(0), + is_device_closed_(false) { + if (device) { + SetDevice(device); + } +} + +void _MessageHandlerBase::SetDevice(QIODevice *device) { + + device_ = device; + + buffer_.open(QIODevice::ReadWrite); + + connect(device, SIGNAL(readyRead()), SLOT(DeviceReadyRead())); + + // Yeah I know. + if (QAbstractSocket *socket = qobject_cast(device)) { + flush_abstract_socket_ = &QAbstractSocket::flush; + connect(socket, SIGNAL(disconnected()), SLOT(DeviceClosed())); + } + else if (QLocalSocket* socket = qobject_cast(device)) { + flush_local_socket_ = &QLocalSocket::flush; + connect(socket, SIGNAL(disconnected()), SLOT(DeviceClosed())); + } + else { + qFatal("Unsupported device type passed to _MessageHandlerBase"); + } + +} + +void _MessageHandlerBase::DeviceReadyRead() { + + while (device_->bytesAvailable()) { + if (!reading_protobuf_) { + // Read the length of the next message + QDataStream s(device_); + s >> expected_length_; + + reading_protobuf_ = true; + } + + // Read some of the message + buffer_.write(device_->read(expected_length_ - buffer_.size())); + + // Did we get everything? + if (buffer_.size() == expected_length_) { + // Parse the message + if (!RawMessageArrived(buffer_.data())) { + qLog(Error) << "Malformed protobuf message"; + device_->close(); + return; + } + + // Clear the buffer + buffer_.close(); + buffer_.setData(QByteArray()); + buffer_.open(QIODevice::ReadWrite); + reading_protobuf_ = false; + } + } + +} + +void _MessageHandlerBase::WriteMessage(const QByteArray &data) { + + QDataStream s(device_); + s << quint32(data.length()); + s.writeRawData(data.data(), data.length()); + + // Sorry. + if (flush_abstract_socket_) { + ((static_cast(device_))->*(flush_abstract_socket_))(); + } + else if (flush_local_socket_) { + ((static_cast(device_))->*(flush_local_socket_))(); + } +} + +void _MessageHandlerBase::DeviceClosed() { + is_device_closed_ = true; + AbortAll(); +} + diff --git a/ext/libstrawberry-common/core/messagehandler.h b/ext/libstrawberry-common/core/messagehandler.h new file mode 100644 index 00000000..65dfde81 --- /dev/null +++ b/ext/libstrawberry-common/core/messagehandler.h @@ -0,0 +1,181 @@ +/* This file is part of Strawberry. + Copyright 2011, David Sansome + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + + +#ifndef MESSAGEHANDLER_H +#define MESSAGEHANDLER_H + +#include +#include +#include +#include +#include +#include +#include + +#include "core/logging.h" +#include "core/messagereply.h" + +class QAbstractSocket; +class QIODevice; +class QLocalSocket; + +#define QStringFromStdString(x) QString::fromUtf8(x.data(), x.size()) +#define DataCommaSizeFromQString(x) x.toUtf8().constData(), x.toUtf8().length() + +// Reads and writes uint32 length encoded protobufs to a socket. +// This base QObject is separate from AbstractMessageHandler because moc can't +// handle templated classes. Use AbstractMessageHandler instead. +class _MessageHandlerBase : public QObject { + Q_OBJECT + +public: + // device can be NULL, in which case you must call SetDevice before writing + // any messages. + _MessageHandlerBase(QIODevice* device, QObject* parent); + + void SetDevice(QIODevice* device); + + // After this is true, messages cannot be sent to the handler any more. + bool is_device_closed() const { return is_device_closed_; } + +protected slots: + void WriteMessage(const QByteArray& data); + void DeviceReadyRead(); + virtual void DeviceClosed(); + +protected: + virtual bool RawMessageArrived(const QByteArray& data) = 0; + virtual void AbortAll() = 0; + +protected: + typedef bool (QAbstractSocket::*FlushAbstractSocket)(); + typedef bool (QLocalSocket::*FlushLocalSocket)(); + + QIODevice* device_; + FlushAbstractSocket flush_abstract_socket_; + FlushLocalSocket flush_local_socket_; + + bool reading_protobuf_; + quint32 expected_length_; + QBuffer buffer_; + + bool is_device_closed_; +}; + +// Reads and writes uint32 length encoded MessageType messages to a socket. +// You should subclass this and implement the MessageArrived(MessageType) +// method. +template +class AbstractMessageHandler : public _MessageHandlerBase { +public: + AbstractMessageHandler(QIODevice* device, QObject* parent); + ~AbstractMessageHandler() { AbortAll(); } + + typedef MT MessageType; + typedef MessageReply ReplyType; + + // Serialises the message and writes it to the socket. This version MUST be + // called from the thread in which the AbstractMessageHandler was created. + void SendMessage(const MessageType& message); + + // Serialises the message and writes it to the socket. This version may be + // called from any thread. + void SendMessageAsync(const MessageType& message); + + // Sends the request message inside and takes ownership of the MessageReply. + // The MessageReply's Finished() signal will be emitted when a reply arrives + // with the same ID. Must be called from my thread. + void SendRequest(ReplyType* reply); + + // Sets the "id" field of reply to the same as the request, and sends the + // reply on the socket. Used on the worker side. + void SendReply(const MessageType& request, MessageType* reply); + +protected: + // Called when a message is received from the socket. + virtual void MessageArrived(const MessageType& message) {} + + // _MessageHandlerBase + bool RawMessageArrived(const QByteArray& data); + void AbortAll(); + +private: + QMap pending_replies_; +}; + +template +AbstractMessageHandler::AbstractMessageHandler(QIODevice* device, + QObject* parent) + : _MessageHandlerBase(device, parent) {} + +template +void AbstractMessageHandler::SendMessage(const MessageType& message) { + Q_ASSERT(QThread::currentThread() == thread()); + + std::string data = message.SerializeAsString(); + WriteMessage(QByteArray(data.data(), data.size())); +} + +template +void AbstractMessageHandler::SendMessageAsync(const MessageType& message) { + std::string data = message.SerializeAsString(); + metaObject()->invokeMethod(this, "WriteMessage", Qt::QueuedConnection, + Q_ARG(QByteArray, QByteArray(data.data(), data.size()))); +} + +template +void AbstractMessageHandler::SendRequest(ReplyType* reply) { + pending_replies_[reply->id()] = reply; + SendMessage(reply->request_message()); +} + +template +void AbstractMessageHandler::SendReply(const MessageType& request, + MessageType* reply) { + reply->set_id(request.id()); + SendMessage(*reply); +} + +template +bool AbstractMessageHandler::RawMessageArrived(const QByteArray& data) { + MessageType message; + if (!message.ParseFromArray(data.constData(), data.size())) { + return false; + } + + ReplyType* reply = pending_replies_.take(message.id()); + + if (reply) { + // This is a reply to a message that we created earlier. + reply->SetReply(message); + } else { + MessageArrived(message); + } + + return true; +} + +template +void AbstractMessageHandler::AbortAll() { + for (ReplyType* reply : pending_replies_) { + reply->Abort(); + } + pending_replies_.clear(); +} + +#endif // MESSAGEHANDLER_H + diff --git a/ext/libstrawberry-common/core/messagereply.cpp b/ext/libstrawberry-common/core/messagereply.cpp new file mode 100644 index 00000000..3c17cf43 --- /dev/null +++ b/ext/libstrawberry-common/core/messagereply.cpp @@ -0,0 +1,38 @@ +/* This file is part of Strawberry. + Copyright 2011, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#include "messagereply.h" + +_MessageReplyBase::_MessageReplyBase(QObject *parent) + : QObject(parent), finished_(false), success_(false) {} + +bool _MessageReplyBase::WaitForFinished() { + qLog(Debug) << "Waiting on ID" << id(); + semaphore_.acquire(); + qLog(Debug) << "Acquired ID" << id(); + return success_; +} + +void _MessageReplyBase::Abort() { + Q_ASSERT(!finished_); + finished_ = true; + success_ = false; + + emit Finished(success_); + qLog(Debug) << "Releasing ID" << id() << "(aborted)"; + semaphore_.release(); +} diff --git a/ext/libstrawberry-common/core/messagereply.h b/ext/libstrawberry-common/core/messagereply.h new file mode 100644 index 00000000..e71b320d --- /dev/null +++ b/ext/libstrawberry-common/core/messagereply.h @@ -0,0 +1,97 @@ +/* This file is part of Strawberry. + Copyright 2011, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#ifndef MESSAGEREPLY_H +#define MESSAGEREPLY_H + +#include +#include + +#include "core/logging.h" + +// Base QObject for a reply future class that is returned immediately for +// requests that will occur in the background. Similar to QNetworkReply. +// Use MessageReply instead. +class _MessageReplyBase : public QObject { + Q_OBJECT + + public: + _MessageReplyBase(QObject* parent = nullptr); + + virtual int id() const = 0; + bool is_finished() const { return finished_; } + bool is_successful() const { return success_; } + + // Waits for the reply to finish by waiting on a semaphore. Never call this + // from the MessageHandler's thread or it will block forever. + // Returns true if the call was successful. + bool WaitForFinished(); + + void Abort(); + +signals: + void Finished(bool success); + +protected: + bool finished_; + bool success_; + + QSemaphore semaphore_; +}; + +// A reply future class that is returned immediately for requests that will +// occur in the background. Similar to QNetworkReply. +template +class MessageReply : public _MessageReplyBase { + public: + MessageReply(const MessageType& request_message, QObject* parent = nullptr); + + int id() const { return request_message_.id(); } + const MessageType& request_message() const { return request_message_; } + const MessageType& message() const { return reply_message_; } + + void SetReply(const MessageType& message); + +private: + MessageType request_message_; + MessageType reply_message_; +}; + + +template +MessageReply::MessageReply(const MessageType& request_message, + QObject* parent) + : _MessageReplyBase(parent) +{ + request_message_.MergeFrom(request_message); +} + +template +void MessageReply::SetReply(const MessageType& message) { + Q_ASSERT(!finished_); + + reply_message_.MergeFrom(message); + finished_ = true; + success_ = true; + + emit Finished(success_); + qLog(Debug) << "Releasing ID" << id() << "(finished)"; + semaphore_.release(); +} + +#endif // MESSAGEREPLY_H + diff --git a/ext/libstrawberry-common/core/override.h b/ext/libstrawberry-common/core/override.h new file mode 100644 index 00000000..9e4bcd10 --- /dev/null +++ b/ext/libstrawberry-common/core/override.h @@ -0,0 +1,33 @@ +/* This file is part of Strawberry. + Copyright 2012, David Sansome + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef OVERRIDE_H +#define OVERRIDE_H + +// Defines the OVERRIDE macro as C++11's override control keyword if +// it is available. + +#ifndef __has_extension + #define __has_extension(x) 0 +#endif + +#if __has_extension(cxx_override_control) // Clang feature checking macro. +# define OVERRIDE override +#else +# define OVERRIDE +#endif + +#endif // OVERRIDE_H diff --git a/ext/libstrawberry-common/core/waitforsignal.cpp b/ext/libstrawberry-common/core/waitforsignal.cpp new file mode 100644 index 00000000..534ffdf9 --- /dev/null +++ b/ext/libstrawberry-common/core/waitforsignal.cpp @@ -0,0 +1,26 @@ +/* This file is part of Strawberry. + Copyright 2011, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#include "waitforsignal.h" + +#include + +void WaitForSignal(QObject *sender, const char *signal) { + QEventLoop loop; + QObject::connect(sender, signal, &loop, SLOT(quit())); + loop.exec(); +} diff --git a/ext/libstrawberry-common/core/waitforsignal.h b/ext/libstrawberry-common/core/waitforsignal.h new file mode 100644 index 00000000..78fa1757 --- /dev/null +++ b/ext/libstrawberry-common/core/waitforsignal.h @@ -0,0 +1,25 @@ +/* This file is part of Strawberry. + Copyright 2011, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#ifndef WAITFORSIGNAL_H +#define WAITFORSIGNAL_H + +class QObject; + +void WaitForSignal(QObject *sender, const char *signal); + +#endif // WAITFORSIGNAL_H diff --git a/ext/libstrawberry-common/core/workerpool.cpp b/ext/libstrawberry-common/core/workerpool.cpp new file mode 100644 index 00000000..1767c009 --- /dev/null +++ b/ext/libstrawberry-common/core/workerpool.cpp @@ -0,0 +1,20 @@ +/* This file is part of Strawberry. + Copyright 2011, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#include "workerpool.h" + +_WorkerPoolBase::_WorkerPoolBase(QObject *parent) : QObject(parent) {} diff --git a/ext/libstrawberry-common/core/workerpool.h b/ext/libstrawberry-common/core/workerpool.h new file mode 100644 index 00000000..9e283171 --- /dev/null +++ b/ext/libstrawberry-common/core/workerpool.h @@ -0,0 +1,402 @@ +/* This file is part of Strawberry. + Copyright 2011, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#ifndef WORKERPOOL_H +#define WORKERPOOL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/closure.h" +#include "core/logging.h" + + +// Base class containing signals and slots - required because moc doesn't do +// templated objects. +class _WorkerPoolBase : public QObject { + Q_OBJECT + + public: + _WorkerPoolBase(QObject* parent = nullptr); + +signals: + // Emitted when a worker failed to start. This usually happens when the + // worker wasn't found, or couldn't be executed. + void WorkerFailedToStart(); + +protected slots: + virtual void DoStart() {} + virtual void NewConnection() {} + virtual void ProcessError(QProcess::ProcessError) {} + virtual void SendQueuedMessages() {} +}; + + +// Manages a pool of one or more external processes. A local socket server is +// started for each process, and the address is passed to the process as +// argv[1]. The process is expected to connect back to the socket server, and +// when it does a HandlerType is created for it. +// Instances of HandlerType are created in the WorkerPool's thread. +template +class WorkerPool : public _WorkerPoolBase { + public: + WorkerPool(QObject* parent = nullptr); + ~WorkerPool(); + + typedef typename HandlerType::MessageType MessageType; + typedef typename HandlerType::ReplyType ReplyType; + + // Sets the name of the worker executable. This is looked for first in the + // current directory, and then in $PATH. You must call this before calling + // Start(). + void SetExecutableName(const QString& executable_name); + + // Sets the number of worker process to use. Defaults to + // 1 <= (processors / 2) <= 2. + void SetWorkerCount(int count); + + // Sets the prefix to use for the local server (on unix this is a named pipe + // in /tmp). Defaults to QApplication::applicationName(). A random number + // is appended to this name when creating each server. + void SetLocalServerName(const QString& local_server_name); + + // Starts all workers. + void Start(); + + // Fills in the message's "id" field and creates a reply future. The message + // is queued and the WorkerPool's thread will send it to the next available + // worker. Can be called from any thread. + ReplyType* SendMessageWithReply(MessageType* message); + +protected: + // These are all reimplemented slots, they are called on the WorkerPool's + // thread. + void DoStart(); + void NewConnection(); + void ProcessError(QProcess::ProcessError error); + void SendQueuedMessages(); + +private: + struct Worker { + Worker() : local_server_(NULL), local_socket_(NULL), process_(NULL), handler_(NULL) {} + + QLocalServer *local_server_; + QLocalSocket *local_socket_; + QProcess *process_; + HandlerType* handler_; + }; + + // Must only ever be called on my thread. + void StartOneWorker(Worker* worker); + + template + Worker* FindWorker(T Worker::*member, T value) { + for (typename QList::iterator it = workers_.begin() ; + it != workers_.end() ; ++it) { + if ((*it).*member == value) { + return &(*it); + } + } + return NULL; + } + + template + void DeleteQObjectPointerLater(T** p) { + if (*p) { + (*p)->deleteLater(); + *p = NULL; + } + } + + // Creates a new reply future for the request with the next sequential ID, + // and sets the request's ID to the ID of the reply. Can be called from any + // thread + ReplyType* NewReply(MessageType* message); + + // Returns the next handler, or NULL if there isn't one. Must be called from + // my thread. + HandlerType* NextHandler() const; + +private: + QString local_server_name_; + QString executable_name_; + QString executable_path_; + + int worker_count_; + mutable int next_worker_; + QList workers_; + + QAtomicInt next_id_; + + QMutex message_queue_mutex_; + QQueue message_queue_; +}; + + +template +WorkerPool::WorkerPool(QObject* parent) + : _WorkerPoolBase(parent), + next_worker_(0), + next_id_(0) +{ + worker_count_ = qBound(1, QThread::idealThreadCount() / 2, 2); + local_server_name_ = qApp->applicationName().toLower(); + + if (local_server_name_.isEmpty()) + local_server_name_ = "workerpool"; +} + +template +WorkerPool::~WorkerPool() { + for (const Worker& worker : workers_) { + if (worker.local_socket_ && worker.process_) { + disconnect(worker.process_, SIGNAL(error(QProcess::ProcessError)), this, SLOT(ProcessError(QProcess::ProcessError))); + + // The worker is connected. Close his socket and wait for him to exit. + qLog(Debug) << "Closing worker socket"; + worker.local_socket_->close(); + worker.process_->waitForFinished(500); + } + + if (worker.process_ && worker.process_->state() == QProcess::Running) { + // The worker is still running - kill it. + qLog(Debug) << "Killing worker process"; + worker.process_->terminate(); + if (!worker.process_->waitForFinished(500)) { + worker.process_->kill(); + } + } + } + + for (ReplyType* reply : message_queue_) { + reply->Abort(); + } +} + +template +void WorkerPool::SetWorkerCount(int count) { + Q_ASSERT(workers_.isEmpty()); + worker_count_ = count; +} + +template +void WorkerPool::SetLocalServerName(const QString& local_server_name) { + Q_ASSERT(workers_.isEmpty()); + local_server_name_ = local_server_name; +} + +template +void WorkerPool::SetExecutableName(const QString& executable_name) { + Q_ASSERT(workers_.isEmpty()); + executable_name_ = executable_name; +} + +template +void WorkerPool::Start() { + metaObject()->invokeMethod(this, "DoStart"); +} + +template +void WorkerPool::DoStart() { + Q_ASSERT(workers_.isEmpty()); + Q_ASSERT(!executable_name_.isEmpty()); + Q_ASSERT(QThread::currentThread() == thread()); + + // Find the executable if we can, default to searching $PATH + executable_path_ = executable_name_; + + QStringList search_path; + search_path << qApp->applicationDirPath(); +#ifdef Q_OS_MAC + search_path << qApp->applicationDirPath() + "/../PlugIns"; +#endif + + for (const QString& path_prefix : search_path) { + const QString executable_path = path_prefix + "/" + executable_name_; + if (QFile::exists(executable_path)) { + executable_path_ = executable_path; + break; + } + } + + // Start all the workers + for (int i = 0; i < worker_count_; ++i) { + Worker worker; + StartOneWorker(&worker); + + workers_ << worker; + } +} + +template +void WorkerPool::StartOneWorker(Worker *worker) { + Q_ASSERT(QThread::currentThread() == thread()); + + DeleteQObjectPointerLater(&worker->local_server_); + DeleteQObjectPointerLater(&worker->local_socket_); + DeleteQObjectPointerLater(&worker->process_); + DeleteQObjectPointerLater(&worker->handler_); + + worker->local_server_ = new QLocalServer(this); + worker->process_ = new QProcess(this); + + connect(worker->local_server_, SIGNAL(newConnection()), SLOT(NewConnection())); + connect(worker->process_, SIGNAL(error(QProcess::ProcessError)), SLOT(ProcessError(QProcess::ProcessError))); + + // Create a server, find an unused name and start listening + forever { + const int unique_number = qrand() ^ ((int)(quint64(this) & 0xFFFFFFFF)); + const QString name = QString("%1_%2").arg(local_server_name_).arg(unique_number); + + if (worker->local_server_->listen(name)) { + break; + } + } + + qLog(Debug) << "Starting worker" << worker << executable_path_ << worker->local_server_->fullServerName(); + + // Start the process + worker->process_->setProcessChannelMode(QProcess::ForwardedChannels); + worker->process_->start(executable_path_, QStringList() << worker->local_server_->fullServerName()); +} + +template +void WorkerPool::NewConnection() { + + Q_ASSERT(QThread::currentThread() == thread()); + + QLocalServer *server = qobject_cast(sender()); + + // Find the worker with this server. + Worker* worker = FindWorker(&Worker::local_server_, server); + if (!worker) return; + + qLog(Debug) << "Worker" << worker << "connected to" << server->fullServerName(); + + // Accept the connection. + worker->local_socket_ = server->nextPendingConnection(); + + // We only ever accept one connection per worker, so destroy the server now. + worker->local_socket_->setParent(this); + worker->local_server_->deleteLater(); + worker->local_server_ = NULL; + + // Create the handler. + worker->handler_ = new HandlerType(worker->local_socket_, this); + + SendQueuedMessages(); +} + +template +void WorkerPool::ProcessError(QProcess::ProcessError error) { + + Q_ASSERT(QThread::currentThread() == thread()); + + QProcess *process = qobject_cast(sender()); + + // Find the worker with this process. + Worker *worker = FindWorker(&Worker::process_, process); + if (!worker) return; + + switch (error) { + case QProcess::FailedToStart: + // Failed to start errors are bad - it usually means the worker isn't + // installed. Don't restart the process, but tell our owner, who will + // probably want to do something fatal. + qLog(Error) << "Worker failed to start"; + emit WorkerFailedToStart(); + break; + + default: + // On any other error we just restart the process. + qLog(Debug) << "Worker" << worker << "failed with error" << error << "- restarting"; + StartOneWorker(worker); + break; + } +} + +template +typename WorkerPool::ReplyType* +WorkerPool::NewReply(MessageType* message) { + const int id = next_id_.fetchAndAddOrdered(1); + message->set_id(id); + + return new ReplyType(*message); +} + +template +typename WorkerPool::ReplyType* +WorkerPool::SendMessageWithReply(MessageType* message) { + ReplyType* reply = NewReply(message); + + // Add the pending reply to the queue + { + QMutexLocker l(&message_queue_mutex_); + message_queue_.enqueue(reply); + } + + // Wake up the main thread + metaObject()->invokeMethod(this, "SendQueuedMessages", Qt::QueuedConnection); + + return reply; +} + +template +void WorkerPool::SendQueuedMessages() { + QMutexLocker l(&message_queue_mutex_); + + while (!message_queue_.isEmpty()) { + ReplyType *reply = message_queue_.dequeue(); + + // Find a worker for this message + HandlerType* handler = NextHandler(); + if (!handler) { + // No available handlers - put the message on the front of the queue. + message_queue_.prepend(reply); + qLog(Debug) << "No available handlers to process request"; + break; + } + + handler->SendRequest(reply); + } +} + +template +HandlerType *WorkerPool::NextHandler() const { + for (int i = 0; i < workers_.count(); ++i) { + const int worker_index = (next_worker_ + i) % workers_.count(); + + if (workers_[worker_index].handler_ && + !workers_[worker_index].handler_->is_device_closed()) { + next_worker_ = (worker_index + 1) % workers_.count(); + return workers_[worker_index].handler_; + } + } + + return NULL; +} + +#endif // WORKERPOOL_H + diff --git a/ext/libstrawberry-tagreader/CMakeLists.txt b/ext/libstrawberry-tagreader/CMakeLists.txt new file mode 100644 index 00000000..8ae27a3a --- /dev/null +++ b/ext/libstrawberry-tagreader/CMakeLists.txt @@ -0,0 +1,35 @@ +include_directories(${PROTOBUF_INCLUDE_DIRS}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_SOURCE_DIR}/ext/libstrawberry-common) +include_directories(${CMAKE_SOURCE_DIR}/src) +include_directories(${CMAKE_BINARY_DIR}/src) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++0x") + +set(MESSAGES + tagreadermessages.proto +) + +set(SOURCES + fmpsparser.cpp + tagreader.cpp +) + +set(HEADERS +) + +qt5_wrap_cpp(MOC ${HEADERS}) + +protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES}) + +add_library(libstrawberry-tagreader STATIC + ${PROTO_SOURCES} + ${SOURCES} + ${MOC} +) + +target_link_libraries(libstrawberry-tagreader + ${PROTOBUF_LIBRARY} + libstrawberry-common +) diff --git a/ext/libstrawberry-tagreader/fmpsparser.cpp b/ext/libstrawberry-tagreader/fmpsparser.cpp new file mode 100644 index 00000000..4061635a --- /dev/null +++ b/ext/libstrawberry-tagreader/fmpsparser.cpp @@ -0,0 +1,127 @@ +/* This file is part of Strawberry. + Copyright 2010, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#include "fmpsparser.h" + +#include + +#include +#include + +using std::placeholders::_1; +using std::placeholders::_2; + +FMPSParser::FMPSParser() : + // The float regex ends with (?:$|(?=::|;;)) to ensure it matches all the way + // up to the end of the value. Without it, it would match a string that + // starts with a number, like "123abc". + float_re_("\\s*([+-]?\\d+(?:\\.\\d+)?)\\s*(?:$|(?=::|;;))"), + + // Matches any character except unescaped slashes, colons and semicolons. + string_re_("((?:[^\\\\;:]|(?:\\\\[\\\\:;]))+)(?:$|(?=::|;;))"), + + // Used for replacing escaped characters. + escape_re_("\\\\([\\\\:;])") {} + +// Parses a list of things (of type T) that are separated by two consecutive +// Separator characters. Each individual thing is parsed by the F function. +// For example, to parse this data: +// foo::bar::baz +// Use: +// QVariantList ret; +// ParseContainer<':'>(data, ParseValue, &ret); +// ret will then contain "foo", "bar", and "baz". +// Returns the number of characters that were consumed from data. +// +// You can parse lists of lists by using different separator characters: +// ParseContainer<';'>(data, ParseContainer<':'>, &ret); +template +static int ParseContainer(const QStringRef& data, F f, QList* ret) { + ret->clear(); + + T value; + int pos = 0; + while (pos < data.length()) { + const int len = data.length() - pos; + int matched_len = f(QStringRef(data.string(), data.position() + pos, len), &value); + if (matched_len == -1 || matched_len > len) + break; + + ret->append(value); + pos += matched_len; + + // Expect two separators in a row + if (pos + 2 <= data.length() && data.at(pos) == Separator && data.at(pos+1) == Separator) { + pos += 2; + } else { + break; + } + } + + return pos; +} + +bool FMPSParser::Parse(const QString &data) { + + result_ = Result(); + + // Only return success if we matched the whole string + return ParseListList(data, &result_) == data.length(); +} + +int FMPSParser::ParseValueRef(const QStringRef& data, QVariant* ret) const { + // Try to match a float + int pos = float_re_.indexIn(*data.string(), data.position()); + if (pos == data.position()) { + *ret = float_re_.cap(1).toDouble(); + return float_re_.matchedLength(); + } + + // Otherwise try to match a string + pos = string_re_.indexIn(*data.string(), data.position()); + if (pos == data.position()) { + // Replace escape sequences with their actual characters + QString value = string_re_.cap(1); + value.replace(escape_re_, "\\1"); + *ret = value; + return string_re_.matchedLength(); + } + + return -1; +} + +// Parses an inner list - a list of values +int FMPSParser::ParseListRef(const QStringRef &data, QVariantList *ret) const { + return ParseContainer<':'>(data, std::bind(&FMPSParser::ParseValueRef, this, _1, _2), ret); +} + +// Parses an outer list - a list of lists +int FMPSParser::ParseListListRef(const QStringRef &data, Result *ret) const { + return ParseContainer<';'>(data, std::bind(&FMPSParser::ParseListRef, this, _1, _2), ret); +} + +// Convenience functions that take QStrings instead of QStringRefs. Use the +// QStringRef versions if possible, they're faster. +int FMPSParser::ParseValue(const QString &data, QVariant *ret) const { + return ParseValueRef(QStringRef(&data), ret); +} +int FMPSParser::ParseList(const QString &data, QVariantList *ret) const { + return ParseListRef(QStringRef(&data), ret); +} +int FMPSParser::ParseListList(const QString &data, Result *ret) const { + return ParseListListRef(QStringRef(&data), ret); +} diff --git a/ext/libstrawberry-tagreader/fmpsparser.h b/ext/libstrawberry-tagreader/fmpsparser.h new file mode 100644 index 00000000..9b00fed8 --- /dev/null +++ b/ext/libstrawberry-tagreader/fmpsparser.h @@ -0,0 +1,58 @@ +/* This file is part of Strawberry. + Copyright 2010, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#ifndef FMPSPARSER_H +#define FMPSPARSER_H + +#include +#include + +class FMPSParser { +public: + FMPSParser(); + + // A FMPS result is a list of lists of values (where a value is a string or + // a float). + typedef QList Result; + + // Parses a FMPS value and returns true on success. + bool Parse(const QString& data); + + // Gets the result of the last successful Parse. + Result result() const { return result_; } + + // Returns true if result() is empty. + bool is_empty() const { return result().isEmpty() || result()[0].isEmpty(); } + + // Internal functions, public for unit tests + int ParseValue(const QString &data, QVariant *ret) const; + int ParseValueRef(const QStringRef &data, QVariant *ret) const; + + int ParseList(const QString &data, QVariantList *ret) const; + int ParseListRef(const QStringRef &data, QVariantList *ret) const; + + int ParseListList(const QString &data, Result *ret) const; + int ParseListListRef(const QStringRef &data, Result *ret) const; + +private: + QRegExp float_re_; + QRegExp string_re_; + QRegExp escape_re_; + Result result_; +}; + +#endif // FMPSPARSER_H diff --git a/ext/libstrawberry-tagreader/tagreader.cpp b/ext/libstrawberry-tagreader/tagreader.cpp new file mode 100644 index 00000000..7d344ebb --- /dev/null +++ b/ext/libstrawberry-tagreader/tagreader.cpp @@ -0,0 +1,705 @@ +/* This file is part of Strawberry. + Copyright 2013, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#include "tagreader.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef TAGLIB_HAS_OPUS + #include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "fmpsparser.h" +#include "core/logging.h" +#include "core/messagehandler.h" +#include "core/timeconstants.h" + +// Taglib added support for FLAC pictures in 1.7.0 +#if (TAGLIB_MAJOR_VERSION > 1) || (TAGLIB_MAJOR_VERSION == 1 && TAGLIB_MINOR_VERSION >= 7) +# define TAGLIB_HAS_FLAC_PICTURELIST +#endif + +#define NumberToASFAttribute(x) TagLib::ASF::Attribute(QStringToTaglibString(QString::number(x))) + +class FileRefFactory { + public: + virtual ~FileRefFactory() {} + virtual TagLib::FileRef *GetFileRef(const QString& filename) = 0; +}; + +class TagLibFileRefFactory : public FileRefFactory { + public: + virtual TagLib::FileRef *GetFileRef(const QString& filename) { + #ifdef Q_OS_WIN32 + return new TagLib::FileRef(filename.toStdWString().c_str()); + #else + return new TagLib::FileRef(QFile::encodeName(filename).constData()); + #endif + } +}; + +namespace { + +TagLib::String StdStringToTaglibString(const std::string& s) { + return TagLib::String(s.c_str(), TagLib::String::UTF8); +} + +TagLib::String QStringToTaglibString(const QString& s) { + return TagLib::String(s.toUtf8().constData(), TagLib::String::UTF8); +} + +} + +namespace { +// Tags containing the year the album was originally released (in contrast to +// other tags that contain the release year of the current edition) +const char *kMP4_OriginalYear_ID = "----:com.apple.iTunes:ORIGINAL YEAR"; +const char *kASF_OriginalDate_ID = "WM/OriginalReleaseTime"; +const char *kASF_OriginalYear_ID = "WM/OriginalReleaseYear"; +} + + +TagReader::TagReader() + : factory_(new TagLibFileRefFactory), + network_(new QNetworkAccessManager), + kEmbeddedCover("(embedded)") {} + +void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *song) const { + + const QByteArray url(QUrl::fromLocalFile(filename).toEncoded()); + const QFileInfo info(filename); + + qLog(Debug) << "Reading tags from" << filename; + + song->set_basefilename(DataCommaSizeFromQString(info.fileName())); + song->set_url(url.constData(), url.size()); + song->set_filesize(info.size()); + song->set_mtime(info.lastModified().toTime_t()); + song->set_ctime(info.created().toTime_t()); + + std::unique_ptr fileref(factory_->GetFileRef(filename)); + if (fileref->isNull()) { + qLog(Info) << "TagLib hasn't been able to read " << filename << " file"; + return; + } + + if (fileref->audioProperties()) { + song->set_bitrate(fileref->audioProperties()->bitrate()); + song->set_samplerate(fileref->audioProperties()->sampleRate()); + song->set_length_nanosec(fileref->audioProperties()->length() * kNsecPerSec); + } + + // Get the filetype if we can + song->set_filetype(GuessFileType(fileref.get())); + + TagLib::Tag *tag = fileref->tag(); + if (tag) { + Decode(tag->title(), nullptr, song->mutable_title()); + Decode(tag->artist(), nullptr, song->mutable_artist()); // TPE1 + Decode(tag->album(), nullptr, song->mutable_album()); + Decode(tag->genre(), nullptr, song->mutable_genre()); + song->set_year(tag->year()); + song->set_track(tag->track()); + song->set_valid(true); + } + + QString disc; + QString compilation; + + // Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same way; + // apart, so we keep specific behavior for some formats by adding another + // "else if" block below. + if (TagLib::Ogg::XiphComment *tag = dynamic_cast(fileref->file()->tag())) { + + ParseOggTag(tag->fieldListMap(), nullptr, &disc, &compilation, song); +#if TAGLIB_MAJOR_VERSION >= 1 && TAGLIB_MINOR_VERSION >= 11 + if (!tag->pictureList().isEmpty()) song->set_art_automatic(kEmbeddedCover); +#endif + } + + if (TagLib::MPEG::File *file = dynamic_cast(fileref->file())) { + + if (file->ID3v2Tag()) { + const TagLib::ID3v2::FrameListMap &map = file->ID3v2Tag()->frameListMap(); + + if (!map["TPOS"].isEmpty()) disc = TStringToQString(map["TPOS"].front()->toString()).trimmed(); + //if (!map["TBPM"].isEmpty()) song->set_bpm(TStringToQString(map["TBPM"].front()->toString()).trimmed().toFloat()); + if (!map["TCOM"].isEmpty()) Decode(map["TCOM"].front()->toString(), nullptr, song->mutable_composer()); + + // content group + if (!map["TIT1"].isEmpty()) Decode(map["TIT1"].front()->toString(), nullptr, song->mutable_grouping()); + + // ID3v2: lead performer/soloist + if (!map["TPE1"].isEmpty()) Decode(map["TPE1"].front()->toString(), nullptr, song->mutable_performer()); + + // original artist/performer + if (!map["TOPE"].isEmpty()) Decode(map["TOPE"].front()->toString(), nullptr, song->mutable_performer()); + + // Skip TPE1 (which is the artist) here because we already fetched it + + + // non-standard: Apple, Microsoft + if (!map["TPE2"].isEmpty()) Decode(map["TPE2"].front()->toString(), nullptr, song->mutable_albumartist()); + + if (!map["TCMP"].isEmpty()) compilation = TStringToQString(map["TCMP"].front()->toString()).trimmed(); + + if (!map["TDOR"].isEmpty()) { song->set_originalyear(map["TDOR"].front()->toString().substr(0, 4).toInt()); } + else if (!map["TORY"].isEmpty()) { + song->set_originalyear(map["TORY"].front()->toString().substr(0, 4).toInt()); + } + + + if (!map["APIC"].isEmpty()) song->set_art_automatic(kEmbeddedCover); + + // Find a suitable comment tag. For now we ignore iTunNORM comments. + for (int i = 0; i < map["COMM"].size(); ++i) { + const TagLib::ID3v2::CommentsFrame *frame = dynamic_cast(map["COMM"][i]); + + if (frame && TStringToQString(frame->description()) != "iTunNORM") { + Decode(frame->text(), nullptr, song->mutable_comment()); + break; + } + } + + // Parse FMPS frames + for (int i = 0; i < map["TXXX"].size(); ++i) { + const TagLib::ID3v2::UserTextIdentificationFrame *frame = dynamic_cast(map["TXXX"][i]); + + if (frame && frame->description().startsWith("FMPS_")) { + ParseFMPSFrame(TStringToQString(frame->description()), TStringToQString(frame->fieldList()[1]), song); + } + } + + } + } + else if (TagLib::FLAC::File *file = dynamic_cast(fileref->file())) { + + song->set_bitdepth(file->audioProperties()->bitsPerSample()); + + if ( file->xiphComment() ) { + ParseOggTag(file->xiphComment()->fieldListMap(), nullptr, &disc, &compilation, song); +#ifdef TAGLIB_HAS_FLAC_PICTURELIST + if (!file->pictureList().isEmpty()) { + song->set_art_automatic(kEmbeddedCover); + } +#endif + } + Decode(tag->comment(), nullptr, song->mutable_comment()); + } + else if (TagLib::MP4::File *file = dynamic_cast(fileref->file())) { + + song->set_bitdepth(file->audioProperties()->bitsPerSample()); + + if (file->tag()) { + TagLib::MP4::Tag *mp4_tag = file->tag(); + const TagLib::MP4::ItemListMap& items = mp4_tag->itemListMap(); + + // Find album artists + TagLib::MP4::ItemListMap::ConstIterator it = items.find("aART"); + if (it != items.end()) { + TagLib::StringList album_artists = it->second.toStringList(); + if (!album_artists.isEmpty()) { + Decode(album_artists.front(), nullptr, song->mutable_albumartist()); + } + } + + // Find album cover art + if (items.find("covr") != items.end()) { + song->set_art_automatic(kEmbeddedCover); + } + + if (items.contains("disk")) { + disc = TStringToQString(TagLib::String::number(items["disk"].toIntPair().first)); + } + + + if(items.contains("\251wrt")) { + Decode(items["\251wrt"].toStringList().toString(", "), nullptr, song->mutable_composer()); + } + if(items.contains("\251grp")) { + Decode(items["\251grp"].toStringList().toString(" "), nullptr, song->mutable_grouping()); + } + + if (items.contains(kMP4_OriginalYear_ID)) { + song->set_originalyear( + TStringToQString( + items[kMP4_OriginalYear_ID].toStringList().toString('\n')) + .left(4) + .toInt()); + } + + Decode(mp4_tag->comment(), nullptr, song->mutable_comment()); + } + } +#ifdef TAGLIB_WITH_ASF + + else if (TagLib::ASF::File *file = dynamic_cast(fileref->file())) { + + song->set_bitdepth(file->audioProperties()->bitsPerSample()); + + const TagLib::ASF::AttributeListMap &attributes_map = file->tag()->attributeListMap(); + + if (attributes_map.contains(kASF_OriginalDate_ID)) { + const TagLib::ASF::AttributeList &attributes = + attributes_map[kASF_OriginalDate_ID]; + if (!attributes.isEmpty()) { + song->set_originalyear( + TStringToQString(attributes.front().toString()).left(4).toInt()); + } + } + else if (attributes_map.contains(kASF_OriginalYear_ID)) { + const TagLib::ASF::AttributeList &attributes = + attributes_map[kASF_OriginalYear_ID]; + if (!attributes.isEmpty()) { + song->set_originalyear( + TStringToQString(attributes.front().toString()).left(4).toInt()); + } + } + } +#endif + else if (tag) { + Decode(tag->comment(), nullptr, song->mutable_comment()); + } + + if (!disc.isEmpty()) { + const int i = disc.indexOf('/'); + if (i != -1) { + // disc.right( i ).toInt() is total number of discs, we don't use this at the moment + song->set_disc(disc.left(i).toInt()); + } + else { + song->set_disc(disc.toInt()); + } + } + + if (compilation.isEmpty()) { + // well, it wasn't set, but if the artist is VA assume it's a compilation + if (QStringFromStdString(song->artist()).toLower() == "various artists") { + song->set_compilation(true); + } + } + else { + song->set_compilation(compilation.toInt() == 1); + } + + + + // Set integer fields to -1 if they're not valid + #define SetDefault(field) if (song->field() <= 0) { song->set_##field(-1); } + SetDefault(track); + SetDefault(disc); + SetDefault(year); + SetDefault(bitrate); + SetDefault(samplerate); + SetDefault(bitdepth); + SetDefault(lastplayed); + #undef SetDefault +} + + +void TagReader::Decode(const TagLib::String &tag, const QTextCodec *codec, std::string *output) { + + QString tmp; + + if (codec && tag.isLatin1()) { // Never override UTF-8. + const std::string fixed = QString::fromUtf8(tag.toCString(true)).toStdString(); + tmp = codec->toUnicode(fixed.c_str()).trimmed(); + } + else { + tmp = TStringToQString(tag).trimmed(); + } + + output->assign(DataCommaSizeFromQString(tmp)); +} + +void TagReader::Decode(const QString &tag, const QTextCodec *codec, std::string *output) { + + if (!codec) { + output->assign(DataCommaSizeFromQString(tag)); + } + else { + const QString decoded(codec->toUnicode(tag.toUtf8())); + output->assign(DataCommaSizeFromQString(decoded)); + } +} + +void TagReader::ParseFMPSFrame(const QString &name, const QString &value, pb::tagreader::SongMetadata *song) const { + + qLog(Debug) << "Parsing FMPSFrame" << name << ", " << value; + FMPSParser parser; + + if (!parser.Parse(value) || parser.is_empty()) return; + + QVariant var; + + if (name == "FMPS_PlayCount") { + var = parser.result()[0][0]; + if (var.type() == QVariant::Double) { + song->set_playcount(var.toDouble()); + } + } + else if (name == "FMPS_PlayCount_User") { + // Take a user playcount only if there's no playcount already set + if (song->playcount() == 0 && parser.result()[0].count() >= 2) { + var = parser.result()[0][1]; + if (var.type() == QVariant::Double) { + song->set_playcount(var.toDouble()); + } + } + } + +} + +void TagReader::ParseOggTag(const TagLib::Ogg::FieldListMap &map, const QTextCodec *codec, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const { + + if (!map["COMPOSER"].isEmpty()) Decode(map["COMPOSER"].front(), codec, song->mutable_composer()); + if (!map["PERFORMER"].isEmpty()) Decode(map["PERFORMER"].front(), codec, song->mutable_performer()); + if (!map["CONTENT GROUP"].isEmpty()) Decode(map["CONTENT GROUP"].front(), codec, song->mutable_grouping()); + + if (!map["ALBUMARTIST"].isEmpty()) { + Decode(map["ALBUMARTIST"].front(), codec, song->mutable_albumartist()); + } + else if (!map["ALBUM ARTIST"].isEmpty()) { + Decode(map["ALBUM ARTIST"].front(), codec, song->mutable_albumartist()); + } + + if (!map["ORIGINALDATE"].isEmpty()) + song->set_originalyear(TStringToQString(map["ORIGINALDATE"].front()).left(4).toInt()); + else if (!map["ORIGINALYEAR"].isEmpty()) + song->set_originalyear(TStringToQString(map["ORIGINALYEAR"].front()).toInt()); + + if (!map["DISCNUMBER"].isEmpty()) *disc = TStringToQString( map["DISCNUMBER"].front() ).trimmed(); + if (!map["COMPILATION"].isEmpty()) *compilation = TStringToQString( map["COMPILATION"].front() ).trimmed(); + if (!map["COVERART"].isEmpty()) song->set_art_automatic(kEmbeddedCover); + if (!map["METADATA_BLOCK_PICTURE"].isEmpty()) song->set_art_automatic(kEmbeddedCover); + + if (!map["FMPS_PLAYCOUNT"].isEmpty() && song->playcount() <= 0) song->set_playcount(TStringToQString( map["FMPS_PLAYCOUNT"].front() ).trimmed().toFloat()); + +} + +void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const pb::tagreader::SongMetadata &song) const { + + vorbis_comments->addField("COMPOSER", StdStringToTaglibString(song.composer()), true); + vorbis_comments->addField("PERFORMER", StdStringToTaglibString(song.performer()), true); + vorbis_comments->addField("CONTENT GROUP", StdStringToTaglibString(song.grouping()), true); + vorbis_comments->addField("DISCNUMBER", QStringToTaglibString(song.disc() <= 0 -1 ? QString() : QString::number(song.disc())), true); + vorbis_comments->addField("COMPILATION", StdStringToTaglibString(song.compilation() ? "1" : "0"), true); + + // Try to be coherent, the two forms are used but the first one is preferred + + vorbis_comments->addField("ALBUMARTIST", StdStringToTaglibString(song.albumartist()), true); + vorbis_comments->removeField("ALBUM ARTIST"); + + +} + +pb::tagreader::SongMetadata_Type TagReader::GuessFileType(TagLib::FileRef *fileref) const { + +#ifdef TAGLIB_WITH_ASF + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_ASF; +#endif + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_FLAC; +#ifdef TAGLIB_WITH_MP4 + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_MP4; +#endif + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_MPC; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_MPEG; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_OGGFLAC; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_OGGSPEEX; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_OGGVORBIS; +#ifdef TAGLIB_HAS_OPUS + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_OGGOPUS; +#endif + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_AIFF; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_WAV; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_TRUEAUDIO; + + return pb::tagreader::SongMetadata_Type_UNKNOWN; + +} + +bool TagReader::SaveFile(const QString &filename, const pb::tagreader::SongMetadata &song) const { + + if (filename.isNull()) return false; + + qLog(Debug) << "Saving tags to" << filename; + + std::unique_ptr fileref(factory_->GetFileRef(filename)); + + if (!fileref || fileref->isNull()) // The file probably doesn't exist + return false; + + fileref->tag()->setTitle(StdStringToTaglibString(song.title())); + fileref->tag()->setArtist(StdStringToTaglibString(song.artist())); + fileref->tag()->setAlbum(StdStringToTaglibString(song.album())); + fileref->tag()->setGenre(StdStringToTaglibString(song.genre())); + fileref->tag()->setComment(StdStringToTaglibString(song.comment())); + fileref->tag()->setYear(song.year()); + fileref->tag()->setTrack(song.track()); + + if (TagLib::MPEG::File *file = dynamic_cast(fileref->file())) { + TagLib::ID3v2::Tag *tag = file->ID3v2Tag(true); + SetTextFrame("TPOS", song.disc() <= 0 -1 ? QString() : QString::number(song.disc()), tag); + SetTextFrame("TCOM", song.composer(), tag); + SetTextFrame("TIT1", song.grouping(), tag); + SetTextFrame("TOPE", song.performer(), tag); + // Skip TPE1 (which is the artist) here because we already set it + SetTextFrame("TPE2", song.albumartist(), tag); + SetTextFrame("TCMP", std::string(song.compilation() ? "1" : "0"), tag); + } + else if (TagLib::FLAC::File *file = dynamic_cast(fileref->file())) { + TagLib::Ogg::XiphComment *tag = file->xiphComment(); + SetVorbisComments(tag, song); + } + else if (TagLib::MP4::File *file = dynamic_cast(fileref->file())) { + TagLib::MP4::Tag *tag = file->tag(); + tag->itemListMap()["disk"] = TagLib::MP4::Item(song.disc() <= 0 -1 ? 0 : song.disc(), 0); + tag->itemListMap()["\251wrt"] = TagLib::StringList(song.composer().c_str()); + tag->itemListMap()["\251grp"] = TagLib::StringList(song.grouping().c_str()); + tag->itemListMap()["aART"] = TagLib::StringList(song.albumartist().c_str()); + tag->itemListMap()["cpil"] = TagLib::StringList(song.compilation() ? "1" : "0"); + } + + // Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same way; + // apart, so we keep specific behavior for some formats by adding another + // "else if" block above. + if (TagLib::Ogg::XiphComment *tag = dynamic_cast(fileref->file()->tag())) { + SetVorbisComments(tag, song); + } + + bool ret = fileref->save(); + #ifdef Q_OS_LINUX + if (ret) { + // Linux: inotify doesn't seem to notice the change to the file unless we + // change the timestamps as well. (this is what touch does) + utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0); + } + #endif // Q_OS_LINUX + + return ret; +} + +void TagReader::SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const { + + const QByteArray descr_utf8(description.toUtf8()); + const QByteArray value_utf8(value.toUtf8()); + qLog(Debug) << "Setting FMPSFrame:" << description << ", " << value; + SetUserTextFrame(std::string(descr_utf8.constData(), descr_utf8.length()), std::string(value_utf8.constData(), value_utf8.length()), tag); + +} + +void TagReader::SetUserTextFrame(const std::string &description, const std::string &value, TagLib::ID3v2::Tag *tag) const { + + const TagLib::String t_description = StdStringToTaglibString(description); + // Remove the frame if it already exists + TagLib::ID3v2::UserTextIdentificationFrame *frame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, t_description); + if (frame) { + tag->removeFrame(frame); + } + + // Create and add a new frame + frame = new TagLib::ID3v2::UserTextIdentificationFrame(TagLib::String::UTF8); + + frame->setDescription(t_description); + frame->setText(StdStringToTaglibString(value)); + tag->addFrame(frame); +} + +void TagReader::SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const { + + const QByteArray utf8(value.toUtf8()); + SetTextFrame(id, std::string(utf8.constData(), utf8.length()), tag); +} + +void TagReader::SetTextFrame(const char *id, const std::string &value, TagLib::ID3v2::Tag *tag) const { + + TagLib::ByteVector id_vector(id); + QVector frames_buffer; + + // Store and clear existing frames + while (tag->frameListMap().contains(id_vector) && tag->frameListMap()[id_vector].size() != 0) { + frames_buffer.push_back(tag->frameListMap()[id_vector].front()->render()); + tag->removeFrame(tag->frameListMap()[id_vector].front()); + } + + // If no frames stored create empty frame + if (frames_buffer.isEmpty()) { + TagLib::ID3v2::TextIdentificationFrame frame(id_vector, TagLib::String::UTF8); + frames_buffer.push_back(frame.render()); + } + + // add frame takes ownership and clears the memory + TagLib::ID3v2::TextIdentificationFrame *frame; + frame->setText(StdStringToTaglibString(value)); + tag->addFrame(frame); + +} + +bool TagReader::IsMediaFile(const QString &filename) const { + + qLog(Debug) << "Checking for valid file" << filename; + + std::unique_ptr fileref(factory_->GetFileRef(filename)); + return !fileref->isNull() && fileref->tag(); + +} + +QByteArray TagReader::LoadEmbeddedArt(const QString &filename) const { + + if (filename.isEmpty()) return QByteArray(); + + qLog(Debug) << "Loading art from" << filename; + +#ifdef Q_OS_WIN32 + TagLib::FileRef ref(filename.toStdWString().c_str()); +#else + TagLib::FileRef ref(QFile::encodeName(filename).constData()); +#endif + + if (ref.isNull() || !ref.file()) return QByteArray(); + + // MP3 + TagLib::MPEG::File *file = dynamic_cast(ref.file()); + if (file && file->ID3v2Tag()) { + TagLib::ID3v2::FrameList apic_frames = file->ID3v2Tag()->frameListMap()["APIC"]; + if (apic_frames.isEmpty()) + return QByteArray(); + + TagLib::ID3v2::AttachedPictureFrame *pic = static_cast(apic_frames.front()); + + return QByteArray((const char*) pic->picture().data(), pic->picture().size()); + } + + // Ogg vorbis/speex + TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast(ref.file()->tag()); + + if (xiph_comment) { + + TagLib::Ogg::FieldListMap map = xiph_comment->fieldListMap(); + +#if TAGLIB_MAJOR_VERSION <= 1 && TAGLIB_MINOR_VERSION < 11 + // Other than the below mentioned non-standard COVERART, + // METADATA_BLOCK_PICTURE + // is the proposed tag for cover pictures. + // (see http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE) + if (map.contains("METADATA_BLOCK_PICTURE")) { + TagLib::StringList pict_list = map["METADATA_BLOCK_PICTURE"]; + for (std::list::iterator it = pict_list.begin(); it != pict_list.end(); ++it) { + QByteArray data(QByteArray::fromBase64(it->toCString())); + TagLib::ByteVector tdata(data.data(), data.size()); + TagLib::FLAC::Picture p(tdata); + if (p.type() == TagLib::FLAC::Picture::FrontCover) + return QByteArray(p.data().data(), p.data().size()); + } + // If there was no specific front cover, just take the first picture + QByteArray data(QByteArray::fromBase64(map["METADATA_BLOCK_PICTURE"].front().toCString())); + TagLib::ByteVector tdata(data.data(), data.size()); + TagLib::FLAC::Picture p(tdata); + return QByteArray(p.data().data(), p.data().size()); + } +#else + TagLib::List pics = xiph_comment->pictureList(); + if (!pics.isEmpty()) { + for (auto p : pics) { + if (p->type() == TagLib::FLAC::Picture::FrontCover) + return QByteArray(p->data().data(), p->data().size()); + } + // If there was no specific front cover, just take the first picture + std::list::iterator it = pics.begin(); + TagLib::FLAC::Picture *picture = *it; + + return QByteArray(picture->data().data(), picture->data().size()); + } +#endif + + // Ogg lacks a definitive standard for embedding cover art, but it seems + // b64 encoding a field called COVERART is the general convention + if (!map.contains("COVERART")) return QByteArray(); + + return QByteArray::fromBase64(map["COVERART"].toString().toCString()); + } + +#ifdef TAGLIB_HAS_FLAC_PICTURELIST + // Flac + TagLib::FLAC::File *flac_file = dynamic_cast(ref.file()); + if (flac_file && flac_file->xiphComment()) { + TagLib::List pics = flac_file->pictureList(); + if (!pics.isEmpty()) { + // Use the first picture in the file - this could be made cleverer and + // pick the front cover if it's present. + + std::list::iterator it = pics.begin(); + TagLib::FLAC::Picture *picture = *it; + + return QByteArray(picture->data().data(), picture->data().size()); + } + } +#endif + + // MP4/AAC + TagLib::MP4::File *aac_file = dynamic_cast(ref.file()); + if (aac_file) { + TagLib::MP4::Tag *tag = aac_file->tag(); + const TagLib::MP4::ItemListMap& items = tag->itemListMap(); + TagLib::MP4::ItemListMap::ConstIterator it = items.find("covr"); + if (it != items.end()) { + const TagLib::MP4::CoverArtList& art_list = it->second.toCoverArtList(); + + if (!art_list.isEmpty()) { + // Just take the first one for now + const TagLib::MP4::CoverArt &art = art_list.front(); + return QByteArray(art.data().data(), art.data().size()); + } + } + } + + return QByteArray(); + +} diff --git a/ext/libstrawberry-tagreader/tagreader.h b/ext/libstrawberry-tagreader/tagreader.h new file mode 100644 index 00000000..64857931 --- /dev/null +++ b/ext/libstrawberry-tagreader/tagreader.h @@ -0,0 +1,83 @@ +/* This file is part of Strawberry. + Copyright 2013, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#ifndef TAGREADER_H +#define TAGREADER_H + +#include + +#include + +#include "config.h" +#include "tagreadermessages.pb.h" + +class QNetworkAccessManager; +class QString; +class QTextCodec; +class QUrl; + +namespace TagLib { + class FileRef; + class String; + + namespace ID3v2 { + class Tag; + class PopularimeterFrame; + } +} + +class FileRefFactory; + +/** + * This class holds all useful methods to read and write tags from/to files. + * You should not use it directly in the main process but rather use a + * TagReaderWorker process (using TagReaderClient) + */ +class TagReader { + public: + TagReader(); + + void ReadFile(const QString& filename, pb::tagreader::SongMetadata *song) const; + bool SaveFile(const QString& filename, const pb::tagreader::SongMetadata &song) const; + + bool IsMediaFile(const QString &filename) const; + QByteArray LoadEmbeddedArt(const QString &filename) const; + + static void Decode(const TagLib::String& tag, const QTextCodec *codec, std::string *output); + static void Decode(const QString &tag, const QTextCodec *codec, std::string *output); + + void ParseFMPSFrame(const QString &name, const QString &value, pb::tagreader::SongMetadata *song) const; + void ParseOggTag(const TagLib::Ogg::FieldListMap &map, const QTextCodec *codec, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const; + void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const pb::tagreader::SongMetadata &song) const; + + pb::tagreader::SongMetadata_Type GuessFileType(TagLib::FileRef *fileref) const; + + void SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const; + void SetUserTextFrame(const std::string &description, const std::string& value, TagLib::ID3v2::Tag *tag) const; + + void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const; + void SetTextFrame(const char *id, const std::string &value, TagLib::ID3v2::Tag *tag) const; + +private: + + FileRefFactory *factory_; + QNetworkAccessManager *network_; + + const std::string kEmbeddedCover; +}; + +#endif // TAGREADER_H diff --git a/ext/libstrawberry-tagreader/tagreadermessages.proto b/ext/libstrawberry-tagreader/tagreadermessages.proto new file mode 100644 index 00000000..629e9c10 --- /dev/null +++ b/ext/libstrawberry-tagreader/tagreadermessages.proto @@ -0,0 +1,110 @@ +package pb.tagreader; + +message SongMetadata { + + enum Type { + UNKNOWN = 0; + ASF = 1; + FLAC = 2; + MP4 = 3; + MPC = 4; + MPEG = 5; + OGGFLAC = 6; + OGGSPEEX = 7; + OGGVORBIS = 8; + AIFF = 9; + WAV = 10; + TRUEAUDIO = 11; + CDDA = 12; + OGGOPUS = 13; + //STREAM = 99; + } + + optional bool valid = 1; + + optional string title = 2; + optional string album = 3; + optional string artist = 4; + optional string albumartist = 5; + optional int32 track = 6; + optional int32 disc = 7; + optional int32 year = 8; + optional int32 originalyear = 9; + optional string genre = 10; + optional bool compilation = 11; + optional string composer = 12; + optional string performer = 13; + optional string grouping = 14; + optional string comment = 15; + + optional uint64 length_nanosec = 16; + + optional int32 bitrate = 17; + optional int32 samplerate = 18; + optional int32 bitdepth = 19; + + optional string url = 20; + optional string basefilename = 21; + optional Type filetype = 22; + optional int32 filesize = 23; + optional int32 mtime = 24; + optional int32 ctime = 25; + + optional int32 playcount = 26; + optional int32 skipcount = 27; + optional int32 lastplayed = 28; + + optional bool suspicious_tags = 29; + optional string art_automatic = 30; + +} + +message ReadFileRequest { + optional string filename = 1; +} + +message ReadFileResponse { + optional SongMetadata metadata = 1; +} + +message SaveFileRequest { + optional string filename = 1; + optional SongMetadata metadata = 2; +} + +message SaveFileResponse { + optional bool success = 1; +} + +message IsMediaFileRequest { + optional string filename = 1; +} + +message IsMediaFileResponse { + optional bool success = 1; +} + +message LoadEmbeddedArtRequest { + optional string filename = 1; +} + +message LoadEmbeddedArtResponse { + optional bytes data = 1; +} + +message Message { + optional int32 id = 1; + + optional ReadFileRequest read_file_request = 2; + optional ReadFileResponse read_file_response = 3; + + optional SaveFileRequest save_file_request = 4; + optional SaveFileResponse save_file_response = 5; + + optional IsMediaFileRequest is_media_file_request = 6; + optional IsMediaFileResponse is_media_file_response = 7; + + optional LoadEmbeddedArtRequest load_embedded_art_request = 8; + optional LoadEmbeddedArtResponse load_embedded_art_response = 9; + +} diff --git a/ext/strawberry-tagreader/CMakeLists.txt b/ext/strawberry-tagreader/CMakeLists.txt new file mode 100644 index 00000000..767f00f2 --- /dev/null +++ b/ext/strawberry-tagreader/CMakeLists.txt @@ -0,0 +1,53 @@ +include_directories(${PROTOBUF_INCLUDE_DIRS}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_SOURCE_DIR}/ext/libstrawberry-common) +include_directories(${CMAKE_SOURCE_DIR}/ext/libstrawberry-tagreader) +include_directories(${CMAKE_BINARY_DIR}/ext/libstrawberry-tagreader) +include_directories(${CMAKE_SOURCE_DIR}/src) +include_directories(${CMAKE_BINARY_DIR}/src) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++0x -U__STRICT_ANSI__") + +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}) + +set(SOURCES + main.cpp + tagreaderworker.cpp +) + +qt5_wrap_cpp(MOC ${HEADERS}) +qt5_add_resources(QRC data/data.qrc) + +add_executable(strawberry-tagreader + ${SOURCES} + ${MOC} + ${QRC} +) + +target_link_libraries(strawberry-tagreader + ${TAGLIB_LIBRARIES} + ${QT_QTCORE_LIBRARY} + ${QT_QTNETWORK_LIBRARY} + libstrawberry-common + libstrawberry-tagreader +) + +if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + target_link_libraries(strawberry-tagreader + execinfo + ) +endif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + +if(APPLE) + target_link_libraries(strawberry-tagreader + /System/Library/Frameworks/Foundation.framework + ) +endif(APPLE) + +if(NOT APPLE) + # macdeploy.py takes care of this on mac + install(TARGETS strawberry-tagreader + RUNTIME DESTINATION bin + ) +endif(NOT APPLE) diff --git a/ext/strawberry-tagreader/data/data.qrc b/ext/strawberry-tagreader/data/data.qrc new file mode 100644 index 00000000..8e2f501e --- /dev/null +++ b/ext/strawberry-tagreader/data/data.qrc @@ -0,0 +1,5 @@ + + + godaddy-root.pem + + diff --git a/ext/strawberry-tagreader/data/godaddy-root.pem b/ext/strawberry-tagreader/data/godaddy-root.pem new file mode 100644 index 00000000..42e8d1ee --- /dev/null +++ b/ext/strawberry-tagreader/data/godaddy-root.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh +MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE +YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 +MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo +ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg +MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN +ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA +PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w +wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi +EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY +avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ +YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE +sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h +/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 +IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy +OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P +TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER +dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf +ReYNnyicsbkqWletNw+vHX/bvZ8= +-----END CERTIFICATE----- diff --git a/ext/strawberry-tagreader/main.cpp b/ext/strawberry-tagreader/main.cpp new file mode 100644 index 00000000..5a260e29 --- /dev/null +++ b/ext/strawberry-tagreader/main.cpp @@ -0,0 +1,62 @@ +/* This file is part of Strawberry. + Copyright 2011, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#include "tagreaderworker.h" +#include "core/logging.h" + +#include +#include +#include +#include + +#include +#include + +int main(int argc, char **argv) { + + QCoreApplication a(argc, argv); + QStringList args(a.arguments()); + + if (args.count() != 2) { + std::cerr << "This program is used internally by Strawberry to parse tags in music files\n" + "without exposing the whole application to crashes caused by malformed\n" + "files. It is not meant to be run on its own.\n"; + return 1; + } + + // Seed random number generator + timeval time; + gettimeofday(&time, nullptr); + qsrand((time.tv_sec * 1000) + (time.tv_usec / 1000)); + + logging::Init(); + qLog(Info) << "TagReader worker connecting to" << args[1]; + + // Connect to the parent process. + QLocalSocket socket; + socket.connectToServer(args[1]); + if (!socket.waitForConnected(2000)) { + std::cerr << "Failed to connect to the parent process.\n"; + return 1; + } + + QSslSocket::addDefaultCaCertificates(QSslCertificate::fromPath(":/certs/godaddy-root.pem", QSsl::Pem)); + + TagReaderWorker worker(&socket); + + return a.exec(); +} diff --git a/ext/strawberry-tagreader/tagreaderworker.cpp b/ext/strawberry-tagreader/tagreaderworker.cpp new file mode 100644 index 00000000..49426d44 --- /dev/null +++ b/ext/strawberry-tagreader/tagreaderworker.cpp @@ -0,0 +1,68 @@ +/* This file is part of Strawberry. + Copyright 2011, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#include "tagreaderworker.h" + +#include +#include +#include +#include +#include +#include + + +TagReaderWorker::TagReaderWorker(QIODevice *socket, QObject *parent) + : AbstractMessageHandler(socket, parent) +{ +} + +void TagReaderWorker::MessageArrived(const pb::tagreader::Message& message) { + + pb::tagreader::Message reply; + +#if 0 + // Crash every few requests + if (qrand() % 10 == 0) { + qLog(Debug) << "Crashing on request ID" << message.id(); + abort(); + } +#endif + + if (message.has_read_file_request()) { + tag_reader_.ReadFile(QStringFromStdString(message.read_file_request().filename()), reply.mutable_read_file_response()->mutable_metadata()); + } + else if (message.has_save_file_request()) { + reply.mutable_save_file_response()->set_success(tag_reader_.SaveFile(QStringFromStdString(message.save_file_request().filename()), message.save_file_request().metadata())); + } + + else if (message.has_is_media_file_request()) { + reply.mutable_is_media_file_response()->set_success(tag_reader_.IsMediaFile(QStringFromStdString(message.is_media_file_request().filename()))); + } + else if (message.has_load_embedded_art_request()) { + QByteArray data = tag_reader_.LoadEmbeddedArt(QStringFromStdString(message.load_embedded_art_request().filename())); + reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size()); + } + + SendReply(message, &reply); +} + + +void TagReaderWorker::DeviceClosed() { + AbstractMessageHandler::DeviceClosed(); + + qApp->exit(); +} diff --git a/ext/strawberry-tagreader/tagreaderworker.h b/ext/strawberry-tagreader/tagreaderworker.h new file mode 100644 index 00000000..798f112b --- /dev/null +++ b/ext/strawberry-tagreader/tagreaderworker.h @@ -0,0 +1,38 @@ +/* This file is part of Strawberry. + Copyright 2011, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#ifndef TAGREADERWORKER_H +#define TAGREADERWORKER_H + +#include "config.h" +#include "tagreader.h" +#include "tagreadermessages.pb.h" +#include "core/messagehandler.h" + +class TagReaderWorker : public AbstractMessageHandler { +public: + TagReaderWorker(QIODevice *socket, QObject *parent = NULL); + +protected: + void MessageArrived(const pb::tagreader::Message &message); + void DeviceClosed(); + +private: + TagReader tag_reader_; +}; + +#endif // TAGREADERWORKER_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 00000000..11cfec75 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,1127 @@ +# Strawberry Music Player +# Copyright 2013, Jonas Kvinge +# This file was part of Clementine. +# Copyright 2010, David Sansome +# +# Strawberry is free software: you can 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. +# +# Strawberry is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Strawberry. If not, see . + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Woverloaded-virtual -Wno-sign-compare -Wno-deprecated-declarations -Wno-unused-local-typedefs -fpermissive --std=c++0x -U__STRICT_ANSI__") + +option(BUILD_WERROR "Build with -Werror" ON) + +if(BUILD_WERROR) + if (LINUX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") + endif (LINUX) +endif(BUILD_WERROR) + +include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(../3rdparty/gmock/gtest/include) +if(WIN32) + include_directories(../3rdparty/qtwin) +endif(WIN32) + +# Activate fast QString concatenation +#if (QT_VERSION_MINOR GREATER 5) + #if (QT_VERSION_MINOR GREATER 7) + add_definitions(-DQT_USE_QSTRINGBUILDER) + #else(QT_VERSION_MINOR GREATER 7) + #add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) + #endif(QT_VERSION_MINOR GREATER 7) +#endif(QT_VERSION_MINOR GREATER 5) +add_definitions(-DQT_NO_URL_CAST_FROM_STRING) +add_definitions(-DBOOST_BIND_NO_PLACEHOLDERS) + +if(ENABLE_IMOBILEDEVICE AND IMOBILEDEVICE_VERSION VERSION_GREATER 1.1.1) + set(IMOBILEDEVICE_USES_UDIDS ON) +endif() + +include_directories(${CMAKE_BINARY_DIR}) +include_directories(${GLIB_INCLUDE_DIRS}) +include_directories(${LIBXML_INCLUDE_DIRS}) +include_directories(${GOBJECT_INCLUDE_DIRS}) +include_directories(${QTSINGLEAPPLICATION_INCLUDE_DIRS}) +include_directories(${QXT_INCLUDE_DIRS}) +include_directories(${SHA2_INCLUDE_DIRS}) +include_directories(${CHROMAPRINT_INCLUDE_DIRS}) +include_directories(${QJSON_INCLUDE_DIRS}) + +find_package(OpenGL) +include_directories(${OPENGL_INCLUDE_DIR}) + +if(HAVE_LIBLASTFM) + include_directories(${LASTFM5_INCLUDE_DIRS}) +endif(HAVE_LIBLASTFM) + +include_directories(${CMAKE_SOURCE_DIR}/ext/libstrawberry-common) +include_directories(${CMAKE_SOURCE_DIR}/ext/libstrawberry-tagreader) +include_directories(${CMAKE_BINARY_DIR}/ext/libstrawberry-tagreader) + +#cmake_policy(SET CMP0011 NEW) +include(../cmake/AddEngine.cmake) +include(../cmake/ParseArguments.cmake) + +set(SOURCES + core/mainwindow.cpp + core/application.cpp + core/appearance.cpp + core/player.cpp + core/commandlineoptions.cpp + core/database.cpp + core/metatypes.cpp + core/deletefiles.cpp + core/filesystemmusicstorage.cpp + core/filesystemwatcherinterface.cpp + core/mergedproxymodel.cpp + core/multisortfilterproxy.cpp + core/musicstorage.cpp + core/network.cpp + core/networkproxyfactory.cpp + core/qtfslistener.cpp + core/settingsprovider.cpp + core/signalchecker.cpp + core/song.cpp + core/songloader.cpp + core/stylesheetloader.cpp + core/tagreaderclient.cpp + core/taskmanager.cpp + core/thread.cpp + core/urlhandler.cpp + core/utilities.cpp + core/scangiomodulepath.cpp + core/flowlayout.cpp + core/iconloader.cpp + core/qtsystemtrayicon.cpp + core/standarditemiconloader.cpp + core/systemtrayicon.cpp + core/windows7thumbbar.cpp + core/screensaver.cpp + core/scopedtransaction.cpp + + engine/enginetype.cpp + engine/enginebase.cpp + engine/enginedevice.cpp + engine/devicefinder.cpp + + analyzer/fht.cpp + analyzer/analyzerbase.cpp + analyzer/analyzercontainer.cpp + analyzer/blockanalyzer.cpp + + equalizer/equalizer.cpp + equalizer/equalizerslider.cpp + + collection/collection.cpp + collection/collectionmodel.cpp + collection/collectionbackend.cpp + collection/collectionwatcher.cpp + collection/collectionview.cpp + collection/collectionviewcontainer.cpp + collection/collectiondirectorymodel.cpp + collection/collectionfilterwidget.cpp + collection/collectionplaylistitem.cpp + collection/collectionquery.cpp + collection/sqlrow.cpp + collection/savedgroupingmanager.cpp + collection/groupbydialog.cpp + + playlist/playlist.cpp + playlist/playlistbackend.cpp + playlist/playlistcontainer.cpp + playlist/playlistdelegates.cpp + playlist/playlistfilter.cpp + playlist/playlistfilterparser.cpp + playlist/playlistheader.cpp + playlist/playlistitem.cpp + playlist/playlistlistcontainer.cpp + playlist/playlistlistmodel.cpp + playlist/playlistlistview.cpp + playlist/playlistmanager.cpp + playlist/playlistsaveoptionsdialog.cpp + playlist/playlistsequence.cpp + playlist/playlisttabbar.cpp + playlist/playlistundocommands.cpp + playlist/playlistview.cpp + playlist/queue.cpp + playlist/queuemanager.cpp + playlist/songloaderinserter.cpp + playlist/songplaylistitem.cpp + + playlistparsers/asxiniparser.cpp + playlistparsers/asxparser.cpp + playlistparsers/cueparser.cpp + playlistparsers/m3uparser.cpp + playlistparsers/parserbase.cpp + playlistparsers/playlistparser.cpp + playlistparsers/plsparser.cpp + playlistparsers/wplparser.cpp + playlistparsers/xmlparser.cpp + playlistparsers/xspfparser.cpp + + covermanager/albumcovermanager.cpp + covermanager/albumcovermanagerlist.cpp + covermanager/albumcoverloader.cpp + covermanager/albumcoverloaderoptions.cpp + covermanager/albumcoverfetcher.cpp + covermanager/albumcoverfetchersearch.cpp + covermanager/albumcoversearcher.cpp + covermanager/albumcoverexport.cpp + covermanager/albumcoverexporter.cpp + covermanager/albumcoverchoicecontroller.cpp + covermanager/coverprovider.cpp + covermanager/coverproviders.cpp + covermanager/coversearchstatistics.cpp + covermanager/coversearchstatisticsdialog.cpp + covermanager/coverexportrunnable.cpp + covermanager/currentartloader.cpp + covermanager/coverfromurldialog.cpp + covermanager/musicbrainzcoverprovider.cpp + covermanager/amazoncoverprovider.cpp + covermanager/discogscoverprovider.cpp + + settings/settingsdialog.cpp + settings/settingspage.cpp + settings/behavioursettingspage.cpp + settings/collectionsettingspage.cpp + settings/backendsettingspage.cpp + settings/playbacksettingspage.cpp + settings/playlistsettingspage.cpp + settings/networkproxysettingspage.cpp + settings/shortcutssettingspage.cpp + settings/appearancesettingspage.cpp + settings/notificationssettingspage.cpp + + dialogs/about.cpp + dialogs/console.cpp + dialogs/errordialog.cpp + dialogs/edittagdialog.cpp + dialogs/trackselectiondialog.cpp + + widgets/autoexpandingtreeview.cpp + widgets/busyindicator.cpp + widgets/clickablelabel.cpp + widgets/didyoumean.cpp + widgets/elidedlabel.cpp + widgets/fancytabwidget.cpp + widgets/favoritewidget.cpp + widgets/fileview.cpp + widgets/fileviewlist.cpp + widgets/forcescrollperpixel.cpp + widgets/freespacebar.cpp + widgets/groupediconview.cpp + widgets/lineedit.cpp + widgets/linetextedit.cpp + widgets/multiloadingindicator.cpp + widgets/statusview.cpp + widgets/playingwidget.cpp + widgets/osd.cpp + widgets/osdpretty.cpp + widgets/prettyimage.cpp + widgets/prettyimageview.cpp + widgets/progressitemdelegate.cpp + widgets/ratingwidget.cpp + widgets/renametablineedit.cpp + widgets/sliderwidget.cpp + widgets/stickyslider.cpp + widgets/stretchheaderview.cpp + widgets/stylehelper.cpp + widgets/trackslider.cpp + widgets/tracksliderpopup.cpp + widgets/tracksliderslider.cpp + widgets/widgetfadehelper.cpp + + musicbrainz/acoustidclient.cpp + musicbrainz/musicbrainzclient.cpp + + globalshortcuts/globalshortcutbackend.cpp + globalshortcuts/globalshortcuts.cpp + globalshortcuts/gnomeglobalshortcutbackend.cpp + globalshortcuts/qxtglobalshortcutbackend.cpp + globalshortcuts/globalshortcutgrabber.cpp + + device/connecteddevice.cpp + device/devicedatabasebackend.cpp + device/devicelister.cpp + device/devicemanager.cpp + device/deviceproperties.cpp + device/devicestatefiltermodel.cpp + device/deviceview.cpp + device/deviceviewcontainer.cpp + device/filesystemdevice.cpp + +) + +set(HEADERS + core/mainwindow.h + core/application.h + core/appearance.h + core/player.h + core/commandlineoptions.h + core/database.h + core/metatypes.h + core/deletefiles.h + core/filesystemmusicstorage.h + core/filesystemwatcherinterface.h + core/mergedproxymodel.h + core/multisortfilterproxy.h + core/musicstorage.h + core/network.h + core/networkproxyfactory.h + core/qtfslistener.h + core/settingsprovider.h + core/signalchecker.h + core/song.h + core/songloader.h + core/stylesheetloader.h + core/tagreaderclient.h + core/taskmanager.h + core/thread.h + core/urlhandler.h + core/utilities.h + core/scangiomodulepath.h + core/flowlayout.h + core/iconloader.h + core/qtsystemtrayicon.h + core/standarditemiconloader.h + core/systemtrayicon.h + core/windows7thumbbar.h + core/screensaver.h + core/cachedlist.h + core/mimedata.h + core/qhash_qurl.h + core/simpletreeitem.h + core/simpletreemodel.h + core/timeconstants.h + core/qt_blurimage.h + core/scopedtransaction.h + core/scopedgobject.h + core/scoped_cftyperef.h + core/scoped_nsautorelease_pool.h + core/scoped_nsobject.h + + engine/enginetype.h + engine/enginebase.h + engine/enginedevice.h + engine/devicefinder.h + engine/engine_fwd.h + + analyzer/fht.h + analyzer/analyzerbase.h + analyzer/analyzercontainer.h + analyzer/blockanalyzer.h + + equalizer/equalizer.h + equalizer/equalizerslider.h + + collection/collection.h + collection/collectionmodel.h + collection/collectionbackend.h + collection/collectionwatcher.h + collection/collectionview.h + collection/collectionviewcontainer.h + collection/collectiondirectorymodel.h + collection/collectionfilterwidget.h + collection/collectionplaylistitem.h + collection/collectionquery.h + collection/collectionitem.h + collection/sqlrow.h + collection/savedgroupingmanager.h + collection/directory.h + collection/groupbydialog.h + + playlist/playlist.h + playlist/playlistbackend.h + playlist/playlistcontainer.h + playlist/playlistdelegates.h + playlist/playlistfilter.h + playlist/playlistfilterparser.h + playlist/playlistheader.h + playlist/playlistlistcontainer.h + playlist/playlistlistmodel.h + playlist/playlistlistview.h + playlist/playlistmanager.h + playlist/playlistsaveoptionsdialog.h + playlist/playlistsequence.h + playlist/playlisttabbar.h + playlist/playlistundocommands.h + playlist/playlistview.h + playlist/playlistitemmimedata.h + playlist/queue.h + playlist/queuemanager.h + playlist/songloaderinserter.h + playlist/songmimedata.h + playlist/songplaylistitem.h + + playlistparsers/asxiniparser.h + playlistparsers/asxparser.h + playlistparsers/cueparser.h + playlistparsers/m3uparser.h + playlistparsers/parserbase.h + playlistparsers/playlistparser.h + playlistparsers/plsparser.h + playlistparsers/wplparser.h + playlistparsers/xmlparser.h + playlistparsers/xspfparser.h + + covermanager/albumcovermanager.h + covermanager/albumcovermanagerlist.h + covermanager/albumcoverloader.h + covermanager/albumcoverloaderoptions.h + covermanager/albumcoverfetcher.h + covermanager/albumcoverfetchersearch.h + covermanager/albumcoversearcher.h + covermanager/albumcoverexport.h + covermanager/albumcoverexporter.h + covermanager/albumcoverchoicecontroller.h + covermanager/coverprovider.h + covermanager/coverproviders.h + covermanager/coversearchstatisticsdialog.h + covermanager/coversearchstatistics.h + covermanager/coverexportrunnable.h + covermanager/currentartloader.h + covermanager/coverfromurldialog.h + covermanager/amazoncoverprovider.h + covermanager/musicbrainzcoverprovider.h + covermanager/discogscoverprovider.h + + settings/settingsdialog.h + settings/settingspage.h + settings/behavioursettingspage.h + settings/collectionsettingspage.h + settings/backendsettingspage.h + settings/playbacksettingspage.h + settings/playlistsettingspage.h + settings/networkproxysettingspage.h + settings/shortcutssettingspage.h + settings/appearancesettingspage.h + settings/notificationssettingspage.h + + dialogs/about.h + dialogs/errordialog.h + dialogs/console.h + dialogs/edittagdialog.h + dialogs/trackselectiondialog.h + + widgets/autoexpandingtreeview.h + widgets/busyindicator.h + widgets/clickablelabel.h + widgets/didyoumean.h + widgets/elidedlabel.h + widgets/fancytabwidget.h + widgets/favoritewidget.h + widgets/fileview.h + widgets/fileviewlist.h + widgets/forcescrollperpixel.h + widgets/freespacebar.h + widgets/groupediconview.h + widgets/lineedit.h + widgets/linetextedit.h + widgets/multiloadingindicator.h + widgets/statusview.h + widgets/playingwidget.h + widgets/osd.h + widgets/osdpretty.h + widgets/prettyimage.h + widgets/prettyimageview.h + widgets/progressitemdelegate.h + widgets/ratingwidget.h + widgets/renametablineedit.h + widgets/sliderwidget.h + widgets/stickyslider.h + widgets/stretchheaderview.h + widgets/stylehelper.h + widgets/trackslider.h + widgets/tracksliderpopup.h + widgets/tracksliderslider.h + widgets/widgetfadehelper.h + + musicbrainz/acoustidclient.h + musicbrainz/musicbrainzclient.h + + globalshortcuts/globalshortcutbackend.h + globalshortcuts/globalshortcuts.h + globalshortcuts/gnomeglobalshortcutbackend.h + globalshortcuts/qxtglobalshortcutbackend.h + globalshortcuts/globalshortcutgrabber.h + + device/connecteddevice.h + device/devicedatabasebackend.h + device/devicekitlister.h + device/devicelister.h + device/devicemanager.h + device/deviceproperties.h + device/devicestatefiltermodel.h + device/deviceviewcontainer.h + device/deviceview.h + device/filesystemdevice.h + +) + +set(UI + + core/mainwindow.ui + + collection/groupbydialog.ui + collection/collectionfilterwidget.ui + collection/collectionviewcontainer.ui + collection/savedgroupingmanager.ui + + playlist/playlistcontainer.ui + playlist/playlistlistcontainer.ui + playlist/playlistsaveoptionsdialog.ui + playlist/playlistsequence.ui + playlist/queuemanager.ui + + covermanager/albumcoverexport.ui + covermanager/albumcovermanager.ui + covermanager/albumcoversearcher.ui + covermanager/coversearchstatisticsdialog.ui + covermanager/coverfromurldialog.ui + + settings/settingsdialog.ui + settings/behavioursettingspage.ui + settings/collectionsettingspage.ui + settings/backendsettingspage.ui + settings/playbacksettingspage.ui + settings/playlistsettingspage.ui + settings/networkproxysettingspage.ui + settings/shortcutssettingspage.ui + settings/appearancesettingspage.ui + settings/notificationssettingspage.ui + + equalizer/equalizer.ui + equalizer/equalizerslider.ui + + dialogs/about.ui + dialogs/errordialog.ui + dialogs/console.ui + dialogs/edittagdialog.ui + dialogs/trackselectiondialog.ui + + widgets/trackslider.ui + widgets/osdpretty.ui + widgets/fileview.ui + + device/deviceproperties.ui + device/deviceviewcontainer.ui + + globalshortcuts/globalshortcutgrabber.ui + +) + +set(RESOURCES + ../data/data.qrc + #../data/icons.qrc +) + +set(OTHER_SOURCES) + +option(USE_INSTALL_PREFIX "Look for data in CMAKE_INSTALL_PREFIX" ON) + +# Engines + +set(GST_ENGINE_SRC engine/gstengine.cpp engine/gstenginepipeline.cpp engine/gstelementdeleter.cpp) +set(GST_ENGINE_MOC engine/gstengine.h engine/gstenginepipeline.h engine/gstelementdeleter.h engine/bufferconsumer.h) +set(GST_ENGINE_LIB GSTREAMER GSTREAMER_BASE GSTREAMER_APP GSTREAMER_AUDIO GSTREAMER_TAG GSTREAMER_PBUTILS) +#set(GST_ENGINE_LIB gstreamer-1.0 gstreamer-base-1.0 gstreamer-app-1.0 streamer-audio-1.0 gstreamer-tag-1.0 gstreamer-pbutils-1.0) +#set(GST_ENGINE_LIB ${GSTREAMER_BASE_LIBRARIES} ${GSTREAMER_LIBRARIES} ${GSTREAMER_APP_LIBRARIES} ${GSTREAMER_TAG_LIBRARIES} ${GSTREAMER_PBUTILS_LIBRARIES}) + +set(XINE_ENGINE_SRC engine/xineengine.cpp engine/xinescope.c) +set(XINE_ENGINE_MOC engine/xineengine.h engine/xinescope.h) + +set(VLC_ENGINE_SRC engine/vlcengine.cpp) +set(VLC_ENGINE_MOC engine/vlcengine.h engine/vlcscopedref.h) + +set(PHONON_ENGINE_SRC engine/phononengine.cpp) +set(PHONON_ENGINE_MOC engine/phononengine.h) + +add_engine(gstreamer GSTREAMER "${GST_ENGINE_LIB}" "${GST_ENGINE_SRC}" "${GST_ENGINE_MOC}" ON) +add_engine(xine XINE LIBXINE "${XINE_ENGINE_SRC}" "${XINE_ENGINE_MOC}" ON) +add_engine(vlc VLC LIBVLC "${VLC_ENGINE_SRC}" "${VLC_ENGINE_MOC}" ON) +add_engine(phonon PHONON PHONON "${PHONON_ENGINE_SRC}" "${PHONON_ENGINE_MOC}" OFF) + +print_engines() + +# Lastfm +optional_source(HAVE_LIBLASTFM + SOURCES + covermanager/lastfmcoverprovider.cpp + covermanager/lastfmcompat.cpp + HEADERS + covermanager/lastfmcoverprovider.h + covermanager/lastfmcompat.h +) + +# Platform specific - Linux +optional_source(LINUX + SOURCES + engine/alsadevicefinder.cpp + HEADERS + engine/alsadevicefinder.h +) + +# Platform specific - OS X +optional_source(APPLE + SOURCES + core/mac_startup.mm + core/macsystemtrayicon.mm + core/macscreensaver.cpp + core/macfslistener.mm + core/scoped_nsautorelease_pool.mm + widgets/osd_mac.mm + engine/osxdevicefinder.cpp + device/macdevicelister.mm + globalshortcuts/shortcutgrabber.mm + globalshortcuts/macglobalshortcutbackend.mm + globalshortcuts/globalshortcutgrabber.mm + HEADERS + core/mac_startup.h + core/macsystemtrayicon.h + core/macscreensaver.h + core/macfslistener.h + core/mac_utilities.h + core/mac_delegate.h + engine/osxdevicefinder.h + device/macdevicelister.h + globalshortcuts/macglobalshortcutbackend.h +) + +if(APPLE) + optional_source(HAVE_LIBMTP + SOURCES + device/macdevicelister.mm + HEADERS + device/macdevicelister.h + ) +endif() + +# Platform specific - Windows +optional_source(WIN32 + SOURCES + engine/directsounddevicefinder.cpp + widgets/osd_win.cpp + HEADERS + engine/directsounddevicefinder.h + INCLUDE_DIRECTORIES + ${CMAKE_SOURCE_DIR}/3rdparty/tinysvcmdns +) + +# Platform specific - X11 +optional_source(LINUX SOURCES widgets/osd_x11.cpp) + +# DBUS and MPRIS - Linux specific +if(HAVE_DBUS) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dbus) + + # MPRIS DBUS interfaces + #qt5_add_dbus_adaptor(SOURCES + # dbus/org.freedesktop.MediaPlayer.player.xml + # core/mpris1.h mpris::Mpris1Player core/mpris_player MprisPlayer) + #qt5_add_dbus_adaptor(SOURCES + # dbus/org.freedesktop.MediaPlayer.root.xml + # core/mpris1.h mpris::Mpris1Root core/mpris_root MprisRoot) + #qt5_add_dbus_adaptor(SOURCES + # dbus/org.freedesktop.MediaPlayer.tracklist.xml + # core/mpris1.h mpris::Mpris1TrackList core/mpris_tracklist MprisTrackList) + + # MPRIS 2.0 DBUS interfaces + qt5_add_dbus_adaptor(SOURCES + dbus/org.mpris.MediaPlayer2.Player.xml + core/mpris2.h mpris::Mpris2 core/mpris2_player Mpris2Player) + qt5_add_dbus_adaptor(SOURCES + dbus/org.mpris.MediaPlayer2.xml + core/mpris2.h mpris::Mpris2 core/mpris2_root Mpris2Root) + qt5_add_dbus_adaptor(SOURCES + dbus/org.mpris.MediaPlayer2.TrackList.xml + core/mpris2.h mpris::Mpris2 core/mpris2_tracklist Mpris2TrackList) + + # MPRIS 2.1 DBUS interfaces + qt5_add_dbus_adaptor(SOURCES + dbus/org.mpris.MediaPlayer2.Playlists.xml + core/mpris2.h mpris::Mpris2 core/mpris2_playlists Mpris2Playlists) + + # org.freedesktop.Notifications DBUS interface + qt5_add_dbus_interface(SOURCES + dbus/org.freedesktop.Notifications.xml + dbus/notification) + + # org.gnome.SettingsDaemon interface + qt5_add_dbus_interface(SOURCES + dbus/org.gnome.SettingsDaemon.MediaKeys.xml + dbus/gnomesettingsdaemon) + + # org.freedesktop.Avahi.Server interface + add_custom_command( + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.cpp + ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.h + COMMAND ${QT_DBUSXML2CPP_EXECUTABLE} + dbus/org.freedesktop.Avahi.Server.xml + -p ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver + -i dbus/metatypes.h + DEPENDS dbus/org.freedesktop.Avahi.Server.xml + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + list(APPEND HEADERS ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.h) + list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.cpp) + + # org.freedesktop.Avahi.EntryGroup interface + add_custom_command( + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.cpp + ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.h + COMMAND ${QT_DBUSXML2CPP_EXECUTABLE} + dbus/org.freedesktop.Avahi.EntryGroup.xml + -p ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup + -i dbus/metatypes.h + DEPENDS dbus/org.freedesktop.Avahi.EntryGroup.xml + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + list(APPEND HEADERS ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.h) + list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.cpp) + + # DeviceKit DBUS interfaces + if(HAVE_DEVICEKIT) + set_source_files_properties(dbus/org.freedesktop.UDisks.xml + PROPERTIES NO_NAMESPACE dbus/udisks) + set_source_files_properties(dbus/org.freedesktop.UDisks.Device.xml + PROPERTIES NO_NAMESPACE dbus/udisksdevice) + qt5_add_dbus_interface(SOURCES + dbus/org.freedesktop.UDisks.xml + dbus/udisks) + qt5_add_dbus_interface(SOURCES + dbus/org.freedesktop.UDisks.Device.xml + dbus/udisksdevice) + endif(HAVE_DEVICEKIT) + + if(HAVE_UDISKS2) + set_source_files_properties(dbus/org.freedesktop.DBus.ObjectManager.xml + PROPERTIES NO_NAMESPACE dbus/objectmanager INCLUDE dbus/metatypes.h) + set_source_files_properties(dbus/org.freedesktop.UDisks2.Filesystem.xml + PROPERTIES NO_NAMESPACE dbus/udisks2filesystem INCLUDE dbus/metatypes.h) + set_source_files_properties(dbus/org.freedesktop.UDisks2.Block.xml + PROPERTIES NO_NAMESPACE dbus/udisks2block INCLUDE dbus/metatypes.h) + set_source_files_properties(dbus/org.freedesktop.UDisks2.Drive.xml + PROPERTIES NO_NAMESPACE dbus/udisks2drive INCLUDE dbus/metatypes.h) + set_source_files_properties(dbus/org.freedesktop.UDisks2.Job.xml + PROPERTIES NO_NAMESPACE dbus/udisks2job INCLUDE dbus/metatypes.h) + qt5_add_dbus_interface(SOURCES + dbus/org.freedesktop.DBus.ObjectManager.xml + dbus/objectmanager) + qt5_add_dbus_interface(SOURCES + dbus/org.freedesktop.UDisks2.Filesystem.xml + dbus/udisks2filesystem) + qt5_add_dbus_interface(SOURCES + dbus/org.freedesktop.UDisks2.Block.xml + dbus/udisks2block) + qt5_add_dbus_interface(SOURCES + dbus/org.freedesktop.UDisks2.Drive.xml + dbus/udisks2drive) + qt5_add_dbus_interface(SOURCES + dbus/org.freedesktop.UDisks2.Job.xml + dbus/udisks2job) + endif(HAVE_UDISKS2) + +endif(HAVE_DBUS) + +optional_source(HAVE_DBUS + SOURCES + core/mpris.cpp + core/mpris2.cpp + core/dbusscreensaver.cpp + HEADERS + core/mpris.h + core/mpris2.h + core/mpris_common.h + core/dbusscreensaver.h +) + +optional_source(HAVE_DEVICEKIT + SOURCES device/devicekitlister.cpp + HEADERS device/devicekitlister.h +) + +optional_source(HAVE_UDISKS2 + SOURCES device/udisks2lister.cpp + HEADERS device/udisks2lister.h +) + +# Libgpod device backend +optional_source(HAVE_LIBGPOD + INCLUDE_DIRECTORIES ${LIBGPOD_INCLUDE_DIRS} + SOURCES + device/gpoddevice.cpp + device/gpodloader.cpp + HEADERS + device/gpoddevice.h + device/gpodloader.h +) + +# GIO device backend +optional_source(HAVE_GIO + INCLUDE_DIRECTORIES ${GIO_INCLUDE_DIRS} + SOURCES device/giolister.cpp + HEADERS device/giolister.h +) + +# libimobiledevice backend and device +optional_source(HAVE_IMOBILEDEVICE + INCLUDE_DIRECTORIES + ${IMOBILEDEVICE_INCLUDE_DIRS} + ${PLIST_INCLUDE_DIRS} + ${PLISTPP_INCLUDE_DIRS} + SOURCES + device/afcdevice.cpp + device/afcfile.cpp + device/afctransfer.cpp + device/ilister.cpp + device/imobiledeviceconnection.cpp + HEADERS + device/afcdevice.h + device/afcfile.h + device/afctransfer.h + device/ilister.h + device/imobiledeviceconnection.h +) + +# mtp device +optional_source(HAVE_LIBMTP + INCLUDE_DIRECTORIES ${LIBMTP_INCLUDE_DIRS} + SOURCES + device/mtpconnection.cpp + device/mtpdevice.cpp + device/mtploader.cpp + HEADERS + device/mtpdevice.h + device/mtploader.h + device/mtpconnection.h +) + +# Pulse audio integration +optional_source(HAVE_LIBPULSE + INCLUDE_DIRECTORIES + ${LIBPULSE_INCLUDE_DIRS} + SOURCES + engine/pulsedevicefinder.cpp + HEADERS + engine/pulsedevicefinder.h +) + +optional_source(HAVE_GSTREAMER +SOURCES + core/organise.cpp + core/organiseformat.cpp + settings/transcodersettingspage.cpp + dialogs/organisedialog.cpp + dialogs/organiseerrordialog.cpp + musicbrainz/chromaprinter.cpp + musicbrainz/tagfetcher.cpp + transcoder/transcoder.cpp + transcoder/transcodedialog.cpp + transcoder/transcoderoptionsaac.cpp + transcoder/transcoderoptionsdialog.cpp + transcoder/transcoderoptionsflac.cpp + transcoder/transcoderoptionsmp3.cpp + transcoder/transcoderoptionsopus.cpp + transcoder/transcoderoptionsspeex.cpp + transcoder/transcoderoptionsvorbis.cpp + transcoder/transcoderoptionswma.cpp +HEADERS + core/organise.h + core/organiseformat.h + settings/transcodersettingspage.h + dialogs/organisedialog.h + dialogs/organiseerrordialog.h + musicbrainz/chromaprinter.h + musicbrainz/tagfetcher.h + transcoder/transcoder.h + transcoder/transcodedialog.h + transcoder/transcoderoptionsaac.h + transcoder/transcoderoptionsdialog.h + transcoder/transcoderoptionsflac.h + transcoder/transcoderoptionsinterface.h + transcoder/transcoderoptionsmp3.h + transcoder/transcoderoptionsopus.h + transcoder/transcoderoptionsspeex.h + transcoder/transcoderoptionsvorbis.h + transcoder/transcoderoptionswma.h +UI + settings/transcodersettingspage.ui + dialogs/organisedialog.ui + dialogs/organiseerrordialog.ui + transcoder/transcodedialog.ui + transcoder/transcodelogdialog.ui + transcoder/transcoderoptionsaac.ui + transcoder/transcoderoptionsdialog.ui + transcoder/transcoderoptionsflac.ui + transcoder/transcoderoptionsmp3.ui + transcoder/transcoderoptionsopus.ui + transcoder/transcoderoptionsspeex.ui + transcoder/transcoderoptionsvorbis.ui + transcoder/transcoderoptionswma.ui +) + +# CDIO backend and device +if(HAVE_GSTREAMER) +optional_source(HAVE_AUDIOCD + SOURCES + device/cddadevice.cpp + device/cddalister.cpp + device/cddasongloader.cpp + HEADERS + device/cddadevice.h + device/cddalister.h + device/cddasongloader.h + UI +) +endif(HAVE_GSTREAMER) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/config.h) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in + ${CMAKE_CURRENT_BINARY_DIR}/version.h) + +qt5_wrap_cpp(MOC ${HEADERS}) +qt5_wrap_ui(UIC ${UI}) +qt5_add_resources(QRC ${RESOURCES}) + +add_library(strawberry_lib STATIC + ${SOURCES} + ${MOC} + ${UIC} + ${QRC} + ${POT} + ${PO} + ${OTHER_UIC_SOURCES} +) + +target_link_libraries(strawberry_lib + libstrawberry-common + libstrawberry-tagreader + ${GLIB_LIBRARIES} + ${GIO_LIBRARIES} + ${SHA2_LIBRARIES} + ${TAGLIB_LIBRARIES} + ${GOBJECT_LIBRARIES} + ${QT_LIBRARIES} + ${ENGINE_LIBRARIES} + ${CHROMAPRINT_LIBRARIES} + ${QTSINGLEAPPLICATION_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + ${SQLITE_LIBRARIES} + ${QJSON_LIBRARIES} + z + Qocoa +) + +if(LINUX) + target_link_libraries(strawberry_lib ${ALSA_LIBRARIES}) +endif(LINUX) + +if(HAVE_LIBLASTFM) + target_link_libraries(strawberry_lib ${LASTFM5_LIBRARIES}) +endif(HAVE_LIBLASTFM) + +if(HAVE_LIBGPOD) + target_link_libraries(strawberry_lib ${LIBGPOD_LIBRARIES}) +endif(HAVE_LIBGPOD) + +if(HAVE_GIO) + target_link_libraries(strawberry_lib ${GIO_LIBRARIES}) +endif(HAVE_GIO) + +if(HAVE_AUDIOCD) + target_link_libraries(strawberry_lib ${CDIO_LIBRARIES}) +endif(HAVE_AUDIOCD) + +if(HAVE_IMOBILEDEVICE) + target_link_libraries(strawberry_lib + ${IMOBILEDEVICE_LIBRARIES} + ${PLIST_LIBRARIES} + ${USBMUXD_LIBRARIES} + gstafcsrc + ) + link_directories(${IMOBILEDEVICE_LIBRARY_DIRS}) + link_directories(${USBMUXD_LIBRARY_DIRS}) +endif(HAVE_IMOBILEDEVICE) + +if(HAVE_LIBMTP) + target_link_libraries(strawberry_lib ${LIBMTP_LIBRARIES}) +endif(HAVE_LIBMTP) + +#if(HAVE_LIBINDICATE) +# target_link_libraries(strawberry_lib ${INDICATEQT_LIBRARIES}) +#endif(HAVE_LIBINDICATE) + +if(HAVE_LIBPULSE) + target_link_libraries(strawberry_lib ${LIBPULSE_LIBRARIES}) +endif() + +if (APPLE) + target_link_libraries(strawberry_lib + "-framework AppKit" + "-framework Carbon" + "-framework CoreAudio" + "-framework DiskArbitration" + "-framework Foundation" + "-framework IOKit" + "-framework ScriptingBridge" + ) + target_link_libraries(strawberry_lib ${SPMEDIAKEYTAP_LIBRARIES}) + if (HAVE_SPARKLE) + include_directories(${SPARKLE}/Headers) + target_link_libraries(strawberry_lib ${SPARKLE}) + endif (HAVE_SPARKLE) +else (APPLE) + target_link_libraries(strawberry_lib ${QXT_LIBRARIES}) +endif (APPLE) + +set(3RDPARTY_SQLITE_LIBRARY qsqlite) +target_link_libraries(strawberry_lib qsqlite) + +set(3RDPARTY_QJSON_LIBRARY qjson) +target_link_libraries(strawberry_lib qjson) + +if (WIN32) + target_link_libraries(strawberry_lib + ${ZLIB_LIBRARIES} + ${QTSPARKLE_LIBRARIES} + tinysvcmdns + qtwin + dsound + ${QT_QTGUI_LIBRARY} + ) +endif (WIN32) + +if (UNIX AND NOT APPLE) + # Hack: the Gold linker pays attention to the order that libraries are + # specified on the link line. -lX11 and -ldl are provided earlier in the link + # command but they're actually used by libraries that appear after them, so + # they end up getting ignored. This appends them to the very end of the link + # line, ensuring they're always used. + find_package(X11) + if (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + target_link_libraries(strawberry_lib ${X11_X11_LIB}) + else () + target_link_libraries(strawberry_lib ${X11_X11_LIB} ${CMAKE_DL_LIBS}) + endif () +endif () + +#add_dependencies(strawberry_lib qtsingleapplication) + + +############################################################################### + +set(EXECUTABLE_OUTPUT_PATH ..) + +# Show the console window in debug mode on Windows +if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT ENABLE_WIN32_CONSOLE) + set(STRAWBERRY-WIN32-FLAG WIN32) +endif (NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT ENABLE_WIN32_CONSOLE) + +# resource file for windows +if(WIN32) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../dist/windows/windres.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/windres.rc) + set(STRAWBERRY-WIN32-RESOURCES windres.rc) +endif(WIN32) + +add_executable(strawberry + MACOSX_BUNDLE + ${STRAWBERRY-WIN32-FLAG} + ${STRAWBERRY-WIN32-RESOURCES} + core/main.cpp +) + +if (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + target_link_libraries(strawberry execinfo) +endif (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + +target_link_libraries(strawberry + strawberry_lib +) + +# macdeploy.py relies on the blob being built first. +add_dependencies(strawberry strawberry-tagreader) + +set_target_properties(strawberry PROPERTIES + MACOSX_BUNDLE_INFO_PLIST "../dist/Info.plist" +) + +if (APPLE) + install(FILES ../dist/strawberry.icns + DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Resources") + install(FILES ../dist/qt.conf + DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Resources") + install(FILES ../dist/sparkle_pub.pem + DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Resources") + + install(DIRECTORY "${QT_QTGUI_LIBRARY_RELEASE}/Versions/Current/Resources/" + DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Resources") + + if (HAVE_SPARKLE) + install(DIRECTORY "${SPARKLE}/Versions/Current/Resources" + DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Frameworks/Sparkle.framework") + endif (HAVE_SPARKLE) + + install(FILES "${QT_QTCORE_LIBRARY_RELEASE}/Contents/Info.plist" + DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Frameworks/QtCore.framework/Versions/4/Resources") + install(FILES "${QT_QTGUI_LIBRARY_RELEASE}/Contents/Info.plist" + DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Frameworks/QtGui.framework/Versions/4/Resources") + install(FILES "${QT_QTNETWORK_LIBRARY_RELEASE}/Contents/Info.plist" + DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Frameworks/QtNetwork.framework/Versions/4/Resources") + install(FILES "${QT_QTOPENGL_LIBRARY_RELEASE}/Contents/Info.plist" + DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Frameworks/QtOpenGL.framework/Versions/4/Resources") + install(FILES "${QT_QTSQL_LIBRARY_RELEASE}/Contents/Info.plist" + DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Frameworks/QtSql.framework/Versions/4/Resources") + install(FILES "${QT_QTSVG_LIBRARY_RELEASE}/Contents/Info.plist" + DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Frameworks/QtSvg.framework/Versions/4/Resources") + install(FILES "${QT_QTXML_LIBRARY_RELEASE}/Contents/Info.plist" + DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Frameworks/QtXml.framework/Versions/4/Resources") + + add_custom_command(TARGET strawberry + POST_BUILD + COMMAND + ${CMAKE_CURRENT_SOURCE_DIR}/../dist/macdeploy.py ${PROJECT_BINARY_DIR}/strawberry.app -f + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + ) + + if (APPLE_DEVELOPER_ID) + add_custom_target( + sign + COMMAND + ${PROJECT_SOURCE_DIR}/dist/codesign.py ${APPLE_DEVELOPER_ID} ${PROJECT_BINARY_DIR}/strawberry.app + DEPENDS strawberry + VERBATIM + ) + endif() + + + add_custom_command( + OUTPUT ${PROJECT_BINARY_DIR}/strawberry-${STRAWBERRY_VERSION_SPARKLE}.dmg + ${CMAKE_COMMAND} -E remove -f ${PROJECT_BINARY_DIR}/strawberry-${STRAWBERRY_VERSION_SPARKLE}.dmg + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/../dist/create-dmg.sh ${PROJECT_BINARY_DIR}/strawberry.app + COMMAND ${CMAKE_COMMAND} -E rename + ${PROJECT_BINARY_DIR}/strawberry.dmg + ${PROJECT_BINARY_DIR}/strawberry-${STRAWBERRY_VERSION_SPARKLE}.dmg + DEPENDS strawberry + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + ) + add_custom_target(dmg + DEPENDS ${PROJECT_BINARY_DIR}/strawberry-${STRAWBERRY_VERSION_SPARKLE}.dmg) +else (APPLE) + install(TARGETS strawberry + RUNTIME DESTINATION bin + ) +endif (APPLE) diff --git a/src/analyzer/analyzer.cpp b/src/analyzer/analyzer.cpp new file mode 100644 index 00000000..c708172f --- /dev/null +++ b/src/analyzer/analyzer.cpp @@ -0,0 +1,14 @@ +#include "analyzer.h" + +#include "engines/enginebase.h" + +AnalyzerBase::AnalyzerBase(QWidget* parent) + : QGLWidget(parent), engine_(nullptr) {} + +void AnalyzerBase::set_engine(Engine::Base* engine) { + disconnect(engine_); + engine_ = engine; + if (engine_) { + connect(engine_, SIGNAL(SpectrumAvailable(const QVector&)), SLOT(SpectrumAvailable(const QVector&))); + } +} diff --git a/src/analyzer/analyzerbase.cpp b/src/analyzer/analyzerbase.cpp new file mode 100755 index 00000000..37c38173 --- /dev/null +++ b/src/analyzer/analyzerbase.cpp @@ -0,0 +1,223 @@ +/*************************************************************************** + viswidget.cpp - description + ------------------- + begin : Die Jan 7 2003 + copyright : (C) 2003 by Max Howell + email : markey@web.de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "analyzerbase.h" + +#include //interpolate() + +#include //event() +#include +#include +#include + +#include "engine/enginebase.h" + +// INSTRUCTIONS Base2D +// 1. do anything that depends on height() in init(), Base2D will call it before +// you are shown +// 2. otherwise you can use the constructor to initialise things +// 3. reimplement analyze(), and paint to canvas(), Base2D will update the +// widget when you return control to it +// 4. if you want to manipulate the scope, reimplement transform() +// 5. for convenience are pre-included +// TODO make an INSTRUCTIONS file +// can't mod scope in analyze you have to use transform + +// TODO for 2D use setErasePixmap Qt function insetead of m_background + +// make the linker happy only for gcc < 4.0 +#if !(__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 0)) && \ + !defined(Q_OS_WIN32) +template class Analyzer::Base; +#endif + +Analyzer::Base::Base(QWidget* parent, uint scopeSize) + : QWidget(parent), + m_timeout(40) // msec + , + m_fht(new FHT(scopeSize)), + m_engine(nullptr), + m_lastScope(512), + current_chunk_(0), + new_frame_(false), + is_playing_(false) {} + +void Analyzer::Base::hideEvent(QHideEvent*) { m_timer.stop(); } + +void Analyzer::Base::showEvent(QShowEvent*) { m_timer.start(timeout(), this); } + +void Analyzer::Base::transform(Scope& scope) // virtual +{ + + // this is a standard transformation that should give + // an FFT scope that has bands for pretty analyzers + + // NOTE resizing here is redundant as FHT routines only calculate FHT::size() + // values + // scope.resize( m_fht->size() ); + + float* front = static_cast(&scope.front()); + + float* f = new float[m_fht->size()]; + m_fht->copy(&f[0], front); + m_fht->logSpectrum(front, &f[0]); + m_fht->scale(front, 1.0 / 20); + + scope.resize(m_fht->size() / 2); // second half of values are rubbish + delete[] f; + +} + +void Analyzer::Base::paintEvent(QPaintEvent* e) { + + QPainter p(this); + p.fillRect(e->rect(), palette().color(QPalette::Window)); + + switch (m_engine->state()) { + case Engine::Playing: { + const Engine::Scope& thescope = m_engine->scope(m_timeout); + int i = 0; + + // convert to mono here - our built in analyzers need mono, but the + // engines provide interleaved pcm + for (uint x = 0; (int)x < m_fht->size(); ++x) { + m_lastScope[x] = double(thescope[i] + thescope[i + 1]) / (2 * (1 << 15)); + i += 2; + } + + is_playing_ = true; + transform(m_lastScope); + analyze(p, m_lastScope, new_frame_); + + // scope.resize( m_fht->size() ); + + break; + } + case Engine::Paused: + is_playing_ = false; + analyze(p, m_lastScope, new_frame_); + break; + + default: + is_playing_ = false; + demo(p); + } + + new_frame_ = false; + +} + +int Analyzer::Base::resizeExponent(int exp) { + if (exp < 3) + exp = 3; + else if (exp > 9) + exp = 9; + + if (exp != m_fht->sizeExp()) { + delete m_fht; + m_fht = new FHT(exp); + } + return exp; +} + +int Analyzer::Base::resizeForBands(int bands) { + + int exp; + if (bands <= 8) + exp = 4; + else if (bands <= 16) + exp = 5; + else if (bands <= 32) + exp = 6; + else if (bands <= 64) + exp = 7; + else if (bands <= 128) + exp = 8; + else + exp = 9; + + resizeExponent(exp); + return m_fht->size() / 2; + +} + +void Analyzer::Base::demo(QPainter& p) // virtual +{ + + static int t = 201; // FIXME make static to namespace perhaps + + if (t > 999) t = 1; // 0 = wasted calculations + if (t < 201) { + Scope s(32); + + const double dt = double(t) / 200; + for (uint i = 0; i < s.size(); ++i) + s[i] = dt * (sin(M_PI + (i * M_PI) / s.size()) + 1.0); + + analyze(p, s, new_frame_); + } else + analyze(p, Scope(32, 0), new_frame_); + + ++t; + +} + +void Analyzer::Base::polishEvent() { + init(); // virtual +} + +void Analyzer::interpolate(const Scope& inVec, Scope& outVec) // static +{ + + double pos = 0.0; + const double step = (double)inVec.size() / outVec.size(); + + for (uint i = 0; i < outVec.size(); ++i, pos += step) { + const double error = pos - std::floor(pos); + const unsigned long offset = (unsigned long)pos; + + unsigned long indexLeft = offset + 0; + + if (indexLeft >= inVec.size()) indexLeft = inVec.size() - 1; + + unsigned long indexRight = offset + 1; + + if (indexRight >= inVec.size()) indexRight = inVec.size() - 1; + + outVec[i] = inVec[indexLeft] * (1.0 - error) + inVec[indexRight] * error; + } + +} + +void Analyzer::initSin(Scope& v, const uint size) // static +{ + double step = (M_PI * 2) / size; + double radian = 0; + + for (uint i = 0; i < size; i++) { + v.push_back(sin(radian)); + radian += step; + } +} + +void Analyzer::Base::timerEvent(QTimerEvent* e) { + QWidget::timerEvent(e); + if (e->timerId() != m_timer.timerId()) return; + + new_frame_ = true; + update(); +} diff --git a/src/analyzer/analyzerbase.h b/src/analyzer/analyzerbase.h new file mode 100644 index 00000000..795b5c95 --- /dev/null +++ b/src/analyzer/analyzerbase.h @@ -0,0 +1,89 @@ +// Maintainer: Max Howell , (C) 2004 +// Copyright: See COPYING file that comes with this distribution + +#ifndef ANALYZERBASE_H +#define ANALYZERBASE_H + +#ifdef __FreeBSD__ +#include +#endif + +#include "analyzer/fht.h" //stack allocated and convenience +#include "engine/engine_fwd.h" +#include //stack allocated and convenience +#include //stack allocated +#include //baseclass +#include //included for convenience + +#include //baseclass +#ifdef Q_WS_MACX +#include //included for convenience +#include //included for convenience +#else +#include //included for convenience +#include //included for convenience +#endif + +class QEvent; +class QPaintEvent; +class QResizeEvent; + +namespace Analyzer { + +typedef std::vector Scope; + +class Base : public QWidget { + Q_OBJECT + + public: + ~Base() { delete m_fht; } + + uint timeout() const { return m_timeout; } + + void set_engine(EngineBase *engine) { m_engine = engine; } + + void changeTimeout(uint newTimeout) { + m_timeout = newTimeout; + if (m_timer.isActive()) { + m_timer.stop(); + m_timer.start(m_timeout, this); + } + } + + virtual void framerateChanged() {} + + protected: + Base(QWidget*, uint scopeSize = 7); + + void hideEvent(QHideEvent*); + void showEvent(QShowEvent*); + void paintEvent(QPaintEvent*); + void timerEvent(QTimerEvent*); + + void polishEvent(); + + int resizeExponent(int); + int resizeForBands(int); + virtual void init() {} + virtual void transform(Scope&); + virtual void analyze(QPainter& p, const Scope&, bool new_frame) = 0; + virtual void demo(QPainter& p); + + protected: + QBasicTimer m_timer; + uint m_timeout; + FHT* m_fht; + EngineBase* m_engine; + Scope m_lastScope; + int current_chunk_; + + bool new_frame_; + bool is_playing_; +}; + +void interpolate(const Scope&, Scope&); +void initSin(Scope&, const uint = 6000); + +} // END namespace Analyzer + +#endif diff --git a/src/analyzer/analyzercontainer.cpp b/src/analyzer/analyzercontainer.cpp new file mode 100644 index 00000000..7bdfd386 --- /dev/null +++ b/src/analyzer/analyzercontainer.cpp @@ -0,0 +1,220 @@ +/* + Strawberry Music Player + This file was part of Clementine. + Copyright 2010, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#include "analyzercontainer.h" +#include "blockanalyzer.h" + +#include "core/logging.h" + +#include +#include +#include +#include +#include + +const char* AnalyzerContainer::kSettingsGroup = "Analyzer"; +const char* AnalyzerContainer::kSettingsFramerate = "framerate"; + +// Framerates +const int AnalyzerContainer::kLowFramerate = 20; +const int AnalyzerContainer::kMediumFramerate = 25; +const int AnalyzerContainer::kHighFramerate = 30; +const int AnalyzerContainer::kSuperHighFramerate = 60; + +AnalyzerContainer::AnalyzerContainer(QWidget* parent) + : QWidget(parent), + current_framerate_(kMediumFramerate), + context_menu_(new QMenu(this)), + context_menu_framerate_(new QMenu(tr("Framerate"), this)), + group_(new QActionGroup(this)), + group_framerate_(new QActionGroup(this)), + mapper_(new QSignalMapper(this)), + mapper_framerate_(new QSignalMapper(this)), + visualisation_action_(nullptr), + double_click_timer_(new QTimer(this)), + ignore_next_click_(false), + current_analyzer_(nullptr), + engine_(nullptr) { + QHBoxLayout* layout = new QHBoxLayout(this); + setLayout(layout); + layout->setContentsMargins(0, 0, 0, 0); + + // Init framerate sub-menu + AddFramerate(tr("Low (%1 fps)").arg(kLowFramerate), kLowFramerate); + AddFramerate(tr("Medium (%1 fps)").arg(kMediumFramerate), kMediumFramerate); + AddFramerate(tr("High (%1 fps)").arg(kHighFramerate), kHighFramerate); + AddFramerate(tr("Super high (%1 fps)").arg(kSuperHighFramerate), kSuperHighFramerate); + connect(mapper_framerate_, SIGNAL(mapped(int)), SLOT(ChangeFramerate(int))); + + context_menu_->addMenu(context_menu_framerate_); + context_menu_->addSeparator(); + + AddAnalyzerType(); + + connect(mapper_, SIGNAL(mapped(int)), SLOT(ChangeAnalyzer(int))); + disable_action_ = context_menu_->addAction(tr("No analyzer"), this, SLOT(DisableAnalyzer())); + disable_action_->setCheckable(true); + group_->addAction(disable_action_); + + context_menu_->addSeparator(); + // Visualisation action gets added in SetActions + + double_click_timer_->setSingleShot(true); + double_click_timer_->setInterval(250); + connect(double_click_timer_, SIGNAL(timeout()), SLOT(ShowPopupMenu())); + + Load(); +} + +void AnalyzerContainer::SetActions(QAction* visualisation) { + visualisation_action_ = visualisation; + context_menu_->addAction(visualisation_action_); +} + +void AnalyzerContainer::mouseReleaseEvent(QMouseEvent* e) { + if (e->button() == Qt::LeftButton) { + if (ignore_next_click_) { + ignore_next_click_ = false; + } + else { + // Might be the first click in a double click, so wait a while before + // actually doing anything + double_click_timer_->start(); + last_click_pos_ = e->globalPos(); + } + } + else if (e->button() == Qt::RightButton) { + context_menu_->popup(e->globalPos()); + } +} + +void AnalyzerContainer::ShowPopupMenu() { + context_menu_->popup(last_click_pos_); +} + +void AnalyzerContainer::mouseDoubleClickEvent(QMouseEvent*) { + double_click_timer_->stop(); + ignore_next_click_ = true; + + if (visualisation_action_) visualisation_action_->trigger(); +} + +void AnalyzerContainer::wheelEvent(QWheelEvent* e) { + emit WheelEvent(e->delta()); +} + +void AnalyzerContainer::SetEngine(EngineBase* engine) { + if (current_analyzer_) current_analyzer_->set_engine(engine); + engine_ = engine; +} + +void AnalyzerContainer::DisableAnalyzer() { + delete current_analyzer_; + current_analyzer_ = nullptr; + + Save(); +} + +void AnalyzerContainer::ChangeAnalyzer(int id) { + QObject* instance = analyzer_types_[id]->newInstance(Q_ARG(QWidget*, this)); + + if (!instance) { + qLog(Warning) << "Couldn't intialise a new" + << analyzer_types_[id]->className(); + return; + } + + delete current_analyzer_; + current_analyzer_ = qobject_cast(instance); + current_analyzer_->set_engine(engine_); + // Even if it is not supposed to happen, I don't want to get a dbz error + current_framerate_ = current_framerate_ == 0 ? kMediumFramerate : current_framerate_; + current_analyzer_->changeTimeout(1000 / current_framerate_); + + layout()->addWidget(current_analyzer_); + + Save(); +} + +void AnalyzerContainer::ChangeFramerate(int new_framerate) { + if (current_analyzer_) { + // Even if it is not supposed to happen, I don't want to get a dbz error + new_framerate = new_framerate == 0 ? kMediumFramerate : new_framerate; + current_analyzer_->changeTimeout(1000 / new_framerate); + + // notify the current analyzer that the framerate has changed + current_analyzer_->framerateChanged(); + } + SaveFramerate(new_framerate); +} + +void AnalyzerContainer::Load() { + QSettings s; + s.beginGroup(kSettingsGroup); + + // Analyzer + QString type = s.value("type", "BlockAnalyzer").toString(); + if (type.isEmpty()) { + DisableAnalyzer(); + disable_action_->setChecked(true); + } + else { + for (int i = 0; i < analyzer_types_.count(); ++i) { + if (type == analyzer_types_[i]->className()) { + ChangeAnalyzer(i); + actions_[i]->setChecked(true); + break; + } + } + } + + // Framerate + current_framerate_ = s.value(kSettingsFramerate, kMediumFramerate).toInt(); + for (int i = 0; i < framerate_list_.count(); ++i) { + if (current_framerate_ == framerate_list_[i]) { + ChangeFramerate(current_framerate_); + group_framerate_->actions()[i]->setChecked(true); + break; + } + } +} + +void AnalyzerContainer::SaveFramerate(int framerate) { + // For now, framerate is common for all analyzers. Maybe each analyzer should + // have its own framerate? + current_framerate_ = framerate; + QSettings s; + s.beginGroup(kSettingsGroup); + s.setValue(kSettingsFramerate, current_framerate_); +} + +void AnalyzerContainer::Save() { + QSettings s; + s.beginGroup(kSettingsGroup); + + s.setValue("type", current_analyzer_ ? current_analyzer_->metaObject()->className() : QVariant()); +} + +void AnalyzerContainer::AddFramerate(const QString& name, int framerate) { + QAction* action = context_menu_framerate_->addAction(name, mapper_framerate_, SLOT(map())); + mapper_framerate_->setMapping(action, framerate); + group_framerate_->addAction(action); + framerate_list_ << framerate; + action->setCheckable(true); +} diff --git a/src/analyzer/analyzercontainer.h b/src/analyzer/analyzercontainer.h new file mode 100644 index 00000000..c2ce73af --- /dev/null +++ b/src/analyzer/analyzercontainer.h @@ -0,0 +1,106 @@ +/* + Strawberry Music Player + This file was part of Clementine. + Copyright 2010, David Sansome + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#ifndef ANALYZERCONTAINER_H +#define ANALYZERCONTAINER_H + +#include +#include +#include + +#include "analyzerbase.h" +#include "engine/engine_fwd.h" + +class AnalyzerContainer : public QWidget { + Q_OBJECT + + public: + AnalyzerContainer(QWidget* parent); + + void SetEngine(EngineBase *engine); + void SetActions(QAction *visualisation); + + static const char *kSettingsGroup; + static const char *kSettingsFramerate; + +signals: + void WheelEvent(int delta); + + protected: + void mouseReleaseEvent(QMouseEvent*); + void mouseDoubleClickEvent(QMouseEvent*); + void wheelEvent(QWheelEvent *e); + + private slots: + void ChangeAnalyzer(int id); + void ChangeFramerate(int new_framerate); + void DisableAnalyzer(); + void ShowPopupMenu(); + + private: + static const int kLowFramerate; + static const int kMediumFramerate; + static const int kHighFramerate; + static const int kSuperHighFramerate; + + void Load(); + void Save(); + void SaveFramerate(int framerate); + template + void AddAnalyzerType(); + void AddFramerate(const QString& name, int framerate); + + private: + int current_framerate_; // fps + QMenu *context_menu_; + QMenu *context_menu_framerate_; + QActionGroup *group_; + QActionGroup *group_framerate_; + QSignalMapper *mapper_; + QSignalMapper *mapper_framerate_; + + QList analyzer_types_; + QList framerate_list_; + QList actions_; + QAction *disable_action_; + + QAction *visualisation_action_; + QTimer *double_click_timer_; + QPoint last_click_pos_; + bool ignore_next_click_; + + Analyzer::Base* current_analyzer_; + EngineBase *engine_; + +}; + +template +void AnalyzerContainer::AddAnalyzerType() { + int id = analyzer_types_.count(); + analyzer_types_ << &T::staticMetaObject; + + QAction *action = context_menu_->addAction(tr(T::kName), mapper_, SLOT(map())); + group_->addAction(action); + mapper_->setMapping(action, id); + action->setCheckable(true); + actions_ << action; +} + +#endif + diff --git a/src/analyzer/blockanalyzer.cpp b/src/analyzer/blockanalyzer.cpp new file mode 100644 index 00000000..80294d54 --- /dev/null +++ b/src/analyzer/blockanalyzer.cpp @@ -0,0 +1,417 @@ +// Author: Max Howell , (C) 2003-5 +// Mark Kretschmann , (C) 2005 +// Copyright: See COPYING file that comes with this distribution +// + +#include "blockanalyzer.h" + +#include + +#include +#include +#include +#include + +const uint BlockAnalyzer::HEIGHT = 2; +const uint BlockAnalyzer::WIDTH = 4; +const uint BlockAnalyzer::MIN_ROWS = 3; // arbituary +const uint BlockAnalyzer::MIN_COLUMNS = 32; // arbituary +const uint BlockAnalyzer::MAX_COLUMNS = 256; // must be 2**n +const uint BlockAnalyzer::FADE_SIZE = 90; + +const char* BlockAnalyzer::kName = + QT_TRANSLATE_NOOP("AnalyzerContainer", "Block analyzer"); + +BlockAnalyzer::BlockAnalyzer(QWidget* parent) + : Analyzer::Base(parent, 9), + m_columns(0) // uint + , + m_rows(0) // uint + , + m_y(0) // uint + , + m_barPixmap(1, 1) // null qpixmaps cause crashes + , + m_topBarPixmap(WIDTH, HEIGHT), + m_scope(MIN_COLUMNS) // Scope + , + m_store(1 << 8, 0) // vector + , + m_fade_bars(FADE_SIZE) // vector + , + m_fade_pos(1 << 8, 50) // vector + , + m_fade_intensity(1 << 8, 32) // vector +{ + setMinimumSize(MIN_COLUMNS * (WIDTH + 1) - 1, + MIN_ROWS * (HEIGHT + 1) - + 1); //-1 is padding, no drawing takes place there + setMaximumWidth(MAX_COLUMNS * (WIDTH + 1) - 1); + + // mxcl says null pixmaps cause crashes, so let's play it safe + for (uint i = 0; i < FADE_SIZE; ++i) m_fade_bars[i] = QPixmap(1, 1); +} + +BlockAnalyzer::~BlockAnalyzer() {} + +void BlockAnalyzer::resizeEvent(QResizeEvent* e) { + QWidget::resizeEvent(e); + + m_background = QPixmap(size()); + canvas_ = QPixmap(size()); + + const uint oldRows = m_rows; + + // all is explained in analyze().. + //+1 to counter -1 in maxSizes, trust me we need this! + m_columns = qMax(uint(double(width() + 1) / (WIDTH + 1)), MAX_COLUMNS); + m_rows = uint(double(height() + 1) / (HEIGHT + 1)); + + // this is the y-offset for drawing from the top of the widget + m_y = (height() - (m_rows * (HEIGHT + 1)) + 2) / 2; + + m_scope.resize(m_columns); + + if (m_rows != oldRows) { + m_barPixmap = QPixmap(WIDTH, m_rows * (HEIGHT + 1)); + + for (uint i = 0; i < FADE_SIZE; ++i) + m_fade_bars[i] = QPixmap(WIDTH, m_rows * (HEIGHT + 1)); + + m_yscale.resize(m_rows + 1); + + const uint PRE = 1, + PRO = 1; // PRE and PRO allow us to restrict the range somewhat + + for (uint z = 0; z < m_rows; ++z) + m_yscale[z] = 1 - (log10(PRE + z) / log10(PRE + m_rows + PRO)); + + m_yscale[m_rows] = 0; + + determineStep(); + paletteChange(palette()); + } + + drawBackground(); +} + +void BlockAnalyzer::determineStep() { + // falltime is dependent on rowcount due to our digital resolution (ie we have + // boxes/blocks of pixels) + // I calculated the value 30 based on some trial and error + + // the fall time of 30 is too slow on framerates above 50fps + const double fallTime = timeout() < 20 ? 20 * m_rows : 30 * m_rows; + + m_step = double(m_rows * timeout()) / fallTime; +} + +void BlockAnalyzer::framerateChanged() { // virtual + determineStep(); +} + +void BlockAnalyzer::transform(Analyzer::Scope& s) // pure virtual +{ + for (uint x = 0; x < s.size(); ++x) s[x] *= 2; + + float* front = static_cast(&s.front()); + + m_fht->spectrum(front); + m_fht->scale(front, 1.0 / 20); + + // the second half is pretty dull, so only show it if the user has a large + // analyzer + // by setting to m_scope.size() if large we prevent interpolation of large + // analyzers, this is good! + s.resize(m_scope.size() <= MAX_COLUMNS / 2 ? MAX_COLUMNS / 2 : m_scope.size()); +} + +void BlockAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s, + bool new_frame) { + // y = 2 3 2 1 0 2 + // . . . . # . + // . . . # # . + // # . # # # # + // # # # # # # + // + // visual aid for how this analyzer works. + // y represents the number of blanks + // y starts from the top and increases in units of blocks + + // m_yscale looks similar to: { 0.7, 0.5, 0.25, 0.15, 0.1, 0 } + // if it contains 6 elements there are 5 rows in the analyzer + + if (!new_frame) { + p.drawPixmap(0, 0, canvas_); + return; + } + + QPainter canvas_painter(&canvas_); + + Analyzer::interpolate(s, m_scope); + + // Paint the background + canvas_painter.drawPixmap(0, 0, m_background); + + for (uint y, x = 0; x < m_scope.size(); ++x) { + // determine y + for (y = 0; m_scope[x] < m_yscale[y]; ++y) + ; + + // this is opposite to what you'd think, higher than y + // means the bar is lower than y (physically) + if ((float)y > m_store[x]) + y = int(m_store[x] += m_step); + else + m_store[x] = y; + + // if y is lower than m_fade_pos, then the bar has exceeded the height of + // the fadeout + // if the fadeout is quite faded now, then display the new one + if (y <= m_fade_pos[x] /*|| m_fade_intensity[x] < FADE_SIZE / 3*/) { + m_fade_pos[x] = y; + m_fade_intensity[x] = FADE_SIZE; + } + + if (m_fade_intensity[x] > 0) { + const uint offset = --m_fade_intensity[x]; + const uint y = m_y + (m_fade_pos[x] * (HEIGHT + 1)); + canvas_painter.drawPixmap(x * (WIDTH + 1), y, m_fade_bars[offset], 0, 0, WIDTH, height() - y); + } + + if (m_fade_intensity[x] == 0) m_fade_pos[x] = m_rows; + + // REMEMBER: y is a number from 0 to m_rows, 0 means all blocks are glowing, + // m_rows means none are + canvas_painter.drawPixmap(x * (WIDTH + 1), y * (HEIGHT + 1) + m_y, *bar(), + 0, y * (HEIGHT + 1), bar()->width(), + bar()->height()); + } + + for (uint x = 0; x < m_store.size(); ++x) + canvas_painter.drawPixmap(x * (WIDTH + 1), int(m_store[x]) * (HEIGHT + 1) + m_y, m_topBarPixmap); + + p.drawPixmap(0, 0, canvas_); +} + +static inline void adjustToLimits(int& b, int& f, uint& amount) { + // with a range of 0-255 and maximum adjustment of amount, + // maximise the difference between f and b + + if (b < f) { + if (b > 255 - f) { + amount -= f; + f = 0; + } + else { + amount -= (255 - f); + f = 255; + } + } + else { + if (f > 255 - b) { + amount -= f; + f = 0; + } + else { + amount -= (255 - f); + f = 255; + } + } +} + +/** + * Clever contrast function + * + * It will try to adjust the foreground color such that it contrasts well with + *the background + * It won't modify the hue of fg unless absolutely necessary + * @return the adjusted form of fg + */ +QColor ensureContrast(const QColor& bg, const QColor& fg, uint _amount = 150) { + class OutputOnExit { + public: + OutputOnExit(const QColor& color) : c(color) {} + ~OutputOnExit() { + int h, s, v; + c.getHsv(&h, &s, &v); + } + + private: + const QColor& c; + }; + +// hack so I don't have to cast everywhere +#define amount static_cast(_amount) + // #define STAMP debug() << (QValueList() << fh << fs << fv) << endl; + // #define STAMP1( string ) debug() << string << ": " << + // (QValueList() << fh << fs << fv) << endl; + // #define STAMP2( string, value ) debug() << string << "=" << value << ": + // " << (QValueList() << fh << fs << fv) << endl; + + OutputOnExit allocateOnTheStack(fg); + + int bh, bs, bv; + int fh, fs, fv; + + bg.getHsv(&bh, &bs, &bv); + fg.getHsv(&fh, &fs, &fv); + + int dv = abs(bv - fv); + + // STAMP2( "DV", dv ); + + // value is the best measure of contrast + // if there is enough difference in value already, return fg unchanged + if (dv > amount) return fg; + + int ds = abs(bs - fs); + + // STAMP2( "DS", ds ); + + // saturation is good enough too. But not as good. TODO adapt this a little + if (ds > amount) return fg; + + int dh = abs(bh - fh); + + // STAMP2( "DH", dh ); + + if (dh > 120) { + // a third of the colour wheel automatically guarentees contrast + // but only if the values are high enough and saturations significant enough + // to allow the colours to be visible and not be shades of grey or black + + // check the saturation for the two colours is sufficient that hue alone can + // provide sufficient contrast + if (ds > amount / 2 && (bs > 125 && fs > 125)) + // STAMP1( "Sufficient saturation difference, and hues are + // compliemtary" ); + return fg; + else if (dv > amount / 2 && (bv > 125 && fv > 125)) + // STAMP1( "Sufficient value difference, and hues are + // compliemtary" ); + return fg; + + // STAMP1( "Hues are complimentary but we must modify the value or + // saturation of the contrasting colour" ); + + // but either the colours are two desaturated, or too dark + // so we need to adjust the system, although not as much + ///_amount /= 2; + } + + if (fs < 50 && ds < 40) { + // low saturation on a low saturation is sad + const int tmp = 50 - fs; + fs = 50; + if (amount > tmp) + _amount -= tmp; + else + _amount = 0; + } + + // test that there is available value to honor our contrast requirement + if (255 - dv < amount) { + // we have to modify the value and saturation of fg + // adjustToLimits( bv, fv, amount ); + + // STAMP + + // see if we need to adjust the saturation + if (amount > 0) adjustToLimits(bs, fs, _amount); + + // STAMP + + // see if we need to adjust the hue + if (amount > 0) fh += amount; // cycles around; + + // STAMP + + return QColor::fromHsv(fh, fs, fv); + } + + // STAMP + + if (fv > bv && bv > amount) return QColor::fromHsv(fh, fs, bv - amount); + + // STAMP + + if (fv < bv && fv > amount) return QColor::fromHsv(fh, fs, fv - amount); + + // STAMP + + if (fv > bv && (255 - fv > amount)) + return QColor::fromHsv(fh, fs, fv + amount); + + // STAMP + + if (fv < bv && (255 - bv > amount)) + return QColor::fromHsv(fh, fs, bv + amount); + + // STAMP + // debug() << "Something went wrong!\n"; + + return Qt::blue; + +#undef amount + // #undef STAMP +} + +void BlockAnalyzer::paletteChange(const QPalette&) // virtual +{ + const QColor bg = palette().color(QPalette::Background); + const QColor fg = ensureContrast(bg, palette().color(QPalette::Highlight)); + + m_topBarPixmap.fill(fg); + + const double dr = 15 * double(bg.red() - fg.red()) / (m_rows * 16); + const double dg = 15 * double(bg.green() - fg.green()) / (m_rows * 16); + const double db = 15 * double(bg.blue() - fg.blue()) / (m_rows * 16); + const int r = fg.red(), g = fg.green(), b = fg.blue(); + + bar()->fill(bg); + + QPainter p(bar()); + for (int y = 0; (uint)y < m_rows; ++y) + // graduate the fg color + p.fillRect(0, y * (HEIGHT + 1), WIDTH, HEIGHT, QColor(r + int(dr * y), g + int(dg * y), b + int(db * y))); + + { + const QColor bg = palette().color(QPalette::Background).dark(112); + + // make a complimentary fadebar colour + // TODO dark is not always correct, dumbo! + int h, s, v; + palette().color(QPalette::Background).dark(150).getHsv(&h, &s, &v); + const QColor fg(QColor::fromHsv(h + 120, s, v)); + + const double dr = fg.red() - bg.red(); + const double dg = fg.green() - bg.green(); + const double db = fg.blue() - bg.blue(); + const int r = bg.red(), g = bg.green(), b = bg.blue(); + + // Precalculate all fade-bar pixmaps + for (uint y = 0; y < FADE_SIZE; ++y) { + m_fade_bars[y].fill(palette().color(QPalette::Background)); + QPainter f(&m_fade_bars[y]); + for (int z = 0; (uint)z < m_rows; ++z) { + const double Y = 1.0 - (log10(FADE_SIZE - y) / log10(FADE_SIZE)); + f.fillRect(0, z * (HEIGHT + 1), WIDTH, HEIGHT, QColor(r + int(dr * Y), g + int(dg * Y), b + int(db * Y))); + } + } + } + + drawBackground(); +} + +void BlockAnalyzer::drawBackground() { + const QColor bg = palette().color(QPalette::Background); + const QColor bgdark = bg.dark(112); + + m_background.fill(bg); + + QPainter p(&m_background); + for (int x = 0; (uint)x < m_columns; ++x) + for (int y = 0; (uint)y < m_rows; ++y) + p.fillRect(x * (WIDTH + 1), y * (HEIGHT + 1) + m_y, WIDTH, HEIGHT, bgdark); +} diff --git a/src/analyzer/blockanalyzer.h b/src/analyzer/blockanalyzer.h new file mode 100644 index 00000000..eeaf8cf2 --- /dev/null +++ b/src/analyzer/blockanalyzer.h @@ -0,0 +1,65 @@ +// Maintainer: Max Howell , (C) 2003-5 +// Copyright: See COPYING file that comes with this distribution +// + +#ifndef BLOCKANALYZER_H +#define BLOCKANALYZER_H + +#include "analyzerbase.h" +#include + +class QResizeEvent; +class QMouseEvent; +class QPalette; + +/** + * @author Max Howell + */ + +class BlockAnalyzer : public Analyzer::Base { + Q_OBJECT + public: + Q_INVOKABLE BlockAnalyzer(QWidget*); + ~BlockAnalyzer(); + + static const uint HEIGHT; + static const uint WIDTH; + static const uint MIN_ROWS; + static const uint MIN_COLUMNS; + static const uint MAX_COLUMNS; + static const uint FADE_SIZE; + + static const char *kName; + + protected: + virtual void transform(Analyzer::Scope&); + virtual void analyze(QPainter &p, const Analyzer::Scope&, bool new_frame); + virtual void resizeEvent(QResizeEvent*); + virtual void paletteChange(const QPalette&); + virtual void framerateChanged(); + + void drawBackground(); + void determineStep(); + + private: + QPixmap* bar() { return &m_barPixmap; } + + uint m_columns, m_rows; // number of rows and columns of blocks + uint m_y; // y-offset from top of widget + QPixmap m_barPixmap; + QPixmap m_topBarPixmap; + QPixmap m_background; + QPixmap canvas_; + Analyzer::Scope m_scope; // so we don't create a vector every frame + std::vector m_store; // current bar heights + std::vector m_yscale; + + // FIXME why can't I namespace these? c++ issue? + std::vector m_fade_bars; + std::vector m_fade_pos; + std::vector m_fade_intensity; + + float m_step; // rows to fall per frame +}; + +#endif diff --git a/src/analyzer/fht.cpp b/src/analyzer/fht.cpp new file mode 100644 index 00000000..9d88631c --- /dev/null +++ b/src/analyzer/fht.cpp @@ -0,0 +1,203 @@ +// FHT - Fast Hartley Transform Class +// +// Copyright (C) 2004 Melchior FRANZ - mfranz@kde.org +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA +// +// $Id$ + +#include +#include +#include "fht.h" + +FHT::FHT(int n) : m_buf(0), m_tab(0), m_log(0) { + if (n < 3) { + m_num = 0; + m_exp2 = -1; + return; + } + m_exp2 = n; + m_num = 1 << n; + if (n > 3) { + m_buf = new float[m_num]; + m_tab = new float[m_num * 2]; + makeCasTable(); + } +} + +FHT::~FHT() { + delete[] m_buf; + delete[] m_tab; + delete[] m_log; +} + +void FHT::makeCasTable(void) { + float d, *costab, *sintab; + int ul, ndiv2 = m_num / 2; + + for (costab = m_tab, sintab = m_tab + m_num / 2 + 1, ul = 0; ul < m_num; ul++) { + d = M_PI * ul / ndiv2; + *costab = *sintab = cos(d); + + costab += 2, sintab += 2; + if (sintab > m_tab + m_num * 2) sintab = m_tab + 1; + } +} + +float* FHT::copy(float* d, float* s) { + return (float*)memcpy(d, s, m_num * sizeof(float)); +} + +float* FHT::clear(float* d) { + return (float*)memset(d, 0, m_num * sizeof(float)); +} + +void FHT::scale(float* p, float d) { + for (int i = 0; i < (m_num / 2); i++) *p++ *= d; +} + +void FHT::ewma(float* d, float* s, float w) { + for (int i = 0; i < (m_num / 2); i++, d++, s++) *d = *d * w + *s * (1 - w); +} + +void FHT::logSpectrum(float* out, float* p) { + int n = m_num / 2, i, j, k, *r; + if (!m_log) { + m_log = new int[n]; + float f = n / log10((double)n); + for (i = 0, r = m_log; i < n; i++, r++) { + j = int(rint(log10(i + 1.0) * f)); + *r = j >= n ? n - 1 : j; + } + } + semiLogSpectrum(p); + *out++ = *p = *p / 100; + for (k = i = 1, r = m_log; i < n; i++) { + j = *r++; + if (i == j) + *out++ = p[i]; + else { + float base = p[k - 1]; + float step = (p[j] - base) / (j - (k - 1)); + for (float corr = 0; k <= j; k++, corr += step) *out++ = base + corr; + } + } +} + +void FHT::semiLogSpectrum(float* p) { + float e; + power2(p); + for (int i = 0; i < (m_num / 2); i++, p++) { + e = 10.0 * log10(sqrt(*p * .5)); + *p = e < 0 ? 0 : e; + } +} + +void FHT::spectrum(float* p) { + power2(p); + for (int i = 0; i < (m_num / 2); i++, p++) *p = (float)sqrt(*p * .5); +} + +void FHT::power(float* p) { + power2(p); + for (int i = 0; i < (m_num / 2); i++) *p++ *= .5; +} + +void FHT::power2(float* p) { + int i; + float* q; + _transform(p, m_num, 0); + + *p = (*p * *p), *p += *p, p++; + + for (i = 1, q = p + m_num - 2; i < (m_num / 2); i++, --q) + *p = (*p * *p) + (*q * *q), p++; +} + +void FHT::transform(float* p) { + if (m_num == 8) + transform8(p); + else + _transform(p, m_num, 0); +} + +void FHT::transform8(float* p) { + float a, b, c, d, e, f, g, h, b_f2, d_h2; + float a_c_eg, a_ce_g, ac_e_g, aceg, b_df_h, bdfh; + + a = *p++, b = *p++, c = *p++, d = *p++; + e = *p++, f = *p++, g = *p++, h = *p; + b_f2 = (b - f) * M_SQRT2; + d_h2 = (d - h) * M_SQRT2; + + a_c_eg = a - c - e + g; + a_ce_g = a - c + e - g; + ac_e_g = a + c - e - g; + aceg = a + c + e + g; + + b_df_h = b - d + f - h; + bdfh = b + d + f + h; + + *p = a_c_eg - d_h2; + *--p = a_ce_g - b_df_h; + *--p = ac_e_g - b_f2; + *--p = aceg - bdfh; + *--p = a_c_eg + d_h2; + *--p = a_ce_g + b_df_h; + *--p = ac_e_g + b_f2; + *--p = aceg + bdfh; +} + +void FHT::_transform(float* p, int n, int k) { + + if (n == 8) { + transform8(p + k); + return; + } + + int i, j, ndiv2 = n / 2; + float a, *t1, *t2, *t3, *t4, *ptab, *pp; + + for (i = 0, t1 = m_buf, t2 = m_buf + ndiv2, pp = &p[k]; i < ndiv2; i++) + *t1++ = *pp++, *t2++ = *pp++; + + memcpy(p + k, m_buf, sizeof(float) * n); + + _transform(p, ndiv2, k); + _transform(p, ndiv2, k + ndiv2); + + j = m_num / ndiv2 - 1; + t1 = m_buf; + t2 = t1 + ndiv2; + t3 = p + k + ndiv2; + ptab = m_tab; + pp = p + k; + + a = *ptab++ * *t3++; + a += *ptab * *pp; + ptab += j; + + *t1++ = *pp + a; + *t2++ = *pp++ - a; + + for (i = 1, t4 = p + k + n; i < ndiv2; i++, ptab += j) { + a = *ptab++ * *t3++; + a += *ptab * *--t4; + + *t1++ = *pp + a; + *t2++ = *pp++ - a; + } + memcpy(p + k, m_buf, sizeof(float) * n); +} diff --git a/src/analyzer/fht.h b/src/analyzer/fht.h new file mode 100644 index 00000000..73c5daa6 --- /dev/null +++ b/src/analyzer/fht.h @@ -0,0 +1,118 @@ +// FHT - Fast Hartley Transform Class +// +// Copyright (C) 2004 Melchior FRANZ - mfranz@kde.org +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA +// +// $Id$ + +#ifndef FHT_H +#define FHT_H + +/** + * Implementation of the Hartley Transform after Bracewell's discrete + * algorithm. The algorithm is subject to US patent No. 4,646,256 (1987) + * but was put into public domain by the Board of Trustees of Stanford + * University in 1994 and is now freely available[1]. + * + * [1] Computer in Physics, Vol. 9, No. 4, Jul/Aug 1995 pp 373-379 + */ +class FHT { + int m_exp2; + int m_num; + float* m_buf; + float* m_tab; + int* m_log; + + /** + * Create a table of "cas" (cosine and sine) values. + * Has only to be done in the constructor and saves from + * calculating the same values over and over while transforming. + */ + void makeCasTable(); + + /** + * Recursive in-place Hartley transform. For internal use only! + */ + void _transform(float*, int, int); + + public: + /** + * Prepare transform for data sets with @f$2^n@f$ numbers, whereby @f$n@f$ + * should be at least 3. Values of more than 3 need a trigonometry table. + * @see makeCasTable() + */ + FHT(int); + + ~FHT(); + inline int sizeExp() const { return m_exp2; } + inline int size() const { return m_num; } + float* copy(float*, float*); + float* clear(float*); + void scale(float*, float); + + /** + * Exponentially Weighted Moving Average (EWMA) filter. + * @param d is the filtered data. + * @param s is fresh input. + * @param w is the weighting factor. + */ + void ewma(float* d, float* s, float w); + + /** + * Logarithmic audio spectrum. Maps semi-logarithmic spectrum + * to logarithmic frequency scale, interpolates missing values. + * A logarithmic index map is calculated at the first run only. + * @param p is the input array. + * @param out is the spectrum. + */ + void logSpectrum(float* out, float* p); + + /** + * Semi-logarithmic audio spectrum. + */ + void semiLogSpectrum(float*); + + /** + * Fourier spectrum. + */ + void spectrum(float*); + + /** + * Calculates a mathematically correct FFT power spectrum. + * If further scaling is applied later, use power2 instead + * and factor the 0.5 in the final scaling factor. + * @see FHT::power2() + */ + void power(float*); + + /** + * Calculates an FFT power spectrum with doubled values as a + * result. The values need to be multiplied by 0.5 to be exact. + * Note that you only get @f$2^{n-1}@f$ power values for a data set + * of @f$2^n@f$ input values. This is the fastest transform. + * @see FHT::power() + */ + void power2(float*); + + /** + * Discrete Hartley transform of data sets with 8 values. + */ + void transform8(float*); + + void transform(float*); +}; + +#endif diff --git a/src/cmakelists-check.sh b/src/cmakelists-check.sh new file mode 100755 index 00000000..de2354f5 --- /dev/null +++ b/src/cmakelists-check.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +for f in `find .` +do + file=$(basename $f) + grep -i $file CMakeLists.txt >/dev/null 2>&1 + #echo $? + if [ $? -eq 0 ]; then + continue + fi + echo "$file not in CMakeLists.txt" +done diff --git a/src/collection/collection.cpp b/src/collection/collection.cpp new file mode 100644 index 00000000..c408fc47 --- /dev/null +++ b/src/collection/collection.cpp @@ -0,0 +1,155 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include "collection.h" + +#include "collectionmodel.h" +#include "collectionbackend.h" +#include "core/application.h" +#include "core/database.h" +#include "core/player.h" +#include "core/tagreaderclient.h" +#include "core/taskmanager.h" +#include "core/thread.h" +#include "core/logging.h" + +const char *Collection::kSongsTable = "songs"; +const char *Collection::kDirsTable = "directories"; +const char *Collection::kSubdirsTable = "subdirectories"; +const char *Collection::kFtsTable = "songs_fts"; + +Collection::Collection(Application *app, QObject *parent) + : QObject(parent), + app_(app), + backend_(nullptr), + model_(nullptr), + watcher_(nullptr), + watcher_thread_(nullptr) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + backend_ = new CollectionBackend; + backend()->moveToThread(app->database()->thread()); + + backend_->Init(app->database(), kSongsTable, kDirsTable, kSubdirsTable, kFtsTable); + + model_ = new CollectionModel(backend_, app_, this); + + + ReloadSettings(); + +} + +Collection::~Collection() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + watcher_->deleteLater(); + watcher_thread_->exit(); + watcher_thread_->wait(5000 /* five seconds */); +} + +void Collection::Init() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + watcher_ = new CollectionWatcher; + watcher_thread_ = new Thread(this); + watcher_thread_->SetIoPriority(Utilities::IOPRIO_CLASS_IDLE); + + watcher_->moveToThread(watcher_thread_); + watcher_thread_->start(QThread::IdlePriority); + + watcher_->set_backend(backend_); + watcher_->set_task_manager(app_->task_manager()); + + connect(backend_, SIGNAL(DirectoryDiscovered(Directory, SubdirectoryList)), watcher_, SLOT(AddDirectory(Directory, SubdirectoryList))); + connect(backend_, SIGNAL(DirectoryDeleted(Directory)), watcher_, SLOT(RemoveDirectory(Directory))); + connect(watcher_, SIGNAL(NewOrUpdatedSongs(SongList)), backend_, SLOT(AddOrUpdateSongs(SongList))); + connect(watcher_, SIGNAL(SongsMTimeUpdated(SongList)), backend_, SLOT(UpdateMTimesOnly(SongList))); + connect(watcher_, SIGNAL(SongsDeleted(SongList)), backend_, SLOT(MarkSongsUnavailable(SongList))); + connect(watcher_, SIGNAL(SongsReadded(SongList, bool)), backend_, SLOT(MarkSongsUnavailable(SongList, bool))); + connect(watcher_, SIGNAL(SubdirsDiscovered(SubdirectoryList)), backend_, SLOT(AddOrUpdateSubdirs(SubdirectoryList))); + connect(watcher_, SIGNAL(SubdirsMTimeUpdated(SubdirectoryList)), backend_, SLOT(AddOrUpdateSubdirs(SubdirectoryList))); + connect(watcher_, SIGNAL(CompilationsNeedUpdating()), backend_, SLOT(UpdateCompilations())); + connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(CurrentSongChanged(Song))); + connect(app_->player(), SIGNAL(Stopped()), SLOT(Stopped())); + + // This will start the watcher checking for updates + backend_->LoadDirectoriesAsync(); +} + +void Collection::IncrementalScan() { watcher_->IncrementalScanAsync(); } + +void Collection::FullScan() { watcher_->FullScanAsync(); } + +void Collection::PauseWatcher() { watcher_->SetRescanPausedAsync(true); } + +void Collection::ResumeWatcher() { watcher_->SetRescanPausedAsync(false); } + +void Collection::ReloadSettings() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + watcher_->ReloadSettingsAsync(); + +} + +void Collection::Stopped() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + CurrentSongChanged(Song()); +} + +void Collection::CurrentSongChanged(const Song &song) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + TagReaderReply *reply = nullptr; + + if (reply) { + connect(reply, SIGNAL(Finished(bool)), reply, SLOT(deleteLater())); + } + + if (song.filetype() == Song::Type_Asf) { + current_wma_song_url_ = song.url(); + } +} + +SongList Collection::FilterCurrentWMASong(SongList songs, Song* queued) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + for (SongList::iterator it = songs.begin(); it != songs.end(); ) { + if (it->url() == current_wma_song_url_) { + *queued = *it; + it = songs.erase(it); + } + else { + ++it; + } + } + return songs; +} diff --git a/src/collection/collection.h b/src/collection/collection.h new file mode 100644 index 00000000..619f35c6 --- /dev/null +++ b/src/collection/collection.h @@ -0,0 +1,98 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COLLECTION_H +#define COLLECTION_H + +#include "config.h" + +#include +#include +#include + +#include "core/song.h" + +class Application; +class Database; +class CollectionBackend; +class CollectionModel; +class CollectionWatcher; +class TaskManager; +class Thread; + +class Collection : public QObject { + Q_OBJECT + + public: + Collection(Application* app, QObject* parent); + ~Collection(); + + static const char *kSongsTable; + static const char *kDirsTable; + static const char *kSubdirsTable; + static const char *kFtsTable; + + void Init(); + + CollectionBackend *backend() const { return backend_; } + CollectionModel *model() const { return model_; } + + QString full_rescan_reason(int schema_version) const { return full_rescan_revisions_.value(schema_version, QString()); } + + int Total_Albums = 0; + int total_songs_ = 0; + int Total_Artists = 0; + + public slots: + void ReloadSettings(); + + void PauseWatcher(); + void ResumeWatcher(); + + void FullScan(); + + private slots: + void IncrementalScan(); + + void CurrentSongChanged(const Song &song); + void Stopped(); + + private: + SongList FilterCurrentWMASong(SongList songs, Song* queued); + + private: + Application *app_; + CollectionBackend *backend_; + CollectionModel *model_; + + CollectionWatcher *watcher_; + Thread *watcher_thread_; + + // Hack: Gstreamer doesn't cope well with WMA files being rewritten while + // being played, so we delay statistics and rating changes until the current + // song has finished playing. + QUrl current_wma_song_url_; + + // DB schema versions which should trigger a full collection rescan (each of + // those with a short reason why). + QHash full_rescan_revisions_; +}; + +#endif diff --git a/src/collection/collectionbackend.cpp b/src/collection/collectionbackend.cpp new file mode 100644 index 00000000..8b8d0b78 --- /dev/null +++ b/src/collection/collectionbackend.cpp @@ -0,0 +1,1132 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "collectionbackend.h" +#include "collectionquery.h" +#include "sqlrow.h" +#include "core/application.h" +#include "core/database.h" +#include "core/scopedtransaction.h" +#include "core/tagreaderclient.h" +#include "core/utilities.h" + +const char *CollectionBackend::kSettingsGroup = "Collection"; + +CollectionBackend::CollectionBackend(QObject *parent) + : CollectionBackendInterface(parent) + {} + +void CollectionBackend::Init(Database *db, const QString &songs_table, const QString &dirs_table, const QString &subdirs_table, const QString &fts_table) { + + db_ = db; + songs_table_ = songs_table; + dirs_table_ = dirs_table; + subdirs_table_ = subdirs_table; + fts_table_ = fts_table; +} + +void CollectionBackend::LoadDirectoriesAsync() { + metaObject()->invokeMethod(this, "LoadDirectories", Qt::QueuedConnection); +} + +void CollectionBackend::UpdateTotalSongCountAsync() { + metaObject()->invokeMethod(this, "UpdateTotalSongCount", Qt::QueuedConnection); +} + +void CollectionBackend::UpdateTotalArtistCountAsync() { + metaObject()->invokeMethod(this, "UpdateTotalArtistCount", Qt::QueuedConnection); +} + +void CollectionBackend::UpdateTotalAlbumCountAsync() { + metaObject()->invokeMethod(this, "UpdateTotalAlbumCount", Qt::QueuedConnection); +} + +void CollectionBackend::IncrementPlayCountAsync(int id) { + metaObject()->invokeMethod(this, "IncrementPlayCount", Qt::QueuedConnection, Q_ARG(int, id)); +} + +void CollectionBackend::IncrementSkipCountAsync(int id, float progress) { + metaObject()->invokeMethod(this, "IncrementSkipCount", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(float, progress)); +} + +void CollectionBackend::ResetStatisticsAsync(int id) { + metaObject()->invokeMethod(this, "ResetStatistics", Qt::QueuedConnection, Q_ARG(int, id)); +} + +void CollectionBackend::LoadDirectories() { + + DirectoryList dirs = GetAllDirectories(); + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + for (const Directory &dir : dirs) { + emit DirectoryDiscovered(dir, SubdirsInDirectory(dir.id, db)); + } + +} + +void CollectionBackend::ChangeDirPath(int id, const QString &old_path, const QString &new_path) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + ScopedTransaction t(&db); + + // Do the dirs table + QSqlQuery q(db); + q.prepare(QString("UPDATE %1 SET path=:path WHERE ROWID=:id").arg(dirs_table_)); + q.bindValue(":path", new_path); + q.bindValue(":id", id); + q.exec(); + if (db_->CheckErrors(q)) return; + + const QByteArray old_url = QUrl::fromLocalFile(old_path).toEncoded(); + const QByteArray new_url = QUrl::fromLocalFile(new_path).toEncoded(); + + const int path_len = old_url.length(); + + // Do the subdirs table + q = QSqlQuery(db); + q.prepare(QString("UPDATE %1 SET path=:path || substr(path, %2) WHERE directory=:id").arg(subdirs_table_).arg(path_len)); + q.bindValue(":path", new_url); + q.bindValue(":id", id); + q.exec(); + if (db_->CheckErrors(q)) return; + + // Do the songs table + q = QSqlQuery(db); + q.prepare(QString("UPDATE %1 SET filename=:path || substr(filename, %2) WHERE directory=:id").arg(songs_table_).arg(path_len)); + q.bindValue(":path", new_url); + q.bindValue(":id", id); + q.exec(); + if (db_->CheckErrors(q)) return; + + t.Commit(); + +} + +DirectoryList CollectionBackend::GetAllDirectories() { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + DirectoryList ret; + + QSqlQuery q(db); + q.prepare(QString("SELECT ROWID, path FROM %1").arg(dirs_table_)); + q.exec(); + if (db_->CheckErrors(q)) return ret; + + while (q.next()) { + Directory dir; + dir.id = q.value(0).toInt(); + dir.path = q.value(1).toString(); + + ret << dir; + } + return ret; + +} + +SubdirectoryList CollectionBackend::SubdirsInDirectory(int id) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db = db_->Connect(); + return SubdirsInDirectory(id, db); + +} + +SubdirectoryList CollectionBackend::SubdirsInDirectory(int id, QSqlDatabase &db) { + + QSqlQuery q(db); + q.prepare(QString("SELECT path, mtime FROM %1 WHERE directory_id = :dir").arg(subdirs_table_)); + q.bindValue(":dir", id); + q.exec(); + if (db_->CheckErrors(q)) return SubdirectoryList(); + + SubdirectoryList subdirs; + while (q.next()) { + Subdirectory subdir; + subdir.directory_id = id; + subdir.path = q.value(0).toString(); + subdir.mtime = q.value(1).toUInt(); + subdirs << subdir; + } + + return subdirs; + +} + +void CollectionBackend::UpdateTotalSongCount() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + QSqlQuery q(db); + q.prepare(QString("SELECT COUNT(*) FROM %1 WHERE unavailable = 0").arg(songs_table_)); + q.exec(); + if (db_->CheckErrors(q)) return; + if (!q.next()) return; + + emit TotalSongCountUpdated(q.value(0).toInt()); + +} + +void CollectionBackend::UpdateTotalArtistCount() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + QSqlQuery q(db); + q.prepare(QString("select COUNT(distinct artist) from %1 WHERE unavailable = 0").arg(songs_table_)); + q.exec(); + if (db_->CheckErrors(q)) return; + if (!q.next()) return; + + //qLog(Debug) << "TotalArtist: " << q.value(0).toInt(); + + emit TotalArtistCountUpdated(q.value(0).toInt()); + +} + +void CollectionBackend::UpdateTotalAlbumCount() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + QSqlQuery q(db); + q.prepare(QString("select COUNT(distinct album) from %1 WHERE unavailable = 0").arg(songs_table_)); + q.exec(); + if (db_->CheckErrors(q)) return; + if (!q.next()) return; + + //qLog(Debug) << "TotalAlbum: " << q.value(0).toInt(); + + emit TotalAlbumCountUpdated(q.value(0).toInt()); + +} + +void CollectionBackend::AddDirectory(const QString &path) { + + QString canonical_path = QFileInfo(path).canonicalFilePath(); + QString db_path = canonical_path; + + if (Application::kIsPortable && Utilities::UrlOnSameDriveAsStrawberry(QUrl::fromLocalFile(canonical_path))) { + + db_path = Utilities::GetRelativePathToStrawberryBin(QUrl::fromLocalFile(db_path)).toLocalFile(); + qLog(Debug) << "db_path" << db_path; + } + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + QSqlQuery q(db); + q.prepare(QString("INSERT INTO %1 (path, subdirs) VALUES (:path, 1)").arg(dirs_table_)); + q.bindValue(":path", db_path); + q.exec(); + if (db_->CheckErrors(q)) return; + + Directory dir; + dir.path = canonical_path; + dir.id = q.lastInsertId().toInt(); + + emit DirectoryDiscovered(dir, SubdirectoryList()); +} + +void CollectionBackend::RemoveDirectory(const Directory &dir) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + // Remove songs first + DeleteSongs(FindSongsInDirectory(dir.id)); + + ScopedTransaction transaction(&db); + + // Delete the subdirs that were in this directory + QSqlQuery q(db); + q.prepare(QString("DELETE FROM %1 WHERE directory_id = :id").arg(subdirs_table_)); + q.bindValue(":id", dir.id); + q.exec(); + if (db_->CheckErrors(q)) return; + + // Now remove the directory itself + q = QSqlQuery(db); + q.prepare(QString("DELETE FROM %1 WHERE ROWID = :id").arg(dirs_table_)); + q.bindValue(":id", dir.id); + q.exec(); + if (db_->CheckErrors(q)) return; + + emit DirectoryDeleted(dir); + + transaction.Commit(); +} + +SongList CollectionBackend::FindSongsInDirectory(int id) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + QSqlQuery q(db); + q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE directory_id = :directory_id").arg(songs_table_)); + q.bindValue(":directory_id", id); + q.exec(); + if (db_->CheckErrors(q)) return SongList(); + + SongList ret; + while (q.next()) { + Song song; + song.InitFromQuery(q, true); + ret << song; + } + return ret; +} + +void CollectionBackend::AddOrUpdateSubdirs(const SubdirectoryList &subdirs) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + QSqlQuery find_query(db); + find_query.prepare(QString("SELECT ROWID FROM %1 WHERE directory_id = :id AND path = :path").arg(subdirs_table_)); + QSqlQuery add_query(db); + add_query.prepare(QString("INSERT INTO %1 (directory_id, path, mtime) VALUES (:id, :path, :mtime)").arg(subdirs_table_)); + QSqlQuery update_query(db); + update_query.prepare(QString("UPDATE %1 SET mtime = :mtime WHERE directory_id = :id AND path = :path").arg(subdirs_table_)); + QSqlQuery delete_query(db); + delete_query.prepare(QString("DELETE FROM %1 WHERE directory_id = :id AND path = :path").arg(subdirs_table_)); + + ScopedTransaction transaction(&db); + for (const Subdirectory &subdir : subdirs) { + if (subdir.mtime == 0) { + // Delete the subdirectory + delete_query.bindValue(":id", subdir.directory_id); + delete_query.bindValue(":path", subdir.path); + delete_query.exec(); + db_->CheckErrors(delete_query); + } + else { + // See if this subdirectory already exists in the database + find_query.bindValue(":id", subdir.directory_id); + find_query.bindValue(":path", subdir.path); + find_query.exec(); + if (db_->CheckErrors(find_query)) continue; + + if (find_query.next()) { + update_query.bindValue(":mtime", subdir.mtime); + update_query.bindValue(":id", subdir.directory_id); + update_query.bindValue(":path", subdir.path); + update_query.exec(); + db_->CheckErrors(update_query); + } + else { + add_query.bindValue(":id", subdir.directory_id); + add_query.bindValue(":path", subdir.path); + add_query.bindValue(":mtime", subdir.mtime); + add_query.exec(); + db_->CheckErrors(add_query); + } + } + } + transaction.Commit(); + +} + +void CollectionBackend::AddOrUpdateSongs(const SongList &songs) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + QSqlQuery check_dir(db); + check_dir.prepare(QString("SELECT ROWID FROM %1 WHERE ROWID = :id").arg(dirs_table_)); + QSqlQuery add_song(db); + add_song.prepare(QString("INSERT INTO %1 (" + Song::kColumnSpec + ") VALUES (" + Song::kBindSpec + ")").arg(songs_table_)); + QSqlQuery update_song(db); + update_song.prepare(QString("UPDATE %1 SET " + Song::kUpdateSpec + " WHERE ROWID = :id").arg(songs_table_)); + QSqlQuery add_song_fts(db); + add_song_fts.prepare(QString("INSERT INTO %1 (ROWID, " + Song::kFtsColumnSpec + ") VALUES (:id, " + Song::kFtsBindSpec + ")").arg(fts_table_)); + QSqlQuery update_song_fts(db); + update_song_fts.prepare(QString("UPDATE %1 SET " + Song::kFtsUpdateSpec + " WHERE ROWID = :id").arg(fts_table_)); + + ScopedTransaction transaction(&db); + + SongList added_songs; + SongList deleted_songs; + + for (const Song &song : songs) { + // Do a sanity check first - make sure the song's directory still exists + // This is to fix a possible race condition when a directory is removed + // while CollectionWatcher is scanning it. + if (!dirs_table_.isEmpty()) { + check_dir.bindValue(":id", song.directory_id()); + check_dir.exec(); + if (db_->CheckErrors(check_dir)) continue; + + if (!check_dir.next()) continue; // Directory didn't exist + } + + if (song.id() == -1) { + // Create + + // Insert the row and create a new ID + song.BindToQuery(&add_song); + add_song.exec(); + if (db_->CheckErrors(add_song)) continue; + + // Get the new ID + const int id = add_song.lastInsertId().toInt(); + + // Add to the FTS index + add_song_fts.bindValue(":id", id); + song.BindToFtsQuery(&add_song_fts); + add_song_fts.exec(); + if (db_->CheckErrors(add_song_fts)) continue; + + Song copy(song); + copy.set_id(id); + added_songs << copy; + } + else { + // Get the previous song data first + Song old_song(GetSongById(song.id())); + if (!old_song.is_valid()) continue; + + // Update + song.BindToQuery(&update_song); + update_song.bindValue(":id", song.id()); + update_song.exec(); + if (db_->CheckErrors(update_song)) continue; + + song.BindToFtsQuery(&update_song_fts); + update_song_fts.bindValue(":id", song.id()); + update_song_fts.exec(); + if (db_->CheckErrors(update_song_fts)) continue; + + deleted_songs << old_song; + added_songs << song; + } + } + + transaction.Commit(); + + if (!deleted_songs.isEmpty()) emit SongsDeleted(deleted_songs); + + if (!added_songs.isEmpty()) emit SongsDiscovered(added_songs); + + UpdateTotalSongCountAsync(); + UpdateTotalArtistCountAsync(); + UpdateTotalAlbumCountAsync(); + +} + +void CollectionBackend::UpdateMTimesOnly(const SongList &songs) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + QSqlQuery q(db); + q.prepare(QString("UPDATE %1 SET mtime = :mtime WHERE ROWID = :id").arg(songs_table_)); + + ScopedTransaction transaction(&db); + for (const Song &song : songs) { + q.bindValue(":mtime", song.mtime()); + q.bindValue(":id", song.id()); + q.exec(); + db_->CheckErrors(q); + } + transaction.Commit(); + +} + +void CollectionBackend::DeleteSongs(const SongList &songs) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + QSqlQuery remove(db); + remove.prepare(QString("DELETE FROM %1 WHERE ROWID = :id").arg(songs_table_)); + QSqlQuery remove_fts(db); + remove_fts.prepare(QString("DELETE FROM %1 WHERE ROWID = :id").arg(fts_table_)); + + ScopedTransaction transaction(&db); + for (const Song &song : songs) { + remove.bindValue(":id", song.id()); + remove.exec(); + db_->CheckErrors(remove); + + remove_fts.bindValue(":id", song.id()); + remove_fts.exec(); + db_->CheckErrors(remove_fts); + } + transaction.Commit(); + + emit SongsDeleted(songs); + + UpdateTotalSongCountAsync(); + UpdateTotalArtistCountAsync(); + UpdateTotalAlbumCountAsync(); + +} + +void CollectionBackend::MarkSongsUnavailable(const SongList &songs, bool unavailable) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + QSqlQuery remove(db); + remove.prepare(QString("UPDATE %1 SET unavailable = %2 WHERE ROWID = :id").arg(songs_table_).arg(int(unavailable))); + + ScopedTransaction transaction(&db); + for (const Song &song : songs) { + remove.bindValue(":id", song.id()); + remove.exec(); + db_->CheckErrors(remove); + } + transaction.Commit(); + + emit SongsDeleted(songs); + UpdateTotalSongCountAsync(); + UpdateTotalArtistCountAsync(); + UpdateTotalAlbumCountAsync(); + +} + +QStringList CollectionBackend::GetAll(const QString &column, const QueryOptions &opt) { + + CollectionQuery query(opt); + query.SetColumnSpec("DISTINCT " + column); + query.AddCompilationRequirement(false); + + QMutexLocker l(db_->Mutex()); + if (!ExecQuery(&query)) return QStringList(); + + QStringList ret; + while (query.Next()) { + ret << query.Value(0).toString(); + } + return ret; + +} + +QStringList CollectionBackend::GetAllArtists(const QueryOptions &opt) { + return GetAll("artist", opt); +} + +QStringList CollectionBackend::GetAllArtistsWithAlbums(const QueryOptions &opt) { + + // Albums with 'albumartist' field set: + CollectionQuery query(opt); +// query.SetColumnSpec("DISTINCT artist"); + query.SetColumnSpec("DISTINCT albumartist"); + query.AddCompilationRequirement(false); + query.AddWhere("album", "", "!="); + +// Albums with no 'albumartist' (extract 'artist'): + CollectionQuery query2(opt); + query2.SetColumnSpec("DISTINCT artist"); + query2.AddCompilationRequirement(false); + query2.AddWhere("album", "", "!="); + query2.AddWhere("albumartist", "", "="); + + { + QMutexLocker l(db_->Mutex()); + if (!ExecQuery(&query) || !ExecQuery(&query2)) { + return QStringList(); + } + } + +// QStringList ret; + QSet artists; + while (query.Next()) { + //ret << query.Value(0).toString(); + artists << query.Value(0).toString(); + } + + while (query2.Next()) { + artists << query2.Value(0).toString(); + } + +// return ret; + return QStringList(artists.toList()); +} + +CollectionBackend::AlbumList CollectionBackend::GetAllAlbums(const QueryOptions &opt) { + return GetAlbums(QString(), QString(), false, opt); +} + +CollectionBackend::AlbumList CollectionBackend::GetAlbumsByArtist(const QString &artist, const QueryOptions &opt) { + return GetAlbums(artist, QString(), false, opt); +} + +CollectionBackend::AlbumList CollectionBackend::GetAlbumsByAlbumArtist( + const QString &album_artist, const QueryOptions &opt) { + return GetAlbums(QString(), album_artist, false, opt); +} + +SongList CollectionBackend::GetSongsByAlbum(const QString &album, const QueryOptions &opt) { + CollectionQuery query(opt); + query.AddCompilationRequirement(false); + query.AddWhere("album", album); + return ExecCollectionQuery(&query); +} + +SongList CollectionBackend::GetSongs(const QString &artist, const QString &album, const QueryOptions &opt) { + CollectionQuery query(opt); + query.AddCompilationRequirement(false); + query.AddWhere("artist", artist); + query.AddWhere("album", album); + return ExecCollectionQuery(&query); +} + +SongList CollectionBackend::ExecCollectionQuery(CollectionQuery *query) { + + query->SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec); + QMutexLocker l(db_->Mutex()); + if (!ExecQuery(query)) return SongList(); + + SongList ret; + while (query->Next()) { + Song song; + song.InitFromQuery(*query, true); + ret << song; + } + return ret; +} + +Song CollectionBackend::GetSongById(int id) { + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + return GetSongById(id, db); +} + +SongList CollectionBackend::GetSongsById(const QList &ids) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + QStringList str_ids; + for (int id : ids) { + str_ids << QString::number(id); + } + + return GetSongsById(str_ids, db); +} + +SongList CollectionBackend::GetSongsById(const QStringList &ids) { + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + return GetSongsById(ids, db); +} + +SongList CollectionBackend::GetSongsByForeignId(const QStringList &ids, const QString &table, const QString &column) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + QString in = ids.join(","); + + QSqlQuery q(db); + q.prepare(QString("SELECT %2.ROWID, " + Song::kColumnSpec + ", %2.%3 FROM %2, %1 WHERE %2.%3 IN (%4) AND %1.ROWID = %2.ROWID AND unavailable = 0").arg(songs_table_, table, column, in)); + q.exec(); + if (db_->CheckErrors(q)) return SongList(); + + QVector ret(ids.count()); + while (q.next()) { + const QString foreign_id = q.value(Song::kColumns.count() + 1).toString(); + const int index = ids.indexOf(foreign_id); + if (index == -1) continue; + + ret[index].InitFromQuery(q, true); + } + return ret.toList(); +} + +Song CollectionBackend::GetSongById(int id, QSqlDatabase &db) { + SongList list = GetSongsById(QStringList() << QString::number(id), db); + if (list.isEmpty()) return Song(); + return list.first(); +} + +SongList CollectionBackend::GetSongsById(const QStringList &ids, QSqlDatabase &db) { + + QString in = ids.join(","); + + QSqlQuery q(db); + q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE ROWID IN (%2)").arg(songs_table_, in)); + q.exec(); + if (db_->CheckErrors(q)) return SongList(); + + SongList ret; + while (q.next()) { + Song song; + song.InitFromQuery(q, true); + ret << song; + } + return ret; +} + +Song CollectionBackend::GetSongByUrl(const QUrl &url, qint64 beginning) { + + CollectionQuery query; + query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec); + query.AddWhere("filename", url.toEncoded()); + query.AddWhere("beginning", beginning); + + Song song; + if (ExecQuery(&query) && query.Next()) { + song.InitFromQuery(query, true); + } + return song; +} + +SongList CollectionBackend::GetSongsByUrl(const QUrl &url) { + + CollectionQuery query; + query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec); + query.AddWhere("filename", url.toEncoded()); + + SongList songlist; + if (ExecQuery(&query)) { + while (query.Next()) { + Song song; + song.InitFromQuery(query, true); + + songlist << song; + } + } + return songlist; +} + +CollectionBackend::AlbumList CollectionBackend::GetCompilationAlbums(const QueryOptions &opt) { + return GetAlbums(QString(), QString(), true, opt); +} + +SongList CollectionBackend::GetCompilationSongs(const QString &album, const QueryOptions &opt) { + + CollectionQuery query(opt); + query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec); + query.AddCompilationRequirement(true); + query.AddWhere("album", album); + + QMutexLocker l(db_->Mutex()); + if (!ExecQuery(&query)) return SongList(); + + SongList ret; + while (query.Next()) { + Song song; + song.InitFromQuery(query, true); + ret << song; + } + return ret; +} + +void CollectionBackend::UpdateCompilations() { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + // Look for albums that have songs by more than one 'effective album artist' in the same + // directory + + QSqlQuery q(db); + q.prepare(QString("SELECT effective_albumartist, album, filename, compilation_detected FROM %1 WHERE unavailable = 0 ORDER BY album").arg(songs_table_)); + q.exec(); + if (db_->CheckErrors(q)) return; + + QMap compilation_info; + while (q.next()) { + QString artist = q.value(0).toString(); + QString album = q.value(1).toString(); + QString filename = q.value(2).toString(); + bool compilation_detected = q.value(3).toBool(); + + // Ignore songs that don't have an album field set + if (album.isEmpty()) continue; + + // Find the directory the song is in + int last_separator = filename.lastIndexOf('/'); + if (last_separator == -1) continue; + + CompilationInfo &info = compilation_info[album]; + info.artists.insert(artist); + info.directories.insert(filename.left(last_separator)); + if (compilation_detected) info.has_compilation_detected = true; + else info.has_not_compilation_detected = true; + } + + // Now mark the songs that we think are in compilations + QSqlQuery update(db); + update.prepare(QString("UPDATE %1 SET compilation_detected = :compilation_detected, compilation_effective = ((compilation OR :compilation_detected OR compilation_on) AND NOT compilation_off) + 0 WHERE album = :album AND unavailable = 0").arg(songs_table_)); + QSqlQuery find_songs(db); + find_songs.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE album = :album AND compilation_detected = :compilation_detected AND unavailable = 0").arg(songs_table_)); + + SongList deleted_songs; + SongList added_songs; + + ScopedTransaction transaction(&db); + + QMap::const_iterator it = compilation_info.constBegin(); + for (; it != compilation_info.constEnd(); ++it) { + const CompilationInfo &info = it.value(); + QString album(it.key()); + + // If there were more 'effective album artists' than there were directories for this album then it's a compilation. + + if (info.artists.count() > info.directories.count()) { + if (info.has_not_compilation_detected) + UpdateCompilations(find_songs, update, deleted_songs, added_songs, album, 1); + } + else { + if (info.has_compilation_detected) + UpdateCompilations(find_songs, update, deleted_songs, added_songs, album, 0); + } + } + + transaction.Commit(); + + if (!deleted_songs.isEmpty()) { + emit SongsDeleted(deleted_songs); + emit SongsDiscovered(added_songs); + } +} + +void CollectionBackend::UpdateCompilations(QSqlQuery &find_songs, QSqlQuery &update, SongList &deleted_songs, SongList &added_songs, const QString &album, int compilation_detected) { + + // Get songs that were already in that album, so we can tell the model + // they've been updated + find_songs.bindValue(":album", album); + find_songs.bindValue(":compilation_detected", int(!compilation_detected)); + find_songs.exec(); + while (find_songs.next()) { + Song song; + song.InitFromQuery(find_songs, true); + deleted_songs << song; + song.set_compilation_detected(true); + added_songs << song; + } + + // Mark this album + update.bindValue(":compilation_detected", compilation_detected); + update.bindValue(":album", album); + update.exec(); + db_->CheckErrors(update); +} + +CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist, const QString &album_artist, bool compilation, const QueryOptions &opt) { + + AlbumList ret; + + CollectionQuery query(opt); + query.SetColumnSpec("album, artist, albumartist, compilation, compilation_detected, art_automatic, art_manual, filename"); + query.SetOrderBy("album"); + + if (compilation) { + query.AddCompilationRequirement(true); + } + else if (!album_artist.isNull()) { + query.AddCompilationRequirement(false); + query.AddWhere("albumartist", album_artist); + } + else if (!artist.isNull()) { + query.AddCompilationRequirement(false); + query.AddWhere("artist", artist); + } + + { + QMutexLocker l(db_->Mutex()); + if (!ExecQuery(&query)) return ret; + } + + QString last_album; + QString last_artist; + QString last_album_artist; + while (query.Next()) { + bool compilation = query.Value(3).toBool() | query.Value(4).toBool(); + + Album info; + info.artist = compilation ? QString() : query.Value(1).toString(); + info.album_artist = compilation ? QString() : query.Value(2).toString(); + info.album_name = query.Value(0).toString(); + info.art_automatic = query.Value(5).toString(); + info.art_manual = query.Value(6).toString(); + info.first_url = QUrl::fromEncoded(query.Value(7).toByteArray()); + + if ((info.artist == last_artist || info.album_artist == last_album_artist) && info.album_name == last_album) + continue; + + ret << info; + + last_album = info.album_name; + last_artist = info.artist; + last_album_artist = info.album_artist; + } + + return ret; + +} + +CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &artist, const QString &albumartist, const QString &album) { + + Album ret; + ret.album_name = album; + ret.artist = artist; + ret.album_artist = albumartist; + + CollectionQuery query = CollectionQuery(QueryOptions()); + query.SetColumnSpec("art_automatic, art_manual, filename"); + if (!albumartist.isEmpty()) { + query.AddWhere("albumartist", albumartist); + } + else if (!artist.isEmpty()) { + query.AddWhere("artist", artist); + } + query.AddWhere("album", album); + + QMutexLocker l(db_->Mutex()); + if (!ExecQuery(&query)) return ret; + + if (query.Next()) { + ret.art_automatic = query.Value(0).toString(); + ret.art_manual = query.Value(1).toString(); + ret.first_url = QUrl::fromEncoded(query.Value(2).toByteArray()); + } + + return ret; + +} + +void CollectionBackend::UpdateManualAlbumArtAsync(const QString &artist, const QString &albumartist, const QString &album, const QString &art) { + + metaObject()->invokeMethod(this, "UpdateManualAlbumArt", Qt::QueuedConnection, Q_ARG(QString, artist), Q_ARG(QString, albumartist), Q_ARG(QString, album), Q_ARG(QString, art)); + +} + +void CollectionBackend::UpdateManualAlbumArt(const QString &artist, const QString &albumartist, const QString &album, const QString &art) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + // Get the songs before they're updated + CollectionQuery query; + query.SetColumnSpec("ROWID, " + Song::kColumnSpec); + query.AddWhere("album", album); + + if (!albumartist.isNull()) { + query.AddWhere("albumartist", albumartist); + } + else if (!artist.isNull()) { + query.AddWhere("artist", artist); + } + + if (!ExecQuery(&query)) return; + + SongList deleted_songs; + while (query.Next()) { + Song song; + song.InitFromQuery(query, true); + deleted_songs << song; + } + + // Update the songs + QString sql(QString("UPDATE %1 SET art_manual = :art WHERE album = :album AND unavailable = 0").arg(songs_table_)); + + if (!albumartist.isNull()) { + sql += " AND albumartist = :albumartist"; + } + else if (!artist.isNull()) { + sql += " AND artist = :artist"; + } + + QSqlQuery q(db); + q.prepare(sql); + q.bindValue(":art", art); + q.bindValue(":album", album); + if (!albumartist.isNull()) { + q.bindValue(":albumartist", albumartist); + } + else if (!artist.isNull()) { + q.bindValue(":artist", artist); + } + + q.exec(); + db_->CheckErrors(q); + + // Now get the updated songs + if (!ExecQuery(&query)) return; + + SongList added_songs; + while (query.Next()) { + Song song; + song.InitFromQuery(query, true); + added_songs << song; + } + + if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) { + emit SongsDeleted(deleted_songs); + emit SongsDiscovered(added_songs); + } + +} + +void CollectionBackend::ForceCompilation(const QString &album, const QList &artists, bool on) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + SongList deleted_songs, added_songs; + + for (const QString &artist : artists) { + // Get the songs before they're updated + CollectionQuery query; + query.SetColumnSpec("ROWID, " + Song::kColumnSpec); + query.AddWhere("album", album); + if (!artist.isNull()) query.AddWhere("artist", artist); + + if (!ExecQuery(&query)) return; + + while (query.Next()) { + Song song; + song.InitFromQuery(query, true); + deleted_songs << song; + } + + // Update the songs + QString sql(QString("UPDATE %1 SET compilation_on = :compilation_on, compilation_off = :compilation_off, compilation_effective = ((compilation OR compilation_detected OR :compilation_on) AND NOT :compilation_off) + 0 WHERE album = :album AND unavailable = 0").arg(songs_table_)); + if (!artist.isEmpty()) sql += " AND artist = :artist"; + + QSqlQuery q(db); + q.prepare(sql); + q.bindValue(":compilation_on", on ? 1 : 0); + q.bindValue(":compilation_off", on ? 0 : 1); + q.bindValue(":album", album); + if (!artist.isEmpty()) q.bindValue(":artist", artist); + + q.exec(); + db_->CheckErrors(q); + + // Now get the updated songs + if (!ExecQuery(&query)) return; + + while (query.Next()) { + Song song; + song.InitFromQuery(query, true); + added_songs << song; + } + } + + if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) { + emit SongsDeleted(deleted_songs); + emit SongsDiscovered(added_songs); + } + +} + +bool CollectionBackend::ExecQuery(CollectionQuery *q) { + return !db_->CheckErrors(q->Exec(db_->Connect(), songs_table_, fts_table_)); +} + +void CollectionBackend::IncrementPlayCount(int id) { + if (id == -1) return; + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + QSqlQuery q(db); + q.prepare(QString("UPDATE %1 SET playcount = playcount + 1, lastplayed = :now WHERE ROWID = :id").arg(songs_table_)); + q.bindValue(":now", QDateTime::currentDateTime().toTime_t()); + q.bindValue(":id", id); + q.exec(); + if (db_->CheckErrors(q)) return; + + Song new_song = GetSongById(id, db); + +} + +void CollectionBackend::IncrementSkipCount(int id, float progress) { + + if (id == -1) return; + progress = qBound(0.0f, progress, 1.0f); + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + QSqlQuery q(db); + q.prepare(QString("UPDATE %1 SET skipcount = skipcount + 1 WHERE ROWID = :id").arg(songs_table_)); + q.bindValue(":id", id); + q.exec(); + if (db_->CheckErrors(q)) return; + + Song new_song = GetSongById(id, db); + +} + +void CollectionBackend::ResetStatistics(int id) { + + if (id == -1) return; + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + QSqlQuery q(db); + q.prepare(QString("UPDATE %1 SET playcount = 0, skipcount = 0, lastplayed = -1 WHERE ROWID = :id").arg(songs_table_)); + q.bindValue(":id", id); + q.exec(); + if (db_->CheckErrors(q)) return; + + Song new_song = GetSongById(id, db); + +} + +void CollectionBackend::DeleteAll() { + { + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + ScopedTransaction t(&db); + + QSqlQuery q("DELETE FROM " + songs_table_, db); + q.exec(); + if (db_->CheckErrors(q)) return; + + q = QSqlQuery("DELETE FROM " + fts_table_, db); + q.exec(); + if (db_->CheckErrors(q)) return; + + t.Commit(); + } + + emit DatabaseReset(); + +} + diff --git a/src/collection/collectionbackend.h b/src/collection/collectionbackend.h new file mode 100644 index 00000000..b812e0a1 --- /dev/null +++ b/src/collection/collectionbackend.h @@ -0,0 +1,232 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COLLECTIONBACKEND_H +#define COLLECTIONBACKEND_H + +#include "config.h" + +#include +#include +#include + +#include "directory.h" +#include "collectionquery.h" +#include "core/song.h" + +class Database; + +class CollectionBackendInterface : public QObject { + Q_OBJECT + + public: + CollectionBackendInterface(QObject *parent = nullptr) : QObject(parent) {} + virtual ~CollectionBackendInterface() {} + + struct Album { + Album() {} + Album(const QString &_artist, const QString &_album_artist, const QString &_album_name, const QString &_art_automatic, const QString &_art_manual, const QUrl &_first_url) : + artist(_artist), + album_artist(_album_artist), + album_name(_album_name), + art_automatic(_art_automatic), + art_manual(_art_manual), + first_url(_first_url) {} + + const QString &effective_albumartist() const { + return album_artist.isEmpty() ? artist : album_artist; + } + + QString artist; + QString album_artist; + QString album_name; + + QString art_automatic; + QString art_manual; + QUrl first_url; + }; + typedef QList AlbumList; + + virtual QString songs_table() const = 0; + + // Get a list of directories in the collection. Emits DirectoriesDiscovered. + virtual void LoadDirectoriesAsync() = 0; + + virtual void UpdateTotalSongCountAsync() = 0; + virtual void UpdateTotalArtistCountAsync() = 0; + virtual void UpdateTotalAlbumCountAsync() = 0; + + virtual SongList FindSongsInDirectory(int id) = 0; + virtual SubdirectoryList SubdirsInDirectory(int id) = 0; + virtual DirectoryList GetAllDirectories() = 0; + virtual void ChangeDirPath(int id, const QString &old_path, const QString &new_path) = 0; + + virtual QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) = 0; + virtual QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) = 0; + virtual SongList GetSongsByAlbum(const QString &album, const QueryOptions &opt = QueryOptions()) = 0; + virtual SongList GetSongs(const QString &artist, const QString &album, const QueryOptions &opt = QueryOptions()) = 0; + + virtual SongList GetCompilationSongs(const QString &album, const QueryOptions &opt = QueryOptions()) = 0; + + virtual AlbumList GetAllAlbums(const QueryOptions &opt = QueryOptions()) = 0; + virtual AlbumList GetAlbumsByArtist(const QString &artist, const QueryOptions &opt = QueryOptions()) = 0; + virtual AlbumList GetCompilationAlbums(const QueryOptions &opt = QueryOptions()) = 0; + + virtual void UpdateManualAlbumArtAsync(const QString &artist, const QString &albumartist, const QString &album, const QString &art) = 0; + virtual Album GetAlbumArt(const QString &artist, const QString &albumartist, const QString &album) = 0; + + virtual Song GetSongById(int id) = 0; + + // Returns all sections of a song with the given filename. If there's just one section + // the resulting list will have it's size equal to 1. + virtual SongList GetSongsByUrl(const QUrl &url) = 0; + // Returns a section of a song with the given filename and beginning. If the section + // is not present in collection, returns invalid song. + // Using default beginning value is suitable when searching for single-section songs. + virtual Song GetSongByUrl(const QUrl &url, qint64 beginning = 0) = 0; + + virtual void AddDirectory(const QString &path) = 0; + virtual void RemoveDirectory(const Directory &dir) = 0; + + virtual bool ExecQuery(CollectionQuery *q) = 0; +}; + +class CollectionBackend : public CollectionBackendInterface { + Q_OBJECT + + public: + static const char *kSettingsGroup; + + Q_INVOKABLE CollectionBackend(QObject *parent = nullptr); + void Init(Database *db, const QString &songs_table, const QString &dirs_table, const QString &subdirs_table, const QString &fts_table); + + Database *db() const { return db_; } + + QString songs_table() const { return songs_table_; } + QString dirs_table() const { return dirs_table_; } + QString subdirs_table() const { return subdirs_table_; } + + // Get a list of directories in the collection. Emits DirectoriesDiscovered. + void LoadDirectoriesAsync(); + + void UpdateTotalSongCountAsync(); + void UpdateTotalArtistCountAsync(); + void UpdateTotalAlbumCountAsync(); + + SongList FindSongsInDirectory(int id); + SubdirectoryList SubdirsInDirectory(int id); + DirectoryList GetAllDirectories(); + void ChangeDirPath(int id, const QString &old_path, const QString &new_path); + + QStringList GetAll(const QString &column, const QueryOptions &opt = QueryOptions()); + QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()); + QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()); + SongList GetSongsByAlbum(const QString &album, const QueryOptions &opt = QueryOptions()); + SongList GetSongs(const QString &artist, const QString &album, const QueryOptions &opt = QueryOptions()); + + SongList GetCompilationSongs(const QString &album, const QueryOptions &opt = QueryOptions()); + + AlbumList GetAllAlbums(const QueryOptions &opt = QueryOptions()); + AlbumList GetAlbumsByArtist(const QString &artist, const QueryOptions &opt = QueryOptions()); + AlbumList GetAlbumsByAlbumArtist(const QString &albumartist, const QueryOptions &opt = QueryOptions()); + AlbumList GetCompilationAlbums(const QueryOptions &opt = QueryOptions()); + + void UpdateManualAlbumArtAsync(const QString &artist, const QString &albumartist, const QString &album, const QString &art); + Album GetAlbumArt(const QString &artist, const QString &albumartist, const QString &album); + + Song GetSongById(int id); + SongList GetSongsById(const QList &ids); + SongList GetSongsById(const QStringList &ids); + SongList GetSongsByForeignId(const QStringList &ids, const QString &table, const QString &column); + + SongList GetSongsByUrl(const QUrl &url); + Song GetSongByUrl(const QUrl &url, qint64 beginning = 0); + + void AddDirectory(const QString &path); + void RemoveDirectory(const Directory &dir); + + bool ExecQuery(CollectionQuery *q); + SongList ExecCollectionQuery(CollectionQuery *query); + + void IncrementPlayCountAsync(int id); + void IncrementSkipCountAsync(int id, float progress); + void ResetStatisticsAsync(int id); + + void DeleteAll(); + + public slots: + void LoadDirectories(); + void UpdateTotalSongCount(); + void UpdateTotalArtistCount(); + void UpdateTotalAlbumCount(); + void AddOrUpdateSongs(const SongList &songs); + void UpdateMTimesOnly(const SongList &songs); + void DeleteSongs(const SongList &songs); + void MarkSongsUnavailable(const SongList &songs, bool unavailable = true); + void AddOrUpdateSubdirs(const SubdirectoryList &subdirs); + void UpdateCompilations(); + void UpdateManualAlbumArt(const QString &artist, const QString &albumartist, const QString &album, const QString &art); + void ForceCompilation(const QString &album, const QList &artists, bool on); + void IncrementPlayCount(int id); + void IncrementSkipCount(int id, float progress); + void ResetStatistics(int id); + +signals: + void DirectoryDiscovered(const Directory &dir, const SubdirectoryList &subdirs); + void DirectoryDeleted(const Directory &dir); + + void SongsDiscovered(const SongList &songs); + void SongsDeleted(const SongList &songs); + + void DatabaseReset(); + + void TotalSongCountUpdated(int total); + void TotalArtistCountUpdated(int total); + void TotalAlbumCountUpdated(int total); + + private: + struct CompilationInfo { + CompilationInfo() : has_compilation_detected(false), has_not_compilation_detected(false) {} + + QSet artists; + QSet directories; + + bool has_compilation_detected; + bool has_not_compilation_detected; + }; + + void UpdateCompilations(QSqlQuery &find_songs, QSqlQuery &update, SongList &deleted_songs, SongList &added_songs, const QString &album, int compilation_detected); + AlbumList GetAlbums(const QString &artist, const QString &album_artist, bool compilation = false, const QueryOptions &opt = QueryOptions()); + SubdirectoryList SubdirsInDirectory(int id, QSqlDatabase &db); + + Song GetSongById(int id, QSqlDatabase &db); + SongList GetSongsById(const QStringList &ids, QSqlDatabase &db); + + private: + Database *db_; + QString songs_table_; + QString dirs_table_; + QString subdirs_table_; + QString fts_table_; + +}; + +#endif // COLLECTIONBACKEND_H + diff --git a/src/collection/collectiondirectorymodel.cpp b/src/collection/collectiondirectorymodel.cpp new file mode 100644 index 00000000..20e176a9 --- /dev/null +++ b/src/collection/collectiondirectorymodel.cpp @@ -0,0 +1,110 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "collectiondirectorymodel.h" +#include "collectionbackend.h" +#include "core/application.h" +#include "core/filesystemmusicstorage.h" +#include "core/musicstorage.h" +#include "core/utilities.h" +#include "core/iconloader.h" + +CollectionDirectoryModel::CollectionDirectoryModel(CollectionBackend* backend, QObject* parent) + : QStandardItemModel(parent), + dir_icon_(IconLoader::Load("document-open-folder")), + backend_(backend) +{ + + connect(backend_, SIGNAL(DirectoryDiscovered(Directory, SubdirectoryList)), SLOT(DirectoryDiscovered(Directory))); + connect(backend_, SIGNAL(DirectoryDeleted(Directory)), SLOT(DirectoryDeleted(Directory))); + +} + +CollectionDirectoryModel::~CollectionDirectoryModel() {} + +void CollectionDirectoryModel::DirectoryDiscovered(const Directory &dir) { + + QStandardItem* item; + if (Application::kIsPortable && Utilities::UrlOnSameDriveAsStrawberry(QUrl::fromLocalFile(dir.path))) { + item = new QStandardItem(Utilities::GetRelativePathToStrawberryBin(QUrl::fromLocalFile(dir.path)).toLocalFile()); + } + else { + item = new QStandardItem(dir.path); + } + item->setData(dir.id, kIdRole); + item->setIcon(dir_icon_); + storage_ << std::shared_ptr(new FilesystemMusicStorage(dir.path)); + appendRow(item); + +} + +void CollectionDirectoryModel::DirectoryDeleted(const Directory &dir) { + + for (int i = 0; i < rowCount(); ++i) { + if (item(i, 0)->data(kIdRole).toInt() == dir.id) { + removeRow(i); + storage_.removeAt(i); + break; + } + } + +} + +void CollectionDirectoryModel::AddDirectory(const QString &path) { + + if (!backend_) return; + + backend_->AddDirectory(path); + +} + +void CollectionDirectoryModel::RemoveDirectory(const QModelIndex &index) { + + if (!backend_ || !index.isValid()) return; + + Directory dir; + dir.path = index.data().toString(); + dir.id = index.data(kIdRole).toInt(); + + backend_->RemoveDirectory(dir); + +} + +QVariant CollectionDirectoryModel::data(const QModelIndex &index, int role) const { + + switch (role) { + case MusicStorage::Role_Storage: + case MusicStorage::Role_StorageForceConnect: + return QVariant::fromValue(storage_[index.row()]); + + case MusicStorage::Role_FreeSpace: + return Utilities::FileSystemFreeSpace(data(index, Qt::DisplayRole).toString()); + + case MusicStorage::Role_Capacity: + return Utilities::FileSystemCapacity(data(index, Qt::DisplayRole).toString()); + + default: + return QStandardItemModel::data(index, role); + } + +} + diff --git a/src/collection/collectiondirectorymodel.h b/src/collection/collectiondirectorymodel.h new file mode 100644 index 00000000..c2a164a8 --- /dev/null +++ b/src/collection/collectiondirectorymodel.h @@ -0,0 +1,62 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COLLECTIONDIRECTORYMODEL_H +#define COLLECTIONDIRECTORYMODEL_H + +#include "config.h" + +#include + +#include +#include + +#include "directory.h" + +class CollectionBackend; +class MusicStorage; + +class CollectionDirectoryModel : public QStandardItemModel { + Q_OBJECT + + public: + CollectionDirectoryModel(CollectionBackend* backend, QObject *parent = nullptr); + ~CollectionDirectoryModel(); + + // To be called by GUIs + void AddDirectory(const QString &path); + void RemoveDirectory(const QModelIndex &index); + + QVariant data(const QModelIndex &index, int role) const; + + private slots: + // To be called by the backend + void DirectoryDiscovered(const Directory &directories); + void DirectoryDeleted(const Directory &directories); + + private: + static const int kIdRole = Qt::UserRole + 1; + + QIcon dir_icon_; + CollectionBackend* backend_; + QList > storage_; +}; + +#endif // COLLECTIONDIRECTORYMODEL_H diff --git a/src/collection/collectionfilterwidget.cpp b/src/collection/collectionfilterwidget.cpp new file mode 100644 index 00000000..620037a5 --- /dev/null +++ b/src/collection/collectionfilterwidget.cpp @@ -0,0 +1,364 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "collectionfilterwidget.h" +#include "collectionmodel.h" +#include "collectionquery.h" +#include "groupbydialog.h" +#include "ui_collectionfilterwidget.h" +#include "core/song.h" +#include "core/iconloader.h" +#include "settings/settingsdialog.h" + +CollectionFilterWidget::CollectionFilterWidget(QWidget *parent) + : QWidget(parent), + ui_(new Ui_CollectionFilterWidget), + model_(nullptr), + group_by_dialog_(new GroupByDialog), + filter_delay_(new QTimer(this)), + filter_applies_to_model_(true), + delay_behaviour_(DelayedOnLargeLibraries) { + ui_->setupUi(this); + + // Add the available fields to the tooltip here instead of the ui + // file to prevent that they get translated by mistake. + QString available_fields = Song::kFtsColumns.join(", ").replace(QRegExp("\\bfts"), ""); + ui_->filter->setToolTip(ui_->filter->toolTip().arg(available_fields)); + + connect(ui_->filter, SIGNAL(returnPressed()), SIGNAL(ReturnPressed())); + connect(filter_delay_, SIGNAL(timeout()), SLOT(FilterDelayTimeout())); + + filter_delay_->setInterval(kFilterDelay); + filter_delay_->setSingleShot(true); + + // Icons + ui_->options->setIcon(IconLoader::Load("configure")); + + // Filter by age + QActionGroup *filter_age_group = new QActionGroup(this); + filter_age_group->addAction(ui_->filter_age_all); + filter_age_group->addAction(ui_->filter_age_today); + filter_age_group->addAction(ui_->filter_age_week); + filter_age_group->addAction(ui_->filter_age_month); + filter_age_group->addAction(ui_->filter_age_three_months); + filter_age_group->addAction(ui_->filter_age_year); + + filter_age_menu_ = new QMenu(tr("Show"), this); + filter_age_menu_->addActions(filter_age_group->actions()); + + filter_age_mapper_ = new QSignalMapper(this); + filter_age_mapper_->setMapping(ui_->filter_age_all, -1); + filter_age_mapper_->setMapping(ui_->filter_age_today, 60 * 60 * 24); + filter_age_mapper_->setMapping(ui_->filter_age_week, 60 * 60 * 24 * 7); + filter_age_mapper_->setMapping(ui_->filter_age_month, 60 * 60 * 24 * 30); + filter_age_mapper_->setMapping(ui_->filter_age_three_months, 60 * 60 * 24 * 30 * 3); + filter_age_mapper_->setMapping(ui_->filter_age_year, 60 * 60 * 24 * 365); + + connect(ui_->filter_age_all, SIGNAL(triggered()), filter_age_mapper_, SLOT(map())); + connect(ui_->filter_age_today, SIGNAL(triggered()), filter_age_mapper_, SLOT(map())); + connect(ui_->filter_age_week, SIGNAL(triggered()), filter_age_mapper_, SLOT(map())); + connect(ui_->filter_age_month, SIGNAL(triggered()), filter_age_mapper_, SLOT(map())); + connect(ui_->filter_age_three_months, SIGNAL(triggered()), filter_age_mapper_, SLOT(map())); + connect(ui_->filter_age_year, SIGNAL(triggered()), filter_age_mapper_, SLOT(map())); + + // "Group by ..." + group_by_group_ = CreateGroupByActions(this); + + group_by_menu_ = new QMenu(tr("Group by"), this); + group_by_menu_->addActions(group_by_group_->actions()); + + connect(group_by_group_, SIGNAL(triggered(QAction*)), SLOT(GroupByClicked(QAction*))); + connect(ui_->save_grouping, SIGNAL(triggered()), this, SLOT(SaveGroupBy())); + connect(ui_->manage_groupings, SIGNAL(triggered()), this, SLOT(ShowGroupingManager())); + + // Collection config menu + collection_menu_ = new QMenu(tr("Display options"), this); + collection_menu_->setIcon(ui_->options->icon()); + collection_menu_->addMenu(filter_age_menu_); + collection_menu_->addMenu(group_by_menu_); + collection_menu_->addAction(ui_->save_grouping); + collection_menu_->addAction(ui_->manage_groupings); + collection_menu_->addSeparator(); + ui_->options->setMenu(collection_menu_); + + connect(ui_->filter, SIGNAL(textChanged(QString)), SLOT(FilterTextChanged(QString))); + +} + +CollectionFilterWidget::~CollectionFilterWidget() { delete ui_; } + +void CollectionFilterWidget::UpdateGroupByActions() { + + if (group_by_group_) { + disconnect(group_by_group_, 0, 0, 0); + delete group_by_group_; + } + + group_by_group_ = CreateGroupByActions(this); + group_by_menu_->clear(); + group_by_menu_->addActions(group_by_group_->actions()); + connect(group_by_group_, SIGNAL(triggered(QAction*)), + SLOT(GroupByClicked(QAction*))); + if (model_) { + CheckCurrentGrouping(model_->GetGroupBy()); + } + +} + + +QActionGroup *CollectionFilterWidget::CreateGroupByActions(QObject *parent) { + + QActionGroup *ret = new QActionGroup(parent); + ret->addAction(CreateGroupByAction(tr("Group by Artist"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Artist))); + ret->addAction(CreateGroupByAction(tr("Group by Artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Artist, CollectionModel::GroupBy_Album))); + ret->addAction(CreateGroupByAction(tr("Group by Album artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_Album))); + ret->addAction(CreateGroupByAction(tr("Group by Artist/Year - Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Artist, CollectionModel::GroupBy_YearAlbum))); + ret->addAction(CreateGroupByAction(tr("Group by Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Album))); + ret->addAction(CreateGroupByAction(tr("Group by Genre/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Genre, CollectionModel::GroupBy_Album))); + ret->addAction(CreateGroupByAction(tr("Group by Genre/Artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Genre, CollectionModel::GroupBy_Artist, CollectionModel::GroupBy_Album))); + + QAction *sep1 = new QAction(parent); + sep1->setSeparator(true); + ret->addAction(sep1); + + // read saved groupings + QSettings s; + s.beginGroup(CollectionModel::kSavedGroupingsSettingsGroup); + QStringList saved = s.childKeys(); + for (int i = 0; i < saved.size(); ++i) { + QByteArray bytes = s.value(saved.at(i)).toByteArray(); + QDataStream ds(&bytes, QIODevice::ReadOnly); + CollectionModel::Grouping g; + ds >> g; + ret->addAction(CreateGroupByAction(saved.at(i), parent, g)); + } + + QAction *sep2 = new QAction(parent); + sep2->setSeparator(true); + ret->addAction(sep2); + + ret->addAction(CreateGroupByAction(tr("Advanced grouping..."), parent, CollectionModel::Grouping())); + + return ret; + +} + +QAction *CollectionFilterWidget::CreateGroupByAction(const QString &text, QObject *parent, const CollectionModel::Grouping &grouping) { + + QAction *ret = new QAction(text, parent); + ret->setCheckable(true); + + if (grouping.first != CollectionModel::GroupBy_None) { + ret->setProperty("group_by", QVariant::fromValue(grouping)); + } + + return ret; + +} + +void CollectionFilterWidget::SaveGroupBy() { + + QString text = QInputDialog::getText(this, tr("Grouping Name"), tr("Grouping name:")); + if (!text.isEmpty() && model_) { + model_->SaveGrouping(text); + UpdateGroupByActions(); + } + +} + +void CollectionFilterWidget::ShowGroupingManager() { + + if (!groupings_manager_) { + groupings_manager_.reset(new SavedGroupingManager); + } + groupings_manager_->SetFilter(this); + groupings_manager_->UpdateModel(); + groupings_manager_->show(); + +} + + +void CollectionFilterWidget::FocusOnFilter(QKeyEvent *event) { + + ui_->filter->setFocus(); + QApplication::sendEvent(ui_->filter, event); + +} + +void CollectionFilterWidget::SetCollectionModel(CollectionModel *model) { + + if (model_) { + disconnect(model_, 0, this, 0); + disconnect(model_, 0, group_by_dialog_.get(), 0); + disconnect(group_by_dialog_.get(), 0, model_, 0); + disconnect(filter_age_mapper_, 0, model_, 0); + } + + model_ = model; + + // Connect signals + connect(model_, SIGNAL(GroupingChanged(CollectionModel::Grouping)), group_by_dialog_.get(), SLOT(CollectionGroupingChanged(CollectionModel::Grouping))); + connect(model_, SIGNAL(GroupingChanged(CollectionModel::Grouping)), SLOT(GroupingChanged(CollectionModel::Grouping))); + connect(group_by_dialog_.get(), SIGNAL(Accepted(CollectionModel::Grouping)), model_, SLOT(SetGroupBy(CollectionModel::Grouping))); + connect(filter_age_mapper_, SIGNAL(mapped(int)), model_, SLOT(SetFilterAge(int))); + + // Load settings + if (!settings_group_.isEmpty()) { + QSettings s; + s.beginGroup(settings_group_); + model_->SetGroupBy(CollectionModel::Grouping( + CollectionModel::GroupBy(s.value("group_by1", int(CollectionModel::GroupBy_Artist)).toInt()), + CollectionModel::GroupBy(s.value("group_by2", int(CollectionModel::GroupBy_Album)).toInt()), + CollectionModel::GroupBy(s.value("group_by3", int(CollectionModel::GroupBy_None)).toInt()))); + } + +} + +void CollectionFilterWidget::GroupByClicked(QAction *action) { + if (action->property("group_by").isNull()) { + group_by_dialog_->show(); + return; + } + + CollectionModel::Grouping g = action->property("group_by").value(); + model_->SetGroupBy(g); +} + +void CollectionFilterWidget::GroupingChanged(const CollectionModel::Grouping &g) { + + if (!settings_group_.isEmpty()) { + // Save the settings + QSettings s; + s.beginGroup(settings_group_); + s.setValue("group_by1", int(g[0])); + s.setValue("group_by2", int(g[1])); + s.setValue("group_by3", int(g[2])); + } + + // Now make sure the correct action is checked + CheckCurrentGrouping(g); + +} + +void CollectionFilterWidget::CheckCurrentGrouping(const CollectionModel::Grouping &g) { + + for (QAction *action : group_by_group_->actions()) { + if (action->property("group_by").isNull()) continue; + + if (g == action->property("group_by").value()) { + action->setChecked(true); + return; + } + } + + // Check the advanced action + group_by_group_->actions().last()->setChecked(true); + +} + +void CollectionFilterWidget::SetFilterHint(const QString &hint) { + ui_->filter->setPlaceholderText(hint); +} + +void CollectionFilterWidget::SetQueryMode(QueryOptions::QueryMode query_mode) { + + ui_->filter->clear(); + ui_->filter->setEnabled(query_mode == QueryOptions::QueryMode_All); + + model_->SetFilterQueryMode(query_mode); + +} + +void CollectionFilterWidget::ShowInCollection(const QString &search) { + ui_->filter->setText(search); +} + +void CollectionFilterWidget::SetAgeFilterEnabled(bool enabled) { + filter_age_menu_->setEnabled(enabled); +} + +void CollectionFilterWidget::SetGroupByEnabled(bool enabled) { + group_by_menu_->setEnabled(enabled); +} + +void CollectionFilterWidget::AddMenuAction(QAction *action) { + collection_menu_->addAction(action); +} + +void CollectionFilterWidget::keyReleaseEvent(QKeyEvent *e) { + + switch (e->key()) { + case Qt::Key_Up: + emit UpPressed(); + e->accept(); + break; + + case Qt::Key_Down: + emit DownPressed(); + e->accept(); + break; + + case Qt::Key_Escape: + ui_->filter->clear(); + e->accept(); + break; + } + + QWidget::keyReleaseEvent(e); + +} + +void CollectionFilterWidget::FilterTextChanged(const QString &text) { + + // Searching with one or two characters can be very expensive on the database + // even with FTS, so if there are a large number of songs in the database + // introduce a small delay before actually filtering the model, so if the + // user is typing the first few characters of something it will be quicker. + const bool delay = (delay_behaviour_ == AlwaysDelayed) || (delay_behaviour_ == DelayedOnLargeLibraries && !text.isEmpty() && text.length() < 3 && model_->total_song_count() >= 100000); + + if (delay) { + filter_delay_->start(); + } + else { + filter_delay_->stop(); + FilterDelayTimeout(); + } + +} + +void CollectionFilterWidget::FilterDelayTimeout() { + + emit Filter(ui_->filter->text()); + if (filter_applies_to_model_) { + model_->SetFilterText(ui_->filter->text()); + } + +} diff --git a/src/collection/collectionfilterwidget.h b/src/collection/collectionfilterwidget.h new file mode 100644 index 00000000..098163c1 --- /dev/null +++ b/src/collection/collectionfilterwidget.h @@ -0,0 +1,123 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COLLECTIONFILTERWIDGET_H +#define COLLECTIONFILTERWIDGET_H + +#include "config.h" + +#include + +#include + +#include "collectionmodel.h" +#include "savedgroupingmanager.h" + +class GroupByDialog; +class SettingsDialog; +class Ui_CollectionFilterWidget; + +struct QueryOptions; + +class QMenu; +class QActionGroup; +class QSignalMapper; + +class CollectionFilterWidget : public QWidget { + Q_OBJECT + + public: + CollectionFilterWidget(QWidget *parent = nullptr); + ~CollectionFilterWidget(); + + static const int kFilterDelay = 500; // msec + + enum DelayBehaviour { + AlwaysInstant, + DelayedOnLargeLibraries, + AlwaysDelayed, + }; + + static QActionGroup *CreateGroupByActions(QObject *parent); + + void UpdateGroupByActions(); + void SetFilterHint(const QString &hint); + void SetApplyFilterToCollection(bool filter_applies_to_model) { filter_applies_to_model_ = filter_applies_to_model; } + void SetDelayBehaviour(DelayBehaviour behaviour) { delay_behaviour_ = behaviour; } + void SetAgeFilterEnabled(bool enabled); + void SetGroupByEnabled(bool enabled); + void ShowInCollection(const QString &search); + + QMenu *menu() const { return collection_menu_; } + void AddMenuAction(QAction *action); + + void SetSettingsGroup(const QString &group) { settings_group_ = group; } + void SetCollectionModel(CollectionModel *model); + + public slots: + void SetQueryMode(QueryOptions::QueryMode view); + void FocusOnFilter(QKeyEvent *e); + +signals: + void UpPressed(); + void DownPressed(); + void ReturnPressed(); + void Filter(const QString &text); + + protected: + void keyReleaseEvent(QKeyEvent *e); + + private slots: + void GroupingChanged(const CollectionModel::Grouping &g); + void GroupByClicked(QAction *action); + void SaveGroupBy(); + void ShowGroupingManager(); + + void FilterTextChanged(const QString &text); + void FilterDelayTimeout(); + + private: + static QAction *CreateGroupByAction(const QString &text, QObject *parent, const CollectionModel::Grouping &grouping); + void CheckCurrentGrouping(const CollectionModel::Grouping &g); + + private: + Ui_CollectionFilterWidget *ui_; + CollectionModel *model_; + + std::unique_ptr group_by_dialog_; + std::unique_ptr groupings_manager_; + SettingsDialog *settings_dialog_; + + QMenu *filter_age_menu_; + QMenu *group_by_menu_; + QMenu *collection_menu_; + QActionGroup *group_by_group_; + QSignalMapper *filter_age_mapper_; + + QTimer *filter_delay_; + + bool filter_applies_to_model_; + DelayBehaviour delay_behaviour_; + + QString settings_group_; +}; + +#endif // COLLECTIONFILTERWIDGET_H + diff --git a/src/collection/collectionfilterwidget.ui b/src/collection/collectionfilterwidget.ui new file mode 100644 index 00000000..c5b4db50 --- /dev/null +++ b/src/collection/collectionfilterwidget.ui @@ -0,0 +1,121 @@ + + + CollectionFilterWidget + + + + 0 + 0 + 400 + 30 + + + + Form + + + + 0 + + + 0 + + + + + <html><head/><body><p>Prefix a word with a field name to limit the search to that field, e.g. <span style=" font-weight:600;">artist:</span><span style=" font-style:italic;">Bode</span> searches the collection for all artists that contain the word Bode.</p><p><span style=" font-weight:600;">Available fields: </span><span style=" font-style:italic;">%1</span>.</p></body></html> + + + Enter search terms here + + + + + + + + 16 + 16 + + + + QToolButton::InstantPopup + + + + + + + true + + + true + + + Entire collection + + + + + true + + + Added today + + + + + true + + + Added this week + + + + + true + + + Added within three months + + + Added within three months + + + + + true + + + Added this year + + + + + true + + + Added this month + + + + + Save current grouping + + + + + Manage saved groupings + + + + + + QSearchField + QWidget +
3rdparty/qocoa/qsearchfield.h
+
+
+ + +
diff --git a/src/collection/collectionitem.h b/src/collection/collectionitem.h new file mode 100644 index 00000000..25c742b2 --- /dev/null +++ b/src/collection/collectionitem.h @@ -0,0 +1,58 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COLLECTIONITEM_H +#define COLLECTIONITEM_H + +#include "config.h" + +#include +#include + +#include "core/simpletreeitem.h" +#include "core/song.h" + +class CollectionItem : public SimpleTreeItem { + public: + enum Type { + Type_Root, + Type_Divider, + Type_Container, + Type_Song, + Type_PlaylistContainer, + Type_LoadingIndicator, + }; + + CollectionItem(SimpleTreeModel *model) + : SimpleTreeItem(Type_Root, model), + container_level(-1), + compilation_artist_node_(nullptr) {} + + CollectionItem(Type type, CollectionItem *parent = nullptr) + : SimpleTreeItem(type, parent), + container_level(-1), + compilation_artist_node_(nullptr) {} + + int container_level; + Song metadata; + CollectionItem *compilation_artist_node_; +}; + +#endif // COLLECTIONITEM_H diff --git a/src/collection/collectionmodel.cpp b/src/collection/collectionmodel.cpp new file mode 100644 index 00000000..57827cdf --- /dev/null +++ b/src/collection/collectionmodel.cpp @@ -0,0 +1,1522 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "collectionmodel.h" + +#include "collectionbackend.h" +#include "collectionitem.h" +#include "collectiondirectorymodel.h" +#include "collectionview.h" +#include "sqlrow.h" +#include "core/application.h" +#include "core/database.h" +#include "core/logging.h" +#include "core/taskmanager.h" +#include "core/utilities.h" +#include "core/iconloader.h" +#include "covermanager/albumcoverloader.h" +#include "playlist/songmimedata.h" + +using std::placeholders::_1; +using std::placeholders::_2; + +const char *CollectionModel::kSavedGroupingsSettingsGroup = "SavedGroupings"; +const int CollectionModel::kPrettyCoverSize = 32; +const qint64 CollectionModel::kIconCacheSize = 100000000; //~100MB + +static bool IsArtistGroupBy(const CollectionModel::GroupBy by) { + //qLog(Debug) << __PRETTY_FUNCTION__; + return by == CollectionModel::GroupBy_Artist || by == CollectionModel::GroupBy_AlbumArtist; +} + +static bool IsCompilationArtistNode(const CollectionItem *node) { + //qLog(Debug) << __PRETTY_FUNCTION__; + return node == node->parent->compilation_artist_node_; +} + +CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, QObject *parent) : + SimpleTreeModel(new CollectionItem(this), parent), + backend_(backend), + app_(app), + dir_model_(new CollectionDirectoryModel(backend, this)), + show_various_artists_(true), + total_song_count_(0), + total_artist_count_(0), + total_album_count_(0), + artist_icon_(IconLoader::Load("guitar")), + album_icon_(IconLoader::Load("cd")), + playlists_dir_icon_(IconLoader::Load("folder-sound")), + playlist_icon_(IconLoader::Load("albums")), + init_task_id_(-1), + use_pretty_covers_(false), + show_dividers_(true) +{ + + //qLog(Debug) << __PRETTY_FUNCTION__; + + root_->lazy_loaded = true; + + group_by_[0] = GroupBy_Artist; + group_by_[1] = GroupBy_Album; + group_by_[2] = GroupBy_None; + + cover_loader_options_.desired_height_ = kPrettyCoverSize; + cover_loader_options_.pad_output_image_ = true; + cover_loader_options_.scale_output_image_ = true; + + connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(AlbumArtLoaded(quint64, QImage))); + + //icon_cache_->setCacheDirectory(Utilities::GetConfigPath(Utilities::Path_CacheRoot) + "/pixmapcache"); + //icon_cache_->setMaximumCacheSize(CollectionModel::kIconCacheSize); + + //QIcon nocover = IconLoader::Load("nocover"); + //QIcon nocover(":/pictures/noalbumart.png"); + //no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + no_cover_icon_ = QPixmap(":/pictures/noalbumart.png").scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + connect(backend_, SIGNAL(SongsDiscovered(SongList)), SLOT(SongsDiscovered(SongList))); + connect(backend_, SIGNAL(SongsDeleted(SongList)), SLOT(SongsDeleted(SongList))); + connect(backend_, SIGNAL(DatabaseReset()), SLOT(Reset())); + connect(backend_, SIGNAL(TotalSongCountUpdated(int)), SLOT(TotalSongCountUpdatedSlot(int))); + connect(backend_, SIGNAL(TotalArtistCountUpdated(int)), SLOT(TotalArtistCountUpdatedSlot(int))); + connect(backend_, SIGNAL(TotalAlbumCountUpdated(int)), SLOT(TotalAlbumCountUpdatedSlot(int))); + + backend_->UpdateTotalSongCountAsync(); + backend_->UpdateTotalArtistCountAsync(); + backend_->UpdateTotalAlbumCountAsync(); + +} + +CollectionModel::~CollectionModel() { delete root_; } + +void CollectionModel::set_pretty_covers(bool use_pretty_covers) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (use_pretty_covers != use_pretty_covers_) { + use_pretty_covers_ = use_pretty_covers; + Reset(); + } +} + +void CollectionModel::set_show_dividers(bool show_dividers) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (show_dividers != show_dividers_) { + show_dividers_ = show_dividers; + Reset(); + } +} + +void CollectionModel::SaveGrouping(QString name) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + qLog(Debug) << "Model, save to: " << name; + + QByteArray buffer; + QDataStream ds(&buffer, QIODevice::WriteOnly); + ds << group_by_; + + QSettings s; + s.beginGroup(kSavedGroupingsSettingsGroup); + s.setValue(name, buffer); + +} + + +void CollectionModel::Init(bool async) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (async) { + // Show a loading indicator in the model. + CollectionItem *loading = new CollectionItem(CollectionItem::Type_LoadingIndicator, root_); + loading->display_text = tr("Loading..."); + loading->lazy_loaded = true; + beginResetModel(); + endResetModel(); + + // Show a loading indicator in the status bar too. + init_task_id_ = app_->task_manager()->StartTask(tr("Loading songs")); + + ResetAsync(); + } + else { + Reset(); + } + +} + +void CollectionModel::SongsDiscovered(const SongList &songs) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + for (const Song &song : songs) { + // Sanity check to make sure we don't add songs that are outside the user's filter + if (!query_options_.Matches(song)) continue; + + // Hey, we've already got that one! + if (song_nodes_.contains(song.id())) continue; + + // Before we can add each song we need to make sure the required container + // items already exist in the tree. These depend on which "group by" + // settings the user has on the collection. Eg. if the user grouped by + // artist and album, we would need to make sure nodes for the song's artist + // and album were already in the tree. + + // Find parent containers in the tree + CollectionItem *container = root_; + for (int i = 0; i < 3; ++i) { + GroupBy type = group_by_[i]; + if (type == GroupBy_None) break; + + // Special case: if the song is a compilation and the current GroupBy + // level is Artists, then we want the Various Artists node :( + if (IsArtistGroupBy(type) && song.is_compilation()) { + if (container->compilation_artist_node_ == nullptr) + CreateCompilationArtistNode(true, container); + container = container->compilation_artist_node_; + } + else { + // Otherwise find the proper container at this level based on the + // item's key + QString key; + switch (type) { + case GroupBy_Album: key = song.album(); break; + case GroupBy_Artist: key = song.artist(); break; + case GroupBy_Composer: key = song.composer(); break; + case GroupBy_Performer: key = song.performer(); break; + case GroupBy_Disc: key = QString::number(song.disc()); break; + case GroupBy_Grouping: key = song.grouping(); break; + case GroupBy_Genre: key = song.genre(); break; + case GroupBy_AlbumArtist: key = song.effective_albumartist(); break; + case GroupBy_Year: + key = QString::number(qMax(0, song.year())); break; + case GroupBy_OriginalYear: + key = QString::number(qMax(0, song.effective_originalyear())); + break; + case GroupBy_YearAlbum: + key = PrettyYearAlbum(qMax(0, song.year()), song.album()); + break; + case GroupBy_OriginalYearAlbum: + key = PrettyYearAlbum(qMax(0, song.effective_originalyear()), + song.album()); + break; + case GroupBy_FileType: + key = song.filetype(); + break; + case GroupBy_Bitrate: + key = song.bitrate(); + break; + case GroupBy_None: + qLog(Error) << "GroupBy_None"; + break; + } + + // Does it exist already? + if (!container_nodes_[i].contains(key)) { + // Create the container + qLog(Debug) << "Adding song:" << song.album(); + container_nodes_[i][key] = ItemFromSong(type, true, i == 0, container, song, i); + } + container = container_nodes_[i][key]; + } + + // If we just created the damn thing then we don't need to continue into + // it any further because it'll get lazy-loaded properly later. + if (!container->lazy_loaded) break; + } + + if (!container->lazy_loaded) continue; + + // We've gone all the way down to the deepest level and everything was + // already lazy loaded, so now we have to create the song in the container. + song_nodes_[song.id()] = ItemFromSong(GroupBy_None, true, false, container, song, -1); + } + +} + +void CollectionModel::SongsSlightlyChanged(const SongList &songs) { + + // This is called if there was a minor change to the songs that will not + // normally require the collection to be restructured. We can just update our + // internal cache of Song objects without worrying about resetting the model. + for (const Song &song : songs) { + if (song_nodes_.contains(song.id())) { + song_nodes_[song.id()]->metadata = song; + } + } + +} + +CollectionItem *CollectionModel::CreateCompilationArtistNode(bool signal, CollectionItem *parent) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (signal) beginInsertRows(ItemToIndex(parent), parent->children.count(), parent->children.count()); + + parent->compilation_artist_node_ = new CollectionItem(CollectionItem::Type_Container, parent); + parent->compilation_artist_node_->compilation_artist_node_ = nullptr; + parent->compilation_artist_node_->key = tr("Various artists"); + parent->compilation_artist_node_->sort_text = " various"; + parent->compilation_artist_node_->container_level = parent->container_level + 1; + + if (signal) endInsertRows(); + + return parent->compilation_artist_node_; + +} + +QString CollectionModel::DividerKey(GroupBy type, CollectionItem *item) const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // Items which are to be grouped under the same divider must produce the + // same divider key. This will only get called for top-level items. + + if (item->sort_text.isEmpty()) return QString(); + + switch (type) { + case GroupBy_Album: + case GroupBy_Artist: + case GroupBy_Composer: + case GroupBy_Performer: + case GroupBy_Disc: + case GroupBy_Grouping: + case GroupBy_Genre: + case GroupBy_AlbumArtist: + case GroupBy_FileType: { + QChar c = item->sort_text[0]; + if (c.isDigit()) return "0"; + if (c == ' ') return QString(); + if (c.decompositionTag() != QChar::NoDecomposition) + return QChar(c.decomposition()[0]); + return c; + } + + case GroupBy_Year: + case GroupBy_OriginalYear: + return SortTextForNumber(item->sort_text.toInt() / 10 * 10); + + case GroupBy_YearAlbum: + return SortTextForNumber(item->metadata.year()); + + case GroupBy_OriginalYearAlbum: + return SortTextForNumber(item->metadata.effective_originalyear()); + + case GroupBy_Bitrate: + return SortTextForNumber(item->metadata.bitrate()); + + case GroupBy_None: + return QString(); + } + qLog(Error) << "Unknown GroupBy type" << type << "for item" << item->display_text; + return QString(); + +} + +QString CollectionModel::DividerDisplayText(GroupBy type, const QString &key) const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // Pretty display text for the dividers. + + switch (type) { + case GroupBy_Album: + case GroupBy_Artist: + case GroupBy_Composer: + case GroupBy_Performer: + case GroupBy_Disc: + case GroupBy_Grouping: + case GroupBy_Genre: + case GroupBy_AlbumArtist: + case GroupBy_FileType: + if (key == "0") return "0-9"; + return key.toUpper(); + + case GroupBy_YearAlbum: + case GroupBy_OriginalYearAlbum: + if (key == "0000") return tr("Unknown"); + return key.toUpper(); + + case GroupBy_Year: + case GroupBy_OriginalYear: + if (key == "0000") return tr("Unknown"); + return QString::number(key.toInt()); // To remove leading 0s + + case GroupBy_Bitrate: + if (key == "000") return tr("Unknown"); + return QString::number(key.toInt()); // To remove leading 0s + + case GroupBy_None: + // fallthrough + ; + } + qLog(Error) << "Unknown GroupBy type" << type << "for divider key" << key; + return QString(); + +} + +void CollectionModel::SongsDeleted(const SongList &songs) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // Delete the actual song nodes first, keeping track of each parent so we + // might check to see if they're empty later. + QSet parents; + for (const Song &song : songs) { + if (song_nodes_.contains(song.id())) { + CollectionItem *node = song_nodes_[song.id()]; + + if (node->parent != root_) parents << node->parent; + + beginRemoveRows(ItemToIndex(node->parent), node->row, node->row); + node->parent->Delete(node->row); + song_nodes_.remove(song.id()); + endRemoveRows(); + } + else { + // If we get here it means some of the songs we want to delete haven't + // been lazy-loaded yet. This is bad, because it would mean that to + // clean up empty parents we would need to lazy-load them all + // individually to see if they're empty. This can take a very long time, + // so better to just reset the model and be done with it. + Reset(); + return; + } + } + + // Now delete empty parents + QSet divider_keys; + while (!parents.isEmpty()) { + // Since we are going to remove elements from the container, we + // need a copy to iterate over. If we iterate over the original, + // the behavior will be undefined. + QSet parents_copy = parents; + for (CollectionItem *node : parents_copy) { + parents.remove(node); + if (node->children.count() != 0) continue; + + // Consider its parent for the next round + if (node->parent != root_) parents << node->parent; + + // Maybe consider its divider node + if (node->container_level == 0) + divider_keys << DividerKey(group_by_[0], node); + + // Special case the Various Artists node + if (IsCompilationArtistNode(node)) + node->parent->compilation_artist_node_ = nullptr; + else + container_nodes_[node->container_level].remove(node->key); + + // It was empty - delete it + beginRemoveRows(ItemToIndex(node->parent), node->row, node->row); + node->parent->Delete(node->row); + endRemoveRows(); + } + } + + // Delete empty dividers + for (const QString ÷r_key : divider_keys) { + if (!divider_nodes_.contains(divider_key)) continue; + + // Look to see if there are any other items still under this divider + bool found = false; + for (CollectionItem *node : container_nodes_[0].values()) { + if (DividerKey(group_by_[0], node) == divider_key) { + found = true; + break; + } + } + + if (found) continue; + + // Remove the divider + int row = divider_nodes_[divider_key]->row; + beginRemoveRows(ItemToIndex(root_), row, row); + root_->Delete(row); + endRemoveRows(); + divider_nodes_.remove(divider_key); + } + +} + +QString CollectionModel::AlbumIconPixmapCacheKey(const QModelIndex &index) const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QStringList path; + QModelIndex index_copy(index); + while (index_copy.isValid()) { + path.prepend(index_copy.data().toString()); + index_copy = index_copy.parent(); + } + + //qLog(Debug) << __PRETTY_FUNCTION__ << "collectionart:" << path.join("/"); + + return "collectionart:" + path.join("/"); + +} + +QVariant CollectionModel::AlbumIcon(const QModelIndex &index) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + CollectionItem *item = IndexToItem(index); + if (!item) return no_cover_icon_; + + // Check the cache for a pixmap we already loaded. + const QString cache_key = AlbumIconPixmapCacheKey(index); + + //qLog(Debug) << cache_key ; + + QPixmap cached_pixmap; + if (QPixmapCache::find(cache_key, &cached_pixmap)) { + return cached_pixmap; + } + +#if 0 + // Try to load it from the disk cache + std::unique_ptr cache(icon_cache_->data(QUrl(cache_key))); + if (cache) { + QImage cached_image; + if (cached_image.load(cache.get(), "XPM")) { + QPixmapCache::insert(cache_key, QPixmap::fromImage(cached_image)); + return QPixmap::fromImage(cached_image); + } + } +#endif + + // Maybe we're loading a pixmap already? + if (pending_cache_keys_.contains(cache_key)) { + return no_cover_icon_; + } + + // No art is cached and we're not loading it already. Load art for the first song in the album. + SongList songs = GetChildSongs(index); + if (!songs.isEmpty()) { + const quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, songs.first()); + pending_art_[id] = ItemAndCacheKey(item, cache_key); + pending_cache_keys_.insert(cache_key); + } + + return no_cover_icon_; + +} + +void CollectionModel::AlbumArtLoaded(quint64 id, const QImage &image) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + ItemAndCacheKey item_and_cache_key = pending_art_.take(id); + CollectionItem *item = item_and_cache_key.first; + const QString &cache_key = item_and_cache_key.second; + + if (!item) return; + + pending_cache_keys_.remove(cache_key); + + // Insert this image in the cache. + if (image.isNull()) { + // Set the no_cover image so we don't continually try to load art. + QPixmapCache::insert(cache_key, no_cover_icon_); + } + else { + //qLog(Debug) << cache_key; + QPixmap image_pixmap; + image_pixmap = QPixmap::fromImage(image); + QPixmapCache::insert(cache_key, image_pixmap); + } + +#if 0 + // if not already in the disk cache + std::unique_ptr cached_img(icon_cache_->data(QUrl(cache_key))); + if (!cached_img && !image.isNull()) { + QNetworkCacheMetaData item_metadata; + item_metadata.setSaveToDisk(true); + item_metadata.setUrl(QUrl(cache_key)); + QIODevice *cache = icon_cache_->prepare(item_metadata); + if (cache) { + image.save(cache, "XPM"); + icon_cache_->insert(cache); + } + } +#endif + + const QModelIndex index = ItemToIndex(item); + emit dataChanged(index, index); + +} + +QVariant CollectionModel::data(const QModelIndex &index, int role) const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + const CollectionItem *item = IndexToItem(index); + + // Handle a special case for returning album artwork instead of a generic CD icon. + // this is here instead of in the other data() function to let us use the + // QModelIndex& version of GetChildSongs, which satisfies const-ness, instead + // of the CollectionItem *version, which doesn't. + if (use_pretty_covers_) { + bool is_album_node = false; + if (role == Qt::DecorationRole && item->type == CollectionItem::Type_Container) { + GroupBy container_type = group_by_[item->container_level]; + is_album_node = container_type == GroupBy_Album || container_type == GroupBy_YearAlbum || container_type == GroupBy_OriginalYearAlbum; + } + if (is_album_node) { + // It has const behaviour some of the time - that's ok right? + return const_cast(this)->AlbumIcon(index); + } + } + + return data(item, role); + +} + +QVariant CollectionModel::data(const CollectionItem *item, int role) const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + GroupBy container_type = item->type == CollectionItem::Type_Container ? group_by_[item->container_level] : GroupBy_None; + + switch (role) { + case Qt::DisplayRole: + case Qt::ToolTipRole: + return item->DisplayText(); + + case Qt::DecorationRole: + switch (item->type) { + case CollectionItem::Type_PlaylistContainer: + return playlists_dir_icon_; + case CollectionItem::Type_Container: + switch (container_type) { + case GroupBy_Album: + case GroupBy_YearAlbum: + case GroupBy_OriginalYearAlbum: + return album_icon_; + case GroupBy_Artist: + case GroupBy_AlbumArtist: + return artist_icon_; + default: + break; + } + break; + default: + break; + } + break; + + case Role_Type: + return item->type; + + case Role_IsDivider: + return item->type == CollectionItem::Type_Divider; + + case Role_ContainerType: + return container_type; + + case Role_Key: + return item->key; + + case Role_Artist: + return item->metadata.artist(); + + case Role_Editable: + if (!item->lazy_loaded) { + const_cast(this)->LazyPopulate(const_cast(item), true); + } + + if (item->type == CollectionItem::Type_Container) { + // if we have even one non editable item as a child, we ourselves + // are not available for edit + if (!item->children.isEmpty()) { + for (CollectionItem *child : item->children) { + if (!data(child, role).toBool()) { + return false; + } + } + return true; + } else { + return false; + } + } + else if (item->type == CollectionItem::Type_Song) { + return item->metadata.IsEditable(); + } + else { + return false; + } + + case Role_SortText: + return item->SortText(); + } + return QVariant(); + +} + +bool CollectionModel::HasCompilations(const CollectionQuery &query) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + CollectionQuery q = query; + q.AddCompilationRequirement(true); + q.SetLimit(1); + + QMutexLocker l(backend_->db()->Mutex()); + if (!backend_->ExecQuery(&q)) return false; + + return q.Next(); + +} + +CollectionModel::QueryResult CollectionModel::RunQuery(CollectionItem *parent) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QueryResult result; + + // Information about what we want the children to be + int child_level = parent == root_ ? 0 : parent->container_level + 1; + GroupBy child_type = child_level >= 3 ? GroupBy_None : group_by_[child_level]; + + // Initialise the query. child_type says what type of thing we want (artists, + // songs, etc.) + CollectionQuery q(query_options_); + InitQuery(child_type, &q); + + // Walk up through the item's parents adding filters as necessary + CollectionItem *p = parent; + while (p && p->type == CollectionItem::Type_Container) { + FilterQuery(group_by_[p->container_level], p, &q); + p = p->parent; + } + + // Artists GroupBy is special - we don't want compilation albums appearing + if (IsArtistGroupBy(child_type)) { + // Add the special Various artists node + if (show_various_artists_ && HasCompilations(q)) { + result.create_va = true; + } + + // Don't show compilations again outside the Various artists node + q.AddCompilationRequirement(false); + } + + // Execute the query + QMutexLocker l(backend_->db()->Mutex()); + if (!backend_->ExecQuery(&q)) return result; + + while (q.Next()) { + result.rows << SqlRow(q); + } + return result; + +} + +void CollectionModel::PostQuery(CollectionItem *parent, const CollectionModel::QueryResult &result, bool signal) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // Information about what we want the children to be + int child_level = parent == root_ ? 0 : parent->container_level + 1; + GroupBy child_type = child_level >= 3 ? GroupBy_None : group_by_[child_level]; + + if (result.create_va) { + CreateCompilationArtistNode(signal, parent); + } + + // Step through the results + for (const SqlRow &row : result.rows) { + // Create the item - it will get inserted into the model here + CollectionItem *item = ItemFromQuery(child_type, signal, child_level == 0, parent, row, child_level); + + // Save a pointer to it for later + if (child_type == GroupBy_None) + song_nodes_[item->metadata.id()] = item; + else + container_nodes_[child_level][item->key] = item; + } + +} + +void CollectionModel::LazyPopulate(CollectionItem *parent, bool signal) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (parent->lazy_loaded) return; + parent->lazy_loaded = true; + + QueryResult result = RunQuery(parent); + PostQuery(parent, result, signal); + +} + +void CollectionModel::ResetAsync() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QFuture future = QtConcurrent::run(this, &CollectionModel::RunQuery, root_); + NewClosure(future, this, SLOT(ResetAsyncQueryFinished(QFuture)), future); + +} + +void CollectionModel::ResetAsyncQueryFinished(QFuture future) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + const struct QueryResult result = future.result(); + + BeginReset(); + root_->lazy_loaded = true; + + PostQuery(root_, result, false); + + if (init_task_id_ != -1) { + app_->task_manager()->SetTaskFinished(init_task_id_); + init_task_id_ = -1; + } + + endResetModel(); + +} + +void CollectionModel::BeginReset() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + beginResetModel(); + delete root_; + song_nodes_.clear(); + container_nodes_[0].clear(); + container_nodes_[1].clear(); + container_nodes_[2].clear(); + divider_nodes_.clear(); + pending_art_.clear(); + + root_ = new CollectionItem(this); + root_->compilation_artist_node_ = nullptr; + root_->lazy_loaded = false; + +} + +void CollectionModel::Reset() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + BeginReset(); + + // Populate top level + LazyPopulate(root_, false); + + endResetModel(); + +} + +void CollectionModel::InitQuery(GroupBy type, CollectionQuery *q) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // Say what type of thing we want to get back from the database. + switch (type) { + case GroupBy_Artist: + q->SetColumnSpec("DISTINCT artist"); + break; + case GroupBy_Album: + q->SetColumnSpec("DISTINCT album"); + break; + case GroupBy_Composer: + q->SetColumnSpec("DISTINCT composer"); + break; + case GroupBy_Performer: + q->SetColumnSpec("DISTINCT performer"); + break; + case GroupBy_Disc: + q->SetColumnSpec("DISTINCT disc"); + break; + case GroupBy_Grouping: + q->SetColumnSpec("DISTINCT grouping"); + break; + case GroupBy_YearAlbum: + q->SetColumnSpec("DISTINCT year, album, grouping"); + break; + case GroupBy_OriginalYearAlbum: + q->SetColumnSpec("DISTINCT year, originalyear, album, grouping"); + break; + case GroupBy_Year: + q->SetColumnSpec("DISTINCT year"); + break; + case GroupBy_OriginalYear: + q->SetColumnSpec("DISTINCT effective_originalyear"); + break; + case GroupBy_Genre: + q->SetColumnSpec("DISTINCT genre"); + break; + case GroupBy_AlbumArtist: + q->SetColumnSpec("DISTINCT effective_albumartist"); + break; + case GroupBy_Bitrate: + q->SetColumnSpec("DISTINCT bitrate"); + break; + case GroupBy_None: + q->SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec); + break; + case GroupBy_FileType: + q->SetColumnSpec("DISTINCT filetype"); + break; + } + +} + +void CollectionModel::FilterQuery(GroupBy type, CollectionItem *item, CollectionQuery *q) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // Say how we want the query to be filtered. This is done once for each + // parent going up the tree. + + switch (type) { + case GroupBy_Artist: + if (IsCompilationArtistNode(item)) + q->AddCompilationRequirement(true); + else { + // Don't duplicate compilations outside the Various artists node + q->AddCompilationRequirement(false); + q->AddWhere("artist", item->key); + } + break; + case GroupBy_Album: + q->AddWhere("album", item->key); + break; + case GroupBy_YearAlbum: + q->AddWhere("year", item->metadata.year()); + q->AddWhere("album", item->metadata.album()); + q->AddWhere("grouping", item->metadata.grouping()); + break; + case GroupBy_OriginalYearAlbum: + q->AddWhere("year", item->metadata.year()); + q->AddWhere("originalyear", item->metadata.originalyear()); + q->AddWhere("album", item->metadata.album()); + q->AddWhere("grouping", item->metadata.grouping()); + break; + + case GroupBy_Year: + q->AddWhere("year", item->key); + break; + case GroupBy_OriginalYear: + q->AddWhere("effective_originalyear", item->key); + break; + case GroupBy_Composer: + q->AddWhere("composer", item->key); + break; + case GroupBy_Performer: + q->AddWhere("performer", item->key); + break; + case GroupBy_Disc: + q->AddWhere("disc", item->key); + break; + case GroupBy_Grouping: + q->AddWhere("grouping", item->key); + break; + case GroupBy_Genre: + q->AddWhere("genre", item->key); + break; + case GroupBy_AlbumArtist: + if (IsCompilationArtistNode(item)) + q->AddCompilationRequirement(true); + else { + // Don't duplicate compilations outside the Various artists node + q->AddCompilationRequirement(false); + q->AddWhere("effective_albumartist", item->key); + } + break; + case GroupBy_FileType: + q->AddWhere("filetype", item->metadata.filetype()); + break; + case GroupBy_Bitrate: + q->AddWhere("bitrate", item->key); + break; + case GroupBy_None: + qLog(Error) << "Unknown GroupBy type" << type << "used in filter"; + break; + } + +} + +CollectionItem *CollectionModel::InitItem(GroupBy type, bool signal, CollectionItem *parent, int container_level) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + CollectionItem::Type item_type = type == GroupBy_None ? CollectionItem::Type_Song : CollectionItem::Type_Container; + + if (signal) beginInsertRows(ItemToIndex(parent), parent->children.count(),parent->children.count()); + + // Initialise the item depending on what type it's meant to be + CollectionItem *item = new CollectionItem(item_type, parent); + item->compilation_artist_node_ = nullptr; + item->container_level = container_level; + + //qLog(Debug) << __PRETTY_FUNCTION__ << "end"; + + return item; + +} + +CollectionItem *CollectionModel::ItemFromQuery(GroupBy type, bool signal, bool create_divider, CollectionItem *parent, const SqlRow &row, int container_level) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + CollectionItem *item = InitItem(type, signal, parent, container_level); + int year = 0; + int effective_originalyear = 0; + int bitrate = 0; + int disc = 0; + + switch (type) { + case GroupBy_Artist: + item->key = row.value(0).toString(); + item->display_text = TextOrUnknown(item->key); + item->sort_text = SortTextForArtist(item->key); + break; + + case GroupBy_YearAlbum: + year = qMax(0, row.value(0).toInt()); + item->metadata.set_year(row.value(0).toInt()); + item->metadata.set_album(row.value(1).toString()); + item->metadata.set_grouping(row.value(2).toString()); + item->key = PrettyYearAlbum(year, item->metadata.album()); + item->sort_text = SortTextForNumber(year) + item->metadata.grouping() + item->metadata.album(); + break; + + case GroupBy_OriginalYearAlbum: + item->metadata.set_year(row.value(0).toInt()); + item->metadata.set_originalyear(row.value(1).toInt()); + item->metadata.set_album(row.value(2).toString()); + item->metadata.set_grouping(row.value(3).toString()); + effective_originalyear = qMax(0, item->metadata.effective_originalyear()); + item->key = PrettyYearAlbum(effective_originalyear, item->metadata.album()); + item->sort_text = SortTextForNumber(effective_originalyear) + item->metadata.grouping() + item->metadata.album(); + break; + + case GroupBy_Year: + year = qMax(0, row.value(0).toInt()); + item->key = QString::number(year); + item->sort_text = SortTextForNumber(year) + " "; + break; + + case GroupBy_OriginalYear: + year = qMax(0, row.value(0).toInt()); + item->key = QString::number(year); + item->sort_text = SortTextForNumber(year) + " "; + break; + + case GroupBy_Composer: + case GroupBy_Performer: + case GroupBy_Grouping: + case GroupBy_Genre: + case GroupBy_Album: + case GroupBy_AlbumArtist: + item->key = row.value(0).toString(); + item->display_text = TextOrUnknown(item->key); + item->sort_text = SortTextForArtist(item->key); + break; + + case GroupBy_Disc: + disc = row.value(0).toInt(); + item->key = QString::number(disc); + item->sort_text = SortTextForNumber(disc); + break; + + case GroupBy_FileType: + item->metadata.set_filetype(Song::FileType(row.value(0).toInt())); + item->key = item->metadata.TextForFiletype(); + break; + + case GroupBy_Bitrate: + bitrate = qMax(0, row.value(0).toInt()); + item->key = QString::number(bitrate); + item->sort_text = SortTextForNumber(bitrate) + " "; + break; + + case GroupBy_None: + item->metadata.InitFromQuery(row, true); + item->key = item->metadata.title(); + item->display_text = item->metadata.TitleWithCompilationArtist(); + item->sort_text = SortTextForSong(item->metadata); + break; + } + + FinishItem(type, signal, create_divider, parent, item); + + //qLog(Debug) << __PRETTY_FUNCTION__ << "end"; + + return item; + +} + +CollectionItem *CollectionModel::ItemFromSong(GroupBy type, bool signal, bool create_divider, CollectionItem *parent, const Song &s, int container_level) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + CollectionItem *item = InitItem(type, signal, parent, container_level); + int year = 0; + int originalyear = 0; + int effective_originalyear = 0; + int bitrate = 0; + + switch (type) { + case GroupBy_Artist: + item->key = s.artist(); + item->display_text = TextOrUnknown(item->key); + item->sort_text = SortTextForArtist(item->key); + break; + + case GroupBy_YearAlbum: + year = qMax(0, s.year()); + item->metadata.set_year(year); + item->metadata.set_album(s.album()); + item->key = PrettyYearAlbum(year, s.album()); + item->sort_text = SortTextForNumber(year) + s.grouping() + s.album(); + break; + + case GroupBy_OriginalYearAlbum: + year = qMax(0, s.year()); + originalyear = qMax(0, s.originalyear()); + effective_originalyear = qMax(0, s.effective_originalyear()); + item->metadata.set_year(year); + item->metadata.set_originalyear(originalyear); + item->metadata.set_album(s.album()); + item->key = PrettyYearAlbum(effective_originalyear, s.album()); + item->sort_text = SortTextForNumber(effective_originalyear) + s.grouping() + s.album(); + break; + + case GroupBy_Year: + year = qMax(0, s.year()); + item->key = QString::number(year); + item->sort_text = SortTextForNumber(year) + " "; + break; + + case GroupBy_OriginalYear: + year = qMax(0, s.effective_originalyear()); + item->key = QString::number(year); + item->sort_text = SortTextForNumber(year) + " "; + break; + + case GroupBy_Composer: item->key = s.composer(); + case GroupBy_Performer: item->key = s.performer(); + case GroupBy_Grouping: item->key = s.grouping(); + case GroupBy_Genre: if (item->key.isNull()) item->key = s.genre(); + case GroupBy_Album: if (item->key.isNull()) item->key = s.album(); + case GroupBy_AlbumArtist: if (item->key.isNull()) item->key = s.effective_albumartist(); + item->display_text = TextOrUnknown(item->key); + item->sort_text = SortTextForArtist(item->key); + break; + + case GroupBy_Disc: + item->key = QString::number(s.disc()); + item->sort_text = SortTextForNumber(s.disc()); + break; + + case GroupBy_FileType: + item->metadata.set_filetype(s.filetype()); + item->key = s.TextForFiletype(); + break; + + case GroupBy_Bitrate: + bitrate = qMax(0, s.bitrate()); + item->key = QString::number(bitrate); + item->sort_text = SortTextForNumber(bitrate) + " "; + break; + + case GroupBy_None: + item->metadata = s; + item->key = s.title(); + item->display_text = s.TitleWithCompilationArtist(); + item->sort_text = SortTextForSong(s); + break; + } + +// qLog(Debug) << s.url().scheme(); + + FinishItem(type, signal, create_divider, parent, item); + if (s.url().scheme() == "cdda") item->lazy_loaded = true; + return item; + +} + +void CollectionModel::FinishItem(GroupBy type, bool signal, bool create_divider, CollectionItem *parent, CollectionItem *item) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (type == GroupBy_None) item->lazy_loaded = true; + + if (signal) endInsertRows(); + + // Create the divider entry if we're supposed to + if (create_divider && show_dividers_) { + QString divider_key = DividerKey(type, item); + item->sort_text.prepend(divider_key); + + if (!divider_key.isEmpty() && !divider_nodes_.contains(divider_key)) { + if (signal) + beginInsertRows(ItemToIndex(parent), parent->children.count(), parent->children.count()); + + CollectionItem *divider = new CollectionItem(CollectionItem::Type_Divider, root_); + divider->key = divider_key; + divider->display_text = DividerDisplayText(type, divider_key); + divider->lazy_loaded = true; + + divider_nodes_[divider_key] = divider; + + if (signal) endInsertRows(); + } + } + +} + +QString CollectionModel::TextOrUnknown(const QString &text) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (text.isEmpty()) return tr("Unknown"); + return text; + +} + +QString CollectionModel::PrettyYearAlbum(int year, const QString &album) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (year <= 0) return TextOrUnknown(album); + return QString::number(year) + " - " + TextOrUnknown(album); + +} + +QString CollectionModel::SortText(QString text) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (text.isEmpty()) { + text = " unknown"; + } + else { + text = text.toLower(); + } + text = text.remove(QRegExp("[^\\w ]")); + + return text; + +} + +QString CollectionModel::SortTextForArtist(QString artist) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + artist = SortText(artist); + + if (artist.startsWith("the ")) { + artist = artist.right(artist.length() - 4) + ", the"; + } + else if (artist.startsWith("a ")) { + artist = artist.right(artist.length() - 2) + ", a"; + } + else if (artist.startsWith("an ")) { + artist = artist.right(artist.length() - 3) + ", an"; + } + + return artist; + +} + +QString CollectionModel::SortTextForNumber(int number) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + return QString("%1").arg(number, 4, 10, QChar('0')); +} + +QString CollectionModel::SortTextForYear(int year) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QString str = QString::number(year); + return QString("0").repeated(qMax(0, 4 - str.length())) + str; + +} + +QString CollectionModel::SortTextForBitrate(int bitrate) { + + QString str = QString::number(bitrate); + return QString("0").repeated(qMax(0, 3 - str.length())) + str; + +} + +QString CollectionModel::SortTextForSong(const Song &song) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QString ret = QString::number(qMax(0, song.disc()) * 1000 + qMax(0, song.track())); + ret.prepend(QString("0").repeated(6 - ret.length())); + ret.append(song.url().toString()); + return ret; + +} + +Qt::ItemFlags CollectionModel::flags(const QModelIndex &index) const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + switch (IndexToItem(index)->type) { + case CollectionItem::Type_Song: + case CollectionItem::Type_Container: + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled; + case CollectionItem::Type_Divider: + case CollectionItem::Type_Root: + case CollectionItem::Type_LoadingIndicator: + default: + return Qt::ItemIsEnabled; + } + +} + +QStringList CollectionModel::mimeTypes() const { + return QStringList() << "text/uri-list"; +} + +QMimeData *CollectionModel::mimeData(const QModelIndexList &indexes) const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (indexes.isEmpty()) return nullptr; + + SongMimeData *data = new SongMimeData; + QList urls; + QSet song_ids; + + data->backend = backend_; + + for (const QModelIndex &index : indexes) { + GetChildSongs(IndexToItem(index), &urls, &data->songs, &song_ids); + } + + data->setUrls(urls); + data->name_for_new_playlist_ = PlaylistManager::GetNameForNewPlaylist(data->songs); + + return data; + +} + +bool CollectionModel::CompareItems(const CollectionItem *a, const CollectionItem *b) const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QVariant left(data(a, CollectionModel::Role_SortText)); + QVariant right(data(b, CollectionModel::Role_SortText)); + + if (left.type() == QVariant::Int) return left.toInt() < right.toInt(); + return left.toString() < right.toString(); + +} + +void CollectionModel::GetChildSongs(CollectionItem *item, QList *urls, SongList *songs, QSet *song_ids) const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + switch (item->type) { + case CollectionItem::Type_Container: { + const_cast(this)->LazyPopulate(item); + + QList children = item->children; + qSort(children.begin(), children.end(), std::bind(&CollectionModel::CompareItems, this, _1, _2)); + + for (CollectionItem *child : children) + GetChildSongs(child, urls, songs, song_ids); + break; + } + + case CollectionItem::Type_Song: + urls->append(item->metadata.url()); + if (!song_ids->contains(item->metadata.id())) { + songs->append(item->metadata); + song_ids->insert(item->metadata.id()); + } + break; + + default: + break; + } + +} + +SongList CollectionModel::GetChildSongs(const QModelIndexList &indexes) const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QList dontcare; + SongList ret; + QSet song_ids; + + for (const QModelIndex &index : indexes) { + GetChildSongs(IndexToItem(index), &dontcare, &ret, &song_ids); + } + return ret; + +} + +SongList CollectionModel::GetChildSongs(const QModelIndex &index) const { + //qLog(Debug) << __PRETTY_FUNCTION__; + return GetChildSongs(QModelIndexList() << index); +} + +void CollectionModel::SetFilterAge(int age) { + //qLog(Debug) << __PRETTY_FUNCTION__; + query_options_.set_max_age(age); + ResetAsync(); +} + +void CollectionModel::SetFilterText(const QString &text) { + //qLog(Debug) << __PRETTY_FUNCTION__; + query_options_.set_filter(text); + ResetAsync(); + +} + +void CollectionModel::SetFilterQueryMode(QueryOptions::QueryMode query_mode) { + //qLog(Debug) << __PRETTY_FUNCTION__; + query_options_.set_query_mode(query_mode); + ResetAsync(); + +} + +bool CollectionModel::canFetchMore(const QModelIndex &parent) const { + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (!parent.isValid()) return false; + + CollectionItem *item = IndexToItem(parent); + return !item->lazy_loaded; + +} + +void CollectionModel::SetGroupBy(const Grouping &g) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + group_by_ = g; + + ResetAsync(); + emit GroupingChanged(g); + +} + +const CollectionModel::GroupBy &CollectionModel::Grouping::operator[](int i) const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + switch (i) { + case 0: return first; + case 1: return second; + case 2: return third; + } + qLog(Error) << "CollectionModel::Grouping[] index out of range" << i; + return first; + +} + +CollectionModel::GroupBy &CollectionModel::Grouping::operator[](int i) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + switch (i) { + case 0: return first; + case 1: return second; + case 2: return third; + } + qLog(Error) << "CollectionModel::Grouping[] index out of range" << i; + + return first; + +} + + +void CollectionModel::TotalSongCountUpdatedSlot(int count) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + total_song_count_ = count; + emit TotalSongCountUpdated(count); + +} + +void CollectionModel::TotalArtistCountUpdatedSlot(int count) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + total_artist_count_ = count; + emit TotalArtistCountUpdated(count); + +} + +void CollectionModel::TotalAlbumCountUpdatedSlot(int count) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + total_album_count_ = count; + emit TotalAlbumCountUpdated(count); + +} + +QDataStream &operator<<(QDataStream &s, const CollectionModel::Grouping &g) { + //qLog(Debug) << __PRETTY_FUNCTION__; + s << quint32(g.first) << quint32(g.second) << quint32(g.third); + return s; +} + +QDataStream &operator>>(QDataStream &s, CollectionModel::Grouping &g) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + quint32 buf; + s >> buf; + g.first = CollectionModel::GroupBy(buf); + s >> buf; + g.second = CollectionModel::GroupBy(buf); + s >> buf; + g.third = CollectionModel::GroupBy(buf); + return s; + +} + diff --git a/src/collection/collectionmodel.h b/src/collection/collectionmodel.h new file mode 100644 index 00000000..1f8a7d30 --- /dev/null +++ b/src/collection/collectionmodel.h @@ -0,0 +1,284 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COLLECTIONMODEL_H +#define COLLECTIONMODEL_H + +#include "config.h" + +#include +#include +#include + +#include "collectionitem.h" +#include "collectionquery.h" +#include "collectionwatcher.h" +#include "sqlrow.h" +#include "core/simpletreemodel.h" +#include "core/song.h" +#include "covermanager/albumcoverloaderoptions.h" +#include "engine/engine_fwd.h" +#include "playlist/playlistmanager.h" + +class Application; +class AlbumCoverLoader; +class CollectionDirectoryModel; +class CollectionBackend; + +class QSettings; + +class CollectionModel : public SimpleTreeModel { + Q_OBJECT + Q_ENUMS(GroupBy); + + public: + CollectionModel(CollectionBackend *backend, Application *app, QObject *parent = nullptr); + ~CollectionModel(); + + static const char *kSavedGroupingsSettingsGroup; + + static const int kPrettyCoverSize; + static const qint64 kIconCacheSize; + + enum Role { + Role_Type = Qt::UserRole + 1, + Role_ContainerType, + Role_SortText, + Role_Key, + Role_Artist, + Role_IsDivider, + Role_Editable, + LastRole + }; + + // These values get saved in QSettings - don't change them + enum GroupBy { + GroupBy_None = 0, + GroupBy_Artist = 1, + GroupBy_Album = 2, + GroupBy_YearAlbum = 3, + GroupBy_Year = 4, + GroupBy_Composer = 5, + GroupBy_Genre = 6, + GroupBy_AlbumArtist = 7, + GroupBy_FileType = 8, + GroupBy_Performer = 9, + GroupBy_Grouping = 10, + GroupBy_Bitrate = 11, + GroupBy_Disc = 12, + GroupBy_OriginalYearAlbum = 13, + GroupBy_OriginalYear = 14, + }; + + struct Grouping { + Grouping(GroupBy f = GroupBy_None, GroupBy s = GroupBy_None, GroupBy t = GroupBy_None) + : first(f), second(s), third(t) {} + + GroupBy first; + GroupBy second; + GroupBy third; + + const GroupBy &operator[](int i) const; + GroupBy &operator[](int i); + bool operator==(const Grouping &other) const { + return first == other.first && second == other.second && third == other.third; + } + bool operator!=(const Grouping &other) const { return !(*this == other); } + }; + + struct QueryResult { + QueryResult() : create_va(false) {} + + SqlRowList rows; + bool create_va; + }; + + CollectionBackend *backend() const { return backend_; } + CollectionDirectoryModel *directory_model() const { return dir_model_; } + + // Call before Init() + void set_show_various_artists(bool show_various_artists) { show_various_artists_ = show_various_artists; } + + // Get information about the collection + void GetChildSongs(CollectionItem *item, QList *urls, SongList *songs, QSet *song_ids) const; + SongList GetChildSongs(const QModelIndex &index) const; + SongList GetChildSongs(const QModelIndexList &indexes) const; + + // Might be accurate + int total_song_count() const { return total_song_count_; } + int total_artist_count() const { return total_artist_count_; } + int total_album_count() const { return total_album_count_; } + + // QAbstractItemModel + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool canFetchMore(const QModelIndex &parent) const; + + // Whether or not to use album cover art, if it exists, in the collection view + void set_pretty_covers(bool use_pretty_covers); + bool use_pretty_covers() const { return use_pretty_covers_; } + + // Whether or not to show letters heading in the collection view + void set_show_dividers(bool show_dividers); + + // Save the current grouping + void SaveGrouping(QString name); + + // Utility functions for manipulating text + static QString TextOrUnknown(const QString &text); + static QString PrettyYearAlbum(int year, const QString &album); + static QString SortText(QString text); + static QString SortTextForNumber(int year); + static QString SortTextForArtist(QString artist); + static QString SortTextForSong(const Song &song); + static QString SortTextForYear(int year); + static QString SortTextForBitrate(int bitrate); + +signals: + void TotalSongCountUpdated(int count); + void TotalArtistCountUpdated(int count); + void TotalAlbumCountUpdated(int count); + void GroupingChanged(const CollectionModel::Grouping &g); + + public slots: + void SetFilterAge(int age); + void SetFilterText(const QString &text); + void SetFilterQueryMode(QueryOptions::QueryMode query_mode); + + void SetGroupBy(const CollectionModel::Grouping &g); + const CollectionModel::Grouping &GetGroupBy() const { return group_by_; } + void Init(bool async = true); + void Reset(); + void ResetAsync(); + + protected: + void LazyPopulate(CollectionItem *item) { LazyPopulate(item, true); } + void LazyPopulate(CollectionItem *item, bool signal); + + private slots: + // From CollectionBackend + void SongsDiscovered(const SongList &songs); + void SongsDeleted(const SongList &songs); + void SongsSlightlyChanged(const SongList &songs); + void TotalSongCountUpdatedSlot(int count); + void TotalArtistCountUpdatedSlot(int count); + void TotalAlbumCountUpdatedSlot(int count); + + // Called after ResetAsync + void ResetAsyncQueryFinished(QFuture future); + + void AlbumArtLoaded(quint64 id, const QImage &image); + + private: + // Provides some optimisations for loading the list of items in the root. + // This gets called a lot when filtering the playlist, so it's nice to be + // able to do it in a background thread. + QueryResult RunQuery(CollectionItem *parent); + void PostQuery(CollectionItem *parent, const QueryResult &result, bool signal); + + bool HasCompilations(const CollectionQuery &query); + + void BeginReset(); + + // Functions for working with queries and creating items. + // When the model is reset or when a node is lazy-loaded the Collection + // constructs a database query to populate the items. Filters are added + // for each parent item, restricting the songs returned to a particular + // album or artist for example. + static void InitQuery(GroupBy type, CollectionQuery *q); + void FilterQuery(GroupBy type, CollectionItem *item, CollectionQuery *q); + + // Items can be created either from a query that's been run to populate a + // node, or by a spontaneous SongsDiscovered emission from the backend. + CollectionItem *ItemFromQuery(GroupBy type, bool signal, bool create_divider, CollectionItem *parent, const SqlRow &row, int container_level); + CollectionItem *ItemFromSong(GroupBy type, bool signal, bool create_divider, CollectionItem *parent, const Song &s, int container_level); + + // The "Various Artists" node is an annoying special case. + CollectionItem *CreateCompilationArtistNode(bool signal, CollectionItem *parent); + + // Smart playlists are shown in another top-level node + + void ItemFromSmartPlaylist(const QSettings &s, bool notify) const; + + // Helpers for ItemFromQuery and ItemFromSong + CollectionItem *InitItem(GroupBy type, bool signal, CollectionItem *parent, int container_level); + void FinishItem(GroupBy type, bool signal, bool create_divider, CollectionItem *parent, CollectionItem *item); + + QString DividerKey(GroupBy type, CollectionItem *item) const; + QString DividerDisplayText(GroupBy type, const QString &key) const; + + // Helpers + QString AlbumIconPixmapCacheKey(const QModelIndex &index) const; + QVariant AlbumIcon(const QModelIndex &index); + QVariant data(const CollectionItem *item, int role) const; + bool CompareItems(const CollectionItem *a, const CollectionItem *b) const; + + private: + CollectionBackend *backend_; + Application *app_; + CollectionDirectoryModel *dir_model_; + bool show_various_artists_; + + int total_song_count_; + int total_artist_count_; + int total_album_count_; + + QueryOptions query_options_; + Grouping group_by_; + + // Keyed on database ID + QMap song_nodes_; + + // Keyed on whatever the key is for that level - artist, album, year, etc. + QMap container_nodes_[3]; + + // Keyed on a letter, a year, a century, etc. + QMap divider_nodes_; + + QIcon artist_icon_; + QIcon album_icon_; + // used as a generic icon to show when no cover art is found, + // fixed to the same size as the artwork (32x32) + QPixmap no_cover_icon_; + QIcon playlists_dir_icon_; + QIcon playlist_icon_; + + QNetworkDiskCache *icon_cache_; + + int init_task_id_; + + bool use_pretty_covers_; + bool show_dividers_; + + AlbumCoverLoaderOptions cover_loader_options_; + + typedef QPair ItemAndCacheKey; + QMap pending_art_; + QSet pending_cache_keys_; +}; + +Q_DECLARE_METATYPE(CollectionModel::Grouping); + +QDataStream &operator<<(QDataStream &s, const CollectionModel::Grouping &g); +QDataStream &operator>>(QDataStream &s, CollectionModel::Grouping &g); + +#endif // COLLECTIONMODEL_H diff --git a/src/collection/collectionplaylistitem.cpp b/src/collection/collectionplaylistitem.cpp new file mode 100644 index 00000000..69f2c9d0 --- /dev/null +++ b/src/collection/collectionplaylistitem.cpp @@ -0,0 +1,58 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "collectionplaylistitem.h" +#include "core/tagreaderclient.h" + +#include + +CollectionPlaylistItem::CollectionPlaylistItem(const QString &type) + : PlaylistItem(type) {} + +CollectionPlaylistItem::CollectionPlaylistItem(const Song &song) + : PlaylistItem("Collection"), song_(song) {} + +QUrl CollectionPlaylistItem::Url() const { return song_.url(); } + +void CollectionPlaylistItem::Reload() { + TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_); +} + +bool CollectionPlaylistItem::InitFromQuery(const SqlRow &query) { + // Rows from the songs tables come first + song_.InitFromQuery(query, true); + + return song_.is_valid(); +} + +QVariant CollectionPlaylistItem::DatabaseValue(DatabaseColumn column) const { + switch (column) { + case Column_CollectionId: return song_.id(); + default: return PlaylistItem::DatabaseValue(column); + } +} + +Song CollectionPlaylistItem::Metadata() const { + if (HasTemporaryMetadata()) return temp_metadata_; + return song_; +} + diff --git a/src/collection/collectionplaylistitem.h b/src/collection/collectionplaylistitem.h new file mode 100644 index 00000000..7e72973d --- /dev/null +++ b/src/collection/collectionplaylistitem.h @@ -0,0 +1,52 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COLLECTIONPLAYLISTITEM_H +#define COLLECTIONPLAYLISTITEM_H + +#include "config.h" + +#include "core/song.h" +#include "playlist/playlistitem.h" + +class CollectionPlaylistItem : public PlaylistItem { + public: + CollectionPlaylistItem(const QString &type); + CollectionPlaylistItem(const Song &song); + + bool InitFromQuery(const SqlRow &query); + void Reload(); + + Song Metadata() const; + void SetMetadata(const Song &song) { song_ = song; } + + QUrl Url() const; + + bool IsLocalCollectionItem() const { return true; } + + protected: + QVariant DatabaseValue(DatabaseColumn column) const; + + protected: + Song song_; +}; + +#endif // COLLECTIONPLAYLISTITEM_H + diff --git a/src/collection/collectionquery.cpp b/src/collection/collectionquery.cpp new file mode 100644 index 00000000..01a1644e --- /dev/null +++ b/src/collection/collectionquery.cpp @@ -0,0 +1,204 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "collectionquery.h" +#include "core/song.h" + +#include +#include +#include + +QueryOptions::QueryOptions() : max_age_(-1), query_mode_(QueryMode_All) {} + +CollectionQuery::CollectionQuery(const QueryOptions& options) + : include_unavailable_(false), join_with_fts_(false), limit_(-1) { + + if (!options.filter().isEmpty()) { + // We need to munge the filter text a little bit to get it to work as + // expected with sqlite's FTS3: + // 1) Append * to all tokens. + // 2) Prefix "fts" to column names. + // 3) Remove colons which don't correspond to column names. + + // Split on whitespace + QStringList tokens(options.filter().split(QRegExp("\\s+"), QString::SkipEmptyParts)); + QString query; + for (QString token : tokens) { + token.remove('('); + token.remove(')'); + token.remove('"'); + token.replace('-', ' '); + + if (token.contains(':')) { + // Only prefix fts if the token is a valid column name. + if (Song::kFtsColumns.contains("fts" + token.section(':', 0, 0), + Qt::CaseInsensitive)) { + // Account for multiple colons. + QString columntoken = token.section(':', 0, 0, QString::SectionIncludeTrailingSep); + QString subtoken = token.section(':', 1, -1); + subtoken.replace(":", " "); + subtoken = subtoken.trimmed(); + query += "fts" + columntoken + subtoken + "* "; + } + else { + token.replace(":", " "); + token = token.trimmed(); + query += token + "* "; + } + } + else { + query += token + "* "; + } + } + + where_clauses_ << "fts.%fts_table_noprefix MATCH ?"; + bound_values_ << query; + join_with_fts_ = true; + } + + if (options.max_age() != -1) { + int cutoff = QDateTime::currentDateTime().toTime_t() - options.max_age(); + + where_clauses_ << "ctime > ?"; + bound_values_ << cutoff; + } + + // TODO: currently you cannot use any QueryMode other than All and fts at the + // same time. + // joining songs, duplicated_songs and songs_fts all together takes a huge + // amount of + // time. the query takes about 20 seconds on my machine then. why? + // untagged mode could work with additional filtering but I'm disabling it + // just to be + // consistent - this way filtering is available only in the All mode. + // remember though that when you fix the Duplicates + FTS cooperation, enable + // the filtering in both Duplicates and Untagged modes. + duplicates_only_ = options.query_mode() == QueryOptions::QueryMode_Duplicates; + + if (options.query_mode() == QueryOptions::QueryMode_Untagged) { + where_clauses_ << "(artist = '' OR album = '' OR title ='')"; + } + +} + +QString CollectionQuery::GetInnerQuery() { + return duplicates_only_ + ? QString(" INNER JOIN (select * from duplicated_songs) dsongs " + "ON (%songs_table.artist = dsongs.dup_artist " + "AND %songs_table.album = dsongs.dup_album " + "AND %songs_table.title = dsongs.dup_title) ") + : QString(); +} + +void CollectionQuery::AddWhere(const QString &column, const QVariant &value, const QString &op) { + + // ignore 'literal' for IN + if (!op.compare("IN", Qt::CaseInsensitive)) { + QStringList final; + for (const QString& single_value : value.toStringList()) { + final.append("?"); + bound_values_ << single_value; + } + + where_clauses_ << QString("%1 IN (" + final.join(",") + ")").arg(column); + } + else { + // Do integers inline - sqlite seems to get confused when you pass integers + // to bound parameters + if (value.type() == QVariant::Int) { + where_clauses_ << QString("%1 %2 %3").arg(column, op, value.toString()); + } + else { + where_clauses_ << QString("%1 %2 ?").arg(column, op); + bound_values_ << value; + } + } + +} + +void CollectionQuery::AddCompilationRequirement(bool compilation) { + // The unary + is added to prevent sqlite from using the index + // idx_comp_artist. When joining with fts, sqlite 3.8 has a tendency + // to use this index and thereby nesting the tables in an order + // which gives very poor performance + + where_clauses_ << QString("+compilation_effective = %1").arg(compilation ? 1 : 0); + +} + +QSqlQuery CollectionQuery::Exec(QSqlDatabase db, const QString &songs_table, const QString &fts_table) { + + QString sql; + + if (join_with_fts_) { + sql = QString("SELECT %1 FROM %2 INNER JOIN %3 AS fts ON %2.ROWID = fts.ROWID").arg(column_spec_, songs_table, fts_table); + } + else { + sql = QString("SELECT %1 FROM %2 %3").arg(column_spec_, songs_table, GetInnerQuery()); + } + + QStringList where_clauses(where_clauses_); + if (!include_unavailable_) { + where_clauses << "unavailable = 0"; + } + + if (!where_clauses.isEmpty()) sql += " WHERE " + where_clauses.join(" AND "); + + if (!order_by_.isEmpty()) sql += " ORDER BY " + order_by_; + + if (limit_ != -1) sql += " LIMIT " + QString::number(limit_); + + sql.replace("%songs_table", songs_table); + sql.replace("%fts_table_noprefix", fts_table.section('.', -1, -1)); + sql.replace("%fts_table", fts_table); + + query_ = QSqlQuery(db); + query_.prepare(sql); + + // Bind values + for (const QVariant& value : bound_values_) { + query_.addBindValue(value); + } + + query_.exec(); + return query_; + +} + +bool CollectionQuery::Next() { return query_.next(); } + +QVariant CollectionQuery::Value(int column) const { return query_.value(column); } + +bool QueryOptions::Matches(const Song &song) const { + + if (max_age_ != -1) { + const uint cutoff = QDateTime::currentDateTime().toTime_t() - max_age_; + if (song.ctime() <= cutoff) return false; + } + + if (!filter_.isNull()) { + return song.artist().contains(filter_, Qt::CaseInsensitive) || song.album().contains(filter_, Qt::CaseInsensitive) || song.title().contains(filter_, Qt::CaseInsensitive); + } + + return true; + +} diff --git a/src/collection/collectionquery.h b/src/collection/collectionquery.h new file mode 100644 index 00000000..3369d8a0 --- /dev/null +++ b/src/collection/collectionquery.h @@ -0,0 +1,116 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COLLECTIONQUERY_H +#define COLLECTIONQUERY_H + +#include "config.h" + +#include +#include +#include +#include +#include + +class Song; +class CollectionBackend; + +// This structure let's you customize behaviour of any CollectionQuery. +struct QueryOptions { + // Modes of CollectionQuery: + // - use the all songs table + // - use the duplicated songs view; by duplicated we mean those songs + // for which the (artist, album, title) tuple is found more than once + // in the songs table + // - use the untagged songs view; by untagged we mean those for which + // at least one of the (artist, album, title) tags is empty + // Please note that additional filtering based on fts table (the filter + // attribute) won't work in Duplicates and Untagged modes. + enum QueryMode { + QueryMode_All, + QueryMode_Duplicates, + QueryMode_Untagged + }; + + QueryOptions(); + + bool Matches(const Song &song) const; + + QString filter() const { return filter_; } + void set_filter(const QString &filter) { + this->filter_ = filter; + this->query_mode_ = QueryMode_All; + } + + int max_age() const { return max_age_; } + void set_max_age(int max_age) { this->max_age_ = max_age; } + + QueryMode query_mode() const { return query_mode_; } + void set_query_mode(QueryMode query_mode) { + this->query_mode_ = query_mode; + this->filter_ = QString(); + } + + private: + QString filter_; + int max_age_; + QueryMode query_mode_; +}; + +class CollectionQuery { + public: + CollectionQuery(const QueryOptions &options = QueryOptions()); + + // Sets contents of SELECT clause on the query (list of columns to get). + void SetColumnSpec(const QString &spec) { column_spec_ = spec; } + // Sets an ORDER BY clause on the query. + void SetOrderBy(const QString &order_by) { order_by_ = order_by; } + + // Adds a fragment of WHERE clause. When executed, this Query will connect all + // the fragments with AND operator. + // Please note that IN operator expects a QStringList as value. + void AddWhere(const QString &column, const QVariant &value, const QString &op = "="); + + void AddCompilationRequirement(bool compilation); + void SetLimit(int limit) { limit_ = limit; } + void SetIncludeUnavailable(bool include_unavailable) { include_unavailable_ = include_unavailable; } + + QSqlQuery Exec(QSqlDatabase db, const QString &songs_table, const QString &fts_table); + bool Next(); + QVariant Value(int column) const; + + operator const QSqlQuery &() const { return query_; } + + private: + QString GetInnerQuery(); + + bool include_unavailable_; + bool join_with_fts_; + QString column_spec_; + QString order_by_; + QStringList where_clauses_; + QVariantList bound_values_; + int limit_; + bool duplicates_only_; + + QSqlQuery query_; +}; + +#endif // COLLECTIONQUERY_H diff --git a/src/collection/collectionview.cpp b/src/collection/collectionview.cpp new file mode 100644 index 00000000..b6ba197c --- /dev/null +++ b/src/collection/collectionview.cpp @@ -0,0 +1,716 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "collectionview.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "collectiondirectorymodel.h" +#include "collectionfilterwidget.h" +#include "collectionmodel.h" +#include "collectionitem.h" +#include "collectionbackend.h" +#include "core/application.h" +#include "core/logging.h" +#include "core/mimedata.h" +#include "core/musicstorage.h" +#include "core/utilities.h" +#include "core/iconloader.h" +#include "device/devicemanager.h" +#include "device/devicestatefiltermodel.h" +#ifdef HAVE_GSTREAMER +#include "dialogs/organisedialog.h" +#include "dialogs/organiseerrordialog.h" +#endif +#include "settings/collectionsettingspage.h" + +CollectionItemDelegate::CollectionItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {} + +void CollectionItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const { + + const bool is_divider = index.data(CollectionModel::Role_IsDivider).toBool(); + + if (is_divider) { + QString text(index.data().toString()); + + painter->save(); + + QRect text_rect(opt.rect); + + // Does this item have an icon? + QPixmap pixmap; + QVariant decoration = index.data(Qt::DecorationRole); + if (!decoration.isNull()) { + if (decoration.canConvert()) { + pixmap = decoration.value(); + } + else if (decoration.canConvert()) { + pixmap = decoration.value().pixmap(opt.decorationSize); + } + } + + // Draw the icon at the left of the text rectangle + if (!pixmap.isNull()) { + QRect icon_rect(text_rect.topLeft(), opt.decorationSize); + const int padding = (text_rect.height() - icon_rect.height()) / 2; + icon_rect.adjust(padding, padding, padding, padding); + text_rect.moveLeft(icon_rect.right() + padding + 6); + + if (pixmap.size() != opt.decorationSize) { + pixmap = pixmap.scaled(opt.decorationSize, Qt::KeepAspectRatio); + } + + painter->drawPixmap(icon_rect, pixmap); + } + else { + text_rect.setLeft(text_rect.left() + 30); + } + + // Draw the text + QFont bold_font(opt.font); + bold_font.setBold(true); + + painter->setPen(opt.palette.color(QPalette::Text)); + painter->setFont(bold_font); + painter->drawText(text_rect, text); + + // Draw the line under the item + QColor line_color = opt.palette.color(QPalette::Text); + QLinearGradient grad_color(opt.rect.bottomLeft(), opt.rect.bottomRight()); + const double fade_start_end = (opt.rect.width()/3.0)/opt.rect.width(); + line_color.setAlphaF(0.0); + grad_color.setColorAt(0, line_color); + line_color.setAlphaF(0.5); + grad_color.setColorAt(fade_start_end, line_color); + grad_color.setColorAt(1.0 - fade_start_end, line_color); + line_color.setAlphaF(0.0); + grad_color.setColorAt(1, line_color); + painter->setPen(QPen(grad_color, 1)); + painter->drawLine(opt.rect.bottomLeft(), opt.rect.bottomRight()); + + painter->restore(); + } + else { + QStyledItemDelegate::paint(painter, opt, index); + } + +} + +bool CollectionItemDelegate::helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) { + + Q_UNUSED(option); + + if (!event || !view) return false; + + QHelpEvent *he = static_cast(event); + QString text = displayText(index.data(), QLocale::system()); + + if (text.isEmpty() || !he) return false; + + switch (event->type()) { + case QEvent::ToolTip: { + QRect displayed_text; + QSize real_text; + bool is_elided = false; + + real_text = sizeHint(option, index); + displayed_text = view->visualRect(index); + is_elided = displayed_text.width() < real_text.width(); + + if (is_elided) { + QToolTip::showText(he->globalPos(), text, view); + } + else if (index.data(Qt::ToolTipRole).isValid()) { + // If the item has a tooltip text, display it + QString tooltip_text = index.data(Qt::ToolTipRole).toString(); + QToolTip::showText(he->globalPos(), tooltip_text, view); + } + else { + // in case that another text was previously displayed + QToolTip::hideText(); + } + return true; + } + + case QEvent::QueryWhatsThis: + return true; + + case QEvent::WhatsThis: + QWhatsThis::showText(he->globalPos(), text, view); + return true; + + default: + break; + } + return false; + +} + +CollectionView::CollectionView(QWidget *parent) + : AutoExpandingTreeView(parent), + app_(nullptr), + filter_(nullptr), + total_song_count_(-1), + total_artist_count_(-1), + total_album_count_(-1), + nomusic_(":/pictures/nomusic.png"), + context_menu_(nullptr), + is_in_keyboard_search_(false) + { + + setItemDelegate(new CollectionItemDelegate(this)); + setAttribute(Qt::WA_MacShowFocusRect, false); + setHeaderHidden(true); + setAllColumnsShowFocus(true); + setDragEnabled(true); + setDragDropMode(QAbstractItemView::DragOnly); + setSelectionMode(QAbstractItemView::ExtendedSelection); + + setStyleSheet("QTreeView::item{padding-top:1px;}"); + +} + +CollectionView::~CollectionView() {} + +void CollectionView::SaveFocus() { + + QModelIndex current = currentIndex(); + QVariant type = model()->data(current, CollectionModel::Role_Type); + if (!type.isValid() || !(type.toInt() == CollectionItem::Type_Song || type.toInt() == CollectionItem::Type_Container || type.toInt() == CollectionItem::Type_Divider)) { + return; + } + + last_selected_path_.clear(); + last_selected_song_ = Song(); + last_selected_container_ = QString(); + + switch (type.toInt()) { + case CollectionItem::Type_Song: { + QModelIndex index = qobject_cast(model())->mapToSource(current); + SongList songs = app_->collection_model()->GetChildSongs(index); + if (!songs.isEmpty()) { + last_selected_song_ = songs.last(); + } + break; + } + + case CollectionItem::Type_Container: + case CollectionItem::Type_Divider: { + QString text = model()->data(current, CollectionModel::Role_SortText).toString(); + last_selected_container_ = text; + break; + } + + default: + return; + } + + SaveContainerPath(current); + +} + +void CollectionView::SaveContainerPath(const QModelIndex &child) { + + QModelIndex current = model()->parent(child); + QVariant type = model()->data(current, CollectionModel::Role_Type); + if (!type.isValid() || !(type.toInt() == CollectionItem::Type_Container || type.toInt() == CollectionItem::Type_Divider)) { + return; + } + + QString text = model()->data(current, CollectionModel::Role_SortText).toString(); + last_selected_path_ << text; + SaveContainerPath(current); + +} + +void CollectionView::RestoreFocus() { + + if (last_selected_container_.isEmpty() && last_selected_song_.url().isEmpty()) { + return; + } + RestoreLevelFocus(); + +} + +bool CollectionView::RestoreLevelFocus(const QModelIndex &parent) { + + if (model()->canFetchMore(parent)) { + model()->fetchMore(parent); + } + int rows = model()->rowCount(parent); + for (int i = 0; i < rows; i++) { + QModelIndex current = model()->index(i, 0, parent); + QVariant type = model()->data(current, CollectionModel::Role_Type); + switch (type.toInt()) { + case CollectionItem::Type_Song: + if (!last_selected_song_.url().isEmpty()) { + QModelIndex index = qobject_cast(model())->mapToSource(current); + SongList songs = app_->collection_model()->GetChildSongs(index); + for (const Song& song : songs) { + if (song == last_selected_song_) { + setCurrentIndex(current); + return true; + } + } + } + break; + + case CollectionItem::Type_Container: + case CollectionItem::Type_Divider: { + QString text = model()->data(current, CollectionModel::Role_SortText).toString(); + if (!last_selected_container_.isEmpty() && last_selected_container_ == text) { + emit expand(current); + setCurrentIndex(current); + return true; + } + else if (last_selected_path_.contains(text)) { + emit expand(current); + // If a selected container or song were not found, we've got into a wrong subtree + // (happens with "unknown" all the time) + if (!RestoreLevelFocus(current)) { + emit collapse(current); + } + else { + return true; + } + } + break; + } + } + } + return false; + +} + +void CollectionView::ReloadSettings() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QSettings settings; + + settings.beginGroup(CollectionSettingsPage::kSettingsGroup); + SetAutoOpen(settings.value("auto_open", true).toBool()); + + if (app_ != nullptr) { + app_->collection_model()->set_pretty_covers(settings.value("pretty_covers", true).toBool()); + app_->collection_model()->set_show_dividers(settings.value("show_dividers", true).toBool()); + } + + settings.endGroup(); + +} + +void CollectionView::SetApplication(Application *app) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + app_ = app; + + ReloadSettings(); + +} + +void CollectionView::SetFilter(CollectionFilterWidget *filter) { filter_ = filter; } + +void CollectionView::TotalSongCountUpdated(int count) { + + //qLog(Debug) << __FUNCTION__ << count; + + bool old = total_song_count_; + total_song_count_ = count; + if (old != total_song_count_) update(); + + if (total_song_count_ == 0) + setCursor(Qt::PointingHandCursor); + else + unsetCursor(); + + emit TotalSongCountUpdated_(); + +} + +void CollectionView::TotalArtistCountUpdated(int count) { + + //qLog(Debug) << __FUNCTION__ << count; + + bool old = total_artist_count_; + total_artist_count_ = count; + if (old != total_artist_count_) update(); + + if (total_artist_count_ == 0) + setCursor(Qt::PointingHandCursor); + else + unsetCursor(); + + emit TotalArtistCountUpdated_(); + +} + +void CollectionView::TotalAlbumCountUpdated(int count) { + + //qLog(Debug) << __FUNCTION__ << count; + + bool old = total_album_count_; + total_album_count_ = count; + if (old != total_album_count_) update(); + + if (total_album_count_ == 0) + setCursor(Qt::PointingHandCursor); + else + unsetCursor(); + + emit TotalAlbumCountUpdated_(); + +} + +void CollectionView::paintEvent(QPaintEvent *event) { + + //qLog(Debug) << __FUNCTION__; + + if (total_song_count_ == 0) { + QPainter p(viewport()); + QRect rect(viewport()->rect()); + + // Draw the confused strawberry + QRect image_rect((rect.width() - nomusic_.width()) / 2, 50, nomusic_.width(), nomusic_.height()); + p.drawPixmap(image_rect, nomusic_); + + // Draw the title text + QFont bold_font; + bold_font.setBold(true); + p.setFont(bold_font); + + QFontMetrics metrics(bold_font); + + QRect title_rect(0, image_rect.bottom() + 20, rect.width(), metrics.height()); + p.drawText(title_rect, Qt::AlignHCenter, tr("Your collection is empty!")); + + // Draw the other text + p.setFont(QFont()); + + QRect text_rect(0, title_rect.bottom() + 5, rect.width(), metrics.height()); + p.drawText(text_rect, Qt::AlignHCenter, tr("Click here to add some music")); + } + else { + QTreeView::paintEvent(event); + } + +} + +void CollectionView::mouseReleaseEvent(QMouseEvent *e) { + + QTreeView::mouseReleaseEvent(e); + + if (total_song_count_ == 0) { + emit ShowConfigDialog(); + } + +} + +void CollectionView::contextMenuEvent(QContextMenuEvent *e) { + + if (!context_menu_) { + context_menu_ = new QMenu(this); + add_to_playlist_ = context_menu_->addAction(IconLoader::Load("media-play"), tr("Append to current playlist"), this, SLOT(AddToPlaylist())); + load_ = context_menu_->addAction(IconLoader::Load("media-play"), tr("Replace current playlist"), this, SLOT(Load())); + open_in_new_playlist_ = context_menu_->addAction(IconLoader::Load("document-new"), tr("Open in new playlist"), this, SLOT(OpenInNewPlaylist())); + + context_menu_->addSeparator(); + add_to_playlist_enqueue_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue track"), this, SLOT(AddToPlaylistEnqueue())); + +#ifdef HAVE_GSTREAMER + context_menu_->addSeparator(); + organise_ = context_menu_->addAction(IconLoader::Load("edit-copy"), tr("Organise files..."), this, SLOT(Organise())); + copy_to_device_ = context_menu_->addAction(IconLoader::Load("device"), tr("Copy to device..."), this, SLOT(CopyToDevice())); + //delete_ = context_menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete from disk..."), this, SLOT(Delete())); +#endif + + context_menu_->addSeparator(); + edit_track_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit track information..."), this, SLOT(EditTracks())); + edit_tracks_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit tracks information..."), this, SLOT(EditTracks())); + show_in_browser_ = context_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, SLOT(ShowInBrowser())); + + context_menu_->addSeparator(); + show_in_various_ = context_menu_->addAction( tr("Show in various artists"), this, SLOT(ShowInVarious())); + no_show_in_various_ = context_menu_->addAction( tr("Don't show in various artists"), this, SLOT(NoShowInVarious())); + + context_menu_->addSeparator(); + + context_menu_->addMenu(filter_->menu()); + +#ifdef HAVE_GSTREAMER + copy_to_device_->setDisabled(app_->device_manager()->connected_devices_model()->rowCount() == 0); + connect(app_->device_manager()->connected_devices_model(), SIGNAL(IsEmptyChanged(bool)), copy_to_device_, SLOT(setDisabled(bool))); +#endif + + } + + context_menu_index_ = indexAt(e->pos()); + if (!context_menu_index_.isValid()) return; + + context_menu_index_ = qobject_cast(model())->mapToSource(context_menu_index_); + + QModelIndexList selected_indexes = qobject_cast(model())->mapSelectionToSource(selectionModel()->selection()).indexes(); + + int regular_elements = 0; + int regular_editable = 0; + + for (const QModelIndex& index : selected_indexes) { + regular_elements++; + if(app_->collection_model()->data(index, CollectionModel::Role_Editable).toBool()) { + regular_editable++; + } + } + + // TODO: check if custom plugin actions should be enabled / visible + //const int songs_selected = smart_playlists + smart_playlists_header + regular_elements; + const int songs_selected = regular_elements; + const bool regular_elements_only = songs_selected == regular_elements && regular_elements > 0; + + // in all modes + load_->setEnabled(songs_selected); + add_to_playlist_->setEnabled(songs_selected); + open_in_new_playlist_->setEnabled(songs_selected); + add_to_playlist_enqueue_->setEnabled(songs_selected); + + // if neither edit_track not edit_tracks are available, we show disabled edit_track element + //edit_track_->setVisible(!smart_playlists_only && (regular_editable <= 1)); + edit_track_->setVisible(regular_editable <= 1); + edit_track_->setEnabled(regular_editable == 1); + + // only when no smart playlists selected +#ifdef HAVE_GSTREAMER + organise_->setVisible(regular_elements_only); + copy_to_device_->setVisible(regular_elements_only); + //delete_->setVisible(regular_elements_only); +#endif + show_in_various_->setVisible(regular_elements_only); + no_show_in_various_->setVisible(regular_elements_only); + + // only when all selected items are editable +#ifdef HAVE_GSTREAMER + organise_->setEnabled(regular_elements == regular_editable); + copy_to_device_->setEnabled(regular_elements == regular_editable); + //delete_->setEnabled(regular_elements == regular_editable); +#endif + + context_menu_->popup(e->globalPos()); + +} + +void CollectionView::ShowInVarious() { ShowInVarious(true); } + +void CollectionView::NoShowInVarious() { ShowInVarious(false); } + +void CollectionView::ShowInVarious(bool on) { + + if (!context_menu_index_.isValid()) return; + + // Map is from album name -> all artists sharing that album name, built from each selected + // song. We put through "Various Artists" changes one album at a time, to make sure the old album + // node gets removed (due to all children removed), before the new one gets added + QMultiMap albums; + for (const Song& song : GetSelectedSongs()) { + if (albums.find(song.album(), song.artist()) == albums.end()) + albums.insert(song.album(), song.artist()); + } + + // If we have only one album and we are putting it into Various Artists, check to see + // if there are other Artists in this album and prompt the user if they'd like them moved, too + if (on && albums.keys().count() == 1) { + const QString album = albums.keys().first(); + QList all_of_album = app_->collection_backend()->GetSongsByAlbum(album); + QSet other_artists; + for (const Song &s : all_of_album) { + if (!albums.contains(album, s.artist()) && + !other_artists.contains(s.artist())) { + other_artists.insert(s.artist()); + } + } + if (other_artists.count() > 0) { + if (QMessageBox::question(this, + tr("There are other songs in this album"), + tr("Would you like to move the other songs in this album to Various Artists as well?"), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::Yes) == QMessageBox::Yes) { + for (const QString &s : other_artists) { + albums.insert(album, s); + } + } + } + } + + for (const QString &album : QSet::fromList(albums.keys())) { + app_->collection_backend()->ForceCompilation(album, albums.values(album), on); + } + +} + +void CollectionView::Load() { + + QMimeData *data = model()->mimeData(selectedIndexes()); + if (MimeData* mime_data = qobject_cast(data)) { + mime_data->clear_first_ = true; + } + emit AddToPlaylistSignal(data); + +} + +void CollectionView::AddToPlaylist() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + emit AddToPlaylistSignal(model()->mimeData(selectedIndexes())); + +} + +void CollectionView::AddToPlaylistEnqueue() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QMimeData *data = model()->mimeData(selectedIndexes()); + if (MimeData* mime_data = qobject_cast(data)) { + mime_data->enqueue_now_ = true; + } + emit AddToPlaylistSignal(data); + +} + +void CollectionView::OpenInNewPlaylist() { + + QMimeData *data = model()->mimeData(selectedIndexes()); + if (MimeData* mime_data = qobject_cast(data)) { + mime_data->open_in_new_playlist_ = true; + } + emit AddToPlaylistSignal(data); + +} + +void CollectionView::keyboardSearch(const QString &search) { + + is_in_keyboard_search_ = true; + QTreeView::keyboardSearch(search); + is_in_keyboard_search_ = false; + +} + +void CollectionView::scrollTo(const QModelIndex &index, ScrollHint hint) { + + if (is_in_keyboard_search_) + QTreeView::scrollTo(index, QAbstractItemView::PositionAtTop); + else + QTreeView::scrollTo(index, hint); + +} + +SongList CollectionView::GetSelectedSongs() const { + + QModelIndexList selected_indexes = qobject_cast(model())->mapSelectionToSource(selectionModel()->selection()).indexes(); + return app_->collection_model()->GetChildSongs(selected_indexes); + +} + +#ifdef HAVE_GSTREAMER +void CollectionView::Organise() { + + if (!organise_dialog_) + organise_dialog_.reset(new OrganiseDialog(app_->task_manager())); + + organise_dialog_->SetDestinationModel(app_->collection_model()->directory_model()); + organise_dialog_->SetCopy(false); + if (organise_dialog_->SetSongs(GetSelectedSongs())) + organise_dialog_->show(); + else { + QMessageBox::warning(this, tr("Error"), tr("None of the selected songs were suitable for copying to a device")); + } +} +#endif + +void CollectionView::EditTracks() { + + if (!edit_tag_dialog_) { + edit_tag_dialog_.reset(new EditTagDialog(app_, this)); + } + edit_tag_dialog_->SetSongs(GetSelectedSongs()); + edit_tag_dialog_->show(); + +} + +#ifdef HAVE_GSTREAMER +void CollectionView::CopyToDevice() { + + if (!organise_dialog_) + organise_dialog_.reset(new OrganiseDialog(app_->task_manager())); + + organise_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true); + organise_dialog_->SetCopy(true); + organise_dialog_->SetSongs(GetSelectedSongs()); + organise_dialog_->show(); + +} +#endif + +void CollectionView::FilterReturnPressed() { + + if (!currentIndex().isValid()) { + // Pick the first thing that isn't a divider + for (int row = 0; row < model()->rowCount(); ++row) { + QModelIndex idx(model()->index(row, 0)); + if (idx.data(CollectionModel::Role_Type) != CollectionItem::Type_Divider) { + setCurrentIndex(idx); + break; + } + } + } + + if (!currentIndex().isValid()) return; + + emit doubleClicked(currentIndex()); +} + +void CollectionView::ShowInBrowser() { + QList urls; + for (const Song &song : GetSelectedSongs()) { + urls << song.url(); + } + + Utilities::OpenInFileBrowser(urls); +} + +int CollectionView::TotalSongs() { + return total_song_count_; +} +int CollectionView::TotalArtists() { + return total_artist_count_; +} +int CollectionView::TotalAlbums() { + return total_album_count_; +} diff --git a/src/collection/collectionview.h b/src/collection/collectionview.h new file mode 100644 index 00000000..214fac9c --- /dev/null +++ b/src/collection/collectionview.h @@ -0,0 +1,163 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COLLECTIONVIEW_H +#define COLLECTIONVIEW_H + +#include "config.h" + +#include + +#include + +#include "core/song.h" +#include "dialogs/edittagdialog.h" +#include "widgets/autoexpandingtreeview.h" + +class Application; +class CollectionFilterWidget; +#ifdef HAVE_GSTREAMER +class OrganiseDialog; +#endif + +class QMimeData; + +class CollectionItemDelegate : public QStyledItemDelegate { + Q_OBJECT + + public: + CollectionItemDelegate(QObject *parent); + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + + public slots: + bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index); +}; + +class CollectionView : public AutoExpandingTreeView { + Q_OBJECT + + public: + CollectionView(QWidget *parent = nullptr); + ~CollectionView(); + + //static const char *kSettingsGroup; + + // Returns Songs currently selected in the collection view. Please note that the + // selection is recursive meaning that if for example an album is selected + // this will return all of it's songs. + SongList GetSelectedSongs() const; + + void SetApplication(Application *app); + void SetFilter(CollectionFilterWidget *filter); + + // QTreeView + void keyboardSearch(const QString &search); + void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible); + + int TotalSongs(); + int TotalArtists(); + int TotalAlbums(); + + public slots: + void TotalSongCountUpdated(int count); + void TotalArtistCountUpdated(int count); + void TotalAlbumCountUpdated(int count); + void ReloadSettings(); + + void FilterReturnPressed(); + + void SaveFocus(); + void RestoreFocus(); + +signals: + void ShowConfigDialog(); + + void TotalSongCountUpdated_(); + void TotalArtistCountUpdated_(); + void TotalAlbumCountUpdated_(); + + protected: + // QWidget + void paintEvent(QPaintEvent *event); + void mouseReleaseEvent(QMouseEvent *e); + void contextMenuEvent(QContextMenuEvent *e); + + private slots: + void Load(); + void AddToPlaylist(); + void AddToPlaylistEnqueue(); + void OpenInNewPlaylist(); +#ifdef HAVE_GSTREAMER + void Organise(); + void CopyToDevice(); +#endif + void EditTracks(); + void ShowInBrowser(); + void ShowInVarious(); + void NoShowInVarious(); + + private: + void RecheckIsEmpty(); + void ShowInVarious(bool on); + bool RestoreLevelFocus(const QModelIndex &parent = QModelIndex()); + void SaveContainerPath(const QModelIndex &child); + + private: + Application *app_; + CollectionFilterWidget *filter_; + + int total_song_count_; + int total_artist_count_; + int total_album_count_; + + QPixmap nomusic_; + + QMenu *context_menu_; + QModelIndex context_menu_index_; + QAction *load_; + QAction *add_to_playlist_; + QAction *add_to_playlist_enqueue_; + QAction *open_in_new_playlist_; +#ifdef HAVE_GSTREAMER + QAction *organise_; + QAction *copy_to_device_; +#endif + QAction *delete_; + QAction *edit_track_; + QAction *edit_tracks_; + QAction *show_in_browser_; + QAction *show_in_various_; + QAction *no_show_in_various_; + +#ifdef HAVE_GSTREAMER + std::unique_ptr organise_dialog_; +#endif + std::unique_ptr edit_tag_dialog_; + + bool is_in_keyboard_search_; + + // Save focus + Song last_selected_song_; + QString last_selected_container_; + QSet last_selected_path_; +}; + +#endif // COLLECTIONVIEW_H + diff --git a/src/collection/collectionviewcontainer.cpp b/src/collection/collectionviewcontainer.cpp new file mode 100644 index 00000000..cd76afb4 --- /dev/null +++ b/src/collection/collectionviewcontainer.cpp @@ -0,0 +1,48 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "collectionviewcontainer.h" +#include "ui_collectionviewcontainer.h" + +CollectionViewContainer::CollectionViewContainer(QWidget *parent) : QWidget(parent), ui_(new Ui_CollectionViewContainer) { + + ui_->setupUi(this); + view()->SetFilter(filter()); + + connect(filter(), SIGNAL(UpPressed()), view(), SLOT(UpAndFocus())); + connect(filter(), SIGNAL(DownPressed()), view(), SLOT(DownAndFocus())); + connect(filter(), SIGNAL(ReturnPressed()), view(), SLOT(FilterReturnPressed())); + connect(view(), SIGNAL(FocusOnFilterSignal(QKeyEvent*)), filter(), SLOT(FocusOnFilter(QKeyEvent*))); + + ReloadSettings(); + +} + +CollectionViewContainer::~CollectionViewContainer() { delete ui_; } + +CollectionView* CollectionViewContainer::view() const { return ui_->view; } + +CollectionFilterWidget *CollectionViewContainer::filter() const { + return ui_->filter; +} + +void CollectionViewContainer::ReloadSettings() { view()->ReloadSettings(); } diff --git a/src/collection/collectionviewcontainer.h b/src/collection/collectionviewcontainer.h new file mode 100644 index 00000000..8026a9d7 --- /dev/null +++ b/src/collection/collectionviewcontainer.h @@ -0,0 +1,49 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COLLECTIONVIEWCONTAINER_H +#define COLLECTIONVIEWCONTAINER_H + +#include "config.h" + +#include + +class CollectionFilterWidget; +class CollectionView; +class Ui_CollectionViewContainer; + +class CollectionViewContainer : public QWidget { + Q_OBJECT + + public: + CollectionViewContainer(QWidget *parent = nullptr); + ~CollectionViewContainer(); + + CollectionFilterWidget *filter() const; + CollectionView *view() const; + + void ReloadSettings(); + + private: + Ui_CollectionViewContainer *ui_; +}; + +#endif // COLLECTIONVIEWCONTAINER_H + diff --git a/src/collection/collectionviewcontainer.ui b/src/collection/collectionviewcontainer.ui new file mode 100644 index 00000000..ddc805a5 --- /dev/null +++ b/src/collection/collectionviewcontainer.ui @@ -0,0 +1,47 @@ + + + CollectionViewContainer + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 0 + + + 0 + + + + + + + + + + + + CollectionFilterWidget + QWidget +
collection/collectionfilterwidget.h
+ 1 +
+ + CollectionView + QWidget +
collection/collectionview.h
+ 1 +
+
+ + +
diff --git a/src/collection/collectionwatcher.cpp b/src/collection/collectionwatcher.cpp new file mode 100644 index 00000000..c4a9965d --- /dev/null +++ b/src/collection/collectionwatcher.cpp @@ -0,0 +1,802 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include + +#include "collectionwatcher.h" + +#include "collectionbackend.h" +#include "core/filesystemwatcherinterface.h" +#include "core/logging.h" +#include "core/tagreaderclient.h" +#include "core/taskmanager.h" +#include "core/utilities.h" +#include "playlistparsers/cueparser.h" +#include "settings/collectionsettingspage.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// This is defined by one of the windows headers that is included by taglib. +#ifdef RemoveDirectory +#undef RemoveDirectory +#endif + +namespace { +static const char *kNoMediaFile = ".nomedia"; +static const char *kNoMusicFile = ".nomusic"; +} + +QStringList CollectionWatcher::sValidImages; + +CollectionWatcher::CollectionWatcher(QObject *parent) + : QObject(parent), + backend_(nullptr), + task_manager_(nullptr), + fs_watcher_(FileSystemWatcherInterface::Create(this)), + stop_requested_(false), + scan_on_startup_(true), + monitor_(true), + rescan_timer_(new QTimer(this)), + rescan_paused_(false), + total_watches_(0), + cue_parser_(new CueParser(backend_, this)) { + Utilities::SetThreadIOPriority(Utilities::IOPRIO_CLASS_IDLE); + + rescan_timer_->setInterval(1000); + rescan_timer_->setSingleShot(true); + + if (sValidImages.isEmpty()) { + sValidImages << "jpg" << "png" << "gif" << "jpeg"; + } + + ReloadSettings(); + + connect(rescan_timer_, SIGNAL(timeout()), SLOT(RescanPathsNow())); +} + +CollectionWatcher::ScanTransaction::ScanTransaction(CollectionWatcher *watcher, int dir, bool incremental, bool ignores_mtime) + : progress_(0), + progress_max_(0), + dir_(dir), + incremental_(incremental), + ignores_mtime_(ignores_mtime), + watcher_(watcher), + cached_songs_dirty_(true), + known_subdirs_dirty_(true) { + + QString description; + + if (watcher_->device_name_.isEmpty()) + description = tr("Updating collection"); + else + description = tr("Updating %1").arg(watcher_->device_name_); + + task_id_ = watcher_->task_manager_->StartTask(description); + emit watcher_->ScanStarted(task_id_); + +} + +CollectionWatcher::ScanTransaction::~ScanTransaction() { + + // If we're stopping then don't commit the transaction + if (watcher_->stop_requested_) return; + + if (!new_songs.isEmpty()) emit watcher_->NewOrUpdatedSongs(new_songs); + + if (!touched_songs.isEmpty()) emit watcher_->SongsMTimeUpdated(touched_songs); + + if (!deleted_songs.isEmpty()) emit watcher_->SongsDeleted(deleted_songs); + + if (!readded_songs.isEmpty()) emit watcher_->SongsReadded(readded_songs); + + if (!new_subdirs.isEmpty()) emit watcher_->SubdirsDiscovered(new_subdirs); + + if (!touched_subdirs.isEmpty()) + emit watcher_->SubdirsMTimeUpdated(touched_subdirs); + + watcher_->task_manager_->SetTaskFinished(task_id_); + + if (watcher_->monitor_) { + // Watch the new subdirectories + for (const Subdirectory& subdir : new_subdirs) { + watcher_->AddWatch(watcher_->watched_dirs_[dir_], subdir.path); + } + } + +} + +void CollectionWatcher::ScanTransaction::AddToProgress(int n) { + + progress_ += n; + watcher_->task_manager_->SetTaskProgress(task_id_, progress_, progress_max_); + +} + +void CollectionWatcher::ScanTransaction::AddToProgressMax(int n) { + + progress_max_ += n; + watcher_->task_manager_->SetTaskProgress(task_id_, progress_, progress_max_); + +} + +SongList CollectionWatcher::ScanTransaction::FindSongsInSubdirectory(const QString &path) { + + if (cached_songs_dirty_) { + cached_songs_ = watcher_->backend_->FindSongsInDirectory(dir_); + cached_songs_dirty_ = false; + } + + // TODO: Make this faster + SongList ret; + for (const Song &song : cached_songs_) { + if (song.url().toLocalFile().section('/', 0, -2) == path) ret << song; + } + return ret; + +} + +void CollectionWatcher::ScanTransaction::SetKnownSubdirs(const SubdirectoryList &subdirs) { + + known_subdirs_ = subdirs; + known_subdirs_dirty_ = false; + +} + +bool CollectionWatcher::ScanTransaction::HasSeenSubdir(const QString &path) { + + if (known_subdirs_dirty_) + SetKnownSubdirs(watcher_->backend_->SubdirsInDirectory(dir_)); + + for (const Subdirectory &subdir : known_subdirs_) { + if (subdir.path == path && subdir.mtime != 0) return true; + } + return false; + +} + +SubdirectoryList CollectionWatcher::ScanTransaction::GetImmediateSubdirs(const QString &path) { + + if (known_subdirs_dirty_) + SetKnownSubdirs(watcher_->backend_->SubdirsInDirectory(dir_)); + + SubdirectoryList ret; + for (const Subdirectory &subdir : known_subdirs_) { + if (subdir.path.left(subdir.path.lastIndexOf(QDir::separator())) == path && + subdir.mtime != 0) { + ret << subdir; + } + } + + return ret; + +} + +SubdirectoryList CollectionWatcher::ScanTransaction::GetAllSubdirs() { + + if (known_subdirs_dirty_) + SetKnownSubdirs(watcher_->backend_->SubdirsInDirectory(dir_)); + return known_subdirs_; +} + +void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryList &subdirs) { + + watched_dirs_[dir.id] = dir; + + if (subdirs.isEmpty()) { + // This is a new directory that we've never seen before. Scan it fully. + ScanTransaction transaction(this, dir.id, false); + transaction.SetKnownSubdirs(subdirs); + transaction.AddToProgressMax(1); + ScanSubdirectory(dir.path, Subdirectory(), &transaction); + } + else { + // We can do an incremental scan - looking at the mtimes of each + // subdirectory and only rescan if the directory has changed. + ScanTransaction transaction(this, dir.id, true); + transaction.SetKnownSubdirs(subdirs); + transaction.AddToProgressMax(subdirs.count()); + for (const Subdirectory& subdir : subdirs) { + if (stop_requested_) return; + + if (scan_on_startup_) ScanSubdirectory(subdir.path, subdir, &transaction); + + if (monitor_) AddWatch(dir, subdir.path); + } + } + + emit CompilationsNeedUpdating(); + +} + +void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory &subdir, ScanTransaction *t, bool force_noincremental) { + + QFileInfo path_info(path); + QDir path_dir(path); + + // Do not scan symlinked dirs that are already in collection + if (path_info.isSymLink()) { + QString real_path = path_info.symLinkTarget(); + for (const Directory& dir : watched_dirs_) { + if (real_path.startsWith(dir.path)) { + t->AddToProgress(1); + return; + } + } + } + + // Do not scan directories containing a .nomedia or .nomusic file + if (path_dir.exists(kNoMediaFile) || path_dir.exists(kNoMusicFile)) { + t->AddToProgress(1); + return; + } + + if (!t->ignores_mtime() && !force_noincremental && t->is_incremental() && subdir.mtime == path_info.lastModified().toTime_t()) { + // The directory hasn't changed since last time + t->AddToProgress(1); + return; + } + + QMap album_art; + QStringList files_on_disk; + SubdirectoryList my_new_subdirs; + + // If a directory is moved then only its parent gets a changed notification, + // so we need to look and see if any of our children don't exist any more. + // If one has been removed, "rescan" it to get the deleted songs + SubdirectoryList previous_subdirs = t->GetImmediateSubdirs(path); + for (const Subdirectory& subdir : previous_subdirs) { + if (!QFile::exists(subdir.path) && subdir.path != path) { + t->AddToProgressMax(1); + ScanSubdirectory(subdir.path, subdir, t, true); + } + } + + // First we "quickly" get a list of the files in the directory that we think might be music. While we're here, we also look for new subdirectories and possible album artwork. + QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot); + while (it.hasNext()) { + if (stop_requested_) return; + + QString child(it.next()); + QFileInfo child_info(child); + + if (child_info.isDir()) { + if (!child_info.isHidden() && !t->HasSeenSubdir(child)) { + // We haven't seen this subdirectory before - add it to a list and + // later we'll tell the backend about it and scan it. + Subdirectory new_subdir; + new_subdir.directory_id = -1; + new_subdir.path = child; + new_subdir.mtime = child_info.lastModified().toTime_t(); + my_new_subdirs << new_subdir; + } + } + else { + QString ext_part(ExtensionPart(child)); + QString dir_part(DirectoryPart(child)); + + if (sValidImages.contains(ext_part)) + album_art[dir_part] << child; + else if (!child_info.isHidden()) + files_on_disk << child; + } + } + + if (stop_requested_) return; + + // Ask the database for a list of files in this directory + SongList songs_in_db = t->FindSongsInSubdirectory(path); + + QSet cues_processed; + + // Now compare the list from the database with the list of files on disk + for (const QString& file : files_on_disk) { + if (stop_requested_) return; + + // associated cue + QString matching_cue = NoExtensionPart(file) + ".cue"; + + Song matching_song; + if (FindSongByPath(songs_in_db, file, &matching_song)) { + uint matching_cue_mtime = GetMtimeForCue(matching_cue); + + // The song is in the database and still on disk. + // Check the mtime to see if it's been changed since it was added. + QFileInfo file_info(file); + + if (!file_info.exists()) { + // Partially fixes race condition - if file was removed between being + // added to the list and now. + files_on_disk.removeAll(file); + continue; + } + + // cue sheet's path from collection (if any) + QString song_cue = matching_song.cue_path(); + uint song_cue_mtime = GetMtimeForCue(song_cue); + + bool cue_deleted = song_cue_mtime == 0 && matching_song.has_cue(); + bool cue_added = matching_cue_mtime != 0 && !matching_song.has_cue(); + + // watch out for cue songs which have their mtime equal to + // qMax(media_file_mtime, cue_sheet_mtime) + bool changed = (matching_song.mtime() != qMax(file_info.lastModified().toTime_t(), song_cue_mtime)) || cue_deleted || cue_added; + + // Also want to look to see whether the album art has changed + QString image = ImageForSong(file, album_art); + if ((matching_song.art_automatic().isEmpty() && !image.isEmpty()) || (!matching_song.art_automatic().isEmpty() && !matching_song.has_embedded_cover() && !QFile::exists(matching_song.art_automatic()))) { + changed = true; + } + + // the song's changed - reread the metadata from file + if (t->ignores_mtime() || changed) { + qLog(Debug) << file << "changed"; + + // if cue associated... + if (!cue_deleted && (matching_song.has_cue() || cue_added)) { + UpdateCueAssociatedSongs(file, path, matching_cue, image, t); + // if no cue or it's about to lose it... + } + else { + UpdateNonCueAssociatedSong(file, matching_song, image, cue_deleted, t); + } + } + + // nothing has changed - mark the song available without re-scanning + if (matching_song.is_unavailable()) t->readded_songs << matching_song; + + } else { + // The song is on disk but not in the DB + SongList song_list = ScanNewFile(file, path, matching_cue, &cues_processed); + + if (song_list.isEmpty()) { + continue; + } + + qLog(Debug) << file << "created"; + // choose an image for the song(s) + QString image = ImageForSong(file, album_art); + + for (Song song : song_list) { + song.set_directory_id(t->dir()); + if (song.art_automatic().isEmpty()) song.set_art_automatic(image); + + t->new_songs << song; + } + } + } + + // Look for deleted songs + for (const Song& song : songs_in_db) { + if (!song.is_unavailable() && !files_on_disk.contains(song.url().toLocalFile())) { + qLog(Debug) << "Song deleted from disk:" << song.url().toLocalFile(); + t->deleted_songs << song; + } + } + + // Add this subdir to the new or touched list + Subdirectory updated_subdir; + updated_subdir.directory_id = t->dir(); + updated_subdir.mtime = path_info.exists() ? path_info.lastModified().toTime_t() : 0; + updated_subdir.path = path; + + if (subdir.directory_id == -1) + t->new_subdirs << updated_subdir; + else + t->touched_subdirs << updated_subdir; + + t->AddToProgress(1); + + // Recurse into the new subdirs that we found + t->AddToProgressMax(my_new_subdirs.count()); + for (const Subdirectory& my_new_subdir : my_new_subdirs) { + if (stop_requested_) return; + ScanSubdirectory(my_new_subdir.path, my_new_subdir, t, true); + } + +} + +void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file, const QString &path, const QString &matching_cue, const QString &image, ScanTransaction *t) { + + QFile cue(matching_cue); + cue.open(QIODevice::ReadOnly); + + SongList old_sections = backend_->GetSongsByUrl(QUrl::fromLocalFile(file)); + + QHash sections_map; + for (const Song& song : old_sections) { + sections_map[song.beginning_nanosec()] = song; + } + + QSet used_ids; + + // update every song that's in the cue and collection + for (Song cue_song : cue_parser_->Load(&cue, matching_cue, path)) { + cue_song.set_directory_id(t->dir()); + + Song matching = sections_map[cue_song.beginning_nanosec()]; + // a new section + if (!matching.is_valid()) { + t->new_songs << cue_song; + // changed section + } else { + PreserveUserSetData(file, image, matching, &cue_song, t); + used_ids.insert(matching.id()); + } + } + + // sections that are now missing + for (const Song &matching : old_sections) { + if (!used_ids.contains(matching.id())) { + t->deleted_songs << matching; + } + } + +} + +void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file, const Song &matching_song, const QString &image, bool cue_deleted, ScanTransaction *t) { + + // if a cue got deleted, we turn it's first section into the new + // 'raw' (cueless) song and we just remove the rest of the sections + // from the collection + if (cue_deleted) { + for (const Song &song : + backend_->GetSongsByUrl(QUrl::fromLocalFile(file))) { + if (!song.IsMetadataEqual(matching_song)) { + t->deleted_songs << song; + } + } + } + + Song song_on_disk; + song_on_disk.set_directory_id(t->dir()); + TagReaderClient::Instance()->ReadFileBlocking(file, &song_on_disk); + + if (song_on_disk.is_valid()) { + PreserveUserSetData(file, image, matching_song, &song_on_disk, t); + } + +} + +SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path, const QString &matching_cue, QSet *cues_processed) { + + SongList song_list; + + uint matching_cue_mtime = GetMtimeForCue(matching_cue); + // if it's a cue - create virtual tracks + if (matching_cue_mtime) { + // don't process the same cue many times + if (cues_processed->contains(matching_cue)) return song_list; + + QFile cue(matching_cue); + cue.open(QIODevice::ReadOnly); + + // Ignore FILEs pointing to other media files. Also, watch out for incorrect + // media files. Playlist parser for CUEs considers every entry in sheet + // valid and we don't want invalid media getting into collection! + QString file_nfd = file.normalized(QString::NormalizationForm_D); + for (const Song& cue_song : cue_parser_->Load(&cue, matching_cue, path)) { + if (cue_song.url().toLocalFile().normalized(QString::NormalizationForm_D) == file_nfd) { + if (TagReaderClient::Instance()->IsMediaFileBlocking(file)) { + song_list << cue_song; + } + } + } + + if (!song_list.isEmpty()) { + *cues_processed << matching_cue; + } + + // it's a normal media file + } + else { + Song song; + TagReaderClient::Instance()->ReadFileBlocking(file, &song); + + if (song.is_valid()) { + song_list << song; + } + } + + return song_list; + +} + +void CollectionWatcher::PreserveUserSetData(const QString &file, const QString &image, const Song &matching_song, Song *out, ScanTransaction *t) { + + out->set_id(matching_song.id()); + + // Previous versions of Clementine incorrectly overwrote this and + // stored it in the DB, so we can't rely on matching_song to + // know if it has embedded artwork or not, but we can check here. + if (!out->has_embedded_cover()) out->set_art_automatic(image); + + out->MergeUserSetData(matching_song); + + // The song was deleted from the database (e.g. due to an unmounted + // filesystem), but has been restored. + if (matching_song.is_unavailable()) { + qLog(Debug) << file << " unavailable song restored"; + + t->new_songs << *out; + } + else if (!matching_song.IsMetadataEqual(*out)) { + qLog(Debug) << file << "metadata changed"; + + // Update the song in the DB + t->new_songs << *out; + } + else { + // Only the mtime's changed + t->touched_songs << *out; + } + +} + +uint CollectionWatcher::GetMtimeForCue(const QString &cue_path) { + + // slight optimisation + if (cue_path.isEmpty()) { + return 0; + } + + const QFileInfo file_info(cue_path); + if (!file_info.exists()) { + return 0; + } + + const QDateTime cue_last_modified = file_info.lastModified(); + + return cue_last_modified.isValid() ? cue_last_modified.toTime_t() : 0; +} + +void CollectionWatcher::AddWatch(const Directory &dir, const QString &path) { + + if (!QFile::exists(path)) return; + + connect(fs_watcher_, SIGNAL(PathChanged(const QString&)), this, SLOT(DirectoryChanged(const QString&)), Qt::UniqueConnection); + fs_watcher_->AddPath(path); + subdir_mapping_[path] = dir; + +} + +void CollectionWatcher::RemoveDirectory(const Directory& dir) { + + rescan_queue_.remove(dir.id); + watched_dirs_.remove(dir.id); + + // Stop watching the directory's subdirectories + for (const QString& subdir_path : subdir_mapping_.keys(dir)) { + fs_watcher_->RemovePath(subdir_path); + subdir_mapping_.remove(subdir_path); + } + +} + +bool CollectionWatcher::FindSongByPath(const SongList &list, const QString &path, Song *out) { + + // TODO: Make this faster + for (const Song &song : list) { + if (song.url().toLocalFile() == path) { + *out = song; + return true; + } + } + return false; + +} + +void CollectionWatcher::DirectoryChanged(const QString &subdir) { + + // Find what dir it was in + QHash::const_iterator it = subdir_mapping_.constFind(subdir); + if (it == subdir_mapping_.constEnd()) { + return; + } + Directory dir = *it; + + qLog(Debug) << "Subdir" << subdir << "changed under directory" << dir.path << "id" << dir.id; + + // Queue the subdir for rescanning + if (!rescan_queue_[dir.id].contains(subdir)) rescan_queue_[dir.id] << subdir; + + if (!rescan_paused_) rescan_timer_->start(); + +} + +void CollectionWatcher::RescanPathsNow() { + + for (int dir : rescan_queue_.keys()) { + if (stop_requested_) return; + ScanTransaction transaction(this, dir, false); + transaction.AddToProgressMax(rescan_queue_[dir].count()); + + for (const QString &path : rescan_queue_[dir]) { + if (stop_requested_) return; + Subdirectory subdir; + subdir.directory_id = dir; + subdir.mtime = 0; + subdir.path = path; + ScanSubdirectory(path, subdir, &transaction); + } + } + + rescan_queue_.clear(); + + emit CompilationsNeedUpdating(); + +} + +QString CollectionWatcher::PickBestImage(const QStringList &images) { + + // This is used when there is more than one image in a directory. + // Pick the biggest image that matches the most important filter + + QStringList filtered; + + for (const QString &filter_text : best_image_filters_) { + // the images in the images list are represented by a full path, + // so we need to isolate just the filename + for (const QString& image : images) { + QFileInfo file_info(image); + QString filename(file_info.fileName()); + if (filename.contains(filter_text, Qt::CaseInsensitive)) + filtered << image; + } + + /* We assume the filters are give in the order best to worst, so + if we've got a result, we go with it. Otherwise we might + start capturing more generic rules */ + if (!filtered.isEmpty()) break; + } + + if (filtered.isEmpty()) { + // the filter was too restrictive, just use the original list + filtered = images; + } + + int biggest_size = 0; + QString biggest_path; + + for (const QString& path : filtered) { + QImage image(path); + if (image.isNull()) continue; + + int size = image.width() * image.height(); + if (size > biggest_size) { + biggest_size = size; + biggest_path = path; + } + } + + return biggest_path; + +} + +QString CollectionWatcher::ImageForSong(const QString &path, QMap &album_art) { + + QString dir(DirectoryPart(path)); + + if (album_art.contains(dir)) { + if (album_art[dir].count() == 1) + return album_art[dir][0]; + else { + QString best_image = PickBestImage(album_art[dir]); + album_art[dir] = QStringList() << best_image; + return best_image; + } + } + return QString(); + +} + +void CollectionWatcher::ReloadSettingsAsync() { + + QMetaObject::invokeMethod(this, "ReloadSettings", Qt::QueuedConnection); + +} + +void CollectionWatcher::ReloadSettings() { + + const bool was_monitoring_before = monitor_; + QSettings s; + + s.beginGroup(CollectionSettingsPage::kSettingsGroup); + scan_on_startup_ = s.value("startup_scan", true).toBool(); + monitor_ = s.value("monitor", true).toBool(); + + best_image_filters_.clear(); + QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList(); + for (const QString& filter : filters) { + QString s = filter.trimmed(); + if (!s.isEmpty()) best_image_filters_ << s; + } + + if (!monitor_ && was_monitoring_before) { + fs_watcher_->Clear(); + } + else if (monitor_ && !was_monitoring_before) { + // Add all directories to all QFileSystemWatchers again + for (const Directory& dir : watched_dirs_.values()) { + SubdirectoryList subdirs = backend_->SubdirsInDirectory(dir.id); + for (const Subdirectory& subdir : subdirs) { + AddWatch(dir, subdir.path); + } + } + } + +} + +void CollectionWatcher::SetRescanPausedAsync(bool pause) { + + QMetaObject::invokeMethod(this, "SetRescanPaused", Qt::QueuedConnection, Q_ARG(bool, pause)); + +} + +void CollectionWatcher::SetRescanPaused(bool pause) { + + rescan_paused_ = pause; + if (!rescan_paused_ && !rescan_queue_.isEmpty()) RescanPathsNow(); + +} + +void CollectionWatcher::IncrementalScanAsync() { + + QMetaObject::invokeMethod(this, "IncrementalScanNow", Qt::QueuedConnection); + +} + +void CollectionWatcher::FullScanAsync() { + + QMetaObject::invokeMethod(this, "FullScanNow", Qt::QueuedConnection); + +} + +void CollectionWatcher::IncrementalScanNow() { PerformScan(true, false); } + +void CollectionWatcher::FullScanNow() { PerformScan(false, true); } + +void CollectionWatcher::PerformScan(bool incremental, bool ignore_mtimes) { + + for (const Directory & dir : watched_dirs_.values()) { + ScanTransaction transaction(this, dir.id, incremental, ignore_mtimes); + SubdirectoryList subdirs(transaction.GetAllSubdirs()); + transaction.AddToProgressMax(subdirs.count()); + + for (const Subdirectory & subdir : subdirs) { + if (stop_requested_) return; + + ScanSubdirectory(subdir.path, subdir, &transaction); + } + } + + emit CompilationsNeedUpdating(); + +} diff --git a/src/collection/collectionwatcher.h b/src/collection/collectionwatcher.h new file mode 100644 index 00000000..c743e677 --- /dev/null +++ b/src/collection/collectionwatcher.h @@ -0,0 +1,213 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COLLECTIONWATCHER_H +#define COLLECTIONWATCHER_H + +#include "config.h" + +#include "directory.h" + +#include +#include +#include +#include + +#include "core/song.h" + +class QFileSystemWatcher; +class QTimer; + +class CueParser; +class FileSystemWatcherInterface; +class CollectionBackend; +class TaskManager; + +class CollectionWatcher : public QObject { + Q_OBJECT + + public: + CollectionWatcher(QObject *parent = nullptr); + + void set_backend(CollectionBackend *backend) { backend_ = backend; } + void set_task_manager(TaskManager *task_manager) { task_manager_ = task_manager; } + void set_device_name(const QString& device_name) { device_name_ = device_name; } + + void IncrementalScanAsync(); + void FullScanAsync(); + void SetRescanPausedAsync(bool pause); + void ReloadSettingsAsync(); + + void Stop() { stop_requested_ = true; } + +signals: + void NewOrUpdatedSongs(const SongList &songs); + void SongsMTimeUpdated(const SongList &songs); + void SongsDeleted(const SongList &songs); + void SongsReadded(const SongList &songs, bool unavailable = false); + void SubdirsDiscovered(const SubdirectoryList &subdirs); + void SubdirsMTimeUpdated(const SubdirectoryList &subdirs); + void CompilationsNeedUpdating(); + + void ScanStarted(int task_id); + + public slots: + void ReloadSettings(); + void AddDirectory(const Directory &dir, const SubdirectoryList &subdirs); + void RemoveDirectory(const Directory &dir); + void SetRescanPaused(bool pause); + + private: + // This class encapsulates a full or partial scan of a directory. + // Each directory has one or more subdirectories, and any number of + // subdirectories can be scanned during one transaction. ScanSubdirectory() + // adds its results to the members of this transaction class, and they are + // "committed" through calls to the CollectionBackend in the transaction's dtor. + // The transaction also caches the list of songs in this directory according + // to the collection. Multiple calls to FindSongsInSubdirectory during one + // transaction will only result in one call to + // CollectionBackend::FindSongsInDirectory. + class ScanTransaction { + public: + ScanTransaction(CollectionWatcher *watcher, int dir, bool incremental, bool ignores_mtime = false); + ~ScanTransaction(); + + SongList FindSongsInSubdirectory(const QString &path); + bool HasSeenSubdir(const QString &path); + void SetKnownSubdirs(const SubdirectoryList &subdirs); + SubdirectoryList GetImmediateSubdirs(const QString &path); + SubdirectoryList GetAllSubdirs(); + + void AddToProgress(int n = 1); + void AddToProgressMax(int n); + + int dir() const { return dir_; } + bool is_incremental() const { return incremental_; } + bool ignores_mtime() const { return ignores_mtime_; } + + SongList deleted_songs; + SongList readded_songs; + SongList new_songs; + SongList touched_songs; + SubdirectoryList new_subdirs; + SubdirectoryList touched_subdirs; + + private: + ScanTransaction(const ScanTransaction&) {} + ScanTransaction& operator=(const ScanTransaction&) { return *this; } + + int task_id_; + int progress_; + int progress_max_; + + int dir_; + // Incremental scan enters a directory only if it has changed since the last scan. + bool incremental_; + // This type of scan updates every file in a folder that's + // being scanned. Even if it detects the file hasn't changed since + // the last scan. Also, since it's ignoring mtimes on folders too, + // it will go as deep in the folder hierarchy as it's possible. + bool ignores_mtime_; + + CollectionWatcher *watcher_; + + SongList cached_songs_; + bool cached_songs_dirty_; + + SubdirectoryList known_subdirs_; + bool known_subdirs_dirty_; + }; + + private slots: + void DirectoryChanged(const QString &path); + void IncrementalScanNow(); + void FullScanNow(); + void RescanPathsNow(); + void ScanSubdirectory(const QString &path, const Subdirectory &subdir, ScanTransaction *t, bool force_noincremental = false); + + private: + static bool FindSongByPath(const SongList &list, const QString &path, Song *out); + inline static QString NoExtensionPart(const QString &fileName); + inline static QString ExtensionPart(const QString &fileName); + inline static QString DirectoryPart(const QString &fileName); + QString PickBestImage(const QStringList &images); + QString ImageForSong(const QString &path, QMap &album_art); + void AddWatch(const Directory &dir, const QString &path); + uint GetMtimeForCue(const QString &cue_path); + void PerformScan(bool incremental, bool ignore_mtimes); + + // Updates the sections of a cue associated and altered (according to mtime) + // media file during a scan. + void UpdateCueAssociatedSongs(const QString &file, const QString &path, const QString &matching_cue, const QString &image, ScanTransaction *t); + // Updates a single non-cue associated and altered (according to mtime) song + // during a scan. + void UpdateNonCueAssociatedSong(const QString &file, const Song &matching_song, const QString &image, bool cue_deleted, ScanTransaction *t); + // Updates a new song with some metadata taken from it's equivalent old + // song (for example rating and score). + void PreserveUserSetData(const QString &file, const QString &image, const Song &matching_song, Song *out, ScanTransaction *t); + // Scans a single media file that's present on the disk but not yet in the collection. + // It may result in a multiple files added to the collection when the media file + // has many sections (like a CUE related media file). + SongList ScanNewFile(const QString &file, const QString &path, const QString &matching_cue, QSet *cues_processed); + + private: + CollectionBackend *backend_; + TaskManager *task_manager_; + QString device_name_; + + FileSystemWatcherInterface *fs_watcher_; + QHash subdir_mapping_; + + /* A list of words use to try to identify the (likely) best image + * found in an directory to use as cover artwork. + * e.g. using ["front", "cover"] would identify front.jpg and + * exclude back.jpg. + */ + QStringList best_image_filters_; + + bool stop_requested_; + bool scan_on_startup_; + bool monitor_; + + QMap watched_dirs_; + QTimer *rescan_timer_; + QMap rescan_queue_; // dir id -> list of subdirs to be scanned + bool rescan_paused_; + + int total_watches_; + + CueParser *cue_parser_; + + static QStringList sValidImages; +}; + +inline QString CollectionWatcher::NoExtensionPart(const QString& fileName) { + return fileName.contains('.') ? fileName.section('.', 0, -2) : ""; +} +// Thanks Amarok +inline QString CollectionWatcher::ExtensionPart(const QString& fileName) { + return fileName.contains( '.' ) ? fileName.mid( fileName.lastIndexOf('.') + 1 ).toLower() : ""; +} +inline QString CollectionWatcher::DirectoryPart(const QString& fileName) { + return fileName.section('/', 0, -2); +} + +#endif // COLLECTIONWATCHER_H + diff --git a/src/collection/directory.h b/src/collection/directory.h new file mode 100644 index 00000000..feb3ba13 --- /dev/null +++ b/src/collection/directory.h @@ -0,0 +1,61 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef DIRECTORY_H +#define DIRECTORY_H + +#include "config.h" + +#include +#include +#include + +class QSqlQuery; + +struct Directory { + Directory() : id(-1) {} + + bool operator ==(const Directory& other) const { + return path == other.path && id == other.id; + } + + QString path; + int id; +}; +Q_DECLARE_METATYPE(Directory) + +typedef QList DirectoryList; +Q_DECLARE_METATYPE(DirectoryList) + + +struct Subdirectory { + Subdirectory() : directory_id(-1), mtime(0) {} + + int directory_id; + QString path; + uint mtime; +}; +Q_DECLARE_METATYPE(Subdirectory) + +typedef QList SubdirectoryList; +Q_DECLARE_METATYPE(SubdirectoryList) + +#endif // DIRECTORY_H + diff --git a/src/collection/groupbydialog.cpp b/src/collection/groupbydialog.cpp new file mode 100644 index 00000000..a7e4b5d0 --- /dev/null +++ b/src/collection/groupbydialog.cpp @@ -0,0 +1,121 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include + +#include "groupbydialog.h" +#include "ui_groupbydialog.h" + +// boost::multi_index still relies on these being in the global namespace. +using std::placeholders::_1; +using std::placeholders::_2; + +#include +#include +#include + +using boost::multi_index_container; +using boost::multi_index::indexed_by; +using boost::multi_index::ordered_unique; +using boost::multi_index::tag; +using boost::multi_index::member; + +namespace { + +struct Mapping { + Mapping(CollectionModel::GroupBy g, int i) : group_by(g), combo_box_index(i) {} + + CollectionModel::GroupBy group_by; + int combo_box_index; +}; + +struct tag_index {}; +struct tag_group_by {}; + +} // namespace + +class GroupByDialogPrivate { + private: + typedef multi_index_container< + Mapping, + indexed_by< + ordered_unique, + member >, + ordered_unique, + member > > > MappingContainer; + + public: + MappingContainer mapping_; +}; + +GroupByDialog::GroupByDialog(QWidget* parent) : QDialog(parent), ui_(new Ui_GroupByDialog), p_(new GroupByDialogPrivate) { + + ui_->setupUi(this); + Reset(); + + p_->mapping_.insert(Mapping(CollectionModel::GroupBy_None, 0)); + p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Album, 1)); + p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Artist, 2)); + p_->mapping_.insert(Mapping(CollectionModel::GroupBy_AlbumArtist, 3)); + p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Composer, 4)); + p_->mapping_.insert(Mapping(CollectionModel::GroupBy_FileType, 5)); + p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Genre, 6)); + p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Year, 7)); + p_->mapping_.insert(Mapping(CollectionModel::GroupBy_OriginalYear, 8)); + p_->mapping_.insert(Mapping(CollectionModel::GroupBy_YearAlbum, 9)); + p_->mapping_.insert(Mapping(CollectionModel::GroupBy_OriginalYearAlbum, 10)); + p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Bitrate, 11)); + p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Disc, 12)); + p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Performer, 13)); + p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Grouping, 14)); + + connect(ui_->button_box->button(QDialogButtonBox::Reset), SIGNAL(clicked()), SLOT(Reset())); + + resize(sizeHint()); +} + +GroupByDialog::~GroupByDialog() {} + +void GroupByDialog::Reset() { + ui_->first->setCurrentIndex(2); // Artist + ui_->second->setCurrentIndex(1); // Album + ui_->third->setCurrentIndex(0); // None +} + +void GroupByDialog::accept() { + emit Accepted(CollectionModel::Grouping( + p_->mapping_.get().find(ui_->first->currentIndex())->group_by, + p_->mapping_.get().find(ui_->second->currentIndex())->group_by, + p_->mapping_.get().find(ui_->third->currentIndex())->group_by) + ); + QDialog::accept(); +} + +void GroupByDialog::CollectionGroupingChanged(const CollectionModel::Grouping &g) { + ui_->first->setCurrentIndex(p_->mapping_.get().find(g[0])->combo_box_index); + ui_->second->setCurrentIndex(p_->mapping_.get().find(g[1])->combo_box_index); + ui_->third->setCurrentIndex(p_->mapping_.get().find(g[2])->combo_box_index); +} + diff --git a/src/collection/groupbydialog.h b/src/collection/groupbydialog.h new file mode 100644 index 00000000..a43d2b2b --- /dev/null +++ b/src/collection/groupbydialog.h @@ -0,0 +1,57 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef GROUPBYDIALOG_H +#define GROUPBYDIALOG_H + +#include "config.h" + +#include + +#include + +#include "collectionmodel.h" + +class GroupByDialogPrivate; +class Ui_GroupByDialog; + +class GroupByDialog : public QDialog { + Q_OBJECT + + public: + GroupByDialog(QWidget *parent = nullptr); + ~GroupByDialog(); + + public slots: + void CollectionGroupingChanged(const CollectionModel::Grouping &g); + void accept(); + +signals: + void Accepted(const CollectionModel::Grouping &g); + + private slots: + void Reset(); + + private: + std::unique_ptr ui_; + std::unique_ptr p_; +}; + +#endif // GROUPBYDIALOG_H diff --git a/src/collection/groupbydialog.ui b/src/collection/groupbydialog.ui new file mode 100644 index 00000000..f112437e --- /dev/null +++ b/src/collection/groupbydialog.ui @@ -0,0 +1,366 @@ + + + GroupByDialog + + + + 0 + 0 + 354 + 236 + + + + Collection advanced grouping + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + + + + You can change the way the songs in the collection are organised. + + + true + + + + + + + Group Collection by... + + + + + + First level + + + + + + + + None + + + + + Album + + + + + Artist + + + + + Album artist + + + + + Composer + + + + + File type + + + + + Genre + + + + + Year + + + + + Original year + + + + + Year - Album + + + + + Original year - Album + + + + + Bitrate + + + + + Disc + + + + + Performer + + + + + Grouping + + + + + + + + Second level + + + + + + + + None + + + + + Album + + + + + Artist + + + + + Album artist + + + + + Composer + + + + + File type + + + + + Genre + + + + + Year + + + + + Original year + + + + + Year - Album + + + + + Original year - Album + + + + + Bitrate + + + + + Disc + + + + + Performer + + + + + Grouping + + + + + + + + Third level + + + + + + + + None + + + + + Album + + + + + Artist + + + + + Album artist + + + + + Composer + + + + + File type + + + + + Genre + + + + + Year + + + + + Original year + + + + + Year - Album + + + + + Original year - Album + + + + + Bitrate + + + + + Disc + + + + + Performer + + + + + Grouping + + + + + + + + + + + Qt::Vertical + + + + 20 + 11 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset + + + + + + + first + second + third + button_box + + + + + + + button_box + accepted() + GroupByDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + button_box + rejected() + GroupByDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/collection/savedgroupingmanager.cpp b/src/collection/savedgroupingmanager.cpp new file mode 100644 index 00000000..97d109e1 --- /dev/null +++ b/src/collection/savedgroupingmanager.cpp @@ -0,0 +1,164 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "core/iconloader.h" +#include "collectionfilterwidget.h" +#include "collectionmodel.h" +#include "savedgroupingmanager.h" +#include "ui_savedgroupingmanager.h" + +#include +#include +#include +#include + +SavedGroupingManager::SavedGroupingManager(QWidget *parent) + : QDialog(parent), + ui_(new Ui_SavedGroupingManager), + model_(new QStandardItemModel(0, 4, this)) { + + ui_->setupUi(this); + + model_->setHorizontalHeaderItem(0, new QStandardItem(tr("Name"))); + model_->setHorizontalHeaderItem(1, new QStandardItem(tr("First level"))); + model_->setHorizontalHeaderItem(2, new QStandardItem(tr("Second Level"))); + model_->setHorizontalHeaderItem(3, new QStandardItem(tr("Third Level"))); + ui_->list->setModel(model_); + ui_->remove->setIcon(IconLoader::Load("edit-delete")); + ui_->remove->setEnabled(false); + + ui_->remove->setShortcut(QKeySequence::Delete); + connect(ui_->list->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), SLOT(UpdateButtonState())); + + connect(ui_->remove, SIGNAL(clicked()), SLOT(Remove())); +} + +SavedGroupingManager::~SavedGroupingManager() { + delete ui_; + delete model_; +} + +QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy &g) { + switch (g) { + case CollectionModel::GroupBy_None: { + return tr("None"); + } + case CollectionModel::GroupBy_Artist: { + return tr("Artist"); + } + case CollectionModel::GroupBy_Album: { + return tr("Album"); + } + case CollectionModel::GroupBy_YearAlbum: { + return tr("Year - Album"); + } + case CollectionModel::GroupBy_Year: { + return tr("Year"); + } + case CollectionModel::GroupBy_Composer: { + return tr("Composer"); + } + case CollectionModel::GroupBy_Genre: { + return tr("Genre"); + } + case CollectionModel::GroupBy_AlbumArtist: { + return tr("Album artist"); + } + case CollectionModel::GroupBy_FileType: { + return tr("File type"); + } + case CollectionModel::GroupBy_Performer: { + return tr("Performer"); + } + case CollectionModel::GroupBy_Grouping: { + return tr("Grouping"); + } + case CollectionModel::GroupBy_Bitrate: { + return tr("Bitrate"); + } + case CollectionModel::GroupBy_Disc: { + return tr("Disc"); + } + case CollectionModel::GroupBy_OriginalYearAlbum: { + return tr("Original year - Album"); + } + case CollectionModel::GroupBy_OriginalYear: { + return tr("Original year"); + } + default: { return tr("Unknown"); } + } + +} + +void SavedGroupingManager::UpdateModel() { + + model_->setRowCount(0); // don't use clear, it deletes headers + QSettings s; + s.beginGroup(CollectionModel::kSavedGroupingsSettingsGroup); + QStringList saved = s.childKeys(); + for (int i = 0; i < saved.size(); ++i) { + QByteArray bytes = s.value(saved.at(i)).toByteArray(); + QDataStream ds(&bytes, QIODevice::ReadOnly); + CollectionModel::Grouping g; + ds >> g; + + QList list; + list << new QStandardItem(saved.at(i)) + << new QStandardItem(GroupByToString(g.first)) + << new QStandardItem(GroupByToString(g.second)) + << new QStandardItem(GroupByToString(g.third)); + + model_->appendRow(list); + } + +} + +void SavedGroupingManager::Remove() { + + if (ui_->list->selectionModel()->hasSelection()) { + QSettings s; + s.beginGroup(CollectionModel::kSavedGroupingsSettingsGroup); + for (const QModelIndex &index : + ui_->list->selectionModel()->selectedRows()) { + if (index.isValid()) { + qLog(Debug) << "Remove saved grouping: " << model_->item(index.row(), 0)->text(); + s.remove(model_->item(index.row(), 0)->text()); + } + } + } + UpdateModel(); + filter_->UpdateGroupByActions(); + +} + +void SavedGroupingManager::UpdateButtonState() { + + if (ui_->list->selectionModel()->hasSelection()) { + const QModelIndex current = ui_->list->selectionModel()->currentIndex(); + ui_->remove->setEnabled(current.isValid()); + } + else { + ui_->remove->setEnabled(false); + } + +} + diff --git a/src/collection/savedgroupingmanager.h b/src/collection/savedgroupingmanager.h new file mode 100644 index 00000000..866211cf --- /dev/null +++ b/src/collection/savedgroupingmanager.h @@ -0,0 +1,56 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2015, Nick Lanham + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef SAVEDGROUPINGMANAGER_H +#define SAVEDGROUPINGMANAGER_H + +#include "config.h" + +#include +#include + +#include "collectionmodel.h" + +class Ui_SavedGroupingManager; +class CollectionFilterWidget; + +class SavedGroupingManager : public QDialog { + Q_OBJECT + + public: + SavedGroupingManager(QWidget *parent = nullptr); + ~SavedGroupingManager(); + + void UpdateModel(); + void SetFilter(CollectionFilterWidget* filter) { filter_ = filter; } + + static QString GroupByToString(const CollectionModel::GroupBy &g); + + private slots: + void UpdateButtonState(); + void Remove(); + + private: + Ui_SavedGroupingManager* ui_; + QStandardItemModel *model_; + CollectionFilterWidget *filter_; +}; + +#endif // SAVEDGROUPINGMANAGER_H diff --git a/src/collection/savedgroupingmanager.ui b/src/collection/savedgroupingmanager.ui new file mode 100644 index 00000000..2b5faa5c --- /dev/null +++ b/src/collection/savedgroupingmanager.ui @@ -0,0 +1,144 @@ + + + SavedGroupingManager + + + + 0 + 0 + 582 + 363 + + + + Saved Grouping Manager + + + + :/icon.png:/icon.png + + + + + + + + true + + + true + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + false + + + true + + + + + + + + + false + + + Remove + + + + 16 + 16 + + + + Ctrl+Up + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + + + buttonBox + accepted() + SavedGroupingManager + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SavedGroupingManager + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/collection/sqlrow.cpp b/src/collection/sqlrow.cpp new file mode 100644 index 00000000..d76c6df2 --- /dev/null +++ b/src/collection/sqlrow.cpp @@ -0,0 +1,40 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "collectionquery.h" +#include "sqlrow.h" + +#include +#include + +SqlRow::SqlRow(const QSqlQuery &query) { Init(query); } + +SqlRow::SqlRow(const CollectionQuery &query) { Init(query); } + +void SqlRow::Init(const QSqlQuery &query) { + + int rows = query.record().count(); + for (int i = 0; i < rows; ++i) { + columns_ << query.value(i); + } + +} diff --git a/src/collection/sqlrow.h b/src/collection/sqlrow.h new file mode 100644 index 00000000..4d04a28d --- /dev/null +++ b/src/collection/sqlrow.h @@ -0,0 +1,54 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef SQLROW_H +#define SQLROW_H + +#include "config.h" + +#include +#include + +class QSqlQuery; + +class CollectionQuery; + +class SqlRow { + + public: + // WARNING: Implicit construction from QSqlQuery and CollectionQuery. + SqlRow(const QSqlQuery &query); + SqlRow(const CollectionQuery &query); + + const QVariant& value(int i) const { return columns_[i]; } + + QList columns_; + + private: + SqlRow(); + + void Init(const QSqlQuery &query); + +}; + +typedef QList SqlRowList; + +#endif + diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 00000000..56bd0e60 --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,48 @@ +/* This file is part of Strawberry. + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#ifndef CONFIG_H_IN +#define CONFIG_H_IN + +#define CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" +#define CMAKE_EXECUTABLE_SUFFIX "${CMAKE_EXECUTABLE_SUFFIX}" + +#cmakedefine HAVE_GIO +#cmakedefine HAVE_DBUS +#cmakedefine HAVE_UDISKS2 +#cmakedefine HAVE_DEVICEKIT +#cmakedefine HAVE_IMOBILEDEVICE +#cmakedefine HAVE_LIBARCHIVE +#cmakedefine HAVE_AUDIOCD +#cmakedefine HAVE_LIBGPOD +#cmakedefine HAVE_LIBLASTFM +#cmakedefine HAVE_LIBLASTFM1 +#cmakedefine HAVE_LIBMTP +#cmakedefine HAVE_LIBPULSE +#cmakedefine HAVE_QCA +#cmakedefine HAVE_SPARKLE +#cmakedefine IMOBILEDEVICE_USES_UDIDS +#cmakedefine TAGLIB_HAS_OPUS +#cmakedefine USE_INSTALL_PREFIX +#cmakedefine USE_SYSTEM_SHA2 + +#cmakedefine HAVE_GSTREAMER +#cmakedefine HAVE_VLC +#cmakedefine HAVE_XINE +#cmakedefine HAVE_PHONON + +#endif // CONFIG_H_IN + diff --git a/src/core/SBSystemPreferences.h b/src/core/SBSystemPreferences.h new file mode 100644 index 00000000..387d3002 --- /dev/null +++ b/src/core/SBSystemPreferences.h @@ -0,0 +1,171 @@ +/* + * SBSystemPreferences.h + * + * Generated with: + * sdef "/Applications/System Preferences.app" | sdp -fh --basename + *SBSystemPreferences -o SBSystemPreferences.h + */ + +#import +#import + +@class SBSystemPreferencesApplication, SBSystemPreferencesDocument, + SBSystemPreferencesWindow, SBSystemPreferencesPane, + SBSystemPreferencesAnchor; + +enum SBSystemPreferencesSaveOptions { + SBSystemPreferencesSaveOptionsYes = 'yes ' /* Save the file. */, + SBSystemPreferencesSaveOptionsNo = 'no ' /* Do not save the file. */, + SBSystemPreferencesSaveOptionsAsk = + 'ask ' /* Ask the user whether or not to save the file. */ +}; +typedef enum SBSystemPreferencesSaveOptions SBSystemPreferencesSaveOptions; + +enum SBSystemPreferencesPrintingErrorHandling { + SBSystemPreferencesPrintingErrorHandlingStandard = + 'lwst' /* Standard PostScript error handling */, + SBSystemPreferencesPrintingErrorHandlingDetailed = + 'lwdt' /* print a detailed report of PostScript errors */ +}; +typedef enum SBSystemPreferencesPrintingErrorHandling + SBSystemPreferencesPrintingErrorHandling; + +/* + * Standard Suite + */ + +// The application's top-level scripting object. +@interface SBSystemPreferencesApplication : SBApplication + +- (SBElementArray*)documents; +- (SBElementArray*)windows; + +@property(copy, readonly) NSString* name; // The name of the application. +@property(readonly) BOOL frontmost; // Is this the active application? +@property(copy, readonly) + NSString* version; // The version number of the application. + +- (id)open:(id)x; // Open a document. +- (void)print:(id)x + withProperties:(NSDictionary*)withProperties + printDialog:(BOOL)printDialog; // Print a document. +- (void)quitSaving: + (SBSystemPreferencesSaveOptions)saving; // Quit the application. +- (BOOL)exists:(id)x; // Verify that an object exists. + +@end + +// A document. +@interface SBSystemPreferencesDocument : SBObject + +@property(copy, readonly) NSString* name; // Its name. +@property(readonly) BOOL modified; // Has it been modified since the last save? +@property(copy, readonly) NSURL* file; // Its location on disk, if it has one. + +- (void)closeSaving:(SBSystemPreferencesSaveOptions)saving + savingIn:(NSURL*)savingIn; // Close a document. +- (void)saveIn:(NSURL*)in_ as:(id)as; // Save a document. +- (void)printWithProperties:(NSDictionary*)withProperties + printDialog:(BOOL)printDialog; // Print a document. +- (void) delete; // Delete an object. +- (void)duplicateTo:(SBObject*)to + withProperties:(NSDictionary*)withProperties; // Copy an object. +- (void)moveTo:(SBObject*)to; // Move an object to a new location. + +@end + +// A window. +@interface SBSystemPreferencesWindow : SBObject + +@property(copy, readonly) NSString* name; // The title of the window. +- (NSInteger)id; // The unique identifier of the window. +@property NSInteger index; // The index of the window, ordered front to back. +@property NSRect bounds; // The bounding rectangle of the window. +@property(readonly) BOOL closeable; // Does the window have a close button? +@property(readonly) + BOOL miniaturizable; // Does the window have a minimize button? +@property BOOL miniaturized; // Is the window minimized right now? +@property(readonly) BOOL resizable; // Can the window be resized? +@property BOOL visible; // Is the window visible right now? +@property(readonly) BOOL zoomable; // Does the window have a zoom button? +@property BOOL zoomed; // Is the window zoomed right now? +@property(copy, readonly) SBSystemPreferencesDocument* + document; // The document whose contents are displayed in the window. + +- (void)closeSaving:(SBSystemPreferencesSaveOptions)saving + savingIn:(NSURL*)savingIn; // Close a document. +- (void)saveIn:(NSURL*)in_ as:(id)as; // Save a document. +- (void)printWithProperties:(NSDictionary*)withProperties + printDialog:(BOOL)printDialog; // Print a document. +- (void) delete; // Delete an object. +- (void)duplicateTo:(SBObject*)to + withProperties:(NSDictionary*)withProperties; // Copy an object. +- (void)moveTo:(SBObject*)to; // Move an object to a new location. + +@end + +/* + * System Preferences + */ + +// System Preferences top level scripting object +@interface SBSystemPreferencesApplication (SystemPreferences) + +- (SBElementArray*)panes; + +@property(copy) + SBSystemPreferencesPane* currentPane; // the currently selected pane +@property(copy, readonly) SBSystemPreferencesWindow* + preferencesWindow; // the main preferences window +@property BOOL showAll; // Is SystemPrefs in show all view. (Setting to false + // will do nothing) + +@end + +// a preference pane +@interface SBSystemPreferencesPane : SBObject + +- (SBElementArray*)anchors; + +- (NSString*)id; // locale independent name of the preference pane; can refer + // to a pane using the expression: pane id "" +@property(copy, readonly) + NSString* localizedName; // localized name of the preference pane +@property(copy, readonly) NSString* name; // name of the preference pane as it + // appears in the title bar; can + // refer to a pane using the + // expression: pane "" + +- (void)closeSaving:(SBSystemPreferencesSaveOptions)saving + savingIn:(NSURL*)savingIn; // Close a document. +- (void)saveIn:(NSURL*)in_ as:(id)as; // Save a document. +- (void)printWithProperties:(NSDictionary*)withProperties + printDialog:(BOOL)printDialog; // Print a document. +- (void) delete; // Delete an object. +- (void)duplicateTo:(SBObject*)to + withProperties:(NSDictionary*)withProperties; // Copy an object. +- (void)moveTo:(SBObject*)to; // Move an object to a new location. +- (id)reveal; // Reveals an anchor within a preference pane or preference pane + // itself + +@end + +// an anchor within a preference pane +@interface SBSystemPreferencesAnchor : SBObject + +@property(copy, readonly) + NSString* name; // name of the anchor within a preference pane + +- (void)closeSaving:(SBSystemPreferencesSaveOptions)saving + savingIn:(NSURL*)savingIn; // Close a document. +- (void)saveIn:(NSURL*)in_ as:(id)as; // Save a document. +- (void)printWithProperties:(NSDictionary*)withProperties + printDialog:(BOOL)printDialog; // Print a document. +- (void) delete; // Delete an object. +- (void)duplicateTo:(SBObject*)to + withProperties:(NSDictionary*)withProperties; // Copy an object. +- (void)moveTo:(SBObject*)to; // Move an object to a new location. +- (id)reveal; // Reveals an anchor within a preference pane or preference pane + // itself + +@end diff --git a/src/core/appearance.cpp b/src/core/appearance.cpp new file mode 100644 index 00000000..2e48a819 --- /dev/null +++ b/src/core/appearance.cpp @@ -0,0 +1,91 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "appearance.h" + +#include +#include + +#include "settings/appearancesettingspage.h" + +const char *Appearance::kUseCustomColorSet = "use-custom-set"; +const char *Appearance::kForegroundColor = "foreground-color"; +const char *Appearance::kBackgroundColor = "background-color"; + +const QPalette Appearance::kDefaultPalette = QPalette(); + +Appearance::Appearance(QObject *parent) : QObject(parent) { + + QSettings s; + s.beginGroup(AppearanceSettingsPage::kSettingsGroup); + QPalette p = QApplication::palette(); + background_color_ = s.value(kBackgroundColor, p.color(QPalette::WindowText)).value(); + foreground_color_ = s.value(kForegroundColor, p.color(QPalette::Window)).value(); + +} + +void Appearance::LoadUserTheme() { + + QSettings s; + s.beginGroup(AppearanceSettingsPage::kSettingsGroup); + bool use_a_custom_color_set = s.value(kUseCustomColorSet).toBool(); + if (!use_a_custom_color_set) return; + + ChangeForegroundColor(foreground_color_); + ChangeBackgroundColor(background_color_); + +} + +void Appearance::ResetToSystemDefaultTheme() { + QApplication::setPalette(kDefaultPalette); +} + +void Appearance::ChangeForegroundColor(const QColor &color) { + + // Get the application palette + QPalette p = QApplication::palette(); + + // Modify the palette + p.setColor(QPalette::WindowText, color); + p.setColor(QPalette::Text, color); + + // Make the modified palette the new application's palette + QApplication::setPalette(p); + foreground_color_ = color; + +} + +void Appearance::ChangeBackgroundColor(const QColor &color) { + + // Get the application palette + QPalette p = QApplication::palette(); + + // Modify the palette + p.setColor(QPalette::Window, color); + p.setColor(QPalette::Base, color); + + // Make the modified palette the new application's palette + QApplication::setPalette(p); + background_color_ = color; + +} + diff --git a/src/core/appearance.h b/src/core/appearance.h new file mode 100644 index 00000000..8e8a1ae8 --- /dev/null +++ b/src/core/appearance.h @@ -0,0 +1,51 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef APPEARANCE_H +#define APPEARANCE_H + +#include "config.h" + +#include +#include + +class Appearance : public QObject { + public: + explicit Appearance(QObject* parent = nullptr); + // Load the user preferred theme, which could the default system theme or a + // custom set of colors that user has chosen + void LoadUserTheme(); + void ResetToSystemDefaultTheme(); + void ChangeForegroundColor(const QColor& color); + void ChangeBackgroundColor(const QColor& color); + + static const char* kSettingsGroup; + static const char* kUseCustomColorSet; + static const char* kForegroundColor; + static const char* kBackgroundColor; + static const QPalette kDefaultPalette; + + private: + QColor foreground_color_; + QColor background_color_; +}; + +#endif // APPEARANCE_H + diff --git a/src/core/application.cpp b/src/core/application.cpp new file mode 100644 index 00000000..b30df146 --- /dev/null +++ b/src/core/application.cpp @@ -0,0 +1,217 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "application.h" + +#include "config.h" + +#include "core/appearance.h" +#include "core/database.h" +#include "core/lazy.h" +#include "core/player.h" +#include "core/tagreaderclient.h" +#include "core/taskmanager.h" +#include "engine/enginetype.h" +#include "engine/enginedevice.h" +#include "device/devicemanager.h" +#include "collection/collectionbackend.h" +#include "collection/collection.h" +#include "playlist/playlistbackend.h" +#include "playlist/playlistmanager.h" +#include "covermanager/albumcoverloader.h" +#include "covermanager/coverproviders.h" +#include "covermanager/currentartloader.h" +#ifdef HAVE_LIBLASTFM + #include "covermanager/lastfmcoverprovider.h" +#endif // HAVE_LIBLASTFM +#include "covermanager/amazoncoverprovider.h" +#include "covermanager/discogscoverprovider.h" +#include "covermanager/musicbrainzcoverprovider.h" + +bool Application::kIsPortable = false; + +class ApplicationImpl { + public: + ApplicationImpl(Application *app) : + tag_reader_client_([=]() { + TagReaderClient *client = new TagReaderClient(app); + app->MoveToNewThread(client); + client->Start(); + return client; + }), + database_([=]() { + Database *db = new Database(app, app); + app->MoveToNewThread(db); + DoInAMinuteOrSo(db, SLOT(DoBackup())); + return db; + }), + appearance_([=]() { return new Appearance(app); }), + task_manager_([=]() { return new TaskManager(app); }), + player_([=]() { return new Player(app, app); }), + enginedevice_([=]() { return new EngineDevice(app); }), + device_manager_([=]() { return new DeviceManager(app, app); }), + collection_([=]() { return new Collection(app, app); }), + playlist_backend_([=]() { + PlaylistBackend *backend = new PlaylistBackend(app, app); + app->MoveToThread(backend, database_->thread()); + return backend; + }), + playlist_manager_([=]() { return new PlaylistManager(app); }), + cover_providers_([=]() { + CoverProviders *cover_providers = new CoverProviders(app); + // Initialize the repository of cover providers. + #ifdef HAVE_LIBLASTFM + cover_providers->AddProvider(new LastFmCoverProvider(app)); + #endif + cover_providers->AddProvider(new AmazonCoverProvider(app)); + cover_providers->AddProvider(new DiscogsCoverProvider(app)); + cover_providers->AddProvider(new MusicbrainzCoverProvider(app)); + return cover_providers; + }), + album_cover_loader_([=]() { + AlbumCoverLoader *loader = new AlbumCoverLoader(app); + app->MoveToNewThread(loader); + return loader; + }), + current_art_loader_([=]() { return new CurrentArtLoader(app, app); }) + { } + + Lazy tag_reader_client_; + Lazy database_; + Lazy appearance_; + Lazy task_manager_; + Lazy player_; + Lazy enginedevice_; + Lazy device_manager_; + Lazy collection_; + Lazy playlist_backend_; + Lazy playlist_manager_; + Lazy cover_providers_; + Lazy album_cover_loader_; + Lazy current_art_loader_; + +}; + +Application::Application(QObject *parent) + : QObject(parent), p_(new ApplicationImpl(this)) { + + enginedevice()->Init(); + collection()->Init(); + tag_reader_client(); + +} + +Application::~Application() { + + // It's important that the device manager is deleted before the database. + // Deleting the database deletes all objects that have been created in its + // thread, including some device collection backends. + p_->device_manager_.reset(); + + for (QThread *thread : threads_) { + thread->quit(); + } + + for (QThread *thread : threads_) { + thread->wait(); + } +} + +void Application::MoveToNewThread(QObject *object) { + + QThread *thread = new QThread(this); + + MoveToThread(object, thread); + + thread->start(); + threads_ << thread; +} + +void Application::MoveToThread(QObject *object, QThread *thread) { + object->setParent(nullptr); + object->moveToThread(thread); +} + +void Application::AddError(const QString& message) { emit ErrorAdded(message); } + +QString Application::language_without_region() const { + const int underscore = language_name_.indexOf('_'); + if (underscore != -1) { + return language_name_.left(underscore); + } + return language_name_; +} + +void Application::ReloadSettings() { emit SettingsChanged(); } + +void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) { + emit SettingsDialogRequested(page); +} + +AlbumCoverLoader *Application::album_cover_loader() const { + return p_->album_cover_loader_.get(); +} + +Appearance *Application::appearance() const { return p_->appearance_.get(); } + +CoverProviders *Application::cover_providers() const { + return p_->cover_providers_.get(); +} + +CurrentArtLoader *Application::current_art_loader() const { + return p_->current_art_loader_.get(); +} + +Database *Application::database() const { return p_->database_.get(); } + +DeviceManager *Application::device_manager() const { + return p_->device_manager_.get(); +} + +Collection *Application::collection() const { return p_->collection_.get(); } + +CollectionBackend *Application::collection_backend() const { + return collection()->backend(); +} + +CollectionModel *Application::collection_model() const { return collection()->model(); } + +Player *Application::player() const { return p_->player_.get(); } + +PlaylistBackend *Application::playlist_backend() const { + return p_->playlist_backend_.get(); +} + +PlaylistManager *Application::playlist_manager() const { + return p_->playlist_manager_.get(); +} + +TagReaderClient *Application::tag_reader_client() const { + return p_->tag_reader_client_.get(); +} + +TaskManager *Application::task_manager() const { + return p_->task_manager_.get(); +} + +EngineDevice *Application::enginedevice() const { + //qLog(Debug) << __PRETTY_FUNCTION__; + return p_->enginedevice_.get(); +} diff --git a/src/core/application.h b/src/core/application.h new file mode 100644 index 00000000..18e246d4 --- /dev/null +++ b/src/core/application.h @@ -0,0 +1,103 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef APPLICATION_H_ +#define APPLICATION_H_ + +#include "config.h" + +#include + +#include + +#include "settings/settingsdialog.h" + +class ApplicationImpl; +class TagReaderClient; +class Database; +class Appearance; +class TaskManager; +class Player; +class DeviceManager; +class Collection; +class PlaylistBackend; +class PlaylistManager; +class AlbumCoverLoader; +class CoverProviders; +class CurrentArtLoader; +class CollectionBackend; +class CollectionModel; +class EngineDevice; + +class Application : public QObject { + Q_OBJECT + + public: + static bool kIsPortable; + + explicit Application(QObject *parent = nullptr); + ~Application(); + + const QString &language_name() const { return language_name_; } + // Same as language_name, but remove the region code at the end if there is one + QString language_without_region() const; + void set_language_name(const QString &name) { language_name_ = name; } + + TagReaderClient *tag_reader_client() const; + Database *database() const; + Appearance *appearance() const; + TaskManager *task_manager() const; + Player *player() const; + EngineDevice *enginedevice() const; + DeviceManager *device_manager() const; + + Collection *collection() const; + + PlaylistBackend *playlist_backend() const; + PlaylistManager *playlist_manager() const; + + CoverProviders *cover_providers() const; + AlbumCoverLoader *album_cover_loader() const; + CurrentArtLoader *current_art_loader() const; + + CollectionBackend *collection_backend() const; + CollectionModel *collection_model() const; + + void MoveToNewThread(QObject *object); + void MoveToThread(QObject *object, QThread *thread); + + public slots: + void AddError(const QString &message); + void ReloadSettings(); + void OpenSettingsDialogAtPage(SettingsDialog::Page page); + +signals: + void ErrorAdded(const QString &message); + void SettingsChanged(); + void SettingsDialogRequested(SettingsDialog::Page page); + + private: + QString language_name_; + std::unique_ptr p_; + QList threads_; + +}; + +#endif // APPLICATION_H_ diff --git a/src/core/cachedlist.h b/src/core/cachedlist.h new file mode 100644 index 00000000..010ed010 --- /dev/null +++ b/src/core/cachedlist.h @@ -0,0 +1,103 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2011, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef CACHEDLIST_H +#define CACHEDLIST_H + +#include "config.h" + +#include +#include + +template +class CachedList { + public: + // Use a CachedList when you want to download and save a list of things from a + // remote service, updating it only periodically. + // T must be a registered metatype and must support being stored in + // QSettings. This usually means you have to implement QDataStream streaming + // operators, and use qRegisterMetaTypeStreamOperators. + + typedef QList ListType; + + CachedList(const QString &settings_group, const QString &name, int cache_duration_secs) + : settings_group_(settings_group), name_(name), cache_duration_secs_(cache_duration_secs) { + } + + void Load() { + QSettings s; + s.beginGroup(settings_group_); + + last_updated_ = s.value("last_refreshed_" + name_).toDateTime(); + data_.clear(); + + const int count = s.beginReadArray(name_ + "_data"); + for (int i = 0; i < count; ++i) { + s.setArrayIndex(i); + data_ << s.value("value").value(); + } + s.endArray(); + } + + void Save() const { + QSettings s; + s.beginGroup(settings_group_); + + s.setValue("last_refreshed_" + name_, last_updated_); + + s.beginWriteArray(name_ + "_data", data_.size()); + for (int i = 0; i < data_.size(); ++i) { + s.setArrayIndex(i); + s.setValue("value", QVariant::fromValue(data_[i])); + } + s.endArray(); + } + + void Update(const ListType &data) { + data_ = data; + last_updated_ = QDateTime::currentDateTime(); + Save(); + } + + bool IsStale() const { + return last_updated_.isNull() || last_updated_.secsTo(QDateTime::currentDateTime()) > cache_duration_secs_; + } + + void Sort() { qSort(data_); } + + const ListType &Data() const { return data_; } + operator ListType() const { return data_; } + + // Q_FOREACH support + typedef typename ListType::const_iterator const_iterator; + const_iterator begin() const { return data_.begin(); } + const_iterator end() const { return data_.end(); } + + private: + const QString settings_group_; + const QString name_; + const int cache_duration_secs_; + + QDateTime last_updated_; + ListType data_; +}; + +#endif // CACHEDLIST_H + diff --git a/src/core/commandlineoptions.cpp b/src/core/commandlineoptions.cpp new file mode 100644 index 00000000..18e780d4 --- /dev/null +++ b/src/core/commandlineoptions.cpp @@ -0,0 +1,388 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "commandlineoptions.h" +#include "version.h" +#include "core/logging.h" + +#include +#include +#include + +#include +#include +#include + + +const char *CommandlineOptions::kHelpText = + "%1: strawberry [%2] [%3]\n" + "\n" + "%4:\n" + " -p, --play %5\n" + " -t, --play-pause %6\n" + " -u, --pause %7\n" + " -s, --stop %8\n" + " -q, --stop-after-current %9\n" + " -r, --previous %10\n" + " -f, --next %11\n" + " -v, --volume %12\n" + " --volume-up %13\n" + " --volume-down %14\n" + " --volume-increase-by %15\n" + " --volume-decrease-by %16\n" + " --seek-to %17\n" + " --seek-by %18\n" + " --restart-or-previous %19\n" + "\n" + "%20:\n" + " -c, --create %21\n" + " -a, --append %22\n" + " -l, --load %23\n" + " -k, --play-track %24\n" + "\n" + "%25:\n" + " -o, --show-osd %26\n" + " -y, --toggle-pretty-osd %27\n" + " -g, --language %28\n" + " --quiet %29\n" + " --verbose %30\n" + " --log-levels %31\n" + " --version %32\n"; + +const char *CommandlineOptions::kVersionText = "Strawberry %1"; + +CommandlineOptions::CommandlineOptions(int argc, char* *argv) + : argc_(argc), + argv_(argv), + url_list_action_(UrlList_None), + player_action_(Player_None), + set_volume_(-1), + volume_modifier_(0), + seek_to_(-1), + seek_by_(0), + play_track_at_(-1), + show_osd_(false), + toggle_pretty_osd_(false), + log_levels_(logging::kDefaultLogLevels) { + +#ifdef Q_OS_DARWIN + // Remove -psn_xxx option that Mac passes when opened from Finder. + RemoveArg("-psn", 1); +#endif + + // Remove the -session option that KDE passes + RemoveArg("-session", 2); +} + +void CommandlineOptions::RemoveArg(const QString& starts_with, int count) { + + for (int i = 0; i < argc_; ++i) { + QString opt(argv_[i]); + if (opt.startsWith(starts_with)) { + for (int j = i; j < argc_ - count + 1; ++j) { + argv_[j] = argv_[j + count]; + } + argc_ -= count; + break; + } + } + +} + +bool CommandlineOptions::Parse() { + + static const struct option kOptions[] = { + {"help", no_argument, 0, 'h'}, + {"play", no_argument, 0, 'p'}, + {"play-pause", no_argument, 0, 't'}, + {"pause", no_argument, 0, 'u'}, + {"stop", no_argument, 0, 's'}, + {"stop-after-current", no_argument, 0, 'q'}, + {"previous", no_argument, 0, 'r'}, + {"next", no_argument, 0, 'f'}, + {"volume", required_argument, 0, 'v'}, + {"volume-up", no_argument, 0, VolumeUp}, + {"volume-down", no_argument, 0, VolumeDown}, + {"volume-increase-by", required_argument, 0, VolumeIncreaseBy}, + {"volume-decrease-by", required_argument, 0, VolumeDecreaseBy}, + {"seek-to", required_argument, 0, SeekTo}, + {"seek-by", required_argument, 0, SeekBy}, + {"restart-or-previous", no_argument, 0, RestartOrPrevious}, + {"create", required_argument, 0, 'c'}, + {"append", no_argument, 0, 'a'}, + {"load", no_argument, 0, 'l'}, + {"play-track", required_argument, 0, 'k'}, + {"show-osd", no_argument, 0, 'o'}, + {"toggle-pretty-osd", no_argument, 0, 'y'}, + {"language", required_argument, 0, 'g'}, + {"quiet", no_argument, 0, Quiet}, + {"verbose", no_argument, 0, Verbose}, + {"log-levels", required_argument, 0, LogLevels}, + {"version", no_argument, 0, Version}, + {0, 0, 0, 0}}; + + // Parse the arguments + bool ok = false; + forever { + int c = getopt_long(argc_, argv_, "hptusqrfv:c:alk:oyg:", kOptions, nullptr); + + // End of the options + if (c == -1) break; + + switch (c) { + case 'h': { + QString translated_help_text = + QString(kHelpText) + .arg(tr("Usage"), tr("options"), tr("URL(s)"), + tr("Player options"), + tr("Start the playlist currently playing"), + tr("Play if stopped, pause if playing"), + tr("Pause playback"), tr("Stop playback"), + tr("Stop playback after current track")) + .arg(tr("Skip backwards in playlist"), + tr("Skip forwards in playlist"), + tr("Set the volume to percent"), + tr("Increase the volume by 4%"), + tr("Decrease the volume by 4%"), + tr("Increase the volume by percent"), + tr("Decrease the volume by percent")) + .arg(tr("Seek the currently playing track to an absolute " + "position"), + tr("Seek the currently playing track by a relative " + "amount"), + tr("Restart the track, or play the previous track if " + "within 8 seconds of start."), + tr("Playlist options"), + tr("Create a new playlist with files"), + tr("Append files/URLs to the playlist"), + tr("Loads files/URLs, replacing current playlist"), + tr("Play the th track in the playlist")) + .arg(tr("Other options"), tr("Display the on-screen-display"), + tr("Toggle visibility for the pretty on-screen-display"), + tr("Change the language"), + tr("Equivalent to --log-levels *:1"), + tr("Equivalent to --log-levels *:3"), + tr("Comma separated list of class:level, level is 0-3")) + .arg(tr("Print out version information")); + + std::cout << translated_help_text.toLocal8Bit().constData(); + return false; + } + + case 'p': + player_action_ = Player_Play; + break; + case 't': + player_action_ = Player_PlayPause; + break; + case 'u': + player_action_ = Player_Pause; + break; + case 's': + player_action_ = Player_Stop; + break; + case 'q': + player_action_ = Player_StopAfterCurrent; + break; + case 'r': + player_action_ = Player_Previous; + break; + case 'f': + player_action_ = Player_Next; + break; + case 'c': + url_list_action_ = UrlList_CreateNew; + playlist_name_ = QString(optarg); + break; + case 'a': + url_list_action_ = UrlList_Append; + break; + case 'l': + url_list_action_ = UrlList_Load; + break; + case 'o': + show_osd_ = true; + break; + case 'y': + toggle_pretty_osd_ = true; + break; + case 'g': + language_ = QString(optarg); + break; + case VolumeUp: + volume_modifier_ = +4; + break; + case VolumeDown: + volume_modifier_ = -4; + break; + case Quiet: + log_levels_ = "1"; + break; + case Verbose: + log_levels_ = "3"; + break; + case LogLevels: + log_levels_ = QString(optarg); + break; + case Version: { + QString version_text = QString(kVersionText).arg(STRAWBERRY_VERSION_DISPLAY); + std::cout << version_text.toLocal8Bit().constData() << std::endl; + std::exit(0); + } + case 'v': + set_volume_ = QString(optarg).toInt(&ok); + if (!ok) set_volume_ = -1; + break; + + case VolumeIncreaseBy: + volume_modifier_ = QString(optarg).toInt(&ok); + if (!ok) volume_modifier_ = 0; + break; + + case VolumeDecreaseBy: + volume_modifier_ = -QString(optarg).toInt(&ok); + if (!ok) volume_modifier_ = 0; + break; + + case SeekTo: + seek_to_ = QString(optarg).toInt(&ok); + if (!ok) seek_to_ = -1; + break; + + case SeekBy: + seek_by_ = QString(optarg).toInt(&ok); + if (!ok) seek_by_ = 0; + break; + + case RestartOrPrevious: + player_action_ = Player_RestartOrPrevious; + break; + + case 'k': + play_track_at_ = QString(optarg).toInt(&ok); + if (!ok) play_track_at_ = -1; + break; + + case '?': + default: + return false; + } + } + + // Get any filenames or URLs following the arguments + for (int i = optind; i < argc_; ++i) { + QString value = QFile::decodeName(argv_[i]); + QFileInfo file_info(value); + if (file_info.exists()) + urls_ << QUrl::fromLocalFile(file_info.canonicalFilePath()); + else + urls_ << QUrl::fromUserInput(value); + } + + return true; + +} + +bool CommandlineOptions::is_empty() const { + return player_action_ == Player_None && + set_volume_ == -1 && + volume_modifier_ == 0 && + seek_to_ == -1 && + seek_by_ == 0 && + play_track_at_ == -1 && + show_osd_ == false && + toggle_pretty_osd_ == false && + urls_.isEmpty(); +} + +bool CommandlineOptions::contains_play_options() const { + return player_action_ != Player_None || play_track_at_ != -1 || !urls_.isEmpty(); +} + +QByteArray CommandlineOptions::Serialize() const { + + QBuffer buf; + buf.open(QIODevice::WriteOnly); + + QDataStream s(&buf); + s << *this; + buf.close(); + + return buf.data().toBase64(); + +} + +void CommandlineOptions::Load(const QByteArray &serialized) { + + QByteArray copy = QByteArray::fromBase64(serialized); + QBuffer buf(©); + buf.open(QIODevice::ReadOnly); + + QDataStream s(&buf); + s >> *this; + +} + +QString CommandlineOptions::tr(const char *source_text) { + return QObject::tr(source_text); +} + +QDataStream& operator<<(QDataStream &s, const CommandlineOptions &a) { + + s << qint32(a.player_action_) + << qint32(a.url_list_action_) + << a.set_volume_ + << a.volume_modifier_ + << a.seek_to_ + << a.seek_by_ + << a.play_track_at_ + << a.show_osd_ + << a.urls_ + << a.log_levels_ + << a.toggle_pretty_osd_; + + return s; + +} + +QDataStream& operator>>(QDataStream &s, CommandlineOptions &a) { + + quint32 player_action = 0; + quint32 url_list_action = 0; + s >> player_action + >> url_list_action + >> a.set_volume_ + >> a.volume_modifier_ + >> a.seek_to_ + >> a.seek_by_ + >> a.play_track_at_ + >> a.show_osd_ + >> a.urls_ + >> a.log_levels_ + >> a.toggle_pretty_osd_; + a.player_action_ = CommandlineOptions::PlayerAction(player_action); + a.url_list_action_ = CommandlineOptions::UrlListAction(url_list_action); + + return s; + +} + diff --git a/src/core/commandlineoptions.h b/src/core/commandlineoptions.h new file mode 100644 index 00000000..dbcae545 --- /dev/null +++ b/src/core/commandlineoptions.h @@ -0,0 +1,128 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COMMANDLINEOPTIONS_H +#define COMMANDLINEOPTIONS_H + +#include "config.h" + +#include +#include +#include + +class CommandlineOptions { + friend QDataStream &operator<<(QDataStream &s, const CommandlineOptions &a); + friend QDataStream &operator>>(QDataStream &s, CommandlineOptions &a); + + public: + explicit CommandlineOptions(int argc = 0, char **argv = nullptr); + + static const char *kHelpText; + static const char *kVersionText; + + // Don't change the values or order, these get serialised and sent to + // possibly a different version of Strawberry + enum UrlListAction { + UrlList_Append = 0, + UrlList_Load = 1, + UrlList_None = 2, + UrlList_CreateNew = 3, + }; + enum PlayerAction { + Player_None = 0, + Player_Play = 1, + Player_PlayPause = 2, + Player_Pause = 3, + Player_Stop = 4, + Player_Previous = 5, + Player_Next = 6, + Player_RestartOrPrevious = 7, + Player_StopAfterCurrent = 8, + }; + + bool Parse(); + + bool is_empty() const; + bool contains_play_options() const; + + UrlListAction url_list_action() const { return url_list_action_; } + PlayerAction player_action() const { return player_action_; } + int set_volume() const { return set_volume_; } + int volume_modifier() const { return volume_modifier_; } + int seek_to() const { return seek_to_; } + int seek_by() const { return seek_by_; } + int play_track_at() const { return play_track_at_; } + bool show_osd() const { return show_osd_; } + bool toggle_pretty_osd() const { return toggle_pretty_osd_; } + QList urls() const { return urls_; } + QString language() const { return language_; } + QString log_levels() const { return log_levels_; } + QString playlist_name() const { return playlist_name_; } + + QByteArray Serialize() const; + void Load(const QByteArray &serialized); + + private: + // These are "invalid" characters to pass to getopt_long for options that + // shouldn't have a short (single character) option. + enum LongOptions { + VolumeUp = 256, + VolumeDown, + SeekTo, + SeekBy, + Quiet, + Verbose, + LogLevels, + Version, + VolumeIncreaseBy, + VolumeDecreaseBy, + RestartOrPrevious + }; + + QString tr(const char *source_text); + void RemoveArg(const QString &starts_with, int count); + + private: + int argc_; + char **argv_; + + UrlListAction url_list_action_; + PlayerAction player_action_; + + // Don't change the type of these. + int set_volume_; + int volume_modifier_; + int seek_to_; + int seek_by_; + int play_track_at_; + bool show_osd_; + bool toggle_pretty_osd_; + QString language_; + QString log_levels_; + QString playlist_name_; + + QList urls_; +}; + +QDataStream &operator<<(QDataStream &s, const CommandlineOptions &a); +QDataStream &operator>>(QDataStream &s, CommandlineOptions &a); + +#endif // COMMANDLINEOPTIONS_H + diff --git a/src/core/database.cpp b/src/core/database.cpp new file mode 100644 index 00000000..632687d4 --- /dev/null +++ b/src/core/database.cpp @@ -0,0 +1,684 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "database.h" +#include "scopedtransaction.h" +#include "utilities.h" +#include "core/application.h" +#include "core/logging.h" +#include "core/taskmanager.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const char *Database::kDatabaseFilename = "strawberry.db"; +const int Database::kSchemaVersion = 0; +const char *Database::kMagicAllSongsTables = "%allsongstables"; + +int Database::sNextConnectionId = 1; +QMutex Database::sNextConnectionIdMutex; + +Database::Token::Token(const QString &token, int start, int end) + : token(token), start_offset(start), end_offset(end) {} + +struct sqlite3_tokenizer_module { + + int iVersion; + int (*xCreate)(int argc, /* Size of argv array */ + const char *const *argv, /* Tokenizer argument strings */ + sqlite3_tokenizer** ppTokenizer); /* OUT: Created tokenizer */ + + int (*xDestroy)(sqlite3_tokenizer *pTokenizer); + + int (*xOpen)( + sqlite3_tokenizer *pTokenizer, /* Tokenizer object */ + const char *pInput, int nBytes, /* Input buffer */ + sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */ + ); + + int (*xClose)(sqlite3_tokenizer_cursor *pCursor); + + int (*xNext)( + sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */ + const char* *ppToken, int *pnBytes, /* OUT: Normalized text for token */ + int *piStartOffset, /* OUT: Byte offset of token in input buffer */ + int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */ + int *piPosition); /* OUT: Number of tokens returned before this one */ +}; + +struct sqlite3_tokenizer { + const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */ + /* Tokenizer implementations will typically add additional fields */ +}; + +struct sqlite3_tokenizer_cursor { + sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */ + /* Tokenizer implementations will typically add additional fields */ +}; + +sqlite3_tokenizer_module *Database::sFTSTokenizer = nullptr; + +int Database::FTSCreate(int argc, const char *const *argv, sqlite3_tokenizer **tokenizer) { + + *tokenizer = reinterpret_cast(new UnicodeTokenizer); + + return SQLITE_OK; +} + +int Database::FTSDestroy(sqlite3_tokenizer *tokenizer) { + + UnicodeTokenizer *real_tokenizer = reinterpret_cast(tokenizer); + delete real_tokenizer; + return SQLITE_OK; +} + +int Database::FTSOpen(sqlite3_tokenizer *pTokenizer, const char *input, int bytes, sqlite3_tokenizer_cursor **cursor) { + + UnicodeTokenizerCursor *new_cursor = new UnicodeTokenizerCursor; + new_cursor->pTokenizer = pTokenizer; + new_cursor->position = 0; + + QString str = QString::fromUtf8(input, bytes).toLower(); + QChar *data = str.data(); + // Decompose and strip punctuation. + QList tokens; + QString token; + int start_offset = 0; + int offset = 0; + for (int i = 0; i < str.length(); ++i) { + QChar c = data[i]; + ushort unicode = c.unicode(); + if (unicode <= 0x007f) { + offset += 1; + } + else if (unicode >= 0x0080 && unicode <= 0x07ff) { + offset += 2; + } + else if (unicode >= 0x0800) { + offset += 3; + } + // Unicode astral planes unsupported in Qt? + /*else if (unicode >= 0x010000 && unicode <= 0x10ffff) { + offset += 4; + }*/ + + if (!data[i].isLetterOrNumber()) { + // Token finished. + if (token.length() != 0) { + tokens << Token(token, start_offset, offset - 1); + start_offset = offset; + token.clear(); + } + else { + ++start_offset; + } + } + else { + if (data[i].decompositionTag() != QChar::NoDecomposition) { + token.push_back(data[i].decomposition()[0]); + } else { + token.push_back(data[i]); + } + } + + if (i == str.length() - 1) { + if (token.length() != 0) { + tokens << Token(token, start_offset, offset); + token.clear(); + } + } + } + + new_cursor->tokens = tokens; + *cursor = reinterpret_cast(new_cursor); + + return SQLITE_OK; + +} + +int Database::FTSClose(sqlite3_tokenizer_cursor *cursor) { + + UnicodeTokenizerCursor *real_cursor = reinterpret_cast(cursor); + delete real_cursor; + + return SQLITE_OK; + +} + +int Database::FTSNext(sqlite3_tokenizer_cursor *cursor, const char* *token, int *bytes, int *start_offset, int *end_offset, int *position) { + + UnicodeTokenizerCursor *real_cursor = reinterpret_cast(cursor); + + QList tokens = real_cursor->tokens; + if (real_cursor->position >= tokens.size()) { + return SQLITE_DONE; + } + + Token t = tokens[real_cursor->position]; + QByteArray utf8 = t.token.toUtf8(); + *token = utf8.constData(); + *bytes = utf8.size(); + *start_offset = t.start_offset; + *end_offset = t.end_offset; + *position = real_cursor->position++; + + real_cursor->current_utf8 = utf8; + + return SQLITE_OK; + +} + +void Database::StaticInit() { + + sFTSTokenizer = new sqlite3_tokenizer_module; + sFTSTokenizer->iVersion = 0; + sFTSTokenizer->xCreate = &Database::FTSCreate; + sFTSTokenizer->xDestroy = &Database::FTSDestroy; + sFTSTokenizer->xOpen = &Database::FTSOpen; + sFTSTokenizer->xNext = &Database::FTSNext; + sFTSTokenizer->xClose = &Database::FTSClose; + return; + +} + +Database::Database(Application *app, QObject *parent, const QString &database_name) : + QObject(parent), + app_(app), + mutex_(QMutex::Recursive), + injected_database_name_(database_name), + query_hash_(0), + startup_schema_version_(-1) { + + { + QMutexLocker l(&sNextConnectionIdMutex); + connection_id_ = sNextConnectionId++; + } + + directory_ = QDir::toNativeSeparators(Utilities::GetConfigPath(Utilities::Path_Root)); + + QMutexLocker l(&mutex_); + Connect(); + +} + +QSqlDatabase Database::Connect() { + + QMutexLocker l(&connect_mutex_); + + // Create the directory if it doesn't exist + if (!QFile::exists(directory_)) { + QDir dir; + if (!dir.mkpath(directory_)) { + } + } + + const QString connection_id = QString("%1_thread_%2").arg(connection_id_).arg(reinterpret_cast(QThread::currentThread())); + + // Try to find an existing connection for this thread + QSqlDatabase db = QSqlDatabase::database(connection_id); + if (db.isOpen()) { + return db; + } + + db = QSqlDatabase::addDatabase("QSQLITE", connection_id); + + if (!injected_database_name_.isNull()) + db.setDatabaseName(injected_database_name_); + else + db.setDatabaseName(directory_ + "/" + kDatabaseFilename); + + if (!db.open()) { + app_->AddError("Database: " + db.lastError().text()); + return db; + } + + // Find Sqlite3 functions in the Qt plugin. + StaticInit(); + + { + +#ifdef SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER + // In case sqlite>=3.12 is compiled without -DSQLITE_ENABLE_FTS3_TOKENIZER (generally a good idea due to security reasons) the fts3 support should be enabled explicitly. + QVariant v = db.driver()->handle(); + if (v.isValid() && qstrcmp(v.typeName(), "sqlite3*") == 0) { + sqlite3 *handle = *static_cast(v.data()); + if (handle) sqlite3_db_config(handle, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, NULL); + } +#endif + QSqlQuery set_fts_tokenizer(db); + set_fts_tokenizer.prepare("SELECT fts3_tokenizer(:name, :pointer)"); + set_fts_tokenizer.bindValue(":name", "unicode"); + set_fts_tokenizer.bindValue(":pointer", QByteArray(reinterpret_cast(&sFTSTokenizer), sizeof(&sFTSTokenizer))); + if (!set_fts_tokenizer.exec()) { + qLog(Warning) << "Couldn't register FTS3 tokenizer : " << set_fts_tokenizer.lastError(); + } + // Implicit invocation of ~QSqlQuery() when leaving the scope + // to release any remaining database locks! + } + + if (db.tables().count() == 0) { + // Set up initial schema + qLog(Info) << "Creating initial database schema"; + UpdateDatabaseSchema(0, db); + } + + // Attach external databases + for (const QString &key : attached_databases_.keys()) { + QString filename = attached_databases_[key].filename_; + + if (!injected_database_name_.isNull()) filename = injected_database_name_; + + // Attach the db + QSqlQuery q(db); + q.prepare("ATTACH DATABASE :filename AS :alias"); + q.bindValue(":filename", filename); + q.bindValue(":alias", key); + if (!q.exec()) { + qFatal("Couldn't attach external database '%s'", key.toLatin1().constData()); + } + } + + if (startup_schema_version_ == -1) { + UpdateMainSchema(&db); + } + + // We might have to initialise the schema in some attached databases now, if + // they were deleted and don't match up with the main schema version. + for (const QString &key : attached_databases_.keys()) { + if (attached_databases_[key].is_temporary_ && attached_databases_[key].schema_.isEmpty()) + continue; + // Find out if there are any tables in this database + QSqlQuery q(db); + q.prepare(QString("SELECT ROWID FROM %1.sqlite_master WHERE type='table'").arg(key)); + if (!q.exec() || !q.next()) { + q.finish(); + ExecSchemaCommandsFromFile(db, attached_databases_[key].schema_, 0); + } + } + + return db; + +} + +void Database::UpdateMainSchema(QSqlDatabase *db) { + + // Get the database's schema version + int schema_version = 0; + { + QSqlQuery q("SELECT version FROM schema_version", *db); + if (q.next()) schema_version = q.value(0).toInt(); + // Implicit invocation of ~QSqlQuery() when leaving the scope + // to release any remaining database locks! + } + + startup_schema_version_ = schema_version; + + if (schema_version > kSchemaVersion) { + qLog(Warning) << "The database schema (version" << schema_version << ") is newer than I was expecting"; + return; + } + if (schema_version < kSchemaVersion) { + // Update the schema + for (int v = schema_version + 1; v <= kSchemaVersion; ++v) { + UpdateDatabaseSchema(v, *db); + } + } +} + +void Database::RecreateAttachedDb(const QString &database_name) { + + if (!attached_databases_.contains(database_name)) { + qLog(Warning) << "Attached database does not exist:" << database_name; + return; + } + + const QString filename = attached_databases_[database_name].filename_; + + QMutexLocker l(&mutex_); + { + QSqlDatabase db(Connect()); + + QSqlQuery q(db); + q.prepare("DETACH DATABASE :alias"); + q.bindValue(":alias", database_name); + if (!q.exec()) { + qLog(Warning) << "Failed to detach database" << database_name; + return; + } + + if (!QFile::remove(filename)) { + qLog(Warning) << "Failed to remove file" << filename; + } + } + + // We can't just re-attach the database now because it needs to be done for + // each thread. Close all the database connections, so each thread will + // re-attach it when they next connect. + for (const QString &name : QSqlDatabase::connectionNames()) { + QSqlDatabase::removeDatabase(name); + } + +} + +void Database::AttachDatabase(const QString &database_name, const AttachedDatabase &database) { + attached_databases_[database_name] = database; +} + +void Database::AttachDatabaseOnDbConnection(const QString &database_name, const AttachedDatabase &database, QSqlDatabase &db) { + + AttachDatabase(database_name, database); + + // Attach the db + QSqlQuery q(db); + q.prepare("ATTACH DATABASE :filename AS :alias"); + q.bindValue(":filename", database.filename_); + q.bindValue(":alias", database_name); + if (!q.exec()) { + qFatal("Couldn't attach external database '%s'", database_name.toLatin1().constData()); + } + +} + +void Database::DetachDatabase(const QString &database_name) { + + QMutexLocker l(&mutex_); + { + QSqlDatabase db(Connect()); + + QSqlQuery q(db); + q.prepare("DETACH DATABASE :alias"); + q.bindValue(":alias", database_name); + if (!q.exec()) { + qLog(Warning) << "Failed to detach database" << database_name; + return; + } + } + + attached_databases_.remove(database_name); + +} + +void Database::UpdateDatabaseSchema(int version, QSqlDatabase &db) { + + QString filename; + if (version == 0) filename = ":/schema/schema.sql"; + else filename = QString(":/schema/schema-%1.sql").arg(version); + + qLog(Debug) << "Applying database schema update" << version << "from" << filename; + ExecSchemaCommandsFromFile(db, filename, version - 1); + +} + +void Database::UrlEncodeFilenameColumn(const QString &table, QSqlDatabase &db) { + + QSqlQuery select(db); + select.prepare(QString("SELECT ROWID, filename FROM %1").arg(table)); + QSqlQuery update(db); + update.prepare(QString("UPDATE %1 SET filename=:filename WHERE ROWID=:id").arg(table)); + select.exec(); + if (CheckErrors(select)) return; + + while (select.next()) { + const int rowid = select.value(0).toInt(); + const QString filename = select.value(1).toString(); + + if (filename.isEmpty() || filename.contains("://")) { + continue; + } + + const QUrl url = QUrl::fromLocalFile(filename); + + update.bindValue(":filename", url.toEncoded()); + update.bindValue(":id", rowid); + update.exec(); + CheckErrors(update); + } + +} + +void Database::ExecSchemaCommandsFromFile(QSqlDatabase &db, const QString &filename, int schema_version, bool in_transaction) { + + // Open and read the database schema + QFile schema_file(filename); + if (!schema_file.open(QIODevice::ReadOnly)) + qFatal("Couldn't open schema file %s", filename.toUtf8().constData()); + ExecSchemaCommands(db, QString::fromUtf8(schema_file.readAll()), schema_version, in_transaction); + +} + +void Database::ExecSchemaCommands(QSqlDatabase &db, const QString &schema, int schema_version, bool in_transaction) { + + // Run each command + const QStringList commands(schema.split(QRegExp("; *\n\n"))); + + // We don't want this list to reflect possible DB schema changes + // so we initialize it before executing any statements. + // If no outer transaction is provided the song tables need to + // be queried before beginning an inner transaction! Otherwise + // DROP TABLE commands on song tables may fail due to database + // locks. + const QStringList song_tables(SongsTables(db, schema_version)); + + if (!in_transaction) { + ScopedTransaction inner_transaction(&db); + ExecSongTablesCommands(db, song_tables, commands); + inner_transaction.Commit(); + } + else { + ExecSongTablesCommands(db, song_tables, commands); + } + +} + +void Database::ExecSongTablesCommands(QSqlDatabase &db, const QStringList &song_tables, const QStringList &commands) { + + for (const QString &command : commands) { + // There are now lots of "songs" tables that need to have the same schema: + // songs, magnatune_songs, and device_*_songs. We allow a magic value + // in the schema files to update all songs tables at once. + if (command.contains(kMagicAllSongsTables)) { + for (const QString &table : song_tables) { + // Another horrible hack: device songs tables don't have matching _fts + // tables, so if this command tries to touch one, ignore it. + if (table.startsWith("device_") && + command.contains(QString(kMagicAllSongsTables) + "_fts")) { + continue; + } + + qLog(Info) << "Updating" << table << "for" << kMagicAllSongsTables; + QString new_command(command); + new_command.replace(kMagicAllSongsTables, table); + QSqlQuery query(db.exec(new_command)); + if (CheckErrors(query)) + qFatal("Unable to update music collection database"); + } + } else { + QSqlQuery query(db.exec(command)); + if (CheckErrors(query)) qFatal("Unable to update music collection database"); + } + } +} + +QStringList Database::SongsTables(QSqlDatabase &db, int schema_version) const { + + QStringList ret; + + // look for the tables in the main db + for (const QString &table : db.tables()) { + if (table == "songs" || table.endsWith("_songs")) ret << table; + } + + // look for the tables in attached dbs + for (const QString &key : attached_databases_.keys()) { + QSqlQuery q(db); + q.prepare(QString("SELECT NAME FROM %1.sqlite_master WHERE type='table' AND name='songs' OR name LIKE '%songs'").arg(key)); + if (q.exec()) { + while (q.next()) { + QString tab_name = key + "." + q.value(0).toString(); + ret << tab_name; + } + } + } + + ret << "playlist_items"; + + return ret; + +} + +bool Database::CheckErrors(const QSqlQuery &query) { + + QSqlError last_error = query.lastError(); + if (last_error.isValid()) { + qLog(Error) << "db error: " << last_error; + qLog(Error) << "faulty query: " << query.lastQuery(); + qLog(Error) << "bound values: " << query.boundValues(); + + return true; + } + + return false; + +} + +bool Database::IntegrityCheck(QSqlDatabase db) { + + qLog(Debug) << "Starting database integrity check"; + int task_id = app_->task_manager()->StartTask(tr("Integrity check")); + + bool ok = false; + bool error_reported = false; + // Ask for 10 error messages at most. + QSqlQuery q(QString("PRAGMA integrity_check(10)"), db); + while (q.next()) { + QString message = q.value(0).toString(); + + // If no errors are found, a single row with the value "ok" is returned + if (message == "ok") { + ok = true; + break; + } else { + if (!error_reported) { app_->AddError(tr("Database corruption detected.")); } + app_->AddError("Database: " + message); + error_reported = true; + } + } + + app_->task_manager()->SetTaskFinished(task_id); + + return ok; + +} + +void Database::DoBackup() { + + QSqlDatabase db(this->Connect()); + + // Before we overwrite anything, make sure the database is not corrupt + QMutexLocker l(&mutex_); + const bool ok = IntegrityCheck(db); + + if (ok) { + BackupFile(db.databaseName()); + } + +} + +bool Database::OpenDatabase(const QString &filename, sqlite3 **connection) const { + + int ret = sqlite3_open(filename.toUtf8(), connection); + if (ret != 0) { + if (*connection) { + const char *error_message = sqlite3_errmsg(*connection); + qLog(Error) << "Failed to open database for backup:" << filename << error_message; + } + else { + qLog(Error) << "Failed to open database for backup:" << filename; + } + return false; + } + return true; + +} + +void Database::BackupFile(const QString &filename) { + + qLog(Debug) << "Starting database backup"; + QString dest_filename = QString("%1.bak").arg(filename); + const int task_id = app_->task_manager()->StartTask(tr("Backing up database")); + + sqlite3 *source_connection = nullptr; + sqlite3 *dest_connection = nullptr; + + BOOST_SCOPE_EXIT((source_connection)(dest_connection)(task_id)(app_)) { + // Harmless to call sqlite3_close() with a nullptr pointer. + sqlite3_close(source_connection); + sqlite3_close(dest_connection); + app_->task_manager()->SetTaskFinished(task_id); + } + BOOST_SCOPE_EXIT_END + + bool success = OpenDatabase(filename, &source_connection); + if (!success) { + return; + } + + success = OpenDatabase(dest_filename, &dest_connection); + if (!success) { + return; + } + + sqlite3_backup *backup = sqlite3_backup_init(dest_connection, "main", source_connection, "main"); + if (!backup) { + const char *error_message = sqlite3_errmsg(dest_connection); + qLog(Error) << "Failed to start database backup:" << error_message; + return; + } + + int ret = SQLITE_OK; + do { + ret = sqlite3_backup_step(backup, 16); + const int page_count = sqlite3_backup_pagecount(backup); + app_->task_manager()->SetTaskProgress( + task_id, page_count - sqlite3_backup_remaining(backup), page_count); + } while (ret == SQLITE_OK); + + if (ret != SQLITE_DONE) { + qLog(Error) << "Database backup failed"; + } + + sqlite3_backup_finish(backup); + +} + diff --git a/src/core/database.h b/src/core/database.h new file mode 100644 index 00000000..c449bc47 --- /dev/null +++ b/src/core/database.h @@ -0,0 +1,175 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef DATABASE_H +#define DATABASE_H + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "gtest/gtest_prod.h" + +extern "C" { + +struct sqlite3_tokenizer; +struct sqlite3_tokenizer_cursor; +struct sqlite3_tokenizer_module; +} + +class Application; + +class Database : public QObject { + Q_OBJECT + + public: + Database(Application *app, QObject *parent = nullptr, const QString &database_name = QString()); + + struct AttachedDatabase { + AttachedDatabase() {} + AttachedDatabase(const QString &filename, const QString &schema, bool is_temporary) + : filename_(filename), schema_(schema), is_temporary_(is_temporary) {} + + QString filename_; + QString schema_; + bool is_temporary_; + }; + + static const int kSchemaVersion; + static const char *kDatabaseFilename; + static const char *kMagicAllSongsTables; + + QSqlDatabase Connect(); + bool CheckErrors(const QSqlQuery &query); + QMutex *Mutex() { return &mutex_; } + + void RecreateAttachedDb(const QString &database_name); + void ExecSchemaCommands(QSqlDatabase &db, const QString &schema, int schema_version, bool in_transaction = false); + + int startup_schema_version() const { return startup_schema_version_; } + int current_schema_version() const { return kSchemaVersion; } + + void AttachDatabase(const QString &database_name, const AttachedDatabase &database); + void AttachDatabaseOnDbConnection(const QString &database_name, const AttachedDatabase &database, QSqlDatabase &db); + void DetachDatabase(const QString &database_name); + +signals: + void Error(const QString &message); + + public slots: + void DoBackup(); + + private: + void UpdateMainSchema(QSqlDatabase *db); + + void ExecSchemaCommandsFromFile(QSqlDatabase &db, const QString &filename, int schema_version, bool in_transaction = false); + void ExecSongTablesCommands(QSqlDatabase &db, const QStringList &song_tables, const QStringList &commands); + + void UpdateDatabaseSchema(int version, QSqlDatabase &db); + void UrlEncodeFilenameColumn(const QString &table, QSqlDatabase &db); + QStringList SongsTables(QSqlDatabase &db, int schema_version) const; + bool IntegrityCheck(QSqlDatabase db); + void BackupFile(const QString &filename); + bool OpenDatabase(const QString &filename, sqlite3 **connection) const; + + Application *app_; + + // Alias -> filename + QMap attached_databases_; + + QString directory_; + QMutex connect_mutex_; + QMutex mutex_; + + // This ID makes the QSqlDatabase name unique to the object as well as the + // thread + int connection_id_; + + static QMutex sNextConnectionIdMutex; + static int sNextConnectionId; + + // Used by tests + QString injected_database_name_; + + uint query_hash_; + QStringList query_cache_; + + // This is the schema version of Strawberry's DB from the app's last run. + int startup_schema_version_; + + FRIEND_TEST(DatabaseTest, FTSOpenParsesSimpleInput); + FRIEND_TEST(DatabaseTest, FTSOpenParsesUTF8Input); + FRIEND_TEST(DatabaseTest, FTSOpenParsesMultipleTokens); + FRIEND_TEST(DatabaseTest, FTSCursorWorks); + FRIEND_TEST(DatabaseTest, FTSOpenLeavesCyrillicQueries); + + // Do static initialisation like loading sqlite functions. + static void StaticInit(); + + typedef int (*Sqlite3CreateFunc)(sqlite3*, const char*, int, int, void*, void (*)(sqlite3_context*, int, sqlite3_value**), void (*)(sqlite3_context*, int, sqlite3_value**), void (*)(sqlite3_context*)); + + static sqlite3_tokenizer_module *sFTSTokenizer; + + static int FTSCreate(int argc, const char *const *argv, sqlite3_tokenizer **tokenizer); + static int FTSDestroy(sqlite3_tokenizer *tokenizer); + static int FTSOpen(sqlite3_tokenizer *tokenizer, const char *input, int bytes, sqlite3_tokenizer_cursor **cursor); + static int FTSClose(sqlite3_tokenizer_cursor *cursor); + static int FTSNext(sqlite3_tokenizer_cursor *cursor, const char **token, int *bytes, int *start_offset, int *end_offset, int *position); + + struct Token { + Token(const QString &token, int start, int end); + QString token; + int start_offset; + int end_offset; + }; + + // Based on sqlite3_tokenizer. + struct UnicodeTokenizer { + const sqlite3_tokenizer_module *pModule; + }; + + struct UnicodeTokenizerCursor { + const sqlite3_tokenizer *pTokenizer; + + QList tokens; + int position; + QByteArray current_utf8; + }; +}; + +class MemoryDatabase : public Database { + public: + MemoryDatabase(Application *app, QObject *parent = nullptr) + : Database(app, parent, ":memory:") {} + ~MemoryDatabase() { + // Make sure Qt doesn't reuse the same database + QSqlDatabase::removeDatabase(Connect().connectionName()); + } +}; + +#endif // DATABASE_H + diff --git a/src/core/dbusscreensaver.cpp b/src/core/dbusscreensaver.cpp new file mode 100644 index 00000000..ad9cc136 --- /dev/null +++ b/src/core/dbusscreensaver.cpp @@ -0,0 +1,47 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "dbusscreensaver.h" + +#include +#include +#include + +DBusScreensaver::DBusScreensaver(const QString &service, const QString &path, const QString &interface) + : service_(service), path_(path), interface_(interface) {} + +void DBusScreensaver::Inhibit() { + + QDBusInterface gnome_screensaver("org.gnome.ScreenSaver", "/", "org.gnome.ScreenSaver"); + QDBusReply reply = gnome_screensaver.call("Inhibit", QCoreApplication::applicationName(), QObject::tr("Visualizations")); + if (reply.isValid()) { + cookie_ = reply.value(); + } + +} + +void DBusScreensaver::Uninhibit() { + + QDBusInterface gnome_screensaver("org.gnome.ScreenSaver", "/", "org.gnome.ScreenSaver"); + gnome_screensaver.call("UnInhibit", cookie_); + +} diff --git a/src/core/dbusscreensaver.h b/src/core/dbusscreensaver.h new file mode 100644 index 00000000..d00313f7 --- /dev/null +++ b/src/core/dbusscreensaver.h @@ -0,0 +1,45 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef DBUSSCREENSAVER_H +#define DBUSSCREENSAVER_H + +#include "config.h" + +#include + +#include "screensaver.h" + +class DBusScreensaver : public Screensaver { + public: + DBusScreensaver(const QString &service, const QString &path, const QString &interface); + + void Inhibit(); + void Uninhibit(); + +private: + QString service_; + QString path_; + QString interface_; + + quint32 cookie_; +}; + +#endif diff --git a/src/core/deletefiles.cpp b/src/core/deletefiles.cpp new file mode 100644 index 00000000..275ac2c2 --- /dev/null +++ b/src/core/deletefiles.cpp @@ -0,0 +1,123 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "deletefiles.h" + +#include +#include +#include +#include + +#include "musicstorage.h" +#include "taskmanager.h" + +const int DeleteFiles::kBatchSize = 50; + +DeleteFiles::DeleteFiles(TaskManager* task_manager, std::shared_ptr storage) + : thread_(nullptr), + task_manager_(task_manager), + storage_(storage), + started_(false), + task_id_(0), + progress_(0) { + original_thread_ = thread(); +} + +DeleteFiles::~DeleteFiles() {} + +void DeleteFiles::Start(const SongList &songs) { + + if (thread_) return; + + songs_ = songs; + + task_id_ = task_manager_->StartTask(tr("Deleting files")); + task_manager_->SetTaskBlocksCollectionScans(true); + + thread_ = new QThread; + connect(thread_, SIGNAL(started()), SLOT(ProcessSomeFiles())); + + moveToThread(thread_); + thread_->start(); + +} + +void DeleteFiles::Start(const QStringList &filenames) { + + SongList songs; + for (const QString &filename : filenames) { + Song song; + song.set_url(QUrl::fromLocalFile(filename)); + songs << song; + } + + Start(songs); + +} + +void DeleteFiles::ProcessSomeFiles() { + + if (!started_) { + storage_->StartDelete(); + started_ = true; + } + + // None left? + if (progress_ >= songs_.count()) { + task_manager_->SetTaskProgress(task_id_, progress_, songs_.count()); + + storage_->FinishCopy(songs_with_errors_.isEmpty()); + + task_manager_->SetTaskFinished(task_id_); + + emit Finished(songs_with_errors_); + + // Move back to the original thread so deleteLater() can get called in + // the main thread's event loop + moveToThread(original_thread_); + deleteLater(); + + // Stop this thread + thread_->quit(); + return; + } + + // We process files in batches so we can be cancelled part-way through. + + const int n = qMin(songs_.count(), progress_ + kBatchSize); + for (; progress_ < n; ++progress_) { + task_manager_->SetTaskProgress(task_id_, progress_, songs_.count()); + + const Song &song = songs_[progress_]; + + MusicStorage::DeleteJob job; + job.metadata_ = song; + + if (!storage_->DeleteFromStorage(job)) { + songs_with_errors_ << song; + } + } + + QTimer::singleShot(0, this, SLOT(ProcessSomeFiles())); + +} + diff --git a/src/core/deletefiles.h b/src/core/deletefiles.h new file mode 100644 index 00000000..b26c8506 --- /dev/null +++ b/src/core/deletefiles.h @@ -0,0 +1,70 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef DELETEFILES_H +#define DELETEFILES_H + +#include "config.h" + +#include + +#include + +#include "song.h" + +class MusicStorage; +class TaskManager; + +class DeleteFiles : public QObject { + Q_OBJECT + + public: + DeleteFiles(TaskManager *task_manager, std::shared_ptr storage); + ~DeleteFiles(); + + static const int kBatchSize; + + void Start(const SongList& songs); + void Start(const QStringList& filenames); + +signals: + void Finished(const SongList& songs_with_errors); + + private slots: + void ProcessSomeFiles(); + + private: + QThread *thread_; + QThread *original_thread_; + TaskManager *task_manager_; + std::shared_ptr storage_; + + SongList songs_; + + bool started_; + + int task_id_; + int progress_; + + SongList songs_with_errors_; +}; + +#endif // DELETEFILES_H + diff --git a/src/core/filesystemmusicstorage.cpp b/src/core/filesystemmusicstorage.cpp new file mode 100644 index 00000000..b58f7026 --- /dev/null +++ b/src/core/filesystemmusicstorage.cpp @@ -0,0 +1,71 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "filesystemmusicstorage.h" +#include "core/logging.h" +#include "core/utilities.h" + +#include +#include +#include + +FilesystemMusicStorage::FilesystemMusicStorage(const QString &root) + : root_(root) {} + +bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) { + + const QFileInfo src = QFileInfo(job.source_); + const QFileInfo dest = QFileInfo(root_ + "/" + job.destination_); + + // Don't do anything if the destination is the same as the source + if (src == dest) return true; + + // Create directories as required + QDir dir; + if (!dir.mkpath(dest.absolutePath())) { + qLog(Warning) << "Failed to create directory" << dest.dir().absolutePath(); + return false; + } + + // Remove the destination file if it exists and we want to overwrite + if (job.overwrite_ && dest.exists()) QFile::remove(dest.absoluteFilePath()); + + // Copy or move + if (job.remove_original_) + return QFile::rename(src.absoluteFilePath(), dest.absoluteFilePath()); + else + return QFile::copy(src.absoluteFilePath(), dest.absoluteFilePath()); + +} + +bool FilesystemMusicStorage::DeleteFromStorage(const DeleteJob &job) { + + QString path = job.metadata_.url().toLocalFile(); + QFileInfo fileInfo(path); + + if (fileInfo.isDir()) + return Utilities::RemoveRecursive(path); + else + return QFile::remove(path); + +} + diff --git a/src/core/filesystemmusicstorage.h b/src/core/filesystemmusicstorage.h new file mode 100644 index 00000000..e30a33f7 --- /dev/null +++ b/src/core/filesystemmusicstorage.h @@ -0,0 +1,43 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef FILESYSTEMMUSICSTORAGE_H +#define FILESYSTEMMUSICSTORAGE_H + +#include "config.h" + +#include "musicstorage.h" + +class FilesystemMusicStorage : public virtual MusicStorage { +public: + explicit FilesystemMusicStorage(const QString &root); + ~FilesystemMusicStorage() {} + + QString LocalPath() const { return root_; } + + bool CopyToStorage(const CopyJob &job); + bool DeleteFromStorage(const DeleteJob &job); + +private: + QString root_; +}; + +#endif // FILESYSTEMMUSICSTORAGE_H + diff --git a/src/core/filesystemwatcherinterface.cpp b/src/core/filesystemwatcherinterface.cpp new file mode 100644 index 00000000..12d7eaf9 --- /dev/null +++ b/src/core/filesystemwatcherinterface.cpp @@ -0,0 +1,46 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "filesystemwatcherinterface.h" + +#include "qtfslistener.h" + +#ifdef Q_OS_DARWIN +#include "macfslistener.h" +#endif + +FileSystemWatcherInterface::FileSystemWatcherInterface(QObject *parent) + : QObject(parent) {} + +FileSystemWatcherInterface *FileSystemWatcherInterface::Create(QObject *parent) { + FileSystemWatcherInterface *ret; +#ifdef Q_OS_DARWIN + ret = new MacFSListener(parent); +#else + ret = new QtFSListener(parent); +#endif + + ret->Init(); + return ret; + +} + diff --git a/src/core/filesystemwatcherinterface.h b/src/core/filesystemwatcherinterface.h new file mode 100644 index 00000000..5749bd45 --- /dev/null +++ b/src/core/filesystemwatcherinterface.h @@ -0,0 +1,44 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef FILESYSTEMWATCHERINTERFACE_H +#define FILESYSTEMWATCHERINTERFACE_H + +#include "config.h" + +#include + +class FileSystemWatcherInterface : public QObject { + Q_OBJECT + public: + explicit FileSystemWatcherInterface(QObject *parent = nullptr); + virtual void Init() {} + virtual void AddPath(const QString& path) = 0; + virtual void RemovePath(const QString& path) = 0; + virtual void Clear() = 0; + + static FileSystemWatcherInterface* Create(QObject *parent = nullptr); + +signals: + void PathChanged(const QString &path); +}; + +#endif + diff --git a/src/core/flowlayout.cpp b/src/core/flowlayout.cpp new file mode 100644 index 00000000..864d40dd --- /dev/null +++ b/src/core/flowlayout.cpp @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "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 name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its 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 +** OWNER 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." +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include "flowlayout.h" +//! [1] +FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) + : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing) +{ + setContentsMargins(margin, margin, margin, margin); +} + +FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) + : m_hSpace(hSpacing), m_vSpace(vSpacing) { + setContentsMargins(margin, margin, margin, margin); +} +//! [1] + +//! [2] +FlowLayout::~FlowLayout() { + QLayoutItem* item; + while ((item = takeAt(0))) delete item; +} +//! [2] + +//! [3] +void FlowLayout::addItem(QLayoutItem* item) { itemList.append(item); } +//! [3] + +//! [4] +int FlowLayout::horizontalSpacing() const { + if (m_hSpace >= 0) { + return m_hSpace; + } else { + return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); + } +} + +int FlowLayout::verticalSpacing() const { + if (m_vSpace >= 0) { + return m_vSpace; + } else { + return smartSpacing(QStyle::PM_LayoutVerticalSpacing); + } +} +//! [4] + +//! [5] +int FlowLayout::count() const { return itemList.size(); } + +QLayoutItem* FlowLayout::itemAt(int index) const { + return itemList.value(index); +} + +QLayoutItem* FlowLayout::takeAt(int index) { + if (index >= 0 && index < itemList.size()) + return itemList.takeAt(index); + else + return 0; +} +//! [5] + +//! [6] +Qt::Orientations FlowLayout::expandingDirections() const { return 0; } +//! [6] + +//! [7] +bool FlowLayout::hasHeightForWidth() const { return true; } + +int FlowLayout::heightForWidth(int width) const { + int height = doLayout(QRect(0, 0, width, 0), true); + return height; +} +//! [7] + +//! [8] +void FlowLayout::setGeometry(const QRect& rect) { + QLayout::setGeometry(rect); + doLayout(rect, false); +} + +QSize FlowLayout::sizeHint() const { return minimumSize(); } + +QSize FlowLayout::minimumSize() const { + QSize size; + for (QLayoutItem* item : itemList) + size = size.expandedTo(item->minimumSize()); + + size += QSize(2 * margin(), 2 * margin()); + return size; +} +//! [8] + +//! [9] +int FlowLayout::doLayout(const QRect& rect, bool testOnly) const { + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); + int x = effectiveRect.x(); + int y = effectiveRect.y(); + int lineHeight = 0; + //! [9] + + //! [10] + for (QLayoutItem* item : itemList) { + QWidget* wid = item->widget(); + int spaceX = horizontalSpacing(); + if (spaceX == -1) + spaceX = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); + int spaceY = verticalSpacing(); + if (spaceY == -1) + spaceY = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); + //! [10] + //! [11] + int nextX = x + item->sizeHint().width() + spaceX; + if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) { + x = effectiveRect.x(); + y = y + lineHeight + spaceY; + nextX = x + item->sizeHint().width() + spaceX; + lineHeight = 0; + } + + if (!testOnly) item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + + x = nextX; + lineHeight = qMax(lineHeight, item->sizeHint().height()); + } + return y + lineHeight - rect.y() + bottom; +} +//! [11] +//! [12] +int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const { + QObject* parent = this->parent(); + if (!parent) { + return -1; + } else if (parent->isWidgetType()) { + QWidget *pw = static_cast(parent); + return pw->style()->pixelMetric(pm, 0, pw); + } else { + return static_cast(parent)->spacing(); + } +} +//! [12] diff --git a/src/core/flowlayout.h b/src/core/flowlayout.h new file mode 100644 index 00000000..df177dc0 --- /dev/null +++ b/src/core/flowlayout.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "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 name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its 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 +** OWNER 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." +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef FLOWLAYOUT_H +#define FLOWLAYOUT_H + +#include +#include +#include +#include +//! [0] +class FlowLayout : public QLayout { + public: + FlowLayout(QWidget* parent, int margin = -1, int hSpacing = -1, + int vSpacing = -1); + FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1); + ~FlowLayout(); + + void addItem(QLayoutItem *item); + int horizontalSpacing() const; + int verticalSpacing() const; + Qt::Orientations expandingDirections() const; + bool hasHeightForWidth() const; + int heightForWidth(int) const; + int count() const; + QLayoutItem *itemAt(int index) const; + QSize minimumSize() const; + void setGeometry(const QRect &rect); + QSize sizeHint() const; + QLayoutItem *takeAt(int index); + +private: + int doLayout(const QRect &rect, bool testOnly) const; + int smartSpacing(QStyle::PixelMetric pm) const; + + QList itemList; + int m_hSpace; + int m_vSpace; +}; +//! [0] + +#endif diff --git a/src/core/iconloader.cpp b/src/core/iconloader.cpp new file mode 100644 index 00000000..730106ba --- /dev/null +++ b/src/core/iconloader.cpp @@ -0,0 +1,80 @@ +/* + * Strawberry Music Player + * Copyright 2013, Jonas Kvinge + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "iconloader.h" +#include "core/logging.h" +#include "core/appearance.h" + +QList IconLoader::sizes_; +QString IconDefault(":/icons/64x64/strawberry.png"); + +void IconLoader::Init() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + sizes_.clear(); + sizes_ << 22 << 32 << 48 << 64; + + if (!QFile::exists(IconDefault)) { + qLog(Error) << "Default icon does not exist" << IconDefault; + } + +} + +QIcon IconLoader::Load(const QString &name) { + + QIcon ret; + + //qLog(Debug) << __PRETTY_FUNCTION__ << name; + + if (name.isEmpty()) { + qLog(Warning) << "Icon name is empty!"; + ret.addFile(IconDefault, QSize(64, 64)); + return ret; + } + + const QString path(":icons/%1x%2/%3.png"); + for (int size : sizes_) { + QString filename(path.arg(size).arg(size).arg(name)); + if (QFile::exists(filename)) ret.addFile(filename, QSize(size, size)); + } + + // Load icon from system theme only if it hasn't been found + if (ret.isNull()) { + ret = QIcon::fromTheme(name); + if (!ret.isNull()) return ret; + qLog(Warning) << "Couldn't load icon" << name; + } + + if (ret.isNull()) { + ret.addFile(IconDefault, QSize(64, 64)); + } + + return ret; + +} diff --git a/src/core/iconloader.h b/src/core/iconloader.h new file mode 100644 index 00000000..308bafdf --- /dev/null +++ b/src/core/iconloader.h @@ -0,0 +1,38 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ICONLOADER_H +#define ICONLOADER_H + +#include + +class IconLoader { + public: + + static void Init(); + static QIcon Load(const QString &name); + +private: + IconLoader() {} + + static QList sizes_; +}; + +#endif // ICONLOADER_H diff --git a/src/core/mac_delegate.h b/src/core/mac_delegate.h new file mode 100644 index 00000000..0398502f --- /dev/null +++ b/src/core/mac_delegate.h @@ -0,0 +1,33 @@ +#import + +#include "config.h" +#include "macglobalshortcutbackend.h" + +class PlatformInterface; +@class SPMediaKeyTap; + +@interface AppDelegate : NSObject { + PlatformInterface* application_handler_; + NSMenu* dock_menu_; + MacGlobalShortcutBackend* shortcut_handler_; + SPMediaKeyTap* key_tap_; + +} + +- (id) initWithHandler: (PlatformInterface*)handler; + +// NSApplicationDelegate +- (BOOL) applicationShouldHandleReopen: (NSApplication*)app hasVisibleWindows:(BOOL)flag; +- (NSMenu*) applicationDockMenu: (NSApplication*)sender; +- (void)applicationDidFinishLaunching:(NSNotification*)aNotification; +- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication*)sender; + +// NSUserNotificationCenterDelegate +- (BOOL) userNotificationCenter: (id)center + shouldPresentNotification: (id)notification; + +- (void) setDockMenu: (NSMenu*)menu; +- (MacGlobalShortcutBackend*) shortcut_handler; +- (void) setShortcutHandler: (MacGlobalShortcutBackend*)backend; +- (void) mediaKeyTap: (SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event; +@end diff --git a/src/core/mac_startup.h b/src/core/mac_startup.h new file mode 100644 index 00000000..1d8458eb --- /dev/null +++ b/src/core/mac_startup.h @@ -0,0 +1,37 @@ +#ifndef MAC_STARTUP_H +#define MAC_STARTUP_H + +#include "config.h" + +#include + +class MacGlobalShortcutBackend; +class QObject; +class QWidget; + +class PlatformInterface { + public: + // Called when the application should show itself. + virtual void Activate() = 0; + virtual bool LoadUrl(const QString& url) = 0; + + virtual ~PlatformInterface() {} +}; + +namespace mac { + +void MacMain(); +void SetShortcutHandler(MacGlobalShortcutBackend* handler); +void SetApplicationHandler(PlatformInterface* handler); +void CheckForUpdates(); + +QString GetBundlePath(); +QString GetResourcesPath(); +QString GetApplicationSupportPath(); +QString GetMusicDirectory(); + +void EnableFullScreen(const QWidget& main_window); + +} // namespace mac + +#endif diff --git a/src/core/mac_startup.mm b/src/core/mac_startup.mm new file mode 100644 index 00000000..3e1fec45 --- /dev/null +++ b/src/core/mac_startup.mm @@ -0,0 +1,457 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#import +#import +#import +#import +#import + +#import +#import +#import +#import +#import +#import +#import +#import + +#import + +#import + +#import + +#import "3rdparty/SPMediaKeyTap/SPMediaKeyTap.h" + +#include "config.h" +#include "globalshortcuts.h" +#include "mac_delegate.h" +#include "mac_startup.h" +#include "mac_utilities.h" +#include "macglobalshortcutbackend.h" +#include "utilities.h" +#include "core/logging.h" +#include "core/scoped_cftyperef.h" +#include "core/scoped_nsautorelease_pool.h" + +#ifdef HAVE_SPARKLE +#import +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +QDebug operator<<(QDebug dbg, NSObject* object) { + QString ns_format = [[NSString stringWithFormat:@"%@", object] UTF8String]; + dbg.nospace() << ns_format; + return dbg.space(); +} + +// Capture global media keys on Mac (Cocoa only!) +// See: http://www.rogueamoeba.com/utm/2007/09/29/apple-keyboard-media-key-event-handling/ + +@interface MacApplication : NSApplication { + PlatformInterface* application_handler_; + AppDelegate* delegate_; + // shortcut_handler_ only used to temporarily save it + // AppDelegate does all the heavy-shortcut-lifting + MacGlobalShortcutBackend* shortcut_handler_; +} + +- (MacGlobalShortcutBackend*)shortcut_handler; +- (void)SetShortcutHandler:(MacGlobalShortcutBackend*)handler; + +- (PlatformInterface*)application_handler; +- (void)SetApplicationHandler:(PlatformInterface*)handler; + +@end + +#ifdef HAVE_BREAKPAD +static bool BreakpadCallback(int, int, mach_port_t, void*) { return true; } + +static BreakpadRef InitBreakpad() { + ScopedNSAutoreleasePool pool; + BreakpadRef breakpad = nil; + NSDictionary* plist = [[NSBundle mainBundle] infoDictionary]; + if (plist) { + breakpad = BreakpadCreate(plist); + BreakpadSetFilterCallback(breakpad, &BreakpadCallback, nullptr); + } + [pool release]; + return breakpad; +} +#endif // HAVE_BREAKPAD + +@implementation AppDelegate + +- (id)init { + if ((self = [super init])) { + application_handler_ = nil; + shortcut_handler_ = nil; + dock_menu_ = nil; + } + return self; +} + +- (id)initWithHandler:(PlatformInterface*)handler { + application_handler_ = handler; + +#ifdef HAVE_BREAKPAD + breakpad_ = InitBreakpad(); +#endif + + // Register defaults for the whitelist of apps that want to use media keys + [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys: + [SPMediaKeyTap defaultMediaKeyUserBundleIdentifiers], kMediaKeyUsingBundleIdentifiersDefaultsKey, + nil]]; + return self; +} + +- (BOOL) applicationShouldHandleReopen: (NSApplication*)app hasVisibleWindows:(BOOL)flag { + if (application_handler_) { + application_handler_->Activate(); + } + return YES; +} + +- (void)setDockMenu:(NSMenu*)menu { + dock_menu_ = menu; +} + +- (NSMenu*)applicationDockMenu:(NSApplication*)sender { + return dock_menu_; +} + +- (void)setShortcutHandler:(MacGlobalShortcutBackend*)backend { + shortcut_handler_ = backend; +} + +- (MacGlobalShortcutBackend*)shortcut_handler { + return shortcut_handler_; +} + +- (void)applicationDidFinishLaunching:(NSNotification*)aNotification { + key_tap_ = [[SPMediaKeyTap alloc] initWithDelegate:self]; + if ([SPMediaKeyTap usesGlobalMediaKeyTap] && + ![[NSProcessInfo processInfo] + isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){ + .majorVersion = 10, + .minorVersion = 12, + .patchVersion = 0}]) { + [key_tap_ startWatchingMediaKeys]; + } + else { + qLog(Warning) << "Media key monitoring disabled"; + } +} + +- (BOOL)application:(NSApplication*)app openFile:(NSString*)filename { + qLog(Debug) << "Wants to open:" << [filename UTF8String]; + + if (application_handler_->LoadUrl(QString::fromUtf8([filename UTF8String]))) { + return YES; + } + + return NO; +} + +- (void)application:(NSApplication*)app openFiles:(NSArray*)filenames { + qLog(Debug) << "Wants to open:" << filenames; + [filenames enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL* stop) { + [self application:app openFile:(NSString*)object]; + }]; +} + +- (void) mediaKeyTap: (SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event { + NSAssert([event type] == NSSystemDefined && [event subtype] == SPSystemDefinedEventMediaKeys, @"Unexpected NSEvent in mediaKeyTap:receivedMediaKeyEvent:"); + + int key_code = (([event data1] & 0xFFFF0000) >> 16); + int key_flags = ([event data1] & 0x0000FFFF); + BOOL key_is_released = (((key_flags & 0xFF00) >> 8)) == 0xB; + // not used. keep just in case + // int key_repeat = (key_flags & 0x1); + + if (!shortcut_handler_) { + return; + } + if (key_is_released) { + shortcut_handler_->MacMediaKeyPressed(key_code); + } +} + +- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication*) sender { +#ifdef HAVE_BREAKPAD + BreakpadRelease(breakpad_); +#endif + return NSTerminateNow; +} + +- (BOOL) userNotificationCenter: (id)center shouldPresentNotification: (id)notification { + // Always show notifications, even if Strawberry is in the foreground. + return YES; +} + +@end + +@implementation MacApplication + +- (id)init { + if ((self = [super init])) { + [self SetShortcutHandler:nil]; + } + return self; +} + +- (MacGlobalShortcutBackend*)shortcut_handler { + // should be the same as delegate_'s shortcut handler + return shortcut_handler_; +} + +- (void)SetShortcutHandler:(MacGlobalShortcutBackend*)handler { + shortcut_handler_ = handler; + if (delegate_) [delegate_ setShortcutHandler:handler]; +} + +- (PlatformInterface*)application_handler { + return application_handler_; +} + +- (void)SetApplicationHandler:(PlatformInterface*)handler { + delegate_ = [[AppDelegate alloc] initWithHandler:handler]; + // App-shortcut-handler set before delegate is set. + // this makes sure the delegate's shortcut_handler is set + [delegate_ setShortcutHandler:shortcut_handler_]; + [self setDelegate:delegate_]; + + [[NSUserNotificationCenter defaultUserNotificationCenter] + setDelegate:delegate_]; +} + +- (void)sendEvent:(NSEvent*)event { + // If event tap is not installed, handle events that reach the app instead + BOOL shouldHandleMediaKeyEventLocally = ![SPMediaKeyTap usesGlobalMediaKeyTap]; + + if(shouldHandleMediaKeyEventLocally && [event type] == NSSystemDefined && [event subtype] == SPSystemDefinedEventMediaKeys) { + [(id)[self delegate] mediaKeyTap:nil receivedMediaKeyEvent:event]; + } + + [super sendEvent:event]; +} + +@end + +namespace mac { + +void MacMain() { + ScopedNSAutoreleasePool pool; + // Creates and sets the magic global variable so QApplication will find it. + [MacApplication sharedApplication]; +#ifdef HAVE_SPARKLE + // Creates and sets the magic global variable for Sparkle. + [[SUUpdater sharedUpdater] setDelegate:NSApp]; +#endif +} + +void SetShortcutHandler(MacGlobalShortcutBackend* handler) { + [NSApp SetShortcutHandler:handler]; +} + +void SetApplicationHandler(PlatformInterface* handler) { + [NSApp SetApplicationHandler:handler]; +} + +void CheckForUpdates() { +#ifdef HAVE_SPARKLE + [[SUUpdater sharedUpdater] checkForUpdates:NSApp]; +#endif +} + +QString GetBundlePath() { + ScopedCFTypeRef app_url(CFBundleCopyBundleURL(CFBundleGetMainBundle())); + ScopedCFTypeRef mac_path(CFURLCopyFileSystemPath(app_url.get(), kCFURLPOSIXPathStyle)); + const char* path = CFStringGetCStringPtr(mac_path.get(), CFStringGetSystemEncoding()); + QString bundle_path = QString::fromUtf8(path); + return bundle_path; +} + +QString GetResourcesPath() { + QString bundle_path = GetBundlePath(); + return bundle_path + "/Contents/Resources"; +} + +QString GetApplicationSupportPath() { + ScopedNSAutoreleasePool pool; + NSArray* paths = NSSearchPathForDirectoriesInDomains( + NSApplicationSupportDirectory, NSUserDomainMask, YES); + QString ret; + if ([paths count] > 0) { + NSString* user_path = [paths objectAtIndex:0]; + ret = QString::fromUtf8([user_path UTF8String]); + } else { + ret = "~/Collection/Application Support"; + } + return ret; +} + +QString GetMusicDirectory() { + ScopedNSAutoreleasePool pool; + NSArray* paths = NSSearchPathForDirectoriesInDomains(NSMusicDirectory, + NSUserDomainMask, YES); + QString ret; + if ([paths count] > 0) { + NSString* user_path = [paths objectAtIndex:0]; + ret = QString::fromUtf8([user_path UTF8String]); + } else { + ret = "~/Music"; + } + return ret; +} + +static int MapFunctionKey(int keycode) { + switch (keycode) { + // Function keys + case NSInsertFunctionKey: return Qt::Key_Insert; + case NSDeleteFunctionKey: return Qt::Key_Delete; + case NSPauseFunctionKey: return Qt::Key_Pause; + case NSPrintFunctionKey: return Qt::Key_Print; + case NSSysReqFunctionKey: return Qt::Key_SysReq; + case NSHomeFunctionKey: return Qt::Key_Home; + case NSEndFunctionKey: return Qt::Key_End; + case NSLeftArrowFunctionKey: return Qt::Key_Left; + case NSUpArrowFunctionKey: return Qt::Key_Up; + case NSRightArrowFunctionKey: return Qt::Key_Right; + case NSDownArrowFunctionKey: return Qt::Key_Down; + case NSPageUpFunctionKey: return Qt::Key_PageUp; + case NSPageDownFunctionKey: return Qt::Key_PageDown; + case NSScrollLockFunctionKey: return Qt::Key_ScrollLock; + case NSF1FunctionKey: return Qt::Key_F1; + case NSF2FunctionKey: return Qt::Key_F2; + case NSF3FunctionKey: return Qt::Key_F3; + case NSF4FunctionKey: return Qt::Key_F4; + case NSF5FunctionKey: return Qt::Key_F5; + case NSF6FunctionKey: return Qt::Key_F6; + case NSF7FunctionKey: return Qt::Key_F7; + case NSF8FunctionKey: return Qt::Key_F8; + case NSF9FunctionKey: return Qt::Key_F9; + case NSF10FunctionKey: return Qt::Key_F10; + case NSF11FunctionKey: return Qt::Key_F11; + case NSF12FunctionKey: return Qt::Key_F12; + case NSF13FunctionKey: return Qt::Key_F13; + case NSF14FunctionKey: return Qt::Key_F14; + case NSF15FunctionKey: return Qt::Key_F15; + case NSF16FunctionKey: return Qt::Key_F16; + case NSF17FunctionKey: return Qt::Key_F17; + case NSF18FunctionKey: return Qt::Key_F18; + case NSF19FunctionKey: return Qt::Key_F19; + case NSF20FunctionKey: return Qt::Key_F20; + case NSF21FunctionKey: return Qt::Key_F21; + case NSF22FunctionKey: return Qt::Key_F22; + case NSF23FunctionKey: return Qt::Key_F23; + case NSF24FunctionKey: return Qt::Key_F24; + case NSF25FunctionKey: return Qt::Key_F25; + case NSF26FunctionKey: return Qt::Key_F26; + case NSF27FunctionKey: return Qt::Key_F27; + case NSF28FunctionKey: return Qt::Key_F28; + case NSF29FunctionKey: return Qt::Key_F29; + case NSF30FunctionKey: return Qt::Key_F30; + case NSF31FunctionKey: return Qt::Key_F31; + case NSF32FunctionKey: return Qt::Key_F32; + case NSF33FunctionKey: return Qt::Key_F33; + case NSF34FunctionKey: return Qt::Key_F34; + case NSF35FunctionKey: return Qt::Key_F35; + case NSMenuFunctionKey: return Qt::Key_Menu; + case NSHelpFunctionKey: return Qt::Key_Help; + } + + return 0; +} + +QKeySequence KeySequenceFromNSEvent(NSEvent* event) { + NSString* str = [event charactersIgnoringModifiers]; + NSString* upper = [str uppercaseString]; + const char* chars = [upper UTF8String]; + NSUInteger modifiers = [event modifierFlags]; + int key = 0; + unsigned char c = chars[0]; + switch (c) { + case 0x1b: key = Qt::Key_Escape; break; + case 0x09: key = Qt::Key_Tab; break; + case 0x0d: key = Qt::Key_Return; break; + case 0x08: key = Qt::Key_Backspace; break; + case 0x03: key = Qt::Key_Enter; break; + } + + if (key == 0) { + if (c >= 0x20 && c <= 0x7e) { // ASCII from space to ~ + key = c; + } else { + key = MapFunctionKey([event keyCode]); + if (key == 0) { + return QKeySequence(); + } + } + } + + if (modifiers & NSShiftKeyMask) { + key += Qt::SHIFT; + } + if (modifiers & NSControlKeyMask) { + key += Qt::META; + } + if (modifiers & NSAlternateKeyMask) { + key += Qt::ALT; + } + if (modifiers & NSCommandKeyMask) { + key += Qt::CTRL; + } + + return QKeySequence(key); +} + +void DumpDictionary(CFDictionaryRef dict) { + NSDictionary* d = (NSDictionary*)dict; + NSLog(@"%@", d); +} + +// NSWindowCollectionBehaviorFullScreenPrimary +static const NSUInteger kFullScreenPrimary = 1 << 7; + +void EnableFullScreen(const QWidget& main_window) { + + NSView* view = reinterpret_cast(main_window.winId()); + NSWindow* window = [view window]; + [window setCollectionBehavior:kFullScreenPrimary]; +} + +float GetDevicePixelRatio(QWidget* widget) { + NSView* view = reinterpret_cast(widget->winId()); + return [[view window] backingScaleFactor]; +} + +} // namespace mac + diff --git a/src/core/mac_utilities.h b/src/core/mac_utilities.h new file mode 100644 index 00000000..d3dc93bd --- /dev/null +++ b/src/core/mac_utilities.h @@ -0,0 +1,38 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2011, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include + +#ifdef __OBJC__ +@class NSEvent; +#else +class NSEvent; +#endif + +namespace mac { + +QKeySequence KeySequenceFromNSEvent(NSEvent* event); +void DumpDictionary(CFDictionaryRef dict); +float GetDevicePixelRatio(QWidget* widget); +} diff --git a/src/core/macfslistener.h b/src/core/macfslistener.h new file mode 100644 index 00000000..34529045 --- /dev/null +++ b/src/core/macfslistener.h @@ -0,0 +1,63 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MACFSLISTENER_H +#define MACFSLISTENER_H + +#include "config.h" + +#include + +#include +#include +#include + +#include "filesystemwatcherinterface.h" + +class MacFSListener : public FileSystemWatcherInterface { + Q_OBJECT + + public: + explicit MacFSListener(QObject *parent = nullptr); + void Init(); + void AddPath(const QString &path); + void RemovePath(const QString &path); + void Clear(); + +signals: + void PathChanged(const QString &path); + + private slots: + void UpdateStream(); + + private: + void UpdateStreamAsync(); + + static void EventStreamCallback(ConstFSEventStreamRef stream, void *user_data, size_t num_events, void *event_paths, const FSEventStreamEventFlags event_flags[], const FSEventStreamEventId event_ids[]); + + CFRunLoopRef run_loop_; + FSEventStreamRef stream_; + + QSet paths_; + QTimer update_timer_; +}; + +#endif + diff --git a/src/core/macfslistener.mm b/src/core/macfslistener.mm new file mode 100644 index 00000000..9a2d0f4c --- /dev/null +++ b/src/core/macfslistener.mm @@ -0,0 +1,112 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "macfslistener.h" + +#include "config.h" + +#include +#include +#include + +#include "core/logging.h" +#include "core/scoped_nsobject.h" + +MacFSListener::MacFSListener(QObject* parent) + : FileSystemWatcherInterface(parent), run_loop_(nullptr), stream_(nullptr) { + update_timer_.setSingleShot(true); + update_timer_.setInterval(2000); + connect(&update_timer_, SIGNAL(timeout()), SLOT(UpdateStream())); +} + +void MacFSListener::Init() { run_loop_ = CFRunLoopGetCurrent(); } + +void MacFSListener::EventStreamCallback( + ConstFSEventStreamRef stream, + void* user_data, + size_t num_events, + void* event_paths, + const FSEventStreamEventFlags event_flags[], + const FSEventStreamEventId event_ids[]) { + MacFSListener* me = reinterpret_cast(user_data); + char** paths = reinterpret_cast(event_paths); + for (int i = 0; i < num_events; ++i) { + QString path = QString::fromUtf8(paths[i]); + qLog(Debug) << "Something changed at:" << path; + while (path.endsWith('/')) { + path.chop(1); + } + emit me->PathChanged(path); + } +} + +void MacFSListener::AddPath(const QString& path) { + Q_ASSERT(run_loop_); + paths_.insert(path); + UpdateStreamAsync(); +} + +void MacFSListener::RemovePath(const QString& path) { + Q_ASSERT(run_loop_); + paths_.remove(path); + UpdateStreamAsync(); +} + +void MacFSListener::Clear() { + paths_.clear(); + UpdateStreamAsync(); +} + +void MacFSListener::UpdateStreamAsync() { update_timer_.start(); } + +void MacFSListener::UpdateStream() { + if (stream_) { + FSEventStreamStop(stream_); + FSEventStreamInvalidate(stream_); + FSEventStreamRelease(stream_); + stream_ = nullptr; + } + + if (paths_.empty()) { + return; + } + + scoped_nsobject array([[NSMutableArray alloc] init]); + + for (const QString& path : paths_) { + scoped_nsobject string( + [[NSString alloc] initWithUTF8String:path.toUtf8().constData()]); + [array addObject:string.get()]; + } + + FSEventStreamContext context; + memset(&context, 0, sizeof(context)); + context.info = this; + CFAbsoluteTime latency = 1.0; + + stream_ = FSEventStreamCreate(nullptr, &EventStreamCallback, &context, // Copied + reinterpret_cast(array.get()), + kFSEventStreamEventIdSinceNow, latency, + kFSEventStreamCreateFlagNone); + + FSEventStreamScheduleWithRunLoop(stream_, run_loop_, kCFRunLoopDefaultMode); + FSEventStreamStart(stream_); +} + diff --git a/src/core/macscreensaver.cpp b/src/core/macscreensaver.cpp new file mode 100644 index 00000000..a571c842 --- /dev/null +++ b/src/core/macscreensaver.cpp @@ -0,0 +1,46 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "macscreensaver.h" + +#include + +#include "core/utilities.h" + +// kIOPMAssertionTypePreventUserIdleDisplaySleep from Lion. +#define kLionDisplayAssertion CFSTR("PreventUserIdleDisplaySleep") + +MacScreensaver::MacScreensaver() : assertion_id_(0) {} + +void MacScreensaver::Inhibit() { + CFStringRef assertion_type = (Utilities::GetMacVersion() >= 7) ? kLionDisplayAssertion : kIOPMAssertionTypeNoDisplaySleep; + + IOPMAssertionCreateWithName( + assertion_type, + kIOPMAssertionLevelOn, + CFSTR("Showing full-screen Strawberry visualisations"), + &assertion_id_); +} + +void MacScreensaver::Uninhibit() { + IOPMAssertionRelease(assertion_id_); +} diff --git a/src/core/macscreensaver.h b/src/core/macscreensaver.h new file mode 100644 index 00000000..fc3e74fc --- /dev/null +++ b/src/core/macscreensaver.h @@ -0,0 +1,41 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MACSCREENSAVER_H +#define MACSCREENSAVER_H + +#include "config.h" + +#include "screensaver.h" + +#include + +class MacScreensaver : public Screensaver { + public: + MacScreensaver(); + + void Inhibit(); + void Uninhibit(); + + private: + IOPMAssertionID assertion_id_; +}; + +#endif diff --git a/src/core/macsystemtrayicon.h b/src/core/macsystemtrayicon.h new file mode 100644 index 00000000..72ff5546 --- /dev/null +++ b/src/core/macsystemtrayicon.h @@ -0,0 +1,61 @@ +/* + *Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MACSYSTEMTRAYICON_H +#define MACSYSTEMTRAYICON_H + +#include "config.h" + +#include + +#include "systemtrayicon.h" + +class MacSystemTrayIconPrivate; + +class MacSystemTrayIcon : public SystemTrayIcon { + Q_OBJECT + + public: + MacSystemTrayIcon(QObject *parent = nullptr); + ~MacSystemTrayIcon(); + + void SetupMenu(QAction *previous, QAction *play, QAction *stop, QAction *stop_after, QAction *next, QAction *mute, QAction *quit); + + void SetNowPlaying(const Song& song, const QString& image_path); + void ClearNowPlaying(); + +private: + void SetupMenuItem(QAction *action); + +private slots: + void ActionChanged(); + +protected: + // SystemTrayIcon + void UpdateIcon(); + +private: + QPixmap orange_icon_; + QPixmap grey_icon_; + std::unique_ptr p_; + Q_DISABLE_COPY(MacSystemTrayIcon); +}; + +#endif // MACSYSTEMTRAYICON_H diff --git a/src/core/macsystemtrayicon.mm b/src/core/macsystemtrayicon.mm new file mode 100644 index 00000000..a2efe869 --- /dev/null +++ b/src/core/macsystemtrayicon.mm @@ -0,0 +1,210 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "macsystemtrayicon.h" + +#include "core/mac_delegate.h" +#include "core/song.h" + +#include +#include +#include + +#include + +#include +#include + +@interface Target :NSObject { + QAction* action_; +} +- (id) initWithQAction: (QAction*)action; +- (void) clicked; +@end + +@implementation Target // +- (id) init { + return [super init]; +} + +- (id) initWithQAction: (QAction*)action { + action_ = action; + return self; +} + +- (BOOL) validateMenuItem: (NSMenuItem*)menuItem { + // This is called when the menu is shown. + return action_->isEnabled(); +} + +- (void) clicked { + action_->trigger(); +} +@end + +class MacSystemTrayIconPrivate { + public: + MacSystemTrayIconPrivate() { + dock_menu_ = [[NSMenu alloc] initWithTitle:@"DockMenu"]; + + QString title = QT_TR_NOOP("Now Playing"); + NSString* t = [[NSString alloc] initWithUTF8String:title.toUtf8().constData()]; + now_playing_ = [[NSMenuItem alloc] + initWithTitle:t + action:nullptr + keyEquivalent:@""]; + + now_playing_artist_ = [[NSMenuItem alloc] + initWithTitle:@"Nothing to see here" + action:nullptr + keyEquivalent:@""]; + + now_playing_title_ = [[NSMenuItem alloc] + initWithTitle:@"Nothing to see here" + action:nullptr + keyEquivalent:@""]; + + [dock_menu_ insertItem:now_playing_title_ atIndex:0]; + [dock_menu_ insertItem:now_playing_artist_ atIndex:0]; + [dock_menu_ insertItem:now_playing_ atIndex:0]; + + // Don't look now. + // This must be called after our custom NSApplicationDelegate has been set. + [(AppDelegate*)([NSApp delegate]) setDockMenu:dock_menu_]; + + ClearPlaying(); + } + + void AddMenuItem(QAction* action) { + // Strip accelarators from name. + QString text = action->text().remove("&"); + NSString* title = [[NSString alloc] initWithUTF8String: text.toUtf8().constData()]; + // Create an object that can receive user clicks and pass them on to the QAction. + Target* target = [[Target alloc] initWithQAction:action]; + NSMenuItem* item = [[[NSMenuItem alloc] + initWithTitle:title + action:@selector(clicked) + keyEquivalent:@""] autorelease]; + [item setEnabled:action->isEnabled()]; + [item setTarget:target]; + [dock_menu_ addItem:item]; + actions_[action] = item; + } + + void ActionChanged(QAction* action) { + NSMenuItem* item = actions_[action]; + NSString* title = [[NSString alloc] initWithUTF8String: action->text().toUtf8().constData()]; + [item setTitle:title]; + } + + void AddSeparator() { + NSMenuItem* separator = [NSMenuItem separatorItem]; + [dock_menu_ addItem:separator]; + } + + void ShowPlaying(const QString& artist, const QString& title) { + ClearPlaying(); // Makes sure the order is consistent. + [now_playing_artist_ setTitle: + [[NSString alloc] initWithUTF8String: artist.toUtf8().constData()]]; + [now_playing_title_ setTitle: + [[NSString alloc] initWithUTF8String: title.toUtf8().constData()]]; + title.isEmpty() ? HideItem(now_playing_title_) : ShowItem(now_playing_title_); + artist.isEmpty() ? HideItem(now_playing_artist_) : ShowItem(now_playing_artist_); + artist.isEmpty() && title.isEmpty() ? HideItem(now_playing_) : ShowItem(now_playing_); + } + + void ClearPlaying() { + // Hiding doesn't seem to work in the dock menu. + HideItem(now_playing_); + HideItem(now_playing_artist_); + HideItem(now_playing_title_); + } + + private: + void HideItem(NSMenuItem* item) { + if ([dock_menu_ indexOfItem:item] != -1) { + [dock_menu_ removeItem:item]; + } + } + + void ShowItem(NSMenuItem* item, int index = 0) { + if ([dock_menu_ indexOfItem:item] == -1) { + [dock_menu_ insertItem:item atIndex:index]; + } + } + + QMap actions_; + + NSMenu* dock_menu_; + NSMenuItem* now_playing_; + NSMenuItem* now_playing_artist_; + NSMenuItem* now_playing_title_; + + Q_DISABLE_COPY(MacSystemTrayIconPrivate); +}; + +MacSystemTrayIcon::MacSystemTrayIcon(QObject* parent) + : SystemTrayIcon(parent), + orange_icon_(QPixmap(":/icons/64x64/strawberry.png").scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)), + grey_icon_(QPixmap(":icon_large_grey.png").scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)) { + QApplication::setWindowIcon(orange_icon_); +} + +MacSystemTrayIcon::~MacSystemTrayIcon() { +} + +void MacSystemTrayIcon::SetupMenu(QAction* previous, QAction* play, QAction* stop, QAction* stop_after, QAction* next, QAction* mute, QAction* quit) { + + p_.reset(new MacSystemTrayIconPrivate()); + SetupMenuItem(previous); + SetupMenuItem(play); + SetupMenuItem(stop); + SetupMenuItem(stop_after); + SetupMenuItem(next); + p_->AddSeparator(); + SetupMenuItem(mute); + p_->AddSeparator(); + Q_UNUSED(quit); // Mac already has a Quit item. + +} + +void MacSystemTrayIcon::SetupMenuItem(QAction* action) { + p_->AddMenuItem(action); + connect(action, SIGNAL(changed()), SLOT(ActionChanged())); +} + +void MacSystemTrayIcon::UpdateIcon() { + QApplication::setWindowIcon(CreateIcon(orange_icon_, grey_icon_)); +} + +void MacSystemTrayIcon::ActionChanged() { + QAction* action = qobject_cast(sender()); + p_->ActionChanged(action); +} + +void MacSystemTrayIcon::ClearPlaying() { + p_->ClearPlaying(); +} + +void MacSystemTrayIcon::SetPlaying(const Song& song, const QString& image_path) { + p_->ShowPlaying(song.artist(), song.PrettyTitle()); +} diff --git a/src/core/main.cpp b/src/core/main.cpp new file mode 100644 index 00000000..e389bd21 --- /dev/null +++ b/src/core/main.cpp @@ -0,0 +1,305 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include + +#include +#include + +#ifdef HAVE_GSTREAMER + #include + #include +#endif + +#ifdef Q_OS_UNIX + #include +#endif // Q_OS_UNIX + +#ifdef HAVE_DBUS + #include "core/mpris.h" + #include "core/mpris2.h" + #include + #include +#endif + +#ifdef Q_OS_DARWIN + #include + #include +#endif + +#ifdef Q_OS_WIN32 + #define _WIN32_WINNT 0x0600 + #include + #include + #include +#endif // Q_OS_WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/application.h" +#include "core/mainwindow.h" +#include "core/commandlineoptions.h" +#include "core/database.h" +#include "core/logging.h" +#include "core/mac_startup.h" +#include "core/metatypes.h" +#include "core/network.h" +#include "core/networkproxyfactory.h" +#include "core/song.h" +#include "core/utilities.h" +#include "core/iconloader.h" +#include "core/systemtrayicon.h" +#include "core/scangiomodulepath.h" +#include "engine/enginebase.h" +#ifdef HAVE_GSTREAMER +#include "engine/gstengine.h" +#endif +#include "version.h" +#include "widgets/osd.h" +#if 0 +#ifdef HAVE_LIBLASTFM + #include "covermanager/lastfmcoverprovider.h" +#endif +#include "covermanager/amazoncoverprovider.h" +#include "covermanager/coverproviders.h" +#include "covermanager/musicbrainzcoverprovider.h" +#include "covermanager/discogscoverprovider.h" +#endif + +#include "tagreadermessages.pb.h" + +#include "qtsingleapplication.h" +#include "qtsinglecoreapplication.h" + +#ifdef HAVE_DBUS + QDBusArgument &operator<<(QDBusArgument &arg, const QImage &image); + const QDBusArgument &operator>>(const QDBusArgument &arg, QImage &image); +#endif + +// Load sqlite plugin on windows and mac. +#include +Q_IMPORT_PLUGIN(QSQLiteDriverPlugin) + +int main(int argc, char* argv[]) { + +#ifdef Q_OS_DARWIN + // Do Mac specific startup to get media keys working. + // This must go before QApplication initialisation. + mac::MacMain(); + + if (QSysInfo::MacintoshVersion > QSysInfo::MV_10_8) { + // Work around 10.9 issue. + // https://bugreports.qt-project.org/browse/QTBUG-32789 + QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande"); + } +#endif + +#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN) + QCoreApplication::setApplicationName("Strawberry"); + QCoreApplication::setOrganizationName("Strawberry"); +#else + QCoreApplication::setApplicationName("strawberry"); + QCoreApplication::setOrganizationName("strawberry"); +#endif + QCoreApplication::setApplicationVersion(STRAWBERRY_VERSION_DISPLAY); + QCoreApplication::setOrganizationDomain("strawbs.org"); + +// This makes us show up nicely in gnome-volume-control +#if !GLIB_CHECK_VERSION(2, 36, 0) + g_type_init(); // Deprecated in glib 2.36.0 +#endif + g_set_application_name(QCoreApplication::applicationName().toLocal8Bit()); + + RegisterMetaTypes(); + + // Initialise logging. Log levels are set after the commandline options are + // parsed below. + logging::Init(); + g_log_set_default_handler(reinterpret_cast(&logging::GLog), nullptr); + + CommandlineOptions options(argc, argv); + + { + // Only start a core application now so we can check if there's another + // Strawberry running without needing an X server. + // This MUST be done before parsing the commandline options so QTextCodec + // gets the right system locale for filenames. + QtSingleCoreApplication a(argc, argv); + Utilities::CheckPortable(); + //crash_reporting.SetApplicationPath(a.applicationFilePath()); + + // Parse commandline options - need to do this before starting the + // full QApplication so it works without an X server + if (!options.Parse()) return 1; + logging::SetLevels(options.log_levels()); + + if (a.isRunning()) { + if (options.is_empty()) { + qLog(Info) << "Strawberry is already running - activating existing window"; + } + if (a.sendMessage(options.Serialize(), 5000)) { + return 0; + } + // Couldn't send the message so start anyway + } + } + +#ifdef Q_OS_DARWIN + // Must happen after QCoreApplication::setOrganizationName(). + setenv("XDG_CONFIG_HOME", Utilities::GetConfigPath(Utilities::Path_Root).toLocal8Bit().constData(), 1); +#endif + + // Output the version, so when people attach log output to bug reports they + // don't have to tell us which version they're using. + qLog(Info) << "Strawberry" << STRAWBERRY_VERSION_DISPLAY; + + // Seed the random number generators. + time_t t = time(nullptr); + srand(t); + qsrand(t); + + Utilities::IncreaseFDLimit(); + + QtSingleApplication a(argc, argv); + + // A bug in Qt means the wheel_scroll_lines setting gets ignored and replaced + // with the default value of 3 in QApplicationPrivate::initialize. + { + QSettings qt_settings(QSettings::UserScope, "Trolltech"); + qt_settings.beginGroup("Qt"); + QApplication::setWheelScrollLines(qt_settings.value("wheelScrollLines", QApplication::wheelScrollLines()).toInt()); + } + +#ifdef Q_OS_DARWIN + QCoreApplication::setCollectionPaths( + QStringList() << QCoreApplication::applicationDirPath() + "/../PlugIns"); +#endif + + a.setQuitOnLastWindowClosed(false); + + // Do this check again because another instance might have started by now + if (a.isRunning() && a.sendMessage(options.Serialize(), 5000)) { + return 0; + } + +#ifndef Q_OS_DARWIN + // Gnome on Ubuntu has menu icons disabled by default. I think that's a bad + // idea, and makes some menus in Strawberry look confusing. + QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus, false); +#else + QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus, true); + // Fixes focus issue with NSSearchField, see QTBUG-11401 + QCoreApplication::setAttribute(Qt::AA_NativeWindows, true); +#endif + +// 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 + + // Resources + Q_INIT_RESOURCE(data); + +#ifdef Q_OS_WIN32 + // Set the language for qtsparkle + qtsparkle::LoadTranslations(language); +#endif + + // Icons + IconLoader::Init(); + + // This is a nasty hack to ensure that everything in libprotobuf is + // initialised in the main thread. It fixes issue 3265 but nobody knows why. + // Don't remove this unless you can reproduce the error that it fixes. + //ParseAProto(); + //QtConcurrent::run(&ParseAProto); + + Application app; + + // Network proxy + QNetworkProxyFactory::setApplicationProxyFactory(NetworkProxyFactory::Instance()); + +#if 0 +//#ifdef HAVE_LIBLASTFM + app.cover_providers()->AddProvider(new LastFmCoverProvider); + app.cover_providers()->AddProvider(new AmazonCoverProvider); + app.cover_providers()->AddProvider(new DiscogsCoverProvider); + app.cover_providers()->AddProvider(new MusicbrainzCoverProvider); +#endif + + // Create the tray icon and OSD + std::unique_ptr tray_icon(SystemTrayIcon::CreateSystemTrayIcon()); + OSD osd(tray_icon.get(), &app); + +#ifdef HAVE_DBUS + mpris::Mpris mpris(&app); +#endif + +#ifdef HAVE_GSTREAMER + gst_init(nullptr, nullptr); + gst_pb_utils_init(); +#endif + + // Window + MainWindow w(&app, tray_icon.get(), &osd, options); +#ifdef Q_OS_DARWIN + mac::EnableFullScreen(w); +#endif // Q_OS_DARWIN +#ifdef HAVE_GIO + ScanGIOModulePath(); +#endif +#ifdef HAVE_DBUS + QObject::connect(&mpris, SIGNAL(RaiseMainWindow()), &w, SLOT(Raise())); +#endif + QObject::connect(&a, SIGNAL(messageReceived(QString)), &w, SLOT(CommandlineOptionsReceived(QString))); + + int ret = a.exec(); + + return ret; +} diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp new file mode 100644 index 00000000..83dff5ed --- /dev/null +++ b/src/core/mainwindow.cpp @@ -0,0 +1,2300 @@ +/* + * Strawberry Music Player + * Copyright 2013, Jonas Kvinge + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" +#include "version.h" + +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN32 + #include +#endif + +#include "core/appearance.h" +#include "core/application.h" +#include "core/commandlineoptions.h" +#include "core/database.h" +#include "core/filesystemmusicstorage.h" +#include "core/logging.h" +#include "core/mac_startup.h" +#include "core/mergedproxymodel.h" +#include "core/mimedata.h" +#include "core/mpris_common.h" +#include "core/network.h" +#include "core/player.h" +#include "core/songloader.h" +#include "core/stylesheetloader.h" +#include "core/taskmanager.h" +#include "core/timeconstants.h" +#include "core/utilities.h" +#include "core/iconloader.h" +#include "core/qtsystemtrayicon.h" +#include "core/systemtrayicon.h" +#include "core/windows7thumbbar.h" +#include "globalshortcuts/globalshortcuts.h" +#include "widgets/statusview.h" +#include "engine/enginebase.h" +#include "engine/gstengine.h" +#include "collection/groupbydialog.h" +#include "collection/collection.h" +#include "collection/collectionbackend.h" +#include "collection/collectiondirectorymodel.h" +#include "collection/collectionfilterwidget.h" +#include "collection/collectionviewcontainer.h" +#include "playlist/playlistbackend.h" +#include "playlist/playlist.h" +#include "playlist/playlistlistcontainer.h" +#include "playlist/playlistmanager.h" +#include "playlist/playlistsequence.h" +#include "playlist/playlistview.h" +#include "playlist/queue.h" +#include "playlist/queuemanager.h" +#include "playlist/songplaylistitem.h" +#include "playlistparsers/playlistparser.h" +#include "device/devicemanager.h" +#include "device/devicestatefiltermodel.h" +#include "device/deviceview.h" +#include "device/deviceviewcontainer.h" +#include "musicbrainz/tagfetcher.h" +#include "equalizer/equalizer.h" +#include "transcoder/transcodedialog.h" +#include "dialogs/about.h" +#include "dialogs/console.h" +#include "dialogs/edittagdialog.h" +#ifdef HAVE_GSTREAMER +#include "dialogs/organisedialog.h" +#include "dialogs/organiseerrordialog.h" +#endif +#include "dialogs/trackselectiondialog.h" +#include "dialogs/errordialog.h" +#include "widgets/fileview.h" +#include "widgets/multiloadingindicator.h" +#include "widgets/osd.h" +#include "widgets/stylehelper.h" +#include "widgets/trackslider.h" +#include "settings/settingsdialog.h" +#include "covermanager/albumcovermanager.h" + +#include "settings/behavioursettingspage.h" +#include "settings/playlistsettingspage.h" + +#ifdef Q_OS_DARWIN +#include "ui/macsystemtrayicon.h" +#endif + +#ifdef Q_OS_DARWIN +// Non exported mac-specific function. +void qt_mac_set_dock_menu(QMenu*); +#endif + +const char *MainWindow::kSettingsGroup = "MainWindow"; +const char *MainWindow::kAllFilesFilterSpec = QT_TR_NOOP("All Files (*)"); + +namespace { +const int kTrackSliderUpdateTimeMs = 40; +const int kTrackPositionUpdateTimeMs = 1000; +} + +MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, const CommandlineOptions &options, QWidget *parent) : + QMainWindow(parent), + ui_(new Ui_MainWindow), + thumbbar_(new Windows7ThumbBar(this)), + app_(app), + tray_icon_(tray_icon), + osd_(osd), + edit_tag_dialog_(std::bind(&MainWindow::CreateEditTagDialog, this)), + global_shortcuts_(new GlobalShortcuts(this)), + collection_view_(new CollectionViewContainer(this)), + status_view_(new StatusView(collection_view_, this)), + file_view_(new FileView(this)), + playlist_list_(new PlaylistListContainer(this)), + device_view_container_(new DeviceViewContainer(this)), + device_view_(device_view_container_->view()), + settings_dialog_(std::bind(&MainWindow::CreateSettingsDialog, this)), + cover_manager_([=]() { + AlbumCoverManager* cover_manager = new AlbumCoverManager(app, app->collection_backend()); + cover_manager->Init(); + + // Cover manager connections + connect(cover_manager, SIGNAL(AddToPlaylist(QMimeData*)), this, SLOT(AddToPlaylist(QMimeData*))); + return cover_manager; + }), + + //organise_dialog_(new OrganiseDialog(app_->task_manager())), + equalizer_(new Equalizer), +#ifdef HAVE_GSTREAMER + organise_dialog_([=]() { + OrganiseDialog* dialog = new OrganiseDialog(app->task_manager()); + dialog->SetDestinationModel(app->collection()->model()->directory_model()); + return dialog; + }), +#endif + queue_manager_([=]() { + QueueManager* manager = new QueueManager; + manager->SetPlaylistManager(app->playlist_manager()); + return manager; + }), + playlist_menu_(new QMenu(this)), + playlist_add_to_another_(nullptr), + playlistitem_actions_separator_(nullptr), + collection_sort_model_(new QSortFilterProxyModel(this)), + track_position_timer_(new QTimer(this)), + track_slider_timer_(new QTimer(this)), + was_maximized_(false), + saved_playback_position_(0), + saved_playback_state_(Engine::Empty), + doubleclick_addmode_(AddBehaviour_Append), + doubleclick_playmode_(PlayBehaviour_Never), + menu_playmode_(PlayBehaviour_Never) { + + qLog(Debug) << "Starting"; + + connect(app, SIGNAL(ErrorAdded(QString)), SLOT(ShowErrorDialog(QString))); + connect(app, SIGNAL(SettingsDialogRequested(SettingsDialog::Page)), SLOT(OpenSettingsDialogAtPage(SettingsDialog::Page))); + + // Initialise the UI + ui_->setupUi(this); +#ifdef Q_OS_DARWIN + ui_->menu_help->menuAction()->setVisible(false); +#endif + + ui_->multi_loading_indicator->SetTaskManager(app_->task_manager()); + status_view_->SetApplication(app_); + ui_->now_playing->SetApplication(app_); + + int volume = app_->player()->GetVolume(); + ui_->volume->setValue(volume); + VolumeChanged(volume); + + // Initialise the global search widget + StyleHelper::setBaseColor(palette().color(QPalette::Highlight).darker()); + + // Add tabs to the fancy tab widget + + ui_->tabs->AddTab(status_view_, IconLoader::Load("strawberry"), tr("Status")); + ui_->tabs->AddTab(collection_view_, IconLoader::Load("vinyl"), tr("Collection")); + ui_->tabs->AddTab(file_view_, IconLoader::Load("document-open"), tr("Files")); + ui_->tabs->AddTab(playlist_list_, IconLoader::Load("view-media-playlist"), tr("Playlists")); + ui_->tabs->AddTab(device_view_, IconLoader::Load("device"), tr("Devices")); + //ui_->tabs->AddSpacer(); + + // Add the now playing widget to the fancy tab widget + ui_->tabs->AddBottomWidget(ui_->now_playing); + + //ui_->tabs->SetBackgroundPixmap(QPixmap(":/pictures/strawberry-background.png")); + + track_position_timer_->setInterval(kTrackPositionUpdateTimeMs); + connect(track_position_timer_, SIGNAL(timeout()), SLOT(UpdateTrackPosition())); + track_slider_timer_->setInterval(kTrackSliderUpdateTimeMs); + connect(track_slider_timer_, SIGNAL(timeout()), SLOT(UpdateTrackSliderPosition())); + + // Start initialising the player + qLog(Debug) << "Initialising player"; + app_->player()->SetAnalyzer(ui_->analyzer); + app_->player()->SetEqualizer(equalizer_.get()); + app_->player()->Init(); + + // Models + qLog(Debug) << "Creating models"; + collection_sort_model_->setSourceModel(app_->collection()->model()); + collection_sort_model_->setSortRole(CollectionModel::Role_SortText); + collection_sort_model_->setDynamicSortFilter(true); + collection_sort_model_->setSortLocaleAware(true); + collection_sort_model_->sort(0); + + qLog(Debug) << "Creating models finished"; + + connect(ui_->playlist, SIGNAL(ViewSelectionModelChanged()), SLOT(PlaylistViewSelectionModelChanged())); + + ui_->playlist->SetManager(app_->playlist_manager()); + + ui_->playlist->view()->SetApplication(app_); + + collection_view_->view()->setModel(collection_sort_model_); + collection_view_->view()->SetApplication(app_); + device_view_->SetApplication(app_); + playlist_list_->SetApplication(app_); + +#ifdef HAVE_GSTREAMER + organise_dialog_->SetDestinationModel(app_->collection()->model()->directory_model()); +#endif + + // Icons + qLog(Debug) << "Creating UI"; + + // Help menu + + ui_->action_about_strawberry->setIcon(IconLoader::Load("strawberry")); + ui_->action_about_qt->setIcon(QIcon(":/qt-project.org/qmessagebox/images/qtlogo-64.png")); + + // Music menu + + ui_->action_open_file->setIcon(IconLoader::Load("document-open")); + ui_->action_open_cd->setIcon(IconLoader::Load("cd")); + ui_->action_previous_track->setIcon(IconLoader::Load("media-rewind")); + ui_->action_play_pause->setIcon(IconLoader::Load("media-play")); + ui_->action_stop->setIcon(IconLoader::Load("media-stop")); + ui_->action_stop_after_this_track->setIcon(IconLoader::Load("media-stop")); + ui_->action_next_track->setIcon(IconLoader::Load("media-forward")); + ui_->action_quit->setIcon(IconLoader::Load("application-exit")); + + // Playlist + + ui_->action_add_file->setIcon(IconLoader::Load("document-open")); + ui_->action_add_folder->setIcon(IconLoader::Load("document-open-folder")); + ui_->action_shuffle_mode->setIcon(IconLoader::Load("media-playlist-shuffle")); + ui_->action_repeat_mode->setIcon(IconLoader::Load("media-playlist-repeat")); + ui_->action_new_playlist->setIcon(IconLoader::Load("document-new")); + ui_->action_save_playlist->setIcon(IconLoader::Load("document-save")); + ui_->action_load_playlist->setIcon(IconLoader::Load("document-open")); + ui_->action_jump->setIcon(IconLoader::Load("go-jump")); + ui_->action_clear_playlist->setIcon(IconLoader::Load("edit-clear-list")); + ui_->action_shuffle->setIcon(IconLoader::Load("media-playlist-shuffle")); + ui_->action_remove_duplicates->setIcon(IconLoader::Load("list-remove")); + ui_->action_remove_unavailable->setIcon(IconLoader::Load("list-remove")); + + //ui_->action_remove_from_playlist->setIcon(IconLoader::Load("list-remove")); + + // Configure + + ui_->action_cover_manager->setIcon(IconLoader::Load("document-download")); + ui_->action_queue_manager->setIcon(IconLoader::Load("footsteps")); + ui_->action_edit_track->setIcon(IconLoader::Load("edit-rename")); + ui_->action_equalizer->setIcon(IconLoader::Load("equalizer")); + ui_->action_update_collection->setIcon(IconLoader::Load("view-refresh")); + ui_->action_full_collection_scan->setIcon(IconLoader::Load("view-refresh")); + ui_->action_settings->setIcon(IconLoader::Load("configure")); + + // File view connections + connect(file_view_, SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); + connect(file_view_, SIGNAL(PathChanged(QString)), SLOT(FilePathChanged(QString))); +#ifdef HAVE_GSTREAMER + connect(file_view_, SIGNAL(CopyToCollection(QList)), SLOT(CopyFilesToCollection(QList))); + connect(file_view_, SIGNAL(MoveToCollection(QList)), SLOT(MoveFilesToCollection(QList))); + connect(file_view_, SIGNAL(EditTags(QList)), SLOT(EditFileTags(QList))); + connect(file_view_, SIGNAL(CopyToDevice(QList)), SLOT(CopyFilesToDevice(QList))); +#endif + file_view_->SetTaskManager(app_->task_manager()); + + // Action connections + connect(ui_->action_next_track, SIGNAL(triggered()), app_->player(), SLOT(Next())); + connect(ui_->action_previous_track, SIGNAL(triggered()), app_->player(), SLOT(Previous())); + connect(ui_->action_play_pause, SIGNAL(triggered()), app_->player(), SLOT(PlayPause())); + connect(ui_->action_stop, SIGNAL(triggered()), app_->player(), SLOT(Stop())); + connect(ui_->action_quit, SIGNAL(triggered()), SLOT(Exit())); + connect(ui_->action_stop_after_this_track, SIGNAL(triggered()), SLOT(StopAfterCurrent())); + connect(ui_->action_mute, SIGNAL(triggered()), app_->player(), SLOT(Mute())); + + connect(ui_->action_clear_playlist, SIGNAL(triggered()), app_->playlist_manager(), SLOT(ClearCurrent())); + connect(ui_->action_remove_duplicates, SIGNAL(triggered()), app_->playlist_manager(), SLOT(RemoveDuplicatesCurrent())); + connect(ui_->action_remove_unavailable, SIGNAL(triggered()), app_->playlist_manager(), SLOT(RemoveUnavailableCurrent())); + connect(ui_->action_remove_from_playlist, SIGNAL(triggered()), SLOT(PlaylistRemoveCurrent())); + connect(ui_->action_edit_track, SIGNAL(triggered()), SLOT(EditTracks())); + connect(ui_->action_renumber_tracks, SIGNAL(triggered()), SLOT(RenumberTracks())); + connect(ui_->action_selection_set_value, SIGNAL(triggered()), SLOT(SelectionSetValue())); + connect(ui_->action_edit_value, SIGNAL(triggered()), SLOT(EditValue())); +#ifdef HAVE_GSTREAMER + connect(ui_->action_auto_complete_tags, SIGNAL(triggered()), SLOT(AutoCompleteTags())); +#endif + connect(ui_->action_settings, SIGNAL(triggered()), SLOT(OpenSettingsDialog())); + connect(ui_->action_about_strawberry, SIGNAL(triggered()), SLOT(ShowAboutDialog())); + connect(ui_->action_about_qt, SIGNAL(triggered()), qApp, SLOT(aboutQt())); + connect(ui_->action_shuffle, SIGNAL(triggered()), app_->playlist_manager(), SLOT(ShuffleCurrent())); + connect(ui_->action_open_file, SIGNAL(triggered()), SLOT(AddFile())); + connect(ui_->action_open_cd, SIGNAL(triggered()), SLOT(AddCDTracks())); + connect(ui_->action_add_file, SIGNAL(triggered()), SLOT(AddFile())); + connect(ui_->action_add_folder, SIGNAL(triggered()), SLOT(AddFolder())); + connect(ui_->action_cover_manager, SIGNAL(triggered()), SLOT(ShowCoverManager())); + connect(ui_->action_equalizer, SIGNAL(triggered()), equalizer_.get(), SLOT(show())); +#ifdef HAVE_GSTREAMER + //connect(ui_->action_transcode, SIGNAL(triggered()), SLOT(ShowTranscodeDialog())); +#endif + connect(ui_->action_jump, SIGNAL(triggered()), ui_->playlist->view(), SLOT(JumpToCurrentlyPlayingTrack())); + connect(ui_->action_update_collection, SIGNAL(triggered()), app_->collection(), SLOT(IncrementalScan())); + connect(ui_->action_full_collection_scan, SIGNAL(triggered()), app_->collection(), SLOT(FullScan())); + connect(ui_->action_queue_manager, SIGNAL(triggered()), SLOT(ShowQueueManager())); + //connect(ui_->action_add_files_to_transcoder, SIGNAL(triggered()), SLOT(AddFilesToTranscoder())); + + // Playlist view actions + ui_->action_next_playlist->setShortcuts(QList() << QKeySequence::fromString("Ctrl+Tab")<< QKeySequence::fromString("Ctrl+PgDown")); + ui_->action_previous_playlist->setShortcuts(QList() << QKeySequence::fromString("Ctrl+Shift+Tab")<< QKeySequence::fromString("Ctrl+PgUp")); + + // Actions for switching tabs will be global to the entire window, so adding them here + addAction(ui_->action_next_playlist); + addAction(ui_->action_previous_playlist); + + // Give actions to buttons + ui_->forward_button->setDefaultAction(ui_->action_next_track); + ui_->back_button->setDefaultAction(ui_->action_previous_track); + ui_->pause_play_button->setDefaultAction(ui_->action_play_pause); + ui_->stop_button->setDefaultAction(ui_->action_stop); + + ui_->playlist->SetActions(ui_->action_new_playlist, ui_->action_load_playlist, ui_->action_save_playlist, ui_->action_clear_playlist, ui_->action_next_playlist, /* These two actions aren't associated */ ui_->action_previous_playlist /* to a button but to the main window */ ); + + // Add the shuffle and repeat action groups to the menu + ui_->action_shuffle_mode->setMenu(ui_->playlist_sequence->shuffle_menu()); + ui_->action_repeat_mode->setMenu(ui_->playlist_sequence->repeat_menu()); + + // Stop actions + QMenu* stop_menu = new QMenu(this); + stop_menu->addAction(ui_->action_stop); + stop_menu->addAction(ui_->action_stop_after_this_track); + ui_->stop_button->setMenu(stop_menu); + + // Player connections + connect(ui_->volume, SIGNAL(valueChanged(int)), app_->player(), SLOT(SetVolume(int))); + + connect(app_->player(), SIGNAL(Error(QString)), SLOT(ShowErrorDialog(QString))); + connect(app_->player(), SIGNAL(SongChangeRequestProcessed(QUrl,bool)), app_->playlist_manager(), SLOT(SongChangeRequestProcessed(QUrl,bool))); + + connect(app_->player(), SIGNAL(Paused()), SLOT(MediaPaused())); + connect(app_->player(), SIGNAL(Playing()), SLOT(MediaPlaying())); + connect(app_->player(), SIGNAL(Stopped()), SLOT(MediaStopped())); + connect(app_->player(), SIGNAL(Seeked(qlonglong)), SLOT(Seeked(qlonglong))); + connect(app_->player(), SIGNAL(TrackSkipped(PlaylistItemPtr)), SLOT(TrackSkipped(PlaylistItemPtr))); + connect(this, SIGNAL(IntroPointReached()), app_->player(), SLOT(IntroPointReached())); + connect(app_->player(), SIGNAL(VolumeChanged(int)), SLOT(VolumeChanged(int))); + + connect(app_->player(), SIGNAL(Paused()), ui_->playlist, SLOT(ActivePaused())); + connect(app_->player(), SIGNAL(Playing()), ui_->playlist, SLOT(ActivePlaying())); + connect(app_->player(), SIGNAL(Stopped()), ui_->playlist, SLOT(ActiveStopped())); + + connect(app_->player(), SIGNAL(Paused()), osd_, SLOT(Paused())); + connect(app_->player(), SIGNAL(Stopped()), osd_, SLOT(Stopped())); + connect(app_->player(), SIGNAL(PlaylistFinished()), osd_, SLOT(PlaylistFinished())); + connect(app_->player(), SIGNAL(VolumeChanged(int)), osd_, SLOT(VolumeChanged(int))); + connect(app_->player(), SIGNAL(VolumeChanged(int)), ui_->volume, SLOT(setValue(int))); + connect(app_->player(), SIGNAL(ForceShowOSD(Song, bool)), SLOT(ForceShowOSD(Song, bool))); + connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(SongChanged(Song))); + connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), app_->player(), SLOT(CurrentMetadataChanged(Song))); + connect(app_->playlist_manager(), SIGNAL(EditingFinished(QModelIndex)), SLOT(PlaylistEditFinished(QModelIndex))); + connect(app_->playlist_manager(), SIGNAL(Error(QString)), SLOT(ShowErrorDialog(QString))); + connect(app_->playlist_manager(), SIGNAL(SummaryTextChanged(QString)), ui_->playlist_summary, SLOT(setText(QString))); + connect(app_->playlist_manager(), SIGNAL(PlayRequested(QModelIndex)), SLOT(PlayIndex(QModelIndex))); + + connect(ui_->playlist->view(), SIGNAL(doubleClicked(QModelIndex)), SLOT(PlaylistDoubleClick(QModelIndex))); + //connect(ui_->playlist->view(), SIGNAL(doubleClicked(QModelIndex)), SLOT(PlayIndex(QModelIndex))); + connect(ui_->playlist->view(), SIGNAL(PlayItem(QModelIndex)), SLOT(PlayIndex(QModelIndex))); + connect(ui_->playlist->view(), SIGNAL(PlayPause()), app_->player(), SLOT(PlayPause())); + connect(ui_->playlist->view(), SIGNAL(RightClicked(QPoint,QModelIndex)), SLOT(PlaylistRightClick(QPoint,QModelIndex))); + //connect(ui_->playlist->view(), SIGNAL(SeekTrack(int)), ui_->track_slider, SLOT(Seek(int))); + connect(ui_->playlist->view(), SIGNAL(SeekForward()), app_->player(), SLOT(SeekForward())); + connect(ui_->playlist->view(), SIGNAL(SeekBackward()), app_->player(), SLOT(SeekBackward())); + connect(ui_->playlist->view(), SIGNAL(BackgroundPropertyChanged()), SLOT(RefreshStyleSheet())); + + connect(ui_->track_slider, SIGNAL(ValueChangedSeconds(int)), app_->player(), SLOT(SeekTo(int))); + + // Collection connections + connect(collection_view_->view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); + connect(collection_view_->view(), SIGNAL(ShowConfigDialog()), SLOT(ShowCollectionConfig())); + connect(app_->collection_model(), SIGNAL(TotalSongCountUpdated(int)), collection_view_->view(), SLOT(TotalSongCountUpdated(int))); + connect(app_->collection_model(), SIGNAL(TotalArtistCountUpdated(int)), collection_view_->view(), SLOT(TotalArtistCountUpdated(int))); + connect(app_->collection_model(), SIGNAL(TotalAlbumCountUpdated(int)), collection_view_->view(), SLOT(TotalAlbumCountUpdated(int))); + connect(app_->collection_model(), SIGNAL(modelAboutToBeReset()), collection_view_->view(), SLOT(SaveFocus())); + connect(app_->collection_model(), SIGNAL(modelReset()), collection_view_->view(), SLOT(RestoreFocus())); + + connect(app_->task_manager(), SIGNAL(PauseCollectionWatchers()), app_->collection(), SLOT(PauseWatcher())); + connect(app_->task_manager(), SIGNAL(ResumeCollectionWatchers()), app_->collection(), SLOT(ResumeWatcher())); + + // Devices connections + connect(device_view_, SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); + + // Collection filter widget + QActionGroup* collection_view_group = new QActionGroup(this); + + collection_show_all_ = collection_view_group->addAction(tr("Show all songs")); + collection_show_duplicates_ = collection_view_group->addAction(tr("Show only duplicates")); + collection_show_untagged_ = collection_view_group->addAction(tr("Show only untagged")); + + collection_show_all_->setCheckable(true); + collection_show_duplicates_->setCheckable(true); + collection_show_untagged_->setCheckable(true); + collection_show_all_->setChecked(true); + + connect(collection_view_group, SIGNAL(triggered(QAction*)), SLOT(ChangeCollectionQueryMode(QAction*))); + + QAction* collection_config_action = new QAction(IconLoader::Load("configure"), tr("Configure collection..."), this); + connect(collection_config_action, SIGNAL(triggered()), SLOT(ShowCollectionConfig())); + collection_view_->filter()->SetSettingsGroup(kSettingsGroup); + collection_view_->filter()->SetCollectionModel(app_->collection()->model()); + + QAction* separator = new QAction(this); + separator->setSeparator(true); + + collection_view_->filter()->AddMenuAction(collection_show_all_); + collection_view_->filter()->AddMenuAction(collection_show_duplicates_); + collection_view_->filter()->AddMenuAction(collection_show_untagged_); + collection_view_->filter()->AddMenuAction(separator); + collection_view_->filter()->AddMenuAction(collection_config_action); + + // Playlist menu + playlist_play_pause_ = playlist_menu_->addAction(tr("Play"), this, SLOT(PlaylistPlay())); + playlist_menu_->addAction(ui_->action_stop); + playlist_stop_after_ = playlist_menu_->addAction(IconLoader::Load("media-stop"), tr("Stop after this track"), this, SLOT(PlaylistStopAfter())); + playlist_queue_ = playlist_menu_->addAction(IconLoader::Load("go-next"), tr("Toggle queue status"), this, SLOT(PlaylistQueue())); + playlist_queue_->setVisible(false); + playlist_queue_->setShortcut(QKeySequence("Ctrl+D")); + ui_->playlist->addAction(playlist_queue_); + playlist_skip_ = playlist_menu_->addAction(IconLoader::Load("media-forward"), tr("Toggle skip status"), this, SLOT(PlaylistSkip())); + playlist_skip_->setVisible(false); + ui_->playlist->addAction(playlist_skip_); + + playlist_menu_->addSeparator(); + playlist_menu_->addAction(ui_->action_remove_from_playlist); + playlist_undoredo_ = playlist_menu_->addSeparator(); + playlist_menu_->addAction(ui_->action_edit_track); + playlist_menu_->addAction(ui_->action_edit_value); + playlist_menu_->addAction(ui_->action_renumber_tracks); + playlist_menu_->addAction(ui_->action_selection_set_value); + playlist_menu_->addAction(ui_->action_auto_complete_tags); + //playlist_menu_->addAction(ui_->action_add_files_to_transcoder); + playlist_menu_->addSeparator(); +#ifdef HAVE_GSTREAMER + playlist_copy_to_collection_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Copy to collection..."), this, SLOT(PlaylistCopyToCollection())); + playlist_move_to_collection_ = playlist_menu_->addAction(IconLoader::Load("go-jump"), tr("Move to collection..."), this, SLOT(PlaylistMoveToCollection())); + //playlist_organise_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Organise files..."), this, SLOT(PlaylistMoveToCollection())); + playlist_copy_to_device_ = playlist_menu_->addAction(IconLoader::Load("device"), tr("Copy to device..."), this, SLOT(PlaylistCopyToDevice())); +#endif + //playlist_delete_ = playlist_menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete from disk..."), this, SLOT(PlaylistDelete())); + playlist_open_in_browser_ = playlist_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, SLOT(PlaylistOpenInBrowser())); + playlist_open_in_browser_->setVisible(false); + playlist_show_in_collection_ = playlist_menu_->addAction(IconLoader::Load("edit-find"), tr("Show in collection..."), this, SLOT(ShowInCollection())); + playlist_menu_->addSeparator(); + playlistitem_actions_separator_ = playlist_menu_->addSeparator(); + playlist_menu_->addAction(ui_->action_clear_playlist); + playlist_menu_->addAction(ui_->action_shuffle); + playlist_menu_->addAction(ui_->action_remove_duplicates); + playlist_menu_->addAction(ui_->action_remove_unavailable); + +#ifdef Q_OS_DARWIN + ui_->action_shuffle->setShortcut(QKeySequence()); +#endif + + // We have to add the actions on the playlist menu to this QWidget otherwise + // their shortcut keys don't work + addActions(playlist_menu_->actions()); + + connect(ui_->playlist, SIGNAL(UndoRedoActionsChanged(QAction*,QAction*)), SLOT(PlaylistUndoRedoChanged(QAction*,QAction*))); + +#ifdef HAVE_GSTREAMER + playlist_copy_to_device_->setDisabled(app_->device_manager()->connected_devices_model()->rowCount() == 0); + connect(app_->device_manager()->connected_devices_model(), SIGNAL(IsEmptyChanged(bool)), playlist_copy_to_device_, SLOT(setDisabled(bool))); +#endif + +#ifdef Q_OS_DARWIN + mac::SetApplicationHandler(this); +#endif + // Tray icon + tray_icon_->SetupMenu(ui_->action_previous_track, ui_->action_play_pause, ui_->action_stop, ui_->action_stop_after_this_track, ui_->action_next_track, ui_->action_mute, ui_->action_quit); + + connect(tray_icon_, SIGNAL(PlayPause()), app_->player(), SLOT(PlayPause())); + connect(tray_icon_, SIGNAL(SeekForward()), app_->player(), SLOT(SeekForward())); + connect(tray_icon_, SIGNAL(SeekBackward()), app_->player(), SLOT(SeekBackward())); + connect(tray_icon_, SIGNAL(NextTrack()), app_->player(), SLOT(Next())); + connect(tray_icon_, SIGNAL(PreviousTrack()), app_->player(), SLOT(Previous())); + connect(tray_icon_, SIGNAL(ShowHide()), SLOT(ToggleShowHide())); + connect(tray_icon_, SIGNAL(ChangeVolume(int)), SLOT(VolumeWheelEvent(int))); + + // Windows 7 thumbbar buttons + thumbbar_->SetActions(QList() << ui_->action_previous_track << ui_->action_play_pause << ui_->action_stop << ui_->action_next_track << nullptr); // spacer + +#if (defined(Q_OS_DARWIN) && defined(HAVE_SPARKLE)) || defined(Q_OS_WIN32) + // Add check for updates item to application menu. + QAction* check_updates = ui_->menu_tools->addAction(tr("Check for updates...")); + check_updates->setMenuRole(QAction::ApplicationSpecificRole); + connect(check_updates, SIGNAL(triggered(bool)), SLOT(CheckForUpdates())); +#endif + +#ifdef Q_OS_WIN32 + qLog(Debug) << "Creating sparkle updater"; + qtsparkle::Updater* updater = new qtsparkle::Updater(QUrl("https://strawberry-data.appspot.com/sparkle-windows"), this); + updater->SetNetworkAccessManager(new NetworkAccessManager(this)); + updater->SetVersion(STRAWBERRY_VERSION_SPARKLE); + connect(check_updates, SIGNAL(triggered()), updater, SLOT(CheckNow())); +#endif + + // Global shortcuts + connect(global_shortcuts_, SIGNAL(Play()), app_->player(), SLOT(Play())); + connect(global_shortcuts_, SIGNAL(Pause()), app_->player(), SLOT(Pause())); + connect(global_shortcuts_, SIGNAL(PlayPause()), ui_->action_play_pause, SLOT(trigger())); + connect(global_shortcuts_, SIGNAL(Stop()), ui_->action_stop, SLOT(trigger())); + connect(global_shortcuts_, SIGNAL(StopAfter()), ui_->action_stop_after_this_track, SLOT(trigger())); + connect(global_shortcuts_, SIGNAL(Next()), ui_->action_next_track, SLOT(trigger())); + connect(global_shortcuts_, SIGNAL(Previous()), ui_->action_previous_track, SLOT(trigger())); + connect(global_shortcuts_, SIGNAL(IncVolume()), app_->player(), SLOT(VolumeUp())); + connect(global_shortcuts_, SIGNAL(DecVolume()), app_->player(), SLOT(VolumeDown())); + connect(global_shortcuts_, SIGNAL(Mute()), app_->player(), SLOT(Mute())); + connect(global_shortcuts_, SIGNAL(SeekForward()), app_->player(), SLOT(SeekForward())); + connect(global_shortcuts_, SIGNAL(SeekBackward()), app_->player(), SLOT(SeekBackward())); + connect(global_shortcuts_, SIGNAL(ShowHide()), SLOT(ToggleShowHide())); + connect(global_shortcuts_, SIGNAL(ShowOSD()), app_->player(), SLOT(ShowOSD())); + connect(global_shortcuts_, SIGNAL(TogglePrettyOSD()), app_->player(), SLOT(TogglePrettyOSD())); + + // Fancy tabs + connect(ui_->tabs, SIGNAL(ModeChanged(FancyTabWidget::Mode)), SLOT(SaveGeometry())); + connect(ui_->tabs, SIGNAL(CurrentChanged(int)), SLOT(TabSwitched())); + connect(ui_->tabs, SIGNAL(CurrentChanged(int)), SLOT(SaveGeometry())); + + // Status + ConnectStatusView(status_view_); + + // Analyzer + //ui_->analyzer->SetEngine(app_->player()->engine()); + + connect(ui_->analyzer, SIGNAL(WheelEvent(int)), SLOT(VolumeWheelEvent(int))); + +#if 0 + // Equalizer + qLog(Debug) << "Creating equalizer"; + connect(equalizer_.get(), SIGNAL(ParametersChanged(int,QList)), app_->player()->engine(), SLOT(SetEqualizerParameters(int,QList))); + connect(equalizer_.get(), SIGNAL(EnabledChanged(bool)), app_->player()->engine(), SLOT(SetEqualizerEnabled(bool))); + connect(equalizer_.get(), SIGNAL(StereoBalanceChanged(float)), app_->player()->engine(), SLOT(SetStereoBalance(float))); + + app_->player()->engine()->SetEqualizerEnabled(equalizer_->is_enabled()); + app_->player()->engine()->SetEqualizerParameters(equalizer_->preamp_value(), equalizer_->gain_values()); + app_->player()->engine()->SetStereoBalance(equalizer_->stereo_balance()); +#endif + + // Statusbar widgets + ui_->playlist_summary->setMinimumWidth(QFontMetrics(font()).width("WW selected of WW tracks - [ WW:WW ]")); + ui_->status_bar_stack->setCurrentWidget(ui_->playlist_summary_page); + connect(ui_->multi_loading_indicator, SIGNAL(TaskCountChange(int)), SLOT(TaskCountChanged(int))); + + ui_->track_slider->SetApplication(app); + + // Now playing widget + qLog(Debug) << "Creating now playing widget"; + ui_->now_playing->set_ideal_height(ui_->status_bar->sizeHint().height() + ui_->player_controls->sizeHint().height()); + connect(app_->player(), SIGNAL(Stopped()), ui_->now_playing, SLOT(Stopped())); + //connect(ui_->now_playing, SIGNAL(ShowAboveStatusBarChanged(bool)), SLOT(PlayingWidgetPositionChanged(bool))); + connect(ui_->action_console, SIGNAL(triggered()), SLOT(ShowConsole())); + PlayingWidgetPositionChanged(); + + // Load theme + // This is tricky: we need to save the default/system palette now, before + // loading user preferred theme (which will overide it), to be able to restore it later + const_cast(Appearance::kDefaultPalette) = QApplication::palette(); + app_->appearance()->LoadUserTheme(); + StyleSheetLoader* css_loader = new StyleSheetLoader(this); + css_loader->SetStyleSheet(this, ":style/mainwindow.css"); + RefreshStyleSheet(); + + // Load playlists + app_->playlist_manager()->Init(app_->collection_backend(), app_->playlist_backend(), ui_->playlist_sequence, ui_->playlist); + + // This connection must be done after the playlists have been initialized. + connect(this, SIGNAL(StopAfterToggled(bool)), osd_, SLOT(StopAfterToggle(bool))); + + // We need to connect these global shortcuts here after the playlist have been initialized + connect(global_shortcuts_, SIGNAL(CycleShuffleMode()), app_->playlist_manager()->sequence(), SLOT(CycleShuffleMode())); + connect(global_shortcuts_, SIGNAL(CycleRepeatMode()), app_->playlist_manager()->sequence(), SLOT(CycleRepeatMode())); + connect(app_->playlist_manager()->sequence(), SIGNAL(RepeatModeChanged(PlaylistSequence::RepeatMode)), osd_, SLOT(RepeatModeChanged(PlaylistSequence::RepeatMode))); + connect(app_->playlist_manager()->sequence(), SIGNAL(ShuffleModeChanged(PlaylistSequence::ShuffleMode)), osd_, SLOT(ShuffleModeChanged(PlaylistSequence::ShuffleMode))); + + // Load settings + qLog(Debug) << "Loading settings"; + settings_.beginGroup(kSettingsGroup); + + // Set last used geometry to position window on the correct monitor + // Set window state only if the window was last maximized + was_maximized_ = settings_.value("maximized", false).toBool(); + restoreGeometry(settings_.value("geometry").toByteArray()); + if (was_maximized_) { + setWindowState(windowState() | Qt::WindowMaximized); + } + + if (!ui_->splitter->restoreState(settings_.value("splitter_state").toByteArray())) { + ui_->splitter->setSizes(QList() << 300 << width() - 300); + } + ui_->tabs->SetCurrentIndex(settings_.value("current_tab", 1 /* Collection tab */ ).toInt()); + FancyTabWidget::Mode default_mode = FancyTabWidget::Mode_LargeSidebar; + ui_->tabs->SetMode(FancyTabWidget::Mode(settings_.value("tab_mode", default_mode).toInt())); + file_view_->SetPath(settings_.value("file_path", QDir::homePath()).toString()); + TabSwitched(); + + // Users often collapse one side of the splitter by mistake and don't know + // how to restore it. This must be set after the state is restored above. + ui_->splitter->setChildrenCollapsible(false); + + ReloadSettings(); + + // Reload pretty OSD to avoid issues with fonts + osd_->ReloadPrettyOSDSettings(); + + // Reload playlist settings, for BG and glowing + ui_->playlist->view()->ReloadSettings(); + + +#ifndef Q_OS_DARWIN + QSettings settings; + settings.beginGroup(BehaviourSettingsPage::kSettingsGroup); + StartupBehaviour behaviour = StartupBehaviour(settings.value("startupbehaviour", Startup_Remember).toInt()); + settings.endGroup(); + bool hidden = settings_.value("hidden", false).toBool(); + + + switch (behaviour) { + case Startup_AlwaysHide: hide(); break; + case Startup_AlwaysShow: show(); break; + case Startup_Remember: + setVisible(!hidden); + break; + } + + // Force the window to show in case somehow the config has tray and window set to hide + if (hidden && !tray_icon_->IsVisible()) { + settings_.setValue("hidden", false); + show(); + } +#else // Q_OS_DARWIN + // Always show mainwindow on startup on OS X. + show(); +#endif + + QShortcut *close_window_shortcut = new QShortcut(this); + close_window_shortcut->setKey(Qt::CTRL + Qt::Key_W); + connect(close_window_shortcut, SIGNAL(activated()), SLOT(SetHiddenInTray())); + + CheckFullRescanRevisions(); + + CommandlineOptionsReceived(options); + + if (!options.contains_play_options()) LoadPlaybackStatus(); + + qLog(Debug) << "Started"; + RefreshStyleSheet(); + +} + +MainWindow::~MainWindow() { + SaveGeometry(); + delete ui_; +} + +void MainWindow::ReloadSettings() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QSettings settings; + +#ifndef Q_OS_DARWIN + + settings.beginGroup(BehaviourSettingsPage::kSettingsGroup); + bool showtrayicon = settings.value("showtrayicon", true).toBool(); + settings.endGroup(); + + //qLog(Debug) << "showtrayicon" << showtrayicon; + + tray_icon_->SetVisible(showtrayicon); + if (!showtrayicon && !isVisible()) show(); +#endif + + settings.beginGroup(PlaylistSettingsPage::kSettingsGroup); + doubleclick_addmode_ = AddBehaviour(settings.value("doubleclick_addmode", AddBehaviour_Append).toInt()); + doubleclick_playmode_ = PlayBehaviour(settings.value("doubleclick_playmode", PlayBehaviour_IfStopped).toInt()); + doubleclick_playlist_addmode_ = PlaylistAddBehaviour(settings.value("doubleclick_playlist_addmode", PlaylistAddBehaviour_Play).toInt()); + menu_playmode_ = PlayBehaviour(settings.value("menu_playmode", PlayBehaviour_IfStopped).toInt()); + settings.endGroup(); + +} + +void MainWindow::ReloadAllSettings() { + + ReloadSettings(); + + // Other settings + app_->ReloadSettings(); + app_->collection()->ReloadSettings(); + app_->player()->ReloadSettings(); + osd_->ReloadSettings(); + collection_view_->ReloadSettings(); + app_->player()->engine()->ReloadSettings(); + ui_->playlist->view()->ReloadSettings(); + +} + +void MainWindow::RefreshStyleSheet() { + setStyleSheet(styleSheet()); +} +void MainWindow::MediaStopped() { + + setWindowTitle("Strawberry Music Player"); + + ui_->action_stop->setEnabled(false); + ui_->action_stop_after_this_track->setEnabled(false); + ui_->action_play_pause->setIcon(IconLoader::Load("media-play")); + ui_->action_play_pause->setText(tr("Play")); + + ui_->action_play_pause->setEnabled(true); + + track_position_timer_->stop(); + track_slider_timer_->stop(); + ui_->track_slider->SetStopped(); + tray_icon_->SetProgress(0); + tray_icon_->SetStopped(); + +} + +void MainWindow::MediaPaused() { + + ui_->action_stop->setEnabled(true); + ui_->action_stop_after_this_track->setEnabled(true); + ui_->action_play_pause->setIcon(IconLoader::Load("media-play")); + ui_->action_play_pause->setText(tr("Play")); + + ui_->action_play_pause->setEnabled(true); + + track_position_timer_->stop(); + track_slider_timer_->stop(); + + tray_icon_->SetPaused(); + +} + +void MainWindow::MediaPlaying() { + + ui_->action_stop->setEnabled(true); + ui_->action_stop_after_this_track->setEnabled(true); + ui_->action_play_pause->setIcon(IconLoader::Load("media-pause")); + ui_->action_play_pause->setText(tr("Pause")); + + bool enable_play_pause = !(app_->player()->GetCurrentItem()->options() & PlaylistItem::PauseDisabled); + ui_->action_play_pause->setEnabled(enable_play_pause); + + bool can_seek = !(app_->player()->GetCurrentItem()->options() & PlaylistItem::SeekDisabled); + ui_->track_slider->SetCanSeek(can_seek); + + tray_icon_->SetPlaying(enable_play_pause); + + track_position_timer_->start(); + track_slider_timer_->start(); + UpdateTrackPosition(); + +} + +void MainWindow::VolumeChanged(int volume) { + ui_->action_mute->setChecked(!volume); + tray_icon_->MuteButtonStateChanged(!volume); +} + +void MainWindow::SongChanged(const Song &song) { + + setWindowTitle(song.PrettyTitleWithArtist() + " --- Strawberry Music Player"); + tray_icon_->SetProgress(0); + +} + +void MainWindow::TrackSkipped(PlaylistItemPtr item) { + // If it was a collection item then we have to increment its skipped count in the database. + + if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) { + + Song song = item->Metadata(); + const qint64 position = app_->player()->engine()->position_nanosec(); + const qint64 length = app_->player()->engine()->length_nanosec(); + const float percentage = (length == 0 ? 1 : float(position) / length); + + const qint64 seconds_left = (length - position) / kNsecPerSec; + const qint64 seconds_total = length / kNsecPerSec; + + if (((0.05 * seconds_total > 60 && percentage < 0.98) || percentage < 0.95) && seconds_left > 5) { // Never count the skip if under 5 seconds left + app_->collection_backend()->IncrementSkipCountAsync(song.id(), percentage); + } + } +} + +void MainWindow::resizeEvent(QResizeEvent*) { SaveGeometry(); } + +void MainWindow::TabSwitched() { + + //qLog(Debug) << __PRETTY_FUNCTION__ << ui_->tabs->current_index() ; + + if (ui_->tabs->current_index() > 0) + ui_->now_playing->SetEnabled(); + else + ui_->now_playing->SetDisabled(); + + SaveGeometry(); + +} + +void MainWindow::SaveGeometry() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + was_maximized_ = isMaximized(); + settings_.setValue("maximized", was_maximized_); + // Save the geometry only when mainwindow is not in maximized state + if (!was_maximized_) { + settings_.setValue("geometry", saveGeometry()); + } + settings_.setValue("splitter_state", ui_->splitter->saveState()); + settings_.setValue("current_tab", ui_->tabs->current_index()); + settings_.setValue("tab_mode", ui_->tabs->mode()); + +} + +void MainWindow::SavePlaybackStatus() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QSettings settings; + + settings.beginGroup("Player"); + settings.setValue("playback_state", app_->player()->GetState()); + if (app_->player()->GetState() == Engine::Playing || app_->player()->GetState() == Engine::Paused) { + settings.setValue("playback_position", app_->player()->engine()->position_nanosec() / kNsecPerSec); + } + else { + settings.setValue("playback_position", 0); + } + + settings.endGroup(); + +} + +void MainWindow::LoadPlaybackStatus() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QSettings settings; + + settings.beginGroup(BehaviourSettingsPage::kSettingsGroup); + bool resume_playback = settings.value("resumeplayback", false).toBool(); + settings.endGroup(); + + if (!resume_playback) return; + + settings.beginGroup("Player"); + saved_playback_state_ = static_cast (settings.value("playback_state", Engine::Empty).toInt()); + saved_playback_position_ = settings.value("playback_position", 0).toDouble(); + settings.endGroup(); + + qLog(Debug) << "playback_state" << saved_playback_state_; + qLog(Debug) << "playback_position" << saved_playback_position_; + + if (saved_playback_state_ == Engine::Empty || saved_playback_state_ == Engine::Idle) { + return; + } + + connect(app_->playlist_manager()->active(), SIGNAL(RestoreFinished()), SLOT(ResumePlayback())); + +} + +void MainWindow::ResumePlayback() { + + qLog(Debug) << "Resuming playback"; + + disconnect(app_->playlist_manager()->active(), SIGNAL(RestoreFinished()), this, SLOT(ResumePlayback())); + + if (saved_playback_state_ == Engine::Paused) { + NewClosure(app_->player(), SIGNAL(Playing()), app_->player(), SLOT(PlayPause())); + } + + app_->player()->Play(); + app_->player()->SeekTo(saved_playback_position_); + +} + +void MainWindow::PlayIndex(const QModelIndex &index) { + + if (!index.isValid()) return; + + int row = index.row(); + if (index.model() == app_->playlist_manager()->current()->proxy()) { + // The index was in the proxy model (might've been filtered), so we need + // to get the actual row in the source model. + row = app_->playlist_manager()->current()->proxy()->mapToSource(index).row(); + } + + app_->playlist_manager()->SetActiveToCurrent(); + app_->player()->PlayAt(row, Engine::Manual, true); +} + +void MainWindow::PlaylistDoubleClick(const QModelIndex &index) { + if (!index.isValid()) return; + + int row = index.row(); + if (index.model() == app_->playlist_manager()->current()->proxy()) { + // The index was in the proxy model (might've been filtered), so we need + // to get the actual row in the source model. + row = + app_->playlist_manager()->current()->proxy()->mapToSource(index).row(); + } + + QModelIndexList dummyIndexList; + + switch (doubleclick_playlist_addmode_) { + case PlaylistAddBehaviour_Play: + app_->playlist_manager()->SetActiveToCurrent(); + app_->player()->PlayAt(row, Engine::Manual, true); + break; + + case PlaylistAddBehaviour_Enqueue: + dummyIndexList.append(index); + app_->playlist_manager()->current()->queue()->ToggleTracks(dummyIndexList); + if (app_->player()->GetState() != Engine::Playing) { + app_->player()->PlayAt(app_->playlist_manager()->current()->queue()->TakeNext(), Engine::Manual, true); + } + break; + } +} + +void MainWindow::VolumeWheelEvent(int delta) { + ui_->volume->setValue(ui_->volume->value() + delta / 30); +} + +void MainWindow::ToggleShowHide() { + + if (settings_.value("hidden").toBool()) { + show(); + SetHiddenInTray(false); + } + else if (isActiveWindow()) { + hide(); + setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); + SetHiddenInTray(true); + } + else if (isMinimized()) { + hide(); + setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); + SetHiddenInTray(false); + } + else if (!isVisible()) { + show(); + activateWindow(); + } + else { + // Window is not hidden but does not have focus; bring it to front. + activateWindow(); + raise(); + } +} + +void MainWindow::StopAfterCurrent() { + app_->playlist_manager()->current()->StopAfter(app_->playlist_manager()->current()->current_row()); + emit StopAfterToggled(app_->playlist_manager()->active()->stop_after_current()); +} + +void MainWindow::closeEvent(QCloseEvent *event) { + + QSettings settings; + settings.beginGroup(BehaviourSettingsPage::kSettingsGroup); + bool keep_running = settings.value("keeprunning", false).toBool(); + settings.endGroup(); + + if (keep_running && event->spontaneous()) { + event->ignore(); + SetHiddenInTray(true); + } + else { + Exit(); + QApplication::quit(); + } +} + +void MainWindow::SetHiddenInTray(bool hidden) { + + settings_.setValue("hidden", hidden); + + // Some window managers don't remember maximized state between calls to + // hide() and show(), so we have to remember it ourself. + if (hidden) { + //was_maximized_ = isMaximized(); + hide(); + } + else { + if (was_maximized_) showMaximized(); + else show(); + } +} + +void MainWindow::FilePathChanged(const QString &path) { + settings_.setValue("file_path", path); +} + +void MainWindow::Seeked(qlonglong microseconds) { + + const int position = microseconds / kUsecPerSec; + const int length = app_->player()->GetCurrentItem()->Metadata().length_nanosec() / kNsecPerSec; + tray_icon_->SetProgress(double(position) / length * 100); + +} + +void MainWindow::UpdateTrackPosition() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // Track position in seconds + //Playlist* playlist = app_->playlist_manager()->active(); + + PlaylistItemPtr item(app_->player()->GetCurrentItem()); + const int position = std::floor(float(app_->player()->engine()->position_nanosec()) / kNsecPerSec + 0.5); + const int length = item->Metadata().length_nanosec() / kNsecPerSec; + + if (length <= 0) { + // Probably a stream that we don't know the length of + //ui_->track_slider->SetStopped(); + //tray_icon_->SetProgress(0); + return; + } + + // Update the tray icon every 10 seconds + if (position % 10 == 0) { + tray_icon_->SetProgress(double(position) / length * 100); + } +} + +void MainWindow::UpdateTrackSliderPosition() { + PlaylistItemPtr item(app_->player()->GetCurrentItem()); + + const int slider_position = std::floor(float(app_->player()->engine()->position_nanosec()) / kNsecPerMsec); + const int slider_length =app_->player()->engine()->length_nanosec() / kNsecPerMsec; + + // Update the slider + ui_->track_slider->SetValue(slider_position, slider_length); +} + +void MainWindow::ApplyAddBehaviour(MainWindow::AddBehaviour b, MimeData* data) const { + + switch (b) { + case AddBehaviour_Append: + data->clear_first_ = false; + data->enqueue_now_ = false; + break; + + case AddBehaviour_Enqueue: + data->clear_first_ = false; + data->enqueue_now_ = true; + break; + + case AddBehaviour_Load: + data->clear_first_ = true; + data->enqueue_now_ = false; + break; + + case AddBehaviour_OpenInNew: + data->open_in_new_playlist_ = true; + break; + } +} + +void MainWindow::ApplyPlayBehaviour(MainWindow::PlayBehaviour b, MimeData* data) const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + switch (b) { + case PlayBehaviour_Always: + data->play_now_ = true; + break; + + case PlayBehaviour_Never: + data->play_now_ = false; + break; + + case PlayBehaviour_IfStopped: + data->play_now_ = !(app_->player()->GetState() == Engine::Playing); + break; + } +} + +void MainWindow::AddToPlaylist(QMimeData* data) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (!data) return; + + if (MimeData *mime_data = qobject_cast(data)) { + // Should we replace the flags with the user's preference? + if (mime_data->override_user_settings_) { + // Do nothing + } + else if (mime_data->from_doubleclick_) { + ApplyAddBehaviour(doubleclick_addmode_, mime_data); + ApplyPlayBehaviour(doubleclick_playmode_, mime_data); + } + else { + ApplyPlayBehaviour(menu_playmode_, mime_data); + } + + // Should we create a new playlist for the songs? + if (mime_data->open_in_new_playlist_) { + app_->playlist_manager()->New(mime_data->get_name_for_new_playlist()); + } + } + app_->playlist_manager()->current()->dropMimeData(data, Qt::CopyAction, -1, 0, QModelIndex()); + delete data; +} + +void MainWindow::AddToPlaylist(QAction* action) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + int destination = action->data().toInt(); + PlaylistItemList items; + + // get the selected playlist items + for (const QModelIndex& index : ui_->playlist->view()->selectionModel()->selection().indexes()) { + if (index.column() != 0) continue; + int row = app_->playlist_manager()->current()->proxy()->mapToSource(index).row(); + items << app_->playlist_manager()->current()->item_at(row); + } + + SongList songs; + for (PlaylistItemPtr item : items) { + songs << item->Metadata(); + } + + // we're creating a new playlist + if (destination == -1) { + // save the current playlist to reactivate it + int current_id = app_->playlist_manager()->current_id(); + // get the name from selection + app_->playlist_manager()->New(app_->playlist_manager()->GetNameForNewPlaylist(songs)); + if (app_->playlist_manager()->current()->id() != current_id) { + //I'm sure the new playlist was created and is selected, so I can just insert items + app_->playlist_manager()->current()->InsertItems(items); + // set back the current playlist + app_->playlist_manager()->SetCurrentPlaylist(current_id); + } + } + else { + // we're inserting in a existing playlist + app_->playlist_manager()->playlist(destination)->InsertItems(items); + } + +} + +void MainWindow::PlaylistRightClick(const QPoint &global_pos, const QModelIndex &index) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(index); + + playlist_menu_index_ = source_index; + + // Is this song currently playing? + if (app_->playlist_manager()->current()->current_row() == source_index.row() && app_->player()->GetState() == Engine::Playing) { + playlist_play_pause_->setText(tr("Pause")); + playlist_play_pause_->setIcon(IconLoader::Load("media-pause")); + } + else { + playlist_play_pause_->setText(tr("Play")); + playlist_play_pause_->setIcon(IconLoader::Load("media-play")); + } + + // Are we allowed to pause? + if (index.isValid()) { + playlist_play_pause_->setEnabled(app_->playlist_manager()->current()->current_row() != source_index.row() || !(app_->playlist_manager()->current()->item_at(source_index.row())->options() & PlaylistItem::PauseDisabled)); + } + else { + playlist_play_pause_->setEnabled(false); + } + + playlist_stop_after_->setEnabled(index.isValid()); + + // Are any of the selected songs editable or queued? + QModelIndexList selection = ui_->playlist->view()->selectionModel()->selection().indexes(); + bool cue_selected = false; + int all = 0; + int selected = 0; + int editable = 0; + int in_queue = 0; + int not_in_queue = 0; + int in_skipped = 0; + int not_in_skipped = 0; + + for (const QModelIndex &index : selection) { + + all++; + + if (index.column() != 0) continue; + + selected++; + + PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(index.row()); + + if (item->Metadata().has_cue()) { + cue_selected = true; + } + else if (item->Metadata().IsEditable()) { + editable++; + } + + if (index.data(Playlist::Role_QueuePosition).toInt() == -1) not_in_queue++; + else in_queue++; + + if (item->GetShouldSkip()) in_skipped++; + else not_in_skipped++; + } + + // this is available when we have one or many files and at least one of those is not CUE related + ui_->action_edit_track->setEnabled(editable); + ui_->action_edit_track->setVisible(editable); + ui_->action_auto_complete_tags->setEnabled(editable); + ui_->action_auto_complete_tags->setVisible(editable); + // the rest of the read / write actions work only when there are no CUEs involved + if (cue_selected) editable = 0; + + if (selected > 0) playlist_open_in_browser_->setVisible(true); + + bool track_column = (index.column() == Playlist::Column_Track); + ui_->action_renumber_tracks->setVisible(editable >= 2 && track_column); + ui_->action_selection_set_value->setVisible(editable >= 2 && !track_column); + ui_->action_edit_value->setVisible(editable); + ui_->action_remove_from_playlist->setEnabled(!selection.isEmpty()); + + playlist_show_in_collection_->setVisible(false); +#ifdef HAVE_GSTREAMER + playlist_copy_to_collection_->setVisible(false); + playlist_move_to_collection_->setVisible(false); + //playlist_organise_->setVisible(false); + playlist_copy_to_device_->setVisible(false); +#endif + playlist_open_in_browser_->setVisible(false); + + //qLog(Debug) << "selected" << selected; + //qLog(Debug) << "in_queue" << in_queue << "not_in_queue" << not_in_queue; + //qLog(Debug) << "in_skipped" << in_skipped << "not_in_skipped" << not_in_skipped; + + if (selected < 1) { + playlist_queue_->setVisible(false); + playlist_skip_->setVisible(false); + } + else { + playlist_queue_->setVisible(true); + playlist_skip_->setVisible(true); + if (in_queue == 1 && not_in_queue == 0) playlist_queue_->setText(tr("Dequeue track")); + else if (in_queue > 1 && not_in_queue == 0) playlist_queue_->setText(tr("Dequeue selected tracks")); + else if (in_queue == 0 && not_in_queue == 1) playlist_queue_->setText(tr("Queue track")); + else if (in_queue == 0 && not_in_queue > 1) playlist_queue_->setText(tr("Queue selected tracks")); + else playlist_queue_->setText(tr("Toggle queue status")); + + if (in_skipped == 1 && not_in_skipped == 0) playlist_skip_->setText(tr("Unskip track")); + else if (in_skipped > 1 && not_in_skipped == 0) playlist_skip_->setText(tr("Unskip selected tracks")); + else if (in_skipped == 0 && not_in_skipped == 1) playlist_skip_->setText(tr("Skip track")); + else if (in_skipped == 0 && not_in_skipped > 1) playlist_skip_->setText(tr("Skip selected tracks")); + else playlist_skip_->setText(tr("Toggle skip status")); + } + + if (not_in_queue == 0) playlist_queue_->setIcon(IconLoader::Load("go-previous")); + else playlist_queue_->setIcon(IconLoader::Load("go-next")); + + if (in_skipped < selected) playlist_skip_->setIcon(IconLoader::Load("media-forward")); + else playlist_skip_->setIcon(IconLoader::Load("media-play")); + + + if (!index.isValid()) { + ui_->action_selection_set_value->setVisible(false); + ui_->action_edit_value->setVisible(false); + } + else { + + + Playlist::Column column = (Playlist::Column)index.column(); + bool column_is_editable = Playlist::column_is_editable(column) && editable; + + ui_->action_selection_set_value->setVisible(ui_->action_selection_set_value->isVisible() && column_is_editable); + ui_->action_edit_value->setVisible(ui_->action_edit_value->isVisible() && column_is_editable); + + QString column_name = Playlist::column_name(column); + QString column_value =app_->playlist_manager()->current()->data(source_index).toString(); + if (column_value.length() > 25) column_value = column_value.left(25) + "..."; + + ui_->action_selection_set_value->setText(tr("Set %1 to \"%2\"...").arg(column_name.toLower()).arg(column_value)); + ui_->action_edit_value->setText(tr("Edit tag \"%1\"...").arg(column_name)); + + // Is it a collection item? + PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row()); + if (item->IsLocalCollectionItem() && item->Metadata().id() != -1) { + //playlist_organise_->setVisible(editable); + playlist_show_in_collection_->setVisible(editable); + playlist_open_in_browser_->setVisible(true); + } +#ifdef HAVE_GSTREAMER + else { + playlist_copy_to_collection_->setVisible(editable); + playlist_move_to_collection_->setVisible(editable); + } +#endif + +#ifdef HAVE_GSTREAMER + playlist_copy_to_device_->setVisible(editable); +#endif + + // Remove old item actions, if any. + for (QAction *action : playlistitem_actions_) { + playlist_menu_->removeAction(action); + } + + // Get the new item actions, and add them + playlistitem_actions_ = item->actions(); + playlistitem_actions_separator_->setVisible(!playlistitem_actions_.isEmpty()); + playlist_menu_->insertActions(playlistitem_actions_separator_,playlistitem_actions_); + } + + //if it isn't the first time we right click, we need to remove the menu previously created + if (playlist_add_to_another_ != nullptr) { + playlist_menu_->removeAction(playlist_add_to_another_); + delete playlist_add_to_another_; + } + + // create the playlist submenu + QMenu *add_to_another_menu = new QMenu(tr("Add to another playlist"), this); + add_to_another_menu->setIcon(IconLoader::Load("list-add")); + + for (const PlaylistBackend::Playlist& playlist : app_->playlist_backend()->GetAllOpenPlaylists()) { + // don't add the current playlist + if (playlist.id != app_->playlist_manager()->current()->id()) { + QAction* existing_playlist = new QAction(this); + existing_playlist->setText(playlist.name); + existing_playlist->setData(playlist.id); + add_to_another_menu->addAction(existing_playlist); + } + } + + add_to_another_menu->addSeparator(); + // add to a new playlist + QAction* new_playlist = new QAction(this); + new_playlist->setText(tr("New playlist")); + new_playlist->setData(-1); // fake id + add_to_another_menu->addAction(new_playlist); + playlist_add_to_another_ = playlist_menu_->insertMenu(ui_->action_remove_from_playlist, add_to_another_menu); + + connect(add_to_another_menu, SIGNAL(triggered(QAction*)), SLOT(AddToPlaylist(QAction*))); + + playlist_menu_->popup(global_pos); +} + +void MainWindow::PlaylistPlay() { + if (app_->playlist_manager()->current()->current_row() ==playlist_menu_index_.row()) { + app_->player()->PlayPause(); + } + else { + PlayIndex(playlist_menu_index_); + } +} + +void MainWindow::PlaylistStopAfter() { + app_->playlist_manager()->current()->StopAfter(playlist_menu_index_.row()); +} + +void MainWindow::EditTracks() { + SongList songs; + PlaylistItemList items; + + for (const QModelIndex& index : ui_->playlist->view()->selectionModel()->selection().indexes()) { + if (index.column() != 0) continue; + int row =app_->playlist_manager()->current()->proxy()->mapToSource(index).row(); + PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(row)); + Song song = item->Metadata(); + + if (song.IsEditable()) { + songs << song; + items << item; + } + } + + //EnsureEditTagDialogCreated(); + edit_tag_dialog_->SetSongs(songs, items); + edit_tag_dialog_->show(); +} + +void MainWindow::EditTagDialogAccepted() { + for (PlaylistItemPtr item : edit_tag_dialog_->playlist_items()) { + item->Reload(); + } + + // This is really lame but we don't know what rows have changed + ui_->playlist->view()->update(); + + app_->playlist_manager()->current()->Save(); +} + +void MainWindow::RenumberTracks() { + QModelIndexList indexes =ui_->playlist->view()->selectionModel()->selection().indexes(); + int track = 1; + + // Get the index list in order + qStableSort(indexes); + + // if first selected song has a track number set, start from that offset + if (!indexes.isEmpty()) { + const Song first_song = app_->playlist_manager()->current()->item_at(indexes[0].row())->Metadata(); + + if (first_song.track() > 0) track = first_song.track(); + } + + for (const QModelIndex& index : indexes) { + if (index.column() != 0) continue; + + const QModelIndex source_index =app_->playlist_manager()->current()->proxy()->mapToSource(index); + int row = source_index.row(); + Song song = app_->playlist_manager()->current()->item_at(row)->Metadata(); + + if (song.IsEditable()) { + song.set_track(track); + + TagReaderReply* reply =TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song); + + NewClosure(reply, SIGNAL(Finished(bool)), this, SLOT(SongSaveComplete(TagReaderReply*, QPersistentModelIndex)),reply, QPersistentModelIndex(source_index)); + } + track++; + } +} + +void MainWindow::SongSaveComplete(TagReaderReply* reply,const QPersistentModelIndex& index) { + if (reply->is_successful() && index.isValid()) { + app_->playlist_manager()->current()->ReloadItems(QList()<< index.row()); + } + reply->deleteLater(); +} + +void MainWindow::SelectionSetValue() { + Playlist::Column column = (Playlist::Column)playlist_menu_index_.column(); + QVariant column_value =app_->playlist_manager()->current()->data(playlist_menu_index_); + + QModelIndexList indexes =ui_->playlist->view()->selectionModel()->selection().indexes(); + + for (const QModelIndex& index : indexes) { + if (index.column() != 0) continue; + + const QModelIndex source_index =app_->playlist_manager()->current()->proxy()->mapToSource(index); + int row = source_index.row(); + Song song = app_->playlist_manager()->current()->item_at(row)->Metadata(); + + if (Playlist::set_column_value(song, column, column_value)) { + TagReaderReply* reply =TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song); + + NewClosure(reply, SIGNAL(Finished(bool)), this, SLOT(SongSaveComplete(TagReaderReply*, QPersistentModelIndex)),reply, QPersistentModelIndex(source_index)); + } + } +} + +void MainWindow::EditValue() { + QModelIndex current = ui_->playlist->view()->currentIndex(); + + if (!current.isValid()) return; + + // Edit the last column that was right-clicked on. If nothing's ever been + // right clicked then look for the first editable column. + int column = playlist_menu_index_.column(); + if (column == -1) { + for (int i = 0; i < ui_->playlist->view()->model()->columnCount(); ++i) { + if (ui_->playlist->view()->isColumnHidden(i)) continue; + if (!Playlist::column_is_editable(Playlist::Column(i))) continue; + column = i; + break; + } + } + + ui_->playlist->view()->edit(current.sibling(current.row(), column)); +} + +void MainWindow::AddFile() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // Last used directory + QString directory =settings_.value("add_media_path", QDir::currentPath()).toString(); + + PlaylistParser parser(app_->collection_backend()); + + // Show dialog + QStringList file_names = QFileDialog::getOpenFileNames(this, tr("Add file"), directory,QString("%1 (%2);;%3;;%4").arg(tr("Music"), FileView::kFileFilter,parser.filters(),tr(kAllFilesFilterSpec))); + + if (file_names.isEmpty()) return; + + // Save last used directory + settings_.setValue("add_media_path", file_names[0]); + + // Convert to URLs + QList urls; + for (const QString& path : file_names) { + urls << QUrl::fromLocalFile(QFileInfo(path).canonicalFilePath()); + } + + MimeData* data = new MimeData; + data->setUrls(urls); + AddToPlaylist(data); +} + +void MainWindow::AddFolder() { + // Last used directory + QString directory =settings_.value("add_folder_path", QDir::currentPath()).toString(); + + // Show dialog + directory =QFileDialog::getExistingDirectory(this, tr("Add folder"), directory); + if (directory.isEmpty()) return; + + // Save last used directory + settings_.setValue("add_folder_path", directory); + + // Add media + MimeData* data = new MimeData; + data->setUrls(QList() << QUrl::fromLocalFile(QFileInfo(directory).canonicalFilePath())); + AddToPlaylist(data); +} + +void MainWindow::AddCDTracks() { + MimeData* data = new MimeData; + // We are putting empty data, but we specify cdda mimetype to indicate that + // we want to load audio cd tracks + data->open_in_new_playlist_ = true; + data->setData(Playlist::kCddaMimeType, QByteArray()); + AddToPlaylist(data); +} + +void MainWindow::ShowInCollection() { + + // Show the first valid selected track artist/album in CollectionView + QModelIndexList proxy_indexes =ui_->playlist->view()->selectionModel()->selectedRows(); + SongList songs; + + for (const QModelIndex& proxy_index : proxy_indexes) { + QModelIndex index =app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); + if (app_->playlist_manager()->current()->item_at(index.row())->IsLocalCollectionItem()) { + songs << app_->playlist_manager()->current()->item_at(index.row())->Metadata(); + break; + } + } + QString search; + if (!songs.isEmpty()) { + search ="artist:" + songs.first().artist() + " album:" + songs.first().album(); + } + collection_view_->filter()->ShowInCollection(search); +} + +void MainWindow::PlaylistRemoveCurrent() { + ui_->playlist->view()->RemoveSelected(false); +} + +void MainWindow::PlaylistEditFinished(const QModelIndex& index) { + if (index == playlist_menu_index_) SelectionSetValue(); +} + +void MainWindow::CommandlineOptionsReceived(const QString& string_options) { + + CommandlineOptions options; + options.Load(string_options.toLatin1()); + + if (options.is_empty()) { + show(); + activateWindow(); + } + else + CommandlineOptionsReceived(options); +} + +void MainWindow::CommandlineOptionsReceived(const CommandlineOptions& options) { + switch (options.player_action()) { + case CommandlineOptions::Player_Play: + if (options.urls().empty()) { + app_->player()->Play(); + } + break; + case CommandlineOptions::Player_PlayPause: + app_->player()->PlayPause(); + break; + case CommandlineOptions::Player_Pause: + app_->player()->Pause(); + break; + case CommandlineOptions::Player_Stop: + app_->player()->Stop(); + break; + case CommandlineOptions::Player_StopAfterCurrent: + app_->player()->StopAfterCurrent(); + break; + case CommandlineOptions::Player_Previous: + app_->player()->Previous(); + break; + case CommandlineOptions::Player_Next: + app_->player()->Next(); + break; + case CommandlineOptions::Player_RestartOrPrevious: + app_->player()->RestartOrPrevious(); + break; + + case CommandlineOptions::Player_None: + break; + } + + if (!options.urls().empty()) { + MimeData* data = new MimeData; + data->setUrls(options.urls()); + // Behaviour depends on command line options, so set it here + data->override_user_settings_ = true; + + if (options.player_action() == CommandlineOptions::Player_Play) data->play_now_ = true; + else ApplyPlayBehaviour(doubleclick_playmode_, data); + + switch (options.url_list_action()) { + case CommandlineOptions::UrlList_Load: + data->clear_first_ = true; + break; + case CommandlineOptions::UrlList_Append: + // Nothing to do + break; + case CommandlineOptions::UrlList_None: + ApplyAddBehaviour(doubleclick_addmode_, data); + break; + case CommandlineOptions::UrlList_CreateNew: + data->name_for_new_playlist_ = options.playlist_name(); + ApplyAddBehaviour(AddBehaviour_OpenInNew, data); + break; + } + + AddToPlaylist(data); + } + + if (options.set_volume() != -1) app_->player()->SetVolume(options.set_volume()); + + if (options.volume_modifier() != 0) { + app_->player()->SetVolume(app_->player()->GetVolume() +options.volume_modifier()); + } + + if (options.seek_to() != -1) { + app_->player()->SeekTo(options.seek_to()); + } + else if (options.seek_by() != 0) { + app_->player()->SeekTo(app_->player()->engine()->position_nanosec() /kNsecPerSec +options.seek_by()); + } + + if (options.play_track_at() != -1) app_->player()->PlayAt(options.play_track_at(), Engine::Manual, true); + + if (options.show_osd()) app_->player()->ShowOSD(); + + if (options.toggle_pretty_osd()) app_->player()->TogglePrettyOSD(); +} + +void MainWindow::ForceShowOSD(const Song& song, const bool toggle) { + if (toggle) { + osd_->SetPrettyOSDToggleMode(toggle); + } + osd_->ReshowCurrentSong(); +} + +void MainWindow::Activate() { + show(); +} + +bool MainWindow::LoadUrl(const QString& url) { + + if (!QFile::exists(url)) return false; + + MimeData* data = new MimeData; + data->setUrls(QList() << QUrl::fromLocalFile(url)); + AddToPlaylist(data); + + return true; +} + +void MainWindow::CheckForUpdates() { +#if defined(Q_OS_DARWIN) + mac::CheckForUpdates(); +#endif +} + +void MainWindow::PlaylistUndoRedoChanged(QAction* undo, QAction* redo) { + + playlist_menu_->insertAction(playlist_undoredo_, undo); + playlist_menu_->insertAction(playlist_undoredo_, redo); +} + +#ifdef HAVE_GSTREAMER +void MainWindow::AddFilesToTranscoder() { + + QStringList filenames; + + for (const QModelIndex& index : ui_->playlist->view()->selectionModel()->selection().indexes()) { + if (index.column() != 0) continue; + int row =app_->playlist_manager()->current()->proxy()->mapToSource(index).row(); + PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(row)); + Song song = item->Metadata(); + filenames << song.url().toLocalFile(); + } + + transcode_dialog_->SetFilenames(filenames); + + ShowTranscodeDialog(); +} +#endif + +void MainWindow::ShowCollectionConfig() { + //EnsureSettingsDialogCreated(); + settings_dialog_->OpenAtPage(SettingsDialog::Page_Collection); +} + +void MainWindow::TaskCountChanged(int count) { + if (count == 0) { + ui_->status_bar_stack->setCurrentWidget(ui_->playlist_summary_page); + } + else { + ui_->status_bar_stack->setCurrentWidget(ui_->multi_loading_indicator); + } +} + +void MainWindow::PlayingWidgetPositionChanged() { + + ui_->status_bar->setParent(ui_->centralWidget); + //ui_->status_bar->setParent(ui_->player_controls_container); + + ui_->status_bar->parentWidget()->layout()->addWidget(ui_->status_bar); + ui_->status_bar->show(); +} + +#ifdef HAVE_GSTREAMER +void MainWindow::CopyFilesToCollection(const QList& urls) { + organise_dialog_->SetDestinationModel(app_->collection_model()->directory_model()); + organise_dialog_->SetUrls(urls); + organise_dialog_->SetCopy(true); + organise_dialog_->show(); +} + +void MainWindow::MoveFilesToCollection(const QList& urls) { + organise_dialog_->SetDestinationModel(app_->collection_model()->directory_model()); + organise_dialog_->SetUrls(urls); + organise_dialog_->SetCopy(false); + organise_dialog_->show(); +} + +void MainWindow::CopyFilesToDevice(const QList& urls) { + organise_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true); + organise_dialog_->SetCopy(true); + if (organise_dialog_->SetUrls(urls)) + organise_dialog_->show(); + else { + QMessageBox::warning(this, tr("Error"), + tr("None of the selected songs were suitable for copying to a device")); + } +} +#endif + +void MainWindow::EditFileTags(const QList& urls) { + //EnsureEditTagDialogCreated(); + + SongList songs; + for (const QUrl& url : urls) { + Song song; + song.set_url(url); + song.set_valid(true); + song.set_filetype(Song::Type_Mpeg); + songs << song; + } + + edit_tag_dialog_->SetSongs(songs); + edit_tag_dialog_->show(); +} + +#ifdef HAVE_GSTREAMER +void MainWindow::PlaylistCopyToCollection() { + PlaylistOrganiseSelected(true); +} + +void MainWindow::PlaylistMoveToCollection() { + PlaylistOrganiseSelected(false); +} + +void MainWindow::PlaylistOrganiseSelected(bool copy) { + + QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows(); + SongList songs; + + for (const QModelIndex& proxy_index : proxy_indexes) { + QModelIndex index =app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); + + songs << app_->playlist_manager()->current()->item_at(index.row())->Metadata(); + } + + organise_dialog_->SetDestinationModel(app_->collection_model()->directory_model()); + organise_dialog_->SetSongs(songs); + organise_dialog_->SetCopy(copy); + organise_dialog_->show(); +} +#endif + +#if 0 +void MainWindow::PlaylistDelete() { + + // Note: copied from CollectionView::Delete + + if (QMessageBox::warning(this, tr("Delete files"), + tr("These files will be deleted from disk, are you sure you want to continue?"), + QMessageBox::Yes, QMessageBox::Cancel) != QMessageBox::Yes) + return; + + std::shared_ptr storage(new FilesystemMusicStorage("/")); + + // Get selected songs + SongList selected_songs; + QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows(); + for (const QModelIndex& proxy_index : proxy_indexes) { + QModelIndex index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); + selected_songs << app_->playlist_manager()->current()->item_at(index.row())->Metadata(); + } + + if (app_->player()->GetState() == Engine::Playing) { + if (app_->playlist_manager()->current()->rowCount() == selected_songs.length()) { + app_->player()->Stop(); + } + else { + for (Song x : selected_songs) { + if (x == app_->player()->GetCurrentItem()->Metadata()) { + app_->player()->Next(); + } + } + } + } + + ui_->playlist->view()->RemoveSelected(true); + + DeleteFiles* delete_files = new DeleteFiles(app_->task_manager(), storage); + connect(delete_files, SIGNAL(Finished(SongList)), SLOT(DeleteFinished(SongList))); + delete_files->Start(selected_songs); +} +#endif + +void MainWindow::PlaylistOpenInBrowser() { + + QList urls; + QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows(); + + for (const QModelIndex& proxy_index : proxy_indexes) { + const QModelIndex index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); + urls << QUrl(index.sibling(index.row(), Playlist::Column_Filename).data().toString()); + } + + Utilities::OpenInFileBrowser(urls); +} + +#if 0 +void MainWindow::DeleteFinished(const SongList& songs_with_errors) { + if (songs_with_errors.isEmpty()) return; + + OrganiseErrorDialog* dialog = new OrganiseErrorDialog(this); + dialog->Show(OrganiseErrorDialog::Type_Delete, songs_with_errors); + // It deletes itself when the user closes it +} +#endif + +void MainWindow::PlaylistQueue() { + + QModelIndexList indexes; + for (const QModelIndex& proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) { + indexes << app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); + } + + app_->playlist_manager()->current()->queue()->ToggleTracks(indexes); +} + +void MainWindow::PlaylistSkip() { + + QModelIndexList indexes; + + for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) { + indexes << app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); + } + + app_->playlist_manager()->current()->SkipTracks(indexes); + +} + +#ifdef HAVE_GSTREAMER +void MainWindow::PlaylistCopyToDevice() { + + QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows(); + SongList songs; + + for (const QModelIndex& proxy_index : proxy_indexes) { + QModelIndex index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); + + songs << app_->playlist_manager()->current()->item_at(index.row())->Metadata(); + } + + organise_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true); + organise_dialog_->SetCopy(true); + if (organise_dialog_->SetSongs(songs)) + organise_dialog_->show(); + else { + QMessageBox::warning(this, tr("Error"), + tr("None of the selected songs were suitable for copying to a device")); + } +} +#endif + +void MainWindow::ChangeCollectionQueryMode(QAction* action) { + if (action == collection_show_duplicates_) { + collection_view_->filter()->SetQueryMode(QueryOptions::QueryMode_Duplicates); + } + else if (action == collection_show_untagged_) { + collection_view_->filter()->SetQueryMode(QueryOptions::QueryMode_Untagged); + } + else { + collection_view_->filter()->SetQueryMode(QueryOptions::QueryMode_All); + } +} + +void MainWindow::ShowCoverManager() { + + //if (!cover_manager_) { + //cover_manager_.reset(new AlbumCoverManager(app_, app_->collection_backend())); + //cover_manager_->Init(); + + // Cover manager connections + //connect(cover_manager_.get(), SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); + //} + + cover_manager_->show(); + +} + +SettingsDialog* MainWindow::CreateSettingsDialog() { + + SettingsDialog* settings_dialog = new SettingsDialog(app_); + settings_dialog->SetGlobalShortcutManager(global_shortcuts_); + //settings_dialog->SetSongInfoView(song_info_view_); + + // Settings + connect(settings_dialog, SIGNAL(accepted()), SLOT(ReloadAllSettings())); + + // Allows custom notification preview + connect(settings_dialog, SIGNAL(NotificationPreview(OSD::Behaviour, QString, QString)), SLOT(HandleNotificationPreview(OSD::Behaviour, QString, QString))); + return settings_dialog; + +} + +void MainWindow::EnsureSettingsDialogCreated() { + + //if (settings_dialog_) return; + + //settings_dialog_.reset(new SettingsDialog(app_)); + //settings_dialog_->SetGlobalShortcutManager(global_shortcuts_); + //settings_dialog_->SetSongInfoView(song_info_view_); + + // Settings + //connect(settings_dialog_.get(), SIGNAL(accepted()), SLOT(ReloadAllSettings())); + + // Allows custom notification preview + //connect(settings_dialog_.get(), SIGNAL(NotificationPreview(OSD::Behaviour,QString,QString)), SLOT(HandleNotificationPreview(OSD::Behaviour, QString, QString))); + +} + +void MainWindow::OpenSettingsDialog() { + + EnsureSettingsDialogCreated(); + settings_dialog_->show(); + +} + +void MainWindow::OpenSettingsDialogAtPage(SettingsDialog::Page page) { + EnsureSettingsDialogCreated(); + settings_dialog_->OpenAtPage(page); +} + +EditTagDialog* MainWindow::CreateEditTagDialog() { + + EditTagDialog *edit_tag_dialog = new EditTagDialog(app_); + connect(edit_tag_dialog, SIGNAL(accepted()), SLOT(EditTagDialogAccepted())); + connect(edit_tag_dialog, SIGNAL(Error(QString)), SLOT(ShowErrorDialog(QString))); + return edit_tag_dialog; + +} + +void MainWindow::EnsureEditTagDialogCreated() { + + //if (edit_tag_dialog_) return; + + //edit_tag_dialog_.reset(new EditTagDialog(app_)); + //connect(edit_tag_dialog_.get(), SIGNAL(accepted()), SLOT(EditTagDialogAccepted())); + //connect(edit_tag_dialog_.get(), SIGNAL(Error(QString)), SLOT(ShowErrorDialog(QString))); + +} + +void MainWindow::ShowAboutDialog() { + + //if (!about_dialog_) { + //about_dialog_.reset(new About); + //} + + about_dialog_->show(); + +} + +#ifdef HAVE_GSTREAMER +void MainWindow::ShowTranscodeDialog() { + + //if (!transcode_dialog_) { + // transcode_dialog_.reset(new TranscodeDialog); + //} + transcode_dialog_->show(); + +} +#endif + +void MainWindow::ShowErrorDialog(const QString& message) { + //if (!error_dialog_) { + // error_dialog_.reset(new ErrorDialog); + //} + error_dialog_->ShowMessage(message); +} + +void MainWindow::CheckFullRescanRevisions() { + + int from = app_->database()->startup_schema_version(); + int to = app_->database()->current_schema_version(); + + // if we're restoring DB from scratch or nothing has + // changed, do nothing + if (from == 0 || from == to) { + return; + } + + // collect all reasons + QSet reasons; + for (int i = from; i <= to; i++) { + QString reason = app_->collection()->full_rescan_reason(i); + + if (!reason.isEmpty()) { + reasons.insert(reason); + } + } + + // if we have any... + if (!reasons.isEmpty()) { + QString message = tr("The version of Strawberry you've just updated to requires a full collection rescan because of the new features listed below:") + "
    "; + for(const QString& reason : reasons) { + message += ("
  • " + reason + "
  • "); + } + message += "
" + tr("Would you like to run a full rescan right now?"); + + if(QMessageBox::question(this, tr("Collection rescan notice"), message, QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { + app_->collection()->FullScan(); + } + } +} + +void MainWindow::ShowQueueManager() { + //if (!queue_manager_) { + //queue_manager_.reset(new QueueManager); + //queue_manager_->SetPlaylistManager(app_->playlist_manager()); + //} + queue_manager_->show(); +} + +#if 0 +void MainWindow::ConnectInfoView(SongInfoBase *view) { + + QObject::connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), view, SLOT(SongChanged(Song))); + QObject::connect(app_->player(), SIGNAL(PlaylistFinished()), view, SLOT(SongFinished())); + QObject::connect(app_->player(), SIGNAL(Stopped()), view, SLOT(SongFinished())); + + QObject::connect(view, SIGNAL(ShowSettingsDialog()), SLOT(ShowSongInfoConfig())); + +} +#endif + +void MainWindow::ConnectStatusView(StatusView *statusview) { + + QObject::connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), statusview, SLOT(SongChanged(Song))); + QObject::connect(app_->player(), SIGNAL(PlaylistFinished()), statusview, SLOT(SongFinished())); + QObject::connect(app_->player(), SIGNAL(Stopped()), statusview, SLOT(SongFinished())); + + //QObject::connect(statusview, SIGNAL(ShowSettingsDialog()), SLOT(ShowSongInfoConfig())); + +} + +#if 0 +void MainWindow::ShowSongInfoConfig() { + OpenSettingsDialogAtPage(SettingsDialog::Page_SongInformation); +} +#endif + +void MainWindow::PlaylistViewSelectionModelChanged() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + connect(ui_->playlist->view()->selectionModel(),SIGNAL(currentChanged(QModelIndex, QModelIndex)), SLOT(PlaylistCurrentChanged(QModelIndex))); +} + +void MainWindow::PlaylistCurrentChanged(const QModelIndex& proxy_current) { + const QModelIndex source_current =app_->playlist_manager()->current()->proxy()->mapToSource(proxy_current); + + // If the user moves the current index using the keyboard and then presses + // F2, we don't want that editing the last column that was right clicked on. + if (source_current != playlist_menu_index_) playlist_menu_index_ = QModelIndex(); +} + +void MainWindow::Raise() { + show(); + activateWindow(); +} + +#ifdef Q_OS_WIN32 +bool MainWindow::winEvent(MSG* msg, long*) { + thumbbar_->HandleWinEvent(msg); + return false; +} +#endif // Q_OS_WIN32 + +void MainWindow::Exit() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + SavePlaybackStatus(); + //settings_.setValue("show_sidebar", ui_->action_toggle_show_sidebar->isChecked()); + + if (app_->player()->engine()->is_fadeout_enabled()) { + // To shut down the application when fadeout will be finished + connect(app_->player()->engine(), SIGNAL(FadeoutFinishedSignal()), qApp, SLOT(quit())); + if (app_->player()->GetState() == Engine::Playing) { + app_->player()->Stop(); + hide(); + tray_icon_->SetVisible(false); + return; // Don't quit the application now: wait for the fadeout finished signal + } + } + +#ifdef HAVE_GSTREAMER + gst_deinit(); +#endif + + qApp->quit(); + +} + +#ifdef HAVE_GSTREAMER +void MainWindow::AutoCompleteTags() { + + // Create the tag fetching stuff if it hasn't been already + if (!tag_fetcher_) { + tag_fetcher_.reset(new TagFetcher); + track_selection_dialog_.reset(new TrackSelectionDialog); + track_selection_dialog_->set_save_on_close(true); + + connect(tag_fetcher_.get(), SIGNAL(ResultAvailable(Song, SongList)), track_selection_dialog_.get(), SLOT(FetchTagFinished(Song, SongList)), Qt::QueuedConnection); + connect(tag_fetcher_.get(), SIGNAL(Progress(Song, QString)), track_selection_dialog_.get(), SLOT(FetchTagProgress(Song,QString))); + connect(track_selection_dialog_.get(), SIGNAL(accepted()), SLOT(AutoCompleteTagsAccepted())); + connect(track_selection_dialog_.get(), SIGNAL(finished(int)), tag_fetcher_.get(), SLOT(Cancel())); + connect(track_selection_dialog_.get(), SIGNAL(Error(QString)), SLOT(ShowErrorDialog(QString))); + } + + // Get the selected songs and start fetching tags for them + SongList songs; + autocomplete_tag_items_.clear(); + for (const QModelIndex& index : ui_->playlist->view()->selectionModel()->selection().indexes()) { + if (index.column() != 0) continue; + int row = app_->playlist_manager()->current()->proxy()->mapToSource(index).row(); + PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(row)); + Song song = item->Metadata(); + + if (song.IsEditable()) { + songs << song; + autocomplete_tag_items_ << item; + } + } + + track_selection_dialog_->Init(songs); + tag_fetcher_->StartFetch(songs); + + track_selection_dialog_->show(); +} + +void MainWindow::AutoCompleteTagsAccepted() { + + for (PlaylistItemPtr item : autocomplete_tag_items_) { + item->Reload(); + } + + // This is really lame but we don't know what rows have changed + ui_->playlist->view()->update(); +} +#endif + +void MainWindow::HandleNotificationPreview(OSD::Behaviour type, QString line1, QString line2) { + + if (!app_->playlist_manager()->current()->GetAllSongs().isEmpty()) { + // Show a preview notification for the first song in the current playlist + osd_->ShowPreview(type, line1, line2, app_->playlist_manager()->current()->GetAllSongs().first()); + } + else { + qLog(Debug) << "The current playlist is empty, showing a fake song"; + // Create a fake song + Song fake; + fake.Init("Title", "Artist", "Album", 123); + fake.set_genre("Classical"); + fake.set_composer("Anonymous"); + fake.set_performer("Anonymous"); + fake.set_track(1); + fake.set_disc(1); + fake.set_year(2011); + + osd_->ShowPreview(type, line1, line2, fake); + } + +} + +void MainWindow::FocusCollectionTab() { + ui_->tabs->SetCurrentWidget(collection_view_); +} + +void MainWindow::ShowConsole() { + Console* console = new Console(app_, this); + console->show(); +} + +void MainWindow::keyPressEvent(QKeyEvent* event) { + if (event->key() == Qt::Key_Space) { + app_->player()->PlayPause(); + event->accept(); + } + else if (event->key() == Qt::Key_Left) { + ui_->track_slider->Seek(-1); + event->accept(); + } + else if (event->key() == Qt::Key_Right) { + ui_->track_slider->Seek(1); + event->accept(); + } + else { + QMainWindow::keyPressEvent(event); + } +} + diff --git a/src/core/mainwindow.h b/src/core/mainwindow.h new file mode 100644 index 00000000..ace23f22 --- /dev/null +++ b/src/core/mainwindow.h @@ -0,0 +1,353 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "config.h" + +#include + +#include +#include +#include + +#include "core/lazy.h" +#include "core/mac_startup.h" +#include "core/tagreaderclient.h" +#include "engine/engine_fwd.h" +#include "collection/collectionmodel.h" +#include "playlist/playlistitem.h" +#ifdef HAVE_GSTREAMER +#include "dialogs/organisedialog.h" +#endif +#include "settings/settingsdialog.h" + +class StatusView; +class About; +class AlbumCoverManager; +class Appearance; +class Application; +class ArtistInfoView; +class CommandlineOptions; +class CoverProviders; +class Database; +class DeviceManager; +class DeviceView; +class DeviceViewContainer; +class EditTagDialog; +class Equalizer; +class ErrorDialog; +class FileView; +class GlobalShortcuts; +class GroupByDialog; +class Collection; +class CollectionViewContainer; +class MimeData; +class MultiLoadingIndicator; +class OSD; +class Player; +class PlaylistBackend; +class PlaylistListContainer; +class PlaylistManager; +class QueueManager; +class Song; +class SystemTrayIcon; +class TagFetcher; +class TaskManager; +class TrackSelectionDialog; +#ifdef HAVE_GSTREAMER +class TranscodeDialog; +#endif +class Windows7ThumbBar; +class Ui_MainWindow; + +class QSortFilterProxyModel; + +class MainWindow : public QMainWindow, public PlatformInterface { + Q_OBJECT + + public: + MainWindow(Application *app, SystemTrayIcon* tray_icon, OSD* osd, const CommandlineOptions& options, QWidget* parent = nullptr); + ~MainWindow(); + + static const char *kSettingsGroup; + static const char *kAllFilesFilterSpec; + + // Don't change the values + enum StartupBehaviour { + Startup_Remember = 1, + Startup_AlwaysShow = 2, + Startup_AlwaysHide = 3, + }; + + // Don't change the values + enum AddBehaviour { + AddBehaviour_Append = 1, + AddBehaviour_Enqueue = 2, + AddBehaviour_Load = 3, + AddBehaviour_OpenInNew = 4 + }; + + // Don't change the values + enum PlayBehaviour { + PlayBehaviour_Never = 1, + PlayBehaviour_IfStopped = 2, + PlayBehaviour_Always = 3, + }; + + // Don't change the values + enum PlaylistAddBehaviour { + PlaylistAddBehaviour_Play = 1, + PlaylistAddBehaviour_Enqueue = 2, + }; + + void SetHiddenInTray(bool hidden); + void CommandlineOptionsReceived(const CommandlineOptions& options); + + protected: + void keyPressEvent(QKeyEvent* event); + void resizeEvent(QResizeEvent* event); + void closeEvent(QCloseEvent* event); + +#ifdef Q_OS_WIN32 + bool winEvent(MSG* message, long* result); +#endif + + // PlatformInterface + void Activate(); + bool LoadUrl(const QString& url); + +signals: + // Signals that stop playing after track was toggled. + void StopAfterToggled(bool stop); + + void IntroPointReached(); + + private slots: + void FilePathChanged(const QString& path); + + void MediaStopped(); + void MediaPaused(); + void MediaPlaying(); + void TrackSkipped(PlaylistItemPtr item); + void ForceShowOSD(const Song& song, const bool toggle); + + void PlaylistRightClick(const QPoint& global_pos, const QModelIndex& index); + void PlaylistCurrentChanged(const QModelIndex& current); + void PlaylistViewSelectionModelChanged(); + void PlaylistPlay(); + void PlaylistStopAfter(); + void PlaylistQueue(); + void PlaylistSkip(); + void PlaylistRemoveCurrent(); + void PlaylistEditFinished(const QModelIndex& index); + void EditTracks(); + void EditTagDialogAccepted(); + void RenumberTracks(); + void SelectionSetValue(); + void EditValue(); +#ifdef HAVE_GSTREAMER + void AutoCompleteTags(); + void AutoCompleteTagsAccepted(); +#endif + void PlaylistUndoRedoChanged(QAction* undo, QAction* redo); +#ifdef HAVE_GSTREAMER + void AddFilesToTranscoder(); +#endif + +#ifdef HAVE_GSTREAMER + void PlaylistCopyToCollection(); + void PlaylistMoveToCollection(); + void PlaylistCopyToDevice(); + void PlaylistOrganiseSelected(bool copy); +#endif + //void PlaylistDelete(); + void PlaylistOpenInBrowser(); + void ShowInCollection(); + + void ChangeCollectionQueryMode(QAction* action); + + void PlayIndex(const QModelIndex& index); + void PlaylistDoubleClick(const QModelIndex& index); + void StopAfterCurrent(); + + void SongChanged(const Song& song); + void VolumeChanged(int volume); + +#ifdef HAVE_GSTREAMER + void CopyFilesToCollection(const QList& urls); + void MoveFilesToCollection(const QList& urls); + void CopyFilesToDevice(const QList& urls); +#endif + void EditFileTags(const QList& urls); + + void AddToPlaylist(QMimeData* data); + void AddToPlaylist(QAction* action); + + void VolumeWheelEvent(int delta); + void ToggleShowHide(); + + void Seeked(qlonglong microseconds); + void UpdateTrackPosition(); + void UpdateTrackSliderPosition(); + + void TaskCountChanged(int count); + + void ShowCollectionConfig(); + void ReloadSettings(); + void ReloadAllSettings(); + void RefreshStyleSheet(); + void SetHiddenInTray() { SetHiddenInTray(true); } + + void AddFile(); + void AddFolder(); + void AddCDTracks(); + + void CommandlineOptionsReceived(const QString& string_options); + + void CheckForUpdates(); + + void PlayingWidgetPositionChanged(); + + void SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex& index); + + void ShowCoverManager(); + + void ShowAboutDialog(); +#ifdef HAVE_GSTREAMER + void ShowTranscodeDialog(); +#endif + void ShowErrorDialog(const QString& message); + void ShowQueueManager(); + void EnsureSettingsDialogCreated(); + void EnsureEditTagDialogCreated(); + SettingsDialog* CreateSettingsDialog(); + EditTagDialog* CreateEditTagDialog(); + void OpenSettingsDialog(); + void OpenSettingsDialogAtPage(SettingsDialog::Page page); + + void TabSwitched(); + void SaveGeometry(); + void SavePlaybackStatus(); + void LoadPlaybackStatus(); + void ResumePlayback(); + + void Raise(); + + void Exit(); + + void HandleNotificationPreview(OSD::Behaviour type, QString line1, QString line2); + void FocusCollectionTab(); + + void ShowConsole(); + + private: + void ConnectStatusView(StatusView *statusview); + + void ApplyAddBehaviour(AddBehaviour b, MimeData *data) const; + void ApplyPlayBehaviour(PlayBehaviour b, MimeData *data) const; + + void CheckFullRescanRevisions(); + + // creates the icon by painting the full one depending on the current position + QPixmap CreateOverlayedIcon(int position, int scrobble_point); + + private: + Ui_MainWindow *ui_; + Windows7ThumbBar *thumbbar_; + + Application *app_; + SystemTrayIcon * tray_icon_; + OSD* osd_; + Lazy edit_tag_dialog_; + Lazy about_dialog_; + + GlobalShortcuts* global_shortcuts_; + + CollectionViewContainer *collection_view_; + StatusView *status_view_; + FileView *file_view_; + PlaylistListContainer *playlist_list_; + DeviceViewContainer *device_view_container_; + DeviceView *device_view_; + + Lazy settings_dialog_; + Lazy cover_manager_; + std::unique_ptr equalizer_; +#ifdef HAVE_GSTREAMER + Lazy transcode_dialog_; +#endif + Lazy error_dialog_; +#ifdef HAVE_GSTREAMER + Lazy organise_dialog_; +#endif + Lazy queue_manager_; + +#ifdef HAVE_GSTREAMER + std::unique_ptr tag_fetcher_; +#endif + std::unique_ptr track_selection_dialog_; +#ifdef HAVE_GSTREAMER + PlaylistItemList autocomplete_tag_items_; +#endif + + QAction *collection_show_all_; + QAction *collection_show_duplicates_; + QAction *collection_show_untagged_; + + QMenu *playlist_menu_; + QAction *playlist_play_pause_; + QAction *playlist_stop_after_; + QAction *playlist_undoredo_; + //QAction *playlist_organise_; + QAction *playlist_show_in_collection_; +#ifdef HAVE_GSTREAMER + QAction *playlist_copy_to_collection_; + QAction *playlist_move_to_collection_; + QAction *playlist_copy_to_device_; + //QAction *playlist_delete_; +#endif + QAction *playlist_open_in_browser_; + QAction *playlist_queue_; + QAction *playlist_skip_; + QAction *playlist_add_to_another_; + QList playlistitem_actions_; + QAction *playlistitem_actions_separator_; + QModelIndex playlist_menu_index_; + + QSortFilterProxyModel *collection_sort_model_; + + QTimer *track_position_timer_; + QTimer *track_slider_timer_; + QSettings settings_; + + bool was_maximized_; + int saved_playback_position_; + Engine::State saved_playback_state_; + AddBehaviour doubleclick_addmode_; + PlayBehaviour doubleclick_playmode_; + PlaylistAddBehaviour doubleclick_playlist_addmode_; + PlayBehaviour menu_playmode_; + +}; + +#endif // MAINWINDOW_H + diff --git a/src/core/mainwindow.ui b/src/core/mainwindow.ui new file mode 100644 index 00000000..7772f70e --- /dev/null +++ b/src/core/mainwindow.ui @@ -0,0 +1,784 @@ + + + MainWindow + + + + 0 + 0 + 1131 + 685 + + + + Strawberry Music Player + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + 0 + + + + + + + + + + + + + 0 + + + + + + 0 + 0 + + + + + + + + + + 0 + + + + + Qt::Vertical + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + + 1 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 22 + 22 + + + + true + + + + + + + + 22 + 22 + + + + true + + + + + + + false + + + + 22 + 22 + + + + QToolButton::MenuButtonPopup + + + true + + + + + + + + 22 + 22 + + + + true + + + + + + + Qt::Vertical + + + + + + + + 100 + 0 + + + + + 0 + 36 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + 100 + + + Qt::Horizontal + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Vertical + + + + + + + + + + Qt::Vertical + + + + + + + + 10 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1131 + 24 + + + + + &Music + + + + + + + + + + + + + + + + + &Playlist + + + + + + + + + + + + + + + + + + + + &Help + + + + + + + + &Tools + + + + + + + + + + + + + + + + + + + &Previous track + + + F5 + + + + + P&lay + + + F6 + + + + + false + + + &Stop + + + F7 + + + + + &Next track + + + F8 + + + + + &Quit + + + Ctrl+Q + + + QAction::QuitRole + + + + + Stop after this track + + + Ctrl+Alt+V + + + + + &Clear playlist + + + Clear playlist + + + Ctrl+K + + + + + Edit track information... + + + Ctrl+E + + + + + Renumber tracks in this order... + + + + + Set value for all selected tracks... + + + + + Edit tag... + + + F2 + + + + + &Settings... + + + Ctrl+P + + + QAction::PreferencesRole + + + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + &About Strawberry + + + F1 + + + QAction::AboutRole + + + + + S&huffle playlist + + + Ctrl+H + + + + + &Add file... + + + Ctrl+Shift+A + + + + + &Open file... + + + + + Open &audio CD... + + + + + &Cover Manager + + + + + C&onsole + + + + + &Shuffle mode + + + + + &Repeat mode + + + + + Remove from playlist + + + + + &Equalizer + + + + + Add &folder... + + + + + &Jump to the currently playing track + + + Ctrl+J + + + + + &New playlist + + + Ctrl+N + + + + + Save &playlist... + + + Ctrl+S + + + + + &Load playlist... + + + Ctrl+Shift+O + + + + + Go to next playlist tab + + + + + Go to previous playlist tab + + + + + &Update changed collection folders + + + + + &Queue Manager + + + + + About &Qt + + + QAction::AboutQtRole + + + + + true + + + &Mute + + + Ctrl+M + + + + + &Do a full collection rescan + + + + + + :/pictures/musicbrainz.png:/pictures/musicbrainz.png + + + Complete tags automatically... + + + Ctrl+T + + + + + Toggle scrobbling + + + + + Remove &duplicates from playlist + + + + + Remove &unavailable tracks from playlist + + + + + + + Amarok::VolumeSlider + QSlider +
widgets/sliderwidget.h
+
+ + AnalyzerContainer + QWidget +
analyzer/analyzercontainer.h
+ 1 +
+ + PlaylistContainer + QWidget +
playlist/playlistcontainer.h
+ 1 +
+ + TrackSlider + QWidget +
widgets/trackslider.h
+ 1 +
+ + PlaylistSequence + QWidget +
playlist/playlistsequence.h
+ 1 +
+ + MultiLoadingIndicator + QWidget +
widgets/multiloadingindicator.h
+ 1 +
+ + PlayingWidget + QWidget +
widgets/playingwidget.h
+ 1 +
+ + FancyTabWidget + QWidget +
widgets/fancytabwidget.h
+
+
+ + + + +
diff --git a/src/core/mergedproxymodel.cpp b/src/core/mergedproxymodel.cpp new file mode 100644 index 00000000..bc31005d --- /dev/null +++ b/src/core/mergedproxymodel.cpp @@ -0,0 +1,545 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "mergedproxymodel.h" +#include "core/logging.h" + +#include + +#include + +// boost::multi_index still relies on these being in the global namespace. +using std::placeholders::_1; +using std::placeholders::_2; + +#include +#include +#include +#include + +using boost::multi_index::hashed_unique; +using boost::multi_index::identity; +using boost::multi_index::indexed_by; +using boost::multi_index::member; +using boost::multi_index::multi_index_container; +using boost::multi_index::ordered_unique; +using boost::multi_index::tag; + +std::size_t hash_value(const QModelIndex& index) { return qHash(index); } + +namespace { + +struct Mapping { + explicit Mapping(const QModelIndex& _source_index) : source_index(_source_index) {} + + QModelIndex source_index; +}; + +struct tag_by_source {}; +struct tag_by_pointer {}; + +} // namespace + +class MergedProxyModelPrivate { + private: + typedef multi_index_container< + Mapping*, + indexed_by< + hashed_unique, + member >, + ordered_unique, identity > > > + MappingContainer; + + public: + MappingContainer mappings_; +}; + +MergedProxyModel::MergedProxyModel(QObject* parent) + : QAbstractProxyModel(parent), + resetting_model_(nullptr), + p_(new MergedProxyModelPrivate) {} + +MergedProxyModel::~MergedProxyModel() { DeleteAllMappings(); } + +void MergedProxyModel::DeleteAllMappings() { + const auto& begin = p_->mappings_.get().begin(); + const auto& end = p_->mappings_.get().end(); + qDeleteAll(begin, end); +} + +void MergedProxyModel::AddSubModel(const QModelIndex& source_parent, QAbstractItemModel* submodel) { + + connect(submodel, SIGNAL(modelReset()), this, SLOT(SubModelReset())); + connect(submodel, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(RowsAboutToBeInserted(QModelIndex, int, int))); + connect(submodel, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(RowsAboutToBeRemoved(QModelIndex, int, int))); + connect(submodel, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(RowsInserted(QModelIndex, int, int))); + connect(submodel, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(RowsRemoved(QModelIndex, int, int))); + connect(submodel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(DataChanged(QModelIndex, QModelIndex))); + + QModelIndex proxy_parent = mapFromSource(source_parent); + const int rows = submodel->rowCount(); + + if (rows) beginInsertRows(proxy_parent, 0, rows - 1); + + merge_points_.insert(submodel, source_parent); + + if (rows) endInsertRows(); +} + +void MergedProxyModel::RemoveSubModel(const QModelIndex& source_parent) { + // Find the submodel that the parent corresponded to + QAbstractItemModel* submodel = merge_points_.key(source_parent); + merge_points_.remove(submodel); + + // The submodel might have been deleted already so we must be careful not + // to dereference it. + + // Remove all the children of the item that got deleted + QModelIndex proxy_parent = mapFromSource(source_parent); + + // We can't know how many children it had, since we can't dereference it + resetting_model_ = submodel; + beginRemoveRows(proxy_parent, 0, std::numeric_limits::max() - 1); + endRemoveRows(); + resetting_model_ = nullptr; + + // Delete all the mappings that reference the submodel + auto it = p_->mappings_.get().begin(); + auto end = p_->mappings_.get().end(); + while (it != end) { + if ((*it)->source_index.model() == submodel) { + delete *it; + it = p_->mappings_.get().erase(it); + } else { + ++it; + } + } +} + +void MergedProxyModel::setSourceModel(QAbstractItemModel* source_model) { + + if (sourceModel()) { + disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(SourceModelReset())); + disconnect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(RowsAboutToBeInserted(QModelIndex,int,int))); + disconnect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(RowsAboutToBeRemoved(QModelIndex,int,int))); + disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(RowsInserted(QModelIndex,int,int))); + disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(RowsRemoved(QModelIndex,int,int))); + disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(DataChanged(QModelIndex, QModelIndex))); + disconnect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), this, SLOT(LayoutAboutToBeChanged())); + disconnect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(LayoutChanged())); + } + + QAbstractProxyModel::setSourceModel(source_model); + + connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(SourceModelReset())); + connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(RowsAboutToBeInserted(QModelIndex, int, int))); + connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(RowsAboutToBeRemoved(QModelIndex, int, int))); + connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(RowsInserted(QModelIndex,int,int))); + connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(RowsRemoved(QModelIndex,int,int))); + connect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(DataChanged(QModelIndex,QModelIndex))); + connect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), this, SLOT(LayoutAboutToBeChanged())); + connect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(LayoutChanged())); + +} + +void MergedProxyModel::SourceModelReset() { + + // Delete all mappings + DeleteAllMappings(); + + // Reset the proxy + beginResetModel(); + + // Clear the containers + p_->mappings_.clear(); + merge_points_.clear(); + + endResetModel(); + +} + +void MergedProxyModel::SubModelReset() { + + QAbstractItemModel* submodel = static_cast(sender()); + + // TODO: When we require Qt 4.6, use beginResetModel() and endResetModel() + // in CollectionModel and catch those here - that will let us do away with this + // std::numeric_limits::max() hack. + + // Remove all the children of the item that got deleted + QModelIndex source_parent = merge_points_.value(submodel); + QModelIndex proxy_parent = mapFromSource(source_parent); + + // We can't know how many children it had, since it's already disappeared... + resetting_model_ = submodel; + beginRemoveRows(proxy_parent, 0, std::numeric_limits::max() - 1); + endRemoveRows(); + resetting_model_ = nullptr; + + // Delete all the mappings that reference the submodel + auto it = p_->mappings_.get().begin(); + auto end = p_->mappings_.get().end(); + while (it != end) { + if ((*it)->source_index.model() == submodel) { + delete *it; + it = p_->mappings_.get().erase(it); + } else { + ++it; + } + } + + // "Insert" items from the newly reset submodel + int count = submodel->rowCount(); + if (count) { + beginInsertRows(proxy_parent, 0, count - 1); + endInsertRows(); + } + + emit SubModelReset(proxy_parent, submodel); + +} + +QModelIndex MergedProxyModel::GetActualSourceParent(const QModelIndex& source_parent, QAbstractItemModel* model) const { + + if (!source_parent.isValid() && model != sourceModel()) + return merge_points_.value(model); + return source_parent; + +} + +void MergedProxyModel::RowsAboutToBeInserted(const QModelIndex& source_parent, int start, int end) { + beginInsertRows(mapFromSource(GetActualSourceParent(source_parent, static_cast(sender()))), start, end); +} + +void MergedProxyModel::RowsInserted(const QModelIndex&, int, int) { + endInsertRows(); +} + +void MergedProxyModel::RowsAboutToBeRemoved(const QModelIndex& source_parent, int start, int end) { + beginRemoveRows(mapFromSource(GetActualSourceParent(source_parent, static_cast(sender()))), start, end); +} + +void MergedProxyModel::RowsRemoved(const QModelIndex&, int, int) { + endRemoveRows(); +} + +QModelIndex MergedProxyModel::mapToSource(const QModelIndex& proxy_index) const { + + if (!proxy_index.isValid()) return QModelIndex(); + + Mapping* mapping = static_cast(proxy_index.internalPointer()); + if (p_->mappings_.get().find(mapping) == + p_->mappings_.get().end()) + return QModelIndex(); + if (mapping->source_index.model() == resetting_model_) return QModelIndex(); + + return mapping->source_index; + +} + +QModelIndex MergedProxyModel::mapFromSource(const QModelIndex& source_index) const { + + if (!source_index.isValid()) return QModelIndex(); + if (source_index.model() == resetting_model_) return QModelIndex(); + + // Add a mapping if we don't have one already + const auto& it = p_->mappings_.get().find(source_index); + Mapping* mapping; + if (it != p_->mappings_.get().end()) { + mapping = *it; + } else { + mapping = new Mapping(source_index); + const_cast(this)->p_->mappings_.insert(mapping); + } + + return createIndex(source_index.row(), source_index.column(), mapping); + +} + +QModelIndex MergedProxyModel::index(int row, int column, const QModelIndex &parent) const { + + QModelIndex source_index; + + if (!parent.isValid()) { + source_index = sourceModel()->index(row, column, QModelIndex()); + } + else { + QModelIndex source_parent = mapToSource(parent); + const QAbstractItemModel* child_model = merge_points_.key(source_parent); + + if (child_model) + source_index = child_model->index(row, column, QModelIndex()); + else + source_index = source_parent.model()->index(row, column, source_parent); + } + + return mapFromSource(source_index); + +} + +QModelIndex MergedProxyModel::parent(const QModelIndex& child) const { + + QModelIndex source_child = mapToSource(child); + if (source_child.model() == sourceModel()) + return mapFromSource(source_child.parent()); + + if (!IsKnownModel(source_child.model())) return QModelIndex(); + + if (!source_child.parent().isValid()) + return mapFromSource(merge_points_.value(GetModel(source_child))); + return mapFromSource(source_child.parent()); + +} + +int MergedProxyModel::rowCount(const QModelIndex& parent) const { + + if (!parent.isValid()) return sourceModel()->rowCount(QModelIndex()); + + QModelIndex source_parent = mapToSource(parent); + if (!IsKnownModel(source_parent.model())) return 0; + + const QAbstractItemModel* child_model = merge_points_.key(source_parent); + if (child_model) { + // Query the source model but disregard what it says, so it gets a chance + // to lazy load + source_parent.model()->rowCount(source_parent); + + return child_model->rowCount(QModelIndex()); + } + + return source_parent.model()->rowCount(source_parent); + +} + +int MergedProxyModel::columnCount(const QModelIndex& parent) const { + + if (!parent.isValid()) return sourceModel()->columnCount(QModelIndex()); + + QModelIndex source_parent = mapToSource(parent); + if (!IsKnownModel(source_parent.model())) return 0; + + const QAbstractItemModel* child_model = merge_points_.key(source_parent); + if (child_model) return child_model->columnCount(QModelIndex()); + return source_parent.model()->columnCount(source_parent); + +} + +bool MergedProxyModel::hasChildren(const QModelIndex& parent) const { + + if (!parent.isValid()) return sourceModel()->hasChildren(QModelIndex()); + + QModelIndex source_parent = mapToSource(parent); + if (!IsKnownModel(source_parent.model())) return false; + + const QAbstractItemModel* child_model = merge_points_.key(source_parent); + + if (child_model) return child_model->hasChildren(QModelIndex()) || source_parent.model()->hasChildren(source_parent); + return source_parent.model()->hasChildren(source_parent); + +} + +QVariant MergedProxyModel::data(const QModelIndex& proxyIndex, int role) const { + + QModelIndex source_index = mapToSource(proxyIndex); + if (!IsKnownModel(source_index.model())) return QVariant(); + + return source_index.model()->data(source_index, role); + +} + +QMap MergedProxyModel::itemData(const QModelIndex& proxy_index) const { + + QModelIndex source_index = mapToSource(proxy_index); + + if (!source_index.isValid()) return sourceModel()->itemData(QModelIndex()); + return source_index.model()->itemData(source_index); + +} + +Qt::ItemFlags MergedProxyModel::flags(const QModelIndex& index) const { + + QModelIndex source_index = mapToSource(index); + + if (!source_index.isValid()) return sourceModel()->flags(QModelIndex()); + return source_index.model()->flags(source_index); + +} + +bool MergedProxyModel::setData(const QModelIndex& index, const QVariant& value, int role) { + + QModelIndex source_index = mapToSource(index); + + if (!source_index.isValid()) + return sourceModel()->setData(index, value, role); + return GetModel(index)->setData(index, value, role); + +} + +QStringList MergedProxyModel::mimeTypes() const { + + QStringList ret; + ret << sourceModel()->mimeTypes(); + + for (const QAbstractItemModel* model : merge_points_.keys()) { + ret << model->mimeTypes(); + } + + return ret; + +} + +QMimeData* MergedProxyModel::mimeData(const QModelIndexList& indexes) const { + + if (indexes.isEmpty()) return 0; + + // Only ask the first index's model + const QAbstractItemModel* model = mapToSource(indexes[0]).model(); + if (!model) { + return 0; + } + + // Only ask about the indexes that are actually in that model + QModelIndexList indexes_in_model; + + for (const QModelIndex& proxy_index : indexes) { + QModelIndex source_index = mapToSource(proxy_index); + if (source_index.model() != model) continue; + indexes_in_model << source_index; + } + + return model->mimeData(indexes_in_model); + +} + +bool MergedProxyModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) { + + if (!parent.isValid()) { + return false; + } + + return sourceModel()->dropMimeData(data, action, row, column, parent); + +} + +QModelIndex MergedProxyModel::FindSourceParent(const QModelIndex& proxy_index) const { + + if (!proxy_index.isValid()) return QModelIndex(); + + QModelIndex source_index = mapToSource(proxy_index); + if (source_index.model() == sourceModel()) return source_index; + return merge_points_.value(GetModel(source_index)); + +} + +bool MergedProxyModel::canFetchMore(const QModelIndex& parent) const { + + QModelIndex source_index = mapToSource(parent); + + if (!source_index.isValid()) + return sourceModel()->canFetchMore(QModelIndex()); + return source_index.model()->canFetchMore(source_index); + +} + +void MergedProxyModel::fetchMore(const QModelIndex& parent) { + + QModelIndex source_index = mapToSource(parent); + + if (!source_index.isValid()) + sourceModel()->fetchMore(QModelIndex()); + else + GetModel(source_index)->fetchMore(source_index); + +} + +QAbstractItemModel* MergedProxyModel::GetModel(const QModelIndex& source_index) const { + + // This is essentially const_cast(source_index.model()), + // but without the const_cast + const QAbstractItemModel* const_model = source_index.model(); + if (const_model == sourceModel()) return sourceModel(); + for (QAbstractItemModel* submodel : merge_points_.keys()) { + if (submodel == const_model) return submodel; + } + return nullptr; + +} + +void MergedProxyModel::DataChanged(const QModelIndex& top_left, const QModelIndex& bottom_right) { + emit dataChanged(mapFromSource(top_left), mapFromSource(bottom_right)); +} + +void MergedProxyModel::LayoutAboutToBeChanged() { + + old_merge_points_.clear(); + for (QAbstractItemModel* key : merge_points_.keys()) { + old_merge_points_[key] = merge_points_.value(key); + } + +} + +void MergedProxyModel::LayoutChanged() { + + for (QAbstractItemModel* key : merge_points_.keys()) { + if (!old_merge_points_.contains(key)) continue; + + const int old_row = old_merge_points_[key].row(); + const int new_row = merge_points_[key].row(); + + if (old_row != new_row) { + beginResetModel(); + endResetModel(); + return; + } + } + +} + +bool MergedProxyModel::IsKnownModel(const QAbstractItemModel* model) const { + + if (model == this || model == sourceModel() || + merge_points_.contains(const_cast(model))) + return true; + return false; + +} + +QModelIndexList MergedProxyModel::mapFromSource(const QModelIndexList& source_indexes) const { + + QModelIndexList ret; + for (const QModelIndex& index : source_indexes) { + ret << mapFromSource(index); + } + return ret; + +} + +QModelIndexList MergedProxyModel::mapToSource(const QModelIndexList& proxy_indexes) const { + + QModelIndexList ret; + for (const QModelIndex& index : proxy_indexes) { + ret << mapToSource(index); + } + return ret; + +} + diff --git a/src/core/mergedproxymodel.h b/src/core/mergedproxymodel.h new file mode 100644 index 00000000..db46cbea --- /dev/null +++ b/src/core/mergedproxymodel.h @@ -0,0 +1,110 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MERGEDPROXYMODEL_H +#define MERGEDPROXYMODEL_H + +#include "config.h" + +#include + +#include + +std::size_t hash_value(const QModelIndex& index); + +class MergedProxyModelPrivate; + +class MergedProxyModel : public QAbstractProxyModel { + Q_OBJECT + + public: + explicit MergedProxyModel(QObject* parent = nullptr); + ~MergedProxyModel(); + + // Make another model appear as a child of the given item in the source model. + void AddSubModel(const QModelIndex& source_parent, QAbstractItemModel* submodel); + void RemoveSubModel(const QModelIndex& source_parent); + + // Find the item in the source model that is the parent of the model + // containing proxy_index. If proxy_index is in the source model, then + // this just returns mapToSource(proxy_index). + QModelIndex FindSourceParent(const QModelIndex& proxy_index) const; + + // QAbstractItemModel + 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; + QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const; + bool hasChildren(const QModelIndex& parent) const; + QMap itemData(const QModelIndex& proxyIndex) const; + Qt::ItemFlags flags(const QModelIndex& index) const; + bool setData(const QModelIndex& index, const QVariant& value, int role); + QStringList mimeTypes() const; + QMimeData* mimeData(const QModelIndexList& indexes) const; + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); + bool canFetchMore(const QModelIndex& parent) const; + void fetchMore(const QModelIndex& parent); + + // QAbstractProxyModel + // Note that these implementations of map{To,From}Source will not always + // give you an index in sourceModel(), you might get an index in one of the + // child models instead. + QModelIndex mapFromSource(const QModelIndex& sourceIndex) const; + QModelIndex mapToSource(const QModelIndex& proxyIndex) const; + void setSourceModel(QAbstractItemModel* sourceModel); + + // Convenience functions that call map{To,From}Source multiple times. + QModelIndexList mapFromSource(const QModelIndexList& source_indexes) const; + QModelIndexList mapToSource(const QModelIndexList& proxy_indexes) const; + +signals: + void SubModelReset(const QModelIndex& root, QAbstractItemModel* model); + + private slots: + void SourceModelReset(); + void SubModelReset(); + + void RowsAboutToBeInserted(const QModelIndex& source_parent, int start, int end); + void RowsInserted(const QModelIndex& source_parent, int start, int end); + void RowsAboutToBeRemoved(const QModelIndex& source_parent, int start, int end); + void RowsRemoved(const QModelIndex& source_parent, int start, int end); + void DataChanged(const QModelIndex& top_left, const QModelIndex& bottom_right); + + void LayoutAboutToBeChanged(); + void LayoutChanged(); + + private: + QModelIndex GetActualSourceParent(const QModelIndex& source_parent, + QAbstractItemModel* model) const; + QAbstractItemModel* GetModel(const QModelIndex& source_index) const; + void DeleteAllMappings(); + bool IsKnownModel(const QAbstractItemModel* model) const; + + QMap merge_points_; + QAbstractItemModel* resetting_model_; + + QMap old_merge_points_; + + std::unique_ptr p_; +}; + +#endif // MERGEDPROXYMODEL_H + diff --git a/src/core/metatypes.cpp b/src/core/metatypes.cpp new file mode 100644 index 00000000..6dbd8b5e --- /dev/null +++ b/src/core/metatypes.cpp @@ -0,0 +1,107 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "metatypes.h" +#include "config.h" + +#include +#include +#include + +#include "metatypes.h" + +#include "engine/enginebase.h" +#include "engine/enginetype.h" +#ifdef HAVE_GSTREAMER +# include "engine/gstengine.h" +# include "engine/gstenginepipeline.h" +#endif +#include "collection/directory.h" +#include "playlist/playlist.h" +#include "equalizer/equalizer.h" +#include "covermanager/albumcoverfetcher.h" + +#ifdef HAVE_DBUS +#include +#include "core/mpris2.h" +#include "dbus/metatypes.h" +#endif + +class QNetworkReply; +#ifdef HAVE_GSTREAMER + class GstEnginePipeline; +#endif + +void RegisterMetaTypes() { + //qRegisterMetaType("CollapsibleInfoPane::Data"); + qRegisterMetaType("const char*"); + qRegisterMetaType("CoverSearchResult"); + qRegisterMetaType("CoverSearchResults"); + qRegisterMetaType("Directory"); + qRegisterMetaType("DirectoryList"); + qRegisterMetaType("Engine::SimpleMetaBundle"); + qRegisterMetaType("Engine::State"); + qRegisterMetaType("Engine::TrackChangeFlags"); + qRegisterMetaType("Equalizer::Params"); + qRegisterMetaType("EngineBase::PluginDetails"); + qRegisterMetaType("EngineBase::OutputDetails"); +#ifdef HAVE_GSTREAMER + qRegisterMetaType("GstBuffer*"); + qRegisterMetaType("GstElement*"); + qRegisterMetaType("GstEnginePipeline*"); +#endif + qRegisterMetaType("PlaylistItemList"); + qRegisterMetaType("PlaylistItemPtr"); + qRegisterMetaType >("QList"); + qRegisterMetaType>("QList"); + qRegisterMetaType >("QList"); + qRegisterMetaType("PlaylistSequence::RepeatMode"); + qRegisterMetaType("PlaylistSequence::ShuffleMode"); + qRegisterMetaType("QAbstractSocket::SocketState"); + qRegisterMetaType >("QList"); + qRegisterMetaType >("QList"); + qRegisterMetaType("QNetworkCookie"); + qRegisterMetaType("QNetworkReply*"); + qRegisterMetaType("QNetworkReply**"); + qRegisterMetaType("SongList"); + qRegisterMetaType("Song"); + qRegisterMetaTypeStreamOperators("Equalizer::Params"); + qRegisterMetaTypeStreamOperators >("ColumnAlignmentMap"); + qRegisterMetaType("SubdirectoryList"); + qRegisterMetaType("Subdirectory"); + qRegisterMetaType>("QList"); + qRegisterMetaType(); + + qRegisterMetaType("EngineType"); + +#ifdef HAVE_DBUS + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType>(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); +#endif + +} + diff --git a/src/core/metatypes.h b/src/core/metatypes.h new file mode 100644 index 00000000..d757794a --- /dev/null +++ b/src/core/metatypes.h @@ -0,0 +1,6 @@ +#ifndef METATYPES_H +#define METATYPES_H + +void RegisterMetaTypes(); + +#endif diff --git a/src/core/mimedata.h b/src/core/mimedata.h new file mode 100644 index 00000000..d2e42ee6 --- /dev/null +++ b/src/core/mimedata.h @@ -0,0 +1,76 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MIMEDATA_H +#define MIMEDATA_H + +#include "config.h" + +#include + +class MimeData : public QMimeData { + Q_OBJECT + + public: + MimeData(bool clear = false, bool play_now = false, bool enqueue = false, bool open_in_new_playlist = false) + : override_user_settings_(false), + clear_first_(clear), + play_now_(play_now), + enqueue_now_(enqueue), + open_in_new_playlist_(open_in_new_playlist), + name_for_new_playlist_(QString()), + from_doubleclick_(false) {} + + // If this is set then MainWindow will not touch any of the other flags. + bool override_user_settings_; + + // If this is set then the playlist will be cleared before these songs + // are inserted. + bool clear_first_; + + // If this is set then the first item that is inserted will start playing + // immediately. Note: this is always overridden with the user's preference + // if the MimeData goes via MainWindow, unless you set override_user_settings_. + bool play_now_; + + // If this is set then the items are added to the queue after being inserted. + bool enqueue_now_; + + // If this is set then the items are inserted into a newly created playlist. + bool open_in_new_playlist_; + + // This serves as a name for the new playlist in 'open_in_new_playlist_' mode. + QString name_for_new_playlist_; + + // This can be set if this MimeData goes via MainWindow (ie. it is created + // manually in a double-click). The MainWindow will set the above flags to + // the defaults set by the user. + bool from_doubleclick_; + + // Returns a pretty name for a playlist containing songs described by this MimeData + // object. By pretty name we mean the value of 'name_for_new_playlist_' or generic + // "Playlist" string if the 'name_for_new_playlist_' attribute is empty. + QString get_name_for_new_playlist() { + return name_for_new_playlist_.isEmpty() ? tr("Playlist") : name_for_new_playlist_; + } +}; + +#endif // MIMEDATA_H + diff --git a/src/core/mpris.cpp b/src/core/mpris.cpp new file mode 100644 index 00000000..48ec576d --- /dev/null +++ b/src/core/mpris.cpp @@ -0,0 +1,33 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + + +#include "config.h" + +#include "mpris.h" +#include "mpris2.h" + +namespace mpris { + +Mpris::Mpris(Application* app, QObject* parent) : QObject(parent), mpris2_(new mpris::Mpris2(app, this)) { + connect(mpris2_, SIGNAL(RaiseMainWindow()), SIGNAL(RaiseMainWindow())); +} + +} // namespace mpris diff --git a/src/core/mpris.h b/src/core/mpris.h new file mode 100644 index 00000000..e82f3364 --- /dev/null +++ b/src/core/mpris.h @@ -0,0 +1,52 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MPRIS_H +#define MPRIS_H + +#include "config.h" + +#include + +class Application; + +namespace mpris { + +class Mpris1; +class Mpris2; + +class Mpris : public QObject { + Q_OBJECT + + public: + explicit Mpris(Application *app, QObject *parent = nullptr); + +signals: + void RaiseMainWindow(); + + private: + Mpris1 *mpris1_; + Mpris2 *mpris2_; +}; + +} // namespace mpris + +#endif // MPRIS_H + diff --git a/src/core/mpris2.cpp b/src/core/mpris2.cpp new file mode 100644 index 00000000..596cc811 --- /dev/null +++ b/src/core/mpris2.cpp @@ -0,0 +1,543 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include "mpris2.h" + +#include "core/application.h" +#include "core/mainwindow.h" +#include "core/logging.h" +#include "core/mpris_common.h" +#include "core/mpris2_player.h" +#include "core/mpris2_playlists.h" +#include "core/mpris2_root.h" +#include "core/mpris2_tracklist.h" +#include "core/player.h" +#include "core/timeconstants.h" +#include "engine/enginebase.h" +#include "playlist/playlist.h" +#include "playlist/playlistmanager.h" +#include "playlist/playlistsequence.h" +#include "covermanager/currentartloader.h" + +QDBusArgument &operator<<(QDBusArgument &arg, const MprisPlaylist &playlist) { + arg.beginStructure(); + arg << playlist.id << playlist.name << playlist.icon; + arg.endStructure(); + return arg; +} + +const QDBusArgument &operator>> (const QDBusArgument &arg, MprisPlaylist &playlist) { + arg.beginStructure(); + arg >> playlist.id >> playlist.name >> playlist.icon; + arg.endStructure(); + return arg; +} + +QDBusArgument &operator<<(QDBusArgument &arg, const MaybePlaylist &playlist) { + arg.beginStructure(); + arg << playlist.valid; + arg << playlist.playlist; + arg.endStructure(); + return arg; +} + +const QDBusArgument &operator>> (const QDBusArgument &arg, MaybePlaylist &playlist) { + arg.beginStructure(); + arg >> playlist.valid >> playlist.playlist; + arg.endStructure(); + return arg; +} + +namespace mpris { + +const char* Mpris2::kMprisObjectPath = "/org/mpris/MediaPlayer2"; +const char* Mpris2::kServiceName = "org.mpris.MediaPlayer2.strawberry"; +const char* Mpris2::kFreedesktopPath = "org.freedesktop.DBus.Properties"; + +Mpris2::Mpris2(Application* app, QObject* parent) : QObject(parent), app_(app) { + + new Mpris2Root(this); + new Mpris2TrackList(this); + new Mpris2Player(this); + new Mpris2Playlists(this); + + if (!QDBusConnection::sessionBus().registerService(kServiceName)) { + qLog(Warning) << "Failed to register" << QString(kServiceName) << "on the session bus"; + return; + } + + QDBusConnection::sessionBus().registerObject(kMprisObjectPath, this); + + connect(app_->current_art_loader(), SIGNAL(ArtLoaded(Song,QString,QImage)), SLOT(ArtLoaded(Song,QString))); + + connect(app_->player()->engine(), SIGNAL(StateChanged(Engine::State)), SLOT(EngineStateChanged(Engine::State))); + connect(app_->player(), SIGNAL(VolumeChanged(int)), SLOT(VolumeChanged())); + connect(app_->player(), SIGNAL(Seeked(qlonglong)), SIGNAL(Seeked(qlonglong))); + + connect(app_->playlist_manager(), SIGNAL(PlaylistManagerInitialized()), SLOT(PlaylistManagerInitialized())); + connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(CurrentSongChanged(Song))); + connect(app_->playlist_manager(), SIGNAL(PlaylistChanged(Playlist*)), SLOT(PlaylistChanged(Playlist*))); + connect(app_->playlist_manager(), SIGNAL(CurrentChanged(Playlist*)), SLOT(PlaylistCollectionChanged(Playlist*))); + +} + +// when PlaylistManager gets it ready, we connect PlaylistSequence with this +void Mpris2::PlaylistManagerInitialized() { + connect(app_->playlist_manager()->sequence(), SIGNAL(ShuffleModeChanged(PlaylistSequence::ShuffleMode)), SLOT(ShuffleModeChanged())); + connect(app_->playlist_manager()->sequence(), SIGNAL(RepeatModeChanged(PlaylistSequence::RepeatMode)), SLOT(RepeatModeChanged())); +} + +void Mpris2::EngineStateChanged(Engine::State newState) { + + if (newState != Engine::Playing && newState != Engine::Paused) { + last_metadata_ = QVariantMap(); + EmitNotification("Metadata"); + } + + EmitNotification("PlaybackStatus", PlaybackStatus(newState)); + if (newState == Engine::Playing) EmitNotification("CanSeek", CanSeek(newState)); + +} + +void Mpris2::VolumeChanged() { EmitNotification("Volume"); } + +void Mpris2::ShuffleModeChanged() { EmitNotification("Shuffle"); } + +void Mpris2::RepeatModeChanged() { + + EmitNotification("LoopStatus"); + EmitNotification("CanGoNext", CanGoNext()); + EmitNotification("CanGoPrevious", CanGoPrevious()); + +} + +void Mpris2::EmitNotification(const QString &name, const QVariant &val) { + EmitNotification(name, val, "org.mpris.MediaPlayer2.Player"); +} + +void Mpris2::EmitNotification(const QString &name, const QVariant &val, const QString &mprisEntity) { + + QDBusMessage msg = QDBusMessage::createSignal(kMprisObjectPath, kFreedesktopPath, "PropertiesChanged"); + QVariantMap map; + map.insert(name, val); + QVariantList args = QVariantList() << mprisEntity << map << QStringList(); + msg.setArguments(args); + QDBusConnection::sessionBus().send(msg); + +} + +void Mpris2::EmitNotification(const QString &name) { + + QVariant value; + if (name == "PlaybackStatus") value = PlaybackStatus(); + else if (name == "LoopStatus") value = LoopStatus(); + else if (name == "Shuffle") value = Shuffle(); + else if (name == "Metadata") value = Metadata(); + else if (name == "Volume") value = Volume(); + else if (name == "Position") value = Position(); + else if (name == "CanGoNext") value = CanGoNext(); + else if (name == "CanGoPrevious") value = CanGoPrevious(); + else if (name == "CanSeek") value = CanSeek(); + + if (value.isValid()) EmitNotification(name, value); + +} + +//------------------Root Interface--------------------------// + +bool Mpris2::CanQuit() const { return true; } + +bool Mpris2::CanRaise() const { return true; } + +bool Mpris2::HasTrackList() const { return true; } + +QString Mpris2::Identity() const { return QCoreApplication::applicationName(); } + +QString Mpris2::DesktopEntryAbsolutePath() const { + QStringList xdg_data_dirs = QString(getenv("XDG_DATA_DIRS")).split(":"); + xdg_data_dirs.append("/usr/local/share/"); + xdg_data_dirs.append("/usr/share/"); + + for (const QString &directory : xdg_data_dirs) { + QString path = QString("%1/applications/%2.desktop").arg( + directory, QApplication::applicationName().toLower()); + if (QFile::exists(path)) return path; + } + return QString(); +} + +QString Mpris2::DesktopEntry() const { + return QApplication::applicationName().toLower(); +} + +QStringList Mpris2::SupportedUriSchemes() const { + + static QStringList res = QStringList() << "file" + << "http" + << "cdda" + << "smb" + << "sftp"; + return res; + +} + +QStringList Mpris2::SupportedMimeTypes() const { + + static QStringList res = QStringList() << "application/ogg" + << "application/x-ogg" + << "application/x-ogm-audio" + << "audio/aac" + << "audio/mp4" + << "audio/mpeg" + << "audio/mpegurl" + << "audio/ogg" + << "audio/vnd.rn-realaudio" + << "audio/vorbis" + << "audio/x-flac" + << "audio/x-mp3" + << "audio/x-mpeg" + << "audio/x-mpegurl" + << "audio/x-ms-wma" + << "audio/x-musepack" + << "audio/x-oggflac" + << "audio/x-pn-realaudio" + << "audio/x-scpls" + << "audio/x-speex" + << "audio/x-vorbis" + << "audio/x-vorbis+ogg" + << "audio/x-wav" + << "video/x-ms-asf" + << "x-content/audio-player"; + return res; + +} + +void Mpris2::Raise() { emit RaiseMainWindow(); } + +void Mpris2::Quit() { qApp->quit(); } + +QString Mpris2::PlaybackStatus() const { + return PlaybackStatus(app_->player()->GetState()); +} + +QString Mpris2::PlaybackStatus(Engine::State state) const { + + switch (state) { + case Engine::Playing: return "Playing"; + case Engine::Paused: return "Paused"; + default: return "Stopped"; + } + +} + +QString Mpris2::LoopStatus() const { + + if (!app_->playlist_manager()->sequence()) { + return "None"; + } + + switch (app_->playlist_manager()->sequence()->repeat_mode()) { + case PlaylistSequence::Repeat_Album: + case PlaylistSequence::Repeat_Playlist: return "Playlist"; + case PlaylistSequence::Repeat_Track: return "Track"; + default: return "None"; + } + +} + +void Mpris2::SetLoopStatus(const QString &value) { + + PlaylistSequence::RepeatMode mode = PlaylistSequence::Repeat_Off; + + if (value == "None") { + mode = PlaylistSequence::Repeat_Off; + } else if (value == "Track") { + mode = PlaylistSequence::Repeat_Track; + } else if (value == "Playlist") { + mode = PlaylistSequence::Repeat_Playlist; + } + + app_->playlist_manager()->active()->sequence()->SetRepeatMode(mode); + +} + +double Mpris2::Rate() const { return 1.0; } + +void Mpris2::SetRate(double rate) { + + if (rate == 0) { + app_->player()->Pause(); + } + +} + +bool Mpris2::Shuffle() const { + + return app_->playlist_manager()->sequence()->shuffle_mode() != PlaylistSequence::Shuffle_Off; + +} + +void Mpris2::SetShuffle(bool enable) { + app_->playlist_manager()->active()->sequence()->SetShuffleMode(enable ? PlaylistSequence::Shuffle_All : PlaylistSequence::Shuffle_Off); +} + +QVariantMap Mpris2::Metadata() const { return last_metadata_; } + +QString Mpris2::current_track_id() const { + return QString("/org/strawberry/strawberrymusicplayer/Track/%1").arg(QString::number(app_->playlist_manager()->active()->current_row())); +} + +// We send Metadata change notification as soon as the process of +// changing song starts... +void Mpris2::CurrentSongChanged(const Song &song) { + + ArtLoaded(song, ""); + EmitNotification("CanGoNext", CanGoNext()); + EmitNotification("CanGoPrevious", CanGoPrevious()); + EmitNotification("CanSeek", CanSeek()); + +} + +// ... and we add the cover information later, when it's available. +void Mpris2::ArtLoaded(const Song &song, const QString &art_uri) { + + last_metadata_ = QVariantMap(); + song.ToXesam(&last_metadata_); + + using mpris::AddMetadata; + AddMetadata("mpris:trackid", current_track_id(), &last_metadata_); + + if (!art_uri.isEmpty()) { + AddMetadata("mpris:artUrl", art_uri, &last_metadata_); + } + + AddMetadata("year", song.year(), &last_metadata_); + AddMetadata("bitrate", song.bitrate(), &last_metadata_); + + EmitNotification("Metadata", last_metadata_); + +} + +double Mpris2::Volume() const { return app_->player()->GetVolume() / 100.0; } + +void Mpris2::SetVolume(double value) { app_->player()->SetVolume(value * 100); } + +qlonglong Mpris2::Position() const { + return app_->player()->engine()->position_nanosec() / kNsecPerUsec; +} + +double Mpris2::MaximumRate() const { return 1.0; } + +double Mpris2::MinimumRate() const { return 1.0; } + +bool Mpris2::CanGoNext() const { + return app_->playlist_manager()->active() && app_->playlist_manager()->active()->next_row() != -1; +} + +bool Mpris2::CanGoPrevious() const { + return app_->playlist_manager()->active() && (app_->playlist_manager()->active()->previous_row() != -1 || app_->player()->PreviousWouldRestartTrack()); +} + +bool Mpris2::CanPlay() const { + return app_->playlist_manager()->active() && app_->playlist_manager()->active()->rowCount() != 0 && !(app_->player()->GetState() == Engine::Playing); +} + +// This one's a bit different than MPRIS 1 - we want this to be true even when +// the song is already paused or stopped. +bool Mpris2::CanPause() const { + return (app_->player()->GetCurrentItem() && app_->player()->GetState() == Engine::Playing && !(app_->player()->GetCurrentItem()->options() & PlaylistItem::PauseDisabled)) || PlaybackStatus() == "Paused" || PlaybackStatus() == "Stopped"; +} + +bool Mpris2::CanSeek() const { return CanSeek(app_->player()->GetState()); } + +bool Mpris2::CanSeek(Engine::State state) const { + return app_->player()->GetCurrentItem() && state != Engine::Empty; +} + +bool Mpris2::CanControl() const { return true; } + +void Mpris2::Next() { + if (CanGoNext()) { + app_->player()->Next(); + } +} + +void Mpris2::Previous() { + if (CanGoPrevious()) { + app_->player()->Previous(); + } +} + +void Mpris2::Pause() { + if (CanPause() && app_->player()->GetState() != Engine::Paused) { + app_->player()->Pause(); + } +} + +void Mpris2::PlayPause() { + if (CanPause()) { + app_->player()->PlayPause(); + } +} + +void Mpris2::Stop() { app_->player()->Stop(); } + +void Mpris2::Play() { + if (CanPlay()) { + app_->player()->Play(); + } +} + +void Mpris2::Seek(qlonglong offset) { + if (CanSeek()) { + app_->player()->SeekTo(app_->player()->engine()->position_nanosec() / kNsecPerSec + offset / kUsecPerSec); + } +} + +void Mpris2::SetPosition(const QDBusObjectPath &trackId, qlonglong offset) { + if (CanSeek() && trackId.path() == current_track_id() && offset >= 0) { + offset *= kNsecPerUsec; + + if(offset < app_->player()->GetCurrentItem()->Metadata().length_nanosec()) { + app_->player()->SeekTo(offset / kNsecPerSec); + } + } +} + +void Mpris2::OpenUri(const QString &uri) { + app_->playlist_manager()->active()->InsertUrls(QList() << QUrl(uri), -1, true); +} + +TrackIds Mpris2::Tracks() const { + // TODO + return TrackIds(); +} + +bool Mpris2::CanEditTracks() const { return false; } + +TrackMetadata Mpris2::GetTracksMetadata(const TrackIds &tracks) const { + // TODO + return TrackMetadata(); +} + +void Mpris2::AddTrack(const QString &uri, const QDBusObjectPath &afterTrack, bool setAsCurrent) { + // TODO +} + +void Mpris2::RemoveTrack(const QDBusObjectPath &trackId) { + // TODO +} + +void Mpris2::GoTo(const QDBusObjectPath &trackId) { + // TODO +} + +quint32 Mpris2::PlaylistCount() const { + return app_->playlist_manager()->GetAllPlaylists().size(); +} + +QStringList Mpris2::Orderings() const { return QStringList() << "User"; } + +namespace { + +QDBusObjectPath MakePlaylistPath(int id) { + return QDBusObjectPath(QString("/org/strawberry/strawberrymusicplayer/PlaylistId/%1").arg(id)); +} + +} + +MaybePlaylist Mpris2::ActivePlaylist() const { + + MaybePlaylist maybe_playlist; + Playlist* current_playlist = app_->playlist_manager()->current(); + maybe_playlist.valid = current_playlist; + if (!current_playlist) { + return maybe_playlist; + } + + maybe_playlist.playlist.id = MakePlaylistPath(current_playlist->id()); + maybe_playlist.playlist.name = app_->playlist_manager()->GetPlaylistName(current_playlist->id()); + return maybe_playlist; + +} + +void Mpris2::ActivatePlaylist(const QDBusObjectPath &playlist_id) { + + QStringList split_path = playlist_id.path().split('/'); + qLog(Debug) << Q_FUNC_INFO << playlist_id.path() << split_path; + if (split_path.isEmpty()) { + return; + } + bool ok = false; + int p = split_path.last().toInt(&ok); + if (!ok) { + return; + } + if (!app_->playlist_manager()->IsPlaylistOpen(p)) { + qLog(Error) << "Playlist isn't opened!"; + return; + } + app_->playlist_manager()->SetActivePlaylist(p); + app_->player()->Next(); + +} + +// TODO: Support sort orders. +MprisPlaylistList Mpris2::GetPlaylists(quint32 index, quint32 max_count, const QString &order, bool reverse_order) { + + MprisPlaylistList ret; + for (Playlist* p : app_->playlist_manager()->GetAllPlaylists()) { + MprisPlaylist mpris_playlist; + mpris_playlist.id = MakePlaylistPath(p->id()); + mpris_playlist.name = app_->playlist_manager()->GetPlaylistName(p->id()); + ret << mpris_playlist; + } + + if (reverse_order) { + std::reverse(ret.begin(), ret.end()); + } + + return ret.mid(index, max_count); + +} + +void Mpris2::PlaylistChanged(Playlist* playlist) { + + MprisPlaylist mpris_playlist; + mpris_playlist.id = MakePlaylistPath(playlist->id()); + mpris_playlist.name = app_->playlist_manager()->GetPlaylistName(playlist->id()); + emit PlaylistChanged(mpris_playlist); + +} + +void Mpris2::PlaylistCollectionChanged(Playlist* playlist) { + EmitNotification("PlaylistCount", "", "org.mpris.MediaPlayer2.Playlists"); +} + +} // namespace mpris + diff --git a/src/core/mpris2.h b/src/core/mpris2.h new file mode 100644 index 00000000..607272a8 --- /dev/null +++ b/src/core/mpris2.h @@ -0,0 +1,234 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MPRIS2_H +#define MPRIS2_H + +#include "config.h" + +#include "playlist/playlistitem.h" + +#include +#include +#include + +class Application; +class MainWindow; +class Playlist; + +typedef QList TrackMetadata; +typedef QList TrackIds; +Q_DECLARE_METATYPE(TrackMetadata); + +struct MprisPlaylist { + QDBusObjectPath id; + QString name; + QString icon; // Uri +}; +typedef QList MprisPlaylistList; +Q_DECLARE_METATYPE(MprisPlaylist); +Q_DECLARE_METATYPE(MprisPlaylistList); + +struct MaybePlaylist { + bool valid; + MprisPlaylist playlist; +}; +Q_DECLARE_METATYPE(MaybePlaylist); + +QDBusArgument &operator<<(QDBusArgument &arg, const MprisPlaylist &playlist); +const QDBusArgument &operator>> (const QDBusArgument &arg, MprisPlaylist &playlist); + +QDBusArgument &operator<<(QDBusArgument &arg, const MaybePlaylist &playlist); +const QDBusArgument &operator>> (const QDBusArgument &arg, MaybePlaylist &playlist); + +QDBusArgument &operator<< (QDBusArgument &arg, const QImage &image); +const QDBusArgument &operator>> (const QDBusArgument &arg, QImage &image); + +namespace mpris { + +class Mpris2 : public QObject { + Q_OBJECT + + public: + Mpris2(Application *app, QObject *parent = nullptr); + + // org.mpris.MediaPlayer2 MPRIS 2.0 Root interface + Q_PROPERTY(bool CanQuit READ CanQuit) + Q_PROPERTY(bool CanRaise READ CanRaise) + Q_PROPERTY(bool HasTrackList READ HasTrackList) + Q_PROPERTY(QString Identity READ Identity) + Q_PROPERTY(QString DesktopEntry READ DesktopEntry) + Q_PROPERTY(QStringList SupportedUriSchemes READ SupportedUriSchemes) + Q_PROPERTY(QStringList SupportedMimeTypes READ SupportedMimeTypes) + + // org.mpris.MediaPlayer2 MPRIS 2.2 Root interface + Q_PROPERTY(bool CanSetFullscreen READ CanSetFullscreen) + Q_PROPERTY(bool Fullscreen READ Fullscreen WRITE SetFullscreen) + + // org.mpris.MediaPlayer2.Player MPRIS 2.0 Player interface + Q_PROPERTY(QString PlaybackStatus READ PlaybackStatus) + Q_PROPERTY(QString LoopStatus READ LoopStatus WRITE SetLoopStatus) + Q_PROPERTY(double Rate READ Rate WRITE SetRate) + Q_PROPERTY(bool Shuffle READ Shuffle WRITE SetShuffle) + Q_PROPERTY(QVariantMap Metadata READ Metadata) + Q_PROPERTY(double Volume READ Volume WRITE SetVolume) + Q_PROPERTY(qlonglong Position READ Position) + Q_PROPERTY(double MinimumRate READ MinimumRate) + Q_PROPERTY(double MaximumRate READ MaximumRate) + Q_PROPERTY(bool CanGoNext READ CanGoNext) + Q_PROPERTY(bool CanGoPrevious READ CanGoPrevious) + Q_PROPERTY(bool CanPlay READ CanPlay) + Q_PROPERTY(bool CanPause READ CanPause) + Q_PROPERTY(bool CanSeek READ CanSeek) + Q_PROPERTY(bool CanControl READ CanControl) + + // org.mpris.MediaPlayer2.TrackList MPRIS 2.0 Player interface + Q_PROPERTY(TrackIds Tracks READ Tracks) + Q_PROPERTY(bool CanEditTracks READ CanEditTracks) + + // org.mpris.MediaPlayer2.Playlists MPRIS 2.1 Playlists interface + Q_PROPERTY(quint32 PlaylistCount READ PlaylistCount) + Q_PROPERTY(QStringList Orderings READ Orderings) + Q_PROPERTY(MaybePlaylist ActivePlaylist READ ActivePlaylist) + + // Root Properties + bool CanQuit() const; + bool CanRaise() const; + bool HasTrackList() const; + QString Identity() const; + QString DesktopEntry() const; + QStringList SupportedUriSchemes() const; + QStringList SupportedMimeTypes() const; + + // Root Properties added in MPRIS 2.2 + bool CanSetFullscreen() const { return false; } + bool Fullscreen() const { return false; } + void SetFullscreen(bool) {} + + // Methods + void Raise(); + void Quit(); + + // Player Properties + QString PlaybackStatus() const; + QString LoopStatus() const; + void SetLoopStatus(const QString &value); + double Rate() const; + void SetRate(double value); + bool Shuffle() const; + void SetShuffle(bool value); + QVariantMap Metadata() const; + double Volume() const; + void SetVolume(double value); + qlonglong Position() const; + double MaximumRate() const; + double MinimumRate() const; + bool CanGoNext() const; + bool CanGoPrevious() const; + bool CanPlay() const; + bool CanPause() const; + bool CanSeek() const; + bool CanControl() const; + + // Methods + void Next(); + void Previous(); + void Pause(); + void PlayPause(); + void Stop(); + void Play(); + void Seek(qlonglong offset); + void SetPosition(const QDBusObjectPath &trackId, qlonglong offset); + void OpenUri(const QString &uri); + + // TrackList Properties + TrackIds Tracks() const; + bool CanEditTracks() const; + + // Methods + TrackMetadata GetTracksMetadata(const TrackIds &tracks) const; + void AddTrack(const QString &uri, const QDBusObjectPath &afterTrack, bool setAsCurrent); + void RemoveTrack(const QDBusObjectPath &trackId); + void GoTo(const QDBusObjectPath &trackId); + + // Playlist Properties + quint32 PlaylistCount() const; + QStringList Orderings() const; + MaybePlaylist ActivePlaylist() const; + + // Methods + void ActivatePlaylist(const QDBusObjectPath &playlist_id); + QList GetPlaylists(quint32 index, quint32 max_count, const QString &order, bool reverse_order); + +signals: + // Player + void Seeked(qlonglong position); + + // TrackList + void TrackListReplaced(const TrackIds &Tracks, QDBusObjectPath CurrentTrack); + void TrackAdded(const TrackMetadata &Metadata, QDBusObjectPath AfterTrack); + void TrackRemoved(const QDBusObjectPath &trackId); + void TrackMetadataChanged(const QDBusObjectPath &trackId, const TrackMetadata &metadata); + + void RaiseMainWindow(); + + // Playlist + void PlaylistChanged(const MprisPlaylist &playlist); + + private slots: + void ArtLoaded(const Song &song, const QString &art_uri); + void EngineStateChanged(Engine::State newState); + void VolumeChanged(); + + void PlaylistManagerInitialized(); + void CurrentSongChanged(const Song &song); + void ShuffleModeChanged(); + void RepeatModeChanged(); + void PlaylistChanged(Playlist *playlist); + void PlaylistCollectionChanged(Playlist *playlist); + + private: + void EmitNotification(const QString &name); + void EmitNotification(const QString &name, const QVariant &val); + void EmitNotification(const QString &name, const QVariant &val, const QString &mprisEntity); + + QString PlaybackStatus(Engine::State state) const; + + QString current_track_id() const; + + bool CanSeek(Engine::State state) const; + + QString DesktopEntryAbsolutePath() const; + + private: + static const char *kMprisObjectPath; + static const char *kServiceName; + static const char *kFreedesktopPath; + + QVariantMap last_metadata_; + + Application *app_; + +}; + +} // namespace mpris + + +#endif diff --git a/src/core/mpris_common.h b/src/core/mpris_common.h new file mode 100644 index 00000000..600d67c8 --- /dev/null +++ b/src/core/mpris_common.h @@ -0,0 +1,64 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MPRIS_COMMON_H +#define MPRIS_COMMON_H + +#include "config.h" + +#include +#include +#include +#include + +namespace mpris { + +inline void AddMetadata(const QString &key, const QString &metadata, QVariantMap *map) { + if (!metadata.isEmpty()) (*map)[key] = metadata; +} + +inline void AddMetadataAsList(const QString &key, const QString &metadata, QVariantMap *map) { + if (!metadata.isEmpty()) (*map)[key] = QStringList() << metadata; +} + +inline void AddMetadata(const QString &key, int metadata, QVariantMap *map) { + if (metadata > 0) (*map)[key] = metadata; +} + +inline void AddMetadata(const QString &key, qint64 metadata, QVariantMap *map) { + if (metadata > 0) (*map)[key] = metadata; +} + +inline void AddMetadata(const QString &key, double metadata, QVariantMap *map) { + if (metadata != 0.0) (*map)[key] = metadata; +} + +inline void AddMetadata(const QString &key, const QDateTime &metadata, QVariantMap *map) { + if (metadata.isValid()) (*map)[key] = metadata; +} + +inline QString AsMPRISDateTimeType(uint time) { + return time != -1 ? QDateTime::fromTime_t(time).toString(Qt::ISODate) : ""; +} + +} // namespace mpris + +#endif // MPRIS_COMMON_H + diff --git a/src/core/multisortfilterproxy.cpp b/src/core/multisortfilterproxy.cpp new file mode 100644 index 00000000..777759ac --- /dev/null +++ b/src/core/multisortfilterproxy.cpp @@ -0,0 +1,90 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "multisortfilterproxy.h" +#include "core/logging.h" + +#include +#include +#include + +MultiSortFilterProxy::MultiSortFilterProxy(QObject *parent) + : QSortFilterProxyModel(parent) {} + +void MultiSortFilterProxy::AddSortSpec(int role, Qt::SortOrder order) { + sorting_ << SortSpec(role, order); +} + +bool MultiSortFilterProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { + + for (const SortSpec &spec : sorting_) { + const int ret = Compare(left.data(spec.first), right.data(spec.first)); + + if (ret < 0) { + return spec.second == Qt::AscendingOrder; + } + else if (ret > 0) { + return spec.second != Qt::AscendingOrder; + } + } + + return left.row() < right.row(); + +} + +template +static inline int DoCompare(T left, T right) { + + if (left < right) return -1; + if (left > right) return 1; + return 0; + +} + +int MultiSortFilterProxy::Compare(const QVariant &left, const QVariant &right) const { + + // Copied from the QSortFilterProxyModel::lessThan implementation, but returns + // -1, 0 or 1 instead of true or false. + switch (left.userType()) { + case QVariant::Invalid: return (right.type() != QVariant::Invalid) ? -1 : 0; + case QVariant::Int: return DoCompare(left.toInt(), right.toInt()); + case QVariant::UInt: return DoCompare(left.toUInt(), right.toUInt()); + case QVariant::LongLong: return DoCompare(left.toLongLong(), right.toLongLong()); + case QVariant::ULongLong: return DoCompare(left.toULongLong(), right.toULongLong()); + case QMetaType::Float: return DoCompare(left.toFloat(), right.toFloat()); + case QVariant::Double: return DoCompare(left.toDouble(), right.toDouble()); + case QVariant::Char: return DoCompare(left.toChar(), right.toChar()); + case QVariant::Date: return DoCompare(left.toDate(), right.toDate()); + case QVariant::Time: return DoCompare(left.toTime(), right.toTime()); + case QVariant::DateTime: return DoCompare(left.toDateTime(), right.toDateTime()); + case QVariant::String: + default: + if (isSortLocaleAware()) + return left.toString().localeAwareCompare(right.toString()); + else + return left.toString().compare(right.toString(), sortCaseSensitivity()); + } + + return 0; + +} + diff --git a/src/core/multisortfilterproxy.h b/src/core/multisortfilterproxy.h new file mode 100644 index 00000000..1f770aea --- /dev/null +++ b/src/core/multisortfilterproxy.h @@ -0,0 +1,45 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MULTISORTFILTERPROXY_H +#define MULTISORTFILTERPROXY_H + +#include "config.h" + +#include + +class MultiSortFilterProxy : public QSortFilterProxyModel { + public: + MultiSortFilterProxy(QObject *parent = nullptr); + + void AddSortSpec(int role, Qt::SortOrder order = Qt::AscendingOrder); + + protected: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + + private: + int Compare(const QVariant &left, const QVariant &right) const; + + typedef QPair SortSpec; + QList sorting_; +}; + +#endif // MULTISORTFILTERPROXY_H + diff --git a/src/core/musicstorage.cpp b/src/core/musicstorage.cpp new file mode 100644 index 00000000..7ae5849b --- /dev/null +++ b/src/core/musicstorage.cpp @@ -0,0 +1,28 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "musicstorage.h" + +MusicStorage::MusicStorage() +{ +} + diff --git a/src/core/musicstorage.h b/src/core/musicstorage.h new file mode 100644 index 00000000..1e5507cc --- /dev/null +++ b/src/core/musicstorage.h @@ -0,0 +1,89 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MUSICSTORAGE_H +#define MUSICSTORAGE_H + +#include "config.h" + +#include "song.h" + +#include +#include + +#include + +class MusicStorage { + public: + MusicStorage(); + virtual ~MusicStorage() {} + + enum Role { + Role_Storage = Qt::UserRole + 100, + Role_StorageForceConnect, + Role_Capacity, + Role_FreeSpace, + }; + + // Values are saved in the database - don't change + enum TranscodeMode { + Transcode_Always = 1, + Transcode_Never = 2, + Transcode_Unsupported = 3, + }; + + typedef std::function ProgressFunction; + + struct CopyJob { + QString source_; + QString destination_; + Song metadata_; + bool overwrite_; + bool mark_as_listened_; + bool remove_original_; + ProgressFunction progress_; + }; + + struct DeleteJob { + Song metadata_; + }; + + virtual QString LocalPath() const { return QString(); } + + virtual TranscodeMode GetTranscodeMode() const { return Transcode_Never; } + virtual Song::FileType GetTranscodeFormat() const { return Song::Type_Unknown; } + virtual bool GetSupportedFiletypes(QList* ret) { return true; } + + virtual bool StartCopy(QList* supported_types) { return true;} + virtual bool CopyToStorage(const CopyJob& job) = 0; + virtual void FinishCopy(bool success) {} + + virtual void StartDelete() {} + virtual bool DeleteFromStorage(const DeleteJob& job) = 0; + virtual void FinishDelete(bool success) {} + + virtual void Eject() {} +}; + +Q_DECLARE_METATYPE(MusicStorage*); +Q_DECLARE_METATYPE(std::shared_ptr); + +#endif // MUSICSTORAGE_H + diff --git a/src/core/network.cpp b/src/core/network.cpp new file mode 100644 index 00000000..1a1a76ac --- /dev/null +++ b/src/core/network.cpp @@ -0,0 +1,224 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "network.h" + +#include +#include +#include +#include +#include + +#include "core/closure.h" +#include "utilities.h" + +QMutex ThreadSafeNetworkDiskCache::sMutex; +QNetworkDiskCache *ThreadSafeNetworkDiskCache::sCache = nullptr; + +ThreadSafeNetworkDiskCache::ThreadSafeNetworkDiskCache(QObject *parent) +: QAbstractNetworkCache(parent) { + QMutexLocker l(&sMutex); + if (!sCache) { + sCache = new QNetworkDiskCache; + sCache->setCacheDirectory(Utilities::GetConfigPath(Utilities::Path_NetworkCache)); + } +} + +qint64 ThreadSafeNetworkDiskCache::cacheSize() const { + QMutexLocker l(&sMutex); + return sCache->cacheSize(); +} + +QIODevice *ThreadSafeNetworkDiskCache::data(const QUrl &url) { + QMutexLocker l(&sMutex); + return sCache->data(url); +} + +void ThreadSafeNetworkDiskCache::insert(QIODevice *device) { + QMutexLocker l(&sMutex); + sCache->insert(device); +} + +QNetworkCacheMetaData ThreadSafeNetworkDiskCache::metaData(const QUrl &url) { + QMutexLocker l(&sMutex); + return sCache->metaData(url); +} + +QIODevice *ThreadSafeNetworkDiskCache::prepare(const QNetworkCacheMetaData &metaData) { + QMutexLocker l(&sMutex); + return sCache->prepare(metaData); +} + +bool ThreadSafeNetworkDiskCache::remove(const QUrl &url) { + QMutexLocker l(&sMutex); + return sCache->remove(url); +} + +void ThreadSafeNetworkDiskCache::updateMetaData(const QNetworkCacheMetaData &metaData) { + QMutexLocker l(&sMutex); + sCache->updateMetaData(metaData); +} + +void ThreadSafeNetworkDiskCache::clear() { + QMutexLocker l(&sMutex); + sCache->clear(); +} + +NetworkAccessManager::NetworkAccessManager(QObject *parent) + : QNetworkAccessManager(parent) { + setCache(new ThreadSafeNetworkDiskCache(this)); +} + +QNetworkReply *NetworkAccessManager::createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) { + + QByteArray user_agent = QString("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion()).toUtf8(); + + if (request.hasRawHeader("User-Agent")) { + // Append the existing user-agent set by a client collection (such as + // libmygpo-qt). + user_agent += " " + request.rawHeader("User-Agent"); + } + + QNetworkRequest new_request(request); + new_request.setRawHeader("User-Agent", user_agent); + + if (op == QNetworkAccessManager::PostOperation && + !new_request.header(QNetworkRequest::ContentTypeHeader).isValid()) { + new_request.setHeader(QNetworkRequest::ContentTypeHeader, + "application/x-www-form-urlencoded"); + } + + // Prefer the cache unless the caller has changed the setting already + if (request.attribute(QNetworkRequest::CacheLoadControlAttribute).toInt() == QNetworkRequest::PreferNetwork) { + new_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + } + + return QNetworkAccessManager::createRequest(op, new_request, outgoingData); +} + + +NetworkTimeouts::NetworkTimeouts(int timeout_msec, QObject *parent) + : QObject(parent), timeout_msec_(timeout_msec) {} + +void NetworkTimeouts::AddReply(QNetworkReply *reply) { + + if (timers_.contains(reply)) return; + + connect(reply, SIGNAL(destroyed()), SLOT(ReplyFinished())); + connect(reply, SIGNAL(finished()), SLOT(ReplyFinished())); + timers_[reply] = startTimer(timeout_msec_); + +} + +void NetworkTimeouts::AddReply(RedirectFollower *reply) { + + if (redirect_timers_.contains(reply)) { + return; + } + + NewClosure(reply, SIGNAL(destroyed()), this, SLOT(RedirectFinished(RedirectFollower*)), reply); + NewClosure(reply, SIGNAL(finished()), this, SLOT(RedirectFinished(RedirectFollower*)), reply); + redirect_timers_[reply] = startTimer(timeout_msec_); + +} + +void NetworkTimeouts::ReplyFinished() { + + QNetworkReply *reply = reinterpret_cast(sender()); + if (timers_.contains(reply)) { + killTimer(timers_.take(reply)); + } + +} + +void NetworkTimeouts::RedirectFinished(RedirectFollower *reply) { + + if (redirect_timers_.contains(reply)) { + killTimer(redirect_timers_.take(reply)); + } + +} + +void NetworkTimeouts::timerEvent(QTimerEvent *e) { + + QNetworkReply *reply = timers_.key(e->timerId()); + if (reply) { + reply->abort(); + } + + RedirectFollower *redirect = redirect_timers_.key(e->timerId()); + if (redirect) { + redirect->abort(); + } + +} + + +RedirectFollower::RedirectFollower(QNetworkReply *first_reply, int max_redirects) : QObject(nullptr), current_reply_(first_reply), redirects_remaining_(max_redirects) { + ConnectReply(first_reply); +} + +void RedirectFollower::ConnectReply(QNetworkReply *reply) { + + connect(reply, SIGNAL(readyRead()), SLOT(ReadyRead())); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), SIGNAL(error(QNetworkReply::NetworkError))); + connect(reply, SIGNAL(downloadProgress(qint64,qint64)), SIGNAL(downloadProgress(qint64,qint64))); + connect(reply, SIGNAL(uploadProgress(qint64,qint64)), SIGNAL(uploadProgress(qint64,qint64))); + connect(reply, SIGNAL(finished()), SLOT(ReplyFinished())); + +} + +void RedirectFollower::ReadyRead() { + + // Don't re-emit this signal for redirect replies. + if (current_reply_->attribute(QNetworkRequest::RedirectionTargetAttribute).isValid()) { + return; + } + + emit readyRead(); + +} + +void RedirectFollower::ReplyFinished() { + + current_reply_->deleteLater(); + + if (current_reply_->attribute(QNetworkRequest::RedirectionTargetAttribute).isValid()) { + if (redirects_remaining_-- == 0) { + emit finished(); + return; + } + + const QUrl next_url = current_reply_->url().resolved(current_reply_->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl()); + + QNetworkRequest req(current_reply_->request()); + req.setUrl(next_url); + + current_reply_ = current_reply_->manager()->get(req); + ConnectReply(current_reply_); + return; + } + + emit finished(); + +} + diff --git a/src/core/network.h b/src/core/network.h new file mode 100644 index 00000000..1b8e69e8 --- /dev/null +++ b/src/core/network.h @@ -0,0 +1,128 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef NETWORK_H +#define NETWORK_H + +#include "config.h" + +#include +#include +#include +#include + +class QNetworkDiskCache; + +class ThreadSafeNetworkDiskCache : public QAbstractNetworkCache { + public: + explicit ThreadSafeNetworkDiskCache(QObject *parent); + + qint64 cacheSize() const; + QIODevice *data(const QUrl &url); + void insert(QIODevice *device); + QNetworkCacheMetaData metaData(const QUrl &url); + QIODevice *prepare(const QNetworkCacheMetaData &metaData); + bool remove(const QUrl &url); + void updateMetaData(const QNetworkCacheMetaData &metaData); + + void clear(); + + private: + static QMutex sMutex; + static QNetworkDiskCache *sCache; +}; + +class NetworkAccessManager : public QNetworkAccessManager { + Q_OBJECT + + public: + explicit NetworkAccessManager(QObject *parent = nullptr); + + protected: + QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData); +}; + +class RedirectFollower : public QObject { + Q_OBJECT + + public: + explicit RedirectFollower(QNetworkReply *first_reply, int max_redirects = 5); + + bool hit_redirect_limit() const { return redirects_remaining_ < 0; } + QNetworkReply *reply() const { return current_reply_; } + + // These are all forwarded to the current reply. + QNetworkReply::NetworkError error() const { return current_reply_->error(); } + QString errorString() const { return current_reply_->errorString(); } + QVariant attribute(QNetworkRequest::Attribute code) const { return current_reply_->attribute(code); } + QVariant header(QNetworkRequest::KnownHeaders header) const { return current_reply_->header(header); } + qint64 bytesAvailable() const { return current_reply_->bytesAvailable(); } + QUrl url() const { return current_reply_->url(); } + QByteArray readAll() { return current_reply_->readAll(); } + void abort() { current_reply_->abort(); } + +signals: + // These are all forwarded from the current reply. + void readyRead(); + void error(QNetworkReply::NetworkError); + void uploadProgress(qint64 bytesSent, qint64 bytesTotal); + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + + // This is NOT emitted when a request that has a redirect finishes. + void finished(); + + private slots: + void ReadyRead(); + void ReplyFinished(); + + private: + void ConnectReply(QNetworkReply *reply); + + private: + QNetworkReply *current_reply_; + int redirects_remaining_; +}; + +class NetworkTimeouts : public QObject { + Q_OBJECT + + public: + explicit NetworkTimeouts(int timeout_msec, QObject *parent = nullptr); + + // TODO: Template this to avoid code duplication. + void AddReply(QNetworkReply *reply); + void AddReply(RedirectFollower *reply); + void SetTimeout(int msec) { timeout_msec_ = msec; } + + protected: + void timerEvent(QTimerEvent *e); + + private slots: + void ReplyFinished(); + void RedirectFinished(RedirectFollower *redirect); + + private: + int timeout_msec_; + QMap timers_; + QMap redirect_timers_; +}; + +#endif // NETWORK_H + diff --git a/src/core/networkproxyfactory.cpp b/src/core/networkproxyfactory.cpp new file mode 100644 index 00000000..70b45a66 --- /dev/null +++ b/src/core/networkproxyfactory.cpp @@ -0,0 +1,142 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "networkproxyfactory.h" + +#include + +#include +#include +#include +#include + +#include "core/logging.h" + +NetworkProxyFactory *NetworkProxyFactory::sInstance = nullptr; +const char *NetworkProxyFactory::kSettingsGroup = "NetworkProxy"; + +NetworkProxyFactory::NetworkProxyFactory() + : mode_(Mode_System), + type_(QNetworkProxy::HttpProxy), + port_(8080), + use_authentication_(false) { +#ifdef Q_OS_LINUX + // Linux uses environment variables to pass proxy configuration information, + // which systemProxyForQuery doesn't support for some reason. + + QStringList urls; + urls << QString::fromLocal8Bit(getenv("HTTP_PROXY")); + urls << QString::fromLocal8Bit(getenv("http_proxy")); + urls << QString::fromLocal8Bit(getenv("ALL_PROXY")); + urls << QString::fromLocal8Bit(getenv("all_proxy")); + + qLog(Debug) << "Detected system proxy URLs:" << urls; + + for (const QString &url_str : urls) { + + if (url_str.isEmpty()) continue; + env_url_ = QUrl(url_str); + break; + + } +#endif + + ReloadSettings(); + +} + +NetworkProxyFactory *NetworkProxyFactory::Instance() { + + if (!sInstance) { + sInstance = new NetworkProxyFactory; + } + + return sInstance; + +} + +void NetworkProxyFactory::ReloadSettings() { + + QMutexLocker l(&mutex_); + + QSettings s; + s.beginGroup(kSettingsGroup); + + 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(); + use_authentication_ = s.value("use_authentication", false).toBool(); + username_ = s.value("username").toString(); + password_ = s.value("password").toString(); + +} + +QList NetworkProxyFactory::queryProxy(const QNetworkProxyQuery &query) { + + QMutexLocker l(&mutex_); + + QNetworkProxy ret; + + switch (mode_) { + case Mode_System: +#ifdef Q_OS_LINUX + Q_UNUSED(query); + + if (env_url_.isEmpty()) { + ret.setType(QNetworkProxy::NoProxy); + } + else { + ret.setHostName(env_url_.host()); + ret.setPort(env_url_.port()); + ret.setUser(env_url_.userName()); + ret.setPassword(env_url_.password()); + if (env_url_.scheme().startsWith("http")) + ret.setType(QNetworkProxy::HttpProxy); + else + ret.setType(QNetworkProxy::Socks5Proxy); + qLog(Debug) << "Using proxy URL:" << env_url_; + } + break; +#else + return systemProxyForQuery(query); +#endif + + case Mode_Direct: + ret.setType(QNetworkProxy::NoProxy); + break; + + case Mode_Manual: + ret.setType(type_); + ret.setHostName(hostname_); + ret.setPort(port_); + if (use_authentication_) { + ret.setUser(username_); + ret.setPassword(password_); + } + break; + } + + return QList() << ret; + +} + diff --git a/src/core/networkproxyfactory.h b/src/core/networkproxyfactory.h new file mode 100644 index 00000000..bca6328c --- /dev/null +++ b/src/core/networkproxyfactory.h @@ -0,0 +1,62 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef NETWORKPROXYFACTORY_H +#define NETWORKPROXYFACTORY_H + +#include "config.h" + +#include +#include +#include + +class NetworkProxyFactory : public QNetworkProxyFactory { + public: + // These values are persisted + enum Mode { Mode_System = 0, Mode_Direct = 1, Mode_Manual = 2, }; + + static NetworkProxyFactory *Instance(); + static const char *kSettingsGroup; + + // These methods are thread-safe + void ReloadSettings(); + QList queryProxy(const QNetworkProxyQuery &query); + + private: + NetworkProxyFactory(); + + static NetworkProxyFactory *sInstance; + + QMutex mutex_; + + Mode mode_; + QNetworkProxy::ProxyType type_; + QString hostname_; + int port_; + bool use_authentication_; + QString username_; + QString password_; + +#ifdef Q_OS_LINUX + QUrl env_url_; +#endif +}; + +#endif // NETWORKPROXYFACTORY_H diff --git a/src/core/organise.cpp b/src/core/organise.cpp new file mode 100644 index 00000000..3cb16408 --- /dev/null +++ b/src/core/organise.cpp @@ -0,0 +1,298 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "organise.h" + +#include + +#include +#include +#include +#include +#include + +#include "musicstorage.h" +#include "taskmanager.h" +#include "core/logging.h" +#include "core/tagreaderclient.h" +#include "core/utilities.h" + +using std::placeholders::_1; + +const int Organise::kBatchSize = 10; +const int Organise::kTranscodeProgressInterval = 500; + +Organise::Organise(TaskManager *task_manager, std::shared_ptr destination, const OrganiseFormat &format, bool copy, bool overwrite, bool mark_as_listened, const NewSongInfoList &songs_info, bool eject_after) + : thread_(nullptr), + task_manager_(task_manager), + transcoder_(new Transcoder(this)), + destination_(destination), + format_(format), + copy_(copy), + overwrite_(overwrite), + mark_as_listened_(mark_as_listened), + eject_after_(eject_after), + task_count_(songs_info.count()), + transcode_suffix_(1), + tasks_complete_(0), + started_(false), + task_id_(0), + current_copy_progress_(0) { + + original_thread_ = thread(); + + for (const NewSongInfo &song_info : songs_info) { + tasks_pending_ << Task(song_info); + } + +} + +void Organise::Start() { + + if (thread_) return; + + task_id_ = task_manager_->StartTask(tr("Organising files")); + task_manager_->SetTaskBlocksCollectionScans(true); + + thread_ = new QThread; + connect(thread_, SIGNAL(started()), SLOT(ProcessSomeFiles())); + connect(transcoder_, SIGNAL(JobComplete(QString, QString, bool)), SLOT(FileTranscoded(QString, QString, bool))); + + moveToThread(thread_); + thread_->start(); +} + +void Organise::ProcessSomeFiles() { + + if (!started_) { + transcode_temp_name_.open(); + + if (!destination_->StartCopy(&supported_filetypes_)) { + // Failed to start - mark everything as failed :( + for (const Task &task : tasks_pending_) files_with_errors_ << task.song_info_.song_.url().toLocalFile(); + tasks_pending_.clear(); + } + started_ = true; + } + + // None left? + if (tasks_pending_.isEmpty()) { + if (!tasks_transcoding_.isEmpty()) { + // Just wait - FileTranscoded will start us off again in a little while + qLog(Debug) << "Waiting for transcoding jobs"; + transcode_progress_timer_.start(kTranscodeProgressInterval, this); + return; + } + + UpdateProgress(); + + destination_->FinishCopy(files_with_errors_.isEmpty()); + if (eject_after_) destination_->Eject(); + + task_manager_->SetTaskFinished(task_id_); + + emit Finished(files_with_errors_); + + // Move back to the original thread so deleteLater() can get called in + // the main thread's event loop + moveToThread(original_thread_); + deleteLater(); + + // Stop this thread + thread_->quit(); + return; + } + + // We process files in batches so we can be cancelled part-way through. + for (int i = 0; i < kBatchSize; ++i) { + SetSongProgress(0); + + if (tasks_pending_.isEmpty()) break; + + Task task = tasks_pending_.takeFirst(); + qLog(Info) << "Processing" << task.song_info_.song_.url().toLocalFile(); + + // Use a Song instead of a tag reader + Song song = task.song_info_.song_; + if (!song.is_valid()) continue; + + // Maybe this file is one that's been transcoded already? + if (!task.transcoded_filename_.isEmpty()) { + qLog(Debug) << "This file has already been transcoded"; + + // Set the new filetype on the song so the formatter gets it right + song.set_filetype(task.new_filetype_); + + // Fiddle the filename extension as well to match the new type + song.set_url(QUrl::fromLocalFile(Utilities::FiddleFileExtension(song.basefilename(), task.new_extension_))); + song.set_basefilename(Utilities::FiddleFileExtension(song.basefilename(), task.new_extension_)); + + // Have to set this to the size of the new file or else funny stuff happens + song.set_filesize(QFileInfo(task.transcoded_filename_).size()); + } + else { + // Figure out if we need to transcode it + Song::FileType dest_type = CheckTranscode(song.filetype()); + if (dest_type != Song::Type_Unknown) { + // Get the preset + TranscoderPreset preset = Transcoder::PresetForFileType(dest_type); + qLog(Debug) << "Transcoding with" << preset.name_; + + // Get a temporary name for the transcoded file + task.transcoded_filename_ = transcode_temp_name_.fileName() + "-" + QString::number(transcode_suffix_++); + task.new_extension_ = preset.extension_; + task.new_filetype_ = dest_type; + tasks_transcoding_[task.song_info_.song_.url().toLocalFile()] = task; + + qLog(Debug) << "Transcoding to" << task.transcoded_filename_; + + // Start the transcoding - this will happen in the background and + // FileTranscoded() will get called when it's done. At that point the + // task will get re-added to the pending queue with the new filename. + transcoder_->AddJob(task.song_info_.song_.url().toLocalFile(), preset, task.transcoded_filename_); + transcoder_->Start(); + continue; + } + } + + MusicStorage::CopyJob job; + job.source_ = task.transcoded_filename_.isEmpty() ? task.song_info_.song_.url().toLocalFile() : task.transcoded_filename_; + job.destination_ = task.song_info_.new_filename_; + job.metadata_ = song; + job.overwrite_ = overwrite_; + job.mark_as_listened_ = mark_as_listened_; + job.remove_original_ = !copy_; + job.progress_ = std::bind(&Organise::SetSongProgress, this, _1, !task.transcoded_filename_.isEmpty()); + + if (!destination_->CopyToStorage(job)) { + files_with_errors_ << task.song_info_.song_.basefilename(); + } else { + if (job.mark_as_listened_) { + emit FileCopied(job.metadata_.id()); + } + } + + // Clean up the temporary transcoded file + if (!task.transcoded_filename_.isEmpty()) + QFile::remove(task.transcoded_filename_); + + tasks_complete_++; + } + SetSongProgress(0); + + QTimer::singleShot(0, this, SLOT(ProcessSomeFiles())); + +} + +Song::FileType Organise::CheckTranscode(Song::FileType original_type) const { + + //if (original_type == Song::Type_Stream) return Song::Type_Unknown; + + const MusicStorage::TranscodeMode mode = destination_->GetTranscodeMode(); + const Song::FileType format = destination_->GetTranscodeFormat(); + + switch (mode) { + case MusicStorage::Transcode_Never: + return Song::Type_Unknown; + + case MusicStorage::Transcode_Always: + if (original_type == format) return Song::Type_Unknown; + return format; + + case MusicStorage::Transcode_Unsupported: + if (supported_filetypes_.isEmpty() || supported_filetypes_.contains(original_type)) return Song::Type_Unknown; + + if (format != Song::Type_Unknown) return format; + + // The user hasn't visited the device properties page yet to set a + // preferred format for the device, so we have to pick the best + // available one. + return Transcoder::PickBestFormat(supported_filetypes_); + } + return Song::Type_Unknown; + +} + +void Organise::SetSongProgress(float progress, bool transcoded) { + + const int max = transcoded ? 50 : 100; + current_copy_progress_ = (transcoded ? 50 : 0) + qBound(0, static_cast(progress * max), max - 1); + UpdateProgress(); + +} + +void Organise::UpdateProgress() { + + const int total = task_count_ * 100; + + // Update transcoding progress + QMap transcode_progress = transcoder_->GetProgress(); + for (const QString &filename : transcode_progress.keys()) { + if (!tasks_transcoding_.contains(filename)) continue; + tasks_transcoding_[filename].transcode_progress_ = transcode_progress[filename]; + } + + // Count the progress of all tasks that are in the queue. Files that need + // transcoding total 50 for the transcode and 50 for the copy, files that + // only need to be copied total 100. + int progress = tasks_complete_ * 100; + + for (const Task &task : tasks_pending_) { + progress += qBound(0, static_cast(task.transcode_progress_ * 50), 50); + } + for (const Task &task : tasks_transcoding_.values()) { + progress += qBound(0, static_cast(task.transcode_progress_ * 50), 50); + } + + // Add the progress of the track that's currently copying + progress += current_copy_progress_; + + task_manager_->SetTaskProgress(task_id_, progress, total); + +} + +void Organise::FileTranscoded(const QString &input, const QString &output, bool success) { + + qLog(Info) << "File finished" << input << success; + transcode_progress_timer_.stop(); + + Task task = tasks_transcoding_.take(input); + if (!success) { + files_with_errors_ << input; + } + else { + tasks_pending_ << task; + } + QTimer::singleShot(0, this, SLOT(ProcessSomeFiles())); + +} + +void Organise::timerEvent(QTimerEvent *e) { + + QObject::timerEvent(e); + + if (e->timerId() == transcode_progress_timer_.timerId()) { + UpdateProgress(); + } + +} + diff --git a/src/core/organise.h b/src/core/organise.h new file mode 100644 index 00000000..1429d822 --- /dev/null +++ b/src/core/organise.h @@ -0,0 +1,114 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ORGANISE_H +#define ORGANISE_H + +#include "config.h" + +#include + +#include +#include +#include + +#include "organiseformat.h" +#include "transcoder/transcoder.h" + +class MusicStorage; +class TaskManager; + +class Organise : public QObject { + Q_OBJECT + + public: + struct NewSongInfo { + NewSongInfo(const Song &song = Song(), const QString &new_filename = QString()) : song_(song), new_filename_(new_filename) {} + Song song_; + QString new_filename_; + }; + typedef QList NewSongInfoList; + + Organise(TaskManager *task_manager, std::shared_ptr destination, const OrganiseFormat &format, bool copy, bool overwrite, bool mark_as_listened, const NewSongInfoList &songs, bool eject_after); + + static const int kBatchSize; + static const int kTranscodeProgressInterval; + + void Start(); + +signals: + void Finished(const QStringList &files_with_errors); + void FileCopied(int database_id); + + protected: + void timerEvent(QTimerEvent *e); + + private slots: + void ProcessSomeFiles(); + void FileTranscoded(const QString &input, const QString &output, bool success); + + private: + void SetSongProgress(float progress, bool transcoded = false); + void UpdateProgress(); + Song::FileType CheckTranscode(Song::FileType original_type) const; + + private: + struct Task { + explicit Task(const NewSongInfo &song_info = NewSongInfo()) : song_info_(song_info), transcode_progress_(0.0) {} + + NewSongInfo song_info_; + + float transcode_progress_; + QString transcoded_filename_; + QString new_extension_; + Song::FileType new_filetype_; + }; + + QThread *thread_; + QThread *original_thread_; + TaskManager *task_manager_; + Transcoder *transcoder_; + std::shared_ptr destination_; + QList supported_filetypes_; + + const OrganiseFormat format_; + const bool copy_; + const bool overwrite_; + const bool mark_as_listened_; + const bool eject_after_; + int task_count_; + + QBasicTimer transcode_progress_timer_; + QTemporaryFile transcode_temp_name_; + int transcode_suffix_; + + QList tasks_pending_; + QMap tasks_transcoding_; + int tasks_complete_; + + bool started_; + + int task_id_; + int current_copy_progress_; + + QStringList files_with_errors_; +}; + +#endif // ORGANISE_H diff --git a/src/core/organiseformat.cpp b/src/core/organiseformat.cpp new file mode 100644 index 00000000..3a58cda2 --- /dev/null +++ b/src/core/organiseformat.cpp @@ -0,0 +1,312 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "organiseformat.h" + +#include +#include +#include +#include + +#include "core/arraysize.h" +#include "core/timeconstants.h" +#include "core/utilities.h" +#include "core/logging.h" + +const char *OrganiseFormat::kTagPattern = "\\%([a-zA-Z]*)"; +const char *OrganiseFormat::kBlockPattern = "\\{([^{}]+)\\}"; +const QStringList OrganiseFormat::kKnownTags = QStringList() << "title" + << "album" + << "artist" + << "artistinitial" + << "albumartist" + << "composer" + << "track" + << "disc" + << "year" + << "genre" + << "comment" + << "length" + << "bitrate" + << "samplerate" + << "bitdepth" + << "extension" + << "performer" + << "grouping" + << "originalyear"; + +// From http://en.wikipedia.org/wiki/8.3_filename#Directory_table +const char OrganiseFormat::kInvalidFatCharacters[] = "\"*/\\:<>?|"; +const int OrganiseFormat::kInvalidFatCharactersCount = arraysize(OrganiseFormat::kInvalidFatCharacters) - 1; + +const char OrganiseFormat::kInvalidPrefixCharacters[] = "."; +const int OrganiseFormat::kInvalidPrefixCharactersCount = arraysize(OrganiseFormat::kInvalidPrefixCharacters) - 1; + +const QRgb OrganiseFormat::SyntaxHighlighter::kValidTagColorLight = qRgb(64, 64, 255); +const QRgb OrganiseFormat::SyntaxHighlighter::kInvalidTagColorLight = qRgb(255, 64, 64); +const QRgb OrganiseFormat::SyntaxHighlighter::kBlockColorLight = qRgb(230, 230, 230); + +const QRgb OrganiseFormat::SyntaxHighlighter::kValidTagColorDark = qRgb(128, 128, 255); +const QRgb OrganiseFormat::SyntaxHighlighter::kInvalidTagColorDark = qRgb(255, 128, 128); +const QRgb OrganiseFormat::SyntaxHighlighter::kBlockColorDark = qRgb(64, 64, 64); + +OrganiseFormat::OrganiseFormat(const QString &format) + : format_(format), + replace_non_ascii_(false), + replace_spaces_(false), + replace_the_(false) {} + +void OrganiseFormat::set_format(const QString &v) { + format_ = v; + format_.replace('\\', '/'); +} + +bool OrganiseFormat::IsValid() const { + + int pos = 0; + QString format_copy(format_); + + Validator v; + return v.validate(format_copy, pos) == QValidator::Acceptable; + +} + +QString OrganiseFormat::GetFilenameForSong(const Song &song) const { + + QString filename = ParseBlock(format_, song); + + if (QFileInfo(filename).completeBaseName().isEmpty()) { + // Avoid having empty filenames, or filenames with extension only: in this + // case, keep the original filename. + // We remove the extension from "filename" if it exists, as + // song.basefilename() + // also contains the extension. + filename = + Utilities::PathWithoutFilenameExtension(filename) + song.basefilename(); + } + + if (replace_spaces_) filename.replace(QRegExp("\\s"), "_"); + + if (replace_non_ascii_) { + QString stripped; + for (int i = 0; i < filename.length(); ++i) { + const QCharRef c = filename[i]; + if (c < 128) { + stripped.append(c); + } else { + const QString decomposition = c.decomposition(); + if (!decomposition.isEmpty() && decomposition[0] < 128) + stripped.append(decomposition[0]); + else + stripped.append("_"); + } + } + filename = stripped; + } + + // Fix any parts of the path that start with dots. + QStringList parts = filename.split("/"); + for (int i = 0; i < parts.count(); ++i) { + QString *part = &parts[i]; + for (int j = 0; j < kInvalidPrefixCharactersCount; ++j) { + if (part->startsWith(kInvalidPrefixCharacters[j])) { + part->replace(0, 1, '_'); + break; + } + } + } + + return parts.join("/"); + +} + +QString OrganiseFormat::ParseBlock(QString block, const Song &song, bool *any_empty) const { + + QRegExp tag_regexp(kTagPattern); + QRegExp block_regexp(kBlockPattern); + + // Find any blocks first + int pos = 0; + while ((pos = block_regexp.indexIn(block, pos)) != -1) { + // Recursively parse the block + bool empty = false; + QString value = ParseBlock(block_regexp.cap(1), song, &empty); + if (empty) value = ""; + + // Replace the block's value + block.replace(pos, block_regexp.matchedLength(), value); + pos += value.length(); + } + + // Now look for tags + bool empty = false; + pos = 0; + while ((pos = tag_regexp.indexIn(block, pos)) != -1) { + QString value = TagValue(tag_regexp.cap(1), song); + if (value.isEmpty()) empty = true; + + block.replace(pos, tag_regexp.matchedLength(), value); + pos += value.length(); + } + + if (any_empty) *any_empty = empty; + return block; + +} + +QString OrganiseFormat::TagValue(const QString &tag, const Song &song) const { + + QString value; + + if (tag == "title") + value = song.title(); + else if (tag == "album") + value = song.album(); + else if (tag == "artist") + value = song.artist(); + else if (tag == "composer") + value = song.composer(); + else if (tag == "performer") + value = song.performer(); + else if (tag == "grouping") + value = song.grouping(); + else if (tag == "genre") + value = song.genre(); + else if (tag == "comment") + value = song.comment(); + else if (tag == "year") + value = QString::number(song.year()); + else if (tag == "originalyear") + value = QString::number(song.effective_originalyear()); + else if (tag == "track") + value = QString::number(song.track()); + else if (tag == "disc") + value = QString::number(song.disc()); + else if (tag == "length") + value = QString::number(song.length_nanosec() / kNsecPerSec); + else if (tag == "bitrate") + value = QString::number(song.bitrate()); + else if (tag == "samplerate") value = QString::number(song.samplerate()); + else if (tag == "bitdepth") value = QString::number(song.bitdepth()); + else if (tag == "extension") + value = QFileInfo(song.url().toLocalFile()).suffix(); + else if (tag == "artistinitial") { + value = song.effective_albumartist().trimmed(); + if (replace_the_ && !value.isEmpty()) value.replace(QRegExp("^the\\s+", Qt::CaseInsensitive), ""); + if (!value.isEmpty()) value = value[0].toUpper(); + } + else if (tag == "albumartist") { + value = song.is_compilation() ? "Various Artists" : song.effective_albumartist(); + } + + if (replace_the_ && (tag == "artist" || tag == "albumartist")) + value.replace(QRegExp("^the\\s+", Qt::CaseInsensitive), ""); + + if (value == "0" || value == "-1") value = ""; + + // Prepend a 0 to single-digit track numbers + if (tag == "track" && value.length() == 1) value.prepend('0'); + + // Replace characters that really shouldn't be in paths + for (int i = 0; i < kInvalidFatCharactersCount; ++i) { + value.replace(kInvalidFatCharacters[i], '_'); + } + + return value; +} + +OrganiseFormat::Validator::Validator(QObject *parent) : QValidator(parent) {} + +QValidator::State OrganiseFormat::Validator::validate(QString &input, int&) const { + + QRegExp tag_regexp(kTagPattern); + + // Make sure all the blocks match up + int block_level = 0; + for (int i = 0; i < input.length(); ++i) { + if (input[i] == '{') + block_level++; + else if (input[i] == '}') + block_level--; + + if (block_level < 0 || block_level > 1) return QValidator::Invalid; + } + + if (block_level != 0) return QValidator::Invalid; + + // Make sure the tags are valid + int pos = 0; + while ((pos = tag_regexp.indexIn(input, pos)) != -1) { + if (!OrganiseFormat::kKnownTags.contains(tag_regexp.cap(1))) + return QValidator::Invalid; + + pos += tag_regexp.matchedLength(); + } + + return QValidator::Acceptable; + +} + +OrganiseFormat::SyntaxHighlighter::SyntaxHighlighter(QObject *parent) + : QSyntaxHighlighter(parent) {} + +OrganiseFormat::SyntaxHighlighter::SyntaxHighlighter(QTextEdit *parent) + : QSyntaxHighlighter(parent) {} + +OrganiseFormat::SyntaxHighlighter::SyntaxHighlighter(QTextDocument *parent) + : QSyntaxHighlighter(parent) {} + +void OrganiseFormat::SyntaxHighlighter::highlightBlock(const QString &text) { + + const bool light = QApplication::palette().color(QPalette::Base).value() > 128; + const QRgb block_color = light ? kBlockColorLight : kBlockColorDark; + const QRgb valid_tag_color = light ? kValidTagColorLight : kValidTagColorDark; + const QRgb invalid_tag_color = light ? kInvalidTagColorLight : kInvalidTagColorDark; + + QRegExp tag_regexp(kTagPattern); + QRegExp block_regexp(kBlockPattern); + + QTextCharFormat block_format; + block_format.setBackground(QColor(block_color)); + + // Reset formatting + setFormat(0, text.length(), QTextCharFormat()); + + // Blocks + int pos = 0; + while ((pos = block_regexp.indexIn(text, pos)) != -1) { + setFormat(pos, block_regexp.matchedLength(), block_format); + pos += block_regexp.matchedLength(); + } + + // Tags + pos = 0; + while ((pos = tag_regexp.indexIn(text, pos)) != -1) { + QTextCharFormat f = format(pos); + f.setForeground(QColor(OrganiseFormat::kKnownTags.contains(tag_regexp.cap(1)) ? valid_tag_color : invalid_tag_color)); + + setFormat(pos, tag_regexp.matchedLength(), f); + pos += tag_regexp.matchedLength(); + } + +} + diff --git a/src/core/organiseformat.h b/src/core/organiseformat.h new file mode 100644 index 00000000..7a1c95a2 --- /dev/null +++ b/src/core/organiseformat.h @@ -0,0 +1,91 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ORGANISEFORMAT_H +#define ORGANISEFORMAT_H + +#include "config.h" + +#include +#include +#include + +#include "core/song.h" + +class OrganiseFormat { + + public: + explicit OrganiseFormat(const QString &format = QString()); + + static const char* kTagPattern; + static const char* kBlockPattern; + static const QStringList kKnownTags; + static const char kInvalidFatCharacters[]; + static const int kInvalidFatCharactersCount; + static const char kInvalidPrefixCharacters[]; + static const int kInvalidPrefixCharactersCount; + + QString format() const { return format_; } + bool replace_non_ascii() const { return replace_non_ascii_; } + bool replace_spaces() const { return replace_spaces_; } + bool replace_the() const { return replace_the_; } + + void set_format(const QString &v); + void set_replace_non_ascii(bool v) { replace_non_ascii_ = v; } + void set_replace_spaces(bool v) { replace_spaces_ = v; } + void set_replace_the(bool v) { replace_the_ = v; } + + bool IsValid() const; + QString GetFilenameForSong(const Song &song) const; + + class Validator : public QValidator { + public: + explicit Validator(QObject* parent = nullptr); + QValidator::State validate(QString &format, int &pos) const; + }; + + class SyntaxHighlighter : public QSyntaxHighlighter { + public: + static const QRgb kValidTagColorLight; + static const QRgb kInvalidTagColorLight; + static const QRgb kBlockColorLight; + static const QRgb kValidTagColorDark; + static const QRgb kInvalidTagColorDark; + static const QRgb kBlockColorDark; + + explicit SyntaxHighlighter(QObject* parent = nullptr); + explicit SyntaxHighlighter(QTextEdit* parent); + explicit SyntaxHighlighter(QTextDocument* parent); + void highlightBlock(const QString &format); + }; + + private: + QString ParseBlock(QString block, const Song &song, bool* any_empty = nullptr) const; + QString TagValue(const QString &tag, const Song &song) const; + + QString format_; + bool replace_non_ascii_; + bool replace_spaces_; + bool replace_the_; + +}; + +#endif // ORGANISEFORMAT_H + diff --git a/src/core/player.cpp b/src/core/player.cpp new file mode 100644 index 00000000..1ec600b6 --- /dev/null +++ b/src/core/player.cpp @@ -0,0 +1,784 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "player.h" + +#include "config.h" + +#include + +#include +#include +#include + +#include "core/application.h" +#include "core/logging.h" +#include "core/urlhandler.h" +#include "core/timeconstants.h" +#include "engine/enginetype.h" +#include "engine/enginebase.h" +#include "engine/enginedevice.h" + +#ifdef HAVE_GSTREAMER +# include "engine/gstengine.h" +#endif +#ifdef HAVE_XINE +# include "engine/xineengine.h" +#endif +#ifdef HAVE_PHONON +# include "engine/phononengine.h" +#endif +#ifdef HAVE_VLC +# include "engine/vlcengine.h" +#endif + +#include "collection/collectionbackend.h" +#include "playlist/playlist.h" +#include "playlist/playlistitem.h" +#include "playlist/playlistmanager.h" + +#include "analyzer/analyzercontainer.h" +#include "equalizer/equalizer.h" + +#include "settings/backendsettingspage.h" +#include "settings/behavioursettingspage.h" +#include "settings/playlistsettingspage.h" + +using std::shared_ptr; + +Player::Player(Application *app, QObject *parent) + : PlayerInterface(parent), + app_(app), + //engine_(new GstEngine(app_->task_manager())), + //engine_(CreateEngine()), + stream_change_type_(Engine::First), + last_state_(Engine::Empty), + nb_errors_received_(0), + volume_before_mute_(50), + last_pressed_previous_(QDateTime::currentDateTime()), + menu_previousmode_(PreviousBehaviour_DontRestart), + seek_step_sec_(10) + { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QSettings s; + s.beginGroup(BackendSettingsPage::kSettingsGroup); + Engine::EngineType enginetype = Engine::EngineTypeFromName(s.value("engine", BackendSettingsPage::EngineText_Xine).toString().toLower()); + s.endGroup(); + + CreateEngine(enginetype); + + settings_.beginGroup("Player"); + + SetVolume(settings_.value("volume", 100).toInt()); + +#if 0 + connect(engine_.get(), SIGNAL(Error(QString)), SIGNAL(Error(QString))); + connect(engine_.get(), SIGNAL(ValidSongRequested(QUrl)), SLOT(ValidSongRequested(QUrl))); + connect(engine_.get(), SIGNAL(InvalidSongRequested(QUrl)), SLOT(InvalidSongRequested(QUrl))); +#endif + +} + +Player::~Player() {} + +EngineBase *Player::CreateEngine(Engine::EngineType enginetype) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + bool engine = false; + EngineBase *enginebase = nullptr; + + for (int i = 1 ; !engine ; i++) { + switch(enginetype) { + case Engine::None: +#ifdef HAVE_XINE + case Engine::Xine: + engine=true; + enginetype=Engine::Xine; + enginebase = new XineEngine(app_->task_manager()); + break; +#endif +#ifdef HAVE_GSTREAMER + case Engine::GStreamer: + engine=true; + enginetype=Engine::GStreamer; + enginebase = new GstEngine(app_->task_manager()); + break; +#endif +#ifdef HAVE_PHONON + case Engine::Phonon: + engine=true; + enginetype=Engine::Phonon; + enginebase = new PhononEngine(app_->task_manager()); + break; +#endif +#ifdef HAVE_VLC + case Engine::VLC: + engine=true; + enginetype=Engine::VLC; + enginebase = new VLCEngine(app_->task_manager()); + break; +#endif + default: + if (i > 1) { qFatal("No engine available!"); return nullptr; } + QSettings s; + s.beginGroup(BackendSettingsPage::kSettingsGroup); + s.setValue("engine", ""); + s.setValue("output", ""); + s.setValue("device", ""); + s.endGroup(); + enginetype = Engine::None; + break; + } + } + + QSettings s; + s.beginGroup(BackendSettingsPage::kSettingsGroup); + s.setValue("engine", Engine::EngineNameFromType(enginetype)); + s.endGroup(); + + if (enginebase == nullptr) { + qFatal("Failed to create engine!"); + return nullptr; + } + + engine_.reset(enginebase); + + return enginebase; + +} + + +void Player::Init() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + connect(engine_.get(), SIGNAL(Error(QString)), SIGNAL(Error(QString))); + connect(engine_.get(), SIGNAL(ValidSongRequested(QUrl)), SLOT(ValidSongRequested(QUrl))); + connect(engine_.get(), SIGNAL(InvalidSongRequested(QUrl)), SLOT(InvalidSongRequested(QUrl))); + + if (!engine_->Init()) qFatal("Error initialising audio engine"); + + connect(engine_.get(), SIGNAL(StateChanged(Engine::State)), SLOT(EngineStateChanged(Engine::State))); + connect(engine_.get(), SIGNAL(TrackAboutToEnd()), SLOT(TrackAboutToEnd())); + connect(engine_.get(), SIGNAL(TrackEnded()), SLOT(TrackEnded())); + connect(engine_.get(), SIGNAL(MetaData(Engine::SimpleMetaBundle)), SLOT(EngineMetadataReceived(Engine::SimpleMetaBundle))); + + engine_->SetVolume(settings_.value("volume", 50).toInt()); + + analyzer_->SetEngine(engine_.get()); + + // Equalizer + qLog(Debug) << "Creating equalizer"; + connect(equalizer_, SIGNAL(ParametersChanged(int,QList)), app_->player()->engine(), SLOT(SetEqualizerParameters(int,QList))); + connect(equalizer_, SIGNAL(EnabledChanged(bool)), app_->player()->engine(), SLOT(SetEqualizerEnabled(bool))); + connect(equalizer_, SIGNAL(StereoBalanceChanged(float)), app_->player()->engine(), SLOT(SetStereoBalance(float))); + + engine_->SetEqualizerEnabled(equalizer_->is_enabled()); + + engine_->SetEqualizerParameters(equalizer_->preamp_value(), equalizer_->gain_values()); + engine_->SetStereoBalance(equalizer_->stereo_balance()); + + ReloadSettings(); + +} + +void Player::SetAnalyzer(AnalyzerContainer *analyzer) { + + analyzer_ = analyzer; + +} +void Player::SetEqualizer(Equalizer *equalizer) { + equalizer_ = equalizer; +} + +void Player::ReloadSettings() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QSettings s; + + s.beginGroup(PlaylistSettingsPage::kSettingsGroup); + menu_previousmode_ = PreviousBehaviour(s.value("menu_previousmode", PreviousBehaviour_DontRestart).toInt()); + s.endGroup(); + + s.beginGroup(BehaviourSettingsPage::kSettingsGroup); + seek_step_sec_ = s.value("seek_step_sec", 10).toInt(); + s.endGroup(); + + engine_->ReloadSettings(); + +} + +void Player::HandleLoadResult(const UrlHandler::LoadResult &result) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // Might've been an async load, so check we're still on the same item + shared_ptr item = app_->playlist_manager()->active()->current_item(); + if (!item) { + loading_async_ = QUrl(); + return; + } + + if (item->Url() != result.original_url_) return; + + switch (result.type_) { + case UrlHandler::LoadResult::NoMoreTracks: + qLog(Debug) << "URL handler for" << result.original_url_ << "said no more tracks"; + + loading_async_ = QUrl(); + NextItem(stream_change_type_); + break; + + case UrlHandler::LoadResult::TrackAvailable: { + + qLog(Debug) << "URL handler for" << result.original_url_ << "returned" << result.media_url_; + + // If there was no length info in song's metadata, use the one provided by + // URL handler, if there is one + if (item->Metadata().length_nanosec() <= 0 && result.length_nanosec_ != -1) { + Song song = item->Metadata(); + song.set_length_nanosec(result.length_nanosec_); + item->SetTemporaryMetadata(song); + app_->playlist_manager()->active()->InformOfCurrentSongChange(); + } + engine_->Play(result.media_url_, stream_change_type_, item->Metadata().has_cue(), item->Metadata().beginning_nanosec(), item->Metadata().end_nanosec()); + + current_item_ = item; + loading_async_ = QUrl(); + break; + } + + case UrlHandler::LoadResult::WillLoadAsynchronously: + qLog(Debug) << "URL handler for" << result.original_url_ << "is loading asynchronously"; + + // We'll get called again later with either NoMoreTracks or TrackAvailable + loading_async_ = result.original_url_; + break; + } +} + +void Player::Next() { NextInternal(Engine::Manual); } + +void Player::NextInternal(Engine::TrackChangeFlags change) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (HandleStopAfter()) return; + + if (app_->playlist_manager()->active()->current_item()) { + const QUrl url = app_->playlist_manager()->active()->current_item()->Url(); + + if (url_handlers_.contains(url.scheme())) { + // The next track is already being loaded + if (url == loading_async_) return; + + stream_change_type_ = change; + HandleLoadResult(url_handlers_[url.scheme()]->LoadNext(url)); + return; + } + } + + NextItem(change); +} + +void Player::NextItem(Engine::TrackChangeFlags change) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + Playlist *active_playlist = app_->playlist_manager()->active(); + + // If we received too many errors in auto change, with repeat enabled, we stop + if (change == Engine::Auto) { + const PlaylistSequence::RepeatMode repeat_mode = active_playlist->sequence()->repeat_mode(); + if (repeat_mode != PlaylistSequence::Repeat_Off) { + if ((repeat_mode == PlaylistSequence::Repeat_Track && nb_errors_received_ >= 3) || (nb_errors_received_ >= app_->playlist_manager()->active()->proxy()->rowCount())) { + // We received too many "Error" state changes: probably looping over a + // playlist which contains only unavailable elements: stop now. + nb_errors_received_ = 0; + Stop(); + return; + } + } + } + + // Manual track changes override "Repeat track" + const bool ignore_repeat_track = change & Engine::Manual; + + int i = active_playlist->next_row(ignore_repeat_track); + if (i == -1) { + app_->playlist_manager()->active()->set_current_row(i); + emit PlaylistFinished(); + Stop(); + return; + } + + PlayAt(i, change, false); + +} + +bool Player::HandleStopAfter() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (app_->playlist_manager()->active()->stop_after_current()) { + // Find what the next track would've been, and mark that one as current + // so it plays next time the user presses Play. + const int next_row = app_->playlist_manager()->active()->next_row(); + if (next_row != -1) { + app_->playlist_manager()->active()->set_current_row(next_row, true); + } + + app_->playlist_manager()->active()->StopAfter(-1); + + Stop(true); + return true; + } + return false; +} + +void Player::TrackEnded() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (HandleStopAfter()) return; + + if (current_item_ && current_item_->IsLocalCollectionItem() && current_item_->Metadata().id() != -1) { + app_->playlist_manager()->collection_backend()->IncrementPlayCountAsync( current_item_->Metadata().id()); + } + + NextInternal(Engine::Auto); +} + +void Player::PlayPause() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + switch (engine_->state()) { + case Engine::Paused: + engine_->Unpause(); + break; + + case Engine::Playing: { + if (current_item_->options() & PlaylistItem::PauseDisabled) { + Stop(); + } + else { + engine_->Pause(); + } + break; + } + + case Engine::Empty: + case Engine::Error: + case Engine::Idle: { + app_->playlist_manager()->SetActivePlaylist(app_->playlist_manager()->current_id()); + if (app_->playlist_manager()->active()->rowCount() == 0) break; + + int i = app_->playlist_manager()->active()->current_row(); + if (i == -1) i = app_->playlist_manager()->active()->last_played_row(); + if (i == -1) i = 0; + + PlayAt(i, Engine::First, true); + break; + } + } + +} + +void Player::RestartOrPrevious() { + + if (engine_->position_nanosec() < 8 * kNsecPerSec) return Previous(); + + SeekTo(0); + +} + +void Player::Stop(bool stop_after) { + engine_->Stop(stop_after); + app_->playlist_manager()->active()->set_current_row(-1); + current_item_.reset(); +} + +void Player::StopAfterCurrent() { + app_->playlist_manager()->active()->StopAfter(app_->playlist_manager()->active()->current_row()); +} + +bool Player::PreviousWouldRestartTrack() const { + // Check if it has been over two seconds since previous button was pressed + return menu_previousmode_ == PreviousBehaviour_Restart && last_pressed_previous_.isValid() && last_pressed_previous_.secsTo(QDateTime::currentDateTime()) >= 2; +} + +void Player::Previous() { PreviousItem(Engine::Manual); } + +void Player::PreviousItem(Engine::TrackChangeFlags change) { + + const bool ignore_repeat_track = change & Engine::Manual; + + if (menu_previousmode_ == PreviousBehaviour_Restart) { + // Check if it has been over two seconds since previous button was pressed + QDateTime now = QDateTime::currentDateTime(); + if (last_pressed_previous_.isValid() && last_pressed_previous_.secsTo(now) >= 2) { + last_pressed_previous_ = now; + PlayAt(app_->playlist_manager()->active()->current_row(), change, false); + return; + } + last_pressed_previous_ = now; + } + + int i = app_->playlist_manager()->active()->previous_row(ignore_repeat_track); + app_->playlist_manager()->active()->set_current_row(i); + if (i == -1) { + Stop(); + PlayAt(i, change, true); + return; + } + + PlayAt(i, change, false); + +} + +void Player::EngineStateChanged(Engine::State state) { + + if (Engine::Error == state) { + nb_errors_received_++; + } + else { + nb_errors_received_ = 0; + } + + switch (state) { + case Engine::Paused: + emit Paused(); + break; + case Engine::Playing: + emit Playing(); + break; + case Engine::Error: + case Engine::Empty: + case Engine::Idle: + emit Stopped(); + break; + } + last_state_ = state; + +} + +void Player::SetVolume(int value) { + + int old_volume = engine_->volume(); + + int volume = qBound(0, value, 100); + settings_.setValue("volume", volume); + engine_->SetVolume(volume); + + if (volume != old_volume) { + emit VolumeChanged(volume); + } + +} + +int Player::GetVolume() const { return engine_->volume(); } + +void Player::PlayAt(int index, Engine::TrackChangeFlags change, bool reshuffle) { + + if (change == Engine::Manual && engine_->position_nanosec() != engine_->length_nanosec()) { + emit TrackSkipped(current_item_); + const QUrl& url = current_item_->Url(); + if (url_handlers_.contains(url.scheme())) { + url_handlers_[url.scheme()]->TrackSkipped(); + } + } + + if (current_item_ && app_->playlist_manager()->active()->has_item_at(index) && current_item_->Metadata().IsOnSameAlbum(app_->playlist_manager()->active()->item_at(index)->Metadata())) { + change |= Engine::SameAlbum; + } + + if (reshuffle) app_->playlist_manager()->active()->ReshuffleIndices(); + app_->playlist_manager()->active()->set_current_row(index); + + if (app_->playlist_manager()->active()->current_row() == -1) { + // Maybe index didn't exist in the playlist. + return; + } + + current_item_ = app_->playlist_manager()->active()->current_item(); + const QUrl url = current_item_->Url(); + + if (url_handlers_.contains(url.scheme())) { + // It's already loading + if (url == loading_async_) return; + + stream_change_type_ = change; + HandleLoadResult(url_handlers_[url.scheme()]->StartLoading(url)); + } + else { + loading_async_ = QUrl(); + engine_->Play(current_item_->Url(), change, current_item_->Metadata().has_cue(), current_item_->Metadata().beginning_nanosec(), current_item_->Metadata().end_nanosec()); + + } + +} + +void Player::CurrentMetadataChanged(const Song &metadata) { + // those things might have changed (especially when a previously invalid + // song was reloaded) so we push the latest version into Engine + engine_->RefreshMarkers(metadata.beginning_nanosec(), metadata.end_nanosec()); + +} + +void Player::SeekTo(int seconds) { + + const qint64 length_nanosec = engine_->length_nanosec(); + + // If the length is 0 then either there is no song playing, or the song isn't + // seekable. + if (length_nanosec <= 0) { + return; + } + + const qint64 nanosec = qBound(0ll, qint64(seconds) * kNsecPerSec, length_nanosec); + engine_->Seek(nanosec); + + emit Seeked(nanosec / 1000); + +} + +void Player::SeekForward() { + SeekTo(engine()->position_nanosec() / kNsecPerSec + seek_step_sec_); + //SeekTo(engine()->position_nanosec() / kNsecPerSec + 10); +} + +void Player::SeekBackward() { + //SeekTo(engine()->position_nanosec() / kNsecPerSec - 10); + SeekTo(engine()->position_nanosec() / kNsecPerSec - seek_step_sec_); +} + +void Player::EngineMetadataReceived(const Engine::SimpleMetaBundle &bundle) { + + PlaylistItemPtr item = app_->playlist_manager()->active()->current_item(); + if (!item) return; + + Engine::SimpleMetaBundle bundle_copy = bundle; + + // Maybe the metadata is from icycast and has "Artist - Title" shoved + // together in the title field. + const int dash_pos = bundle_copy.title.indexOf('-'); + if (dash_pos != -1 && bundle_copy.artist.isEmpty()) { + // Split on " - " if it exists, otherwise split on "-". + const int space_dash_pos = bundle_copy.title.indexOf(" - "); + if (space_dash_pos != -1) { + bundle_copy.artist = bundle_copy.title.left(space_dash_pos).trimmed(); + bundle_copy.title = bundle_copy.title.mid(space_dash_pos + 3).trimmed(); + } else { + bundle_copy.artist = bundle_copy.title.left(dash_pos).trimmed(); + bundle_copy.title = bundle_copy.title.mid(dash_pos + 1).trimmed(); + } + } + + Song song = item->Metadata(); + song.MergeFromSimpleMetaBundle(bundle_copy); + + // Ignore useless metadata + if (song.title().isEmpty() && song.artist().isEmpty()) return; + + app_->playlist_manager()->active()->SetStreamMetadata(item->Url(), song); + +} + +PlaylistItemPtr Player::GetItemAt(int pos) const { + + if (pos < 0 || pos >= app_->playlist_manager()->active()->rowCount()) + return PlaylistItemPtr(); + return app_->playlist_manager()->active()->item_at(pos); + +} + +void Player::Mute() { + + const int current_volume = engine_->volume(); + + if (current_volume == 0) { + SetVolume(volume_before_mute_); + } + else { + volume_before_mute_ = current_volume; + SetVolume(0); + } +} + +void Player::Pause() { engine_->Pause(); } + +void Player::Play() { + switch (GetState()) { + case Engine::Playing: + SeekTo(0); + break; + case Engine::Paused: + engine_->Unpause(); + break; + default: + PlayPause(); + break; + } + +} + +void Player::ShowOSD() { + if (current_item_) emit ForceShowOSD(current_item_->Metadata(), false); +} + +void Player::TogglePrettyOSD() { + if (current_item_) emit ForceShowOSD(current_item_->Metadata(), true); +} + +void Player::TrackAboutToEnd() { + + // If the current track was from a URL handler then it might have special + // behaviour to queue up a subsequent track. We don't want to preload (and + // scrobble) the next item in the playlist if it's just going to be stopped + // again immediately after. + if (app_->playlist_manager()->active()->current_item()) { + const QUrl url = app_->playlist_manager()->active()->current_item()->Url(); + if (url_handlers_.contains(url.scheme())) { + url_handlers_[url.scheme()]->TrackAboutToEnd(); + return; + } + } + + const bool has_next_row = app_->playlist_manager()->active()->next_row() != -1; + PlaylistItemPtr next_item; + + if (has_next_row) { + next_item = app_->playlist_manager()->active()->item_at(app_->playlist_manager()->active()->next_row()); + } + + if (engine_->is_autocrossfade_enabled()) { + // Crossfade is on, so just start playing the next track. The current one + // will fade out, and the new one will fade in + + // But, if there's no next track and we don't want to fade out, then do + // nothing and just let the track finish to completion. + if (!engine_->is_fadeout_enabled() && !has_next_row) return; + + // If the next track is on the same album (or same cue file), and the + // user doesn't want to crossfade between tracks on the same album, then + // don't do this automatic crossfading. + if (engine_->crossfade_same_album() || !has_next_row || !next_item || !current_item_->Metadata().IsOnSameAlbum(next_item->Metadata())) { + TrackEnded(); + return; + } + } + + // Crossfade is off, so start preloading the next track so we don't get a + // gap between songs. + if (!has_next_row || !next_item) return; + + QUrl url = next_item->Url(); + + // Get the actual track URL rather than the stream URL. + if (url_handlers_.contains(url.scheme())) { + UrlHandler::LoadResult result = url_handlers_[url.scheme()]->LoadNext(url); + switch (result.type_) { + case UrlHandler::LoadResult::NoMoreTracks: + return; + + case UrlHandler::LoadResult::WillLoadAsynchronously: + loading_async_ = url; + return; + + case UrlHandler::LoadResult::TrackAvailable: + url = result.media_url_; + break; + } + } + engine_->StartPreloading(url, next_item->Metadata().has_cue(), next_item->Metadata().beginning_nanosec(), next_item->Metadata().end_nanosec()); + +} + +void Player::IntroPointReached() { NextInternal(Engine::Intro); } + +void Player::ValidSongRequested(const QUrl &url) { + emit SongChangeRequestProcessed(url, true); +} + +void Player::InvalidSongRequested(const QUrl &url) { + + // first send the notification to others... + emit SongChangeRequestProcessed(url, false); + // ... and now when our listeners have completed their processing of the + // current item we can change the current item by skipping to the next song + //NextItem(Engine::Auto); + +} + +void Player::RegisterUrlHandler(UrlHandler *handler) { + + const QString scheme = handler->scheme(); + + if (url_handlers_.contains(scheme)) { + qLog(Warning) << "Tried to register a URL handler for" << scheme << "but one was already registered"; + return; + } + + qLog(Info) << "Registered URL handler for" << scheme; + url_handlers_.insert(scheme, handler); + connect(handler, SIGNAL(destroyed(QObject*)), SLOT(UrlHandlerDestroyed(QObject*))); + connect(handler, SIGNAL(AsyncLoadComplete(UrlHandler::LoadResult)), SLOT(HandleLoadResult(UrlHandler::LoadResult))); + +} + +void Player::UnregisterUrlHandler(UrlHandler *handler) { + + const QString scheme = url_handlers_.key(handler); + if (scheme.isEmpty()) { + qLog(Warning) << "Tried to unregister a URL handler for" << handler->scheme() << "that wasn't registered"; + return; + } + + qLog(Info) << "Unregistered URL handler for" << scheme; + url_handlers_.remove(scheme); + disconnect(handler, SIGNAL(destroyed(QObject*)), this, SLOT(UrlHandlerDestroyed(QObject*))); + disconnect(handler, SIGNAL(AsyncLoadComplete(UrlHandler::LoadResult)), this, SLOT(HandleLoadResult(UrlHandler::LoadResult))); + +} + +const UrlHandler *Player::HandlerForUrl(const QUrl &url) const { + + QMap::const_iterator it = url_handlers_.constFind(url.scheme()); + if (it == url_handlers_.constEnd()) { + return nullptr; + } + return *it; + +} + +void Player::UrlHandlerDestroyed(QObject *object) { + + UrlHandler *handler = static_cast(object); + const QString scheme = url_handlers_.key(handler); + if (!scheme.isEmpty()) { + url_handlers_.remove(scheme); + } + +} diff --git a/src/core/player.h b/src/core/player.h new file mode 100644 index 00000000..2f3cb127 --- /dev/null +++ b/src/core/player.h @@ -0,0 +1,221 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYER_H +#define PLAYER_H + +#include "config.h" + +#include + +#include +#include +#include + +#include "config.h" +#include "core/song.h" +#include "core/urlhandler.h" +#include "covermanager/albumcoverloader.h" +#include "engine/engine_fwd.h" +#include "engine/enginetype.h" +#include "engine/enginedevice.h" +#include "playlist/playlistitem.h" +#include "analyzer/analyzercontainer.h" +#include "equalizer/equalizer.h" + +class Application; +class AnalyzerContainer; +class Equalizer; + +class PlayerInterface : public QObject { + Q_OBJECT + + public: + explicit PlayerInterface(QObject *parent = nullptr) : QObject(parent) {} + + virtual EngineBase *engine() const = 0; + virtual Engine::State GetState() const = 0; + virtual int GetVolume() const = 0; + + virtual PlaylistItemPtr GetCurrentItem() const = 0; + virtual PlaylistItemPtr GetItemAt(int pos) const = 0; + + virtual void RegisterUrlHandler(UrlHandler *handler) = 0; + virtual void UnregisterUrlHandler(UrlHandler *handler) = 0; + + public slots: + virtual void ReloadSettings() = 0; + + // Manual track change to the specified track + virtual void PlayAt(int i, Engine::TrackChangeFlags change, bool reshuffle) = 0; + + // If there's currently a song playing, pause it, otherwise play the track + // that was playing last, or the first one on the playlist + virtual void PlayPause() = 0; + virtual void RestartOrPrevious() = 0; + + // Skips this track. Might load more of the current radio station. + virtual void Next() = 0; + + virtual void Previous() = 0; + virtual void SetVolume(int value) = 0; + virtual void VolumeUp() = 0; + virtual void VolumeDown() = 0; + virtual void SeekTo(int seconds) = 0; + // Moves the position of the currently playing song five seconds forward. + virtual void SeekForward() = 0; + // Moves the position of the currently playing song five seconds backwards. + virtual void SeekBackward() = 0; + + virtual void CurrentMetadataChanged(const Song &metadata) = 0; + + virtual void Mute() = 0; + virtual void Pause() = 0; + virtual void Stop(bool stop_after = false) = 0; + virtual void Play() = 0; + virtual void ShowOSD() = 0; + +signals: + void Playing(); + void Paused(); + void Stopped(); + void PlaylistFinished(); + void VolumeChanged(int volume); + void Error(const QString &message); + void TrackSkipped(PlaylistItemPtr old_track); + // Emitted when there's a manual change to the current's track position. + void Seeked(qlonglong microseconds); + + // Emitted when Player has processed a request to play another song. This contains + // the URL of the song and a flag saying whether it was able to play the song. + void SongChangeRequestProcessed(const QUrl &url, bool valid); + + // The toggle parameter is true when user requests to toggle visibility for Pretty OSD + void ForceShowOSD(Song, bool toogle); +}; + +class Player : public PlayerInterface { + Q_OBJECT + + public: + Player(Application *app, QObject *parent); + ~Player(); + + static const char *kSettingsGroup; + + // Don't change the values: they are saved in preferences + enum PreviousBehaviour { + PreviousBehaviour_DontRestart = 1, + PreviousBehaviour_Restart = 2 + }; + + EngineBase *CreateEngine(Engine::EngineType enginetype); + void Init(); + + EngineBase *engine() const { return engine_.get(); } + Engine::State GetState() const { return last_state_; } + int GetVolume() const; + + PlaylistItemPtr GetCurrentItem() const { return current_item_; } + PlaylistItemPtr GetItemAt(int pos) const; + + void RegisterUrlHandler(UrlHandler *handler); + void UnregisterUrlHandler(UrlHandler *handler); + + const UrlHandler *HandlerForUrl(const QUrl &url) const; + + bool PreviousWouldRestartTrack() const; + + void SetAnalyzer(AnalyzerContainer *analyzer); + void SetEqualizer(Equalizer *equalizer); + + public slots: + void ReloadSettings(); + + void PlayAt(int i, Engine::TrackChangeFlags change, bool reshuffle); + void PlayPause(); + void RestartOrPrevious(); + void Next(); + void Previous(); + void SetVolume(int value); + void VolumeUp() { SetVolume(GetVolume() + 5); } + void VolumeDown() { SetVolume(GetVolume() - 5); } + void SeekTo(int seconds); + void SeekForward(); + void SeekBackward(); + + void CurrentMetadataChanged(const Song &metadata); + + void Mute(); + void Pause(); + void Stop(bool stop_after = false); + void StopAfterCurrent(); + void IntroPointReached(); + void Play(); + void ShowOSD(); + void TogglePrettyOSD(); + + private slots: + void EngineStateChanged(Engine::State); + void EngineMetadataReceived(const Engine::SimpleMetaBundle &bundle); + void TrackAboutToEnd(); + void TrackEnded(); + // Play the next item on the playlist - disregarding radio stations like last.fm that might have more tracks. + void NextItem(Engine::TrackChangeFlags change); + void PreviousItem(Engine::TrackChangeFlags change); + + void NextInternal(Engine::TrackChangeFlags); + + void ValidSongRequested(const QUrl&); + void InvalidSongRequested(const QUrl&); + + void UrlHandlerDestroyed(QObject *object); + void HandleLoadResult(const UrlHandler::LoadResult &result); + + private: + // Returns true if we were supposed to stop after this track. + bool HandleStopAfter(); + + private: + Application *app_; + QSettings settings_; + AnalyzerContainer *analyzer_; + Equalizer *equalizer_; + + PlaylistItemPtr current_item_; + + std::unique_ptr engine_; + Engine::TrackChangeFlags stream_change_type_; + Engine::State last_state_; + int nb_errors_received_; + + QMap url_handlers_; + + QUrl loading_async_; + + int volume_before_mute_; + + QDateTime last_pressed_previous_; + PreviousBehaviour menu_previousmode_; + int seek_step_sec_; + +}; + +#endif // PLAYER_H diff --git a/src/core/qhash_qurl.h b/src/core/qhash_qurl.h new file mode 100644 index 00000000..cfab3e46 --- /dev/null +++ b/src/core/qhash_qurl.h @@ -0,0 +1,31 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef QHASH_QURL_H +#define QHASH_QURL_H + +#include + +#if QT_VERSION < 0x040700 +inline uint qHash(const QUrl& url) { return qHash(url.toEncoded()); } +#endif + +#endif // QHASH_QURL_H + diff --git a/src/core/qt_blurimage.h b/src/core/qt_blurimage.h new file mode 100644 index 00000000..90b5c6ac --- /dev/null +++ b/src/core/qt_blurimage.h @@ -0,0 +1,29 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2011, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef QT_BLURIMAGE_H +#define QT_BLURIMAGE_H + +#include "config.h" + +// Exported by QtGui +void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0); + +#endif // QT_BLURIMAGE_H diff --git a/src/core/qtfslistener.cpp b/src/core/qtfslistener.cpp new file mode 100644 index 00000000..3893a02a --- /dev/null +++ b/src/core/qtfslistener.cpp @@ -0,0 +1,43 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "qtfslistener.h" + +#include + +QtFSListener::QtFSListener(QObject *parent) : FileSystemWatcherInterface(parent), watcher_(this) { + + connect(&watcher_, SIGNAL(directoryChanged(const QString&)), SIGNAL(PathChanged(const QString&))); + +} + +void QtFSListener::AddPath(const QString &path) { watcher_.addPath(path); } + +void QtFSListener::RemovePath(const QString &path) { + watcher_.removePath(path); +} + +void QtFSListener::Clear() { + watcher_.removePaths(watcher_.directories()); + watcher_.removePaths(watcher_.files()); +} + diff --git a/src/core/qtfslistener.h b/src/core/qtfslistener.h new file mode 100644 index 00000000..e262738b --- /dev/null +++ b/src/core/qtfslistener.h @@ -0,0 +1,43 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef QTFSLISTENER_H +#define QTFSLISTENER_H + +#include "config.h" + +#include "filesystemwatcherinterface.h" + +#include + +class QtFSListener : public FileSystemWatcherInterface { + Q_OBJECT + public: + explicit QtFSListener(QObject *parent); + virtual void AddPath(const QString &path); + virtual void RemovePath(const QString &path); + virtual void Clear(); + + private: + QFileSystemWatcher watcher_; + +}; + +#endif diff --git a/src/core/qtsystemtrayicon.cpp b/src/core/qtsystemtrayicon.cpp new file mode 100644 index 00000000..530e9c19 --- /dev/null +++ b/src/core/qtsystemtrayicon.cpp @@ -0,0 +1,263 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "iconloader.h" +#include "qtsystemtrayicon.h" + +#include "core/song.h" +#include "core/logging.h" + +#include +#include +#include +#include +#include +#include +#include + +QtSystemTrayIcon::QtSystemTrayIcon(QObject *parent) + : SystemTrayIcon(parent), + tray_(new QSystemTrayIcon(this)), + menu_(new QMenu), + action_play_pause_(nullptr), + action_stop_(nullptr), + action_stop_after_this_track_(nullptr), + action_mute_(nullptr) +{ + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QIcon theme_icon = IconLoader::Load("strawberry-panel"); + QIcon theme_icon_grey = IconLoader::Load("strawberry-panel-grey"); + + if (theme_icon.isNull() || theme_icon_grey.isNull()) { + // Load the default icon + QIcon icon(":/icons/64x64/strawberry-panel.png"); + normal_icon_ = icon.pixmap(48, QIcon::Normal); + grey_icon_ = icon.pixmap(48, QIcon::Disabled); + } + else { + // Use the icons from the theme + normal_icon_ = theme_icon.pixmap(48); + grey_icon_ = theme_icon_grey.pixmap(48); + } + + tray_->setIcon(normal_icon_); + tray_->installEventFilter(this); + ClearNowPlaying(); + + QFile pattern_file(":/misc/playing_tooltip.txt"); + pattern_file.open(QIODevice::ReadOnly); + pattern_ = QString::fromLatin1(pattern_file.readAll()); + + connect(tray_, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(Clicked(QSystemTrayIcon::ActivationReason))); + +} + +QtSystemTrayIcon::~QtSystemTrayIcon() { + delete menu_; +} + +bool QtSystemTrayIcon::eventFilter(QObject *object, QEvent *event) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (QObject::eventFilter(object, event)) return true; + + if (object != tray_) return false; + + if (event->type() == QEvent::Wheel) { + QWheelEvent *e = static_cast(event); + if (e->modifiers() == Qt::ShiftModifier) { + if (e->delta() > 0) { + emit SeekForward(); + } else { + emit SeekBackward(); + } + } + else if (e->modifiers() == Qt::ControlModifier) { + if (e->delta() < 0) { + emit NextTrack(); + } + else { + emit PreviousTrack(); + } + } + else { + emit ChangeVolume(e->delta()); + } + return true; + } + + return false; + +} + +void QtSystemTrayIcon::SetupMenu(QAction *previous, QAction *play, QAction *stop, QAction *stop_after, QAction *next, QAction *mute, QAction *quit) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // Creating new actions and connecting them to old ones. This allows us to + // use old actions without displaying shortcuts that can not be used when + // Strawberry's window is hidden + menu_->addAction(previous->icon(), previous->text(), previous, SLOT(trigger())); + action_play_pause_ = menu_->addAction(play->icon(), play->text(), play, SLOT(trigger())); + action_stop_ = menu_->addAction(stop->icon(), stop->text(), stop, SLOT(trigger())); + action_stop_after_this_track_ = menu_->addAction(stop_after->icon(), stop_after->text(), stop_after, SLOT(trigger())); + menu_->addAction(next->icon(), next->text(), next, SLOT(trigger())); + + menu_->addSeparator(); + action_mute_ = menu_->addAction(mute->icon(), mute->text(), mute, SLOT(trigger())); + action_mute_->setCheckable(true); + action_mute_->setChecked(mute->isChecked()); + + menu_->addSeparator(); + menu_->addSeparator(); + menu_->addAction(quit->icon(), quit->text(), quit, SLOT(trigger())); + + tray_->setContextMenu(menu_); + +} + +void QtSystemTrayIcon::Clicked(QSystemTrayIcon::ActivationReason reason) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + switch (reason) { + case QSystemTrayIcon::DoubleClick: + case QSystemTrayIcon::Trigger: + emit ShowHide(); + break; + + case QSystemTrayIcon::MiddleClick: + emit PlayPause(); + break; + + default: + break; + } + +} + +void QtSystemTrayIcon::ShowPopup(const QString &summary, const QString &message, int timeout) { + tray_->showMessage(summary, message, QSystemTrayIcon::NoIcon, timeout); +} + +void QtSystemTrayIcon::UpdateIcon() { + tray_->setIcon(CreateIcon(normal_icon_, grey_icon_)); +} + +void QtSystemTrayIcon::SetPaused() { + + SystemTrayIcon::SetPaused(); + + action_stop_->setEnabled(true); + action_stop_after_this_track_->setEnabled(true); + action_play_pause_->setIcon(IconLoader::Load("media-play")); + action_play_pause_->setText(tr("Play")); + + action_play_pause_->setEnabled(true); + +} + +void QtSystemTrayIcon::SetPlaying(bool enable_play_pause) { + + SystemTrayIcon::SetPlaying(); + + action_stop_->setEnabled(true); + action_stop_after_this_track_->setEnabled(true); + action_play_pause_->setIcon(IconLoader::Load("media-pause")); + action_play_pause_->setText(tr("Pause")); + action_play_pause_->setEnabled(enable_play_pause); + +} + +void QtSystemTrayIcon::SetStopped() { + + SystemTrayIcon::SetStopped(); + + action_stop_->setEnabled(false); + action_stop_after_this_track_->setEnabled(false); + action_play_pause_->setIcon(IconLoader::Load("media-play")); + action_play_pause_->setText(tr("Play")); + + action_play_pause_->setEnabled(true); + +} + +void QtSystemTrayIcon::LastFMButtonVisibilityChanged(bool value) { + +} + +void QtSystemTrayIcon::MuteButtonStateChanged(bool value) { + if (action_mute_) action_mute_->setChecked(value); +} + +bool QtSystemTrayIcon::IsVisible() const { + return tray_->isVisible(); +} + +void QtSystemTrayIcon::SetVisible(bool visible) { + tray_->setVisible(visible); +} + +void QtSystemTrayIcon::SetNowPlaying(const Song &song, const QString &image_path) { + +#ifdef Q_WS_WIN + // Windows doesn't support HTML in tooltips, so just show something basic + tray_->setToolTip(song.PrettyTitleWithArtist()); + return; +#endif + + int columns = image_path == nullptr ? 1 : 2; + + QString clone = pattern_; + + clone.replace("%columns" , QString::number(columns)); + clone.replace("%appName" , QCoreApplication::applicationName()); + + clone.replace("%titleKey" , tr("Title") % ":"); + clone.replace("%titleValue" , song.PrettyTitle().toHtmlEscaped()); + clone.replace("%artistKey" , tr("Artist") % ":"); + clone.replace("%artistValue", song.artist().toHtmlEscaped()); + clone.replace("%albumKey" , tr("Album") % ":"); + clone.replace("%albumValue" , song.album().toHtmlEscaped()); + + clone.replace("%lengthKey" , tr("Length") % ":"); + clone.replace("%lengthValue", song.PrettyLength().toHtmlEscaped()); + + if(columns == 2) { + QString final_path = image_path.startsWith("file://") ? image_path.mid(7) : image_path; + clone.replace("%image", " "); + } + else { + clone.replace("%image", ""); + } + + // TODO: we should also repaint this + tray_->setToolTip(clone); + +} + +void QtSystemTrayIcon::ClearNowPlaying() { + tray_->setToolTip(QCoreApplication::applicationName()); +} diff --git a/src/core/qtsystemtrayicon.h b/src/core/qtsystemtrayicon.h new file mode 100644 index 00000000..218d9daa --- /dev/null +++ b/src/core/qtsystemtrayicon.h @@ -0,0 +1,75 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef QTSYSTEMTRAYICON_H +#define QTSYSTEMTRAYICON_H + +#include "config.h" + +#include "systemtrayicon.h" + +#include + +class QtSystemTrayIcon : public SystemTrayIcon { + Q_OBJECT + + public: + QtSystemTrayIcon(QObject* parent = nullptr); + ~QtSystemTrayIcon(); + + void SetupMenu(QAction* previous, QAction *play, QAction *stop, QAction *stop_after, QAction *next, QAction *mute, QAction *quit); + bool IsVisible() const; + void SetVisible(bool visible); + + void ShowPopup(const QString &summary, const QString &message, int timeout); + + void SetNowPlaying(const Song& song, const QString& image_path); + void ClearNowPlaying(); + +protected: + // SystemTrayIcon + void UpdateIcon(); + void SetPaused(); + void SetPlaying(bool enable_play_pause = false); + void SetStopped(); + void LastFMButtonVisibilityChanged(bool value); + void MuteButtonStateChanged(bool value); + + // QObject + bool eventFilter(QObject *, QEvent *); + +private slots: + void Clicked(QSystemTrayIcon::ActivationReason); + +private: + QSystemTrayIcon *tray_; + QMenu *menu_; + QAction *action_play_pause_; + QAction *action_stop_; + QAction *action_stop_after_this_track_; + QAction *action_mute_; + + QString pattern_; + + QPixmap normal_icon_; + QPixmap grey_icon_; +}; + +#endif // QTSYSTEMTRAYICON_H diff --git a/src/core/scangiomodulepath.cpp b/src/core/scangiomodulepath.cpp new file mode 100644 index 00000000..e0285eda --- /dev/null +++ b/src/core/scangiomodulepath.cpp @@ -0,0 +1,44 @@ +/* + * Strawberry Music Player + * This code was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +//#ifdef HAVE_GIO +//#undef signals // Clashes with GIO, and not needed in this file +#include +#include +#include "core/logging.h" + +//namespace { + +void ScanGIOModulePath() { + QString gio_module_path; + +#if defined(Q_OS_WIN32) + gio_module_path = QCoreApplication::applicationDirPath() + "/gio-modules"; +#endif + + if (!gio_module_path.isEmpty()) { + qLog(Debug) << "Adding GIO module path:" << gio_module_path; + QByteArray bytes = gio_module_path.toLocal8Bit(); + g_io_modules_scan_all_in_directory(bytes.data()); + } +} + +//} // namespace +//#endif // HAVE_GIO diff --git a/src/core/scangiomodulepath.h b/src/core/scangiomodulepath.h new file mode 100644 index 00000000..0642a5de --- /dev/null +++ b/src/core/scangiomodulepath.h @@ -0,0 +1,21 @@ +/* + * Strawberry Music Player + * This code was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +void ScanGIOModulePath(); diff --git a/src/core/scoped_cftyperef.h b/src/core/scoped_cftyperef.h new file mode 100644 index 00000000..a526103c --- /dev/null +++ b/src/core/scoped_cftyperef.h @@ -0,0 +1,65 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MAC_SCOPED_CFTYPEREF_H_ +#define BASE_MAC_SCOPED_CFTYPEREF_H_ + +#include + +// ScopedCFTypeRef<> is patterned after scoped_ptr<>, but maintains ownership +// of a CoreFoundation object: any object that can be represented as a +// CFTypeRef. Style deviations here are solely for compatibility with +// scoped_ptr<>'s interface, with which everyone is already familiar. +// +// When ScopedCFTypeRef<> takes ownership of an object (in the constructor or +// in reset()), it takes over the caller's existing ownership claim. The +// caller must own the object it gives to ScopedCFTypeRef<>, and relinquishes +// an ownership claim to that object. ScopedCFTypeRef<> does not call +// CFRetain(). +template +class ScopedCFTypeRef { + public: + typedef CFT element_type; + + explicit ScopedCFTypeRef(CFT object = nullptr) : object_(object) {} + + ~ScopedCFTypeRef() { + if (object_) CFRelease(object_); + } + + void reset(CFT object = nullptr) { + if (object_) CFRelease(object_); + object_ = object; + } + + bool operator==(CFT that) const { return object_ == that; } + + bool operator!=(CFT that) const { return object_ != that; } + + operator CFT() const { return object_; } + + CFT get() const { return object_; } + + void swap(ScopedCFTypeRef& that) { + CFT temp = that.object_; + that.object_ = object_; + object_ = temp; + } + + // ScopedCFTypeRef<>::release() is like scoped_ptr<>::release. It is NOT + // a wrapper for CFRelease(). To force a ScopedCFTypeRef<> object to call + // CFRelease(), use ScopedCFTypeRef<>::reset(). + CFT release() __attribute__((warn_unused_result)) { + CFT temp = object_; + object_ = nullptr; + return temp; + } + + private: + CFT object_; + + Q_DISABLE_COPY(ScopedCFTypeRef); +}; + +#endif // BASE_MAC_SCOPED_CFTYPEREF_H_ diff --git a/src/core/scoped_nsautorelease_pool.h b/src/core/scoped_nsautorelease_pool.h new file mode 100644 index 00000000..fb8a79d5 --- /dev/null +++ b/src/core/scoped_nsautorelease_pool.h @@ -0,0 +1,38 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MAC_SCOPED_NSAUTORELEASE_POOL_H_ +#define BASE_MAC_SCOPED_NSAUTORELEASE_POOL_H_ + +#include + +#if defined(__OBJC__) +@class NSAutoreleasePool; +#else // __OBJC__ +class NSAutoreleasePool; +#endif // __OBJC__ + +// ScopedNSAutoreleasePool allocates an NSAutoreleasePool when instantiated and +// sends it a -drain message when destroyed. This allows an autorelease pool to +// be maintained in ordinary C++ code without bringing in any direct Objective-C +// dependency. + +class ScopedNSAutoreleasePool { + public: + ScopedNSAutoreleasePool(); + ~ScopedNSAutoreleasePool(); + + // Clear out the pool in case its position on the stack causes it to be + // alive for long periods of time (such as the entire length of the app). + // Only use then when you're certain the items currently in the pool are + // no longer needed. + void Recycle(); + private: + NSAutoreleasePool* autorelease_pool_; + + private: + Q_DISABLE_COPY(ScopedNSAutoreleasePool); +}; + +#endif // BASE_MAC_SCOPED_NSAUTORELEASE_POOL_H_ diff --git a/src/core/scoped_nsautorelease_pool.mm b/src/core/scoped_nsautorelease_pool.mm new file mode 100644 index 00000000..d37a7bea --- /dev/null +++ b/src/core/scoped_nsautorelease_pool.mm @@ -0,0 +1,24 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "scoped_nsautorelease_pool.h" + +#import + +ScopedNSAutoreleasePool::ScopedNSAutoreleasePool() + : autorelease_pool_([[NSAutoreleasePool alloc] init]) { + Q_ASSERT(autorelease_pool_); +} + +ScopedNSAutoreleasePool::~ScopedNSAutoreleasePool() { + [autorelease_pool_ drain]; +} + +// Cycle the internal pool, allowing everything there to get cleaned up and +// start anew. +void ScopedNSAutoreleasePool::Recycle() { + [autorelease_pool_ drain]; + autorelease_pool_ = [[NSAutoreleasePool alloc] init]; + Q_ASSERT(autorelease_pool_); +} diff --git a/src/core/scoped_nsobject.h b/src/core/scoped_nsobject.h new file mode 100644 index 00000000..0b6f07be --- /dev/null +++ b/src/core/scoped_nsobject.h @@ -0,0 +1,143 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MEMORY_SCOPED_NSOBJECT_H_ +#define BASE_MEMORY_SCOPED_NSOBJECT_H_ + +#import + +// scoped_nsobject<> is patterned after scoped_ptr<>, but maintains ownership +// of an NSObject subclass object. Style deviations here are solely for +// compatibility with scoped_ptr<>'s interface, with which everyone is already +// familiar. +// +// When scoped_nsobject<> takes ownership of an object (in the constructor or +// in reset()), it takes over the caller's existing ownership claim. The +// caller must own the object it gives to scoped_nsobject<>, and relinquishes +// an ownership claim to that object. scoped_nsobject<> does not call +// -retain. +// +// scoped_nsobject<> is not to be used for NSAutoreleasePools. For +// NSAutoreleasePools use ScopedNSAutoreleasePool from +// scoped_nsautorelease_pool.h instead. +// We check for bad uses of scoped_nsobject and NSAutoreleasePool at compile +// time with a template specialization (see below). +template +class scoped_nsobject { + public: + explicit scoped_nsobject(NST* object = nil) : object_(object) {} + + ~scoped_nsobject() { [object_ release]; } + + void reset(NST* object = nil) { + // We intentionally do not check that object != object_ as the caller must + // already have an ownership claim over whatever it gives to + // scoped_nsobject and ScopedCFTypeRef, whether it's in the constructor or + // in a call to reset(). In either case, it relinquishes that claim and + // the scoper assumes it. + [object_ release]; + object_ = object; + } + + bool operator==(NST* that) const { return object_ == that; } + bool operator!=(NST* that) const { return object_ != that; } + + operator NST*() const { return object_; } + + NST* get() const { return object_; } + + void swap(scoped_nsobject& that) { + NST* temp = that.object_; + that.object_ = object_; + object_ = temp; + } + + // scoped_nsobject<>::release() is like scoped_ptr<>::release. It is NOT + // a wrapper for [object_ release]. To force a scoped_nsobject<> object to + // call [object_ release], use scoped_nsobject<>::reset(). + NST* release() __attribute__((warn_unused_result)) { + NST* temp = object_; + object_ = nil; + return temp; + } + + private: + NST* object_; + + Q_DISABLE_COPY(scoped_nsobject); +}; + +// Free functions +template +void swap(scoped_nsobject& p1, scoped_nsobject& p2) { + p1.swap(p2); +} + +template +bool operator==(C* p1, const scoped_nsobject& p2) { + return p1 == p2.get(); +} + +template +bool operator!=(C* p1, const scoped_nsobject& p2) { + return p1 != p2.get(); +} + +// Specialization to make scoped_nsobject work. +template <> +class scoped_nsobject { + public: + explicit scoped_nsobject(id object = nil) : object_(object) {} + + ~scoped_nsobject() { [object_ release]; } + + void reset(id object = nil) { + // We intentionally do not check that object != object_ as the caller must + // already have an ownership claim over whatever it gives to + // scoped_nsobject and ScopedCFTypeRef, whether it's in the constructor or + // in a call to reset(). In either case, it relinquishes that claim and + // the scoper assumes it. + [object_ release]; + object_ = object; + } + + bool operator==(id that) const { return object_ == that; } + bool operator!=(id that) const { return object_ != that; } + + operator id() const { return object_; } + + id get() const { return object_; } + + void swap(scoped_nsobject& that) { + id temp = that.object_; + that.object_ = object_; + object_ = temp; + } + + // scoped_nsobject<>::release() is like scoped_ptr<>::release. It is NOT + // a wrapper for [object_ release]. To force a scoped_nsobject<> object to + // call [object_ release], use scoped_nsobject<>::reset(). + id release() __attribute__((warn_unused_result)) { + id temp = object_; + object_ = nil; + return temp; + } + + private: + id object_; + + Q_DISABLE_COPY(scoped_nsobject); +}; + +// Do not use scoped_nsobject for NSAutoreleasePools, use +// ScopedNSAutoreleasePool instead. This is a compile time check. See details +// at top of header. +template <> +class scoped_nsobject { + private: + explicit scoped_nsobject(NSAutoreleasePool* object = nil); + Q_DISABLE_COPY(scoped_nsobject); +}; + +#endif // BASE_MEMORY_SCOPED_NSOBJECT_H_ diff --git a/src/core/scopedgobject.h b/src/core/scopedgobject.h new file mode 100644 index 00000000..e09101af --- /dev/null +++ b/src/core/scopedgobject.h @@ -0,0 +1,71 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef SCOPEDGOBJECT_H +#define SCOPEDGOBJECT_H + +#include "config.h" + +#include + +#include + +template +class ScopedGObject { + public: + ScopedGObject() : object_(nullptr) {} + + explicit ScopedGObject(const ScopedGObject& other) : object_(nullptr) { + reset(other.object_); + } + + ~ScopedGObject() { reset(); } + + ScopedGObject& operator=(const ScopedGObject& other) { + reset(other.object_); + return *this; + } + + void reset(T* new_object = nullptr) { + if (new_object) g_object_ref(new_object); + reset_without_add(new_object); + } + + void reset_without_add(T* new_object = nullptr) { + if (object_) g_object_unref(object_); + + object_ = new_object; + } + + T* get() const { return object_; } + operator T*() const { return get(); } + T* operator*() const { return get(); } + operator bool() const { return get(); } + + bool operator==(const ScopedGObject& other) const { + return object_ == other.object_; + } + + private: + T* object_; +}; + +#endif // SCOPEDGOBJECT_H + diff --git a/src/core/scopedtransaction.cpp b/src/core/scopedtransaction.cpp new file mode 100644 index 00000000..8e892b8c --- /dev/null +++ b/src/core/scopedtransaction.cpp @@ -0,0 +1,50 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "scopedtransaction.h" +#include "core/logging.h" + +#include +#include + +ScopedTransaction::ScopedTransaction(QSqlDatabase* db) + : db_(db), pending_(true) { + db->transaction(); +} + +ScopedTransaction::~ScopedTransaction() { + if (pending_) { + qLog(Warning) << "Rolling back transaction"; + db_->rollback(); + } +} + +void ScopedTransaction::Commit() { + if (!pending_) { + qLog(Warning) << "Tried to commit a ScopedTransaction twice"; + return; + } + + db_->commit(); + pending_ = false; +} + diff --git a/src/core/scopedtransaction.h b/src/core/scopedtransaction.h new file mode 100644 index 00000000..6d0f1033 --- /dev/null +++ b/src/core/scopedtransaction.h @@ -0,0 +1,45 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef SCOPEDTRANSACTION_H +#define SCOPEDTRANSACTION_H + +#include "config.h" + +#include + +class QSqlDatabase; + +// Opens a transaction on a database. +// Rolls back the transaction if the object goes out of scope before Commit() +// is called. +class ScopedTransaction : boost::noncopyable { + public: + ScopedTransaction(QSqlDatabase* db); + ~ScopedTransaction(); + + void Commit(); + + private: + QSqlDatabase* db_; + bool pending_; +}; + +#endif // SCOPEDTRANSACTION_H diff --git a/src/core/screensaver.cpp b/src/core/screensaver.cpp new file mode 100644 index 00000000..7075d715 --- /dev/null +++ b/src/core/screensaver.cpp @@ -0,0 +1,62 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include "screensaver.h" + +#ifdef HAVE_DBUS + #include "dbusscreensaver.h" + #include + #include +#endif + +#ifdef Q_OS_DARWIN + #include "macscreensaver.h" +#endif + +#include + +const char *Screensaver::kGnomeService = "org.gnome.ScreenSaver"; +const char *Screensaver::kGnomePath = "/"; +const char *Screensaver::kGnomeInterface = "org.gnome.ScreenSaver"; +const char *Screensaver::kKdeService = "org.kde.ScreenSaver"; +const char *Screensaver::kKdePath = "/ScreenSaver/"; +const char *Screensaver::kKdeInterface = "org.freedesktop.ScreenSaver"; + +Screensaver *Screensaver::screensaver_ = 0; + +Screensaver *Screensaver::GetScreensaver() { + if (!screensaver_) { + #if defined(HAVE_DBUS) + if (QDBusConnection::sessionBus().interface()->isServiceRegistered(kGnomeService)) { + screensaver_ = new DBusScreensaver(kGnomeService, kGnomePath, kGnomeInterface); + } + else if (QDBusConnection::sessionBus().interface()->isServiceRegistered(kKdeService)) { + screensaver_ = new DBusScreensaver(kKdeService, kKdePath, kKdeInterface); + } + #elif defined(Q_OS_DARWIN) + screensaver_ = new MacScreensaver(); + #endif + } + return screensaver_; +} diff --git a/src/core/screensaver.h b/src/core/screensaver.h new file mode 100644 index 00000000..d26d2451 --- /dev/null +++ b/src/core/screensaver.h @@ -0,0 +1,47 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef SCREENSAVER_H +#define SCREENSAVER_H + +#include "config.h" + +class Screensaver { + public: + virtual ~Screensaver() {} + + static const char *kGnomeService; + static const char *kGnomePath; + static const char *kGnomeInterface; + + static const char *kKdeService; + static const char *kKdePath; + static const char *kKdeInterface; + + virtual void Inhibit() = 0; + virtual void Uninhibit() = 0; + + static Screensaver *GetScreensaver(); + + private: + static Screensaver *screensaver_; +}; + +#endif diff --git a/src/core/settingsprovider.cpp b/src/core/settingsprovider.cpp new file mode 100644 index 00000000..6ae45346 --- /dev/null +++ b/src/core/settingsprovider.cpp @@ -0,0 +1,56 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "settingsprovider.h" + +SettingsProvider::SettingsProvider() {} + +DefaultSettingsProvider::DefaultSettingsProvider() {} + +void DefaultSettingsProvider::set_group(const char *group) { + while (!backend_.group().isEmpty()) backend_.endGroup(); + + backend_.beginGroup(group); +} + +QVariant DefaultSettingsProvider::value(const QString &key, const QVariant &default_value) const { + return backend_.value(key, default_value); +} + +void DefaultSettingsProvider::setValue(const QString &key, const QVariant &value) { + backend_.setValue(key, value); +} + +int DefaultSettingsProvider::beginReadArray(const QString &prefix) { + return backend_.beginReadArray(prefix); +} + +void DefaultSettingsProvider::beginWriteArray(const QString &prefix, int size) { + backend_.beginWriteArray(prefix, size); +} + +void DefaultSettingsProvider::setArrayIndex(int i) { + backend_.setArrayIndex(i); +} + +void DefaultSettingsProvider::endArray() { backend_.endArray(); } + diff --git a/src/core/settingsprovider.h b/src/core/settingsprovider.h new file mode 100644 index 00000000..2ef1779b --- /dev/null +++ b/src/core/settingsprovider.h @@ -0,0 +1,62 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef SETTINGSPROVIDER_H +#define SETTINGSPROVIDER_H + +#include "config.h" + +#include +#include + +class SettingsProvider { + public: + SettingsProvider(); + virtual ~SettingsProvider() {} + + virtual void set_group(const char *group) = 0; + + virtual QVariant value(const QString &key, const QVariant &default_value = QVariant()) const = 0; + virtual void setValue(const QString &key, const QVariant &value) = 0; + virtual int beginReadArray(const QString &prefix) = 0; + virtual void beginWriteArray(const QString &prefix, int size = -1) = 0; + virtual void setArrayIndex(int i) = 0; + virtual void endArray() = 0; +}; + +class DefaultSettingsProvider : public SettingsProvider { + public: + DefaultSettingsProvider(); + + void set_group(const char* group); + + QVariant value(const QString &key, const QVariant &default_value = QVariant()) const; + void setValue(const QString &key, const QVariant &value); + int beginReadArray(const QString &prefix); + void beginWriteArray(const QString &prefix, int size = -1); + void setArrayIndex(int i); + void endArray(); + + private: + QSettings backend_; +}; + +#endif // SETTINGSPROVIDER_H + diff --git a/src/core/signalchecker.cpp b/src/core/signalchecker.cpp new file mode 100644 index 00000000..285fcf7e --- /dev/null +++ b/src/core/signalchecker.cpp @@ -0,0 +1,52 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * Copyright 2012, 2014, John Maguire + * Copyright 2014, Krzysztof Sobiecki + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "signalchecker.h" + +#include "core/logging.h" + +gulong CheckedGConnect(gpointer source, const char *signal, GCallback callback, gpointer data, const int callback_param_count) { + + guint signal_id = 0; + GQuark detail = 0; + + if (!g_signal_parse_name(signal, G_OBJECT_TYPE(source), &signal_id, &detail, false)) { + qFatal("Connecting to invalid signal: %s", signal); + return 0; + } + + GSignalQuery query; + g_signal_query(signal_id, &query); + // The signature for a signal callback is always: + // return_type callback(gpointer data1, params..., gpointer data2); + int signal_params = query.n_params + 2; + if (signal_params != callback_param_count) { + qFatal("Connecting callback to signal with different parameters counts"); + return 0; + } + + return g_signal_connect(source, signal, G_CALLBACK(callback), data); + +} + diff --git a/src/core/signalchecker.h b/src/core/signalchecker.h new file mode 100644 index 00000000..facc1aec --- /dev/null +++ b/src/core/signalchecker.h @@ -0,0 +1,38 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef SIGNALCHECKER_H +#define SIGNALCHECKER_H + +#include "config.h" + +#include + +#include +#include + +// Do not call this directly, use CHECKED_GCONNECT instead. +gulong CheckedGConnect(gpointer source, const char *signal, GCallback callback, gpointer data, const int callback_param_count); + +#define FUNCTION_ARITY(callback) boost::function_types::function_arity::value + +#define CHECKED_GCONNECT(source, signal, callback, data) CheckedGConnect(source, signal, G_CALLBACK(callback), data, FUNCTION_ARITY(callback)) + +#endif // SIGNALCHECKER_H diff --git a/src/core/simpletreeitem.h b/src/core/simpletreeitem.h new file mode 100644 index 00000000..71f3e65a --- /dev/null +++ b/src/core/simpletreeitem.h @@ -0,0 +1,161 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef SIMPLETREEITEM_H +#define SIMPLETREEITEM_H + +#include "config.h" + +#include "simpletreemodel.h" + +#include +#include + +template +class SimpleTreeItem { + public: + SimpleTreeItem(int _type, SimpleTreeModel* _model); // For the root item + SimpleTreeItem(int _type, const QString& _key, T* _parent = nullptr); + explicit SimpleTreeItem(int _type, T* _parent = nullptr); + virtual ~SimpleTreeItem(); + + void InsertNotify(T* _parent); + void DeleteNotify(int child_row); + void ClearNotify(); + void ChangedNotify(); + + void Delete(int child_row); + T* ChildByKey(const QString& key) const; + + QString DisplayText() const { return display_text.isNull() ? key : display_text; } + QString SortText() const { return sort_text.isNull() ? key : sort_text; } + + int type; + QString key; + QString sort_text; + QString display_text; + + int row; + bool lazy_loaded; + + T* parent; + QList children; + QAbstractItemModel* child_model; + + SimpleTreeModel* model; +}; + +template +SimpleTreeItem::SimpleTreeItem(int _type, SimpleTreeModel* _model) + : type(_type), + row(0), + lazy_loaded(true), + parent(nullptr), + child_model(nullptr), + model(_model) {} + +template +SimpleTreeItem::SimpleTreeItem(int _type, const QString& _key, T* _parent) + : type(_type), + key(_key), + lazy_loaded(false), + parent(_parent), + child_model(nullptr), + model(_parent ? _parent->model : nullptr) { + if (parent) { + row = parent->children.count(); + parent->children << static_cast(this); + } +} + +template +SimpleTreeItem::SimpleTreeItem(int _type, T* _parent) + : type(_type), + lazy_loaded(false), + parent(_parent), + child_model(nullptr), + model(_parent ? _parent->model : nullptr) { + if (parent) { + row = parent->children.count(); + parent->children << static_cast(this); + } +} + +template +void SimpleTreeItem::InsertNotify(T* _parent) { + parent = _parent; + model = parent->model; + row = parent->children.count(); + + model->BeginInsert(parent, row); + parent->children << static_cast(this); + model->EndInsert(); +} + +template +void SimpleTreeItem::DeleteNotify(int child_row) { + model->BeginDelete(static_cast(this), child_row); + delete children.takeAt(child_row); + + // Adjust row numbers of those below it :( + for (int i = child_row; i < children.count(); ++i) children[i]->row--; + model->EndDelete(); +} + +template +void SimpleTreeItem::ClearNotify() { + if (children.count()) { + model->BeginDelete(static_cast(this), 0, children.count() - 1); + + qDeleteAll(children); + children.clear(); + + model->EndDelete(); + } +} + +template +void SimpleTreeItem::ChangedNotify() { + model->EmitDataChanged(static_cast(this)); +} + +template +SimpleTreeItem::~SimpleTreeItem() { + qDeleteAll(children); +} + +template +void SimpleTreeItem::Delete(int child_row) { + delete children.takeAt(child_row); + + // Adjust row numbers of those below it :( + for (int i = child_row; i < children.count(); ++i) children[i]->row--; +} + +template +T* SimpleTreeItem::ChildByKey(const QString& key) const { + for (T* child : children) { + if (child->key == key) return child; + } + return nullptr; +} + +#endif // SIMPLETREEITEM_H + diff --git a/src/core/simpletreemodel.h b/src/core/simpletreemodel.h new file mode 100644 index 00000000..8830708f --- /dev/null +++ b/src/core/simpletreemodel.h @@ -0,0 +1,154 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef SIMPLETREEMODEL_H +#define SIMPLETREEMODEL_H + +#include "config.h" + +#include + +class QModelIndex; + +template +class SimpleTreeModel : public QAbstractItemModel { + public: + explicit SimpleTreeModel(T *root = 0, QObject *parent = nullptr); + virtual ~SimpleTreeModel() {} + + // QAbstractItemModel + int columnCount(const QModelIndex &parent) const; + QModelIndex index(int row, int, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + int rowCount(const QModelIndex &parent) const; + bool hasChildren(const QModelIndex &parent) const; + bool canFetchMore(const QModelIndex &parent) const; + void fetchMore(const QModelIndex &parent); + + T *IndexToItem(const QModelIndex &index) const; + QModelIndex ItemToIndex(T *item) const; + + // Called by items + void BeginInsert(T *parent, int start, int end = -1); + void EndInsert(); + void BeginDelete(T *parent, int start, int end = -1); + void EndDelete(); + void EmitDataChanged(T *item); + + protected: + virtual void LazyPopulate(T *item) = 0; + + protected: + T *root_; +}; + +template +SimpleTreeModel::SimpleTreeModel(T *root, QObject *parent) + : QAbstractItemModel(parent), root_(root) {} + +template +T *SimpleTreeModel::IndexToItem(const QModelIndex &index) const { + if (!index.isValid()) return root_; + return reinterpret_cast(index.internalPointer()); +} + +template +QModelIndex SimpleTreeModel::ItemToIndex(T *item) const { + if (!item || !item->parent) return QModelIndex(); + return createIndex(item->row, 0, item); +} + +template +int SimpleTreeModel::columnCount(const QModelIndex&) const { + return 1; +} + +template +QModelIndex SimpleTreeModel::index(int row, int, const QModelIndex &parent) const { + T *parent_item = IndexToItem(parent); + if (!parent_item || row < 0 || parent_item->children.count() <= row) + return QModelIndex(); + + return ItemToIndex(parent_item->children[row]); +} + +template +QModelIndex SimpleTreeModel::parent(const QModelIndex &index) const { + return ItemToIndex(IndexToItem(index)->parent); +} + +template +int SimpleTreeModel::rowCount(const QModelIndex &parent) const { + T *item = IndexToItem(parent); + return item->children.count(); +} + +template +bool SimpleTreeModel::hasChildren(const QModelIndex &parent) const { + T *item = IndexToItem(parent); + if (item->lazy_loaded) + return !item->children.isEmpty(); + else + return true; +} + +template +bool SimpleTreeModel::canFetchMore(const QModelIndex &parent) const { + T *item = IndexToItem(parent); + return !item->lazy_loaded; +} + +template +void SimpleTreeModel::fetchMore(const QModelIndex &parent) { + T *item = IndexToItem(parent); + if (!item->lazy_loaded) { + LazyPopulate(item); + } +} + +template +void SimpleTreeModel::BeginInsert(T *parent, int start, int end) { + if (end == -1) end = start; + beginInsertRows(ItemToIndex(parent), start, end); +} + +template +void SimpleTreeModel::EndInsert() { + endInsertRows(); +} + +template +void SimpleTreeModel::BeginDelete(T *parent, int start, int end) { + if (end == -1) end = start; + beginRemoveRows(ItemToIndex(parent), start, end); +} + +template +void SimpleTreeModel::EndDelete() { + endRemoveRows(); +} + +template +void SimpleTreeModel::EmitDataChanged(T *item) { + QModelIndex index(ItemToIndex(item)); + emit dataChanged(index, index); +} + +#endif // SIMPLETREEMODEL_H diff --git a/src/core/song.cpp b/src/core/song.cpp new file mode 100644 index 00000000..09ae5cfc --- /dev/null +++ b/src/core/song.cpp @@ -0,0 +1,1152 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "song.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef HAVE_LIBGPOD +#include +#endif + +#ifdef HAVE_LIBMTP +#include +#endif + +#include "core/application.h" +#include "core/logging.h" +#include "core/messagehandler.h" +#include "core/mpris_common.h" +#include "core/timeconstants.h" +#include "core/utilities.h" +#include "engine/enginebase.h" +#include "collection/sqlrow.h" +#include "tagreadermessages.pb.h" +#include "widgets/trackslider.h" +#include "covermanager/albumcoverloader.h" + +const QStringList Song::kColumns = QStringList() << "title" + << "album" + << "artist" + << "albumartist" + << "track" + << "disc" + << "year" + << "originalyear" + << "genre" + << "compilation" + << "composer" + << "performer" + << "grouping" + << "comment" + + << "beginning" + << "length" + + << "bitrate" + << "samplerate" + << "bitdepth" + + << "directory_id" + << "filename" + << "filetype" + << "filesize" + << "mtime" + << "ctime" + << "unavailable" + + << "playcount" + << "skipcount" + << "lastplayed" + + << "compilation_detected" + << "compilation_on" + << "compilation_off" + << "compilation_effective" + + << "art_automatic" + << "art_manual" + + << "effective_albumartist" + << "effective_originalyear" + + << "cue_path" + + ; + +const QString Song::kColumnSpec = Song::kColumns.join(", "); +const QString Song::kBindSpec = Utilities::Prepend(":", Song::kColumns).join(", "); +const QString Song::kUpdateSpec = Utilities::Updateify(Song::kColumns).join(", "); + +const QStringList Song::kFtsColumns = QStringList() << "ftstitle" + << "ftsalbum" + << "ftsartist" + << "ftsalbumartist" + << "ftscomposer" + << "ftsperformer" + << "ftsgrouping" + << "ftsgenre" + << "ftscomment"; + +const QString Song::kFtsColumnSpec = Song::kFtsColumns.join(", "); +const QString Song::kFtsBindSpec = Utilities::Prepend(":", Song::kFtsColumns).join(", "); +const QString Song::kFtsUpdateSpec = Utilities::Updateify(Song::kFtsColumns).join(", "); + +const QString Song::kManuallyUnsetCover = "(unset)"; +const QString Song::kEmbeddedCover = "(embedded)"; + +struct Song::Private : public QSharedData { + + Private(); + + bool valid_; + int id_; + int album_id_; // A unique album ID + + QString title_; + QString album_; + QString artist_; + QString albumartist_; + int track_; + int disc_; + int year_; + int originalyear_; + QString genre_; + bool compilation_; // From the file tag + QString composer_; + QString performer_; + QString grouping_; + QString comment_; + + qint64 beginning_; + qint64 end_; + + int bitrate_; + int samplerate_; + int bitdepth_; + + int directory_id_; + QString basefilename_; + QUrl url_; + FileType filetype_; + int filesize_; + int mtime_; + int ctime_; + bool unavailable_; + + int playcount_; + int skipcount_; + int lastplayed_; + + bool compilation_detected_; // From the collection scanner + bool compilation_on_; // Set by the user + bool compilation_off_; // Set by the user + + // Filenames to album art for this song. + QString art_automatic_; // Guessed by CollectionWatcher + QString art_manual_; // Set by the user - should take priority + + QString cue_path_; // If the song has a CUE, this contains it's path. + + QImage image_; + bool init_from_file_; // Whether this song was loaded from a file using taglib. + bool suspicious_tags_; // Whether our encoding guesser thinks these tags might be incorrectly encoded. + +}; + +Song::Private::Private() + : valid_(false), + id_(-1), + album_id_(-1), + track_(-1), + disc_(-1), + year_(-1), + originalyear_(-1), + compilation_(false), + + beginning_(0), + end_(-1), + bitrate_(-1), + samplerate_(-1), + + directory_id_(-1), + filetype_(Type_Unknown), + filesize_(-1), + mtime_(-1), + ctime_(-1), + unavailable_(false), + + playcount_(0), + skipcount_(0), + lastplayed_(-1), + + compilation_detected_(false), + compilation_on_(false), + compilation_off_(false), + + init_from_file_(false), + suspicious_tags_(false) + + {} + +Song::Song() : d(new Private) {} + +Song::Song(const Song &other) : d(other.d) {} + +Song::~Song() {} + +Song &Song::operator=(const Song &other) { + d = other.d; + return *this; +} + +bool Song::is_valid() const { return d->valid_; } +bool Song::is_unavailable() const { return d->unavailable_; } +int Song::id() const { return d->id_; } +const QString &Song::title() const { return d->title_; } +const QString &Song::album() const { return d->album_; } +const QString &Song::effective_album() const { + // This value is useful for singles, which are one-track albums on their own. + return d->album_.isEmpty() ? d->title_ : d->album_; +} +const QString &Song::artist() const { return d->artist_; } +const QString &Song::albumartist() const { return d->albumartist_; } +const QString &Song::effective_albumartist() const { return d->albumartist_.isEmpty() ? d->artist_ : d->albumartist_; } +const QString &Song::playlist_albumartist() const { return is_compilation() ? d->albumartist_ : effective_albumartist(); } +const QString &Song::composer() const { return d->composer_; } +const QString &Song::performer() const { return d->performer_; } +const QString &Song::grouping() const { return d->grouping_; } +int Song::track() const { return d->track_; } +int Song::disc() const { return d->disc_; } +int Song::year() const { return d->year_; } +int Song::originalyear() const { return d->originalyear_; } +int Song::effective_originalyear() const { + return d->originalyear_ < 0 ? d->year_ : d->originalyear_; +} +const QString &Song::genre() const { return d->genre_; } +const QString &Song::comment() const { return d->comment_; } +bool Song::is_compilation() const { + return (d->compilation_ || d->compilation_detected_ || d->compilation_on_) && ! d->compilation_off_; +} +int Song::playcount() const { return d->playcount_; } +int Song::skipcount() const { return d->skipcount_; } +int Song::lastplayed() const { return d->lastplayed_; } +const QString &Song::cue_path() const { return d->cue_path_; } +bool Song::has_cue() const { return !d->cue_path_.isEmpty(); } +int Song::album_id() const { return d->album_id_; } +qint64 Song::beginning_nanosec() const { return d->beginning_; } +qint64 Song::end_nanosec() const { return d->end_; } +qint64 Song::length_nanosec() const { return d->end_ - d->beginning_; } +int Song::bitrate() const { return d->bitrate_; } +int Song::samplerate() const { return d->samplerate_; } +int Song::bitdepth() const { return d->bitdepth_; } +int Song::directory_id() const { return d->directory_id_; } +const QUrl &Song::url() const { return d->url_; } +const QString &Song::basefilename() const { return d->basefilename_; } +uint Song::mtime() const { return d->mtime_; } +uint Song::ctime() const { return d->ctime_; } +int Song::filesize() const { return d->filesize_; } +Song::FileType Song::filetype() const { return d->filetype_; } +bool Song::is_cdda() const { return d->filetype_ == Type_Cdda; } +bool Song::is_collection_song() const { + return !is_cdda() && id() != -1; +} +const QString &Song::art_automatic() const { return d->art_automatic_; } +const QString &Song::art_manual() const { return d->art_manual_; } +bool Song::has_manually_unset_cover() const { return d->art_manual_ == kManuallyUnsetCover; } +void Song::manually_unset_cover() { d->art_manual_ = kManuallyUnsetCover; } +bool Song::has_embedded_cover() const { return d->art_automatic_ == kEmbeddedCover; } +void Song::set_embedded_cover() { d->art_automatic_ = kEmbeddedCover; } +const QImage &Song::image() const { return d->image_; } + +void Song::set_id(int id) { d->id_ = id; } +void Song::set_album_id(int v) { d->album_id_ = v; } +void Song::set_valid(bool v) { d->valid_ = v; } + +void Song::set_title(const QString &v) { d->title_ = v; } +void Song::set_album(const QString &v) { d->album_ = v; } +void Song::set_artist(const QString &v) { d->artist_ = v; } +void Song::set_albumartist(const QString &v) { d->albumartist_ = v; } +void Song::set_track(int v) { d->track_ = v; } +void Song::set_disc(int v) { d->disc_ = v; } +void Song::set_year(int v) { d->year_ = v; } +void Song::set_originalyear(int v) { d->originalyear_ = v; } +void Song::set_genre(const QString &v) { d->genre_ = v; } +void Song::set_compilation(bool v) { d->compilation_ = v; } +void Song::set_composer(const QString &v) { d->composer_ = v; } +void Song::set_performer(const QString &v) { d->performer_ = v; } +void Song::set_grouping(const QString &v) { d->grouping_ = v; } +void Song::set_comment(const QString &v) { d->comment_ = v; } + +void Song::set_beginning_nanosec(qint64 v) { d->beginning_ = qMax(0ll, v); } +void Song::set_end_nanosec(qint64 v) { d->end_ = v; } +void Song::set_length_nanosec(qint64 v) { d->end_ = d->beginning_ + v; } + +void Song::set_bitrate(int v) { d->bitrate_ = v; } +void Song::set_samplerate(int v) { d->samplerate_ = v; } +void Song::set_bitdepth(int v) { d->bitdepth_ = v; } + +void Song::set_directory_id(int v) { d->directory_id_ = v; } +void Song::set_url(const QUrl &v) { + if (Application::kIsPortable) { + QUrl base = + QUrl::fromLocalFile(QCoreApplication::applicationDirPath() + "/"); + d->url_ = base.resolved(v); + } else { + d->url_ = v; + } +} +void Song::set_basefilename(const QString &v) { d->basefilename_ = v; } +void Song::set_filetype(FileType v) { d->filetype_ = v; } +void Song::set_filesize(int v) { d->filesize_ = v; } +void Song::set_mtime(int v) { d->mtime_ = v; } +void Song::set_ctime(int v) { d->ctime_ = v; } +void Song::set_unavailable(bool v) { d->unavailable_ = v; } + +void Song::set_playcount(int v) { d->playcount_ = v; } +void Song::set_skipcount(int v) { d->skipcount_ = v; } +void Song::set_lastplayed(int v) { d->lastplayed_ = v; } + +void Song::set_compilation_detected(bool v) { d->compilation_detected_ = v; } +void Song::set_compilation_on(bool v) { d->compilation_on_ = v; } +void Song::set_compilation_off(bool v) { d->compilation_off_ = v; } + +void Song::set_art_automatic(const QString &v) { d->art_automatic_ = v; } +void Song::set_art_manual(const QString &v) { d->art_manual_ = v; } +void Song::set_cue_path(const QString &v) { d->cue_path_ = v; } + +void Song::set_image(const QImage &i) { d->image_ = i; } + +QString Song::JoinSpec(const QString &table) { + return Utilities::Prepend(table + ".", kColumns).join(", "); +} + +QString Song::TextForFiletype(FileType type) { + + switch (type) { + case Song::Type_Asf: return QObject::tr("Windows Media audio"); + case Song::Type_Flac: return QObject::tr("Flac"); + case Song::Type_Mp4: return QObject::tr("MP4 AAC"); + case Song::Type_Mpc: return QObject::tr("MPC"); + case Song::Type_Mpeg: return QObject::tr("MP3"); + case Song::Type_OggFlac: return QObject::tr("Ogg Flac"); + case Song::Type_OggSpeex: return QObject::tr("Ogg Speex"); + case Song::Type_OggVorbis: return QObject::tr("Ogg Vorbis"); + case Song::Type_OggOpus: return QObject::tr("Ogg Opus"); + case Song::Type_Aiff: return QObject::tr("AIFF"); + case Song::Type_Wav: return QObject::tr("Wav"); + case Song::Type_TrueAudio: return QObject::tr("TrueAudio"); + case Song::Type_Cdda: return QObject::tr("CDDA"); + + case Song::Type_Unknown: + default: + return QObject::tr("Unknown"); + + } + +} + +bool Song::IsFileLossless() const { + switch (filetype()) { + case Song::Type_Aiff: + case Song::Type_Flac: + case Song::Type_OggFlac: + case Song::Type_Wav: + return true; + default: + return false; + } +} + +int CompareSongsName(const Song &song1, const Song &song2) { + return song1.PrettyTitleWithArtist().localeAwareCompare(song2.PrettyTitleWithArtist()) < 0; +} + +void Song::SortSongsListAlphabetically(SongList *songs) { + Q_ASSERT(songs); + qSort(songs->begin(), songs->end(), CompareSongsName); +} + +void Song::Init(const QString &title, const QString &artist, const QString &album, qint64 length_nanosec) { + + d->valid_ = true; + + d->title_ = title; + d->artist_ = artist; + d->album_ = album; + + set_length_nanosec(length_nanosec); +} + +void Song::Init(const QString &title, const QString &artist, const QString &album, qint64 beginning, qint64 end) { + + d->valid_ = true; + + d->title_ = title; + d->artist_ = artist; + d->album_ = album; + + d->beginning_ = beginning; + d->end_ = end; +} + +void Song::set_genre_id3(int id) { + set_genre(TStringToQString(TagLib::ID3v1::genre(id))); +} + +QString Song::Decode(const QString &tag, const QTextCodec *codec) { + if (!codec) { + return tag; + } + + return codec->toUnicode(tag.toUtf8()); +} + +void Song::InitFromProtobuf(const pb::tagreader::SongMetadata &pb) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + d->init_from_file_ = true; + d->valid_ = pb.valid(); + d->title_ = QStringFromStdString(pb.title()); + d->album_ = QStringFromStdString(pb.album()); + d->artist_ = QStringFromStdString(pb.artist()); + d->albumartist_ = QStringFromStdString(pb.albumartist()); + d->composer_ = QStringFromStdString(pb.composer()); + d->performer_ = QStringFromStdString(pb.performer()); + d->grouping_ = QStringFromStdString(pb.grouping()); + d->track_ = pb.track(); + d->disc_ = pb.disc(); + d->year_ = pb.year(); + d->originalyear_ = pb.originalyear(); + d->genre_ = QStringFromStdString(pb.genre()); + d->comment_ = QStringFromStdString(pb.comment()); + d->compilation_ = pb.compilation(); + d->skipcount_ = pb.skipcount(); + d->lastplayed_ = pb.lastplayed(); + set_length_nanosec(pb.length_nanosec()); + d->bitrate_ = pb.bitrate(); + d->samplerate_ = pb.samplerate(); + d->bitdepth_ = pb.bitdepth(); + set_url(QUrl::fromEncoded(QByteArray(pb.url().data(), pb.url().size()))); + d->basefilename_ = QStringFromStdString(pb.basefilename()); + d->mtime_ = pb.mtime(); + d->ctime_ = pb.ctime(); + d->filesize_ = pb.filesize(); + d->suspicious_tags_ = pb.suspicious_tags(); + d->filetype_ = static_cast(pb.filetype()); + + if (pb.has_art_automatic()) { + d->art_automatic_ = QStringFromStdString(pb.art_automatic()); + } + + if (pb.has_playcount()) { + d->playcount_ = pb.playcount(); + } + + InitArtManual(); + +} + +void Song::ToProtobuf(pb::tagreader::SongMetadata *pb) const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + const QByteArray url(d->url_.toEncoded()); + + pb->set_valid(d->valid_); + pb->set_title(DataCommaSizeFromQString(d->title_)); + pb->set_album(DataCommaSizeFromQString(d->album_)); + pb->set_artist(DataCommaSizeFromQString(d->artist_)); + pb->set_albumartist(DataCommaSizeFromQString(d->albumartist_)); + pb->set_composer(DataCommaSizeFromQString(d->composer_)); + pb->set_performer(DataCommaSizeFromQString(d->performer_)); + pb->set_grouping(DataCommaSizeFromQString(d->grouping_)); + pb->set_track(d->track_); + pb->set_disc(d->disc_); + pb->set_year(d->year_); + pb->set_originalyear(d->originalyear_); + pb->set_genre(DataCommaSizeFromQString(d->genre_)); + pb->set_comment(DataCommaSizeFromQString(d->comment_)); + pb->set_compilation(d->compilation_); + pb->set_playcount(d->playcount_); + pb->set_skipcount(d->skipcount_); + pb->set_lastplayed(d->lastplayed_); + pb->set_length_nanosec(length_nanosec()); + pb->set_bitrate(d->bitrate_); + pb->set_samplerate(d->samplerate_); + pb->set_bitdepth(d->bitdepth_); + pb->set_url(url.constData(), url.size()); + pb->set_basefilename(DataCommaSizeFromQString(d->basefilename_)); + pb->set_mtime(d->mtime_); + pb->set_ctime(d->ctime_); + pb->set_filesize(d->filesize_); + pb->set_suspicious_tags(d->suspicious_tags_); + pb->set_art_automatic(DataCommaSizeFromQString(d->art_automatic_)); + pb->set_filetype(static_cast(d->filetype_)); +} + +#define tostr(n) (q.value(n).isNull() ? QString::null : q.value(n).toString()) +#define toint(n) (q.value(n).isNull() ? -1 : q.value(n).toInt()) +#define tolonglong(n) (q.value(n).isNull() ? -1 : q.value(n).toLongLong()) +#define tofloat(n) (q.value(n).isNull() ? -1 : q.value(n).toDouble()) + +void Song::InitFromQuery(const SqlRow &q, bool reliable_metadata, int col) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + //qLog(Debug) << "Song::kColumns.size():" << Song::kColumns.size() << "q.columns_.size():" << q.columns_.size() << "col:" << col; + + int i = 0; + int x = col; + + d->id_ = toint(col); + + for (i = 0 ; i < Song::kColumns.size(); i++) { + x=i+col+1; + + if (x >= q.columns_.size()) { + qLog(Error) << "Skipping" << Song::kColumns.value(i); + break; + } + //qLog(Debug) << "Index:" << i << x << Song::kColumns.value(i) << q.value(x).toString(); + + if (Song::kColumns.value(i) == "title") { + d->title_ = tostr(x); + } + else if (Song::kColumns.value(i) == "album") { + d->album_ = tostr(x); + } + else if (Song::kColumns.value(i) == "artist") { + d->artist_ = tostr(x); + } + else if (Song::kColumns.value(i) == "albumartist") { + d->albumartist_ = tostr(x); + } + else if (Song::kColumns.value(i) == "track") { + d->track_ = toint(x); + } + else if (Song::kColumns.value(i) == "disc") { + d->disc_ = toint(x); + } + else if (Song::kColumns.value(i) == "year") { + d->year_ = toint(x); + } + else if (Song::kColumns.value(i) == "originalyear") { + d->originalyear_ = toint(x); + } + else if (Song::kColumns.value(i) == "genre") { + d->genre_ = tostr(x); + } + else if (Song::kColumns.value(i) == "compilation") { + d->compilation_ = q.value(x).toBool(); + } + else if (Song::kColumns.value(i) == "composer") { + d->composer_ = tostr(x); + } + else if (Song::kColumns.value(i) == "performer") { + d->performer_ = tostr(x); + } + else if (Song::kColumns.value(i) == "grouping") { + d->grouping_ = tostr(x); + } + else if (Song::kColumns.value(i) == "comment") { + d->comment_ = tostr(x); + } + + else if (Song::kColumns.value(i) == "beginning") { + d->beginning_ = q.value(x).isNull() ? 0 : q.value(x).toLongLong(); + } + else if (Song::kColumns.value(i) == "length") { + set_length_nanosec(tolonglong(x)); + } + + else if (Song::kColumns.value(i) == "bitrate") { + d->bitrate_ = toint(x); + } + else if (Song::kColumns.value(i) == "samplerate") { + d->samplerate_ = toint(x); + } + else if (Song::kColumns.value(i) == "bitdepth") { + d->bitdepth_ = toint(x); + } + + else if (Song::kColumns.value(i) == "directory_id") { + d->directory_id_ = toint(x); + } + else if (Song::kColumns.value(i) == "filename") { + set_url(QUrl::fromEncoded(tostr(x).toUtf8())); + d->basefilename_ = QFileInfo(d->url_.toLocalFile()).fileName(); + } + else if (Song::kColumns.value(i) == "filetype") { + d->filetype_ = FileType(q.value(x).toInt()); + } + else if (Song::kColumns.value(i) == "filesize") { + d->filesize_ = toint(x); + } + else if (Song::kColumns.value(i) == "mtime") { + d->mtime_ = toint(x); + } + else if (Song::kColumns.value(i) == "ctime") { + d->ctime_ = toint(x); + } + else if (Song::kColumns.value(i) == "unavailable") { + d->unavailable_ = q.value(x).toBool(); + } + + else if (Song::kColumns.value(i) == "playcount") { + d->playcount_ = q.value(x).isNull() ? 0 : q.value(x).toInt(); + } + else if (Song::kColumns.value(i) == "skipcount") { + d->skipcount_ = q.value(x).isNull() ? 0 : q.value(x).toInt(); + } + else if (Song::kColumns.value(i) == "lastplayed") { + d->lastplayed_ = toint(x); + } + + else if (Song::kColumns.value(i) == "compilation_detected") { + d->compilation_detected_ = q.value(x).toBool(); + } + else if (Song::kColumns.value(i) == "compilation_on") { + d->compilation_on_ = q.value(x).toBool(); + } + else if (Song::kColumns.value(i) == "compilation_off") { + d->compilation_off_ = q.value(x).toBool(); + } + else if (Song::kColumns.value(i) == "compilation_effective") { + } + + else if (Song::kColumns.value(i) == "art_automatic") { + d->art_automatic_ = q.value(x).toString(); + } + else if (Song::kColumns.value(i) == "art_manual") { + d->art_manual_ = q.value(x).toString(); + } + + else if (Song::kColumns.value(i) == "effective_albumartist") { + } + else if (Song::kColumns.value(i) == "effective_originalyear") { + } + + else if (Song::kColumns.value(i) == "cue_path") { + d->cue_path_ = tostr(col + x); + } + + else { + qLog(Error) << "Forgot to handle" << Song::kColumns.value(i); + } + } + + d->valid_ = true; + d->init_from_file_ = reliable_metadata; + + InitArtManual(); + +#undef tostr +#undef toint +#undef tolonglong +#undef tofloat + +} + +void Song::InitFromFilePartial(const QString &filename) { + + set_url(QUrl::fromLocalFile(filename)); + // We currently rely on filename suffix to know if it's a music file or not. + // TODO: I know this is not satisfying, but currently, we rely on TagLib + // which seems to have the behavior (filename checks). Someday, it would be + // nice to perform some magic tests everywhere. + QFileInfo info(filename); + d->basefilename_ = info.fileName(); + QString suffix = info.suffix().toLower(); + if (suffix == "mp3" || suffix == "ogg" || suffix == "flac" || + suffix == "mpc" || suffix == "m4a" || suffix == "aac" || + suffix == "wma" || suffix == "mp4" || suffix == "spx" || + suffix == "wav" || suffix == "opus" || suffix == "m4b") { + d->valid_ = true; + } + else { + d->valid_ = false; + } + +} + +void Song::InitArtManual() { + + // If we don't have an art, check if we have one in the cache + if (d->art_manual_.isEmpty() && d->art_automatic_.isEmpty()) { + QString filename(Utilities::Sha1CoverHash(d->artist_, d->album_).toHex() + ".jpg"); + QString path(AlbumCoverLoader::ImageCacheDir() + "/" + filename); + if (QFile::exists(path)) { + d->art_manual_ = path; + } + } + +} + +#ifdef HAVE_LIBGPOD +void Song::InitFromItdb(const Itdb_Track *track, const QString &prefix) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + d->valid_ = true; + + d->title_ = QString::fromUtf8(track->title); + d->album_ = QString::fromUtf8(track->album); + d->artist_ = QString::fromUtf8(track->artist); + d->albumartist_ = QString::fromUtf8(track->albumartist); + d->track_ = track->track_nr; + d->disc_ = track->cd_nr; + d->year_ = track->year; + d->genre_ = QString::fromUtf8(track->genre); + d->compilation_ = track->compilation; + d->composer_ = QString::fromUtf8(track->composer); + d->grouping_ = QString::fromUtf8(track->grouping); + d->comment_ = QString::fromUtf8(track->comment); + + set_length_nanosec(track->tracklen * kNsecPerMsec); + + d->bitrate_ = track->bitrate; + d->samplerate_ = track->samplerate; + d->bitdepth_ = -1; //track->bitdepth; + + QString filename = QString::fromLocal8Bit(track->ipod_path); + filename.replace(':', '/'); + if (prefix.contains("://")) { + set_url(QUrl(prefix + filename)); + } else { + set_url(QUrl::fromLocalFile(prefix + filename)); + } + d->basefilename_ = QFileInfo(filename).fileName(); + + d->filetype_ = track->type2 ? Type_Mpeg : Type_Mp4; + d->filesize_ = track->size; + d->mtime_ = track->time_modified; + d->ctime_ = track->time_added; + + d->playcount_ = track->playcount; + d->skipcount_ = track->skipcount; + d->lastplayed_ = track->time_played; + +} + +void Song::ToItdb(Itdb_Track *track) const { + + track->title = strdup(d->title_.toUtf8().constData()); + track->album = strdup(d->album_.toUtf8().constData()); + track->artist = strdup(d->artist_.toUtf8().constData()); + track->albumartist = strdup(d->albumartist_.toUtf8().constData()); + track->track_nr = d->track_; + track->cd_nr = d->disc_; + track->year = d->year_; + track->genre = strdup(d->genre_.toUtf8().constData()); + track->compilation = d->compilation_; + track->composer = strdup(d->composer_.toUtf8().constData()); + track->grouping = strdup(d->grouping_.toUtf8().constData()); + track->comment = strdup(d->comment_.toUtf8().constData()); + + track->tracklen = length_nanosec() / kNsecPerMsec; + + track->bitrate = d->bitrate_; + track->samplerate = d->samplerate_; + //track->bithdepth = d->bithdepth_; + + track->type1 = 0; + track->type2 = d->filetype_ == Type_Mp4 ? 0 : 1; + track->mediatype = 1; // Audio + track->size = d->filesize_; + track->time_modified = d->mtime_; + track->time_added = d->ctime_; + + track->playcount = d->playcount_; + track->skipcount = d->skipcount_; + track->time_played = d->lastplayed_; + +} +#endif + +#ifdef HAVE_LIBMTP +void Song::InitFromMTP(const LIBMTP_track_t *track, const QString &host) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + d->valid_ = true; + + d->title_ = QString::fromUtf8(track->title); + d->artist_ = QString::fromUtf8(track->artist); + d->album_ = QString::fromUtf8(track->album); + d->genre_ = QString::fromUtf8(track->genre); + d->composer_ = QString::fromUtf8(track->composer); + d->track_ = track->tracknumber; + + d->url_ = QUrl(QString("mtp://%1/%2").arg(host, track->item_id)); + d->basefilename_ = QString::number(track->item_id); + d->filesize_ = track->filesize; + d->mtime_ = track->modificationdate; + d->ctime_ = track->modificationdate; + + set_length_nanosec(track->duration * kNsecPerMsec); + + d->samplerate_ = track->samplerate; + d->bitdepth_ = 0; //track->bitdepth; + d->bitrate_ = track->bitrate; + + d->playcount_ = track->usecount; + + switch (track->filetype) { + case LIBMTP_FILETYPE_WAV: d->filetype_ = Type_Wav; break; + case LIBMTP_FILETYPE_MP3: d->filetype_ = Type_Mpeg; break; + case LIBMTP_FILETYPE_WMA: d->filetype_ = Type_Asf; break; + case LIBMTP_FILETYPE_OGG: d->filetype_ = Type_OggVorbis; break; + case LIBMTP_FILETYPE_MP4: d->filetype_ = Type_Mp4; break; + case LIBMTP_FILETYPE_AAC: d->filetype_ = Type_Mp4; break; + case LIBMTP_FILETYPE_FLAC: d->filetype_ = Type_OggFlac; break; + case LIBMTP_FILETYPE_MP2: d->filetype_ = Type_Mpeg; break; + case LIBMTP_FILETYPE_M4A: d->filetype_ = Type_Mp4; break; + default: d->filetype_ = Type_Unknown; break; + } + +} + +void Song::ToMTP(LIBMTP_track_t *track) const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + track->item_id = 0; + track->parent_id = 0; + track->storage_id = 0; + + track->title = strdup(d->title_.toUtf8().constData()); + track->artist = strdup(d->artist_.toUtf8().constData()); + track->album = strdup(d->album_.toUtf8().constData()); + track->genre = strdup(d->genre_.toUtf8().constData()); + track->date = nullptr; + track->tracknumber = d->track_; + track->composer = strdup(d->composer_.toUtf8().constData()); + + track->filename = strdup(d->basefilename_.toUtf8().constData()); + track->filesize = d->filesize_; + track->modificationdate = d->mtime_; + + track->duration = length_nanosec() / kNsecPerMsec; + + track->bitrate = d->bitrate_; + track->bitratetype = 0; + track->samplerate = d->samplerate_; + track->nochannels = 0; + track->wavecodec = 0; + + track->usecount = d->playcount_; + + switch (d->filetype_) { + case Type_Asf: track->filetype = LIBMTP_FILETYPE_ASF; break; + case Type_Mp4: track->filetype = LIBMTP_FILETYPE_MP4; break; + case Type_Mpeg: track->filetype = LIBMTP_FILETYPE_MP3; break; + case Type_Flac: + case Type_OggFlac: track->filetype = LIBMTP_FILETYPE_FLAC; break; + case Type_OggSpeex: + case Type_OggVorbis: track->filetype = LIBMTP_FILETYPE_OGG; break; + case Type_Wav: track->filetype = LIBMTP_FILETYPE_WAV; break; + default: track->filetype = LIBMTP_FILETYPE_UNDEF_AUDIO; break; + } + +} +#endif + +void Song::MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (d->init_from_file_ || d->url_.scheme() == "file") { + // This Song was already loaded using taglib. Our tags are probably better + // than the engine's. Note: init_from_file_ is used for non-file:// URLs + // when the metadata is known to be good, like from Jamendo. + return; + } + + d->valid_ = true; + if (!bundle.title.isEmpty()) d->title_ = bundle.title; + if (!bundle.artist.isEmpty()) d->artist_ = bundle.artist; + if (!bundle.album.isEmpty()) d->album_ = bundle.album; + if (!bundle.comment.isEmpty()) d->comment_ = bundle.comment; + if (!bundle.genre.isEmpty()) d->genre_ = bundle.genre; + if (!bundle.bitrate.isEmpty()) d->bitrate_ = bundle.bitrate.toInt(); + if (!bundle.samplerate.isEmpty()) d->samplerate_ = bundle.samplerate.toInt(); + if (!bundle.bitdepth.isEmpty()) d->samplerate_ = bundle.bitdepth.toInt(); + if (!bundle.length.isEmpty()) set_length_nanosec(bundle.length.toLongLong()); + if (!bundle.year.isEmpty()) d->year_ = bundle.year.toInt(); + if (!bundle.tracknr.isEmpty()) d->track_ = bundle.tracknr.toInt(); + +} + +void Song::BindToQuery(QSqlQuery *query) const { + +#define strval(x) (x.isNull() ? "" : x) +#define intval(x) (x <= 0 ? -1 : x) +#define notnullintval(x) (x == -1 ? QVariant() : x) + + // Remember to bind these in the same order as kBindSpec + + query->bindValue(":title", strval(d->title_)); + query->bindValue(":album", strval(d->album_)); + query->bindValue(":artist", strval(d->artist_)); + query->bindValue(":albumartist", strval(d->albumartist_)); + query->bindValue(":track", intval(d->track_)); + query->bindValue(":disc", intval(d->disc_)); + query->bindValue(":year", intval(d->year_)); + query->bindValue(":originalyear", intval(d->originalyear_)); + query->bindValue(":genre", strval(d->genre_)); + query->bindValue(":compilation", d->compilation_ ? 1 : 0); + query->bindValue(":composer", strval(d->composer_)); + query->bindValue(":performer", strval(d->performer_)); + query->bindValue(":grouping", strval(d->grouping_)); + query->bindValue(":comment", strval(d->comment_)); + + query->bindValue(":beginning", d->beginning_); + query->bindValue(":length", intval(length_nanosec())); + + query->bindValue(":bitrate", intval(d->bitrate_)); + query->bindValue(":samplerate", intval(d->samplerate_)); + query->bindValue(":bitdepth", intval(d->bitdepth_)); + + query->bindValue(":directory_id", notnullintval(d->directory_id_)); + + if (Application::kIsPortable && Utilities::UrlOnSameDriveAsStrawberry(d->url_)) { + query->bindValue(":filename", Utilities::GetRelativePathToStrawberryBin(d->url_).toEncoded()); + } + else { + query->bindValue(":filename", d->url_.toEncoded()); + } + + query->bindValue(":filetype", d->filetype_); + query->bindValue(":filesize", notnullintval(d->filesize_)); + query->bindValue(":mtime", notnullintval(d->mtime_)); + query->bindValue(":ctime", notnullintval(d->ctime_)); + query->bindValue(":unavailable", d->unavailable_ ? 1 : 0); + + query->bindValue(":playcount", d->playcount_); + query->bindValue(":skipcount", d->skipcount_); + query->bindValue(":lastplayed", intval(d->lastplayed_)); + + query->bindValue(":compilation_detected", d->compilation_detected_ ? 1 : 0); + query->bindValue(":compilation_on", d->compilation_on_ ? 1 : 0); + query->bindValue(":compilation_off", d->compilation_off_ ? 1 : 0); + query->bindValue(":compilation_effective", is_compilation() ? 1 : 0); + + query->bindValue(":art_automatic", d->art_automatic_); + query->bindValue(":art_manual", d->art_manual_); + + query->bindValue(":effective_albumartist", this->effective_albumartist()); + query->bindValue(":effective_originalyear", intval(this->effective_originalyear())); + + query->bindValue(":cue_path", d->cue_path_); + +#undef intval +#undef notnullintval +#undef strval + +} + +void Song::BindToFtsQuery(QSqlQuery *query) const { + + query->bindValue(":ftstitle", d->title_); + query->bindValue(":ftsalbum", d->album_); + query->bindValue(":ftsartist", d->artist_); + query->bindValue(":ftsalbumartist", d->albumartist_); + query->bindValue(":ftscomposer", d->composer_); + query->bindValue(":ftsperformer", d->performer_); + query->bindValue(":ftsgrouping", d->grouping_); + query->bindValue(":ftsgenre", d->genre_); + query->bindValue(":ftscomment", d->comment_); + +} + +QString Song::PrettyTitle() const { + + QString title(d->title_); + + if (title.isEmpty()) title = d->basefilename_; + if (title.isEmpty()) title = d->url_.toString(); + + return title; + +} + +QString Song::PrettyTitleWithArtist() const { + + QString title(PrettyTitle()); + + if (!d->artist_.isEmpty()) title = d->artist_ + " - " + title; + + return title; + +} + +QString Song::PrettyLength() const { + + if (length_nanosec() == -1) return QString::null; + + return Utilities::PrettyTimeNanosec(length_nanosec()); + +} + +QString Song::PrettyYear() const { + + if (d->year_ == -1) return QString::null; + + return QString::number(d->year_); + +} + +QString Song::TitleWithCompilationArtist() const { + + QString title(d->title_); + + if (title.isEmpty()) title = d->basefilename_; + + if (is_compilation() && !d->artist_.isEmpty() && !d->artist_.toLower().contains("various")) title = d->artist_ + " - " + title; + + return title; + +} + +QString Song::SampleRateBitDepthToText() const { + + if (d->bitdepth_ == -1) return QString("%1 hz").arg(d->samplerate_); + + return QString("%1 hz / %2 bit").arg(d->samplerate_).arg(d->bitdepth_); + +} + +bool Song::IsMetadataEqual(const Song &other) const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + return d->title_ == other.d->title_ && + d->album_ == other.d->album_ && + d->artist_ == other.d->artist_ && + d->albumartist_ == other.d->albumartist_ && + d->composer_ == other.d->composer_ && + d->performer_ == other.d->performer_ && + d->grouping_ == other.d->grouping_ && + d->track_ == other.d->track_ && + d->disc_ == other.d->disc_ && + d->year_ == other.d->year_ && + d->originalyear_ == other.d->originalyear_ && + d->genre_ == other.d->genre_ && + d->comment_ == other.d->comment_ && + d->compilation_ == other.d->compilation_ && + d->beginning_ == other.d->beginning_ && + length_nanosec() == other.length_nanosec() && + d->bitrate_ == other.d->bitrate_ && + d->samplerate_ == other.d->samplerate_ && + d->bitdepth_ == other.d->bitdepth_ && + d->art_automatic_ == other.d->art_automatic_ && + d->art_manual_ == other.d->art_manual_ && + d->cue_path_ == other.d->cue_path_; +} + +bool Song::IsEditable() const { + return d->valid_ && !d->url_.isEmpty() && d->filetype_ != Type_Unknown && !has_cue(); +} + +bool Song::operator==(const Song &other) const { + return url() == other.url() && beginning_nanosec() == other.beginning_nanosec(); +} + +uint qHash(const Song &song) { + // Should compare the same fields as operator== + return qHash(song.url().toString()) ^ qHash(song.beginning_nanosec()); +} + +bool Song::IsSimilar(const Song &other) const { + return title().compare(other.title(), Qt::CaseInsensitive) == 0 && artist().compare(other.artist(), Qt::CaseInsensitive) == 0; +} + +uint HashSimilar(const Song &song) { + // Should compare the same fields as function IsSimilar + return qHash(song.title().toLower()) ^ qHash(song.artist().toLower()); +} + +bool Song::IsOnSameAlbum(const Song &other) const { + + if (is_compilation() != other.is_compilation()) return false; + + if (has_cue() && other.has_cue() && cue_path() == other.cue_path()) + return true; + + if (is_compilation() && album() == other.album()) return true; + + return effective_album() == other.effective_album() && effective_albumartist() == other.effective_albumartist(); + +} + +QString Song::AlbumKey() const { + return QString("%1|%2|%3").arg(is_compilation() ? "_compilation" : effective_albumartist(), has_cue() ? cue_path() : "", effective_album()); +} + +void Song::ToXesam(QVariantMap *map) const { + + using mpris::AddMetadata; + using mpris::AddMetadataAsList; + using mpris::AsMPRISDateTimeType; + + AddMetadata("xesam:url", url().toString(), map); + AddMetadata("xesam:title", PrettyTitle(), map); + AddMetadataAsList("xesam:artist", artist(), map); + AddMetadata("xesam:album", album(), map); + AddMetadataAsList("xesam:albumArtist", albumartist(), map); + AddMetadata("mpris:length", length_nanosec() / kNsecPerUsec, map); + AddMetadata("xesam:trackNumber", track(), map); + AddMetadataAsList("xesam:genre", genre(), map); + AddMetadata("xesam:discNumber", disc(), map); + AddMetadataAsList("xesam:comment", comment(), map); + AddMetadata("xesam:contentCreated", AsMPRISDateTimeType(ctime()), map); + AddMetadata("xesam:lastUsed", AsMPRISDateTimeType(lastplayed()), map); + AddMetadataAsList("xesam:composer", composer(), map); + AddMetadata("xesam:useCount", playcount(), map); + +} + +void Song::MergeUserSetData(const Song &other) { + + set_playcount(other.playcount()); + set_skipcount(other.skipcount()); + set_lastplayed(other.lastplayed()); + set_art_manual(other.art_manual()); + +} + diff --git a/src/core/song.h b/src/core/song.h new file mode 100644 index 00000000..6265efa4 --- /dev/null +++ b/src/core/song.h @@ -0,0 +1,313 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef SONG_H +#define SONG_H + +#include +#include +#include +#include +#include + +#include "config.h" +#include "engine/engine_fwd.h" + +namespace pb { +namespace tagreader { +class SongMetadata; +} // namespace tagreader +} // namespace pb + +class QSqlQuery; +class QUrl; + +#ifdef HAVE_LIBGPOD +struct _Itdb_Track; +#endif + +#ifdef HAVE_LIBMTP +struct LIBMTP_track_struct; +#endif + +#ifdef HAVE_LIBLASTFM +namespace lastfm { +class Track; +} +#endif + +class SqlRow; + +class Song { + + public: + + Song(); + Song(const Song &other); + ~Song(); + + static const QStringList kColumns; + static const QString kColumnSpec; + static const QString kBindSpec; + static const QString kUpdateSpec; + + static const QStringList kFtsColumns; + static const QString kFtsColumnSpec; + static const QString kFtsBindSpec; + static const QString kFtsUpdateSpec; + + static const QString kManuallyUnsetCover; + static const QString kEmbeddedCover; + + static QString JoinSpec(const QString &table); + + // Don't change these values - they're stored in the database, and defined + // in the tag reader protobuf. + // If a new lossless file is added, also add it to IsFileLossless(). + enum FileType { + Type_Unknown = 0, + Type_Asf = 1, + Type_Flac = 2, + Type_Mp4 = 3, + Type_Mpc = 4, + Type_Mpeg = 5, + Type_OggFlac = 6, + Type_OggSpeex = 7, + Type_OggVorbis = 8, + Type_Aiff = 9, + Type_Wav = 10, + Type_TrueAudio = 11, + Type_Cdda = 12, + Type_OggOpus = 13, + }; + + static QString TextForFiletype(FileType type); + QString TextForFiletype() const { return TextForFiletype(filetype()); } + + bool IsFileLossless() const; + + // Sort songs alphabetically using their pretty title + static void SortSongsListAlphabetically(QList *songs); + + // Constructors + void Init(const QString &title, const QString &artist, const QString &album, qint64 length_nanosec); + void Init(const QString &title, const QString &artist, const QString &album, qint64 beginning, qint64 end); + void InitFromProtobuf(const pb::tagreader::SongMetadata &pb); + void InitFromQuery(const SqlRow &query, bool reliable_metadata, int col = 0); + void InitFromFilePartial(const QString &filename); // Just store the filename: incomplete but fast + void InitArtManual(); // Check if there is already a art in the cache and + // store the filename in art_manual +#ifdef HAVE_LIBLASTFM + void InitFromLastFM(const lastfm::Track &track); +#endif + + void MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle); + +#ifdef HAVE_LIBGPOD + void InitFromItdb(const _Itdb_Track *track, const QString &prefix); + void ToItdb(_Itdb_Track *track) const; +#endif + +#ifdef HAVE_LIBMTP + void InitFromMTP(const LIBMTP_track_struct *track, const QString &host); + void ToMTP(LIBMTP_track_struct *track) const; +#endif + + // Copies important statistics from the other song to this one, overwriting + // any data that already exists. Useful when you want updated tags from disk + // but you want to keep user stats. + void MergeUserSetData(const Song &other); + + static QString Decode(const QString &tag, const QTextCodec *codec = nullptr); + + // Save + void BindToQuery(QSqlQuery *query) const; + void BindToFtsQuery(QSqlQuery *query) const; +#ifdef HAVE_LIBLASTFM + void ToLastFM(lastfm::Track *track, bool prefer_album_artist) const; +#endif + void ToXesam(QVariantMap *map) const; + void ToProtobuf(pb::tagreader::SongMetadata *pb) const; + + // Simple accessors + bool is_valid() const; + bool is_unavailable() const; + int id() const; + + const QString &title() const; + const QString &album() const; + const QString &artist() const; + const QString &albumartist() const; + int track() const; + int disc() const; + int year() const; + int originalyear() const; + const QString &genre() const; + bool is_compilation() const; + const QString &composer() const; + const QString &performer() const; + const QString &grouping() const; + const QString &comment() const; + + int playcount() const; + int skipcount() const; + int lastplayed() const; + int album_id() const; + + qint64 beginning_nanosec() const; + qint64 end_nanosec() const; + qint64 length_nanosec() const; + + int bitrate() const; + int samplerate() const; + int bitdepth() const; + + int directory_id() const; + const QUrl &url() const; + const QString &basefilename() const; + FileType filetype() const; + int filesize() const; + uint mtime() const; + uint ctime() const; + + const QString &art_automatic() const; + const QString &art_manual() const; + + const QString &cue_path() const; + bool has_cue() const; + + const QString &effective_album() const; + int effective_originalyear() const; + const QString &effective_albumartist() const; + + bool is_collection_song() const; + bool is_cdda() const; + + // Playlist views are special because you don't want to fill in album artists automatically for compilations, but you do for normal albums: + const QString &playlist_albumartist() const; + + // Returns true if this Song had it's cover manually unset by user. + bool has_manually_unset_cover() const; + // This method represents an explicit request to unset this song's + // cover. + void manually_unset_cover(); + + // Returns true if this song (it's media file) has an embedded cover. + bool has_embedded_cover() const; + // Sets a flag saying that this song (it's media file) has an embedded cover. + void set_embedded_cover(); + + const QImage &image() const; + + // Pretty accessors + QString PrettyTitle() const; + QString PrettyTitleWithArtist() const; + QString PrettyLength() const; + QString PrettyYear() const; + + QString TitleWithCompilationArtist() const; + + QString SampleRateBitDepthToText() const; + + // Setters + bool IsEditable() const; + + void set_id(int id); + void set_album_id(int v); + void set_valid(bool v); + + void set_title(const QString &v); + void set_album(const QString &v); + void set_artist(const QString &v); + void set_albumartist(const QString &v); + void set_track(int v); + void set_disc(int v); + void set_year(int v); + void set_originalyear(int v); + void set_genre(const QString &v); + void set_genre_id3(int id); + void set_compilation(bool v); + void set_composer(const QString &v); + void set_performer(const QString &v); + void set_grouping(const QString &v); + void set_comment(const QString &v); + + void set_beginning_nanosec(qint64 v); + void set_end_nanosec(qint64 v); + void set_length_nanosec(qint64 v); + + void set_bitrate(int v); + void set_samplerate(int v); + void set_bitdepth(int v); + + void set_directory_id(int v); + void set_url(const QUrl &v); + void set_basefilename(const QString &v); + void set_filetype(FileType v); + void set_filesize(int v); + void set_mtime(int v); + void set_ctime(int v); + void set_unavailable(bool v); + + void set_playcount(int v); + void set_skipcount(int v); + void set_lastplayed(int v); + + void set_compilation_detected(bool v); + void set_compilation_on(bool v); + void set_compilation_off(bool v); + + void set_art_automatic(const QString &v); + void set_art_manual(const QString &v); + + void set_cue_path(const QString &v); + + void set_image(const QImage &i); + + + // Comparison functions + bool IsMetadataEqual(const Song &other) const; + bool IsOnSameAlbum(const Song &other) const; + bool IsSimilar(const Song &other) const; + + bool operator==(const Song &other) const; + + // Two songs that are on the same album will have the same AlbumKey. It is + // more efficient to use IsOnSameAlbum, but this function can be used when + // you need to hash the key to do fast lookups. + QString AlbumKey() const; + + Song &operator=(const Song &other); + + private: + struct Private; + QSharedDataPointer d; +}; +Q_DECLARE_METATYPE(Song); + +typedef QList SongList; +Q_DECLARE_METATYPE(QList); + +uint qHash(const Song &song); +// Hash function using field checked in IsSimilar function +uint HashSimilar(const Song &song); + +#endif // SONG_H + diff --git a/src/core/songloader.cpp b/src/core/songloader.cpp new file mode 100644 index 00000000..42253a57 --- /dev/null +++ b/src/core/songloader.cpp @@ -0,0 +1,623 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "songloader.h" + +#include + +#if defined(HAVE_GSTREAMER) && defined(HAVE_AUDIOCD) + #include +#endif + +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "core/logging.h" +#include "core/player.h" +#include "core/signalchecker.h" +#include "core/song.h" +#include "core/tagreaderclient.h" +#include "core/timeconstants.h" +#include "collection/collectionbackend.h" +#include "collection/sqlrow.h" +#include "playlistparsers/cueparser.h" +#include "playlistparsers/parserbase.h" +#include "playlistparsers/playlistparser.h" + +#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER) + #include "device/cddasongloader.h" +#endif + +using std::placeholders::_1; + +QSet SongLoader::sRawUriSchemes; +const int SongLoader::kDefaultTimeout = 5000; + +SongLoader::SongLoader(CollectionBackendInterface *collection, const Player *player, QObject *parent) : + QObject(parent), + timeout_timer_(new QTimer(this)), + playlist_parser_(new PlaylistParser(collection, this)), + cue_parser_(new CueParser(collection, this)), + timeout_(kDefaultTimeout), + state_(WaitingForType), + success_(false), + parser_(nullptr), + collection_(collection), + player_(player) { + if (sRawUriSchemes.isEmpty()) { + sRawUriSchemes << "udp" + << "mms" + << "mmsh" + << "mmst" + << "mmsu" + << "rtsp" + << "rtspu" + << "rtspt" + << "rtsph"; + } + + timeout_timer_->setSingleShot(true); + + connect(timeout_timer_, SIGNAL(timeout()), SLOT(Timeout())); + +} + +SongLoader::~SongLoader() { + +#ifdef HAVE_GSTREAMER + if (pipeline_) { + state_ = Finished; + gst_element_set_state(pipeline_.get(), GST_STATE_NULL); + } +#endif + +} + +SongLoader::Result SongLoader::Load(const QUrl &url) { + + url_ = url; + + if (url_.scheme() == "file") { + return LoadLocal(url_.toLocalFile()); + } + + if (sRawUriSchemes.contains(url_.scheme()) || player_->HandlerForUrl(url) != nullptr) { + // The URI scheme indicates that it can't possibly be a playlist, or we have + // a custom handler for the URL, so add it as a raw stream. + //AddAsRawStream(); + return Success; + } + +#ifdef HAVE_GSTREAMER + preload_func_ = std::bind(&SongLoader::LoadRemote, this); +#endif + + return BlockingLoadRequired; +} + +void SongLoader::LoadFilenamesBlocking() { + + if (preload_func_) { + preload_func_(); + } + +} + +SongLoader::Result SongLoader::LoadLocalPartial(const QString &filename) { + + qLog(Debug) << "Fast Loading local file" << filename; + // First check to see if it's a directory - if so we can load all the songs + // inside right away. + if (QFileInfo(filename).isDir()) { + LoadLocalDirectory(filename); + return Success; + } + Song song; + song.InitFromFilePartial(filename); + if (song.is_valid()) songs_ << song; + return Success; + +} + +SongLoader::Result SongLoader::LoadAudioCD() { + +#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER) + CddaSongLoader *cdda_song_loader = new CddaSongLoader; + connect(cdda_song_loader, SIGNAL(SongsDurationLoaded(SongList)), this, SLOT(AudioCDTracksLoadedSlot(SongList))); + connect(cdda_song_loader, SIGNAL(SongsMetadataLoaded(SongList)), this, SLOT(AudioCDTracksTagsLoaded(SongList))); + cdda_song_loader->LoadSongs(); + return Success; +#else + return Error; +#endif + +} + +#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER) +void SongLoader::AudioCDTracksLoadedSlot(const SongList &songs) { + songs_ = songs; + emit AudioCDTracksLoaded(); +} + +void SongLoader::AudioCDTracksTagsLoaded(const SongList &songs) { + CddaSongLoader *cdda_song_loader = qobject_cast(sender()); + cdda_song_loader->deleteLater(); + songs_ = songs; + emit LoadAudioCDFinished(true); +} +#endif + +SongLoader::Result SongLoader::LoadLocal(const QString &filename) { + + qLog(Debug) << "Loading local file" << filename; + + // Search in the database. + QUrl url = QUrl::fromLocalFile(filename); + + CollectionQuery query; + query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec); + query.AddWhere("filename", url.toEncoded()); + + if (collection_->ExecQuery(&query) && query.Next()) { + // we may have many results when the file has many sections + do { + Song song; + song.InitFromQuery(query, true); + + if (song.is_valid()) { + songs_ << song; + } + } while (query.Next()); + + return Success; + } + + // It's not in the database, load it asynchronously. + preload_func_ = + std::bind(&SongLoader::LoadLocalAsync, this, filename); + return BlockingLoadRequired; +} + +void SongLoader::LoadLocalAsync(const QString &filename) { + + // First check to see if it's a directory - if so we will load all the songs + // inside right away. + if (QFileInfo(filename).isDir()) { + LoadLocalDirectory(filename); + return; + } + + // It's a local file, so check if it looks like a playlist. + // Read the first few bytes. + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) return; + QByteArray data(file.read(PlaylistParser::kMagicSize)); + + ParserBase *parser = playlist_parser_->ParserForMagic(data); + if (!parser) { + // Check the file extension as well, maybe the magic failed, or it was a + // basic M3U file which is just a plain list of filenames. + parser = playlist_parser_->ParserForExtension(QFileInfo(filename).suffix().toLower()); + } + + if (parser) { + qLog(Debug) << "Parsing using" << parser->name(); + + // It's a playlist! + LoadPlaylist(parser, filename); + return; + } + + // Check if it's a cue file + QString matching_cue = filename.section('.', 0, -2) + ".cue"; + if (QFile::exists(matching_cue)) { + // it's a cue - create virtual tracks + QFile cue(matching_cue); + cue.open(QIODevice::ReadOnly); + + SongList song_list = cue_parser_->Load(&cue, matching_cue, + QDir(filename.section('/', 0, -2))); + for (Song song: song_list){ + if (song.is_valid()) songs_ << song; + } + return; + } + + // Assume it's just a normal file + Song song; + song.InitFromFilePartial(filename); + if (song.is_valid()) songs_ << song; +} + +void SongLoader::LoadMetadataBlocking() { + for (int i = 0; i < songs_.size(); i++) { + EffectiveSongLoad(&songs_[i]); + } +} + +void SongLoader::EffectiveSongLoad(Song *song) { + + if (!song) return; + + if (song->filetype() != Song::Type_Unknown) { + // Maybe we loaded the metadata already, for example from a cuesheet. + return; + } + + // First, try to get the song from the collection + Song collection_song = collection_->GetSongByUrl(song->url()); + if (collection_song.is_valid()) { + *song = collection_song; + } else { + // it's a normal media file + QString filename = song->url().toLocalFile(); + TagReaderClient::Instance()->ReadFileBlocking(filename, song); + } +} + +void SongLoader::LoadPlaylist(ParserBase *parser, const QString &filename) { + QFile file(filename); + file.open(QIODevice::ReadOnly); + songs_ = parser->Load(&file, filename, QFileInfo(filename).path()); +} + +static bool CompareSongs(const Song &left, const Song &right) { + + // Order by artist, album, disc, track + if (left.artist() < right.artist()) return true; + if (left.artist() > right.artist()) return false; + if (left.album() < right.album()) return true; + if (left.album() > right.album()) return false; + if (left.disc() < right.disc()) return true; + if (left.disc() > right.disc()) return false; + if (left.track() < right.track()) return true; + if (left.track() > right.track()) return false; + return left.url() < right.url(); +} + +void SongLoader::LoadLocalDirectory(const QString &filename) { + + QDirIterator it(filename, QDir::Files | QDir::NoDotAndDotDot | QDir::Readable, QDirIterator::Subdirectories); + + while (it.hasNext()) { + LoadLocalPartial(it.next()); + } + + qStableSort(songs_.begin(), songs_.end(), CompareSongs); + + // Load the first song: all songs will be loaded async, but we want the first + // one in our list to be fully loaded, so if the user has the "Start playing + // when adding to playlist" preference behaviour set, it can enjoy the first + // song being played (seek it, have moodbar, etc.) + if (!songs_.isEmpty()) EffectiveSongLoad(&(*songs_.begin())); +} + +//void SongLoader::AddAsRawStream() { +// Song song; +// song.set_valid(true); +// song.set_filetype(Song::Type_Stream); +// song.set_url(url_); +// song.set_title(url_.toString()); +// songs_ << song; +//} + +void SongLoader::Timeout() { + state_ = Finished; + success_ = false; + StopTypefind(); +} + +void SongLoader::StopTypefind() { + +#ifdef HAVE_GSTREAMER + // Destroy the pipeline + if (pipeline_) { + gst_element_set_state(pipeline_.get(), GST_STATE_NULL); + pipeline_.reset(); + } +#endif + timeout_timer_->stop(); + + if (success_ && parser_) { + qLog(Debug) << "Parsing" << url_ << "with" << parser_->name(); + + // Parse the playlist + QBuffer buf(&buffer_); + buf.open(QIODevice::ReadOnly); + songs_ = parser_->Load(&buf); + + } + else if (success_) { + //qLog(Debug) << "Loading" << url_ << "as raw stream"; + + // It wasn't a playlist - just put the URL in as a stream + //AddAsRawStream(); + } + + emit LoadRemoteFinished(); +} + +#ifdef HAVE_GSTREAMER +void SongLoader::LoadRemote() { + + qLog(Debug) << "Loading remote file" << url_; + + // It's not a local file so we have to fetch it to see what it is. We use + // gstreamer to do this since it handles funky URLs for us (http://, ssh://, + // etc) and also has typefinder plugins. + // First we wait for typefinder to tell us what it is. If it's not text/plain + // or text/uri-list assume it's a song and return success. + // Otherwise wait to get 512 bytes of data and do magic on it - if the magic + // fails then we don't know what it is so return failure. + // If the magic succeeds then we know for sure it's a playlist - so read the + // rest of the file, parse the playlist and return success. + + timeout_timer_->start(timeout_); + + // Create the pipeline - it gets unreffed if it goes out of scope + std::shared_ptr pipeline(gst_pipeline_new(nullptr), std::bind(&gst_object_unref, _1)); + + // Create the source element automatically based on the URL + GstElement *source = gst_element_make_from_uri(GST_URI_SRC, url_.toEncoded().constData(), nullptr, nullptr); + if (!source) { + qLog(Warning) << "Couldn't create gstreamer source element for" << url_.toString(); + return; + } + + // Create the other elements and link them up + GstElement *typefind = gst_element_factory_make("typefind", nullptr); + GstElement *fakesink = gst_element_factory_make("fakesink", nullptr); + + gst_bin_add_many(GST_BIN(pipeline.get()), source, typefind, fakesink, nullptr); + gst_element_link_many(source, typefind, fakesink, nullptr); + + // Connect callbacks + GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline.get())); + CHECKED_GCONNECT(typefind, "have-type", &TypeFound, this); + gst_bus_set_sync_handler(bus, BusCallbackSync, this, NULL); + gst_bus_add_watch(bus, BusCallback, this); + + // Add a probe to the sink so we can capture the data if it's a playlist + GstPad *pad = gst_element_get_static_pad(fakesink, "sink"); + gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, &DataReady, this, NULL); + gst_object_unref(pad); + + QEventLoop loop; + loop.connect(this, SIGNAL(LoadRemoteFinished()), SLOT(quit())); + + // Start "playing" + gst_element_set_state(pipeline.get(), GST_STATE_PLAYING); + pipeline_ = pipeline; + + // Wait until loading is finished + loop.exec(); +} +#endif + +#ifdef HAVE_GSTREAMER +void SongLoader::TypeFound(GstElement *, uint, GstCaps *caps, void *self) { + + SongLoader *instance = static_cast(self); + + if (instance->state_ != WaitingForType) return; + + // Check the mimetype + instance->mime_type_ = gst_structure_get_name(gst_caps_get_structure(caps, 0)); + qLog(Debug) << "Mime type is" << instance->mime_type_; + if (instance->mime_type_ == "text/plain" || instance->mime_type_ == "text/uri-list") { + // Yeah it might be a playlist, let's get some data and have a better look + instance->state_ = WaitingForMagic; + return; + } + + // Nope, not a playlist - we're done + instance->StopTypefindAsync(true); + +} +#endif + +#ifdef HAVE_GSTREAMER +GstPadProbeReturn SongLoader::DataReady(GstPad*, GstPadProbeInfo *info, gpointer self) { + + SongLoader *instance = reinterpret_cast(self); + + if (instance->state_ == Finished) + return GST_PAD_PROBE_OK; + + GstBuffer *buffer = gst_pad_probe_info_get_buffer(info); + GstMapInfo map; + gst_buffer_map(buffer, &map, GST_MAP_READ); + + // Append the data to the buffer + instance->buffer_.append(reinterpret_cast(map.data), map.size); + qLog(Debug) << "Received total" << instance->buffer_.size() << "bytes"; + gst_buffer_unmap(buffer, &map); + + if (instance->state_ == WaitingForMagic && (instance->buffer_.size() >= PlaylistParser::kMagicSize || !instance->IsPipelinePlaying())) { + // Got enough that we can test the magic + instance->MagicReady(); + } + + return GST_PAD_PROBE_OK; +} +#endif + +#ifdef HAVE_GSTREAMER +gboolean SongLoader::BusCallback(GstBus *, GstMessage *msg, gpointer self) { + + SongLoader *instance = reinterpret_cast(self); + + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_ERROR: + instance->ErrorMessageReceived(msg); + break; + + default: + break; + } + + return TRUE; +} +#endif + +#ifdef HAVE_GSTREAMER +GstBusSyncReply SongLoader::BusCallbackSync(GstBus *, GstMessage *msg, gpointer self) { + + SongLoader *instance = reinterpret_cast(self); + + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: + instance->EndOfStreamReached(); + break; + + case GST_MESSAGE_ERROR: + instance->ErrorMessageReceived(msg); + break; + + default: + break; + } + return GST_BUS_PASS; +} +#endif + +#ifdef HAVE_GSTREAMER +void SongLoader::ErrorMessageReceived(GstMessage *msg) { + + if (state_ == Finished) return; + + GError *error; + gchar *debugs; + + gst_message_parse_error(msg, &error, &debugs); + qLog(Error) << __PRETTY_FUNCTION__ << error->message; + qLog(Error) << __PRETTY_FUNCTION__ << debugs; + + QString message_str = error->message; + + g_error_free(error); + free(debugs); + + if (state_ == WaitingForType && message_str == gst_error_get_message(GST_STREAM_ERROR, GST_STREAM_ERROR_TYPE_NOT_FOUND)) { + // Don't give up - assume it's a playlist and see if one of our parsers can + // read it. + state_ = WaitingForMagic; + return; + } + + StopTypefindAsync(false); + +} +#endif + +#ifdef HAVE_GSTREAMER +void SongLoader::EndOfStreamReached() { + + qLog(Debug) << Q_FUNC_INFO << state_; + switch (state_) { + case Finished: + break; + + case WaitingForMagic: + // Do the magic on the data we have already + MagicReady(); + if (state_ == Finished) break; + // It looks like a playlist, so parse it + + // fallthrough + case WaitingForData: + // It's a playlist and we've got all the data - finish and parse it + StopTypefindAsync(true); + break; + + case WaitingForType: + StopTypefindAsync(false); + break; + } + +} +#endif + +#ifdef HAVE_GSTREAMER +void SongLoader::MagicReady() { + + qLog(Debug) << Q_FUNC_INFO; + parser_ = playlist_parser_->ParserForMagic(buffer_, mime_type_); + + if (!parser_) { + qLog(Warning) << url_.toString() << "is text, but not a recognised playlist"; + // It doesn't look like a playlist, so just finish + StopTypefindAsync(false); + return; + } + + // We'll get more data and parse the whole thing in EndOfStreamReached + + qLog(Debug) << "Magic says" << parser_->name(); + if (parser_->name() == "ASX/INI" && url_.scheme() == "http") { + // This is actually a weird MS-WMSP stream. Changing the protocol to MMS from + // HTTP makes it playable. + parser_ = nullptr; + url_.setScheme("mms"); + StopTypefindAsync(true); + } + + state_ = WaitingForData; + + if (!IsPipelinePlaying()) { + EndOfStreamReached(); + } + +} +#endif + +#ifdef HAVE_GSTREAMER +bool SongLoader::IsPipelinePlaying() { + + GstState state = GST_STATE_NULL; + GstState pending_state = GST_STATE_NULL; + GstStateChangeReturn ret = gst_element_get_state(pipeline_.get(), &state, &pending_state, GST_SECOND); + + if (ret == GST_STATE_CHANGE_ASYNC && pending_state == GST_STATE_PLAYING) { + // We're still on the way to playing + return true; + } + return state == GST_STATE_PLAYING; + +} +#endif + +#ifdef HAVE_GSTREAMER +void SongLoader::StopTypefindAsync(bool success) { + + state_ = Finished; + success_ = success; + + metaObject()->invokeMethod(this, "StopTypefind", Qt::QueuedConnection); + +} +#endif diff --git a/src/core/songloader.h b/src/core/songloader.h new file mode 100644 index 00000000..c00edd09 --- /dev/null +++ b/src/core/songloader.h @@ -0,0 +1,152 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef SONGLOADER_H +#define SONGLOADER_H + +#include "config.h" + +#include +#include + +#ifdef HAVE_GSTREAMER + #include +#endif + +#include +#include +#include + +#include "song.h" +#include "core/tagreaderclient.h" +#include "musicbrainz/musicbrainzclient.h" + +class CueParser; +class CollectionBackendInterface; +class ParserBase; +class Player; +class PlaylistParser; +#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER) +class CddaSongLoader; +#endif + +class SongLoader : public QObject { + Q_OBJECT + public: + SongLoader(CollectionBackendInterface *collection, const Player *player, QObject *parent = nullptr); + ~SongLoader(); + + enum Result { + Success, + Error, + BlockingLoadRequired, + }; + + static const int kDefaultTimeout; + + const QUrl &url() const { return url_; } + const SongList &songs() const { return songs_; } + + int timeout() const { return timeout_; } + void set_timeout(int msec) { timeout_ = msec; } + + // If Success is returned the songs are fully loaded. If BlockingLoadRequired + // is returned LoadFilenamesBlocking() needs to be called next. + Result Load(const QUrl &url); + // Loads the files with only filenames. When finished, songs() contains a + // complete list of all Song objects, but without metadata. This method is + // blocking, do not call it from the UI thread. + void LoadFilenamesBlocking(); + // Completely load songs previously loaded with LoadFilenamesBlocking(). When + // finished, the Song objects in songs() contain metadata now. This method is + // blocking, do not call it from the UI thread. + void LoadMetadataBlocking(); + Result LoadAudioCD(); + +signals: + void AudioCDTracksLoaded(); + void LoadAudioCDFinished(bool success); + void LoadRemoteFinished(); + + private slots: + void Timeout(); + void StopTypefind(); +#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER) + void AudioCDTracksLoadedSlot(const SongList &songs); + void AudioCDTracksTagsLoaded(const SongList &songs); +#endif // HAVE_AUDIOCD + + private: + enum State { WaitingForType, WaitingForMagic, WaitingForData, Finished }; + + Result LoadLocal(const QString &filename); + void LoadLocalAsync(const QString &filename); + void EffectiveSongLoad(Song *song); + Result LoadLocalPartial(const QString &filename); + void LoadLocalDirectory(const QString &filename); + void LoadPlaylist(ParserBase *parser, const QString &filename); + +#ifdef HAVE_GSTREAMER + void LoadRemote(); + + // GStreamer callbacks + static void TypeFound(GstElement *typefind, uint probability, GstCaps *caps, void *self); + static GstPadProbeReturn DataReady(GstPad*, GstPadProbeInfo *buf, gpointer self); + static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage*, gpointer); + static gboolean BusCallback(GstBus*, GstMessage*, gpointer); + + void StopTypefindAsync(bool success); + void ErrorMessageReceived(GstMessage *msg); + void EndOfStreamReached(); + void MagicReady(); + bool IsPipelinePlaying(); + +#endif + + private: + static QSet sRawUriSchemes; + + QUrl url_; + SongList songs_; + + QTimer *timeout_timer_; + PlaylistParser *playlist_parser_; + CueParser *cue_parser_; + + // For async loads + std::function preload_func_; + int timeout_; + State state_; + bool success_; + ParserBase *parser_; + QString mime_type_; + QByteArray buffer_; + CollectionBackendInterface *collection_; + const Player *player_; + +#ifdef HAVE_GSTREAMER + std::shared_ptr pipeline_; +#endif + + QThreadPool thread_pool_; +}; + +#endif // SONGLOADER_H + diff --git a/src/core/standarditemiconloader.cpp b/src/core/standarditemiconloader.cpp new file mode 100644 index 00000000..7e3ea5a2 --- /dev/null +++ b/src/core/standarditemiconloader.cpp @@ -0,0 +1,98 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include + +#include "standarditemiconloader.h" +#include "covermanager/albumcoverloader.h" + +StandardItemIconLoader::StandardItemIconLoader(AlbumCoverLoader *cover_loader, QObject *parent) + : QObject(parent), + cover_loader_(cover_loader), + model_(nullptr) +{ + cover_options_.desired_height_ = 16; + + connect(cover_loader_, SIGNAL(ImageLoaded(quint64,QImage)), SLOT(ImageLoaded(quint64,QImage))); +} + +void StandardItemIconLoader::SetModel(QAbstractItemModel *model) { + + if (model_) { + disconnect(model_, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(RowsAboutToBeRemoved(QModelIndex,int,int))); + } + + model_ = model; + + connect(model_, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), SLOT(RowsAboutToBeRemoved(QModelIndex,int,int))); + connect(model_, SIGNAL(modelAboutToBeReset()), SLOT(ModelReset())); + +} + +void StandardItemIconLoader::LoadIcon(const QString& art_automatic, const QString& art_manual, QStandardItem *for_item) { + + const quint64 id = cover_loader_->LoadImageAsync(cover_options_, art_automatic, art_manual); + pending_covers_[id] = for_item; + +} + +void StandardItemIconLoader::LoadIcon(const Song& song, QStandardItem *for_item) { + const quint64 id = cover_loader_->LoadImageAsync(cover_options_, song); + pending_covers_[id] = for_item; +} + +void StandardItemIconLoader::RowsAboutToBeRemoved(const QModelIndex& parent, int begin, int end) { + + for (QMap::iterator it = pending_covers_.begin() ; it != pending_covers_.end() ; ) { + const QStandardItem *item = it.value(); + const QStandardItem *item_parent = item->parent(); + + if (item_parent && item_parent->index() == parent && item->index().row() >= begin && item->index().row() <= end) { + cover_loader_->CancelTask(it.key()); + it = pending_covers_.erase(it); + } + else { + ++ it; + } + } + +} + +void StandardItemIconLoader::ModelReset() { + + cover_loader_->CancelTasks(QSet::fromList(pending_covers_.keys())); + pending_covers_.clear(); + +} + +void StandardItemIconLoader::ImageLoaded(quint64 id, const QImage& image) { + + QStandardItem *item = pending_covers_.take(id); + if (!item) return; + + if (!image.isNull()) { + item->setIcon(QIcon(QPixmap::fromImage(image))); + } + +} diff --git a/src/core/standarditemiconloader.h b/src/core/standarditemiconloader.h new file mode 100644 index 00000000..8f4b8eb4 --- /dev/null +++ b/src/core/standarditemiconloader.h @@ -0,0 +1,67 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef STANDARDITEMICONLOADER_H +#define STANDARDITEMICONLOADER_H + +#include "config.h" + +#include +#include + +#include "covermanager/albumcoverloaderoptions.h" + +class AlbumCoverLoader; +class Song; + +class QAbstractItemModel; +class QModelIndex; +class QStandardItem; + +// Uses an AlbumCoverLoader to asynchronously load and set an icon on a QStandardItem. +class StandardItemIconLoader : public QObject { + Q_OBJECT + + public: + StandardItemIconLoader(AlbumCoverLoader *cover_loader, QObject *parent = nullptr); + + AlbumCoverLoaderOptions *options() { return &cover_options_; } + + void SetModel(QAbstractItemModel *model); + + void LoadIcon(const QString &art_automatic, const QString &art_manual, + QStandardItem *for_item); + void LoadIcon(const Song &song, QStandardItem *for_item); + +private slots: + void ImageLoaded(quint64 id, const QImage &image); + void RowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end); + void ModelReset(); + +private: + AlbumCoverLoader *cover_loader_; + AlbumCoverLoaderOptions cover_options_; + + QAbstractItemModel *model_; + + QMap pending_covers_; +}; + +#endif // STANDARDITEMICONLOADER_H diff --git a/src/core/stylesheetloader.cpp b/src/core/stylesheetloader.cpp new file mode 100644 index 00000000..132e8b87 --- /dev/null +++ b/src/core/stylesheetloader.cpp @@ -0,0 +1,110 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "stylesheetloader.h" +#include "core/logging.h" + +#include +#include + +StyleSheetLoader::StyleSheetLoader(QObject *parent) : QObject(parent) {} + +void StyleSheetLoader::SetStyleSheet(QWidget *widget, const QString &filename) { + + filenames_[widget] = filename; + widget->installEventFilter(this); + UpdateStyleSheet(widget); + +} + +void StyleSheetLoader::UpdateStyleSheet(QWidget *widget) { + + QString filename(filenames_[widget]); + + // Load the file + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) { + qLog(Warning) << "error opening" << filename; + return; + } + QString contents(file.readAll()); + + // Replace %palette-role with actual colours + QPalette p(widget->palette()); + + QColor alt = p.color(QPalette::AlternateBase); + alt.setAlpha(50); + contents.replace("%palette-alternate-base", QString("rgba(%1,%2,%3,%4%)") + .arg(alt.red()) + .arg(alt.green()) + .arg(alt.blue()) + .arg(alt.alpha())); + + ReplaceColor(&contents, "Window", p, QPalette::Window); + ReplaceColor(&contents, "Background", p, QPalette::Background); + ReplaceColor(&contents, "WindowText", p, QPalette::WindowText); + ReplaceColor(&contents, "Foreground", p, QPalette::Foreground); + ReplaceColor(&contents, "Base", p, QPalette::Base); + ReplaceColor(&contents, "AlternateBase", p, QPalette::AlternateBase); + ReplaceColor(&contents, "ToolTipBase", p, QPalette::ToolTipBase); + ReplaceColor(&contents, "ToolTipText", p, QPalette::ToolTipText); + ReplaceColor(&contents, "Text", p, QPalette::Text); + ReplaceColor(&contents, "Button", p, QPalette::Button); + ReplaceColor(&contents, "ButtonText", p, QPalette::ButtonText); + ReplaceColor(&contents, "BrightText", p, QPalette::BrightText); + ReplaceColor(&contents, "Light", p, QPalette::Light); + ReplaceColor(&contents, "Midlight", p, QPalette::Midlight); + ReplaceColor(&contents, "Dark", p, QPalette::Dark); + ReplaceColor(&contents, "Mid", p, QPalette::Mid); + ReplaceColor(&contents, "Shadow", p, QPalette::Shadow); + ReplaceColor(&contents, "Highlight", p, QPalette::Highlight); + ReplaceColor(&contents, "HighlightedText", p, QPalette::HighlightedText); + ReplaceColor(&contents, "Link", p, QPalette::Link); + ReplaceColor(&contents, "LinkVisited", p, QPalette::LinkVisited); + +#ifdef Q_OS_DARWIN + contents.replace("darwin", "*"); +#endif + + widget->setStyleSheet(contents); + +} + +void StyleSheetLoader::ReplaceColor(QString *css, const QString &name, const QPalette &palette, QPalette::ColorRole role) const { + + css->replace("%palette-" + name + "-lighter", palette.color(role).lighter().name(), Qt::CaseInsensitive); + css->replace("%palette-" + name + "-darker", palette.color(role).darker().name(), Qt::CaseInsensitive); + css->replace("%palette-" + name, palette.color(role).name(), Qt::CaseInsensitive); + +} + +bool StyleSheetLoader::eventFilter(QObject *obj, QEvent *event) { + + if (event->type() != QEvent::PaletteChange) return false; + + QWidget *widget = qobject_cast(obj); + if (!widget || !filenames_.contains(widget)) return false; + + UpdateStyleSheet(widget); + return false; + +} diff --git a/src/core/stylesheetloader.h b/src/core/stylesheetloader.h new file mode 100644 index 00000000..5c1abe49 --- /dev/null +++ b/src/core/stylesheetloader.h @@ -0,0 +1,53 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef STYLESHEETLOADER_H +#define STYLESHEETLOADER_H + +#include "config.h" + +#include +#include +#include +#include + +class StyleSheetLoader : public QObject { + public: + explicit StyleSheetLoader(QObject *parent = nullptr); + + // Sets the given stylesheet on the given widget. + // If the stylesheet contains strings like %palette-[role], these get replaced + // with actual palette colours. + // The stylesheet is reloaded when the widget's palette changes. + void SetStyleSheet(QWidget *widget, const QString& filename); + + protected: + bool eventFilter(QObject *obj, QEvent *event); + + private: + void UpdateStyleSheet(QWidget *widget); + void ReplaceColor(QString *css, const QString& name, const QPalette& palette, QPalette::ColorRole role) const; + + private: + QMap filenames_; +}; + +#endif // STYLESHEETLOADER_H + diff --git a/src/core/systemtrayicon.cpp b/src/core/systemtrayicon.cpp new file mode 100644 index 00000000..6724bb15 --- /dev/null +++ b/src/core/systemtrayicon.cpp @@ -0,0 +1,111 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "macsystemtrayicon.h" +#include "qtsystemtrayicon.h" +#include "systemtrayicon.h" + +SystemTrayIcon::SystemTrayIcon(QObject *parent) + : QObject(parent), + percentage_(0), + playing_icon_(":/pictures/tiny-play.png"), + paused_icon_(":/pictures/tiny-pause.png") +{ +} + +QPixmap SystemTrayIcon::CreateIcon(const QPixmap &icon, const QPixmap &grey_icon) { + + QRect rect(icon.rect()); + + // The angle of the line that's used to cover the icon. + // Centered on rect.topRight() + double angle = double(100 - song_progress()) / 100.0 * M_PI_2 + M_PI; + double length = sqrt(pow(rect.width(), 2.0) + pow(rect.height(), 2.0)); + + QPolygon mask; + mask << rect.topRight(); + mask << rect.topRight() + QPoint(length * sin(angle), -length * cos(angle)); + + if (song_progress() > 50) mask << rect.bottomLeft(); + + mask << rect.topLeft(); + mask << rect.topRight(); + + QPixmap ret(icon); + QPainter p(&ret); + + // Draw the grey bit + //p.setClipRegion(mask); + //p.drawPixmap(0, 0, grey_icon); + //p.setClipping(false); + + // Draw the playing or paused icon in the top-right + if (!current_state_icon().isNull()) { + int height = rect.height() / 2; + QPixmap scaled(current_state_icon().scaledToHeight(height, Qt::SmoothTransformation)); + + QRect state_rect(rect.width() - scaled.width(), 0, scaled.width(), scaled.height()); + p.drawPixmap(state_rect, scaled); + } + + p.end(); + + return ret; + +} + +void SystemTrayIcon::SetProgress(int percentage) { + percentage_ = percentage; + UpdateIcon(); +} + +void SystemTrayIcon::SetPaused() { + current_state_icon_ = paused_icon_; + UpdateIcon(); +} + +void SystemTrayIcon::SetPlaying(bool enable_play_pause) { + current_state_icon_ = playing_icon_; + UpdateIcon(); +} + +void SystemTrayIcon::SetStopped() { + current_state_icon_ = QPixmap(); + UpdateIcon(); +} + +SystemTrayIcon* SystemTrayIcon::CreateSystemTrayIcon(QObject *parent) { +#ifdef Q_OS_DARWIN + return new MacSystemTrayIcon(parent); +#else + return new QtSystemTrayIcon(parent); +#endif +} diff --git a/src/core/systemtrayicon.h b/src/core/systemtrayicon.h new file mode 100644 index 00000000..f7b2f35f --- /dev/null +++ b/src/core/systemtrayicon.h @@ -0,0 +1,85 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef SYSTEMTRAYICON_H +#define SYSTEMTRAYICON_H + +#include "config.h" + +#include +#include + +class QAction; +class Song; + +class SystemTrayIcon : public QObject { + Q_OBJECT + + public: + SystemTrayIcon(QObject *parent = nullptr); + + // Called once to create the icon's context menu + virtual void SetupMenu(QAction *previous, QAction *play, QAction *stop, QAction *stop_after, QAction *next, QAction *mute, QAction *quit) = 0; + + virtual bool IsVisible() const { return true; } + virtual void SetVisible(bool visible) {} + + // Called by the OSD + virtual void ShowPopup(const QString &summary, const QString &message, int timeout) {} + /** + * If this get's invoked with image_path equal to nullptr, the tooltip should + * still be shown - just without the cover art. + */ + virtual void SetNowPlaying(const Song &song, const QString &image_path) {} + virtual void ClearNowPlaying() {} + + static SystemTrayIcon *CreateSystemTrayIcon(QObject *parent = nullptr); + + public slots: + void SetProgress(int percentage); + virtual void SetPaused(); + virtual void SetPlaying(bool enable_play_pause = false); + virtual void SetStopped(); + virtual void MuteButtonStateChanged(bool value) {} + + signals: + void ChangeVolume(int delta); + void SeekForward(); + void SeekBackward(); + void NextTrack(); + void PreviousTrack(); + void ShowHide(); + void PlayPause(); + + protected: + virtual void UpdateIcon() = 0; + QPixmap CreateIcon(const QPixmap &icon, const QPixmap &grey_icon); + + int song_progress() const { return percentage_; } + QPixmap current_state_icon() const { return current_state_icon_; } + + private: + int percentage_; + QPixmap playing_icon_; + QPixmap paused_icon_; + QPixmap current_state_icon_; +}; + +#endif // SYSTEMTRAYICON_H diff --git a/src/core/tagreaderclient.cpp b/src/core/tagreaderclient.cpp new file mode 100644 index 00000000..621cf4e2 --- /dev/null +++ b/src/core/tagreaderclient.cpp @@ -0,0 +1,155 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "tagreaderclient.h" + +#include +#include +#include +#include +#include +#include + +const char *TagReaderClient::kWorkerExecutableName = "strawberry-tagreader"; +TagReaderClient *TagReaderClient::sInstance = nullptr; + +TagReaderClient::TagReaderClient(QObject *parent) : QObject(parent), worker_pool_(new WorkerPool(this)) { + + sInstance = this; + + worker_pool_->SetExecutableName(kWorkerExecutableName); + worker_pool_->SetWorkerCount(QThread::idealThreadCount()); + connect(worker_pool_, SIGNAL(WorkerFailedToStart()), SLOT(WorkerFailedToStart())); +} + +void TagReaderClient::Start() { worker_pool_->Start(); } + +void TagReaderClient::WorkerFailedToStart() { + qLog(Error) << "The" << kWorkerExecutableName << "executable was not found in the current directory or on the PATH. Strawberry will not be able to read music file tags without it."; +} + +TagReaderReply *TagReaderClient::ReadFile(const QString &filename) { + + pb::tagreader::Message message; + pb::tagreader::ReadFileRequest *req = message.mutable_read_file_request(); + + req->set_filename(DataCommaSizeFromQString(filename)); + + return worker_pool_->SendMessageWithReply(&message); + +} + +TagReaderReply *TagReaderClient::SaveFile(const QString &filename, const Song &metadata) { + + pb::tagreader::Message message; + pb::tagreader::SaveFileRequest *req = message.mutable_save_file_request(); + + req->set_filename(DataCommaSizeFromQString(filename)); + metadata.ToProtobuf(req->mutable_metadata()); + + return worker_pool_->SendMessageWithReply(&message); + +} + +TagReaderReply *TagReaderClient::IsMediaFile(const QString &filename) { + + pb::tagreader::Message message; + pb::tagreader::IsMediaFileRequest *req = message.mutable_is_media_file_request(); + + req->set_filename(DataCommaSizeFromQString(filename)); + + return worker_pool_->SendMessageWithReply(&message); + +} + +TagReaderReply *TagReaderClient::LoadEmbeddedArt(const QString &filename) { + + pb::tagreader::Message message; + pb::tagreader::LoadEmbeddedArtRequest *req = message.mutable_load_embedded_art_request(); + + req->set_filename(DataCommaSizeFromQString(filename)); + + return worker_pool_->SendMessageWithReply(&message); + +} + +void TagReaderClient::ReadFileBlocking(const QString &filename, Song *song) { + + Q_ASSERT(QThread::currentThread() != thread()); + + TagReaderReply *reply = ReadFile(filename); + if (reply->WaitForFinished()) { + song->InitFromProtobuf(reply->message().read_file_response().metadata()); + } + reply->deleteLater(); + +} + +bool TagReaderClient::SaveFileBlocking(const QString &filename, const Song &metadata) { + + Q_ASSERT(QThread::currentThread() != thread()); + + bool ret = false; + + TagReaderReply *reply = SaveFile(filename, metadata); + if (reply->WaitForFinished()) { + ret = reply->message().save_file_response().success(); + } + reply->deleteLater(); + + return ret; + +} + +bool TagReaderClient::IsMediaFileBlocking(const QString &filename) { + + Q_ASSERT(QThread::currentThread() != thread()); + + bool ret = false; + + TagReaderReply *reply = IsMediaFile(filename); + if (reply->WaitForFinished()) { + ret = reply->message().is_media_file_response().success(); + } + reply->deleteLater(); + + return ret; + +} + +QImage TagReaderClient::LoadEmbeddedArtBlocking(const QString &filename) { + + Q_ASSERT(QThread::currentThread() != thread()); + + QImage ret; + + TagReaderReply *reply = LoadEmbeddedArt(filename); + if (reply->WaitForFinished()) { + const std::string &data_str = reply->message().load_embedded_art_response().data(); + ret.loadFromData(QByteArray(data_str.data(), data_str.size())); + } + reply->deleteLater(); + + return ret; + +} + diff --git a/src/core/tagreaderclient.h b/src/core/tagreaderclient.h new file mode 100644 index 00000000..c1a3d478 --- /dev/null +++ b/src/core/tagreaderclient.h @@ -0,0 +1,79 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2011, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TAGREADERCLIENT_H +#define TAGREADERCLIENT_H + +#include "config.h" + +#include "song.h" +#include "tagreadermessages.pb.h" +#include "core/messagehandler.h" +#include "core/workerpool.h" + +#include + +class QLocalServer; +class QProcess; + +class TagReaderClient : public QObject { + Q_OBJECT + + public: + explicit TagReaderClient(QObject *parent = nullptr); + + typedef AbstractMessageHandler HandlerType; + typedef HandlerType::ReplyType ReplyType; + + static const char *kWorkerExecutableName; + + void Start(); + + ReplyType *ReadFile(const QString &filename); + ReplyType *SaveFile(const QString &filename, const Song &metadata); + ReplyType *IsMediaFile(const QString &filename); + ReplyType *LoadEmbeddedArt(const QString &filename); + + // Convenience functions that call the above functions and wait for a + // response. These block the calling thread with a semaphore, and must NOT + // be called from the TagReaderClient's thread. + void ReadFileBlocking(const QString &filename, Song *song); + bool SaveFileBlocking(const QString &filename, const Song &metadata); + bool IsMediaFileBlocking(const QString &filename); + QImage LoadEmbeddedArtBlocking(const QString &filename); + + // TODO: Make this not a singleton + static TagReaderClient *Instance() { return sInstance; } + + public slots: + + private slots: + void WorkerFailedToStart(); + + private: + static TagReaderClient *sInstance; + + WorkerPool *worker_pool_; + QList message_queue_; +}; + +typedef TagReaderClient::ReplyType TagReaderReply; + +#endif // TAGREADERCLIENT_H diff --git a/src/core/taskmanager.cpp b/src/core/taskmanager.cpp new file mode 100644 index 00000000..d7c81ebe --- /dev/null +++ b/src/core/taskmanager.cpp @@ -0,0 +1,138 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "taskmanager.h" + +TaskManager::TaskManager(QObject *parent) : QObject(parent), next_task_id_(1) {} + +int TaskManager::StartTask(const QString &name) { + + Task t; + t.name = name; + t.progress = 0; + t.progress_max = 0; + t.blocks_collection_scans = false; + + { + QMutexLocker l(&mutex_); + t.id = next_task_id_++; + tasks_[t.id] = t; + } + + emit TasksChanged(); + return t.id; + +} + +QList TaskManager::GetTasks() { + + QList ret; + + { + QMutexLocker l(&mutex_); + ret = tasks_.values(); + } + + return ret; + +} + +void TaskManager::SetTaskBlocksCollectionScans(int id) { + + { + QMutexLocker l(&mutex_); + if (!tasks_.contains(id)) return; + + Task &t = tasks_[id]; + t.blocks_collection_scans = true; + } + + emit TasksChanged(); + emit PauseCollectionWatchers(); + +} + +void TaskManager::SetTaskProgress(int id, int progress, int max) { + + { + QMutexLocker l(&mutex_); + if (!tasks_.contains(id)) return; + + Task &t = tasks_[id]; + t.progress = progress; + if (max) t.progress_max = max; + } + + emit TasksChanged(); +} + +void TaskManager::IncreaseTaskProgress(int id, int progress, int max) { + + { + QMutexLocker l(&mutex_); + if (!tasks_.contains(id)) return; + + Task &t = tasks_[id]; + t.progress += progress; + if (max) t.progress_max = max; + } + + emit TasksChanged(); + +} + +void TaskManager::SetTaskFinished(int id) { + + bool resume_collection_watchers = false; + + { + QMutexLocker l(&mutex_); + if (!tasks_.contains(id)) return; + + if (tasks_[id].blocks_collection_scans) { + resume_collection_watchers = true; + for (const Task &task : tasks_.values()) { + if (task.id != id && task.blocks_collection_scans) { + resume_collection_watchers = false; + break; + } + } + } + + tasks_.remove(id); + } + + emit TasksChanged(); + if (resume_collection_watchers) emit ResumeCollectionWatchers(); + +} + +int TaskManager::GetTaskProgress(int id) { + + { + QMutexLocker l(&mutex_); + if (!tasks_.contains(id)) return 0; + return tasks_[id].progress; + } + +} + diff --git a/src/core/taskmanager.h b/src/core/taskmanager.h new file mode 100644 index 00000000..9edd9f24 --- /dev/null +++ b/src/core/taskmanager.h @@ -0,0 +1,82 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TASKMANAGER_H +#define TASKMANAGER_H + +#include "config.h" + +#include +#include +#include + +class TaskManager : public QObject { + Q_OBJECT + + public: + explicit TaskManager(QObject *parent = nullptr); + + struct Task { + int id; + QString name; + int progress; + int progress_max; + bool blocks_collection_scans; + }; + + class ScopedTask { + public: + ScopedTask(const int task_id, TaskManager *task_manager) : task_id_(task_id), task_manager_(task_manager) {} + + ~ScopedTask() { task_manager_->SetTaskFinished(task_id_); } + + private: + const int task_id_; + TaskManager *task_manager_; + + Q_DISABLE_COPY(ScopedTask); + }; + + // Everything here is thread safe + QList GetTasks(); + + int StartTask(const QString &name); + void SetTaskBlocksCollectionScans(int id); + void SetTaskProgress(int id, int progress, int max = 0); + void IncreaseTaskProgress(int id, int progress, int max = 0); + void SetTaskFinished(int id); + int GetTaskProgress(int id); + +signals: + void TasksChanged(); + + void PauseCollectionWatchers(); + void ResumeCollectionWatchers(); + + private: + QMutex mutex_; + QMap tasks_; + int next_task_id_; + + Q_DISABLE_COPY(TaskManager); +}; + +#endif // TASKMANAGER_H + diff --git a/src/core/thread.cpp b/src/core/thread.cpp new file mode 100644 index 00000000..de65cecb --- /dev/null +++ b/src/core/thread.cpp @@ -0,0 +1,25 @@ +/* This file is part of Clementine. + Copyright 2015, 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 "config.h" + +#include "thread.h" + +void Thread::run() { + Utilities::SetThreadIOPriority(io_priority_); + QThread::run(); +} diff --git a/src/core/thread.h b/src/core/thread.h new file mode 100644 index 00000000..6a86498c --- /dev/null +++ b/src/core/thread.h @@ -0,0 +1,41 @@ +/* This file is part of Clementine. + Copyright 2015, 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 CORE_THREAD_H_ +#define CORE_THREAD_H_ + +#include "config.h" + +#include +#include "core/utilities.h" + +// Improve QThread by adding a SetIoPriority function +class Thread : public QThread { + public: + Thread(QObject* parent = nullptr) + : QThread(parent), io_priority_(Utilities::IOPRIO_CLASS_NONE) {} + + void SetIoPriority(Utilities::IoPriority priority) { + io_priority_ = priority; + } + virtual void run() override; + + private: + Utilities::IoPriority io_priority_; +}; + +#endif // CORE_THREAD_H_ diff --git a/src/core/timeconstants.h b/src/core/timeconstants.h new file mode 100644 index 00000000..a4d2015a --- /dev/null +++ b/src/core/timeconstants.h @@ -0,0 +1,32 @@ +/* This file was part of Clementine. + Copyright 2011, David Sansome + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef TIMECONSTANTS_H +#define TIMECONSTANTS_H + +#include + +// Use these to convert between time units +const qint64 kMsecPerSec = 1000ll; +const qint64 kUsecPerMsec = 1000ll; +const qint64 kUsecPerSec = 1000000ll; +const qint64 kNsecPerUsec = 1000ll; +const qint64 kNsecPerMsec = 1000000ll; +const qint64 kNsecPerSec = 1000000000ll; + +const qint64 kSecsPerDay = 24 * 60 * 60; + +#endif // TIMECONSTANTS_H diff --git a/src/core/urlhandler.cpp b/src/core/urlhandler.cpp new file mode 100644 index 00000000..540a1e4c --- /dev/null +++ b/src/core/urlhandler.cpp @@ -0,0 +1,28 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ +#include "config.h" + +#include "urlhandler.h" + +UrlHandler::LoadResult::LoadResult(const QUrl &original_url, Type type, const QUrl &media_url, qint64 length_nanosec) : original_url_(original_url), type_(type), media_url_(media_url), length_nanosec_(length_nanosec) {} + +UrlHandler::UrlHandler(QObject *parent) : QObject(parent) {} + +QIcon UrlHandler::icon() const { return QIcon(); } diff --git a/src/core/urlhandler.h b/src/core/urlhandler.h new file mode 100644 index 00000000..02bb9c19 --- /dev/null +++ b/src/core/urlhandler.h @@ -0,0 +1,89 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef URLHANDLER_H +#define URLHANDLER_H + +#include "config.h" + +#include +#include +#include + +class UrlHandler : public QObject { + Q_OBJECT + + public: + explicit UrlHandler(QObject *parent = nullptr); + + // The URL scheme that this handler handles. + virtual QString scheme() const = 0; + virtual QIcon icon() const; + + // Returned by StartLoading() and LoadNext(), indicates what the player + // should do when it wants to load a URL. + struct LoadResult { + enum Type { + // There wasn't a track available, and the player should move on to the + // next playlist item. + NoMoreTracks, + + // There might be another track available but the handler needs to do some + // work (eg. fetching a remote playlist) to find out. AsyncLoadComplete + // will be emitted later with the same original_url. + WillLoadAsynchronously, + + // There was a track available. Its url is in media_url. + TrackAvailable, + }; + + LoadResult(const QUrl &original_url = QUrl(), Type type = NoMoreTracks, const QUrl &media_url = QUrl(), qint64 length_nanosec_ = -1); + + // The url that the playlist item has in Url(). + // Might be something unplayable like lastfm://... + QUrl original_url_; + + Type type_; + + // The actual url to something that gstreamer can play. + QUrl media_url_; + + // Track length, if we are able to get it only now + qint64 length_nanosec_; + }; + + // Called by the Player when a song starts loading - gives the handler + // a chance to do something clever to get a playable track. + virtual LoadResult StartLoading(const QUrl &url) { return LoadResult(url); } + + // Called by the player when a song finishes - gives the handler a chance to + // get another track to play. + virtual LoadResult LoadNext(const QUrl &url) { return LoadResult(url); } + + // Functions to be warned when something happen to a track handled by UrlHandler. + virtual void TrackAboutToEnd() {}; + virtual void TrackSkipped() {}; + +signals: + void AsyncLoadComplete(const UrlHandler::LoadResult &result); +}; + +#endif // URLHANDLER_H + diff --git a/src/core/utilities.cpp b/src/core/utilities.cpp new file mode 100644 index 00000000..62ab8299 --- /dev/null +++ b/src/core/utilities.cpp @@ -0,0 +1,844 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_LINUX + #include + #include +#endif +#ifdef Q_OS_DARWIN + #include +#endif + +#if defined(Q_OS_UNIX) + #include +#elif defined(Q_OS_WIN32) + #include +#endif + +#ifdef Q_OS_DARWIN + #include + #include "CoreServices/CoreServices.h" + #include "IOKit/ps/IOPowerSources.h" + #include "IOKit/ps/IOPSKeys.h" +#elif defined(Q_OS_WIN32) + #include +#endif + +#include "utilities.h" +#include "core/application.h" +#include "core/logging.h" +#include "timeconstants.h" +#include "sha2.h" + +#ifdef Q_OS_DARWIN + #include "core/mac_startup.h" + #include "core/mac_utilities.h" + #include "core/scoped_cftyperef.h" +#endif + +namespace Utilities { + +static QString tr(const char *str) { + return QCoreApplication::translate("", str); +} + +QString PrettyTimeDelta(int seconds) { + return (seconds >= 0 ? "+" : "-") + PrettyTime(seconds); +} + +QString PrettyTime(int seconds) { + + // last.fm sometimes gets the track length wrong, so you end up with + // negative times. + seconds = qAbs(seconds); + + int hours = seconds / (60 * 60); + int minutes = (seconds / 60) % 60; + seconds %= 60; + + QString ret; + if (hours) ret.sprintf("%d:%02d:%02d", hours, minutes, seconds); + else ret.sprintf("%d:%02d", minutes, seconds); + + return ret; + +} + +QString PrettyTimeNanosec(qint64 nanoseconds) { + return PrettyTime(nanoseconds / kNsecPerSec); +} + +QString WordyTime(quint64 seconds) { + + quint64 days = seconds / (60 * 60 * 24); + + // TODO: Make the plural rules translatable + QStringList parts; + + if (days) parts << (days == 1 ? tr("1 day") : tr("%1 days").arg(days)); + parts << PrettyTime(seconds - days * 60 * 60 * 24); + + return parts.join(" "); + +} + +QString WordyTimeNanosec(qint64 nanoseconds) { + return WordyTime(nanoseconds / kNsecPerSec); +} + +QString Ago(int seconds_since_epoch, const QLocale &locale) { + + const QDateTime now = QDateTime::currentDateTime(); + const QDateTime then = QDateTime::fromTime_t(seconds_since_epoch); + const int days_ago = then.date().daysTo(now.date()); + const QString time = then.time().toString(locale.timeFormat(QLocale::ShortFormat)); + + if (days_ago == 0) return tr("Today") + " " + time; + if (days_ago == 1) return tr("Yesterday") + " " + time; + if (days_ago <= 7) return tr("%1 days ago").arg(days_ago); + + return then.date().toString(locale.dateFormat(QLocale::ShortFormat)); + +} + +QString PrettyFutureDate(const QDate &date) { + + const QDate now = QDate::currentDate(); + const int delta_days = now.daysTo(date); + + if (delta_days < 0) return QString(); + if (delta_days == 0) return tr("Today"); + if (delta_days == 1) return tr("Tomorrow"); + if (delta_days <= 7) return tr("In %1 days").arg(delta_days); + if (delta_days <= 14) return tr("Next week"); + + return tr("In %1 weeks").arg(delta_days / 7); + +} + +QString PrettySize(quint64 bytes) { + + QString ret; + + if (bytes > 0) { + if (bytes <= 1000) + ret = QString::number(bytes) + " bytes"; + else if (bytes <= 1000 * 1000) + ret.sprintf("%.1f KB", float(bytes) / 1000); + else if (bytes <= 1000 * 1000 * 1000) + ret.sprintf("%.1f MB", float(bytes) / (1000 * 1000)); + else + ret.sprintf("%.1f GB", float(bytes) / (1000 * 1000 * 1000)); + } + return ret; + +} + +quint64 FileSystemCapacity(const QString &path) { + +#if defined(Q_OS_UNIX) + struct statvfs fs_info; + if (statvfs(path.toLocal8Bit().constData(), &fs_info) == 0) + return quint64(fs_info.f_blocks) * quint64(fs_info.f_bsize); +#elif defined(Q_OS_WIN32) + _ULARGE_INTEGER ret; + if (GetDiskFreeSpaceEx( + QDir::toNativeSeparators(path).toLocal8Bit().constData(), nullptr, + &ret, nullptr) != 0) + return ret.QuadPart; +#endif + + return 0; + +} + +quint64 FileSystemFreeSpace(const QString &path) { + +#if defined(Q_OS_UNIX) + struct statvfs fs_info; + if (statvfs(path.toLocal8Bit().constData(), &fs_info) == 0) + return quint64(fs_info.f_bavail) * quint64(fs_info.f_bsize); +#elif defined(Q_OS_WIN32) + _ULARGE_INTEGER ret; + if (GetDiskFreeSpaceEx( + QDir::toNativeSeparators(path).toLocal8Bit().constData(), &ret, + nullptr, nullptr) != 0) + return ret.QuadPart; +#endif + + return 0; + +} + +QString MakeTempDir(const QString template_name) { + + QString path; + { + QTemporaryFile tempfile; + if (!template_name.isEmpty()) tempfile.setFileTemplate(template_name); + + tempfile.open(); + path = tempfile.fileName(); + } + + QDir d; + d.mkdir(path); + + return path; + +} + +QString GetTemporaryFileName() { + + QString file; + { + QTemporaryFile tempfile; + // Do not delete the file, we want to do something with it + tempfile.setAutoRemove(false); + tempfile.open(); + file = tempfile.fileName(); + } + + return file; + +} + +QString SaveToTemporaryFile(const QByteArray &data) { + + QTemporaryFile tempfile; + tempfile.setAutoRemove(false); + + if (!tempfile.open()) { + return QString(); + } + + if (tempfile.write(data) != data.size()) { + tempfile.remove(); + return QString(); + } + + tempfile.close(); + return tempfile.fileName(); + +} + +bool RemoveRecursive(const QString &path) { + + QDir dir(path); + for (const QString &child : dir.entryList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Hidden)) { + if (!RemoveRecursive(path + "/" + child)) + return false; + } + + for (const QString &child : dir.entryList(QDir::NoDotAndDotDot | QDir::Files | QDir::Hidden)) { + if (!QFile::remove(path + "/" + child)) + return false; + } + + if (!dir.rmdir(path)) return false; + + return true; + +} + +bool CopyRecursive(const QString &source, const QString &destination) { + + // Make the destination directory + QString dir_name = source.section('/', -1, -1); + QString dest_path = destination + "/" + dir_name; + QDir().mkpath(dest_path); + + QDir dir(source); + for (const QString &child : dir.entryList(QDir::NoDotAndDotDot | QDir::Dirs)) { + if (!CopyRecursive(source + "/" + child, dest_path)) { + qLog(Warning) << "Failed to copy dir" << source + "/" + child << "to" << dest_path; + return false; + } + } + + for (const QString &child : dir.entryList(QDir::NoDotAndDotDot | QDir::Files)) { + if (!QFile::copy(source + "/" + child, dest_path + "/" + child)) { + qLog(Warning) << "Failed to copy file" << source + "/" + child << "to" << dest_path; + return false; + } + } + return true; + +} + +bool Copy(QIODevice *source, QIODevice *destination) { + + if (!source->open(QIODevice::ReadOnly)) return false; + + if (!destination->open(QIODevice::WriteOnly)) return false; + + const qint64 bytes = source->size(); + std::unique_ptr data(new char[bytes]); + qint64 pos = 0; + + qint64 bytes_read; + do { + bytes_read = source->read(data.get() + pos, bytes - pos); + if (bytes_read == -1) return false; + + pos += bytes_read; + } while (bytes_read > 0 && pos != bytes); + + pos = 0; + qint64 bytes_written; + do { + bytes_written = destination->write(data.get() + pos, bytes - pos); + if (bytes_written == -1) return false; + + pos += bytes_written; + } while (bytes_written > 0 && pos != bytes); + + return true; + +} + +QString ColorToRgba(const QColor &c) { + + return QString("rgba(%1, %2, %3, %4)") + .arg(c.red()) + .arg(c.green()) + .arg(c.blue()) + .arg(c.alpha()); + +} + +QString GetConfigPath(ConfigPath config) { + + switch (config) { + + case Path_Root: { + if (Application::kIsPortable) { + return QString("%1/data").arg(QCoreApplication::applicationDirPath()); + } +#ifdef Q_OS_DARWIN + return mac::GetApplicationSupportPath() + "/strawberry"; +#else + return QString("%1/.config/strawberry").arg(QDir::homePath()); +#endif + } + break; + + case Path_CacheRoot: { + if (Application::kIsPortable) { + return GetConfigPath(Path_Root) + "/cache"; + } +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) + char *xdg = getenv("XDG_CACHE_HOME"); + if (!xdg || !*xdg) { + return QString("%1/.cache/strawberry").arg(QDir::homePath()); + } + else { + return QString("%1/strawberry").arg(xdg); + } +#else + return GetConfigPath(Path_Root); +#endif + } + break; + + case Path_Icons: + return GetConfigPath(Path_Root) + "/customiconset"; + + case Path_AlbumCovers: + return GetConfigPath(Path_Root) + "/albumcovers"; + + case Path_NetworkCache: + return GetConfigPath(Path_CacheRoot) + "/networkcache"; + + case Path_GstreamerRegistry: + return GetConfigPath(Path_Root) + QString("/gst-registry-%1-bin").arg(QCoreApplication::applicationVersion()); + + case Path_DefaultMusicCollection: +#ifdef Q_OS_DARWIN + return mac::GetMusicDirectory(); +#else + return QDir::homePath(); +#endif + + default: + qFatal("%s", Q_FUNC_INFO); + return QString::null; + } + +} + +#ifdef Q_OS_DARWIN +qint32 GetMacVersion() { + + SInt32 minor_version; + Gestalt(gestaltSystemVersionMinor, &minor_version); + return minor_version; + +} + +// Better than openUrl(dirname(path)) - also highlights file at path +void RevealFileInFinder(QString const &path) { + QProcess::execute("/usr/bin/open", QStringList() << "-R" << path); +} +#endif // Q_OS_DARWIN + +#ifdef Q_OS_WIN +void ShowFileInExplorer(QString const &path) { + QProcess::execute("explorer.exe", QStringList() << "/select," << QDir::toNativeSeparators(path)); +} +#endif + +void OpenInFileBrowser(const QList &urls) { + + QSet dirs; + + for (const QUrl &url : urls) { + if (url.scheme() != "file") { + continue; + } + QString path = url.toLocalFile(); + + if (!QFile::exists(path)) continue; + + const QString directory = QFileInfo(path).dir().path(); + if (dirs.contains(directory)) continue; + dirs.insert(directory); + qLog(Debug) << path; +#ifdef Q_OS_DARWIN + // revealing multiple files in the finder only opens one window, + // so it also makes sense to reveal at most one per directory + RevealFileInFinder(path); +#elif defined(Q_OS_WIN32) + ShowFileInExplorer(path); +#else + QDesktopServices::openUrl(QUrl::fromLocalFile(directory)); +#endif + } + +} + +QByteArray Hmac(const QByteArray &key, const QByteArray &data, HashFunction method) { + + const int kBlockSize = 64; // bytes + Q_ASSERT(key.length() <= kBlockSize); + + QByteArray inner_padding(kBlockSize, char(0x36)); + QByteArray outer_padding(kBlockSize, char(0x5c)); + + for (int i = 0; i < key.length(); ++i) { + inner_padding[i] = inner_padding[i] ^ key[i]; + outer_padding[i] = outer_padding[i] ^ key[i]; + } + if (Md5_Algo == method) { + return QCryptographicHash::hash(outer_padding + QCryptographicHash::hash(inner_padding + data, QCryptographicHash::Md5), QCryptographicHash::Md5); + } + else if (Sha1_Algo == method) { + return + QCryptographicHash::hash(outer_padding + QCryptographicHash::hash(inner_padding + data, QCryptographicHash::Sha1), QCryptographicHash::Sha1); + } + else { // Sha256_Algo, currently default + return Sha256(outer_padding + Sha256(inner_padding + data)); + } + +} + +QByteArray HmacSha256(const QByteArray &key, const QByteArray &data) { + return Hmac(key, data, Sha256_Algo); +} + +QByteArray HmacMd5(const QByteArray &key, const QByteArray &data) { + return Hmac(key, data, Md5_Algo); +} + +QByteArray HmacSha1(const QByteArray &key, const QByteArray &data) { + return Hmac(key, data, Sha1_Algo); +} + +QByteArray Sha256(const QByteArray &data) { + + #ifndef USE_SYSTEM_SHA2 + using strawberry_sha2::SHA256_CTX; + using strawberry_sha2::SHA256_Init; + using strawberry_sha2::SHA256_Update; + using strawberry_sha2::SHA256_Final; + using strawberry_sha2::SHA256_DIGEST_LENGTH; + #endif + + SHA256_CTX context; + SHA256_Init(&context); + SHA256_Update(&context, reinterpret_cast(data.constData()), data.length()); + + QByteArray ret(SHA256_DIGEST_LENGTH, '\0'); + SHA256_Final(reinterpret_cast(ret.data()), &context); + + return ret; + +} + +// File must not be open and will be closed afterwards! +QByteArray Sha1File(QFile &file) { + + file.open(QIODevice::ReadOnly); + QCryptographicHash hash(QCryptographicHash::Sha1); + QByteArray data; + + while (!file.atEnd()) { + data = file.read(1000000); // 1 mib + hash.addData(data.data(), data.length()); + data.clear(); + } + + file.close(); + + return hash.result(); + +} + +QByteArray Sha1CoverHash(const QString &artist, const QString &album) { + + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(artist.toLower().toUtf8().constData()); + hash.addData(album.toLower().toUtf8().constData()); + + return hash.result(); + +} + +QString PrettySize(const QSize &size) { + return QString::number(size.width()) + "x" + QString::number(size.height()); +} + +void ForwardMouseEvent(const QMouseEvent *e, QWidget *target) { + QMouseEvent c(e->type(), target->mapFromGlobal(e->globalPos()), e->globalPos(), e->button(), e->buttons(), e->modifiers()); + + QApplication::sendEvent(target, &c); +} + +bool IsMouseEventInWidget(const QMouseEvent *e, const QWidget *widget) { + return widget->rect().contains(widget->mapFromGlobal(e->globalPos())); +} + +quint16 PickUnusedPort() { + + forever { + const quint16 port = 49152 + qrand() % 16384; + + QTcpServer server; + if (server.listen(QHostAddress::Any, port)) { + return port; + } + } + +} + +void ConsumeCurrentElement(QXmlStreamReader *reader) { + + int level = 1; + while (level != 0 && !reader->atEnd()) { + switch (reader->readNext()) { + case QXmlStreamReader::StartElement: ++level; break; + case QXmlStreamReader::EndElement: --level; break; + default: break; + } + } + +} + +bool ParseUntilElement(QXmlStreamReader *reader, const QString &name) { + + while (!reader->atEnd()) { + QXmlStreamReader::TokenType type = reader->readNext(); + switch (type) { + case QXmlStreamReader::StartElement: + if (reader->name() == name) { + return true; + } + break; + default: + break; + } + } + return false; + +} + +QDateTime ParseRFC822DateTime(const QString &text) { + + QRegExp regexp( + "(\\d{1,2}) (\\w{3,12}) (\\d+) (\\d{1,2}):(\\d{1,2}):(\\d{1,2})"); + if (regexp.indexIn(text) == -1) { + return QDateTime(); + } + + enum class MatchNames { DAYS = 1, MONTHS, YEARS, HOURS, MINUTES, SECONDS }; + + QMap monthmap; + monthmap["Jan"] = 1; + monthmap["Feb"] = 2; + monthmap["Mar"] = 3; + monthmap["Apr"] = 4; + monthmap["May"] = 5; + monthmap["Jun"] = 6; + monthmap["Jul"] = 7; + monthmap["Aug"] = 8; + monthmap["Sep"] = 9; + monthmap["Oct"] = 10; + monthmap["Nov"] = 11; + monthmap["Dec"] = 12; + monthmap["January"] = 1; + monthmap["February"] = 2; + monthmap["March"] = 3; + monthmap["April"] = 4; + monthmap["May"] = 5; + monthmap["June"] = 6; + monthmap["July"] = 7; + monthmap["August"] = 8; + monthmap["September"] = 9; + monthmap["October"] = 10; + monthmap["November"] = 11; + monthmap["December"] = 12; + + const QDate date(regexp.cap(static_cast(MatchNames::YEARS)).toInt(), monthmap[regexp.cap(static_cast(MatchNames::MONTHS))], regexp.cap(static_cast(MatchNames::DAYS)).toInt()); + + const QTime time(regexp.cap(static_cast(MatchNames::HOURS)).toInt(), regexp.cap(static_cast(MatchNames::MINUTES)).toInt(), regexp.cap(static_cast(MatchNames::SECONDS)).toInt()); + + return QDateTime(date, time); + +} + +const char *EnumToString(const QMetaObject &meta, const char *name, int value) { + + int index = meta.indexOfEnumerator(name); + if (index == -1) return "[UnknownEnum]"; + QMetaEnum metaenum = meta.enumerator(index); + const char *result = metaenum.valueToKey(value); + if (result == 0) return "[UnknownEnumValue]"; + return result; + +} + +QStringList Prepend(const QString &text, const QStringList &list) { + + QStringList ret(list); + for (int i = 0; i < ret.count(); ++i) ret[i].prepend(text); + return ret; + +} + +QStringList Updateify(const QStringList &list) { + + QStringList ret(list); + for (int i = 0; i < ret.count(); ++i) ret[i].prepend(ret[i] + " = :"); + return ret; + +} + +QString DecodeHtmlEntities(const QString &text) { + + QString copy(text); + copy.replace("&", "&"); + copy.replace(""", "\""); + copy.replace("'", "'"); + copy.replace("<", "<"); + copy.replace(">", ">"); + return copy; + +} + +int SetThreadIOPriority(IoPriority priority) { + +#ifdef Q_OS_LINUX + return syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, GetThreadId(), + 4 | priority << IOPRIO_CLASS_SHIFT); +#elif defined(Q_OS_DARWIN) + return setpriority(PRIO_DARWIN_THREAD, 0, + priority == IOPRIO_CLASS_IDLE ? PRIO_DARWIN_BG : 0); +#else + return 0; +#endif + +} + +int GetThreadId() { + +#ifdef Q_OS_LINUX + return syscall(SYS_gettid); +#else + return 0; +#endif + +} + +bool IsLaptop() { + +#ifdef Q_OS_WIN + SYSTEM_POWER_STATUS status; + if (!GetSystemPowerStatus(&status)) { + return false; + } + + return !(status.BatteryFlag & 128); // 128 = no system battery +#elif defined(Q_OS_LINUX) + return !QDir("/proc/acpi/battery").entryList(QDir::Dirs | QDir::NoDotAndDotDot).isEmpty(); +#elif defined(Q_OS_MAC) + ScopedCFTypeRef power_sources(IOPSCopyPowerSourcesInfo()); + ScopedCFTypeRef power_source_list(IOPSCopyPowerSourcesList(power_sources.get())); + for (CFIndex i = 0; i < CFArrayGetCount(power_source_list.get()); ++i) { + CFTypeRef ps = CFArrayGetValueAtIndex(power_source_list.get(), i); + CFDictionaryRef description = IOPSGetPowerSourceDescription(power_sources.get(), ps); + + if (CFDictionaryContainsKey(description, CFSTR(kIOPSBatteryHealthKey))) { + return true; + } + } + return false; +#else + return false; +#endif + +} + +QString SystemLanguageName() { + +#if QT_VERSION >= 0x040800 + QString system_language = QLocale::system().uiLanguages().empty() ? QLocale::system().name() : QLocale::system().uiLanguages().first(); + // uiLanguages returns strings with "-" as separators for language/region; + // however QTranslator needs "_" separators + system_language.replace("-", "_"); +#else + QString system_language = QLocale::system().name(); +#endif + + return system_language; + +} + +bool UrlOnSameDriveAsStrawberry(const QUrl &url) { + + if (url.scheme() != "file") return false; + +#ifdef Q_OS_WIN + QUrl appUrl = QUrl::fromLocalFile(QCoreApplication::applicationDirPath()); + if (url.toLocalFile().left(1) == appUrl.toLocalFile().left(1)) + return true; + else + return false; +#else + // Non windows systems have always a / in the path + return true; +#endif + +} + +QUrl GetRelativePathToStrawberryBin(const QUrl &url) { + + QDir appPath(QCoreApplication::applicationDirPath()); + return QUrl::fromLocalFile(appPath.relativeFilePath(url.toLocalFile())); + +} + +QString PathWithoutFilenameExtension(const QString &filename) { + + if (filename.section('/', -1, -1).contains('.')) + return filename.section('.', 0, -2); + return filename; + +} + +QString FiddleFileExtension(const QString &filename, const QString &new_extension) { + return PathWithoutFilenameExtension(filename) + "." + new_extension; +} + +void SetEnv(const char *key, const QString &value) { + +#ifdef Q_OS_WIN32 + putenv(QString("%1=%2").arg(key, value).toLocal8Bit().constData()); +#else + setenv(key, value.toLocal8Bit().constData(), 1); +#endif + +} + +void IncreaseFDLimit() { + +#ifdef Q_OS_DARWIN + // Bump the soft limit for the number of file descriptors from the default of 256 to + // the maximum (usually 10240). + struct rlimit limit; + getrlimit(RLIMIT_NOFILE, &limit); + + // getrlimit() lies about the hard limit so we have to check sysctl. + int max_fd = 0; + size_t len = sizeof(max_fd); + sysctlbyname("kern.maxfilesperproc", &max_fd, &len, nullptr, 0); + + limit.rlim_cur = max_fd; + int ret = setrlimit(RLIMIT_NOFILE, &limit); + + if (ret == 0) { + qLog(Debug) << "Max fd:" << max_fd; + } +#endif + +} + +void CheckPortable() { + + QFile f(QApplication::applicationDirPath() + QDir::separator() + "data"); + if (f.exists()) { + // We are portable. Set the bool and change the qsettings path + Application::kIsPortable = true; + + QSettings::setDefaultFormat(QSettings::IniFormat); + QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, f.fileName()); + } + +} + +} // namespace Utilities + +ScopedWCharArray::ScopedWCharArray(const QString &str) + : chars_(str.length()), data_(new wchar_t[chars_ + 1]) { + str.toWCharArray(data_.get()); + data_[chars_] = '\0'; +} + diff --git a/src/core/utilities.h b/src/core/utilities.h new file mode 100644 index 00000000..90b4ca82 --- /dev/null +++ b/src/core/utilities.h @@ -0,0 +1,188 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef UTILITIES_H +#define UTILITIES_H + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +class QIODevice; +class QMouseEvent; +class QXmlStreamReader; +struct QMetaObject; + +namespace Utilities { +QString PrettyTime(int seconds); +QString PrettyTimeDelta(int seconds); +QString PrettyTimeNanosec(qint64 nanoseconds); +QString PrettySize(quint64 bytes); +QString PrettySize(const QSize &size); +QString WordyTime(quint64 seconds); +QString WordyTimeNanosec(qint64 nanoseconds); +QString Ago(int seconds_since_epoch, const QLocale &locale); +QString PrettyFutureDate(const QDate &date); + +QString ColorToRgba(const QColor &color); + +quint64 FileSystemCapacity(const QString &path); +quint64 FileSystemFreeSpace(const QString &path); + +QString MakeTempDir(const QString template_name = QString()); +QString GetTemporaryFileName(); +QString SaveToTemporaryFile(const QByteArray &data); + +bool RemoveRecursive(const QString &path); +bool CopyRecursive(const QString &source, const QString &destination); +bool Copy(QIODevice *source, QIODevice *destination); + +void OpenInFileBrowser(const QList &filenames); + + enum HashFunction { + Md5_Algo, + Sha256_Algo, + Sha1_Algo, + }; +QByteArray Hmac(const QByteArray &key, const QByteArray &data, HashFunction algo); +QByteArray HmacMd5(const QByteArray &key, const QByteArray &data); +QByteArray HmacSha256(const QByteArray &key, const QByteArray &data); +QByteArray HmacSha1(const QByteArray &key, const QByteArray &data); +QByteArray Sha256(const QByteArray &data); +QByteArray Sha1File(QFile &file); +QByteArray Sha1CoverHash(const QString &artist, const QString &album); + +// Picks an unused ephemeral port number. Doesn't hold the port open so +// there's the obvious race condition +quint16 PickUnusedPort(); + +// Forwards a mouse event to a different widget, remapping the event's widget +// coordinates relative to those of the target widget. +void ForwardMouseEvent(const QMouseEvent *e, QWidget *target); + +// Checks if the mouse event was inside the widget's rectangle. +bool IsMouseEventInWidget(const QMouseEvent *e, const QWidget *widget); + +// Reads all children of the current element, and returns with the stream +// reader either on the EndElement for the current element, or the end of the +// file - whichever came first. +void ConsumeCurrentElement(QXmlStreamReader *reader); + +// Advances the stream reader until it finds an element with the given name. +// Returns false if the end of the document was reached before finding a +// matching element. +bool ParseUntilElement(QXmlStreamReader *reader, const QString &name); + +// Parses a string containing an RFC822 time and date. +QDateTime ParseRFC822DateTime(const QString &text); + +// Replaces some HTML entities with their normal characters. +QString DecodeHtmlEntities(const QString &text); + +// Shortcut for getting a Qt-aware enum value as a string. +// Pass in the QMetaObject of the class that owns the enum, the string name of +// the enum and a valid value from that enum. +const char *EnumToString(const QMetaObject &meta, const char *name, int value); + +QStringList Prepend(const QString &text, const QStringList &list); +QStringList Updateify(const QStringList &list); + +// Check if two urls are on the same drive (mainly for windows) +bool UrlOnSameDriveAsStrawberry(const QUrl &url); + +// Get relative path to Strawberry binary +QUrl GetRelativePathToStrawberryBin(const QUrl &url); + +// Get the path without the filename extension +QString PathWithoutFilenameExtension(const QString &filename); +QString FiddleFileExtension(const QString &filename, const QString &new_extension); + +void SetEnv(const char *key, const QString &value); +void IncreaseFDLimit(); +void CheckPortable(); + +enum ConfigPath { + Path_Root, + Path_Icons, + Path_AlbumCovers, + Path_NetworkCache, + Path_GstreamerRegistry, + Path_DefaultMusicCollection, + Path_LocalSpotifyBlob, + Path_MoodbarCache, + Path_CacheRoot, +}; +QString GetConfigPath(ConfigPath config); + +// Returns the minor version of OS X (ie. 6 for Snow Leopard, 7 for Lion). +qint32 GetMacVersion(); + +// Borrowed from schedutils +enum IoPriority { + IOPRIO_CLASS_NONE = 0, + IOPRIO_CLASS_RT, + IOPRIO_CLASS_BE, + IOPRIO_CLASS_IDLE, +}; + enum { + IOPRIO_WHO_PROCESS = 1, + IOPRIO_WHO_PGRP, + IOPRIO_WHO_USER, + }; +static const int IOPRIO_CLASS_SHIFT = 13; + +int SetThreadIOPriority(IoPriority priority); +int GetThreadId(); + +// Returns true if this machine has a battery. +bool IsLaptop(); + +QString SystemLanguageName(); +} + +class ScopedWCharArray { + public: + explicit ScopedWCharArray(const QString &str); + + QString ToString() const { return QString::fromWCharArray(data_.get()); } + + wchar_t *get() const { return data_.get(); } + operator wchar_t*() const { return get(); } + + int characters() const { return chars_; } + int bytes() const { return (chars_ + 1) *sizeof(wchar_t); } + + private: + Q_DISABLE_COPY(ScopedWCharArray); + + int chars_; + std::unique_ptr data_; +}; + +#endif // UTILITIES_H + diff --git a/src/core/windows7thumbbar.cpp b/src/core/windows7thumbbar.cpp new file mode 100644 index 00000000..41a83bf3 --- /dev/null +++ b/src/core/windows7thumbbar.cpp @@ -0,0 +1,178 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include + +#include "windows7thumbbar.h" + +#include "core/logging.h" + +#ifdef Q_OS_WIN32 +# define _WIN32_WINNT 0x0600 +# include +# include +# include +#endif // Q_OS_WIN32 + + +const int Windows7ThumbBar::kIconSize = 16; +const int Windows7ThumbBar::kMaxButtonCount = 7; + + +Windows7ThumbBar::Windows7ThumbBar(QWidget *widget) + : QObject(widget), + widget_(widget), + button_created_message_id_(0), + taskbar_list_(nullptr) {} + +void Windows7ThumbBar::SetActions(const QList& actions) { + +#ifdef Q_OS_WIN32 + qLog(Debug) << "Setting actions"; + Q_ASSERT(actions.count() <= kMaxButtonCount); + + actions_ = actions; + for (QAction *action : actions) { + if (action) { + connect(action, SIGNAL(changed()), SLOT(ActionChanged())); + } + } + qLog(Debug) << "Done"; +#endif // Q_OS_WIN32 + +} + +#ifdef Q_OS_WIN32 +static void SetupButton(const QAction *action, THUMBBUTTON *button) { + + if (action) { + button->hIcon = action->icon().pixmap(Windows7ThumbBar::kIconSize).toWinHICON(); + button->dwFlags = action->isEnabled() ? THBF_ENABLED : THBF_DISABLED; + // This is unsafe - doesn't obey 260-char restriction + action->text().toWCharArray(button->szTip); + button->szTip[action->text().count()] = L'\0'; + + if (!action->isVisible()) { + button->dwFlags = THUMBBUTTONFLAGS(button->dwFlags | THBF_HIDDEN); + } + button->dwMask = THUMBBUTTONMASK(THB_ICON | THB_TOOLTIP | THB_FLAGS); + } + else { + button->hIcon = 0; + button->szTip[0] = L'\0'; + button->dwFlags = THBF_NOBACKGROUND; + button->dwMask = THUMBBUTTONMASK(THB_FLAGS); + } +} +#endif // Q_OS_WIN32 + +void Windows7ThumbBar::HandleWinEvent(MSG *msg) { + +#ifdef Q_OS_WIN32 + if (button_created_message_id_ == 0) { + // Compute the value for the TaskbarButtonCreated message + button_created_message_id_ = RegisterWindowMessage("TaskbarButtonCreated"); + qLog(Debug) << "TaskbarButtonCreated message ID registered" << button_created_message_id_; + } + + if (msg->message == button_created_message_id_) { + HRESULT hr; + qLog(Debug) << "Button created"; + // Unref the old taskbar list if we had one + if (taskbar_list_) { + qLog(Debug) << "Releasing old taskbar list"; + reinterpret_cast(taskbar_list_)->Release(); + taskbar_list_ = nullptr; + } + + // Copied from win7 SDK shobjidl.h + static const GUID CLSID_ITaskbarList ={ 0x56FDF344,0xFD6D,0x11d0,{0x95,0x8A,0x00,0x60,0x97,0xC9,0xA0,0x90}}; + // Create the taskbar list + hr = CoCreateInstance(CLSID_ITaskbarList, nullptr, CLSCTX_ALL, + IID_ITaskbarList3, (void**)&taskbar_list_); + if (hr != S_OK) { + qLog(Warning) << "Error creating the ITaskbarList3 interface" << hex << DWORD (hr); + return; + } + + ITaskbarList3 *taskbar_list = reinterpret_cast(taskbar_list_); + hr = taskbar_list->HrInit(); + if (hr != S_OK) { + qLog(Warning) << "Error initialising taskbar list" << hex << DWORD (hr); + taskbar_list->Release(); + taskbar_list_ = nullptr; + return; + } + + // Add the buttons + qLog(Debug) << "Initialising" << actions_.count() << "buttons"; + THUMBBUTTON buttons[kMaxButtonCount]; + for (int i = 0; i < actions_.count(); ++i) { + const QAction *action = actions_[i]; + THUMBBUTTON *button = &buttons[i]; + button->iId = i; + SetupButton(action, button); + } + + qLog(Debug) << "Adding buttons"; + hr = taskbar_list->ThumbBarAddButtons(widget_->winId(), actions_.count(), buttons); + if (hr != S_OK) + qLog(Debug) << "Failed to add buttons" << hex << DWORD (hr); + for (int i = 0; i < actions_.count(); i++) { + if (buttons[i].hIcon > 0) + DestroyIcon (buttons[i].hIcon); + } + } else if (msg->message == WM_COMMAND) { + const int button_id = LOWORD(msg->wParam); + + if (button_id >= 0 && button_id < actions_.count()) { + if (actions_[button_id]) { + qLog(Debug) << "Button activated"; + actions_[button_id]->activate(QAction::Trigger); + } + } + } +#endif // Q_OS_WIN32 + +} + +void Windows7ThumbBar::ActionChanged() { + +#ifdef Q_OS_WIN32 + if (!taskbar_list_) return; + ITaskbarList3 *taskbar_list = reinterpret_cast(taskbar_list_); + + THUMBBUTTON buttons[kMaxButtonCount]; + for (int i = 0; i < actions_.count(); ++i) { + const QAction *action = actions_[i]; + THUMBBUTTON *button = &buttons[i]; + + button->iId = i; + SetupButton(action, button); + if (buttons->hIcon > 0) DestroyIcon(buttons->hIcon); + } + + taskbar_list->ThumbBarUpdateButtons(widget_->winId(), actions_.count(), buttons); +#endif // Q_OS_WIN32 + +} diff --git a/src/core/windows7thumbbar.h b/src/core/windows7thumbbar.h new file mode 100644 index 00000000..4589b0fa --- /dev/null +++ b/src/core/windows7thumbbar.h @@ -0,0 +1,64 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef WINDOWS7THUMBBAR_H +#define WINDOWS7THUMBBAR_H + +#include "config.h" + +#include +#include + +#ifndef Q_OS_WIN32 + typedef void MSG; +#endif // Q_OS_WIN32 + +class Windows7ThumbBar : public QObject { + Q_OBJECT + +public: + // Creates a list of buttons in the taskbar icon for this window. Does + // nothing and is safe to use on other operating systems too. + Windows7ThumbBar(QWidget *widget = 0); + + static const int kIconSize; + static const int kMaxButtonCount; + + // You must call this in the parent widget's constructor before returning + // to the event loop. If an action is nullptr it becomes a spacer. + void SetActions(const QList &actions); + + // Call this from the parent's winEvent() function. + void HandleWinEvent(MSG *msg); + +private slots: + void ActionChanged(); + +private: + QWidget *widget_; + QList actions_; + + unsigned int button_created_message_id_; + + // Really an ITaskbarList3* but I don't want to have to include windows.h here + void* taskbar_list_; +}; + +#endif // WINDOWS7THUMBBAR_H diff --git a/src/covermanager/albumcoverchoicecontroller.cpp b/src/covermanager/albumcoverchoicecontroller.cpp new file mode 100644 index 00000000..4b395d13 --- /dev/null +++ b/src/covermanager/albumcoverchoicecontroller.cpp @@ -0,0 +1,348 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "covermanager/albumcoverchoicecontroller.h" + +#include "core/application.h" +#include "core/logging.h" +#include "core/utilities.h" +#include "core/iconloader.h" +#include "collection/collectionbackend.h" + +#include "covermanager/albumcoverfetcher.h" +#include "covermanager/albumcoverloader.h" +#include "covermanager/currentartloader.h" +#include "covermanager/albumcovermanager.h" +#include "covermanager/albumcoversearcher.h" +#include "covermanager/coverfromurldialog.h" + +const char *AlbumCoverChoiceController::kLoadImageFileFilter = QT_TR_NOOP("Images (*.png *.jpg *.jpeg *.bmp *.gif *.xpm *.pbm *.pgm *.ppm *.xbm)"); +const char *AlbumCoverChoiceController::kSaveImageFileFilter = QT_TR_NOOP("Images (*.png *.jpg *.jpeg *.bmp *.xpm *.pbm *.ppm *.xbm)"); +const char *AlbumCoverChoiceController::kAllFilesFilter = QT_TR_NOOP("All files (*)"); + +QSet *AlbumCoverChoiceController::sImageExtensions = nullptr; + +AlbumCoverChoiceController::AlbumCoverChoiceController(QWidget *parent) : + QWidget(parent), + app_(nullptr), + cover_searcher_(nullptr), + cover_fetcher_(nullptr), + save_file_dialog_(nullptr), + cover_from_url_dialog_(nullptr) { + + cover_from_file_ = new QAction(IconLoader::Load("document-open"), tr("Load cover from disk..."), this); + cover_to_file_ = new QAction(IconLoader::Load("document-save"), tr("Save cover to disk..."), this); + cover_from_url_ = new QAction(IconLoader::Load("download"), tr("Load cover from URL..."), this); + search_for_cover_ = new QAction(IconLoader::Load("search"), tr("Search for album covers..."), this); + unset_cover_ = new QAction(IconLoader::Load("list-remove"), tr("Unset cover"), this); + show_cover_ = new QAction(IconLoader::Load("zoom-in"), tr("Show fullsize..."), this); + + search_cover_auto_ = new QAction(IconLoader::Load("search"), tr("Search automatically"), this); + search_cover_auto_->setCheckable(true); + search_cover_auto_->setChecked(false); + + separator_ = new QAction(this); + separator_->setSeparator(true); + +} + +AlbumCoverChoiceController::~AlbumCoverChoiceController() {} + +void AlbumCoverChoiceController::SetApplication(Application *app) { + + app_ = app; + + cover_fetcher_ = new AlbumCoverFetcher(app_->cover_providers(), this); + cover_searcher_ = new AlbumCoverSearcher(QIcon(":/pictures/noalbumart.png"), app, this); + cover_searcher_->Init(cover_fetcher_); + + connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(quint64, QImage, CoverSearchStatistics)), this, SLOT(AlbumCoverFetched(quint64, QImage, CoverSearchStatistics))); + +} + +QList AlbumCoverChoiceController::GetAllActions() { + return QList() << cover_from_file_ << cover_to_file_ << separator_ << cover_from_url_ << search_for_cover_ << unset_cover_ << show_cover_; +} + +QString AlbumCoverChoiceController::LoadCoverFromFile(Song *song) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QString cover = QFileDialog::getOpenFileName(this, tr("Load cover from disk"), GetInitialPathForFileDialog(*song, QString()), tr(kLoadImageFileFilter) + ";;" + tr(kAllFilesFilter)); + + if (cover.isNull()) return QString(); + + // Can we load the image? + QImage image(cover); + + if (!image.isNull()) { + SaveCover(song, cover); + return cover; + } + else { + return QString(); + } + +} + +void AlbumCoverChoiceController::SaveCoverToFile(const Song &song, const QImage &image) { + + QString initial_file_name = "/" + (song.effective_album().isEmpty() ? tr("Unknown") : song.effective_album()) + ".jpg"; + + QString save_filename = QFileDialog::getSaveFileName(this, tr("Save album cover"), GetInitialPathForFileDialog(song, initial_file_name), tr(kSaveImageFileFilter) + ";;" + tr(kAllFilesFilter)); + + if (save_filename.isNull()) return; + + QString extension = save_filename.right(4); + if (!extension.startsWith('.') || !QImageWriter::supportedImageFormats().contains(extension.right(3).toUtf8())) { + save_filename.append(".jpg"); + } + + image.save(save_filename); + +} + +QString AlbumCoverChoiceController::GetInitialPathForFileDialog(const Song &song, const QString &filename) { + + // art automatic is first to show user which cover the album may be + // using now; the song is using it if there's no manual path but we + // cannot use manual path here because it can contain cached paths + if (!song.art_automatic().isEmpty() && !song.has_embedded_cover()) { + return song.art_automatic(); + + // if no automatic art, start in the song's folder + } + else if (!song.url().isEmpty() && song.url().toLocalFile().contains('/')) { + return song.url().toLocalFile().section('/', 0, -2) + filename; + + // fallback - start in home + } + else { + return QDir::home().absolutePath() + filename; + } + +} + +QString AlbumCoverChoiceController::LoadCoverFromURL(Song *song) { + + if (!cover_from_url_dialog_) { cover_from_url_dialog_ = new CoverFromURLDialog(this); } + + QImage image = cover_from_url_dialog_->Exec(); + + if (!image.isNull()) { + QString cover = SaveCoverInCache(song->artist(), song->album(), image); + SaveCover(song, cover); + + return cover; + } + else { return QString(); } + +} + +QString AlbumCoverChoiceController::SearchForCover(Song *song) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QString album = song->effective_album(); + album = album.remove(QRegExp(" ?-? ?(\\(|\\[)(Disc|CD)? ?[0-9](\\)|\\])$")); + + // Get something sensible to stick in the search box + QImage image = cover_searcher_->Exec(song->effective_albumartist(), album); + + if (!image.isNull()) { + QString cover = SaveCoverInCache(song->artist(), song->album(), image); + SaveCover(song, cover); + + return cover; + } + else { return QString(); } + +} + +QString AlbumCoverChoiceController::UnsetCover(Song *song) { + + QString cover = Song::kManuallyUnsetCover; + SaveCover(song, cover); + + return cover; + +} + +void AlbumCoverChoiceController::ShowCover(const Song &song) { + + QDialog *dialog = new QDialog(this); + dialog->setAttribute(Qt::WA_DeleteOnClose, true); + + // Use Artist - Album as the window title + QString title_text(song.effective_albumartist()); + if (!song.effective_album().isEmpty()) title_text += " - " + song.effective_album(); + + QLabel *label = new QLabel(dialog); + label->setPixmap(AlbumCoverLoader::TryLoadPixmap(song.art_automatic(), song.art_manual(), song.url().toLocalFile())); + + // add (WxHpx) to the title before possibly resizing + title_text += " (" + QString::number(label->pixmap()->width()) + "x" + QString::number(label->pixmap()->height()) + "px)"; + + // if the cover is larger than the screen, resize the window + // 85% seems to be enough to account for title bar and taskbar etc. + QDesktopWidget desktop; + int current_screen = desktop.screenNumber(this); + int desktop_height = desktop.screenGeometry(current_screen).height(); + int desktop_width = desktop.screenGeometry(current_screen).width(); + + // resize differently if monitor is in portrait mode + if (desktop_width < desktop_height) { + const int new_width = (double)desktop_width * 0.95; + if (new_width < label->pixmap()->width()) { + label->setPixmap( + label->pixmap()->scaledToWidth(new_width, Qt::SmoothTransformation)); + } + } + else { + const int new_height = (double)desktop_height * 0.85; + if (new_height < label->pixmap()->height()) { + label->setPixmap(label->pixmap()->scaledToHeight( + new_height, Qt::SmoothTransformation)); + } + } + + dialog->setWindowTitle(title_text); + dialog->setFixedSize(label->pixmap()->size()); + dialog->show(); + +} + +void AlbumCoverChoiceController::SearchCoverAutomatically(const Song &song) { + + qint64 id = cover_fetcher_->FetchAlbumCover(song.effective_albumartist(), song.effective_album()); + + cover_fetching_tasks_[id] = song; + +} + +void AlbumCoverChoiceController::AlbumCoverFetched(quint64 id, const QImage &image, const CoverSearchStatistics &statistics) { + + Song song; + if (cover_fetching_tasks_.contains(id)) { + song = cover_fetching_tasks_.take(id); + } + + if (!image.isNull()) { + QString cover = SaveCoverInCache(song.artist(), song.album(), image); + SaveCover(&song, cover); + } + + emit AutomaticCoverSearchDone(); + +} + +void AlbumCoverChoiceController::SaveCover(Song *song, const QString &cover) { + + if (song->is_valid() && song->id() != -1) { + song->set_art_manual(cover); + app_->collection_backend()->UpdateManualAlbumArtAsync(song->artist(), song->albumartist(), song->album(), cover); + + if (song->url() == app_->current_art_loader()->last_song().url()) { + app_->current_art_loader()->LoadArt(*song); + } + } + +} + +QString AlbumCoverChoiceController::SaveCoverInCache(const QString &artist, const QString &album, const QImage &image) { + + // Hash the artist and album into a filename for the image + QString filename(Utilities::Sha1CoverHash(artist, album).toHex() + ".jpg"); + QString path(AlbumCoverLoader::ImageCacheDir() + "/" + filename); + + // Make sure this directory exists first + QDir dir; + dir.mkdir(AlbumCoverLoader::ImageCacheDir()); + + // Save the image to disk + image.save(path, "JPG"); + + return path; + +} + +bool AlbumCoverChoiceController::IsKnownImageExtension(const QString &suffix) { + + if (!sImageExtensions) { + sImageExtensions = new QSet(); + (*sImageExtensions) << "png" << "jpg" << "jpeg" << "bmp" << "gif" << "xpm" << "pbm" << "pgm" << "ppm" << "xbm"; + } + + return sImageExtensions->contains(suffix); + +} + +bool AlbumCoverChoiceController::CanAcceptDrag(const QDragEnterEvent *e) { + + for (const QUrl &url : e->mimeData()->urls()) { + const QString suffix = QFileInfo(url.toLocalFile()).suffix().toLower(); + if (IsKnownImageExtension(suffix)) return true; + } + if (e->mimeData()->hasImage()) { + return true; + } + return false; + +} + +QString AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) { + + for (const QUrl &url : e->mimeData()->urls()) { + const QString filename = url.toLocalFile(); + const QString suffix = QFileInfo(filename).suffix().toLower(); + + if (IsKnownImageExtension(suffix)) { + SaveCover(song, filename); + return filename; + } + } + + if (e->mimeData()->hasImage()) { + QImage image = qvariant_cast(e->mimeData()->imageData()); + if (!image.isNull()) { + QString cover_path = SaveCoverInCache(song->artist(), song->album(), image); + SaveCover(song, cover_path); + return cover_path; + } + } + + return QString(); + +} + diff --git a/src/covermanager/albumcoverchoicecontroller.h b/src/covermanager/albumcoverchoicecontroller.h new file mode 100644 index 00000000..348d6fdf --- /dev/null +++ b/src/covermanager/albumcoverchoicecontroller.h @@ -0,0 +1,148 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ALBUMCOVERCHOICECONTROLLER_H +#define ALBUMCOVERCHOICECONTROLLER_H + +#include "config.h" + +#include +#include +#include +#include + +class AlbumCoverFetcher; +class AlbumCoverSearcher; +class Application; +class CoverFromURLDialog; +class QFileDialog; +class Song; + +struct CoverSearchStatistics; + +// Controller for the common album cover related menu options. +class AlbumCoverChoiceController : public QWidget { + Q_OBJECT + + public: + static const char *kLoadImageFileFilter; + static const char *kSaveImageFileFilter; + static const char *kAllFilesFilter; + + AlbumCoverChoiceController(QWidget *parent = nullptr); + ~AlbumCoverChoiceController(); + + void SetApplication(Application *app); + + // Getters for all QActions implemented by this controller. + + QAction *cover_from_file_action() const { return cover_from_file_; } + QAction *cover_to_file_action() const { return cover_to_file_; } + QAction *cover_from_url_action() const { return cover_from_url_; } + QAction *search_for_cover_action() const { return search_for_cover_; } + QAction *unset_cover_action() const { return unset_cover_; } + QAction *show_cover_action() const { return show_cover_; } + QAction *search_cover_auto_action() const { return search_cover_auto_; } + + // Returns QAction* for every operation implemented by this controller. + // The list contains QAction* for: + // 1. loading cover from file + // 2. loading cover from URL + // 3. searching for cover using last.fm + // 4. unsetting the cover manually + // 5. showing the cover in original size + QList GetAllActions(); + + // All of the methods below require a currently selected song as an + // input parameter. Also - LoadCoverFromFile, LoadCoverFromURL, + // SearchForCover, UnsetCover and SaveCover all update manual path + // of the given song in collection to the new cover. + + // Lets the user choose a cover from disk. If no cover will be chosen or the chosen + // cover will not be a proper image, this returns an empty string. Otherwise, the + // path to the chosen cover will be returned. + QString LoadCoverFromFile(Song *song); + + // Shows a dialog that allows user to save the given image on disk. The image + // is supposed to be the cover of the given song's album. + void SaveCoverToFile(const Song &song, const QImage &image); + + // Downloads the cover from an URL given by user. This returns the downloaded image + // or null image if something went wrong for example when user cancelled the + // dialog. + QString LoadCoverFromURL(Song *song); + + // Lets the user choose a cover among all that have been found on last.fm. + // Returns the chosen cover or null cover if user didn't choose anything. + QString SearchForCover(Song *song); + + // Returns a path which indicates that the cover has been unset manually. + QString UnsetCover(Song *song); + + // Shows the cover of given song in it's original size. + void ShowCover(const Song &song); + + // Search for covers automatically + void SearchCoverAutomatically(const Song &song); + + // Saves the chosen cover as manual cover path of this song in collection. + void SaveCover(Song *song, const QString &cover); + + // Saves the cover that the user picked through a drag and drop operation. + QString SaveCover(Song *song, const QDropEvent *e); + + // Saves the given image in cache as a cover for 'artist' - 'album'. + // The method returns path of the cached image. + QString SaveCoverInCache(const QString &artist, const QString &album, const QImage &image); + + static bool CanAcceptDrag(const QDragEnterEvent *e); + +signals: + void AutomaticCoverSearchDone(); + + private slots: + void AlbumCoverFetched(quint64 id, const QImage &image, const CoverSearchStatistics &statistics); + + private: + QString GetInitialPathForFileDialog(const Song &song, const QString &filename); + + static bool IsKnownImageExtension(const QString &suffix); + static QSet *sImageExtensions; + + Application *app_; + AlbumCoverSearcher *cover_searcher_; + AlbumCoverFetcher *cover_fetcher_; + + QFileDialog *save_file_dialog_; + CoverFromURLDialog *cover_from_url_dialog_; + + QAction *cover_from_file_; + QAction *cover_to_file_; + QAction *separator_; + QAction *cover_from_url_; + QAction *search_for_cover_; + QAction *unset_cover_; + QAction *show_cover_; + QAction *search_cover_auto_; + + QMap cover_fetching_tasks_; +}; + +#endif // ALBUMCOVERCHOICECONTROLLER_H diff --git a/src/covermanager/albumcoverexport.cpp b/src/covermanager/albumcoverexport.cpp new file mode 100644 index 00000000..cdf9a626 --- /dev/null +++ b/src/covermanager/albumcoverexport.cpp @@ -0,0 +1,96 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "albumcoverexport.h" +#include "ui_albumcoverexport.h" + +#include + +const char *AlbumCoverExport::kSettingsGroup = "AlbumCoverExport"; + +AlbumCoverExport::AlbumCoverExport(QWidget *parent) : QDialog(parent), ui_(new Ui_AlbumCoverExport) { + + ui_->setupUi(this); + + connect(ui_->forceSize, SIGNAL(stateChanged(int)), SLOT(ForceSizeToggled(int))); + +} + +AlbumCoverExport::~AlbumCoverExport() { delete ui_; } + +AlbumCoverExport::DialogResult AlbumCoverExport::Exec() { + + QSettings s; + s.beginGroup(kSettingsGroup); + + // restore last accepted settings + ui_->fileName->setText(s.value("fileName", "cover").toString()); + ui_->doNotOverwrite->setChecked(s.value("overwrite", OverwriteMode_None).toInt() == OverwriteMode_None); + ui_->overwriteAll->setChecked(s.value("overwrite", OverwriteMode_All).toInt() == OverwriteMode_All); + ui_->overwriteSmaller->setChecked(s.value("overwrite", OverwriteMode_Smaller).toInt() == OverwriteMode_Smaller); + ui_->forceSize->setChecked(s.value("forceSize", false).toBool()); + ui_->width->setText(s.value("width", "").toString()); + ui_->height->setText(s.value("height", "").toString()); + ui_->export_downloaded->setChecked(s.value("export_downloaded", true).toBool()); + ui_->export_embedded->setChecked(s.value("export_embedded", false).toBool()); + + ForceSizeToggled(ui_->forceSize->checkState()); + + DialogResult result = DialogResult(); + result.cancelled_ = (exec() == QDialog::Rejected); + + if (!result.cancelled_) { + QString fileName = ui_->fileName->text(); + if (fileName.isEmpty()) { + fileName = "cover"; + } + OverwriteMode overwrite = ui_->doNotOverwrite->isChecked() ? OverwriteMode_None : (ui_->overwriteAll->isChecked() ? OverwriteMode_All : OverwriteMode_Smaller); + bool forceSize = ui_->forceSize->isChecked(); + QString width = ui_->width->text(); + QString height = ui_->height->text(); + + s.setValue("fileName", fileName); + s.setValue("overwrite", overwrite); + s.setValue("forceSize", forceSize); + s.setValue("width", width); + s.setValue("height", height); + s.setValue("export_downloaded", ui_->export_downloaded->isChecked()); + s.setValue("export_embedded", ui_->export_embedded->isChecked()); + + result.fileName_ = fileName; + result.overwrite_ = overwrite; + result.forceSize_ = forceSize; + result.width_ = width.toInt(); + result.height_ = height.toInt(); + result.export_downloaded_ = ui_->export_downloaded->isChecked(); + result.export_embedded_ = ui_->export_embedded->isChecked(); + } + + return result; + +} + +void AlbumCoverExport::ForceSizeToggled(int state) { + ui_->width->setEnabled(state == Qt::Checked); + ui_->height->setEnabled(state == Qt::Checked); +} + diff --git a/src/covermanager/albumcoverexport.h b/src/covermanager/albumcoverexport.h new file mode 100644 index 00000000..156c7b88 --- /dev/null +++ b/src/covermanager/albumcoverexport.h @@ -0,0 +1,77 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ALBUMCOVEREXPORT_H +#define ALBUMCOVEREXPORT_H + +#include "config.h" + +#include + +class Ui_AlbumCoverExport; + +// Controller for the "Export covers" dialog. +class AlbumCoverExport : public QDialog { + Q_OBJECT + + public: + AlbumCoverExport(QWidget *parent = nullptr); + ~AlbumCoverExport(); + + enum OverwriteMode { + OverwriteMode_None = 0, + OverwriteMode_All = 1, + OverwriteMode_Smaller = 2 + }; + + struct DialogResult { + bool cancelled_; + + bool export_downloaded_; + bool export_embedded_; + + QString fileName_; + OverwriteMode overwrite_; + bool forceSize_; + int width_; + int height_; + + bool IsSizeForced() const { + return forceSize_ && width_ > 0 && height_ > 0; + } + + bool RequiresCoverProcessing() const { + return IsSizeForced() || overwrite_ == OverwriteMode_Smaller; + } + }; + + DialogResult Exec(); + + private slots: + void ForceSizeToggled(int state); + + private: + Ui_AlbumCoverExport *ui_; + + static const char *kSettingsGroup; +}; + +#endif // ALBUMCOVEREXPORT_H + diff --git a/src/covermanager/albumcoverexport.ui b/src/covermanager/albumcoverexport.ui new file mode 100644 index 00000000..90600a50 --- /dev/null +++ b/src/covermanager/albumcoverexport.ui @@ -0,0 +1,240 @@ + + + AlbumCoverExport + + + + 0 + 0 + 608 + 412 + + + + + 0 + 0 + + + + Export covers + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + + + + Output + + + + + + Enter a filename for exported covers (no extension): + + + + + + + + + + Export downloaded covers + + + true + + + + + + + Export embedded covers + + + false + + + + + + + + + + Existing covers + + + + + + Do not overwrite + + + + + + + Overwrite all + + + + + + + Overwrite smaller ones only + + + + + + + + + + Size + + + + + + + 50 + false + + + + Scale size + + + + + + + + + Size: + + + + + + + + 0 + 0 + + + + Qt::ImhDigitsOnly + + + + + + 4 + + + + + + + × + + + + + + + Qt::ImhDigitsOnly + + + + + + 4 + + + + + + + Pixel + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 200 + 20 + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + AlbumCoverExport + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AlbumCoverExport + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/covermanager/albumcoverexporter.cpp b/src/covermanager/albumcoverexporter.cpp new file mode 100644 index 00000000..162b6c32 --- /dev/null +++ b/src/covermanager/albumcoverexporter.cpp @@ -0,0 +1,82 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include + +#include "albumcoverexporter.h" +#include "coverexportrunnable.h" +#include "core/song.h" + +const int AlbumCoverExporter::kMaxConcurrentRequests = 3; + +AlbumCoverExporter::AlbumCoverExporter(QObject *parent) + : QObject(parent), + thread_pool_(new QThreadPool(this)), + exported_(0), + skipped_(0), + all_(0) { + thread_pool_->setMaxThreadCount(kMaxConcurrentRequests); +} + +void AlbumCoverExporter::SetDialogResult(const AlbumCoverExport::DialogResult& dialog_result) { + dialog_result_ = dialog_result; +} + +void AlbumCoverExporter::AddExportRequest(Song song) { + requests_.append(new CoverExportRunnable(dialog_result_, song)); + all_ = requests_.count(); +} + +void AlbumCoverExporter::Cancel() { requests_.clear(); } + +void AlbumCoverExporter::StartExporting() { + exported_ = 0; + skipped_ = 0; + AddJobsToPool(); +} + +void AlbumCoverExporter::AddJobsToPool() { + + while (!requests_.isEmpty() && thread_pool_->activeThreadCount() < thread_pool_->maxThreadCount()) { + CoverExportRunnable *runnable = requests_.dequeue(); + + connect(runnable, SIGNAL(CoverExported()), SLOT(CoverExported())); + connect(runnable, SIGNAL(CoverSkipped()), SLOT(CoverSkipped())); + + thread_pool_->start(runnable); + } + +} + +void AlbumCoverExporter::CoverExported() { + exported_++; + emit AlbumCoversExportUpdate(exported_, skipped_, all_); + AddJobsToPool(); +} + +void AlbumCoverExporter::CoverSkipped() { + skipped_++; + emit AlbumCoversExportUpdate(exported_, skipped_, all_); + AddJobsToPool(); +} + diff --git a/src/covermanager/albumcoverexporter.h b/src/covermanager/albumcoverexporter.h new file mode 100644 index 00000000..ebe6f2e6 --- /dev/null +++ b/src/covermanager/albumcoverexporter.h @@ -0,0 +1,72 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ALBUMCOVEREXPORTER_H +#define ALBUMCOVEREXPORTER_H + +#include "config.h" + +#include "coverexportrunnable.h" +#include "core/song.h" +#include "covermanager/albumcoverexport.h" + +#include +#include +#include + +class QThreadPool; + +class AlbumCoverExporter : public QObject { + Q_OBJECT + + public: + explicit AlbumCoverExporter(QObject *parent = nullptr); + virtual ~AlbumCoverExporter() {} + + static const int kMaxConcurrentRequests; + + void SetDialogResult(const AlbumCoverExport::DialogResult &dialog_result); + void AddExportRequest(Song song); + void StartExporting(); + void Cancel(); + + int request_count() { return requests_.size(); } + +signals: + void AlbumCoversExportUpdate(int exported, int skipped, int all); + + private slots: + void CoverExported(); + void CoverSkipped(); + + private: + void AddJobsToPool(); + AlbumCoverExport::DialogResult dialog_result_; + + QQueue requests_; + QThreadPool *thread_pool_; + + int exported_; + int skipped_; + int all_; +}; + +#endif // ALBUMCOVEREXPORTER_H + diff --git a/src/covermanager/albumcoverfetcher.cpp b/src/covermanager/albumcoverfetcher.cpp new file mode 100644 index 00000000..dc5bacaf --- /dev/null +++ b/src/covermanager/albumcoverfetcher.cpp @@ -0,0 +1,132 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include "albumcoverfetcher.h" +#include "albumcoverfetchersearch.h" +#include "core/network.h" + +const int AlbumCoverFetcher::kMaxConcurrentRequests = 5; + +AlbumCoverFetcher::AlbumCoverFetcher(CoverProviders *cover_providers, QObject *parent, QNetworkAccessManager *network) + : QObject(parent), + cover_providers_(cover_providers), + network_(network ? network : new NetworkAccessManager(this)), + next_id_(0), + request_starter_(new QTimer(this)) { + request_starter_->setInterval(1000); + connect(request_starter_, SIGNAL(timeout()), SLOT(StartRequests())); +} + +quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString &album) { + + CoverSearchRequest request; + request.artist = artist; + request.album = album; + request.search = false; + request.id = next_id_++; + + AddRequest(request); + return request.id; + +} + +quint64 AlbumCoverFetcher::SearchForCovers(const QString &artist, const QString &album) { + + CoverSearchRequest request; + request.artist = artist; + request.album = album; + request.search = true; + request.id = next_id_++; + + AddRequest(request); + return request.id; + +} + +void AlbumCoverFetcher::AddRequest(const CoverSearchRequest &req) { + + queued_requests_.enqueue(req); + + if (!request_starter_->isActive()) request_starter_->start(); + + if (active_requests_.size() < kMaxConcurrentRequests) StartRequests(); + +} + +void AlbumCoverFetcher::Clear() { + + queued_requests_.clear(); + + for (AlbumCoverFetcherSearch *search : active_requests_.values()) { + search->Cancel(); + search->deleteLater(); + } + active_requests_.clear(); + +} + +void AlbumCoverFetcher::StartRequests() { + + if (queued_requests_.isEmpty()) { + request_starter_->stop(); + return; + } + + while (!queued_requests_.isEmpty() && active_requests_.size() < kMaxConcurrentRequests) { + + CoverSearchRequest request = queued_requests_.dequeue(); + + // search objects are this fetcher's children so worst case scenario - they get + // deleted with it + AlbumCoverFetcherSearch *search = new AlbumCoverFetcherSearch(request, network_, this); + active_requests_.insert(request.id, search); + + connect(search, SIGNAL(SearchFinished(quint64, CoverSearchResults)), SLOT(SingleSearchFinished(quint64, CoverSearchResults))); + connect(search, SIGNAL(AlbumCoverFetched(quint64, const QImage&)), SLOT(SingleCoverFetched(quint64, const QImage&))); + + search->Start(cover_providers_); + } + +} + +void AlbumCoverFetcher::SingleSearchFinished(quint64 request_id, CoverSearchResults results) { + + AlbumCoverFetcherSearch *search = active_requests_.take(request_id); + if (!search) return; + + search->deleteLater(); + emit SearchFinished(request_id, results, search->statistics()); + +} + +void AlbumCoverFetcher::SingleCoverFetched(quint64 request_id, const QImage &image) { + + AlbumCoverFetcherSearch *search = active_requests_.take(request_id); + if (!search) return; + + search->deleteLater(); + emit AlbumCoverFetched(request_id, image, search->statistics()); + +} + diff --git a/src/covermanager/albumcoverfetcher.h b/src/covermanager/albumcoverfetcher.h new file mode 100644 index 00000000..b7e48d03 --- /dev/null +++ b/src/covermanager/albumcoverfetcher.h @@ -0,0 +1,117 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ALBUMCOVERFETCHER_H +#define ALBUMCOVERFETCHER_H + +#include "config.h" + +#include "coversearchstatistics.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +class QNetworkReply; +class QString; + +class AlbumCoverFetcherSearch; +class CoverProviders; + +// This class represents a single search-for-cover request. It identifies +// and describes the request. +struct CoverSearchRequest { + // an unique (for one AlbumCoverFetcher) request identifier + quint64 id; + + // a search query + QString artist; + QString album; + + // is this only a search request or should we also fetch the first + // cover that's found? + bool search; +}; + +// This structure represents a single result of some album's cover search request. +// It contains an URL that leads to a found cover plus its description (usually +// the "artist - album" string). +struct CoverSearchResult { + // used for grouping in the user interface. This is set automatically - don't + // set it manually in your cover provider. + QString provider; + + // description of this result (we suggest using the "artist - album" format) + QString description; + + // an URL of a cover image described by this CoverSearchResult + QUrl image_url; +}; +Q_DECLARE_METATYPE(CoverSearchResult); + +// This is a complete result of a single search request (a list of results, each +// describing one image, actually). +typedef QList CoverSearchResults; +Q_DECLARE_METATYPE(QList); + +// This class searches for album covers for a given query or artist/album and +// returns URLs. It's NOT thread-safe. +class AlbumCoverFetcher : public QObject { + Q_OBJECT + + public: + AlbumCoverFetcher(CoverProviders *cover_providers, QObject *parent = nullptr, QNetworkAccessManager *network = 0); + virtual ~AlbumCoverFetcher() {} + + static const int kMaxConcurrentRequests; + + quint64 SearchForCovers(const QString &artist, const QString &album); + quint64 FetchAlbumCover(const QString &artist, const QString &album); + + void Clear(); + +signals: + void AlbumCoverFetched(quint64, const QImage &cover, const CoverSearchStatistics &statistics); + void SearchFinished(quint64, const CoverSearchResults &results, const CoverSearchStatistics &statistics); + + private slots: + void SingleSearchFinished(quint64, CoverSearchResults results); + void SingleCoverFetched(quint64, const QImage &cover); + void StartRequests(); + + private: + void AddRequest(const CoverSearchRequest &req); + + CoverProviders *cover_providers_; + QNetworkAccessManager *network_; + quint64 next_id_; + + QQueue queued_requests_; + QHash active_requests_; + + QTimer *request_starter_; +}; + +#endif // ALBUMCOVERFETCHER_H diff --git a/src/covermanager/albumcoverfetchersearch.cpp b/src/covermanager/albumcoverfetchersearch.cpp new file mode 100644 index 00000000..f3be67ce --- /dev/null +++ b/src/covermanager/albumcoverfetchersearch.cpp @@ -0,0 +1,271 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "albumcoverfetchersearch.h" + +#include + +#include +#include +#include +#include + +#include "albumcoverfetcher.h" +#include "coverprovider.h" +#include "coverproviders.h" +#include "core/closure.h" +#include "core/logging.h" +#include "core/network.h" + +const int AlbumCoverFetcherSearch::kSearchTimeoutMs = 10000; +const int AlbumCoverFetcherSearch::kImageLoadTimeoutMs = 2500; +const int AlbumCoverFetcherSearch::kTargetSize = 500; +const float AlbumCoverFetcherSearch::kGoodScore = 1.85; + +AlbumCoverFetcherSearch::AlbumCoverFetcherSearch( + const CoverSearchRequest &request, QNetworkAccessManager *network, QObject *parent) + : QObject(parent), + request_(request), + image_load_timeout_(new NetworkTimeouts(kImageLoadTimeoutMs, this)), + network_(network), + cancel_requested_(false) { + // we will terminate the search after kSearchTimeoutMs miliseconds if we are + // not + // able to find all of the results before that point in time + QTimer::singleShot(kSearchTimeoutMs, this, SLOT(TerminateSearch())); +} + +void AlbumCoverFetcherSearch::TerminateSearch() { + for (int id : pending_requests_.keys()) { + pending_requests_.take(id)->CancelSearch(id); + } + + AllProvidersFinished(); +} + +void AlbumCoverFetcherSearch::Start(CoverProviders *cover_providers) { + + for (CoverProvider *provider : cover_providers->List()) { + connect(provider, SIGNAL(SearchFinished(int, QList)), SLOT(ProviderSearchFinished(int, QList))); + const int id = cover_providers->NextId(); + const bool success = provider->StartSearch(request_.artist, request_.album, id); + + if (success) { + pending_requests_[id] = provider; + statistics_.network_requests_made_++; + } + } + + // end this search before it even began if there are no providers... + if (pending_requests_.isEmpty()) { + TerminateSearch(); + } + +} + +static bool CompareProviders(const CoverSearchResult &a, const CoverSearchResult &b) { + return a.provider < b.provider; +} + +void AlbumCoverFetcherSearch::ProviderSearchFinished(int id, const QList &results) { + + if (!pending_requests_.contains(id)) return; + + CoverProvider *provider = pending_requests_.take(id); + + CoverSearchResults results_copy(results); + // Set categories on the results + for (int i = 0; i < results_copy.count(); ++i) { + results_copy[i].provider = provider->name(); + } + + // Add results from the current provider to our pool + results_.append(results_copy); + statistics_.total_images_by_provider_[provider->name()]++; + + // do we have more providers left? + if (!pending_requests_.isEmpty()) { + return; + } + + AllProvidersFinished(); +} + +void AlbumCoverFetcherSearch::AllProvidersFinished() { + + if (cancel_requested_) { + return; + } + + // if we only wanted to do the search then we're done + if (request_.search) { + emit SearchFinished(request_.id, results_); + return; + } + + // no results? + if (results_.isEmpty()) { + statistics_.missing_images_++; + emit AlbumCoverFetched(request_.id, QImage()); + return; + } + + // Now we have to load some images and figure out which one is the best. + // We'll sort the list of results by category, then load the first few images + // from each category and use some heuristics to score them. If no images + // are good enough we'll keep loading more images until we find one that is + // or we run out of results. + qStableSort(results_.begin(), results_.end(), CompareProviders); + FetchMoreImages(); + +} + +void AlbumCoverFetcherSearch::FetchMoreImages() { + + // Try the first one in each category. + QString last_provider; + for (int i = 0; i < results_.count(); ++i) { + if (results_[i].provider == last_provider) { + continue; + } + + CoverSearchResult result = results_.takeAt(i--); + last_provider = result.provider; + + qLog(Debug) << "Loading" << result.image_url << "from" << result.provider; + + RedirectFollower *image_reply = new RedirectFollower(network_->get(QNetworkRequest(result.image_url))); + NewClosure(image_reply, SIGNAL(finished()), this, SLOT(ProviderCoverFetchFinished(RedirectFollower*)), image_reply); + pending_image_loads_[image_reply] = result.provider; + image_load_timeout_->AddReply(image_reply); + + statistics_.network_requests_made_++; + } + + if (pending_image_loads_.isEmpty()) { + // There were no more results? Time to give up. + SendBestImage(); + } + +} + +void AlbumCoverFetcherSearch::ProviderCoverFetchFinished(RedirectFollower *reply) { + + reply->deleteLater(); + const QString provider = pending_image_loads_.take(reply); + + statistics_.bytes_transferred_ += reply->bytesAvailable(); + + if (cancel_requested_) { + return; + } + + if (reply->error() != QNetworkReply::NoError) { + qLog(Info) << "Error requesting" << reply->url() << reply->errorString(); + } + else { + QImage image; + if (!image.loadFromData(reply->readAll())) { + qLog(Info) << "Error decoding image data from" << reply->url(); + } + else { + const float score = ScoreImage(image); + candidate_images_.insertMulti(score, CandidateImage(provider, image)); + + qLog(Debug) << reply->url() << "scored" << score; + } + } + + if (pending_image_loads_.isEmpty()) { + // We've fetched everything we wanted to fetch for now, check if we have an + // image that's good enough. + float best_score = 0.0; + + if (!candidate_images_.isEmpty()) { + best_score = candidate_images_.keys().last(); + } + + qLog(Debug) << "Best image so far has a score of" << best_score; + if (best_score >= kGoodScore) { + SendBestImage(); + } + else { + FetchMoreImages(); + } + } + +} + +float AlbumCoverFetcherSearch::ScoreImage(const QImage &image) const { + + // Invalid images score nothing + if (image.isNull()) { + return 0.0; + } + + // A 500x500px image scores 1.0, bigger scores higher + const float size_score = std::sqrt(float(image.width() * image.height())) / kTargetSize; + + // A 1:1 image scores 1.0, anything else scores less + const float aspect_score = 1.0 - float(image.height() - image.width()) / std::max(image.height(), image.width()); + + return size_score + aspect_score; + +} + +void AlbumCoverFetcherSearch::SendBestImage() { + + QImage image; + + if (!candidate_images_.isEmpty()) { + const CandidateImage best_image = candidate_images_.values().back(); + image = best_image.second; + + statistics_.chosen_images_by_provider_[best_image.first]++; + statistics_.chosen_images_++; + statistics_.chosen_width_ += image.width(); + statistics_.chosen_height_ += image.height(); + } + else { + statistics_.missing_images_++; + } + + emit AlbumCoverFetched(request_.id, image); + +} + +void AlbumCoverFetcherSearch::Cancel() { + + cancel_requested_ = true; + + if (!pending_requests_.isEmpty()) { + TerminateSearch(); + } + else if (!pending_image_loads_.isEmpty()) { + for (RedirectFollower *reply : pending_image_loads_.keys()) { + reply->abort(); + } + pending_image_loads_.clear(); + } + +} + diff --git a/src/covermanager/albumcoverfetchersearch.h b/src/covermanager/albumcoverfetchersearch.h new file mode 100644 index 00000000..4f70fd9e --- /dev/null +++ b/src/covermanager/albumcoverfetchersearch.h @@ -0,0 +1,102 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ALBUMCOVERFETCHERSEARCH_H +#define ALBUMCOVERFETCHERSEARCH_H + +#include "config.h" + +#include "albumcoverfetcher.h" + +#include +#include + +class CoverProvider; +class CoverProviders; +class NetworkTimeouts; +class NetworkAccessManager; +class RedirectFollower; + +// This class encapsulates a single search for covers initiated by an +// AlbumCoverFetcher. The search engages all of the known cover providers. +// AlbumCoverFetcherSearch signals search results to an interested +// AlbumCoverFetcher when all of the providers have done their part. +class AlbumCoverFetcherSearch : public QObject { + Q_OBJECT + + public: + AlbumCoverFetcherSearch(const CoverSearchRequest &request, QNetworkAccessManager *network, QObject *parent); + + void Start(CoverProviders* cover_providers); + + // Cancels all pending requests. No Finished signals will be emitted, and it + // is the caller's responsibility to delete the AlbumCoverFetcherSearch. + void Cancel(); + + CoverSearchStatistics statistics() const { return statistics_; } + +signals: + // It's the end of search (when there was no fetch-me-a-cover request). + void SearchFinished(quint64, const CoverSearchResults& results); + + // It's the end of search and we've fetched a cover. + void AlbumCoverFetched(quint64, const QImage &cover); + +private slots: + void ProviderSearchFinished(int id, const QList &results); + void ProviderCoverFetchFinished(RedirectFollower *reply); + void TerminateSearch(); + +private: + void AllProvidersFinished(); + + void FetchMoreImages(); + float ScoreImage(const QImage &image) const; + void SendBestImage(); + +private: + static const int kSearchTimeoutMs; + static const int kImageLoadTimeoutMs; + static const int kTargetSize; + static const float kGoodScore; + + CoverSearchStatistics statistics_; + + // Search request encapsulated by this AlbumCoverFetcherSearch. + CoverSearchRequest request_; + + // Complete results (from all of the available providers). + CoverSearchResults results_; + + QMap pending_requests_; + QMap pending_image_loads_; + NetworkTimeouts* image_load_timeout_; + + // QMap is sorted by key (score). Values are (provider_name, image) + typedef QPair CandidateImage; + QMap candidate_images_; + + QNetworkAccessManager *network_; + + bool cancel_requested_; +}; + +#endif // ALBUMCOVERFETCHERSEARCH_H + diff --git a/src/covermanager/albumcoverloader.cpp b/src/covermanager/albumcoverloader.cpp new file mode 100644 index 00000000..69c4971c --- /dev/null +++ b/src/covermanager/albumcoverloader.cpp @@ -0,0 +1,279 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "albumcoverloader.h" + +#include +#include +#include +#include +#include + +#include "config.h" +#include "core/closure.h" +#include "core/logging.h" +#include "core/network.h" +#include "core/tagreaderclient.h" +#include "core/utilities.h" + +AlbumCoverLoader::AlbumCoverLoader(QObject *parent) + : QObject(parent), + stop_requested_(false), + next_id_(1), + network_(new NetworkAccessManager(this)){} + +QString AlbumCoverLoader::ImageCacheDir() { + return Utilities::GetConfigPath(Utilities::Path_AlbumCovers); +} + +void AlbumCoverLoader::CancelTask(quint64 id) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QMutexLocker l(&mutex_); + for (QQueue::iterator it = tasks_.begin(); it != tasks_.end(); ++it) { + if (it->id == id) { + tasks_.erase(it); + break; + } + } +} + +void AlbumCoverLoader::CancelTasks(const QSet &ids) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QMutexLocker l(&mutex_); + for (QQueue::iterator it = tasks_.begin(); it != tasks_.end();) { + if (ids.contains(it->id)) { + it = tasks_.erase(it); + } + else { + ++it; + } + } +} + +quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions& options, const Song &song) { + return LoadImageAsync(options, song.art_automatic(), song.art_manual(), song.url().toLocalFile(), song.image()); +} + +quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QString &art_automatic, const QString &art_manual, const QString &song_filename, const QImage &embedded_image) { + + //qLog(Debug) << __PRETTY_FUNCTION__ << art_automatic << art_manual << song_filename; + + Task task; + task.options = options; + task.art_automatic = art_automatic; + task.art_manual = art_manual; + task.song_filename = song_filename; + task.embedded_image = embedded_image; + task.state = State_TryingManual; + + { + QMutexLocker l(&mutex_); + task.id = next_id_++; + tasks_.enqueue(task); + } + + metaObject()->invokeMethod(this, "ProcessTasks", Qt::QueuedConnection); + + return task.id; +} + +void AlbumCoverLoader::ProcessTasks() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + while (!stop_requested_) { + // Get the next task + Task task; + { + QMutexLocker l(&mutex_); + if (tasks_.isEmpty()) return; + task = tasks_.dequeue(); + } + + ProcessTask(&task); + } +} + +void AlbumCoverLoader::ProcessTask(Task *task) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + TryLoadResult result = TryLoadImage(*task); + if (result.started_async) { + // The image is being loaded from a remote URL, we'll carry on later + // when it's done + return; + } + + if (result.loaded_success) { + QImage scaled = ScaleAndPad(task->options, result.image); + emit ImageLoaded(task->id, scaled); + emit ImageLoaded(task->id, scaled, result.image); + return; + } + + NextState(task); +} + +void AlbumCoverLoader::NextState(Task *task) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (task->state == State_TryingManual) { + // Try the automatic one next + task->state = State_TryingAuto; + ProcessTask(task); + } + else { + // Give up + emit ImageLoaded(task->id, task->options.default_output_image_); + emit ImageLoaded(task->id, task->options.default_output_image_, task->options.default_output_image_); + } +} + +AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(const Task &task) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // An image embedded in the song itself takes priority + if (!task.embedded_image.isNull()) + return TryLoadResult(false, true, ScaleAndPad(task.options, task.embedded_image)); + + QString filename; + switch (task.state) { + case State_TryingAuto: filename = task.art_automatic; break; + case State_TryingManual: filename = task.art_manual; break; + } + + if (filename == Song::kManuallyUnsetCover) + return TryLoadResult(false, true, task.options.default_output_image_); + + if (filename == Song::kEmbeddedCover && !task.song_filename.isEmpty()) { + const QImage taglib_image = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task.song_filename); + + if (!taglib_image.isNull()) + return TryLoadResult(false, true, ScaleAndPad(task.options, taglib_image)); + } + + if (filename.toLower().startsWith("http://") || filename.toLower().startsWith("https://")) { + + QUrl url(filename); + QNetworkReply* reply = network_->get(QNetworkRequest(url)); + NewClosure(reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*)), reply); + + remote_tasks_.insert(reply, task); + return TryLoadResult(true, false, QImage()); + } + else if (filename.isEmpty()) { + // Avoid "QFSFileEngine::open: No file name specified" messages if we know that the filename is empty + return TryLoadResult(false, false, task.options.default_output_image_); + } + + QImage image(filename); + return TryLoadResult(false, !image.isNull(), image.isNull() ? task.options.default_output_image_ : image); + +} + +void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + reply->deleteLater(); + + Task task = remote_tasks_.take(reply); + + // Handle redirects. + QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); + if (redirect.isValid()) { + if (++task.redirects > kMaxRedirects) { + return; // Give up. + } + QNetworkRequest request = reply->request(); + request.setUrl(redirect.toUrl()); + QNetworkReply* redirected_reply = network_->get(request); + NewClosure(redirected_reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*)), redirected_reply); + + remote_tasks_.insert(redirected_reply, task); + return; + } + + if (reply->error() == QNetworkReply::NoError) { + // Try to load the image + QImage image; + if (image.load(reply, 0)) { + QImage scaled = ScaleAndPad(task.options, image); + emit ImageLoaded(task.id, scaled); + emit ImageLoaded(task.id, scaled, image); + return; + } + } + + NextState(&task); +} + +QImage AlbumCoverLoader::ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (image.isNull()) return image; + + // Scale the image down + QImage copy; + if (options.scale_output_image_) { + copy = image.scaled(QSize(options.desired_height_, options.desired_height_), Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + else { + copy = image; + } + + if (!options.pad_output_image_) return copy; + + // Pad the image to height_ x height_ + QImage padded_image(options.desired_height_, options.desired_height_, QImage::Format_ARGB32); + padded_image.fill(0); + + QPainter p(&padded_image); + p.drawImage((options.desired_height_ - copy.width()) / 2, (options.desired_height_ - copy.height()) / 2, copy); + p.end(); + + return padded_image; +} + +QPixmap AlbumCoverLoader::TryLoadPixmap(const QString &automatic, const QString &manual, const QString &filename) { + + //qLog(Debug) << __PRETTY_FUNCTION__ << automatic << manual << filename; + + QPixmap ret; + if (manual == Song::kManuallyUnsetCover) return ret; + if (!manual.isEmpty()) ret.load(manual); + if (ret.isNull()) { + if (automatic == Song::kEmbeddedCover && !filename.isNull()) + ret = QPixmap::fromImage(TagReaderClient::Instance()->LoadEmbeddedArtBlocking(filename)); + else if (!automatic.isEmpty()) + ret.load(automatic); + } + return ret; +} diff --git a/src/covermanager/albumcoverloader.h b/src/covermanager/albumcoverloader.h new file mode 100644 index 00000000..956308b3 --- /dev/null +++ b/src/covermanager/albumcoverloader.h @@ -0,0 +1,110 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ALBUMCOVERLOADER_H +#define ALBUMCOVERLOADER_H + +#include "config.h" + +#include "albumcoverloaderoptions.h" +#include "core/song.h" + +#include +#include +#include +#include +#include + +class NetworkAccessManager; +class QNetworkReply; + +class AlbumCoverLoader : public QObject { + Q_OBJECT + + public: + explicit AlbumCoverLoader(QObject *parent = nullptr); + + void Stop() { stop_requested_ = true; } + + static QString ImageCacheDir(); + + quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const Song &song); + virtual quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QString &art_automatic, const QString &art_manual, const QString &song_filename = QString(), const QImage &embedded_image = QImage()); + + void CancelTask(quint64 id); + void CancelTasks(const QSet &ids); + + static QPixmap TryLoadPixmap(const QString &automatic, const QString &manual, const QString &filename = QString()); + static QImage ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image); + +signals: + void ImageLoaded(quint64 id, const QImage &image); + void ImageLoaded(quint64 id, const QImage &scaled, const QImage &original); + + protected slots: + void ProcessTasks(); + void RemoteFetchFinished(QNetworkReply* reply); + + protected: + enum State { + State_TryingManual, + State_TryingAuto, + }; + + struct Task { + Task() : redirects(0) {} + + AlbumCoverLoaderOptions options; + + quint64 id; + QString art_automatic; + QString art_manual; + QString song_filename; + QImage embedded_image; + State state; + int redirects; + }; + + struct TryLoadResult { + TryLoadResult(bool async, bool success, const QImage &i) : started_async(async), loaded_success(success), image(i) {} + + bool started_async; + bool loaded_success; + QImage image; + }; + + void ProcessTask(Task* task); + void NextState(Task* task); + TryLoadResult TryLoadImage(const Task &task); + + bool stop_requested_; + + QMutex mutex_; + QQueue tasks_; + QMap remote_tasks_; + quint64 next_id_; + + NetworkAccessManager *network_; + + static const int kMaxRedirects = 3; +}; + +#endif // ALBUMCOVERLOADER_H + diff --git a/src/covermanager/albumcoverloaderoptions.cpp b/src/covermanager/albumcoverloaderoptions.cpp new file mode 100644 index 00000000..a9fabaa7 --- /dev/null +++ b/src/covermanager/albumcoverloaderoptions.cpp @@ -0,0 +1,2 @@ +#include "config.h" +#include "albumcoverloaderoptions.h" diff --git a/src/covermanager/albumcoverloaderoptions.h b/src/covermanager/albumcoverloaderoptions.h new file mode 100644 index 00000000..e04242c8 --- /dev/null +++ b/src/covermanager/albumcoverloaderoptions.h @@ -0,0 +1,41 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ALBUMCOVERLOADEROPTIONS_H +#define ALBUMCOVERLOADEROPTIONS_H + +#include "config.h" + +#include + +struct AlbumCoverLoaderOptions { + AlbumCoverLoaderOptions() + : desired_height_(120), + scale_output_image_(true), + pad_output_image_(true) {} + + int desired_height_; + bool scale_output_image_; + bool pad_output_image_; + QImage default_output_image_; +}; + +#endif // ALBUMCOVERLOADEROPTIONS_H + diff --git a/src/covermanager/albumcovermanager.cpp b/src/covermanager/albumcovermanager.cpp new file mode 100644 index 00000000..02dc8a00 --- /dev/null +++ b/src/covermanager/albumcovermanager.cpp @@ -0,0 +1,870 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "albumcovermanager.h" +#include "albumcoversearcher.h" +#include "ui_albumcovermanager.h" + +#include "core/application.h" +#include "core/logging.h" +#include "core/utilities.h" +#include "core/iconloader.h" +#include "covermanager/albumcoverexporter.h" +#include "covermanager/albumcoverfetcher.h" +#include "covermanager/albumcoverloader.h" +#include "covermanager/coverproviders.h" +#include "covermanager/coversearchstatisticsdialog.h" +#include "collection/collectionbackend.h" +#include "collection/collectionquery.h" +#include "collection/sqlrow.h" +#include "playlist/songmimedata.h" +#include "widgets/forcescrollperpixel.h" +#include "covermanager/albumcoverchoicecontroller.h" +#include "covermanager/albumcoverexport.h" + +const char *AlbumCoverManager::kSettingsGroup = "CoverManager"; + +AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QWidget *parent, QNetworkAccessManager *network) + : QMainWindow(parent), + ui_(new Ui_CoverManager), + app_(app), + album_cover_choice_controller_(new AlbumCoverChoiceController(this)), + cover_fetcher_(new AlbumCoverFetcher(app_->cover_providers(), this, network)), + cover_searcher_(nullptr), + cover_export_(nullptr), + cover_exporter_(new AlbumCoverExporter(this)), + artist_icon_(IconLoader::Load("guitar" )), + all_artists_icon_(IconLoader::Load("cd" )), + //no_cover_icon_(IconLoader::Load("nocover")), + no_cover_icon_(":/pictures/noalbumart.png"), + no_cover_image_(GenerateNoCoverImage(no_cover_icon_)), + no_cover_item_icon_(QPixmap::fromImage(no_cover_image_)), + context_menu_(new QMenu(this)), + progress_bar_(new QProgressBar(this)), + abort_progress_(new QPushButton(this)), + jobs_(0), + collection_backend_(collection_backend) { + + ui_->setupUi(this); + ui_->albums->set_cover_manager(this); + + // Icons + ui_->action_fetch->setIcon(IconLoader::Load("download" )); + ui_->export_covers->setIcon(IconLoader::Load("document-save" )); + ui_->view->setIcon(IconLoader::Load("view-choose" )); + ui_->fetch->setIcon(IconLoader::Load("download" )); + ui_->action_add_to_playlist->setIcon(IconLoader::Load("media-play" )); + ui_->action_load->setIcon(IconLoader::Load("media-play" )); + + album_cover_choice_controller_->SetApplication(app_); + + // Get a square version of noalbumart.png + QImage nocover(":/pictures/noalbumart.png"); + nocover = nocover.scaled(120, 120, Qt::KeepAspectRatio, Qt::SmoothTransformation); + QImage square_nocover(120, 120, QImage::Format_ARGB32); + square_nocover.fill(0); + QPainter p(&square_nocover); + p.setOpacity(0.4); + p.drawImage((120 - nocover.width()) / 2, (120 - nocover.height()) / 2, nocover); + p.end(); +// no_cover_item_icon_ = QPixmap::fromImage(square_nocover); + + cover_searcher_ = new AlbumCoverSearcher(no_cover_item_icon_, app_, this); + cover_export_ = new AlbumCoverExport(this); + + // Set up the status bar + statusBar()->addPermanentWidget(progress_bar_); + statusBar()->addPermanentWidget(abort_progress_); + progress_bar_->hide(); + abort_progress_->hide(); + abort_progress_->setText(tr("Abort")); + connect(abort_progress_, SIGNAL(clicked()), this, SLOT(CancelRequests())); + + ui_->albums->setAttribute(Qt::WA_MacShowFocusRect, false); + ui_->artists->setAttribute(Qt::WA_MacShowFocusRect, false); + + QShortcut *close = new QShortcut(QKeySequence::Close, this); + connect(close, SIGNAL(activated()), SLOT(close())); + + EnableCoversButtons(); + +} + +AlbumCoverManager::~AlbumCoverManager() { + CancelRequests(); + + delete ui_; +} + +CollectionBackend *AlbumCoverManager::backend() const { + return collection_backend_; +} + +void AlbumCoverManager::Init() { + + // View menu + QActionGroup *filter_group = new QActionGroup(this); + filter_all_ = filter_group->addAction(tr("All albums")); + filter_with_covers_ = filter_group->addAction(tr("Albums with covers")); + filter_without_covers_ = filter_group->addAction(tr("Albums without covers")); + filter_all_->setCheckable(true); + filter_with_covers_->setCheckable(true); + filter_without_covers_->setCheckable(true); + filter_group->setExclusive(true); + filter_all_->setChecked(true); + + QMenu *view_menu = new QMenu(this); + view_menu->addActions(filter_group->actions()); + + ui_->view->setMenu(view_menu); + + // Context menu + + QList actions = album_cover_choice_controller_->GetAllActions(); + + connect(album_cover_choice_controller_->cover_from_file_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromFile())); + connect(album_cover_choice_controller_->cover_to_file_action(), SIGNAL(triggered()), this, SLOT(SaveCoverToFile())); + connect(album_cover_choice_controller_->cover_from_url_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromURL())); + connect(album_cover_choice_controller_->search_for_cover_action(), SIGNAL(triggered()), this, SLOT(SearchForCover())); + connect(album_cover_choice_controller_->unset_cover_action(), SIGNAL(triggered()), this, SLOT(UnsetCover())); + connect(album_cover_choice_controller_->show_cover_action(), SIGNAL(triggered()), this, SLOT(ShowCover())); + + connect(cover_exporter_, SIGNAL(AlbumCoversExportUpdate(int, int, int)), SLOT(UpdateExportStatus(int, int, int))); + + context_menu_->addActions(actions); + context_menu_->addSeparator(); + context_menu_->addAction(ui_->action_load); + context_menu_->addAction(ui_->action_add_to_playlist); + + ui_->albums->installEventFilter(this); + + // Connections + connect(ui_->artists, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), SLOT(ArtistChanged(QListWidgetItem*))); + connect(ui_->filter, SIGNAL(textChanged(QString)), SLOT(UpdateFilter())); + connect(filter_group, SIGNAL(triggered(QAction*)), SLOT(UpdateFilter())); + connect(ui_->view, SIGNAL(clicked()), ui_->view, SLOT(showMenu())); + connect(ui_->fetch, SIGNAL(clicked()), SLOT(FetchAlbumCovers())); + connect(ui_->export_covers, SIGNAL(clicked()), SLOT(ExportCovers())); + connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(quint64, QImage, CoverSearchStatistics)), SLOT(AlbumCoverFetched(quint64, QImage, CoverSearchStatistics))); + connect(ui_->action_fetch, SIGNAL(triggered()), SLOT(FetchSingleCover())); + connect(ui_->albums, SIGNAL(doubleClicked(QModelIndex)), SLOT(AlbumDoubleClicked(QModelIndex))); + connect(ui_->action_add_to_playlist, SIGNAL(triggered()), SLOT(AddSelectedToPlaylist())); + connect(ui_->action_load, SIGNAL(triggered()), SLOT(LoadSelectedToPlaylist())); + + // Restore settings + QSettings s; + s.beginGroup(kSettingsGroup); + + restoreGeometry(s.value("geometry").toByteArray()); + if (!ui_->splitter->restoreState(s.value("splitter_state").toByteArray())) { + // Sensible default size for the artists view + ui_->splitter->setSizes(QList() << 200 << width() - 200); + } + + connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(CoverImageLoaded(quint64, QImage))); + + cover_searcher_->Init(cover_fetcher_); + + new ForceScrollPerPixel(ui_->albums, this); + +} + +void AlbumCoverManager::showEvent(QShowEvent *) { + Reset(); +} + +void AlbumCoverManager::closeEvent(QCloseEvent *e) { + + if (!cover_fetching_tasks_.isEmpty()) { + std::unique_ptr message_box(new QMessageBox(QMessageBox::Question, tr("Really cancel?"), tr("Closing this window will stop searching for album covers."), QMessageBox::Abort, this)); + message_box->addButton(tr("Don't stop!"), QMessageBox::AcceptRole); + + if (message_box->exec() != QMessageBox::Abort) { + e->ignore(); + return; + } + } + + // Save geometry + QSettings s; + s.beginGroup(kSettingsGroup); + + s.setValue("geometry", saveGeometry()); + s.setValue("splitter_state", ui_->splitter->saveState()); + + // Cancel any outstanding requests + CancelRequests(); +} + +void AlbumCoverManager::CancelRequests() { + + app_->album_cover_loader()->CancelTasks(QSet::fromList(cover_loading_tasks_.keys())); + cover_loading_tasks_.clear(); + + cover_exporter_->Cancel(); + + cover_fetching_tasks_.clear(); + cover_fetcher_->Clear(); + progress_bar_->hide(); + abort_progress_->hide(); + statusBar()->clearMessage(); + EnableCoversButtons(); + +} + +static bool CompareNocase(const QString &left, const QString &right) { + return QString::localeAwareCompare(left, right) < 0; +} + +static bool CompareAlbumNameNocase(const CollectionBackend::Album &left, const CollectionBackend::Album &right) { + return CompareNocase(left.album_name, right.album_name); +} + +void AlbumCoverManager::Reset() { + + EnableCoversButtons(); + + ui_->artists->clear(); + new QListWidgetItem(all_artists_icon_, tr("All artists"), ui_->artists, All_Artists); + new QListWidgetItem(artist_icon_, tr("Various artists"), ui_->artists, Various_Artists); + + QStringList artists(collection_backend_->GetAllArtistsWithAlbums()); + qStableSort(artists.begin(), artists.end(), CompareNocase); + + for (const QString &artist : artists) { + if (artist.isEmpty()) continue; + new QListWidgetItem(artist_icon_, artist, ui_->artists, Specific_Artist); + } + +} + +void AlbumCoverManager::EnableCoversButtons() { + ui_->fetch->setEnabled(app_->cover_providers()->HasAnyProviders()); + ui_->export_covers->setEnabled(true); +} + +void AlbumCoverManager::DisableCoversButtons() { + ui_->fetch->setEnabled(false); + ui_->export_covers->setEnabled(false); +} + +void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) { + + if (!current) return; + + QString artist; + if (current->type() == Specific_Artist) artist = current->text(); + + ui_->albums->clear(); + context_menu_items_.clear(); + CancelRequests(); + + // Get the list of albums. How we do it depends on what thing we have selected in the artist list. + CollectionBackend::AlbumList albums; + switch (current->type()) { + case Various_Artists: albums = collection_backend_->GetCompilationAlbums(); break; + case Specific_Artist: albums = collection_backend_->GetAlbumsByArtist(current->text()); break; + case All_Artists: + default: albums = collection_backend_->GetAllAlbums(); break; + } + + // Sort by album name. The list is already sorted by sqlite but it was done case sensitively. + qStableSort(albums.begin(), albums.end(), CompareAlbumNameNocase); + + for (const CollectionBackend::Album &info : albums) { + // Don't show songs without an album, obviously + if (info.album_name.isEmpty()) continue; + + QListWidgetItem *item = new QListWidgetItem(no_cover_item_icon_, info.album_name, ui_->albums); + item->setData(Role_ArtistName, info.artist); + item->setData(Role_AlbumArtistName, info.album_artist); + item->setData(Role_AlbumName, info.album_name); + item->setData(Role_FirstUrl, info.first_url); + item->setData(Qt::TextAlignmentRole, QVariant(Qt::AlignTop | Qt::AlignHCenter)); + item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); + item->setToolTip(info.artist + " - " + info.album_name); + + QString effective_artist = EffectiveAlbumArtistName(*item); + if (!artist.isEmpty()) { + item->setToolTip(effective_artist + " - " + info.album_name); + } + else { + item->setToolTip(info.album_name); + } + + if (!info.art_automatic.isEmpty() || !info.art_manual.isEmpty()) { + quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, info.art_automatic, info.art_manual, info.first_url.toLocalFile()); + item->setData(Role_PathAutomatic, info.art_automatic); + item->setData(Role_PathManual, info.art_manual); + cover_loading_tasks_[id] = item; + } + } + + UpdateFilter(); + +} + +void AlbumCoverManager::CoverImageLoaded(quint64 id, const QImage &image) { + + if (!cover_loading_tasks_.contains(id)) return; + + QListWidgetItem *item = cover_loading_tasks_.take(id); + + if (image.isNull()) return; + + item->setIcon(QPixmap::fromImage(image)); + UpdateFilter(); +} + +void AlbumCoverManager::UpdateFilter() { + + const QString filter = ui_->filter->text().toLower(); + const bool hide_with_covers = filter_without_covers_->isChecked(); + const bool hide_without_covers = filter_with_covers_->isChecked(); + + HideCovers hide = Hide_None; + if (hide_with_covers) { + hide = Hide_WithCovers; + } + else if (hide_without_covers) { + hide = Hide_WithoutCovers; + } + + qint32 total_count = 0; + qint32 without_cover = 0; + + for (int i = 0; i < ui_->albums->count(); ++i) { + QListWidgetItem *item = ui_->albums->item(i); + bool should_hide = ShouldHide(*item, filter, hide); + item->setHidden(should_hide); + + if (!should_hide) { + total_count++; + if (!ItemHasCover(*item)) { + without_cover++; + } + } + } + + ui_->total_albums->setText(QString::number(total_count)); + ui_->without_cover->setText(QString::number(without_cover)); + +} + +bool AlbumCoverManager::ShouldHide(const QListWidgetItem &item, const QString &filter, HideCovers hide) const { + + bool has_cover = ItemHasCover(item); + if (hide == Hide_WithCovers && has_cover) { + return true; + } + else if (hide == Hide_WithoutCovers && !has_cover) { + return true; + } + + if (filter.isEmpty()) { + return false; + } + + QStringList query = filter.split(' '); + for (const QString &s : query) { + bool in_text = item.text().contains(s, Qt::CaseInsensitive); + bool in_artist = item.data(Role_ArtistName).toString().contains(s, Qt::CaseInsensitive); + bool in_albumartist = item.data(Role_AlbumArtistName).toString().contains(s, Qt::CaseInsensitive); + if (!in_text && !in_artist && !in_albumartist) { + return true; + } + } + + return false; + +} + +void AlbumCoverManager::FetchAlbumCovers() { + + for (int i = 0; i < ui_->albums->count(); ++i) { + QListWidgetItem *item = ui_->albums->item(i); + if (item->isHidden()) continue; + if (ItemHasCover(*item)) continue; + + quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString()); + cover_fetching_tasks_[id] = item; + jobs_++; + } + + if (!cover_fetching_tasks_.isEmpty()) ui_->fetch->setEnabled(false); + + progress_bar_->setMaximum(jobs_); + progress_bar_->show(); + abort_progress_->show(); + fetch_statistics_ = CoverSearchStatistics(); + UpdateStatusText(); + +} + +void AlbumCoverManager::AlbumCoverFetched(quint64 id, const QImage &image, const CoverSearchStatistics &statistics) { + + if (!cover_fetching_tasks_.contains(id)) + return; + + QListWidgetItem *item = cover_fetching_tasks_.take(id); + if (!image.isNull()) { + SaveAndSetCover(item, image); + } + + if (cover_fetching_tasks_.isEmpty()) { + EnableCoversButtons(); + } + + fetch_statistics_ += statistics; + UpdateStatusText(); +} + +void AlbumCoverManager::UpdateStatusText() { + + QString message = tr("Got %1 covers out of %2 (%3 failed)") + .arg(fetch_statistics_.chosen_images_) + .arg(jobs_) + .arg(fetch_statistics_.missing_images_); + + if (fetch_statistics_.bytes_transferred_) { + message += ", " + tr("%1 transferred").arg(Utilities::PrettySize(fetch_statistics_.bytes_transferred_)); + } + + statusBar()->showMessage(message); + progress_bar_->setValue(fetch_statistics_.chosen_images_ + fetch_statistics_.missing_images_); + + if (cover_fetching_tasks_.isEmpty()) { + QTimer::singleShot(2000, statusBar(), SLOT(clearMessage())); + progress_bar_->hide(); + abort_progress_->hide(); + + CoverSearchStatisticsDialog *dialog = new CoverSearchStatisticsDialog(this); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->Show(fetch_statistics_); + + jobs_ = 0; + } + +} + +bool AlbumCoverManager::eventFilter(QObject *obj, QEvent *event) { + + if (obj == ui_->albums && event->type() == QEvent::ContextMenu) { + context_menu_items_ = ui_->albums->selectedItems(); + if (context_menu_items_.isEmpty()) return false; + + bool some_with_covers = false; + + for (QListWidgetItem *item : context_menu_items_) { + if (ItemHasCover(*item)) some_with_covers = true; + } + + album_cover_choice_controller_->cover_from_file_action()->setEnabled(context_menu_items_.size() == 1); + album_cover_choice_controller_->cover_from_url_action()->setEnabled(context_menu_items_.size() == 1); + album_cover_choice_controller_->show_cover_action()->setEnabled(some_with_covers && context_menu_items_.size() == 1); + album_cover_choice_controller_->unset_cover_action()->setEnabled(some_with_covers); + album_cover_choice_controller_->search_for_cover_action()->setEnabled(app_->cover_providers()->HasAnyProviders()); + + QContextMenuEvent *e = static_cast(event); + context_menu_->popup(e->globalPos()); + return true; + } + return QMainWindow::eventFilter(obj, event); +} + +Song AlbumCoverManager::GetSingleSelectionAsSong() { + return context_menu_items_.size() != 1 ? Song() : ItemAsSong(context_menu_items_[0]); +} + +Song AlbumCoverManager::GetFirstSelectedAsSong() { + return context_menu_items_.isEmpty() ? Song() : ItemAsSong(context_menu_items_[0]); +} + +Song AlbumCoverManager::ItemAsSong(QListWidgetItem *item) { + + Song result; + + QString title = item->data(Role_AlbumName).toString(); + QString artist_name = EffectiveAlbumArtistName(*item); + if (!artist_name.isEmpty()) { + result.set_title(artist_name + " - " + title); + } + else { + result.set_title(title); + } + + result.set_artist(item->data(Role_ArtistName).toString()); + result.set_albumartist(item->data(Role_AlbumArtistName).toString()); + result.set_album(item->data(Role_AlbumName).toString()); + + result.set_url(item->data(Role_FirstUrl).toUrl()); + + result.set_art_automatic(item->data(Role_PathAutomatic).toString()); + result.set_art_manual(item->data(Role_PathManual).toString()); + + // force validity + result.set_valid(true); + result.set_id(0); + + return result; +} + +void AlbumCoverManager::ShowCover() { + + Song song = GetSingleSelectionAsSong(); + if (!song.is_valid()) return; + + album_cover_choice_controller_->ShowCover(song); +} + +void AlbumCoverManager::FetchSingleCover() { + + for (QListWidgetItem *item : context_menu_items_) { + quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString()); + cover_fetching_tasks_[id] = item; + jobs_++; + } + + progress_bar_->setMaximum(jobs_); + progress_bar_->show(); + abort_progress_->show(); + UpdateStatusText(); + +} + + +void AlbumCoverManager::UpdateCoverInList(QListWidgetItem *item, const QString &cover) { + + quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QString(), cover); + item->setData(Role_PathManual, cover); + cover_loading_tasks_[id] = item; + +} + +void AlbumCoverManager::LoadCoverFromFile() { + + Song song = GetSingleSelectionAsSong(); + if (!song.is_valid()) return; + + QListWidgetItem *item = context_menu_items_[0]; + + QString cover = album_cover_choice_controller_->LoadCoverFromFile(&song); + + if (!cover.isEmpty()) { + UpdateCoverInList(item, cover); + } + +} + +void AlbumCoverManager::SaveCoverToFile() { + + Song song = GetSingleSelectionAsSong(); + if (!song.is_valid()) return; + + QImage image; + + // load the image from disk + if (song.has_manually_unset_cover()) { + image = no_cover_image_; + } + else { + if (!song.art_manual().isEmpty() && QFile::exists(song.art_manual())) { + image = QImage(song.art_manual()); + } + else if(!song.art_automatic().isEmpty() && QFile::exists(song.art_automatic())) { + image = QImage(song.art_automatic()); + } + else { + image = no_cover_image_; + } + } + + album_cover_choice_controller_->SaveCoverToFile(song, image); + +} + +void AlbumCoverManager::LoadCoverFromURL() { + + Song song = GetSingleSelectionAsSong(); + if (!song.is_valid()) return; + + QListWidgetItem *item = context_menu_items_[0]; + + QString cover = album_cover_choice_controller_->LoadCoverFromURL(&song); + + if (!cover.isEmpty()) { + UpdateCoverInList(item, cover); + } + +} + +void AlbumCoverManager::SearchForCover() { + + Song song = GetFirstSelectedAsSong(); + if (!song.is_valid()) return; + + QListWidgetItem *item = context_menu_items_[0]; + + QString cover = album_cover_choice_controller_->SearchForCover(&song); + if (cover.isEmpty()) return; + + // force the found cover on all of the selected items + for (QListWidgetItem *current : context_menu_items_) { + // don't save the first one twice + if (current != item) { + Song current_song = ItemAsSong(current); + album_cover_choice_controller_->SaveCover(¤t_song, cover); + } + + UpdateCoverInList(current, cover); + } + +} + +void AlbumCoverManager::UnsetCover() { + + Song song = GetFirstSelectedAsSong(); + if (!song.is_valid()) return; + + QListWidgetItem *item = context_menu_items_[0]; + + QString cover = album_cover_choice_controller_->UnsetCover(&song); + + // force the 'none' cover on all of the selected items + for (QListWidgetItem *current : context_menu_items_) { + current->setIcon(no_cover_item_icon_); + current->setData(Role_PathManual, cover); + + // don't save the first one twice + if (current != item) { + Song current_song = ItemAsSong(current); + album_cover_choice_controller_->SaveCover(¤t_song, cover); + } + } + +} + +SongList AlbumCoverManager::GetSongsInAlbum(const QModelIndex &index) const { + + SongList ret; + + CollectionQuery q; + q.SetColumnSpec("ROWID," + Song::kColumnSpec); + q.AddWhere("album", index.data(Role_AlbumName).toString()); + q.SetOrderBy("disc, track, title"); + + QString artist = index.data(Role_ArtistName).toString(); + QString albumartist = index.data(Role_AlbumArtistName).toString(); + + if (!albumartist.isEmpty()) { + q.AddWhere("albumartist", albumartist); + } + else if (!artist.isEmpty()) { + q.AddWhere("artist", artist); + } + + q.AddCompilationRequirement(artist.isEmpty() && albumartist.isEmpty()); + + if (!collection_backend_->ExecQuery(&q)) return ret; + + while (q.Next()) { + Song song; + song.InitFromQuery(q, true); + ret << song; + } + return ret; + +} + +SongList AlbumCoverManager::GetSongsInAlbums(const QModelIndexList &indexes) const { + + SongList ret; + for (const QModelIndex &index : indexes) { + ret << GetSongsInAlbum(index); + } + return ret; + +} + +SongMimeData *AlbumCoverManager::GetMimeDataForAlbums(const QModelIndexList &indexes) const { + + SongList songs = GetSongsInAlbums(indexes); + if (songs.isEmpty()) return nullptr; + + SongMimeData *data = new SongMimeData; + data->backend = collection_backend_; + data->songs = songs; + return data; + +} + +void AlbumCoverManager::AlbumDoubleClicked(const QModelIndex &index) { + + SongMimeData *data = GetMimeDataForAlbums(QModelIndexList() << index); + if (data) { + data->from_doubleclick_ = true; + emit AddToPlaylist(data); + } + +} + +void AlbumCoverManager::AddSelectedToPlaylist() { + emit AddToPlaylist(GetMimeDataForAlbums(ui_->albums->selectionModel()->selectedIndexes())); +} + +void AlbumCoverManager::LoadSelectedToPlaylist() { + + SongMimeData *data = GetMimeDataForAlbums(ui_->albums->selectionModel()->selectedIndexes()); + if (data) { + data->clear_first_ = true; + emit AddToPlaylist(data); + } + +} + +void AlbumCoverManager::SaveAndSetCover(QListWidgetItem *item, const QImage &image) { + + const QString artist = item->data(Role_ArtistName).toString(); + const QString albumartist = item->data(Role_ArtistName).toString(); + const QString album = item->data(Role_AlbumName).toString(); + + QString path = album_cover_choice_controller_->SaveCoverInCache(artist, album, image); + + // Save the image in the database + collection_backend_->UpdateManualAlbumArtAsync(artist, albumartist, album, path); + + // Update the icon in our list + quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QString(), path); + item->setData(Role_PathManual, path); + cover_loading_tasks_[id] = item; +} + +void AlbumCoverManager::ExportCovers() { + + AlbumCoverExport::DialogResult result = cover_export_->Exec(); + + if (result.cancelled_) { + return; + } + + DisableCoversButtons(); + + cover_exporter_->SetDialogResult(result); + + for (int i = 0; i < ui_->albums->count(); ++i) { + QListWidgetItem *item = ui_->albums->item(i); + + // skip hidden and coverless albums + if (item->isHidden() || !ItemHasCover(*item)) { + continue; + } + + cover_exporter_->AddExportRequest(ItemAsSong(item)); + } + + if (cover_exporter_->request_count() > 0) { + progress_bar_->setMaximum(cover_exporter_->request_count()); + progress_bar_->show(); + abort_progress_->show(); + + cover_exporter_->StartExporting(); + } + else { + QMessageBox msg; + msg.setWindowTitle(tr("Export finished")); + msg.setText(tr("No covers to export.")); + msg.exec(); + } + +} + +void AlbumCoverManager::UpdateExportStatus(int exported, int skipped, int max) { + + progress_bar_->setValue(exported); + + QString message = tr("Exported %1 covers out of %2 (%3 skipped)") + .arg(exported) + .arg(max) + .arg(skipped); + statusBar()->showMessage(message); + + // end of the current process + if (exported + skipped >= max) { + QTimer::singleShot(2000, statusBar(), SLOT(clearMessage())); + + progress_bar_->hide(); + abort_progress_->hide(); + EnableCoversButtons(); + + QMessageBox msg; + msg.setWindowTitle(tr("Export finished")); + msg.setText(message); + msg.exec(); + } + +} + +QString AlbumCoverManager::EffectiveAlbumArtistName(const QListWidgetItem &item) const { + QString albumartist = item.data(Role_AlbumArtistName).toString(); + if (!albumartist.isEmpty()) { + return albumartist; + } + return item.data(Role_ArtistName).toString(); +} + +QImage AlbumCoverManager::GenerateNoCoverImage(const QIcon &no_cover_icon) const { + + // Get a square version of noalbumart.png with some transparency: + QImage nocover = no_cover_icon.pixmap(no_cover_icon.availableSizes().last()).toImage(); + nocover = nocover.scaled(120, 120, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + QImage square_nocover(120, 120, QImage::Format_ARGB32); + square_nocover.fill(0); + QPainter p(&square_nocover); + p.setOpacity(0.4); + p.drawImage((120 - nocover.width()) / 2, (120 - nocover.height()) / 2, nocover); + p.end(); + + return square_nocover; + +} + +bool AlbumCoverManager::ItemHasCover(const QListWidgetItem& item) const { + return item.icon().cacheKey() != no_cover_item_icon_.cacheKey(); +} + diff --git a/src/covermanager/albumcovermanager.h b/src/covermanager/albumcovermanager.h new file mode 100644 index 00000000..e38a8318 --- /dev/null +++ b/src/covermanager/albumcovermanager.h @@ -0,0 +1,194 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ALBUMCOVERMANAGER_H +#define ALBUMCOVERMANAGER_H + +#include "config.h" + +#include +#include +#include + +#include "gtest/gtest_prod.h" + +#include "core/song.h" +#include "covermanager/albumcoverloaderoptions.h" +#include "covermanager/coversearchstatistics.h" + +class AlbumCoverChoiceController; +class AlbumCoverExport; +class AlbumCoverExporter; +class AlbumCoverFetcher; +class AlbumCoverSearcher; +class Application; +class CollectionBackend; +class SongMimeData; +class Ui_CoverManager; + +class QListWidgetItem; +class QMenu; +class QNetworkAccessManager; +class QPushButton; +class QProgressBar; + +class AlbumCoverManager : public QMainWindow { + Q_OBJECT + public: + AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QWidget *parent = nullptr, QNetworkAccessManager *network = 0); + ~AlbumCoverManager(); + + static const char *kSettingsGroup; + + CollectionBackend *backend() const; + QIcon no_cover_icon() const { return no_cover_icon_; } + + void Reset(); + void Init(); + + void EnableCoversButtons(); + void DisableCoversButtons(); + + SongList GetSongsInAlbum(const QModelIndex &index) const; + SongList GetSongsInAlbums(const QModelIndexList &indexes) const; + SongMimeData *GetMimeDataForAlbums(const QModelIndexList &indexes) const; + + signals: + void AddToPlaylist(QMimeData *data); + + protected: + void showEvent(QShowEvent *); + void closeEvent(QCloseEvent *); + + // For the album view context menu events + bool eventFilter(QObject *obj, QEvent *event); + + private slots: + void ArtistChanged(QListWidgetItem *current); + void CoverImageLoaded(quint64 id, const QImage &image); + void UpdateFilter(); + void FetchAlbumCovers(); + void ExportCovers(); + void AlbumCoverFetched(quint64 id, const QImage &image, const CoverSearchStatistics &statistics); + void CancelRequests(); + + // On the context menu + void FetchSingleCover(); + + void LoadCoverFromFile(); + void SaveCoverToFile(); + void LoadCoverFromURL(); + void SearchForCover(); + void UnsetCover(); + void ShowCover(); + + // For adding albums to the playlist + void AlbumDoubleClicked(const QModelIndex &index); + void AddSelectedToPlaylist(); + void LoadSelectedToPlaylist(); + + void UpdateCoverInList(QListWidgetItem *item, const QString &cover); + void UpdateExportStatus(int exported, int bad, int count); + + private: + enum ArtistItemType { + All_Artists, + Various_Artists, + Specific_Artist + }; + + enum Role { + Role_ArtistName = Qt::UserRole + 1, + Role_AlbumArtistName, + Role_AlbumName, + Role_PathAutomatic, + Role_PathManual, + Role_FirstUrl + }; + + enum HideCovers { + Hide_None, + Hide_WithCovers, + Hide_WithoutCovers + }; + + QString InitialPathForOpenCoverDialog(const QString &path_automatic, const QString &first_file_name) const; + QString EffectiveAlbumArtistName(const QListWidgetItem &item) const; + + // Returns the selected element in form of a Song ready to be used + // by AlbumCoverChoiceController or invalid song if there's nothing + // or multiple elements selected. + Song GetSingleSelectionAsSong(); + // Returns the first of the selected elements in form of a Song ready + // to be used by AlbumCoverChoiceController or invalid song if there's nothing + // selected. + Song GetFirstSelectedAsSong(); + + Song ItemAsSong(QListWidgetItem *item); + + void UpdateStatusText(); + bool ShouldHide(const QListWidgetItem &item, const QString &filter, HideCovers hide) const; + void SaveAndSetCover(QListWidgetItem *item, const QImage &image); + + private: + Ui_CoverManager *ui_; + Application *app_; + + AlbumCoverChoiceController *album_cover_choice_controller_; + + QAction *filter_all_; + QAction *filter_with_covers_; + QAction *filter_without_covers_; + + AlbumCoverLoaderOptions cover_loader_options_; + QMap cover_loading_tasks_; + + AlbumCoverFetcher *cover_fetcher_; + QMap cover_fetching_tasks_; + CoverSearchStatistics fetch_statistics_; + + AlbumCoverSearcher *cover_searcher_; + AlbumCoverExport *cover_export_; + AlbumCoverExporter *cover_exporter_; + + QImage GenerateNoCoverImage(const QIcon &no_cover_icon) const; + bool ItemHasCover(const QListWidgetItem &item) const; + + QIcon artist_icon_; + QIcon all_artists_icon_; + const QIcon no_cover_icon_; + const QImage no_cover_image_; + const QIcon no_cover_item_icon_; + + QMenu *context_menu_; + QList context_menu_items_; + + QProgressBar *progress_bar_; + QPushButton *abort_progress_; + int jobs_; + + CollectionBackend *collection_backend_; + + FRIEND_TEST(AlbumCoverManagerTest, HidesItemsWithCover); + FRIEND_TEST(AlbumCoverManagerTest, HidesItemsWithoutCover); + FRIEND_TEST(AlbumCoverManagerTest, HidesItemsWithFilter); +}; + +#endif // ALBUMCOVERMANAGER_H diff --git a/src/covermanager/albumcovermanager.ui b/src/covermanager/albumcovermanager.ui new file mode 100644 index 00000000..e5dea7dc --- /dev/null +++ b/src/covermanager/albumcovermanager.ui @@ -0,0 +1,284 @@ + + + CoverManager + + + + 0 + 0 + 903 + 662 + + + + Cover Manager + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + + + 0 + + + + + Qt::Horizontal + + + + true + + + QAbstractItemView::SelectRows + + + + 24 + 24 + + + + true + + + + + + 0 + + + 0 + + + + + 6 + + + 0 + + + + + Enter search terms here + + + + + + + View + + + + 16 + 16 + + + + QToolButton::MenuButtonPopup + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + + + 6 + + + + + 10 + + + 10 + + + + + Total albums: + + + + + + + Without cover: + + + + + + + + + 10 + + + 10 + + + + + Qt::RightToLeft + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 0 + + + + + Fetch Missing Covers + + + + 16 + 16 + + + + + + + + Export Covers + + + + + + + + + + + true + + + QAbstractItemView::DragDrop + + + false + + + QAbstractItemView::ExtendedSelection + + + + 120 + 120 + + + + QListView::LeftToRight + + + true + + + QListView::Adjust + + + 2 + + + QListView::IconMode + + + true + + + true + + + + + + + + + + + + + Fetch automatically + + + + + Load + + + + + Add to playlist + + + + + + QSearchField + QWidget +
3rdparty/qocoa/qsearchfield.h
+
+ + AlbumCoverManagerList + QListWidget +
covermanager/albumcovermanagerlist.h
+
+
+ + artists + albums + + + + + +
diff --git a/src/covermanager/albumcovermanagerlist.cpp b/src/covermanager/albumcovermanagerlist.cpp new file mode 100644 index 00000000..388a33ce --- /dev/null +++ b/src/covermanager/albumcovermanagerlist.cpp @@ -0,0 +1,73 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "albumcovermanagerlist.h" + +#include + +#include +#include + +#include "albumcovermanager.h" +#include "collection/collectionbackend.h" +#include "playlist/songmimedata.h" + +AlbumCoverManagerList::AlbumCoverManagerList(QWidget *parent) : QListWidget(parent), manager_(nullptr) {} + +QMimeData *AlbumCoverManagerList::mimeData(const QList items) const { + + // Get songs + SongList songs; + for (QListWidgetItem *item : items) { + songs << manager_->GetSongsInAlbum(indexFromItem(item)); + } + + if (songs.isEmpty()) return nullptr; + + // Get URLs from the songs + QList urls; + for (const Song &song : songs) { + urls << song.url(); + } + + // Get the QAbstractItemModel data so the picture works + std::unique_ptr orig_data(QListWidget::mimeData(items)); + + SongMimeData *mime_data = new SongMimeData; + mime_data->backend = manager_->backend(); + mime_data->songs = songs; + mime_data->setUrls(urls); + mime_data->setData(orig_data->formats()[0], orig_data->data(orig_data->formats()[0])); + return mime_data; +} + +void AlbumCoverManagerList::dropEvent(QDropEvent *e) { + + // Set movement to Static just for this dropEvent so the user can't move the + // album covers. If it's set to Static all the time then the user can't even + // drag to the playlist + QListWidget::Movement old_movement = movement(); + setMovement(QListWidget::Static); + QListWidget::dropEvent(e); + setMovement(old_movement); + +} diff --git a/src/covermanager/albumcovermanagerlist.h b/src/covermanager/albumcovermanagerlist.h new file mode 100644 index 00000000..01432051 --- /dev/null +++ b/src/covermanager/albumcovermanagerlist.h @@ -0,0 +1,46 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ALBUMCOVERMANAGERLIST_H +#define ALBUMCOVERMANAGERLIST_H + +#include "config.h" + +#include + +class AlbumCoverManager; + +class AlbumCoverManagerList : public QListWidget { + Q_OBJECT + public: + AlbumCoverManagerList(QWidget *parent = nullptr); + + void set_cover_manager(AlbumCoverManager* manager) { manager_ = manager; } + +protected: + QMimeData *mimeData(const QList items) const; + void dropEvent(QDropEvent *event); + +private: + AlbumCoverManager* manager_; +}; + +#endif // ALBUMCOVERMANAGERLIST_H + diff --git a/src/covermanager/albumcoversearcher.cpp b/src/covermanager/albumcoversearcher.cpp new file mode 100644 index 00000000..89731b0e --- /dev/null +++ b/src/covermanager/albumcoversearcher.cpp @@ -0,0 +1,259 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "albumcoversearcher.h" +#include "ui_albumcoversearcher.h" +#include "core/application.h" +#include "core/logging.h" +#include "core/utilities.h" +#include "covermanager/albumcoverfetcher.h" +#include "covermanager/albumcoverloader.h" +#include "widgets/forcescrollperpixel.h" +#include "widgets/groupediconview.h" + +#include +#include +#include +#include + +const int SizeOverlayDelegate::kMargin = 4; +const int SizeOverlayDelegate::kPaddingX = 3; +const int SizeOverlayDelegate::kPaddingY = 1; +const qreal SizeOverlayDelegate::kBorder = 5.0; +const qreal SizeOverlayDelegate::kFontPointSize = 7.5; +const int SizeOverlayDelegate::kBorderAlpha = 200; +const int SizeOverlayDelegate::kBackgroundAlpha = 175; + +SizeOverlayDelegate::SizeOverlayDelegate(QObject *parent) + : QStyledItemDelegate(parent) {} + +void SizeOverlayDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + + QStyledItemDelegate::paint(painter, option, index); + + if (!index.data(AlbumCoverSearcher::Role_ImageFetchFinished).toBool()) { + return; + } + + const QSize size = index.data(AlbumCoverSearcher::Role_ImageSize).toSize(); + const QString text = Utilities::PrettySize(size); + + QFont font(option.font); + font.setPointSizeF(kFontPointSize); + font.setBold(true); + + const QFontMetrics metrics(font); + + const int text_width = metrics.width(text); + + const QRect icon_rect(option.rect.left(), option.rect.top(), option.rect.width(), option.rect.width()); + + const QRect background_rect(icon_rect.right() - kMargin - text_width - kPaddingX * 2, icon_rect.bottom() - kMargin - metrics.height() - kPaddingY * 2, text_width + kPaddingX * 2, metrics.height() + kPaddingY * 2); + const QRect text_rect(background_rect.left() + kPaddingX, background_rect.top() + kPaddingY, text_width, metrics.height()); + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(QColor(0, 0, 0, kBorderAlpha)); + painter->setBrush(QColor(0, 0, 0, kBackgroundAlpha)); + painter->drawRoundedRect(background_rect, kBorder, kBorder); + + painter->setPen(Qt::white); + painter->setFont(font); + painter->drawText(text_rect, text); + painter->restore(); + +} + +AlbumCoverSearcher::AlbumCoverSearcher(const QIcon &no_cover_icon, Application *app, QWidget *parent) + : QDialog(parent), + ui_(new Ui_AlbumCoverSearcher), + app_(app), + model_(new QStandardItemModel(this)), + no_cover_icon_(no_cover_icon), + fetcher_(nullptr), + id_(0) { + + setWindowModality(Qt::WindowModal); + ui_->setupUi(this); + ui_->busy->hide(); + + ui_->covers->set_header_text(tr("Covers from %1")); + ui_->covers->AddSortSpec(Role_ImageDimensions, Qt::DescendingOrder); + ui_->covers->setItemDelegate(new SizeOverlayDelegate(this)); + ui_->covers->setModel(model_); + + options_.scale_output_image_ = false; + options_.pad_output_image_ = false; + + connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(ImageLoaded(quint64, QImage))); + + connect(ui_->search, SIGNAL(clicked()), SLOT(Search())); + connect(ui_->covers, SIGNAL(doubleClicked(QModelIndex)), SLOT(CoverDoubleClicked(QModelIndex))); + + new ForceScrollPerPixel(ui_->covers, this); + + ui_->buttonBox->button(QDialogButtonBox::Cancel)->setShortcut(QKeySequence::Close); + +} + +AlbumCoverSearcher::~AlbumCoverSearcher() { + delete ui_; +} + +void AlbumCoverSearcher::Init(AlbumCoverFetcher *fetcher) { + + fetcher_ = fetcher; + connect(fetcher_, SIGNAL(SearchFinished(quint64,CoverSearchResults,CoverSearchStatistics)), SLOT(SearchFinished(quint64,CoverSearchResults))); + +} + +QImage AlbumCoverSearcher::Exec(const QString &artist, const QString &album) { + + ui_->artist->setText(artist); + ui_->album->setText(album); + ui_->artist->setFocus(); + + if (!artist.isEmpty() || !album.isEmpty()) { + Search(); + } + + if (exec() == QDialog::Rejected) return QImage(); + + QModelIndex selected = ui_->covers->currentIndex(); + if (!selected.isValid() || !selected.data(Role_ImageFetchFinished).toBool()) + return QImage(); + + QIcon icon = selected.data(Qt::DecorationRole).value(); + if (icon.cacheKey() == no_cover_icon_.cacheKey()) return QImage(); + + return icon.pixmap(icon.availableSizes()[0]).toImage(); + +} + +void AlbumCoverSearcher::Search() { + + model_->clear(); + cover_loading_tasks_.clear(); + + if (ui_->album->isEnabled()) { + id_ = fetcher_->SearchForCovers(ui_->artist->text(), ui_->album->text()); + ui_->search->setText(tr("Abort")); + ui_->busy->show(); + ui_->artist->setEnabled(false); + ui_->album->setEnabled(false); + ui_->covers->setEnabled(false); + } + else { + fetcher_->Clear(); + ui_->search->setText(tr("Search")); + ui_->busy->hide(); + ui_->search->setEnabled(true); + ui_->artist->setEnabled(true); + ui_->album->setEnabled(true); + ui_->covers->setEnabled(true); + } + +} + +void AlbumCoverSearcher::SearchFinished(quint64 id, const CoverSearchResults &results) { + + if (id != id_) + return; + + ui_->search->setEnabled(true); + ui_->artist->setEnabled(true); + ui_->album->setEnabled(true); + ui_->covers->setEnabled(true); + ui_->search->setText(tr("Search")); + id_ = 0; + + for (const CoverSearchResult &result : results) { + if (result.image_url.isEmpty()) continue; + + quint64 id = app_->album_cover_loader()->LoadImageAsync(options_, result.image_url.toString(), QString()); + + QStandardItem *item = new QStandardItem; + item->setIcon(no_cover_icon_); + item->setText(result.description); + item->setData(result.image_url, Role_ImageURL); + item->setData(id, Role_ImageRequestId); + item->setData(false, Role_ImageFetchFinished); + item->setData(QVariant(Qt::AlignTop | Qt::AlignHCenter), Qt::TextAlignmentRole); + item->setData(result.provider, GroupedIconView::Role_Group); + + model_->appendRow(item); + + cover_loading_tasks_[id] = item; + } + + if (cover_loading_tasks_.isEmpty()) ui_->busy->hide(); + +} + +void AlbumCoverSearcher::ImageLoaded(quint64 id, const QImage &image) { + + if (!cover_loading_tasks_.contains(id)) return; + QStandardItem *item = cover_loading_tasks_.take(id); + + if (cover_loading_tasks_.isEmpty()) ui_->busy->hide(); + + if (image.isNull()) { + model_->removeRow(item->row()); + return; + } + + QIcon icon(QPixmap::fromImage(image)); + + // Create a pixmap that's padded and exactly the right size for the icon. + QImage scaled_image(image.scaled(ui_->covers->iconSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); + + QImage padded_image(ui_->covers->iconSize(), QImage::Format_ARGB32_Premultiplied); + padded_image.fill(0); + + QPainter p(&padded_image); + p.drawImage((padded_image.width() - scaled_image.width()) / 2, (padded_image.height() - scaled_image.height()) / 2, scaled_image); + p.end(); + + icon.addPixmap(QPixmap::fromImage(padded_image)); + + item->setData(true, Role_ImageFetchFinished); + item->setData(image.width() * image.height(), Role_ImageDimensions); + item->setData(image.size(), Role_ImageSize); + item->setIcon(icon); + +} + +void AlbumCoverSearcher::keyPressEvent(QKeyEvent *e) { + + if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { + e->ignore(); + return; + } + + QDialog::keyPressEvent(e); + +} + +void AlbumCoverSearcher::CoverDoubleClicked(const QModelIndex &index) { + if (index.isValid()) accept(); +} + diff --git a/src/covermanager/albumcoversearcher.h b/src/covermanager/albumcoversearcher.h new file mode 100644 index 00000000..2d504aab --- /dev/null +++ b/src/covermanager/albumcoversearcher.h @@ -0,0 +1,100 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ALBUMCOVERSEARCHER_H +#define ALBUMCOVERSEARCHER_H + +#include "config.h" + +#include +#include +#include + +#include "covermanager/albumcoverfetcher.h" +#include "covermanager/albumcoverloaderoptions.h" + +class AlbumCoverLoader; +class Application; +class Ui_AlbumCoverSearcher; + +class QModelIndex; +class QStandardItem; +class QStandardItemModel; + +class SizeOverlayDelegate : public QStyledItemDelegate { +public: + static const int kMargin; + static const int kPaddingX; + static const int kPaddingY; + static const qreal kBorder; + static const qreal kFontPointSize; + static const int kBorderAlpha; + static const int kBackgroundAlpha; + + SizeOverlayDelegate(QObject *parent = nullptr); + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; +}; + +// This is a dialog that lets the user search for album covers +class AlbumCoverSearcher : public QDialog { + Q_OBJECT + +public: + AlbumCoverSearcher(const QIcon &no_cover_icon, Application *app, QWidget *parent); + ~AlbumCoverSearcher(); + + enum Role { + Role_ImageURL = Qt::UserRole + 1, + Role_ImageRequestId, + Role_ImageFetchFinished, + Role_ImageDimensions, // width * height + Role_ImageSize, + }; + + void Init(AlbumCoverFetcher *fetcher); + + QImage Exec(const QString &artist, const QString &album); + +protected: + void keyPressEvent(QKeyEvent *); + +private slots: + void Search(); + void SearchFinished(quint64 id, const CoverSearchResults &results); + void ImageLoaded(quint64 id, const QImage &image); + + void CoverDoubleClicked(const QModelIndex &index); + +private: + Ui_AlbumCoverSearcher *ui_; + + Application *app_; + QStandardItemModel *model_; + + QIcon no_cover_icon_; + AlbumCoverLoaderOptions options_; + AlbumCoverFetcher *fetcher_; + + quint64 id_; + QMap cover_loading_tasks_; +}; + +#endif // ALBUMCOVERSEARCHER_H diff --git a/src/covermanager/albumcoversearcher.ui b/src/covermanager/albumcoversearcher.ui new file mode 100644 index 00000000..cc41bf79 --- /dev/null +++ b/src/covermanager/albumcoversearcher.ui @@ -0,0 +1,173 @@ + + + AlbumCoverSearcher + + + + 0 + 0 + 829 + 518 + + + + Cover Manager + + + + + + + + Artist + + + Artist + + + + + + + Album + + + Album + + + + + + + Search + + + + + + + + + + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAlwaysOff + + + + 120 + 120 + + + + 2 + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + QSearchField + QWidget +
3rdparty/qocoa/qsearchfield.h
+
+ + BusyIndicator + QWidget +
widgets/busyindicator.h
+
+ + GroupedIconView + QListView +
widgets/groupediconview.h
+
+
+ + artist + album + search + covers + buttonBox + + + + + buttonBox + accepted() + AlbumCoverSearcher + accept() + + + 257 + 508 + + + 157 + 274 + + + + + buttonBox + rejected() + AlbumCoverSearcher + reject() + + + 325 + 508 + + + 286 + 274 + + + + + artist + returnPressed() + search + click() + + + 357 + 35 + + + 812 + 36 + + + + + album + returnPressed() + search + click() + + + 580 + 22 + + + 779 + 21 + + + + +
diff --git a/src/covermanager/amazoncoverprovider.cpp b/src/covermanager/amazoncoverprovider.cpp new file mode 100644 index 00000000..331b261e --- /dev/null +++ b/src/covermanager/amazoncoverprovider.cpp @@ -0,0 +1,181 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * Copyright 2013, Jonas Kvinge + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "amazoncoverprovider.h" + +#include "core/closure.h" +#include "core/logging.h" +#include "core/network.h" +#include "core/utilities.h" + +const char *AmazonCoverProvider::kUrl = "http://ecs.amazonaws.com/onca/xml"; +const char *AmazonCoverProvider::kAssociateTag = "jonas052-20"; + +const char *AmazonCoverProvider::kAccessKeyB64 = "QUtJQUozQ1dIQ0RWSVlYN1JMTFE="; +const char *AmazonCoverProvider::kSecretAccessKeyB64 = "TjFZU3F2c2hJZDVtUGxKVW1Ka0kvc2E1WkZESG9TYy9ZWkgxYWdJdQ=="; + +AmazonCoverProvider::AmazonCoverProvider(QObject *parent) : CoverProvider("Amazon", parent), network_(new NetworkAccessManager(this)) {} + +bool AmazonCoverProvider::StartSearch(const QString &artist, const QString &album, int id) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + typedef QPair Arg; + typedef QList ArgList; + + typedef QPair EncodedArg; + typedef QList EncodedArgList; + + // Must be sorted by parameter name + ArgList args = ArgList() + << Arg("AWSAccessKeyId", QByteArray::fromBase64(kAccessKeyB64)) + //<< Arg("AWSAccessKeyId", kAccessKey) + << Arg("AssociateTag", kAssociateTag) + << Arg("Keywords", artist + " " + album) + << Arg("Operation", "ItemSearch") + << Arg("ResponseGroup", "Images") + << Arg("SearchIndex", "All") + << Arg("Service", "AWSECommerceService") + << Arg("Timestamp", QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss.zzzZ")) + << Arg("Version", "2009-11-01"); + + QUrlQuery url_query; + QUrl url(kUrl); + QStringList query_items; + + // Encode the arguments + for (const Arg &arg : args) { + EncodedArg encoded_arg(QUrl::toPercentEncoding(arg.first), QUrl::toPercentEncoding(arg.second)); + query_items << QString(encoded_arg.first + "=" + encoded_arg.second); + url_query.addQueryItem(encoded_arg.first, encoded_arg.second); + } + + // Sign the request + + const QByteArray data_to_sign = QString("GET\n%1\n%2\n%3").arg(url.host(), url.path(), query_items.join("&")).toLatin1(); + //const QByteArray signature(Utilities::HmacSha256(kSecretAccessKey, data_to_sign)); + const QByteArray signature(Utilities::HmacSha256(QByteArray::fromBase64(kSecretAccessKeyB64), data_to_sign)); + + // Add the signature to the request + + url_query.addQueryItem("Signature", QUrl::toPercentEncoding(signature.toBase64())); + + url.setQuery(url_query); + QNetworkReply *reply = network_->get(QNetworkRequest(url)); + + NewClosure(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(QueryError(QNetworkReply::NetworkError, QNetworkReply*, int)), reply, id); + NewClosure(reply, SIGNAL(finished()), this, SLOT(QueryFinished(QNetworkReply*, int)), reply, id); + + return true; +} + +void AmazonCoverProvider::QueryError(QNetworkReply::NetworkError error, QNetworkReply *reply, int id) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + +} + +void AmazonCoverProvider::QueryFinished(QNetworkReply *reply, int id) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + reply->deleteLater(); + + QString data=(QString)reply->readAll(); + + //qDebug() << data; + + CoverSearchResults results; + + QXmlStreamReader reader(data); + while (!reader.atEnd()) { + if (reader.readNext() == QXmlStreamReader::EndDocument) break; + if (reader.tokenType() == QXmlStreamReader::Invalid ) { qLog(Debug) << reader.error() << reader.errorString(); break; } + //qLog(Debug) << reader.tokenType() << reader.name(); + if (reader.tokenType() == QXmlStreamReader::StartElement && reader.name() == "Item") { + ReadItem(&reader, &results); + } + } + + emit SearchFinished(id, results); + +} + +void AmazonCoverProvider::ReadItem(QXmlStreamReader *reader, CoverSearchResults *results) { + + //qLog(Debug) << __PRETTY_FUNCTION__ << "name: " << reader->name() << " text: " << reader->text(); + + while (!reader->atEnd()) { + switch (reader->readNext()) { + case QXmlStreamReader::StartElement: + if (reader->name() == "LargeImage") { + ReadLargeImage(reader, results); + } + else { + reader->skipCurrentElement(); + } + break; + + case QXmlStreamReader::EndElement: + return; + + default: + break; + } + } +} + +void AmazonCoverProvider::ReadLargeImage(QXmlStreamReader *reader, CoverSearchResults *results) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + while (!reader->atEnd()) { + switch (reader->readNext()) { + case QXmlStreamReader::StartElement: + if (reader->name() == "URL") { + CoverSearchResult result; + result.image_url = QUrl(reader->readElementText()); + results->append(result); + } + else { + reader->skipCurrentElement(); + } + break; + + case QXmlStreamReader::EndElement: + return; + + default: + break; + } + } +} + diff --git a/src/covermanager/amazoncoverprovider.h b/src/covermanager/amazoncoverprovider.h new file mode 100644 index 00000000..e6def4ec --- /dev/null +++ b/src/covermanager/amazoncoverprovider.h @@ -0,0 +1,60 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * Copyright 2013, Jonas Kvinge + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef AMAZONCOVERPROVIDER_H +#define AMAZONCOVERPROVIDER_H + +#include "config.h" + +#include +#include + +#include "coverprovider.h" + +class QNetworkAccessManager; + +class AmazonCoverProvider : public CoverProvider { + Q_OBJECT + + public: + explicit AmazonCoverProvider(QObject *parent = nullptr); + + static const char *kUrl; + static const char *kAssociateTag; + + static const char *kAccessKeyB64; + static const char *kSecretAccessKeyB64; + + bool StartSearch(const QString &artist, const QString &album, int id); + + private slots: + void QueryError(QNetworkReply::NetworkError error, QNetworkReply *reply, int id); + void QueryFinished(QNetworkReply *reply, int id); + + private: + void ReadItem(QXmlStreamReader *reader, CoverSearchResults *results); + void ReadLargeImage(QXmlStreamReader *reader, CoverSearchResults *results); + + private: + QNetworkAccessManager *network_; +}; + +#endif // AMAZONCOVERPROVIDER_H diff --git a/src/covermanager/coverexportrunnable.cpp b/src/covermanager/coverexportrunnable.cpp new file mode 100644 index 00000000..ceb751b8 --- /dev/null +++ b/src/covermanager/coverexportrunnable.cpp @@ -0,0 +1,203 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include + +#include "albumcoverexporter.h" +#include "core/song.h" +#include "core/tagreaderclient.h" + +#include "coverexportrunnable.h" + +CoverExportRunnable::CoverExportRunnable(const AlbumCoverExport::DialogResult &dialog_result, const Song &song) + : dialog_result_(dialog_result), song_(song) {} + +void CoverExportRunnable::run() { + + QString cover_path = GetCoverPath(); + + // manually unset? + if (cover_path.isEmpty()) { + EmitCoverSkipped(); + } + else { + if (dialog_result_.RequiresCoverProcessing()) + ProcessAndExportCover(); + else + ExportCover(); + } + +} + +QString CoverExportRunnable::GetCoverPath() { + + if (song_.has_manually_unset_cover()) { + return QString(); + // Export downloaded covers? + } + else if (!song_.art_manual().isEmpty() && dialog_result_.export_downloaded_) { + return song_.art_manual(); + // Export embedded covers? + } + else if (!song_.art_automatic().isEmpty() && song_.art_automatic() == Song::kEmbeddedCover && dialog_result_.export_embedded_) { + return song_.art_automatic(); + } + else { + return QString(); + } + +} + +// Exports a single album cover using a "save QImage to file" approach. +// For performance reasons this method will be invoked only if loading +// and in memory processing of images is necessary for current settings +// which means that: +// - either the force size flag is being used +// - or the "overwrite smaller" mode is used +// In all other cases, the faster ExportCover() method will be used. +void CoverExportRunnable::ProcessAndExportCover() { + + QString cover_path = GetCoverPath(); + + // either embedded or disk - the one we'll export for the current album + QImage cover; + + QImage embedded_cover; + QImage disk_cover; + + // load embedded cover if any + if (song_.has_embedded_cover()) { + embedded_cover = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song_.url().toLocalFile()); + + if (embedded_cover.isNull()) { + EmitCoverSkipped(); + return; + } + cover = embedded_cover; + } + + // load a file cover which iss mandatory if there's no embedded cover + disk_cover.load(cover_path); + + if (embedded_cover.isNull()) { + if (disk_cover.isNull()) { + EmitCoverSkipped(); + return; + } + cover = disk_cover; + } + + // rescale if necessary + if (dialog_result_.IsSizeForced()) { + cover = cover.scaled(QSize(dialog_result_.width_, dialog_result_.height_), Qt::IgnoreAspectRatio); + } + + QString dir = song_.url().toLocalFile().section('/', 0, -2); + QString extension = cover_path.section('.', -1); + + QString new_file = dir + '/' + dialog_result_.fileName_ + '.' + (cover_path == Song::kEmbeddedCover ? "jpg" : extension); + + // If the file exists, do not override! + if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode_None && QFile::exists(new_file)) { + EmitCoverSkipped(); + return; + } + + // we're handling overwrite as remove + copy so we need to delete the old file + // first + if (QFile::exists(new_file) && dialog_result_.overwrite_ != AlbumCoverExport::OverwriteMode_None) { + + // if the mode is "overwrite smaller" then skip the cover if a bigger one + // is already available in the folder + if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode_Smaller) { + QImage existing; + existing.load(new_file); + + if (existing.isNull() || existing.size().height() >= cover.size().height() || existing.size().width() >= cover.size().width()) { + + EmitCoverSkipped(); + return; + } + } + + if (!QFile::remove(new_file)) { + EmitCoverSkipped(); + return; + } + } + + if (cover.save(new_file)) + EmitCoverExported(); + else + EmitCoverSkipped(); + +} + +// Exports a single album cover using a "copy file" approach. +void CoverExportRunnable::ExportCover() { + + QString cover_path = GetCoverPath(); + + QString dir = song_.url().toLocalFile().section('/', 0, -2); + QString extension = cover_path.section('.', -1); + + QString new_file = dir + '/' + dialog_result_.fileName_ + '.' + (cover_path == Song::kEmbeddedCover ? "jpg" : extension); + + // If the file exists, do not override! + if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode_None && QFile::exists(new_file)) { + EmitCoverSkipped(); + return; + } + + // we're handling overwrite as remove + copy so we need to delete the old file + // first + if (dialog_result_.overwrite_ != AlbumCoverExport::OverwriteMode_None && QFile::exists(new_file)) { + if (!QFile::remove(new_file)) { + EmitCoverSkipped(); + return; + } + } + + if (cover_path == Song::kEmbeddedCover) { + // an embedded cover + QImage embedded = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song_.url().toLocalFile()); + if (!embedded.save(new_file)) { + EmitCoverSkipped(); + return; + } + } + else { + // automatic or manual cover, available in an image file + if (!QFile::copy(cover_path, new_file)) { + EmitCoverSkipped(); + return; + } + } + EmitCoverExported(); + +} + +void CoverExportRunnable::EmitCoverExported() { emit CoverExported(); } + +void CoverExportRunnable::EmitCoverSkipped() { emit CoverSkipped(); } + diff --git a/src/covermanager/coverexportrunnable.h b/src/covermanager/coverexportrunnable.h new file mode 100644 index 00000000..1c936d53 --- /dev/null +++ b/src/covermanager/coverexportrunnable.h @@ -0,0 +1,61 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COVEREXPORTRUNNABLE_H +#define COVEREXPORTRUNNABLE_H + +#include "config.h" + +#include +#include + +#include "core/song.h" +#include "covermanager/albumcoverexport.h" + +class AlbumCoverExporter; + +class CoverExportRunnable : public QObject, public QRunnable { + Q_OBJECT + + public: + CoverExportRunnable(const AlbumCoverExport::DialogResult &dialog_result, const Song &song); + virtual ~CoverExportRunnable() {} + + void run(); + +signals: + void CoverExported(); + void CoverSkipped(); + + private: + void EmitCoverExported(); + void EmitCoverSkipped(); + + void ProcessAndExportCover(); + void ExportCover(); + QString GetCoverPath(); + + AlbumCoverExport::DialogResult dialog_result_; + Song song_; + AlbumCoverExporter* album_cover_exporter_; +}; + +#endif // COVEREXPORTRUNNABLE_H + diff --git a/src/covermanager/coverfromurldialog.cpp b/src/covermanager/coverfromurldialog.cpp new file mode 100644 index 00000000..4fbce2fa --- /dev/null +++ b/src/covermanager/coverfromurldialog.cpp @@ -0,0 +1,91 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "coverfromurldialog.h" +#include "ui_coverfromurldialog.h" + +#include "core/network.h" +#include "covermanager/albumcoverloader.h" + +CoverFromURLDialog::CoverFromURLDialog(QWidget *parent) : QDialog(parent), ui_(new Ui_CoverFromURLDialog), network_(new NetworkAccessManager(this)) { + + ui_->setupUi(this); + ui_->busy->hide(); +} + +CoverFromURLDialog::~CoverFromURLDialog() { + delete ui_; +} + +QImage CoverFromURLDialog::Exec() { + // reset state + ui_->url->setText("");; + last_image_ = QImage(); + + QClipboard *clipboard = QApplication::clipboard(); + ui_->url->setText(clipboard->text()); + + exec(); + return last_image_; +} + +void CoverFromURLDialog::accept() { + + ui_->busy->show(); + + QNetworkRequest network_request = QNetworkRequest(QUrl::fromUserInput(ui_->url->text())); + + QNetworkReply *reply = network_->get(network_request); + connect(reply, SIGNAL(finished()), SLOT(LoadCoverFromURLFinished())); +} + +void CoverFromURLDialog::LoadCoverFromURLFinished() { + + ui_->busy->hide(); + + QNetworkReply *reply = qobject_cast(sender()); + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + QMessageBox::information(this, tr("Fetching cover error"), tr("The site you requested does not exist!")); + return; + } + + QImage image; + image.loadFromData(reply->readAll()); + + if (!image.isNull()) { + last_image_ = image; + QDialog::accept(); + } + else { + QMessageBox::information(this, tr("Fetching cover error"), tr("The site you requested is not an image!")); + } +} + diff --git a/src/covermanager/coverfromurldialog.h b/src/covermanager/coverfromurldialog.h new file mode 100644 index 00000000..ef0985e5 --- /dev/null +++ b/src/covermanager/coverfromurldialog.h @@ -0,0 +1,57 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COVERFROMURLDIALOG_H +#define COVERFROMURLDIALOG_H + +#include "config.h" + +#include +#include + +class NetworkAccessManager; +class Song; +class Ui_CoverFromURLDialog; + +// Controller for a dialog which fetches covers from the given URL. +class CoverFromURLDialog : public QDialog { + Q_OBJECT + + public: + CoverFromURLDialog(QWidget *parent = nullptr); + ~CoverFromURLDialog(); + + // Opens the dialog. This returns an image found at the URL chosen by user + // or null image if the dialog got rejected. + QImage Exec(); + + private slots: + void accept(); + void LoadCoverFromURLFinished(); + + private: + Ui_CoverFromURLDialog *ui_; + + NetworkAccessManager *network_; + QImage last_image_; +}; + +#endif // COVERFROMURLDIALOG_H + diff --git a/src/covermanager/coverfromurldialog.ui b/src/covermanager/coverfromurldialog.ui new file mode 100644 index 00000000..31c34e29 --- /dev/null +++ b/src/covermanager/coverfromurldialog.ui @@ -0,0 +1,117 @@ + + + CoverFromURLDialog + + + + 0 + 0 + 407 + 126 + + + + Load cover from URL + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + + + + + + + 0 + 0 + + + + Enter a URL to download a cover from the Internet: + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 3 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + BusyIndicator + QWidget +
widgets/busyindicator.h
+
+
+ + + + + + buttonBox + accepted() + CoverFromURLDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CoverFromURLDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/covermanager/coverprovider.cpp b/src/covermanager/coverprovider.cpp new file mode 100644 index 00000000..49bbf0b9 --- /dev/null +++ b/src/covermanager/coverprovider.cpp @@ -0,0 +1,26 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "coverprovider.h" + +CoverProvider::CoverProvider(const QString &name, QObject *parent) + : QObject(parent), name_(name) {} diff --git a/src/covermanager/coverprovider.h b/src/covermanager/coverprovider.h new file mode 100644 index 00000000..6c1fe3ca --- /dev/null +++ b/src/covermanager/coverprovider.h @@ -0,0 +1,61 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COVERPROVIDER_H +#define COVERPROVIDER_H + +#include "config.h" + +#include + +#include "albumcoverfetcher.h" +#include "coverproviders.h" + +#include + +class QNetworkReply; + +// Each implementation of this interface downloads covers from one online +// service. There are no limitations on what this service might be - last.fm, +// Amazon, Google Images - you name it. +class CoverProvider : public QObject { + Q_OBJECT + +public: + explicit CoverProvider(const QString& name, QObject* parent); + + // A name (very short description) of this provider, like "last.fm". + QString name() const { return name_; } + + // Starts searching for covers matching the given query text. Returns true + // if the query has been started, or false if an error occurred. The provider + // should remember the ID and emit it along with the result when it finishes. + virtual bool StartSearch(const QString &artist, const QString &album, int id) = 0; + + virtual void CancelSearch(int id) {} + +signals: + void SearchFinished(int id, const QList& results); + +private: + QString name_; +}; + +#endif // COVERPROVIDER_H diff --git a/src/covermanager/coverproviders.cpp b/src/covermanager/coverproviders.cpp new file mode 100644 index 00000000..e177a3e1 --- /dev/null +++ b/src/covermanager/coverproviders.cpp @@ -0,0 +1,72 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "coverprovider.h" +#include "coverproviders.h" + +#include "core/logging.h" + +CoverProviders::CoverProviders(QObject *parent) : QObject(parent) {} + +void CoverProviders::AddProvider(CoverProvider *provider) { + + { + QMutexLocker locker(&mutex_); + cover_providers_.insert(provider, provider->name()); + connect(provider, SIGNAL(destroyed()), SLOT(ProviderDestroyed())); + } + + qLog(Debug) << "Registered cover provider" << provider->name(); + +} + +void CoverProviders::RemoveProvider(CoverProvider *provider) { + + if (!provider) return; + + // It's not safe to dereference provider at this pointbecause it might have + // already been destroyed. + + QString name; + + { + QMutexLocker locker(&mutex_); + name = cover_providers_.take(provider); + } + + if (name.isNull()) { + qLog(Debug) << "Tried to remove a cover provider that was not registered"; + } + else { + qLog(Debug) << "Unregistered cover provider" << name; + } + +} + +void CoverProviders::ProviderDestroyed() { + + CoverProvider *provider = static_cast(sender()); + RemoveProvider(provider); + +} + +int CoverProviders::NextId() { return next_id_.fetchAndAddRelaxed(1); } diff --git a/src/covermanager/coverproviders.h b/src/covermanager/coverproviders.h new file mode 100644 index 00000000..419b5e3f --- /dev/null +++ b/src/covermanager/coverproviders.h @@ -0,0 +1,67 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COVERPROVIDERS_H +#define COVERPROVIDERS_H + +#include "config.h" + +#include +#include +#include + +class AlbumCoverFetcherSearch; +class CoverProvider; + +// This is a repository for cover providers. +// Providers are automatically unregistered from the repository when they are +// deleted. The class is thread safe. +class CoverProviders : public QObject { + Q_OBJECT + + public: + explicit CoverProviders(QObject *parent = nullptr); + + // Lets a cover provider register itself in the repository. + void AddProvider(CoverProvider *provider); + void RemoveProvider(CoverProvider *provider); + + // Returns a list of cover providers + QList List() const { return cover_providers_.keys(); } + + // Returns true if this repository has at least one registered provider. + bool HasAnyProviders() const { return !cover_providers_.isEmpty(); } + + int NextId(); + + private slots: + void ProviderDestroyed(); + + private: + Q_DISABLE_COPY(CoverProviders); + + QMap cover_providers_; + QMutex mutex_; + + QAtomicInt next_id_; +}; + +#endif // COVERPROVIDERS_H + diff --git a/src/covermanager/coversearchstatistics.cpp b/src/covermanager/coversearchstatistics.cpp new file mode 100644 index 00000000..3994b2a5 --- /dev/null +++ b/src/covermanager/coversearchstatistics.cpp @@ -0,0 +1,64 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "coversearchstatistics.h" + +CoverSearchStatistics::CoverSearchStatistics() + : network_requests_made_(0), + bytes_transferred_(0), + chosen_images_(0), + missing_images_(0), + chosen_width_(0), + chosen_height_(0) {} + +CoverSearchStatistics &CoverSearchStatistics::operator +=(const CoverSearchStatistics &other) { + + network_requests_made_ += other.network_requests_made_; + bytes_transferred_ += other.bytes_transferred_; + + for (const QString& key : other.chosen_images_by_provider_.keys()) { + chosen_images_by_provider_[key] += other.chosen_images_by_provider_[key]; + } + for (const QString& key : other.total_images_by_provider_.keys()) { + total_images_by_provider_[key] += other.total_images_by_provider_[key]; + } + + chosen_images_ += other.chosen_images_; + missing_images_ += other.missing_images_; + + chosen_width_ += other.chosen_width_; + chosen_height_ += other.chosen_height_; + + return *this; + +} + +QString CoverSearchStatistics::AverageDimensions() const { + + if (chosen_images_ == 0) { + return "0x0"; + } + + return QString::number(chosen_width_ / chosen_images_) + "x" + QString::number(chosen_height_ / chosen_images_); + +} + diff --git a/src/covermanager/coversearchstatistics.h b/src/covermanager/coversearchstatistics.h new file mode 100644 index 00000000..35a771f0 --- /dev/null +++ b/src/covermanager/coversearchstatistics.h @@ -0,0 +1,50 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COVERSEARCHSTATISTICS_H +#define COVERSEARCHSTATISTICS_H + +#include "config.h" + +#include +#include + +struct CoverSearchStatistics { + + CoverSearchStatistics(); + + CoverSearchStatistics& operator +=(const CoverSearchStatistics &other); + + quint64 network_requests_made_; + quint64 bytes_transferred_; + QMap total_images_by_provider_; + QMap chosen_images_by_provider_; + + quint64 chosen_images_; + quint64 missing_images_; + + quint64 chosen_width_; + quint64 chosen_height_; + + QString AverageDimensions() const; + +}; + +#endif // COVERSEARCHSTATISTICS_H diff --git a/src/covermanager/coversearchstatisticsdialog.cpp b/src/covermanager/coversearchstatisticsdialog.cpp new file mode 100644 index 00000000..9cf2403b --- /dev/null +++ b/src/covermanager/coversearchstatisticsdialog.cpp @@ -0,0 +1,97 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "coversearchstatisticsdialog.h" +#include "core/utilities.h" +#include "ui_coversearchstatisticsdialog.h" + +CoverSearchStatisticsDialog::CoverSearchStatisticsDialog(QWidget *parent) + : QDialog(parent), ui_(new Ui_CoverSearchStatisticsDialog) { + + ui_->setupUi(this); + details_layout_ = new QVBoxLayout(ui_->details); + details_layout_->setSpacing(0); + + setStyleSheet( + "#details {" + " background-color: palette(base);" + "}" + "#details QLabel[type=\"label\"] {" + " border: 2px solid transparent;" + " border-right: 2px solid palette(midlight);" + " margin-right: 10px;" + "}" + "#details QLabel[type=\"value\"] {" + " font-weight: bold;" + " max-width: 100px;" + "}"); +} + +CoverSearchStatisticsDialog::~CoverSearchStatisticsDialog() { delete ui_; } + +void CoverSearchStatisticsDialog::Show(const CoverSearchStatistics &statistics) { + + QStringList providers(statistics.total_images_by_provider_.keys()); + qSort(providers); + + ui_->summary->setText(tr("Got %1 covers out of %2 (%3 failed)") + .arg(statistics.chosen_images_) + .arg(statistics.chosen_images_ + statistics.missing_images_) + .arg(statistics.missing_images_)); + + for (const QString& provider : providers) { + AddLine(tr("Covers from %1").arg(provider), QString::number(statistics.chosen_images_by_provider_[provider])); + } + + if (!providers.isEmpty()) { + AddSpacer(); + } + + AddLine(tr("Total network requests made"), QString::number(statistics.network_requests_made_)); + AddLine(tr("Average image size"), statistics.AverageDimensions()); + AddLine(tr("Total bytes transferred"), statistics.bytes_transferred_ ? Utilities::PrettySize(statistics.bytes_transferred_) : "0 bytes"); + + details_layout_->addStretch(); + + show(); + +} + +void CoverSearchStatisticsDialog::AddLine(const QString &label, const QString &value) { + + QLabel *label1 = new QLabel(label); + QLabel *label2 = new QLabel(value); + + label1->setProperty("type", "label"); + label2->setProperty("type", "value"); + + QHBoxLayout* layout = new QHBoxLayout; + layout->addWidget(label1); + layout->addWidget(label2); + details_layout_->addLayout(layout); + +} + +void CoverSearchStatisticsDialog::AddSpacer() { + details_layout_->addSpacing(20); +} + diff --git a/src/covermanager/coversearchstatisticsdialog.h b/src/covermanager/coversearchstatisticsdialog.h new file mode 100644 index 00000000..75e38fa5 --- /dev/null +++ b/src/covermanager/coversearchstatisticsdialog.h @@ -0,0 +1,53 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef COVERSEARCHSTATISTICSDIALOG_H +#define COVERSEARCHSTATISTICSDIALOG_H + +#include "config.h" + +#include + +#include "coversearchstatistics.h" + +class Ui_CoverSearchStatisticsDialog; + +class QVBoxLayout; + +class CoverSearchStatisticsDialog : public QDialog { + Q_OBJECT + + public: + explicit CoverSearchStatisticsDialog(QWidget *parent = nullptr); + ~CoverSearchStatisticsDialog(); + + void Show(const CoverSearchStatistics& statistics); + + private: + void AddLine(const QString &label, const QString &value); + void AddSpacer(); + + private: + Ui_CoverSearchStatisticsDialog *ui_; + QVBoxLayout *details_layout_; +}; + +#endif // COVERSEARCHSTATISTICSDIALOG_H + diff --git a/src/covermanager/coversearchstatisticsdialog.ui b/src/covermanager/coversearchstatisticsdialog.ui new file mode 100644 index 00000000..6f8a08d5 --- /dev/null +++ b/src/covermanager/coversearchstatisticsdialog.ui @@ -0,0 +1,87 @@ + + + CoverSearchStatisticsDialog + + + + 0 + 0 + 425 + 214 + + + + Fetch completed + + + + + + Got %1 covers out of %2 (%3 failed) + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttonBox + accepted() + CoverSearchStatisticsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CoverSearchStatisticsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/covermanager/currentartloader.cpp b/src/covermanager/currentartloader.cpp new file mode 100644 index 00000000..12ea4086 --- /dev/null +++ b/src/covermanager/currentartloader.cpp @@ -0,0 +1,87 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include + +#include "currentartloader.h" +#include "core/application.h" +#include "core/iconloader.h" +#include "covermanager/albumcoverloader.h" +#include "playlist/playlistmanager.h" + +CurrentArtLoader::CurrentArtLoader(Application *app, QObject *parent) + : QObject(parent), + app_(app), + temp_file_pattern_(QDir::tempPath() + "/strawberry-art-XXXXXX.jpg"), + id_(0) +{ + options_.scale_output_image_ = false; + options_.pad_output_image_ = false; + //QIcon nocover = IconLoader::Load("nocover"); + //options_.default_output_image_ = nocover.pixmap(nocover.availableSizes().last()).toImage(); + options_.default_output_image_ = QImage(":/pictures/noalbumart.png"); + + connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(TempArtLoaded(quint64, QImage))); + + connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(LoadArt(Song))); +} + +CurrentArtLoader::~CurrentArtLoader() {} + +void CurrentArtLoader::LoadArt(const Song &song) { + last_song_ = song; + id_ = app_->album_cover_loader()->LoadImageAsync(options_, last_song_); +} + +void CurrentArtLoader::TempArtLoaded(quint64 id, const QImage &image) { + + if (id != id_) return; + id_ = 0; + + QString uri; + QString thumbnail_uri; + QImage thumbnail; + + if (!image.isNull()) { + temp_art_.reset(new QTemporaryFile(temp_file_pattern_)); + temp_art_->setAutoRemove(true); + temp_art_->open(); + image.save(temp_art_->fileName(), "JPEG"); + + // Scale the image down to make a thumbnail. It's a bit crap doing it here + // since it's the GUI thread, but the alternative is hard. + temp_art_thumbnail_.reset(new QTemporaryFile(temp_file_pattern_)); + temp_art_thumbnail_->open(); + temp_art_thumbnail_->setAutoRemove(true); + thumbnail = image.scaledToHeight(120, Qt::SmoothTransformation); + thumbnail.save(temp_art_thumbnail_->fileName(), "JPEG"); + + uri = "file://" + temp_art_->fileName(); + thumbnail_uri = "file://" + temp_art_thumbnail_->fileName(); + } + + emit ArtLoaded(last_song_, uri, image); + emit ThumbnailLoaded(last_song_, thumbnail_uri, thumbnail); + +} diff --git a/src/covermanager/currentartloader.h b/src/covermanager/currentartloader.h new file mode 100644 index 00000000..8eb65931 --- /dev/null +++ b/src/covermanager/currentartloader.h @@ -0,0 +1,71 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef CURRENTARTLOADER_H +#define CURRENTARTLOADER_H + +#include "config.h" + +#include + +#include + +#include "core/song.h" +#include "covermanager/albumcoverloaderoptions.h" + +class Application; + +class QImage; +class QTemporaryFile; + +class CurrentArtLoader : public QObject { + Q_OBJECT + + public: + explicit CurrentArtLoader(Application *app, QObject *parent = nullptr); + ~CurrentArtLoader(); + + const AlbumCoverLoaderOptions &options() const { return options_; } + const Song &last_song() const { return last_song_; } + + public slots: + void LoadArt(const Song &song); + +signals: + void ArtLoaded(const Song &song, const QString &uri, const QImage &image); + void ThumbnailLoaded(const Song &song, const QString &uri, const QImage &image); + + private slots: + void TempArtLoaded(quint64 id, const QImage &image); + + private: + Application *app_; + AlbumCoverLoaderOptions options_; + + QString temp_file_pattern_; + + std::unique_ptr temp_art_; + std::unique_ptr temp_art_thumbnail_; + quint64 id_; + + Song last_song_; +}; + +#endif // CURRENTARTLOADER_H diff --git a/src/covermanager/discogscoverprovider.cpp b/src/covermanager/discogscoverprovider.cpp new file mode 100644 index 00000000..f94375db --- /dev/null +++ b/src/covermanager/discogscoverprovider.cpp @@ -0,0 +1,216 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * Copyright 2012, Martin Björklund + * Copyright 2016, Jonas Kvinge + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +//#include +#include + +#include +#include +#include +#include +#include + +#include "discogscoverprovider.h" + +#include "core/closure.h" +#include "core/logging.h" +#include "core/network.h" +#include "core/utilities.h" + +const char *DiscogsCoverProvider::kUrl = "https://api.discogs.com/database/search"; + +const char *DiscogsCoverProvider::kAccessKeyB64 = "dGh6ZnljUGJlZ1NEeXBuSFFxSVk="; +const char *DiscogsCoverProvider::kSecretAccessKeyB64 = "ZkFIcmlaSER4aHhRSlF2U3d0bm5ZVmdxeXFLWUl0UXI="; + +DiscogsCoverProvider::DiscogsCoverProvider(QObject *parent) : CoverProvider("Discogs", parent), network_(new NetworkAccessManager(this)) {} + +bool DiscogsCoverProvider::StartSearch(const QString &artist, const QString &album, int id) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + DiscogsCoverSearchContext *ctx = new DiscogsCoverSearchContext; + ctx->id = id; + ctx->artist = artist; + ctx->album = album; + ctx->state = DiscogsCoverSearchContext::State_Init; + pending_requests_.insert(id, ctx); + SendSearchRequest(ctx); + + return true; + +} + +void DiscogsCoverProvider::SendSearchRequest(DiscogsCoverSearchContext *ctx) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + typedef QPair Arg; + typedef QList ArgList; + + typedef QPair EncodedArg; + typedef QList EncodedArgList; + + QString type; + + switch (ctx->state) { + case DiscogsCoverSearchContext::State_Init: + type = "master"; + ctx->state = DiscogsCoverSearchContext::State_MastersRequested; + break; + case DiscogsCoverSearchContext::State_MastersRequested: + type = "release"; + ctx->state = DiscogsCoverSearchContext::State_ReleasesRequested; + break; + default: + EndSearch(ctx); + return; + } + + ArgList args = ArgList() + << Arg("key", QByteArray::fromBase64(kAccessKeyB64)) + << Arg("secret", QByteArray::fromBase64(kSecretAccessKeyB64)); + + if (!ctx->artist.isEmpty()) { + args.append(Arg("artist", ctx->artist.toLower())); + } + if (!ctx->album.isEmpty()) { + args.append(Arg("release_title", ctx->album.toLower())); + } + args.append(Arg("type", type)); + + QUrlQuery url_query; + QUrl url(kUrl); + QStringList query_items; + + // Encode the arguments + for (const Arg &arg : args) { + EncodedArg encoded_arg(QUrl::toPercentEncoding(arg.first), QUrl::toPercentEncoding(arg.second)); + query_items << QString(encoded_arg.first + "=" + encoded_arg.second); + url_query.addQueryItem(encoded_arg.first, encoded_arg.second); + } + + // Sign the request + const QByteArray data_to_sign = QString("GET\n%1\n%2\n%3").arg(url.host(), url.path(), query_items.join("&")).toLatin1(); + //const QByteArray signature(Utilities::HmacSha256(kSecretAccessKey, data_to_sign)); + const QByteArray signature(Utilities::HmacSha256(QByteArray::fromBase64(kSecretAccessKeyB64), data_to_sign)); + + // Add the signature to the request + url_query.addQueryItem("Signature", QUrl::toPercentEncoding(signature.toBase64())); + + url.setQuery(url_query); + QNetworkReply *reply = network_->get(QNetworkRequest(url)); + + NewClosure(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(QueryError(QNetworkReply::NetworkError, QNetworkReply*, int)), reply, ctx->id); + NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleSearchReply(QNetworkReply*, int)), reply, ctx->id); + + return true; + +} + +void DiscogsCoverProvider::HandleSearchReply(QNetworkReply *reply, int id) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + //QString text(reply->readAll()); + //qLog(Debug) << text; + + reply->deleteLater(); + + DiscogsCoverSearchContext *ctx; + if (!pending_requests_.contains(id)) { + // the request was cancelled while we were waiting for the reply + qLog(Debug) << "Discogs: got reply for cancelled request" << id; + return; + } + ctx = pending_requests_.value(id); + + QJson::Parser parser; + bool ok; + bool found = false; + QVariantMap reply_map = parser.parse(reply, &ok).toMap(); + + if (!ok || !reply_map.contains("results")) { + // this is an error; either parse error or bad response from the server + EndSearch(ctx); + return; + } + + QVariantList results = reply_map["results"].toList(); + + for (const QVariant &result : results) { + QVariantMap result_map = result.toMap(); + // In order to use less round-trips, we cheat here. Instead of + // following the "resource_url", and then scan all images in the + // resource, we go directly to the largest primary image by + // constructing the primary image's url from the thmub's url. + if (result_map.contains("thumb")) { + CoverSearchResult cover_result; + cover_result.image_url = QUrl(result_map["thumb"].toString().replace("R-90-", "R-")); + if (result_map.contains("title")) { + cover_result.description = result_map["title"].toString(); + } + ctx->results.append(cover_result); + found = true; + } + } + if (found) { + EndSearch(ctx); + return; + } + + // otherwise, no results + switch (ctx->state) { + case DiscogsCoverSearchContext::State_MastersRequested: + // search again, this time for releases + SendSearchRequest(ctx); + break; + default: + EndSearch(ctx); + break; + } + +} + +void DiscogsCoverProvider::CancelSearch(int id) { + + //qLog(Debug) << __PRETTY_FUNCTION__ << id; + + delete pending_requests_.take(id); +} + +void DiscogsCoverProvider::QueryError(QNetworkReply::NetworkError error, QNetworkReply *reply, int id) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + +} + +void DiscogsCoverProvider::EndSearch(DiscogsCoverSearchContext *ctx) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + (void)pending_requests_.remove(ctx->id); + emit SearchFinished(ctx->id, ctx->results); + delete ctx; + +} diff --git a/src/covermanager/discogscoverprovider.h b/src/covermanager/discogscoverprovider.h new file mode 100644 index 00000000..7dd9f81b --- /dev/null +++ b/src/covermanager/discogscoverprovider.h @@ -0,0 +1,90 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * Copyright 2012, Martin Björklund + * Copyright 2016, Jonas Kvinge + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef DISCOGSCOVERPROVIDER_H +#define DISCOGSCOVERPROVIDER_H + +#include "config.h" + +#include +#include + +#include "coverprovider.h" + +class QNetworkAccessManager; + +// This struct represents a single search-for-cover request. It identifies and describes the request. +struct DiscogsCoverSearchContext { + enum State { State_Init, State_MastersRequested, State_ReleasesRequested }; + + // the unique request identifier + int id; + + // the search query + QString artist; + QString album; + + State state; + + CoverSearchResults results; +}; +Q_DECLARE_METATYPE(DiscogsCoverSearchContext) + +class DiscogsCoverProvider : public CoverProvider { + Q_OBJECT + + public: + explicit DiscogsCoverProvider(QObject *parent = nullptr); + + static const char *kUrl; + + static const char *kRequestTokenURL; + static const char *kAuthorizeURL; + static const char *kAccessTokenURL; + + static const char *kAccessKey; + static const char *kSecretAccessKey; + + static const char *kAccessKeyB64; + static const char *kSecretAccessKeyB64; + + bool StartSearch(const QString &artist, const QString &album, int id); + void CancelSearch(int id); + + private slots: + void QueryError(QNetworkReply::NetworkError error, QNetworkReply *reply, int id); + void HandleSearchReply(QNetworkReply* reply, int id); + + private: + QNetworkAccessManager *network_; + QHash pending_requests_; + + void SendSearchRequest(DiscogsCoverSearchContext *ctx); + void ReadItem(QXmlStreamReader *reader, CoverSearchResults *results); + void ReadLargeImage(QXmlStreamReader *reader, CoverSearchResults *results); + void QueryFinished(QNetworkReply *reply, int id); + void EndSearch(DiscogsCoverSearchContext *ctx); + +}; + +#endif // DISCOGSCOVERPROVIDER_H + diff --git a/src/covermanager/lastfmcompat.cpp b/src/covermanager/lastfmcompat.cpp new file mode 100644 index 00000000..c37698d4 --- /dev/null +++ b/src/covermanager/lastfmcompat.cpp @@ -0,0 +1,121 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "lastfmcompat.h" +#include "core/logging.h" + +namespace lastfm { +namespace compat { + +#ifdef HAVE_LIBLASTFM1 + +XmlQuery EmptyXmlQuery() { return XmlQuery(); } + +bool ParseQuery(const QByteArray &data, XmlQuery *query, bool *connection_problems) { + + const bool ret = query->parse(data); + + if (connection_problems) { + *connection_problems = !ret && query->parseError().enumValue() == lastfm::ws::MalformedResponse; + } + + return ret; +} + +bool ParseUserList(QNetworkReply *reply, QList *users) { + + lastfm::XmlQuery lfm; + if (!lfm.parse(reply->readAll())) { + return false; + } + + *users = lastfm::UserList(lfm).users(); + return true; + +} + +#else // HAVE_LIBLASTFM1 + +XmlQuery EmptyXmlQuery() { + + QByteArray dummy; + return XmlQuery(dummy); + +} + +bool ParseQuery(const QByteArray &data, XmlQuery *query, bool *connection_problems) { + + try { + *query = lastfm::XmlQuery(data); +#ifdef Q_OS_WIN32 + if (lastfm::ws::last_parse_error != lastfm::ws::NoError) { + return false; + } +#endif // Q_OS_WIN32 + } + catch (lastfm::ws::ParseError e) { + qLog(Error) << "Last.fm parse error: " << e.enumValue(); + if (connection_problems) { + *connection_problems = e.enumValue() == lastfm::ws::MalformedResponse; + } + return false; + } + catch (std::runtime_error &e) { + qLog(Error) << __FUNCTION__ << e.what(); + return false; + } + + if (connection_problems) { + *connection_problems = false; + } + + // Check for app errors. + if (QDomElement(*query).attribute("status") == "failed") { + return false; + } + + return true; + +} + +bool ParseUserList(QNetworkReply *reply, QList *users) { + + try { + *users = lastfm::User::list(reply); +#ifdef Q_OS_WIN32 + if (lastfm::ws::last_parse_error != lastfm::ws::NoError) { + return false; + } +#endif // Q_OS_WIN32 + } + catch (std::runtime_error &e) { + qLog(Error) << __FUNCTION__ << e.what(); + return false; + } + return true; + +} + +#endif // HAVE_LIBLASTFM1 +} +} + diff --git a/src/covermanager/lastfmcompat.h b/src/covermanager/lastfmcompat.h new file mode 100644 index 00000000..ef050a6d --- /dev/null +++ b/src/covermanager/lastfmcompat.h @@ -0,0 +1,54 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef LASTFMCOMPAT_H +#define LASTFMCOMPAT_H + +#include "config.h" + +#ifdef HAVE_LIBLASTFM1 +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif + +namespace lastfm { +namespace compat { + +lastfm::XmlQuery EmptyXmlQuery(); +bool ParseQuery(const QByteArray &data, lastfm::XmlQuery *query, bool *connection_problems = nullptr); +bool ParseUserList(QNetworkReply *reply, QList *users); + +#ifdef HAVE_LIBLASTFM1 +typedef lastfm::User AuthenticatedUser; +#else +typedef lastfm::AuthenticatedUser AuthenticatedUser; +#endif +} +} + +#endif // LASTFMCOMPAT_H + diff --git a/src/covermanager/lastfmcoverprovider.cpp b/src/covermanager/lastfmcoverprovider.cpp new file mode 100644 index 00000000..4f33755c --- /dev/null +++ b/src/covermanager/lastfmcoverprovider.cpp @@ -0,0 +1,91 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +/* + +Application name Strawberry Music Player +API key 211990b4c96782c05d1536e7219eb56e +Shared secret 80fd738f49596e9709b1bf9319c444a8 +Registered to jonaskvinge + +*/ + +#include "config.h" + +#include +#include + +#include "lastfmcoverprovider.h" +#include "lastfmcompat.h" + +#include "coverprovider.h" +#include "albumcoverfetcher.h" + +#include "core/closure.h" +#include "core/network.h" +#include "core/logging.h" + +const char *LastFmCoverProvider::kApiKey = "211990b4c96782c05d1536e7219eb56e"; +const char *LastFmCoverProvider::kSecret = "80fd738f49596e9709b1bf9319c444a8"; + +LastFmCoverProvider::LastFmCoverProvider(QObject *parent) : CoverProvider("last.fm", parent), network_(new NetworkAccessManager(this)) { + lastfm::ws::ApiKey = kApiKey; + lastfm::ws::SharedSecret = kSecret; + lastfm::setNetworkAccessManager(network_); +} + +bool LastFmCoverProvider::StartSearch(const QString &artist, const QString &album, int id) { + + //qLog(Debug) << "LastFmCoverProvider artist:" << artist << "album:" << album; + + QMap params; + params["method"] = "album.search"; + params["album"] = album + " " + artist; + + QNetworkReply* reply = lastfm::ws::post(params); + NewClosure(reply, SIGNAL(finished()), this, SLOT(QueryFinished(QNetworkReply*, int)), reply, id); + + return true; +} + +void LastFmCoverProvider::QueryFinished(QNetworkReply *reply, int id) { + + reply->deleteLater(); + + CoverSearchResults results; + + lastfm::XmlQuery query(lastfm::compat::EmptyXmlQuery()); + if (lastfm::compat::ParseQuery(reply->readAll(), &query)) { + // parse the list of search results + QList elements = query["results"]["albummatches"].children("album"); + + for (const lastfm::XmlQuery& element : elements) { + CoverSearchResult result; + result.description = element["artist"].text() + " - " + element["name"].text(); + result.image_url = QUrl(element["image size=extralarge"].text()); + results << result; + } + } + else { + // Drop through and emit an empty list of results. + } + + emit SearchFinished(id, results); +} diff --git a/src/covermanager/lastfmcoverprovider.h b/src/covermanager/lastfmcoverprovider.h new file mode 100644 index 00000000..b51f990e --- /dev/null +++ b/src/covermanager/lastfmcoverprovider.h @@ -0,0 +1,59 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef LASTFMCOVERPROVIDER_H +#define LASTFMCOVERPROVIDER_H + +#include "config.h" + +#include +#include + +#include "albumcoverfetcher.h" +#include "coverprovider.h" + +#include +#include + +class QNetworkAccessManager; +class QNetworkReply; + +// A built-in cover provider which fetches covers from last.fm. +class LastFmCoverProvider : public CoverProvider { + Q_OBJECT + +public: + explicit LastFmCoverProvider(QObject *parent = nullptr); + + bool StartSearch(const QString &artist, const QString &album, int id); + + static const char *kApiKey; + static const char *kSecret; + +private slots: + void QueryFinished(QNetworkReply *reply, int id); + +private: + QNetworkAccessManager *network_; + QMap pending_queries_; +}; + +#endif // LASTFMCOVERPROVIDER_H + diff --git a/src/covermanager/musicbrainzcoverprovider.cpp b/src/covermanager/musicbrainzcoverprovider.cpp new file mode 100644 index 00000000..a4b02f74 --- /dev/null +++ b/src/covermanager/musicbrainzcoverprovider.cpp @@ -0,0 +1,119 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include + +#include +#include + +#include "core/closure.h" +#include "core/network.h" + +#include "musicbrainzcoverprovider.h" + +using std::mem_fun; + +namespace { + +static const char *kReleaseSearchUrl = "https://musicbrainz.org/ws/2/release/"; +static const char *kAlbumCoverUrl = "https://coverartarchive.org/release/%1/front"; +} // namespace + +MusicbrainzCoverProvider::MusicbrainzCoverProvider(QObject *parent): CoverProvider("MusicBrainz", parent), network_(new NetworkAccessManager(this)) {} + +bool MusicbrainzCoverProvider::StartSearch(const QString &artist, const QString &album, int id) { + + // Find release information. + QUrl url(kReleaseSearchUrl); + QString query = QString("release:\"%1\" AND artist:\"%2\"").arg(album.trimmed().replace('"', "\\\"")).arg(artist.trimmed().replace('"', "\\\"")); + QUrlQuery url_query; + url_query.addQueryItem("query", query); + url_query.addQueryItem("limit", "5"); + url.setQuery(url_query); + QNetworkRequest request(url); + + QNetworkReply *reply = network_->get(request); + NewClosure(reply, SIGNAL(finished()), this, SLOT(ReleaseSearchFinished(QNetworkReply*, int)), reply, id); + + cover_names_[id] = QString("%1 - %2").arg(artist, album); + return true; + +} + +void MusicbrainzCoverProvider::ReleaseSearchFinished(QNetworkReply *reply, int id) { + + reply->deleteLater(); + + QList releases; + + QXmlStreamReader reader(reply); + while (!reader.atEnd()) { + QXmlStreamReader::TokenType type = reader.readNext(); + if (type == QXmlStreamReader::StartElement && reader.name() == "release") { + QStringRef release_id = reader.attributes().value("id"); + if (!release_id.isEmpty()) { + releases.append(release_id.toString()); + } + } + } + + for (const QString& release_id : releases) { + QUrl url(QString(kAlbumCoverUrl).arg(release_id)); + QNetworkReply *reply = network_->head(QNetworkRequest(url)); + image_checks_.insert(id, reply); + NewClosure(reply, SIGNAL(finished()), this, SLOT(ImageCheckFinished(int)), id); + } +} + +void MusicbrainzCoverProvider::ImageCheckFinished(int id) { + + QList replies = image_checks_.values(id); + int finished_count = std::count_if(replies.constBegin(), replies.constEnd(), mem_fun(&QNetworkReply::isFinished)); + if (finished_count == replies.size()) { + QString cover_name = cover_names_.take(id); + QList results; + for (QNetworkReply* reply : replies) { + reply->deleteLater(); + if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() < 400) { + CoverSearchResult result; + result.description = cover_name; + result.image_url = reply->url(); + results.append(result); + } + } + image_checks_.remove(id); + emit SearchFinished(id, results); + } + +} + +void MusicbrainzCoverProvider::CancelSearch(int id) { + + QList replies = image_checks_.values(id); + for (QNetworkReply* reply : replies) { + reply->abort(); + reply->deleteLater(); + } + image_checks_.remove(id); + +} diff --git a/src/covermanager/musicbrainzcoverprovider.h b/src/covermanager/musicbrainzcoverprovider.h new file mode 100644 index 00000000..6efd4fb9 --- /dev/null +++ b/src/covermanager/musicbrainzcoverprovider.h @@ -0,0 +1,53 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MUSICBRAINZCOVERPROVIDER_H +#define MUSICBRAINZCOVERPROVIDER_H + +#include "config.h" + +#include + +#include "coverprovider.h" + +class QNetworkAccessManager; +class QNetworkReply; + +class MusicbrainzCoverProvider : public CoverProvider { + Q_OBJECT + public: + explicit MusicbrainzCoverProvider(QObject *parent = nullptr); + + // CoverProvider + virtual bool StartSearch(const QString &artist, const QString &album, int id); + virtual void CancelSearch(int id); + + private slots: + void ReleaseSearchFinished(QNetworkReply *reply, int id); + void ImageCheckFinished(int id); + + private: + QNetworkAccessManager *network_; + QMultiMap image_checks_; + QMap cover_names_; +}; + +#endif // MUSICBRAINZCOVERPROVIDER_H + diff --git a/src/dbus/metatypes.h b/src/dbus/metatypes.h new file mode 100644 index 00000000..2a09e698 --- /dev/null +++ b/src/dbus/metatypes.h @@ -0,0 +1,15 @@ +#ifndef DBUS_METATYPES_H +#define DBUS_METATYPES_H + +#include +#include + +Q_DECLARE_METATYPE(QList) + +typedef QMap InterfacesAndProperties; +typedef QMap ManagedObjectList; + +Q_DECLARE_METATYPE(InterfacesAndProperties) +Q_DECLARE_METATYPE(ManagedObjectList) + +#endif // DBUS_METATYPES_H_ diff --git a/src/dbus/org.freedesktop.Avahi.EntryGroup.xml b/src/dbus/org.freedesktop.Avahi.EntryGroup.xml new file mode 100644 index 00000000..ec7f1374 --- /dev/null +++ b/src/dbus/org.freedesktop.Avahi.EntryGroup.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/dbus/org.freedesktop.Avahi.Server.xml b/src/dbus/org.freedesktop.Avahi.Server.xml new file mode 100644 index 00000000..49493b3e --- /dev/null +++ b/src/dbus/org.freedesktop.Avahi.Server.xml @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/dbus/org.freedesktop.DBus.ObjectManager.xml b/src/dbus/org.freedesktop.DBus.ObjectManager.xml new file mode 100644 index 00000000..efc389dd --- /dev/null +++ b/src/dbus/org.freedesktop.DBus.ObjectManager.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/dbus/org.freedesktop.MediaPlayer.player.xml b/src/dbus/org.freedesktop.MediaPlayer.player.xml new file mode 100644 index 00000000..1523ec00 --- /dev/null +++ b/src/dbus/org.freedesktop.MediaPlayer.player.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/dbus/org.freedesktop.MediaPlayer.root.xml b/src/dbus/org.freedesktop.MediaPlayer.root.xml new file mode 100644 index 00000000..6b8976ed --- /dev/null +++ b/src/dbus/org.freedesktop.MediaPlayer.root.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/dbus/org.freedesktop.MediaPlayer.tracklist.xml b/src/dbus/org.freedesktop.MediaPlayer.tracklist.xml new file mode 100644 index 00000000..d2db72f3 --- /dev/null +++ b/src/dbus/org.freedesktop.MediaPlayer.tracklist.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/dbus/org.freedesktop.Notifications.xml b/src/dbus/org.freedesktop.Notifications.xml new file mode 100644 index 00000000..00e85ea5 --- /dev/null +++ b/src/dbus/org.freedesktop.Notifications.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/dbus/org.freedesktop.UDisks.Device.xml b/src/dbus/org.freedesktop.UDisks.Device.xml new file mode 100644 index 00000000..99d1c459 --- /dev/null +++ b/src/dbus/org.freedesktop.UDisks.Device.xml @@ -0,0 +1,2850 @@ + + + + + + + This interface provides information about a block device on + a UNIX-like system. In addition to just providing + information, methods can be invoked to perform operations on + the block device. Objects implementing this interface have + object paths prefixed with /devices/ + followed by a sanitized representation of the base name of + their native path. As the D-Bus specification greatly limits + what characters can be used in object paths, this doesn't + necessarily map one to one with the native basename; for + example the native + path /sys/devices/virtual/block/dm-0 will + be represented as /devices/dm_0. + + + Most methods on this interface take an array of strings + for options that can affect what the method does. Some of + these options are literal strings (such + as noatime) while some are encoded in the + form of a key/value pair (such + as label=). + + + A general note about properties: the set of values + returned can be expected to grow in the future as both + hardware and operating system capabilities evolve. Care + has been taken to namespace values so applications can + properly fall back (see e.g. + DriveMediaCompatibility) + and export both general and specific properties (such as + IdUsage + vs. + IdType). + In general an empty string in a property means not + set. Since the empty string is not a valid object path we + use the "/" to mean "not set" for object paths. + + + + + + + + + + + + + Cancels a job in progress. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.cancel-job-others + To cancel a job initiated by another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + + The scheme of the partition table to create. + + + + No options are currently supported. + + + + + + Creates a new partition table. The following partition table schemes are supported: + + + none + To zero out existing partition tables signatures. + + + mbr + + Use the + Master Boot Record + partitioning scheme. + + + + gpt + + Use the + GUID Partition Table + scheme. + + + + apm + + Use the + Apple Partition Map + partitioning scheme. + + + + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.change + If the operation is on a + non-system-internal + device + + + org.freedesktop.udisks.change-system-internal + If the operation is on a + system-internal + device + + + + + if the caller lacks the appropriate PolicyKit authorization + if the device or a partition on it are busy + if the operation failed + if the job was cancelled + + + + + + + + + + No options are currently supported. + + + + + + Deletes a partition, removing it from the enclosing partition table. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.change + If the operation is on a + non-system-internal + device + + + org.freedesktop.udisks.change-system-internal + If the operation is on a + system-internal + device + + + + + if the caller lacks the appropriate PolicyKit authorization + if the device is busy + if the operation failed + if the job was cancelled + + + + + + + + + + Where on the device to create the partition. + + + Size of the partitition to create. + + + + The type of the partition to create. Valid types depends + on the partitioning scheme used: + + + mbr + + For the + Master Boot Record + partitioning scheme, the + given type must be a string representation of an + integer (both decimal and hexadecimal encodings are + accepted) between 0 and 255 both inclusive. + + + + gpt + + Any valid GUID string. + + + + apm + + Any valid type for + Apple Partition Map + for example Apple_Unix_SVR2. + + + + + + + + The label to use for the partition. Leave blank if the + partition table scheme is mbr. + + + + + Flags to use for the partition. Valid flags depends on the + partitioning scheme used: + + + mbr + + Only the flag boot is valid. + + + + gpt + + Only the flag required is valid. + + + + apm + + The flags + allocated, + in_use, + boot, + allow_read, + allow_write, + boot_code_is_pic + are valid. + + + + + + + Currently unused. + + + + The file system to create in new partition. Leave + blank to skip creating a file system. See the + FilesystemCreate() method + for details. + + + + + Options to use for file system creation. See the + FilesystemCreate() method + for details. + + + + The object path of the newly added partition. + + + + + + Create a new partition and, optionally, create a file + system on it. The partition won't necessarily be created + at the exact location requested due to disk geometry + constraints. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.change + If the operation is on a + non-system-internal + device + + + org.freedesktop.udisks.change-system-internal + If the operation is on a + system-internal + device + + + + + if the caller lacks the appropriate PolicyKit authorization + if the device is busy + if the operation failed + if the job was cancelled + + + + + + + + + + + The type of the partition to create. See the type parameter of the + PartitionCreate() method + for details on valid types. + + + + + The label to use for the partition. See the label parameter of the + PartitionCreate() method + for details on valid labels. + + + + + Flags to use for the partition. See the flags parameter of the + PartitionCreate() method + for details on valid flags + + + + + + + Modifies meta data for a partition, such as type, label and flags. + TODO: Consider allowing changing offset and + size. Or maybe that should be a separate method. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.change + If the operation is on a + non-system-internal + device + + + org.freedesktop.udisks.change-system-internal + If the operation is on a + system-internal + device + + + + + if the caller lacks the appropriate PolicyKit authorization + if the enclosing partition table device is busy + if the operation failed + if the job was cancelled + + + + + + + + + + + The type of file system to + create. Pass empty to not create a file + system and just clear the areas of the device known to host + file system signatures. Use @TODO@ to get a list of file systems that can + be created. + + + + + To set the label on the file system use + the label=NAME option. Labels may not be + supported for all file systems and the allowed length of a + label may vary (see @TODO@). To create the file system on + an LUKS encrypted block device, pass + the luks_encrypt= option with the value + set to the passphrase to use. For file systems with the + concept of owners (e.g. + ext3), the options take_ownership_uid= and + take_ownership_gid= are supported and can be used to set the + initial owner of the created file system. + + + + + + + Create a file system on a device. If + the luks_encrypt= option is passed then an + LUKS encrypted block device will be created, then unlocked and + the file system will be created on the corresponding + cleartext device. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.change + If the operation is on a + non-system-internal + device + + + org.freedesktop.udisks.change-system-internal + If the operation is on a + system-internal + device + + + + + if the caller lacks the appropriate PolicyKit authorization + if the device is busy + if the operation failed + if the job was cancelled + if mkfs for this type is not available + + + + + + + + + + + New label for file system. + + + + + + + Changes the file system label. See the options parameter of + FilesystemCreate() + method for details of what valid labels are valid. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.change + If the operation is on a + non-system-internal + device + + + org.freedesktop.udisks.change-system-internal + If the operation is on a + system-internal + device + + + + + if the caller lacks the appropriate PolicyKit authorization + if the device is busy and changing the label requires an unmounted file system + if the operation failed + if the job was cancelled + if the label changing tool for this file system type is not available + + + + + + + + + + File system type to use. + + + + Mount Options. Valid mount options include mount options accepted by the native mount program. + The option auth_no_user_interaction can be used to avoid user interaction (e.g. authentication dialogs) when checking whether the caller is authorized. + + + Where the device was mounted. + + + + + + Mount the device. If the device is referenced in the + system-wide /etc/fstab file, the given + parameters are all ignored and the device will be + attempted to be mounted as the calling user. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.filesystem-mount + If the operation is on a + non-system-internal + device + + + org.freedesktop.udisks.filesystem-mount-system-internal + If the operation is on a + system-internal + device + + + + + if the caller lacks the appropriate PolicyKit authorization + if the device is busy + if the operation failed + if the job was cancelled + if an invalid or malformed mount option was given + if the kernel driver for this file system type is not available + + + + + + + + + + Unmount options. Valid options currently include only 'force'. + + + + + + Unmount the device. If the device is referenced in the + system-wide /etc/fstab file (both at + mount time and when this method is invoked), the device + will be attempted to be unmounted as the calling user. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.filesystem-unmount-others + To unmount a device mounted by another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the device is busy + if the operation failed + if the job was cancelled + if an invalid or malformed unmount option was given + + + + + + + + + + Currently unused. + + + + Returns TRUE if the file system is clean, FALSE if there are errors on the file system. + + + + + + + Perform a non-interactive file system check. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.filesystem-check + To check a file system on a + non-system-internal + device. + + + org.freedesktop.udisks.filesystem-check-system-internal + To check a file system on a + system-internal + device. + + + + + if the caller lacks the appropriate PolicyKit authorization + if the device is mounted and the file system doesn't support online file system checking. See TODO for how to determine if a file system supports online fsck + if the operation failed + if the job was cancelled + + + + + + + + + + + + An array of triples (pid, uid, command line for the process + image) for processes currently having open files on the given mounted file system. + Note that this operation is not run as a job. + + + + + + + List open files on a mounted file system. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.filesystem-lsof + To check a file system on a + non-system-internal + device. + + + org.freedesktop.udisks.filesystem-lsof-system-internal + To check a file system on a + system-internal + device. + + + + + if the caller lacks the appropriate PolicyKit authorization + if the device is mounted and the file system doesn't support online file system checking. See TODO for how to determine if a file system supports online fsck + if the operation failed + + + + + + + + + + Passphrase for unlocking the cleartext data. + + + Currently unused. + + + The cleartext device created. + + + + + + Sets up a cleartext device using the given device as backing store. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.luks-unlock + To unlock LUKS encrypted devices + + + + + if the caller lacks the appropriate PolicyKit authorization + if the device is busy + if the operation failed + if the job was cancelled + + + + + + + + + + Currently unused. + + + + + + Tears down the cleartext device set up using + e.g. the LuksUnlock() + method. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.luks-lock-others + To lock an encrypted LUKS device unlocked by another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the device is busy + if the operation failed + if the job was cancelled + + + + + + + + + + The current passphrase. + + + The new passphrase. + + + + + + Change the passphrase used to unlock a LUKS encrypted device. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.change + If the operation is on a + non-system-internal + device + + + org.freedesktop.udisks.change-system-internal + If the operation is on a + system-internal + device + + + + + if the caller lacks the appropriate PolicyKit authorization + if the device is busy + if the operation failed + if the job was cancelled + + + + + + + + + + Object path of the component to add + + + Currently unused. + + + + + + Adds a component to a Linux md RAID array. Existing data + on the given component will be erased. + + + + The caller will need the following PolicyKit authorization: + + + org.freedesktop.udisks.linux-md + + Needed to configured Linux md Software RAID devices. + + + + + + if the caller lacks the appropriate PolicyKit authorization + component to add is busy + if the operation failed + if the job was cancelled + + + + + + + + + + Object paths of the components to use for growing the array + + + Currently unused. + + + + + + Grows the Linux md RAID array with the given components. + + + + The caller will need the following PolicyKit authorization: + + + org.freedesktop.udisks.linux-md + + Needed to configured Linux md Software RAID devices. + + + + + + if the caller lacks the appropriate PolicyKit authorization + component to add is busy + if the operation failed + if the job was cancelled + + + + + + + + + + The component to remove from the array. + + + No options are currently supported. + + + + + + Removes a component from a Linux md RAID array. The component + will be removed and then the signatures on the component will be + scrubbed. + + + + The caller will need the following PolicyKit authorization: + + + org.freedesktop.udisks.linux-md + + Needed to configured Linux md Software RAID devices. + + + + + + if the caller lacks the appropriate PolicyKit authorization + component to add is busy + if the operation failed + if the job was cancelled + + + + + + + + + + Currently unused. + + + + + + Stops a Linux md RAID array. + + + + The caller will need the following PolicyKit authorization: + + + org.freedesktop.udisks.linux-md + + Needed to configured Linux md Software RAID devices. + + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + if the job was cancelled + + + + + + + + + + Currently unused. + + + + + + Stops a Linux LVM2 Logical Volume. + + + + The caller will need the following PolicyKit authorization: + + + org.freedesktop.udisks.linux-lvm2 + + Needed to configured Linux LVM2. + + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + if the job was cancelled + + + + + + + + + + + Use the repair option to fix any problems encountered. + + + + + + Number of mismatched sectors/pages found (or fixed if the repair option is used). + + + + + + + Checks a Linux md RAID array and returns the number of + sectors/page with errors found/fixed. This can only be done if the + property + LinuxMdSyncAction + is idle. + + + + The caller will need the following PolicyKit authorization: + + + org.freedesktop.udisks.linux-md + + Needed to configured Linux md Software RAID devices. + + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + if the job was cancelled + + + + + + + + + + Inhibit options. Currently no options are recognized. + + + + + A cookie that can be used in the + DriveUninhibitPolling() method + to stop inhibiting polling of the device. + + + + + + + Inhibits the daemon from polling the device for media changes. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.inhibit-polling + To inhibit polling + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + + + A cookie obtained from the + DriveInhibitPolling() method. + + + + + + + Uninhibits daemon from polling the device for media changes. + + + + if the given cookie is malformed + + + + + + + + + + + + + + Polls the drive for media. This is typically only useful when the + DeviceIsMediaChangeDetected property + is FALSE. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.inhibit-polling + To inhibit polling + + + + + if the operation failed + + + + + + + + + + Eject options. Currently no options are recognized. + + + + + + Ejects media from the device. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.drive-eject + To eject media from a device + + + + + if the caller lacks the appropriate PolicyKit authorization + if the device or a dependent device (e.g. partition or cleartext luks device) is busy (e.g. mounted) + if the operation failed + if the job was cancelled + if an invalid or malformed option was given + + + + + + + + + + Detach options. Currently no options are recognized. + + + + + + Detachs the device by e.g. powering down the physical port + it is connected to. Note that not all devices or ports are + capable of this. Check the + DriveCanDetach + property before attempting to invoke this method. + + + Note that the physical port a drive belongs to may be + located inside the physical casing - for example, some + netbooks provide a SD card drive connect through USB. As + such, users of this method should be careful – don't + automatically invoke this method if the user presses + e.g. an Eject button in the UI. Instead, provide e.g. a + "Safely Remove Drive" option. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.drive-detach + To detach a device + + + + + if the caller lacks the appropriate PolicyKit authorization + if the device or a dependent device (e.g. partition or cleartext luks device) is busy (e.g. mounted) + if the operation failed + if the job was cancelled + if an invalid or malformed option was given + + + + + + + + + + + Number of seconds before the drive should be spun down. + + + + + + Options related to setting spindown timeout. Currently no options are recognized. + + + + + + A cookie that can be used in the + DriveUnsetSpindownTimeout() method + to unset the spindown timeout of the device. + + + + + + + Configures spindown timeout for the drive. + Check the + DriveCanSpindown + property before attempting to invoke this method. + Caution should be exercised when using this method, see + the SPINNING DOWN DISKS section in the + udisks(1) man page before using it. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.drive-set-spindown + To set spindown timeouts + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + if an invalid or malformed option was given + + + + + + + + + + + + A cookie obtained from the + DriveSetSpindownTimeout() method. + + + + + + + Unsets spindown timeout for the drive. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.drive-set-spindown + To set spindown timeouts + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + + The option nowakeup can be passed to + avoid spinning up the disk if it's in a low-power mode. + The option simulate= can be used to pass a path to + a blob with libatasmart data to use instead of reading it from the disk. + The simulate= option can only be used by the super user. + + + + + + Refreshes the + ATA SMART + data for the given drive. Note that this operation is not run as a job. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.drive-ata-smart-refresh + Needed to refresh ATA SMART data + + + + + if the caller lacks the appropriate PolicyKit authorization + If the disk is sleeping and the nowakeup option was passed + if the operation failed + + + + + + + + + + + + The name of the test to run; supported values are 'short' + (usually less than ten minutes), 'extended' (usually tens + of minutes) and 'conveyance' (usually a few minutes). + + + + Currently unused. + + + + + + Runs a ATA SMART self test on the drive. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.drive-ata-smart-selftest + Needed to run ATA SMART self tests + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + if the job was cancelled + + + + + + + + + + + + + + + If TRUE, write performance will be benchmarked in addition + to read performance. Note that benchmarking write performance + will scribble zeros in various parts of the drive and can + only be used on a drive where the contents are completely + unrecognized (e.g. no partition table and device). Use + this option with caution. + + + + Currently unused. + + + + + An array of pairs where the first element is the offset + and the second element is the measured read transfer rate + (in bytes/sec) at the given offset. + + + + + + An array of pairs where the first element is the offset + and the second element is the measured read transfer rate + (in bytes/sec) at the given offset. + This is an empty array unless write benchmarking has been + requested. + + + + + + An array of pairs where the first element is the offset + and the second element the amount of time (in seconds) it + took to seek to the position. + + + + + + + Benchmarks the drive. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.change + Needed to run benchmarks + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + if the job was cancelled + + + + + + + + + + + Something on the device changed. Changes in job state wont + trigger this signal; see the JobChanged() signal. + + + + + + + + + + Whether a job is currently in progress. + + + Whether the job is cancellable. + + + The identifier of the job. + + + The UNIX user id of the user who initiated the job. + + + Percentage completed of the job (between 0 and 100, negative if unknown). + + + + + + Emitted when a job on a device changes. Clients should + listen to this signal to avoid polling the daemon for job + state. + + + + + + + + + + OS specific native path of the device. On Linux this is the sysfs path, for example /sys/devices/pci0000:00/0000:00:1f.2/host2/target2:0:1/2:0:1:0/block/sda. + + + + + The point in time (seconds since the Epoch Jan 1, 1970 0:00 UTC) when the device + was detected by the daemon. + + + + + The point in time (seconds since the Epoch Jan 1, 1970 0:00 UTC) when the + media currently in the device was detected by the daemon or 0 if the + device has no media. + + + + + Major for the device or -1 if not set. + + + + + Major for the device or -1 if not set. + + + + + UNIX special device file for device. Example: /dev/sda. + + + + + Either the value of the + DeviceFile + property, otherwise the preferred device file (typically a symlink to the value of the + DeviceFile + property) to present in user interface. + Example: /dev/mapper/mpathb or + /dev/vg_phobos/lv_root. + + + + + Symlinks to UNIX special device file that are stable and uniquely identifies the device. + Example: /dev/disk/by-id/scsi-SATA_ST910021AS_3MH05AVA, + /dev/disk/by-id/ata-ST910021AS_3MH05AVA. + + + + + Symlinks to UNIX special device file that uniquely identifies the port/partition the device + is plugged into. Example: /dev/disk/by-path/pci-0000:00:1f.2-scsi-0:0:1:0 + + + + + TRUE if the device is considered system internal. Typically, system internal devices + include non-removable internal hard disks and other drives that are not easily added/removed + by a local console user. The heuristic typically used is that only devices on removable media + and devices connected via Firewire, USB, eSATA and SDIO are considered external. + + + + + TRUE if the device is a partition. See the properties starting with partition- for details. + + + + + TRUE if the device contains a partition table. See partition- properties for details. + + + + + TRUE if the device contains removable media. + + + + + TRUE if media is available in the device. + + + + + TRUE if media changes are detected. + + + + + TRUE if media changes are detected through the host + polling the device, e.g. waking up every two seconds to + revalidate the media. This typically keeps the device in a + high power state and uses cycles on the CPU. As an + example, SATA AN capable optical drives does not need + polling. + + + + + TRUE if it is possible to inhibit media detection on the device (to avoid keeping the device in a high power state and waking up the host). + + + + + TRUE if media detection is inhibited (to avoid keeping the device in a high power state and waking up the host). + + + + + TRUE if the device read-only. + + + + + TRUE if the device is a drive. + See the drive- properties for details. + + + + + TRUE if the device is an optical drive and an optical disc is inserted. + See the optical-disc- properties for details. + + + + + TRUE if the device is mounted. + + + + + A list of paths in the root namespace where the root of the device is mounted. + This property is only valid if + DeviceIsMounted + is TRUE. + + + + + The UNIX user id of the user who mounted the device. Set to 0 if + not mounted by udisks. + This property is only valid if + DeviceIsMounted + is TRUE. + + + + + TRUE if device is an LUKS encrypted device. See Lucks properties for details. + + + + + TRUE if device is a cleartext device backed by a LUKS encrypted device. See LucksCleartext properties for details. + + + + + TRUE if the device is a Linux md RAID component. See LinuxMdComponent properties for details. + + + + + TRUE if the device is a Linux md RAID array. See LinuxMd properties for details. + + + + + TRUE if the device is a Linux LVM2 logical volume. See LinuxLvm2LV properties for details. + + + + + TRUE if the device is a Linux LVM2 physical. See LinuxLvm2PV properties for details. + + + + + TRUE if the device is a component (e.g. active path) of a Linux dm-multipath device. + + + + + TRUE if the device is a Linux dm-multipath device. + + + + + TRUE if the device is a Linux loop device. + + + + + The size of the device in bytes. + + + + + The block size of the device in bytes. + + + + + A hint if the device should be hidden from the user interface. + + + + + A hint if the device (or e.g. the multi-disk device that the device is + part of) shouldn't be automatically mounted / assembled. + + + + + The name to use when presenting the device to an end user. + + + + + The icon to use when presenting the device to an end user. If set, must be a name + following the freedesktop.org icon theme specification. + + + + + + The job properties specify if a job initiated via the + udisks daemon is currently in progress. + + + + + This property is used to identify the job and maps 1-1 + with the names of the method calls on this interface, + e.g. 'FilesystemCreate' and so on. + + + + + The UNIX user id of the user who initiated the job. + + + + + Whether the job can be cancelled + using JobCancel() method. + + + + + Percentage completed of the job (between 0 and 100, negative if unknown). + + + + + + A result of probing for signatures on the block device; + known values are: + + + filesystem + A mountable file system was detected + + + crypto + Encrypted data (e.g. LUKS) was detected + + + partitiontable + A partition table was detected + + + raid + Used for RAID and LVM components + + + other + A non-standard signature was detected + + + If blank, no known signature was detected. This doesn't + necessarily mean the device contains no structured data; + it only means that no signature known to the probing code + was detected. + + + + + This property contains more information about the result + of probing the block device. It's value depends of the + value the + IdUsage + property: + + + filesystem + The mountable file system that was detected (e.g. ext3, vfat) + + + crypto + Known values include crypto_LUKS + + + partitiontable + + Known values include + mbr (for the + Master Boot Record + scheme), + gpt (for the + GUID Partition Table + scheme), + apm (for the + Apple Partition Map + scheme). + + + + raid + + Known values include + LVM1_member (for Linux LVM1 components), + LVM2_member (for Linux LVM2 components), + linux_raid_member (for Linux md (Software RAID) components) + + + + other + + Known values include + swap (for swap space), + suspend (data used when resuming from STD) + + + + + + + + The version of the + detected file system (or other identified data structure) identified by the + IdUsage + and + IdType + properties. + + + + + The UUID (universally unique identifier) of the + detected file system (or other identified data structure) identified by the + IdUsage + and + IdType + properties. + + + + + The user-visible label of the + detected file system (or other identified data structure) identified by the + IdUsage + and + IdType + properties. + + + + + + The cleartext device that is using the LUKS device. + This property is only valid if + DeviceIsLuks + is TRUE. + + + + + + The encrypted LUKS device backing a crypto cleartext device. + This property is only valid if + DeviceIsLuksCleartext + is TRUE. + + + + + The UNIX user id of the user who unlocked the LUKS device. Set to 0 if + not unlocked by udisks. + This property is only valid if + DeviceIsLuksCleartext + is TRUE. + + + + + + The object path of the partition table the partition is part of. + This property is only valid if + DeviceIsPartition + is TRUE. + + + + + The scheme of the partition table this partition is part of. + See the scheme parameter of the + PartitionTableCreate() method + for details on known partitioning schemes. + This property is only valid if + DeviceIsPartition + is TRUE. + + + + + The type of the partition. + See the type parameter of the + PartitionCreate() method + for details on known partitioning types. + This property is only valid if + DeviceIsPartition + is TRUE. + + + + + The label of the partition. + See the label parameter of the + PartitionCreate() method + for details on partition labels. + This property is only valid if + DeviceIsPartition + is TRUE. + + + + + The UUID of the partition. + See the uuid parameter of the + PartitionCreate() method + for details on partition UUID's. + This property is only valid if + DeviceIsPartition + is TRUE. + + + + + Partition flags. + See the flags parameter of the + PartitionCreate() method + for details on partition flags. + This property is only valid if + DeviceIsPartition + is TRUE. + + + + + Number of the partition. Typically partition numbers start at 1 and are identical + to the numbers used by the kernel. Note that partitions may not be sequentially + numbered. + This property is only valid if + DeviceIsPartition + is TRUE. + + + + + Offset in bytes where the partition is located on the enclosing partition table device (see + PartitionSlave). + This property is only valid if + DeviceIsPartition + is TRUE. + + + + + Size of the partition in bytes. + This property is only valid if + DeviceIsPartition + is TRUE. + + + + + + The amount of bytes the beginning of the partition is offset + from the disk's natural alignment. + This property is only valid if + DeviceIsPartition + is TRUE. + + + + + + The scheme of the partition table. + See the scheme parameter of the + PartitionTableCreate() method + for details on known partitioning schemes. + This property is only valid if + DeviceIsPartitionTable + is TRUE. + + + + + Number of partitions in the partition table. + This property is only valid if + DeviceIsPartitionTable + is TRUE. + + + + + + Name of the vendor of the drive, for example MATSHITA or BELKIN. + This property is only valid if + DeviceIsDrive + is TRUE. + + + + + Name of the model of the drive, for example ST910021AS or USB 2 HS-CF. + This property is only valid if + DeviceIsDrive + is TRUE. + + + + + Revision of the drive, for example 3.07 or 1.95. + This property is only valid if + DeviceIsDrive + is TRUE. + + + + + The serial number of the drive or blank if unknown. + Examples: 3MH05AVA, A0000001B900. + This property is only valid if + DeviceIsDrive + is TRUE. + + + + + The World Wide Name + in hex (without a leading "0x") or blank if the drive has no WWN. + Example: 50014ee0016eb572. + This property is only valid if + DeviceIsDrive + is TRUE. + + + + + The rotational rate of the disk (e.g. 4200, 5400, 5900, 7200, 10000, 15000) in rounds + per minute or 0 if unknown. + This property is only valid if + DeviceIsDrive + is TRUE and + DriveIsRotational + is TRUE. + + + + + Whether the write cache is enabled, known values include "enabled" and "disabled" and + the blank string if unknown. + This property is only valid if + DeviceIsDrive + is TRUE. + + + + + The interface through which the drive is connected. Known values include: + + + + virtual + Device is a composite device e.g. Software RAID or similar + + + ata + Connected via ATA + + + ata_serial + Connected via Serial ATA + + + ata_serial_esata + Connected via eSATA + + + ata_parallel + Connected via Parallel ATA + + + scsi + Connected via SCSI + + + usb + Connected via the Universal Serial Bus + + + firewire + Connected via Firewire + + + sdio + Connected via SDIO + + + platform + Part of the platform, e.g. PC floppy drive + + + This property is only valid if + DeviceIsDrive + is TRUE. + + + + + The nominal speed of the connection interface in bits per + second. If unknown this property is set to 0. + This property is only valid if + DeviceIsDrive + is TRUE. + + + + + An array of media types that can be used in the + drive. This property is sometimes set using quirk files if + the hardware isn't capable of precisely reporting it. + Known values include: + + + flash + Flash Card + + + flash_cf + CompactFlash + + + flash_ms + MemoryStick + + + flash_sm + SmartMedia + + + flash_sd + Secure Digital + + + flash_sdhc + Secure Digital High-Capacity + + + flash_mmc + MultiMediaCard + + + floppy + Floppy Disk + + + floppy_zip + Zip Disk + + + floppy_jaz + Jaz Disk + + + optical + Optical Disc + + + optical_cd + Compact Disc + + + optical_cd_r + Compact Disc Recordable + + + optical_cd_rw + Compact Disc Rewritable + + + optical_dvd + Digital Versatile Disc + + + optical_dvd_r + DVD-R + + + optical_dvd_rw + DVD-RW + + + optical_dvd_ram + DVD-RAM + + + optical_dvd_plus_r + DVD+R + + + optical_dvd_plus_rw + DVD+RW + + + optical_dvd_plus_r_dl + DVD+R Dual Layer + + + optical_dvd_plus_rw_dl + DVD+RW Dual Layer + + + optical_bd + Bluray Disc + + + optical_bd_r + BluRay Recordable + + + optical_bd_re + BluRay Rewritable + + + optical_hddvd + HD DVD + + + optical_hddvd_r + HD DVD Recordable + + + optical_hddvd_rw + HD DVD Rewritable + + + optical_mo + Magneto Optical + + + optical_mrw + Can read Mount Rainer media + + + optical_mrw_w + Can write Mount Rainer media + + + + This property is only valid if + DeviceIsDrive + is TRUE. + + + + + The type of media currently in the drive (blank if no media is available). Known + values include the ones listed for the + DriveMediaCompatibility + property. + This property is only valid if + DeviceIsDrive + is TRUE. + + + + + TRUE only if the media can be physically ejected by issuing a command + from the host to the drive (e.g. optical and Zip drives). + This property is only valid if + DeviceIsDrive + is TRUE. + + + + + TRUE only if the drive is capable of being detached by + e.g. powering down the port it is connected to. + This property is only valid if + DeviceIsDrive + is TRUE. + + + + + TRUE only if the drive is capable of being put into + a standby mode (typically powering down the spindle motor). + This property is only valid if + DeviceIsDrive + is TRUE. + + + + + TRUE if the disk uses rotational media, such as a hard disk. + This property is only valid if + DeviceIsDrive + is TRUE. + + + + + The object of the storage adapter for the drive or "/" if no adapter exists. + This property is only valid if + DeviceIsDrive + is TRUE. + + + + + The object paths of the ports for the drive or empty if no ports exist. + This property is only valid if + DeviceIsDrive + is TRUE. + + + + + An array of object paths for devices with similar serial number and/or WWN. + Typically all drives with similar serial number and/or WWN + are configured as a multipath device (for example via the + Linux device-mapper target cf. + DeviceIsLinuxDmmp and + DeviceIsLinuxDmmpComponent) + but in some cases the OS needs manual configuration. + Presentation-level software can (and should) display a + warning when this property is non-empty and the device + isn't a multipath component or multipath device e.g. when both + DeviceIsLinuxDmmpComponent) and + DeviceIsLinuxDmmp) + is FALSE. + This property is only valid if + DeviceIsDrive + is TRUE. + + + + + + + + TRUE only if the disc is appendable. + This property is only valid if + DeviceIsOpticalDisc + is TRUE. + + + + + TRUE only if the disc is appendable. + This property is only valid if + DeviceIsOpticalDisc + is TRUE. + + + + + TRUE only if the disc is appendable. + This property is only valid if + DeviceIsOpticalDisc + is TRUE. + + + + + Number of tracks on the disc. + This property is only valid if + DeviceIsOpticalDisc + is TRUE. + + + + + Number of audio tracks on the disc. + This property is only valid if + DeviceIsOpticalDisc + is TRUE. + + + + + Number of sessions on the disc. + This property is only valid if + DeviceIsOpticalDisc + is TRUE. + + + + + + + + TRUE if the disk is capable of reporting SMART data, FALSE otherwise. + + + + + The point in time (seconds since the Epoch Jan 1, 1970 + 0:00 UTC) when ATA SMART data was collected. + This property is only valid if + DriveAtaSmartTimeCollected + is greater than zero. + + + + + The overall assessment for the disk. Is one of the following strings + GOOD, + BAD_ATTRIBUTES_IN_THE_PAST (At least one pre-fail attribute is exceeded its threshold in the past), + BAD_SECTOR (At least one bad sector), + BAD_ATTRIBUTE_NOW (At least one pre-fail attribute is exceeding its threshold now), + BAD_SECTOR_MANY (Many bad sectors)), + BAD_STATUS (Smart Self Assessment negative) + or empty if some error occured trying to determine the result. + This property is only valid if + DriveAtaSmartTimeCollected + is greater than zero. + + + + + A blob containing the ATA SMART data. This blob can be used with libatasmart to get + more information. + This property is only valid if + DriveAtaSmartTimeCollected + is greater than zero. + + + + + + + + The RAID level of the array the component is part of. Known values include + + + linear + The array is Just A Bunch of Disks + + + raid0 + RAID-0 + + + raid1 + RAID-1 + + + raid4 + RAID-4 + + + raid5 + RAID-5 + + + raid6 + RAID-6 + + + raid10 + RAID-10 + + + This property is only valid if + DeviceIsLinuxMdComponent + is TRUE. + + + + + The zero-based position of the component or -1 if not part of a running array. + This property is only valid if + DeviceIsLinuxMdComponent + is TRUE. + + + + + The number of component devices in the array the component is part of. + This property is only valid if + DeviceIsLinuxMdComponent + is TRUE. + + + + + The UUID of the array the component is part of. + This property is only valid if + DeviceIsLinuxMdComponent + is TRUE. + + + + + The name of the array the component is part of. Blank if the array + doesn't have a name (e.g. pre-1.0 meta data). + This property is only valid if + DeviceIsLinuxMdComponent + is TRUE. + + + + + The home host of the array the component is part of, e.g. where it was created. Blank if the array + has pre-1.0 meta data. + This property is only valid if + DeviceIsLinuxMdComponent + is TRUE. + + + + + The version of superblock of the component. + This property is only valid if + DeviceIsLinuxMdComponent + is TRUE. + + + + + The holder of the component or "/" if the component isn't claimed by any array. + This property is only valid if + DeviceIsLinuxMdComponent + is TRUE. + + + + + The state of the component (contents of md/dev-XXX/state file). + This property is only valid if + DeviceIsLinuxMdComponent + is TRUE and + DeviceIsLinuxMdComponentHolder + is non-empty. + + + + + + The state of the array (contents of the md/array_state file). + This property is only valid if + DeviceIsLinuxMd + is TRUE. + + + + + The RAID level of the array. For known values see the + LinuxMdComponentLevel + property. + This property is only valid if + DeviceIsLinuxMd + is TRUE. + + + + + The UUID of the array. + This property is only valid if + DeviceIsLinuxMd + is TRUE. + + + + + The home host of the array, e.g. where if was created. Blank if the array + has pre-1.0 meta data. + DeviceIsLinuxMd + is TRUE. + + + + + The name of the array. Blank if the array + doesn't have a name (e.g. pre-1.0 meta data). + DeviceIsLinuxMd + is TRUE. + + + + + Number of component devices in the array. + This property is only valid if + DeviceIsLinuxMd + is TRUE. + + + + + Metadata version used in the components. + This property is only valid if + DeviceIsLinuxMd + is TRUE. + + + + + An array of object paths for components currently part of the array. + This property is only valid if + DeviceIsLinuxMd + is TRUE. + + + + + TRUE only if the array is running in degraded mode. + This property is only valid if + DeviceIsLinuxMd + is TRUE. + + + + + The operation currently pending on the array. Known values + include + + + idle + No operation is pending + + + reshape + A reshape is in progress + + + resync + Redudancy is being calculated + + + repair + A repair operation is in progress + + + recover + A hot spare is being built to replace a failed/missing device + + + This property is only valid if + DeviceIsLinuxMd + is TRUE. + + + + + The progress of the current sync operation. + This property is only valid if + DeviceIsLinuxMd + is TRUE and the value of the property + LinuxMdSyncAction + is not idle. + + + + + The speed of the sync operation in bytes per second. + This property is only valid if + DeviceIsLinuxMd + is TRUE and the value of the property + LinuxMdSyncAction + is not idle. + + + + + + + + + + + The UUID of the PV. + This property is only valid if + DeviceIsLinuxLvm2PV + is TRUE. + + + + + The number of metadata areas on the PV. + This property is only valid if + DeviceIsLinuxLvm2PV + is TRUE. + + + + + + + The name of the volume group (that this physical volume belongs to). + This property is only valid if + DeviceIsLinuxLvm2PV + is TRUE. + + + + + The UUID of the volume group (that this physical volume belongs to). + This property is only valid if + DeviceIsLinuxLvm2PV + is TRUE. + + + + + The size of the volume group (that this physical volume belongs to) in bytes. + This property is only valid if + DeviceIsLinuxLvm2PV + is TRUE. + + + + + The unallocated size of the volume group (that this physical volume belongs to) in bytes. + This property is only valid if + DeviceIsLinuxLvm2PV + is TRUE. + + + + + The sequence number for the volume group (that this physical volume belongs to). + This property is only valid if + DeviceIsLinuxLvm2PV + is TRUE. + + + + + The extent size for the volume group (that this physical volume belongs to) in bytes. + This property is only valid if + DeviceIsLinuxLvm2PV + is TRUE. + + + + + The physical volumes that belongs to the volume group (that this physical volume belongs to). + Each element is a semicolon separated list of key/value pairs. The only known key/value + type as this point is uuid for the UUID of the physical volume. + This property is only valid if + DeviceIsLinuxLvm2PV + is TRUE. + + + + + The logical volumes that belongs to the volume group (that this physical volume belongs to). + Each element is a semicolon separated list of key/value pairs. The only known key/value + types as this point are + uuid (for the UUID of the logical volume), + name (for the name of the logical volume), + size (for the size of the logical volume) and + active (whether the logical volume is active). + This property is only valid if + DeviceIsLinuxLvm2PV + is TRUE. + + + + + + + + + + + + The name of the logical volume. + This property is only valid if + DeviceIsLinuxLvm2LV + is TRUE. + + + + + The UUID of the logical volume. + This property is only valid if + DeviceIsLinuxLvm2LV + is TRUE. + + + + + The name of volume group the logical volume belongs to. + This property is only valid if + DeviceIsLinuxLvm2LV + is TRUE. + + + + + The UUID of the volume group the logical volume belongs to. + This property is only valid if + DeviceIsLinuxLvm2LV + is TRUE. + + + + + + + + + + The object path of the multi-path device the component is currently part of. + This property is only valid if + DeviceIsLinuxDmmpComponent + is TRUE. + + + + + + + + + + The symbolic name for the multipath device, e.g. mpathb. + This property is only valid if + DeviceIsLinuxDmmp + is TRUE. + + + + + The object paths of currently active component devices, e.g. paths. + This property is only valid if + DeviceIsLinuxDmmp + is TRUE. + + + + + The parameters/configuration for the multipath device. + This property is only valid if + DeviceIsLinuxDmmp + is TRUE. + + + + + + The file backing the loop device. + This property is only valid if + DeviceIsLinuxLoop + is TRUE. + + + + + + diff --git a/src/dbus/org.freedesktop.UDisks.xml b/src/dbus/org.freedesktop.UDisks.xml new file mode 100644 index 00000000..f046d64b --- /dev/null +++ b/src/dbus/org.freedesktop.UDisks.xml @@ -0,0 +1,1156 @@ + + + + + + + + + + + An array of object paths for storage adapters. + + + + + + Enumerate all storage adapters on the system. + + + + + + + + + + + An array of object paths for storage expanders. + + + + + + Enumerate all storage expanders on the system. + + + + + + + + + + + An array of object paths for ports. + + + + + + Enumerate all storage ports on the system. + + + + + + + + + + + An array of object paths for devices. + + + + + + Enumerate all disk devices on the system. + + + + + + + + + + + An array device file names. + + + + + + Enumerate all device files (including symlinks) for disk devices on the system. + + + + + + + + + + + UNIX special device file + + + Object path of device + + + + + + Finds a device by device path. + + + + + + + + + + + Device major + + + Device minor + + + Object path of device + + + + + + Finds a device by major:minor. + + + + + + + + + + + Inhibit options. Currently no options are recognized. + + + + + A cookie that can be used in the + DriveUninhibitAllPolling() method + to stop inhibiting polling of all devices. + + + + + + + Inhibits the daemon from polling devices for media changes. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.inhibit-polling + To inhibit polling + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + + + A cookie obtained from the + DriveInhibitAllPolling() method. + + + + + + + Uninhibits daemon from polling devices for media changes. + + + + if the given cookie is malformed + + + + + + + + + + + Number of seconds before drives should be spun down. + + + + + + Options related to setting spindown timeouts. Currently no options are recognized. + + + + + + A cookie that can be used in the + DriveUnsetAllSpindownTimeouts() method + to unset the spindown timeout for drives. + + + + + + + Configures spindown timeout for all drives capable of being spun down. + Caution should be exercised when using this method, see + the SPINNING DOWN DISKS section in the + udisks(1) man page before using it. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.drive-set-spindown + To set spindown timeouts + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + if an invalid or malformed option was given + + + + + + + + + + + + A cookie obtained from the + DriveSetSpindownTimeout() method. + + + + + + + Unsets spindown timeout for the drive. + + + + The caller will need one of the following PolicyKit authorizations: + + + org.freedesktop.udisks.drive-set-spindown + To set spindown timeouts + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + + The UUID of the volume group to start. + + + Options for starting the VG. Currently no options are supported. + + + + + + Starts all logical volumes in Linux LVM2 Volume Group. + + + + The caller will need the following PolicyKit authorization: + + + org.freedesktop.udisks.linux-lvm2 + + Needed to configured Linux LVM2 devices. + + + + + + if the caller lacks the appropriate PolicyKit authorization + if one of the given components are busy + if the operation failed + if the job was cancelled + + + + + + + + + + The UUID of the volume group to stop. + + + Options for stopping the VG. Currently no options are supported. + + + + + + Stops all logical volumes in Linux LVM2 Volume Group. + + + + The caller will need the following PolicyKit authorization: + + + org.freedesktop.udisks.linux-lvm2 + + Needed to configured Linux LVM2 devices. + + + + + + if the caller lacks the appropriate PolicyKit authorization + if one of the given components are busy + if the operation failed + if the job was cancelled + + + + + + + + + + The UUID of the volume group to set the name for. + + + The new name for the volume group. + + + + + + Sets the name for a volume group. + + + + The caller will need the following PolicyKit authorization: + + + org.freedesktop.udisks.linux-lvm2 + + Needed to configured Linux LVM2 devices. + + + + + + if the caller lacks the appropriate PolicyKit authorization + if one of the given components are busy + if the operation failed + if the job was cancelled + + + + + + + + + + The UUID of the volume group to add a physical volume to. + + + The objcet path of the device to use as a physical volume. + + + Currently unused. + + + + + + Adds a Physical volume to a Linux LVM2 Volume Group. Existing data + on the given device to use for a physical volume will be erased. + + + + The caller will need the following PolicyKit authorization: + + + org.freedesktop.udisks.linux-lvm2 + + Needed to configured Linux LVM2 devices. + + + + + + if the caller lacks the appropriate PolicyKit authorization + if one of the given components are busy + if the operation failed + if the job was cancelled + + + + + + + + + + The UUID of the volume group to remove the physical volume from. + + + The UUID of the physical volume to remove from the VG. + + + Currently unused. + + + + + + Removes a Physical volume from a Linux LVM2 Volume Group. + + + + The caller will need the following PolicyKit authorization: + + + org.freedesktop.udisks.linux-lvm2 + + Needed to configured Linux LVM2 devices. + + + + + + if the caller lacks the appropriate PolicyKit authorization + if one of the given components are busy + if the operation failed + if the job was cancelled + + + + + + + + + + The UUID of the volume group for the logical volume. + + + The UUID of the logical volume to set the name for. + + + The new name for the logical volume. + + + + + + Sets the name for a logical volume. + + + + The caller will need the following PolicyKit authorization: + + + org.freedesktop.udisks.linux-lvm2 + + Needed to configured Linux LVM2 devices. + + + + + + if the caller lacks the appropriate PolicyKit authorization + if one of the given components are busy + if the operation failed + if the job was cancelled + + + + + + + + + + The UUID of the volume group of the logical volume to start belongs to. + + + The UUID of the logical volume to start. + + + Options for starting the logical volume. Currently no options are supported. + + + + + + Starts a LVM2 logical volume. + + + + The caller will need the following PolicyKit authorization: + + + org.freedesktop.udisks.linux-lvm2 + + Needed to configured Linux LVM2 devices. + + + + + + if the caller lacks the appropriate PolicyKit authorization + if one of the given components are busy + if the operation failed + if the job was cancelled + + + + + + + + + + The UUID of the volume group of the logical volume to start belongs to. + + + The UUID of the logical volume to remove. + + + Options used for the removal of the logical volume. Currently no options are supported. + + + + + + Removes a LVM2 logical volume. + + + + The caller will need the following PolicyKit authorization: + + + org.freedesktop.udisks.linux-lvm2 + + Needed to configured Linux LVM2 devices. + + + + + + if the caller lacks the appropriate PolicyKit authorization + if one of the given components are busy + if the operation failed + if the job was cancelled + + + + + + + + + + The UUID of the volume group to create a logical volume in. + + + The name for the logical volume. + + + The size of the logical volume, in bytes. + + + Number of stripes to use. + + + The stripe size to use or 0 if @num_stripes is 0. This must be a power of two. + + + Number of mirrors to use. + + + Options used when creating the logical volume. Currently no options are supported. + + + + The file system to create in new logical filesystem. Leave + blank to skip creating a file system. See the + Device.FilesystemCreate() method + for details. + + + + + Options to use for file system creation. See the + Device.FilesystemCreate() method + for details. + + + + The object path of the newly added logical volume. + + + + + + Creates a new LVM2 logical volume. + + + + The caller will need the following PolicyKit authorization: + + + org.freedesktop.udisks.linux-lvm2 + + Needed to configured Linux LVM2 devices. + + + + + + if the caller lacks the appropriate PolicyKit authorization + if one of the given components are busy + if the operation failed + if the job was cancelled + + + + + + + + + + The object paths of the components of the array to start. + + + Options for starting the array. Currently no options are supported. + + + + The object path of the assembled array device. + + + + + + Starts an Linux md RAID array. The array will be assembled + and started in degraded mode if an insufficient number of + components are given. + + + + The caller will need the following PolicyKit authorization: + + + org.freedesktop.udisks.linux-md + + Needed to configured Linux md Software RAID devices. + + + + + + if the caller lacks the appropriate PolicyKit authorization + if one of the given components are busy + if the operation failed + if the job was cancelled + + + + + + + + + + The object paths of the components to use for the array. + + + RAID level. + + + Stripe Size in bytes, or 0 to use the default stripe size. + + + Name of the array. + + + Options for creating the array. Currently no options are supported. + + + + The object path of the created array device. + + + + + + Creates a Linux md RAID array. The array will be created and + assembled. + + + + The caller will need the following PolicyKit authorization: + + + org.freedesktop.udisks.linux-md + + Needed to configured Linux md Software RAID devices. + + + + + + if the caller lacks the appropriate PolicyKit authorization + if one of the given components are busy + if the operation failed + if the job was cancelled + + + + + + + + + + + A cookie that can be used in the + Uninhibit() method. + to stop inhibiting the daemon. + + + + + + + Inhibits clients from invoking methods on the daemon + of the daemon that require authorization (all methods + will return the org.freedesktop.PolicyKit.Error.Inhibited error) + if the caller is not the super user. + This is typically used by OS installers and other + programs that expects full control of the system, specifically + to avoid automounting devices. + + + + Only the super user can invoke this method. + + + if the caller is not the super user + + + + + + + + + + + + A cookie obtained from the + Inhibit() method. + + + + + + + Uninhibits other clients from using the daemon. + + + + if the given cookie is malformed + + + + + + + + + + Object path of device that was added. + + + + + + Emitted when a device is added. + + + + + + + + + + Object path of device that was removed. + + + + + + Emitted when a device is removed. + + + + + + + + + + Object path of device that was changed. + + + + + + Emitted when a device changed. + + + + + + + + + + The object path of the device. + + + Whether a job is currently in progress. + + + Whether the job is cancellable. + + + The identifier of the job. + + + Number of tasks in the job. + + + Current task number (zero-based offset). + + + Task identifier for current task. + + + Percentage completed of current task (between 0 and 100, negative if unknown). + + + + + + Emitted when a job on a device changes. + + + + + + + + + + Object path of adapter that was added. + + + + + + Emitted when an adapter is added. + + + + + + + Object path of adapter that was removed. + + + + + + Emitted when an adapter is removed. + + + + + + + Object path of adapter that was changed. + + + + + + Emitted when an adapter changed. + + + + + + + + + + Object path of expander that was added. + + + + + + Emitted when an expander is added. + + + + + + + Object path of expander that was removed. + + + + + + Emitted when an expander is removed. + + + + + + + Object path of expander that was changed. + + + + + + Emitted when an expander changed. + + + + + + + + + + Object path of port that was added. + + + + + + Emitted when a port is added. + + + + + + + Object path of port that was removed. + + + + + + Emitted when a port is removed. + + + + + + + Object path of port that was changed. + + + + + + Emitted when a port changed. + + + + + + + + + + The version of the running daemon. + + + + + + TRUE only if the daemon is inhibited. + + + + + + TRUE only if the daemon can create encrypted LUKS block devices, see the + LuksUnlock() and + LuksLock() + methods for details. + + + + + + + + + An array of file systems known to the daemon and what features are supported. + Each element in the array contains the following members: + + + id + + The name / identifier of the file system (such as ext3 or vfat), + similar to the contents of the + Device:IdType + property. + + + + name + + A human readable name for the file system such as "Linux Ext3". + + + + supports_unix_owners + + Whether the file system supports the UNIX owners model (e.g. ext3 does, but vfat doesn't). + + + + can_mount + + Whether the file system can be mounted. + + + + can_create + + Whether the file system can be created on a device. + + + + max_label_len + + The maximum amount of bytes that the file system label can hold. Set to zero if the file + system doesn't support labels. + + + + supports_label_rename + + Whether the label of the file system can be changed. + + + + supports_online_label_rename + + Whether the label can be changed while the file system is mounted. + + + + supports_fsck + + Whether the file system can be checked. + + + + supports_online_fsck + + Whether the file system can be checked while mounted. + + + + supports_resize_enlarge + + Whether the file system can be enlarged. + + + + supports_online_resize_enlarge + + Whether the file system can be enlarged while mounted. + + + + supports_resize_shrink + + Whether the file system can be shrunk. + + + + supports_online_resize_shrink + + Whether the file system can be shrunk while mounted. + + + + + + + + + + + + diff --git a/src/dbus/org.freedesktop.UDisks2.Block.xml b/src/dbus/org.freedesktop.UDisks2.Block.xml new file mode 100644 index 00000000..f0e3a06c --- /dev/null +++ b/src/dbus/org.freedesktop.UDisks2.Block.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/dbus/org.freedesktop.UDisks2.Drive.xml b/src/dbus/org.freedesktop.UDisks2.Drive.xml new file mode 100644 index 00000000..5312b225 --- /dev/null +++ b/src/dbus/org.freedesktop.UDisks2.Drive.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/dbus/org.freedesktop.UDisks2.Filesystem.xml b/src/dbus/org.freedesktop.UDisks2.Filesystem.xml new file mode 100644 index 00000000..1781919a --- /dev/null +++ b/src/dbus/org.freedesktop.UDisks2.Filesystem.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/dbus/org.freedesktop.UDisks2.Job.xml b/src/dbus/org.freedesktop.UDisks2.Job.xml new file mode 100644 index 00000000..2cd42533 --- /dev/null +++ b/src/dbus/org.freedesktop.UDisks2.Job.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/dbus/org.gnome.SettingsDaemon.MediaKeys.xml b/src/dbus/org.gnome.SettingsDaemon.MediaKeys.xml new file mode 100644 index 00000000..ba9b084c --- /dev/null +++ b/src/dbus/org.gnome.SettingsDaemon.MediaKeys.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + +" diff --git a/src/dbus/org.mpris.MediaPlayer2.Player.xml b/src/dbus/org.mpris.MediaPlayer2.Player.xml new file mode 100644 index 00000000..711a74e4 --- /dev/null +++ b/src/dbus/org.mpris.MediaPlayer2.Player.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/dbus/org.mpris.MediaPlayer2.Playlists.xml b/src/dbus/org.mpris.MediaPlayer2.Playlists.xml new file mode 100644 index 00000000..ef4c28a3 --- /dev/null +++ b/src/dbus/org.mpris.MediaPlayer2.Playlists.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/dbus/org.mpris.MediaPlayer2.TrackList.xml b/src/dbus/org.mpris.MediaPlayer2.TrackList.xml new file mode 100644 index 00000000..24fae962 --- /dev/null +++ b/src/dbus/org.mpris.MediaPlayer2.TrackList.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/dbus/org.mpris.MediaPlayer2.xml b/src/dbus/org.mpris.MediaPlayer2.xml new file mode 100644 index 00000000..2a791cea --- /dev/null +++ b/src/dbus/org.mpris.MediaPlayer2.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + diff --git a/src/device/afcdevice.cpp b/src/device/afcdevice.cpp new file mode 100644 index 00000000..be7ce43b --- /dev/null +++ b/src/device/afcdevice.cpp @@ -0,0 +1,191 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "afcdevice.h" +#include "afcfile.h" +#include "afctransfer.h" +#include "devicemanager.h" +#include "gpodloader.h" +#include "imobiledeviceconnection.h" +#include "core/application.h" +#include "core/utilities.h" + +#include + +AfcDevice::AfcDevice(const QUrl &url, DeviceLister* lister, const QString &unique_id, DeviceManager *manager, Application *app, int database_id, bool first_time) + : GPodDevice(url, lister, unique_id, manager, app, database_id, first_time), transfer_(NULL) +{ +} + +AfcDevice::~AfcDevice() { + Utilities::RemoveRecursive(local_path_); +} + +void AfcDevice::Init() { + + // Make a new temporary directory for the iTunesDB. We copy it off the iPod + // so that libgpod can have a local directory to use. + local_path_ = Utilities::MakeTempDir(); + InitBackendDirectory(local_path_, first_time_, false); + model_->Init(); + + transfer_ = new AfcTransfer(url_.host(), local_path_, app_->task_manager(), shared_from_this()); + transfer_->moveToThread(loader_thread_); + + connect(transfer_, SIGNAL(TaskStarted(int)), SIGNAL(TaskStarted(int))); + connect(transfer_, SIGNAL(CopyFinished(bool)), SLOT(CopyFinished(bool))); + connect(loader_thread_, SIGNAL(started()), transfer_, SLOT(CopyFromDevice())); + loader_thread_->start(); + +} + +void AfcDevice::CopyFinished(bool success) { + + transfer_->deleteLater(); + transfer_ = NULL; + + if (!success) { + app_->AddError(tr("An error occurred copying the iTunes database from the device")); + return; + } + + // Now load the songs from the local database + loader_ = new GPodLoader(local_path_, app_->task_manager(), backend_, + shared_from_this()); + loader_->set_music_path_prefix("afc://" + url_.host()); + loader_->set_song_type(Song::Type_Stream); + loader_->moveToThread(loader_thread_); + + connect(loader_, SIGNAL(Error(QString)), SIGNAL(Error(QString))); + connect(loader_, SIGNAL(TaskStarted(int)), SIGNAL(TaskStarted(int))); + connect(loader_, SIGNAL(LoadFinished(Itdb_iTunesDB*)), SLOT(LoadFinished(Itdb_iTunesDB*))); + QMetaObject::invokeMethod(loader_, "LoadDatabase"); + +} + +bool AfcDevice::StartCopy(QList *supported_types) { + + GPodDevice::StartCopy(supported_types); + connection_.reset(new iMobileDeviceConnection(url_.host())); + + return true; + +} + +bool AfcDevice::CopyToStorage(const CopyJob &job) { + + Q_ASSERT(db_); + + Itdb_Track *track = AddTrackToITunesDb(job.metadata_); + + // Get an unused filename on the device + QString dest = connection_->GetUnusedFilename(db_, job.metadata_); + if (dest.isEmpty()) { + itdb_track_remove(track); + return false; + } + + // Copy the file + { + QFile source_file(job.source_); + AfcFile dest_file(connection_.get(), dest); + if (!Utilities::Copy(&source_file, &dest_file)) + return false; + } + + track->transferred = 1; + + // Set the filetype_marker + QString suffix = dest.section('.', -1, -1).toUpper(); + track->filetype_marker = 0; + for (int i=0 ; i<4 ; ++i) { + track->filetype_marker = track->filetype_marker << 8; + if (i >= suffix.length()) + track->filetype_marker |= ' '; + else + track->filetype_marker |= suffix[i].toLatin1(); + } + + // Set the filename + track->ipod_path = strdup(dest.toUtf8().constData()); + itdb_filename_fs2ipod(track->ipod_path); + + AddTrackToModel(track, "afc://" + url_.host()); + + // Remove the original if it was requested + if (job.remove_original_) { + QFile::remove(job.source_); + } + + return true; + +} + +void AfcDevice::FinishCopy(bool success) { + + // Temporarily unset the GUID so libgpod doesn't lock the device for syncing + itdb_device_set_sysinfo(db_->device, "FirewireGuid", NULL); + + GPodDevice::FinishCopy(success); + + // Close the connection to the device + connection_.reset(); + +} + +void AfcDevice::FinaliseDatabase() { + + // Set the GUID again to lock the device for syncing + itdb_device_set_sysinfo(db_->device, "FirewireGuid", url_.host().toUtf8().constData()); + + // Copy the files back to the iPod + // No need to start another thread since we're already in the organiser thread + AfcTransfer transfer(url_.host(), local_path_, NULL, shared_from_this()); + + itdb_start_sync(db_); + bool success = transfer.CopyToDevice(connection_.get()); + itdb_stop_sync(db_); + + if (!success) { + app_->AddError(tr("An error occurred copying the iTunes database onto the device")); + return; + } + +} + +bool AfcDevice::DeleteFromStorage(const DeleteJob &job) { + + const QString path = job.metadata_.url().toLocalFile(); + + if (!RemoveTrackFromITunesDb(path)) + return false; + + // Remove the file + if (afc_remove_path(connection_->afc(), path.toUtf8().constData()) != AFC_E_SUCCESS) + return false; + + // Remove it from our collection model + songs_to_remove_ << job.metadata_; + + return true; + +} diff --git a/src/device/afcdevice.h b/src/device/afcdevice.h new file mode 100644 index 00000000..265f54a4 --- /dev/null +++ b/src/device/afcdevice.h @@ -0,0 +1,72 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef AFCDEVICE_H +#define AFCDEVICE_H + +#include "config.h" + +#include "gpoddevice.h" + +#include +#include + +#include + +#include + +class AfcTransfer; +class GPodLoader; +class iMobileDeviceConnection; + +class AfcDevice : public GPodDevice { + Q_OBJECT + +public: + Q_INVOKABLE AfcDevice(const QUrl &url, DeviceLister *lister, const QString &unique_id, DeviceManager *manager, Application *app, int database_id, bool first_time); + ~AfcDevice(); + + void Init(); + + static QStringList url_schemes() { return QStringList() << "afc"; } + + bool StartCopy(QList *supported_types); + bool CopyToStorage(const CopyJob &job); + void FinishCopy(bool success); + + bool DeleteFromStorage(const DeleteJob &job); + +protected: + void FinaliseDatabase(); + +private slots: + void CopyFinished(bool success); + +private: + void RemoveRecursive(const QString &path); + +private: + AfcTransfer *transfer_; + boost::scoped_ptr connection_; + + QString local_path_; +}; + +#endif // AFCDEVICE_H diff --git a/src/device/afcfile.cpp b/src/device/afcfile.cpp new file mode 100644 index 00000000..5280dae7 --- /dev/null +++ b/src/device/afcfile.cpp @@ -0,0 +1,91 @@ +#include "config.h" + +#include "afcfile.h" +#include "imobiledeviceconnection.h" + +#include + +AfcFile::AfcFile(iMobileDeviceConnection *connection, const QString &path, QObject *parent) + : QIODevice(parent), + connection_(connection), + handle_(0), + path_(path) +{ +} + +AfcFile::~AfcFile() { + close(); +} + +bool AfcFile::open(QIODevice::OpenMode mode) { + + afc_file_mode_t afc_mode; + switch (mode) { + case ReadOnly: + afc_mode = AFC_FOPEN_RDONLY; + break; + case WriteOnly: + afc_mode = AFC_FOPEN_WRONLY; + break; + case ReadWrite: + afc_mode = AFC_FOPEN_RW; + break; + + default: + afc_mode = AFC_FOPEN_RW; + } + afc_error_t err = afc_file_open( + connection_->afc(), path_.toUtf8().constData(), afc_mode, &handle_); + if (err != AFC_E_SUCCESS) { + return false; + } + + return QIODevice::open(mode); + +} + +void AfcFile::close() { + + if (handle_) { + afc_file_close(connection_->afc(), handle_); + } + QIODevice::close(); + +} + +bool AfcFile::seek(qint64 pos) { + + afc_error_t err = afc_file_seek(connection_->afc(), handle_, pos, SEEK_SET); + if (err != AFC_E_SUCCESS) { + return false; + } + QIODevice::seek(pos); + return true; + +} + +qint64 AfcFile::readData(char *data, qint64 max_size) { + + uint32_t bytes_read = 0; + afc_error_t err = afc_file_read(connection_->afc(), handle_, data, max_size, &bytes_read); + if (err != AFC_E_SUCCESS) { + return -1; + } + return bytes_read; + +} + +qint64 AfcFile::writeData(const char *data, qint64 max_size) { + + uint32_t bytes_written = 0; + afc_error_t err = afc_file_write(connection_->afc(), handle_, data, max_size, &bytes_written); + if (err != AFC_E_SUCCESS) { + return -1; + } + return bytes_written; + +} + +qint64 AfcFile::size() const { + return connection_->GetFileInfo(path_, "st_size").toLongLong(); +} diff --git a/src/device/afcfile.h b/src/device/afcfile.h new file mode 100644 index 00000000..36ef4365 --- /dev/null +++ b/src/device/afcfile.h @@ -0,0 +1,38 @@ +#ifndef AFCFILE_H +#define AFCFILE_H + +#include "config.h" + +#include + +#include + +#include + +class iMobileDeviceConnection; + +class AfcFile : public QIODevice { + Q_OBJECT + +public: + AfcFile(iMobileDeviceConnection* connection, const QString &path, QObject *parent = 0); + ~AfcFile(); + + // QIODevice + void close(); + bool open(OpenMode mode); + bool seek(qint64 pos); + qint64 size() const; + +private: + // QIODevice + qint64 readData(char *data, qint64 max_size); + qint64 writeData(const char *data, qint64 max_size); + + iMobileDeviceConnection *connection_; + uint64_t handle_; + + QString path_; +}; + +#endif diff --git a/src/device/afctransfer.cpp b/src/device/afctransfer.cpp new file mode 100644 index 00000000..cfbe4b63 --- /dev/null +++ b/src/device/afctransfer.cpp @@ -0,0 +1,148 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "afcfile.h" +#include "afctransfer.h" +#include "imobiledeviceconnection.h" +#include "core/taskmanager.h" +#include "core/utilities.h" + +#include +#include + +#include + +AfcTransfer::AfcTransfer(const QString &uuid, const QString &local_destination, TaskManager *task_manager, boost::shared_ptr device) + : QObject(NULL), device_(device), task_manager_(task_manager), uuid_(uuid), local_destination_(local_destination) +{ + + original_thread_ = thread(); + + important_directories_ << "/iTunes_Control/Artwork"; + important_directories_ << "/iTunes_Control/Device"; + important_directories_ << "/iTunes_Control/iTunes"; +} + +AfcTransfer::~AfcTransfer() { +} + +void AfcTransfer::CopyFromDevice() { + + int task_id = 0; + if (task_manager_) { + task_id = task_manager_->StartTask(tr("Copying iPod database")); + emit TaskStarted(task_id); + } + + // Connect to the device + iMobileDeviceConnection c(uuid_); + + // Copy directories. If one fails we stop. + bool success = true; + foreach (const QString &dir, important_directories_) { + if (!CopyDirFromDevice(&c, dir)) { + success = false; + break; + } + } + + if (task_manager_) { + moveToThread(original_thread_); + task_manager_->SetTaskFinished(task_id); + emit CopyFinished(success); + } + +} + +bool AfcTransfer::CopyToDevice(iMobileDeviceConnection *connection) { + + // Connect to the device + if (!connection) + connection = new iMobileDeviceConnection(uuid_); + + foreach (const QString &dir, important_directories_) + if (!CopyDirToDevice(connection, dir)) + return false; + + return true; + +} + +bool AfcTransfer::CopyDirFromDevice(iMobileDeviceConnection *c, const QString &path) { + + foreach (const QString &filename, c->ReadDirectory(path, QDir::Files | QDir::NoDotAndDotDot)) { + if (!CopyFileFromDevice(c, path + "/" + filename)) + return false; + } + + foreach (const QString &dir, c->ReadDirectory(path, QDir::Dirs | QDir::NoDotAndDotDot)) { + if (!CopyDirFromDevice(c, path + "/" + dir)) + return false; + } + return true; + +} + +bool AfcTransfer::CopyDirToDevice(iMobileDeviceConnection *c, const QString &path) { + + QDir dir(local_destination_ + path); + + if (!c->Exists(path)) { + c->MkDir(path); + } + + foreach (const QString &filename, dir.entryList(QDir::Files | QDir::NoDotAndDotDot)) { + if (!CopyFileToDevice(c, path + "/" + filename)) + return false; + } + + foreach (const QString &dir, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + if (!CopyDirToDevice(c, path + "/" + dir)) + return false; + } + return true; + +} + +bool AfcTransfer::CopyFileFromDevice(iMobileDeviceConnection *c, const QString &path) { + + QString local_filename = local_destination_ + path; + QString local_dir = local_filename.section('/', 0, -2); + + QDir d; + d.mkpath(local_dir); + + QFile dest(local_filename); + AfcFile source(c, path); + + return Utilities::Copy(&source, &dest); + +} + +bool AfcTransfer::CopyFileToDevice(iMobileDeviceConnection *c, const QString &path) { + + QFile source(local_destination_ + path); + AfcFile dest(c, path); + + return Utilities::Copy(&source, &dest); + +} diff --git a/src/device/afctransfer.h b/src/device/afctransfer.h new file mode 100644 index 00000000..1fff548f --- /dev/null +++ b/src/device/afctransfer.h @@ -0,0 +1,70 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef AFCTRANSFER_H +#define AFCTRANSFER_H + +#include "config.h" + +#include +#include + +#include + +class ConnectedDevice; +class iMobileDeviceConnection; +class TaskManager; + +class QIODevice; + +class AfcTransfer : public QObject { + Q_OBJECT + +public: + AfcTransfer(const QString &uuid, const QString &local_destination, TaskManager *task_manager, boost::shared_ptr device); + ~AfcTransfer(); + + bool CopyToDevice(iMobileDeviceConnection *connection); + +public slots: + void CopyFromDevice(); + +signals: + void TaskStarted(int task_id); + void CopyFinished(bool success); + +private: + bool CopyDirFromDevice(iMobileDeviceConnection *c, const QString &path); + bool CopyDirToDevice(iMobileDeviceConnection *c, const QString &path); + bool CopyFileFromDevice(iMobileDeviceConnection *c, const QString &path); + bool CopyFileToDevice(iMobileDeviceConnection *c, const QString &path); + +private: + boost::shared_ptr device_; + QThread *original_thread_; + + TaskManager *task_manager_; + QString uuid_; + QString local_destination_; + + QStringList important_directories_; +}; + +#endif // AFCTRANSFER_H diff --git a/src/device/cddadevice.cpp b/src/device/cddadevice.cpp new file mode 100644 index 00000000..698169d5 --- /dev/null +++ b/src/device/cddadevice.cpp @@ -0,0 +1,65 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include "collection/collectionbackend.h" +#include "collection/collectionmodel.h" + +#include "cddadevice.h" + +CddaDevice::CddaDevice(const QUrl &url, DeviceLister *lister, const QString &unique_id, DeviceManager *manager, Application *app, int database_id, bool first_time) + : ConnectedDevice(url, lister, unique_id, manager, app, database_id, first_time), cdda_song_loader_(url) { + + connect(&cdda_song_loader_, SIGNAL(SongsLoaded(SongList)), this, SLOT(SongsLoaded(SongList))); + connect(&cdda_song_loader_, SIGNAL(SongsDurationLoaded(SongList)), this, SLOT(SongsLoaded(SongList))); + connect(&cdda_song_loader_, SIGNAL(SongsMetadataLoaded(SongList)), this, SLOT(SongsLoaded(SongList))); + connect(this, SIGNAL(SongsDiscovered(SongList)), model_, SLOT(SongsDiscovered(SongList))); + +} + +CddaDevice::~CddaDevice() {} + +void CddaDevice::Init() { + + song_count_ = 0; // Reset song count, in case it was already set + cdda_song_loader_.LoadSongs(); + +} + +void CddaDevice::Refresh() { + + if (!cdda_song_loader_.HasChanged()) { + return; + } + Init(); + +} + +void CddaDevice::SongsLoaded(const SongList &songs) { + + model_->Reset(); + emit SongsDiscovered(songs); + song_count_ = songs.size(); + +} + diff --git a/src/device/cddadevice.h b/src/device/cddadevice.h new file mode 100644 index 00000000..a5ffd8bd --- /dev/null +++ b/src/device/cddadevice.h @@ -0,0 +1,62 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef CDDADEVICE_H +#define CDDADEVICE_H + +#include "config.h" + +#include + +// These must come after Qt includes +#include +#include + +#include "cddasongloader.h" +#include "connecteddevice.h" +#include "core/song.h" +#include "musicbrainz/musicbrainzclient.h" + +class CddaDevice : public ConnectedDevice { + Q_OBJECT + + public: + Q_INVOKABLE CddaDevice(const QUrl &url, DeviceLister *lister, const QString &unique_id, DeviceManager *manager, Application *app, int database_id, bool first_time); + ~CddaDevice(); + + void Init(); + void Refresh(); + bool CopyToStorage(const MusicStorage::CopyJob&) { return false; } + bool DeleteFromStorage(const MusicStorage::DeleteJob&) { return false; } + + static QStringList url_schemes() { return QStringList() << "cdda"; } + +signals: + void SongsDiscovered(const SongList &songs); + + private slots: + void SongsLoaded(const SongList &songs); + + private: + CddaSongLoader cdda_song_loader_; +}; + +#endif + diff --git a/src/device/cddalister.cpp b/src/device/cddalister.cpp new file mode 100644 index 00000000..61ad7d2e --- /dev/null +++ b/src/device/cddalister.cpp @@ -0,0 +1,131 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include + +// This must come after Qt includes +#include + +#include "cddalister.h" +#include "core/logging.h" +#include "core/song.h" + +QStringList CddaLister::DeviceUniqueIDs() { return devices_list_; } + +QVariantList CddaLister::DeviceIcons(const QString &) { + QVariantList icons; + icons << QString("cd"); + return icons; +} + +QString CddaLister::DeviceManufacturer(const QString &id) { + + CdIo_t* cdio = cdio_open(id.toLocal8Bit().constData(), DRIVER_DEVICE); + cdio_hwinfo_t cd_info; + if (cdio_get_hwinfo(cdio, &cd_info)) { + cdio_destroy(cdio); + return QString(cd_info.psz_vendor); + } + cdio_destroy(cdio); + return QString(); + +} + +QString CddaLister::DeviceModel(const QString &id) { + + CdIo_t* cdio = cdio_open(id.toLocal8Bit().constData(), DRIVER_DEVICE); + cdio_hwinfo_t cd_info; + if (cdio_get_hwinfo(cdio, &cd_info)) { + cdio_destroy(cdio); + return QString(cd_info.psz_model); + } + cdio_destroy(cdio); + return QString(); + +} + +quint64 CddaLister::DeviceCapacity(const QString &) { return 0; } + +quint64 CddaLister::DeviceFreeSpace(const QString &) { return 0; } + +QVariantMap CddaLister::DeviceHardwareInfo(const QString &) { + return QVariantMap(); +} + +QString CddaLister::MakeFriendlyName(const QString &id) { + + CdIo_t* cdio = cdio_open(id.toLocal8Bit().constData(), DRIVER_DEVICE); + cdio_hwinfo_t cd_info; + if (cdio_get_hwinfo(cdio, &cd_info)) { + cdio_destroy(cdio); + return QString(cd_info.psz_model); + } + cdio_destroy(cdio); + return QString("CD (") + id + ")"; + +} + +QList CddaLister::MakeDeviceUrls(const QString &id) { + return QList() << QUrl("cdda://" + id); +} + +void CddaLister::UnmountDevice(const QString &id) { + cdio_eject_media_drive(id.toLocal8Bit().constData()); +} + +void CddaLister::UpdateDeviceFreeSpace(const QString&) {} + +void CddaLister::Init() { + + cdio_init(); +#ifdef Q_OS_DARWIN + if (!cdio_have_driver(DRIVER_OSX)) { + qLog(Error) << "libcdio was compiled without support for OS X!"; + } +#endif + char** devices = cdio_get_devices(DRIVER_DEVICE); + if (!devices) { + qLog(Debug) << "No CD devices found"; + return; + } + for (; *devices != nullptr; ++devices) { + QString device(*devices); + QFileInfo device_info(device); + if (device_info.isSymLink()) { + device = device_info.symLinkTarget(); + } +#ifdef Q_OS_DARWIN + // Every track is detected as a separate device on Darwin. The raw disk looks like /dev/rdisk1 + if (!device.contains(QRegExp("^/dev/rdisk[0-9]$"))) { + continue; + } +#endif + if (!devices_list_.contains(device)) { + devices_list_ << device; + emit DeviceAdded(device); + } + } + +} diff --git a/src/device/cddalister.h b/src/device/cddalister.h new file mode 100644 index 00000000..da611054 --- /dev/null +++ b/src/device/cddalister.h @@ -0,0 +1,53 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef CDDALISTER_H +#define CDDALISTER_H + +#include "config.h" + +#include + +#include "devicelister.h" + +class CddaLister : public DeviceLister { + Q_OBJECT + + public: + CddaLister() {} + + QStringList DeviceUniqueIDs(); + QVariantList DeviceIcons(const QString &id); + QString DeviceManufacturer(const QString &id); + QString DeviceModel(const QString &id); + quint64 DeviceCapacity(const QString &id); + quint64 DeviceFreeSpace(const QString &id); + QVariantMap DeviceHardwareInfo(const QString &id); + bool AskForScan(const QString&) const { return false; } + QString MakeFriendlyName(const QString&); + QList MakeDeviceUrls(const QString&); + void UnmountDevice(const QString&); + void UpdateDeviceFreeSpace(const QString&); + void Init(); + + private: + QStringList devices_list_; +}; +#endif // CDDALISTER_H diff --git a/src/device/cddasongloader.cpp b/src/device/cddasongloader.cpp new file mode 100644 index 00000000..9deb5043 --- /dev/null +++ b/src/device/cddasongloader.cpp @@ -0,0 +1,216 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include + +#include "cddasongloader.h" + +#include "core/logging.h" +#include "core/timeconstants.h" + +CddaSongLoader::CddaSongLoader(const QUrl &url, QObject *parent) + : QObject(parent), + url_(url), + cdda_(nullptr), + cdio_(nullptr) {} + +CddaSongLoader::~CddaSongLoader() { + if (cdio_) cdio_destroy(cdio_); +} + +QUrl CddaSongLoader::GetUrlFromTrack(int track_number) const { + + if (url_.isEmpty()) { + return QUrl(QString("cdda://%1").arg(track_number)); + } + else { + return QUrl(QString("cdda://%1/%2").arg(url_.path()).arg(track_number)); + } + +} + +void CddaSongLoader::LoadSongs() { + + QMutexLocker locker(&mutex_load_); + cdio_ = cdio_open(url_.path().toLocal8Bit().constData(), DRIVER_DEVICE); + if (cdio_ == nullptr) { + return; + } + // Create gstreamer cdda element + GError *error = nullptr; + cdda_ = gst_element_make_from_uri(GST_URI_SRC, "cdda://", nullptr, &error); + if (error) { + qLog(Error) << error->code << error->message; + } + if (cdda_ == nullptr) { + return; + } + + if (!url_.isEmpty()) { + g_object_set(cdda_, "device", g_strdup(url_.path().toLocal8Bit().constData()), nullptr); + } + if (g_object_class_find_property (G_OBJECT_GET_CLASS (cdda_), "paranoia-mode")) { + g_object_set (cdda_, "paranoia-mode", 0, NULL); + } + + // Change the element's state to ready and paused, to be able to query it + if (gst_element_set_state(cdda_, GST_STATE_READY) == GST_STATE_CHANGE_FAILURE || gst_element_set_state(cdda_, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) { + gst_element_set_state(cdda_, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(cdda_)); + return; + } + + // Get number of tracks + GstFormat fmt = gst_format_get_by_nick("track"); + GstFormat out_fmt = fmt; + gint64 num_tracks = 0; + if (!gst_element_query_duration(cdda_, out_fmt, &num_tracks) || out_fmt != fmt) { + qLog(Error) << "Error while querying cdda GstElement"; + gst_object_unref(GST_OBJECT(cdda_)); + return; + } + + SongList songs; + for (int track_number = 1; track_number <= num_tracks; track_number++) { + // Init song + Song song; + song.set_id(track_number); + song.set_valid(true); + song.set_filetype(Song::Type_Cdda); + song.set_url(GetUrlFromTrack(track_number)); + song.set_title(QString("Track %1").arg(track_number)); + song.set_track(track_number); + songs << song; + } + emit SongsLoaded(songs); + + + gst_tag_register_musicbrainz_tags(); + + GstElement *pipeline = gst_pipeline_new("pipeline"); + GstElement *sink = gst_element_factory_make ("fakesink", NULL); + gst_bin_add_many (GST_BIN (pipeline), cdda_, sink, NULL); + gst_element_link (cdda_, sink); + gst_element_set_state(pipeline, GST_STATE_READY); + gst_element_set_state(pipeline, GST_STATE_PAUSED); + + // Get TOC and TAG messages + GstMessage *msg = nullptr; + GstMessage *msg_toc = nullptr; + GstMessage *msg_tag = nullptr; + while ((msg = gst_bus_timed_pop_filtered(GST_ELEMENT_BUS(pipeline), GST_SECOND, (GstMessageType)(GST_MESSAGE_TOC | GST_MESSAGE_TAG)))) { + if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TOC) { + if (msg_toc) gst_message_unref(msg_toc); // Shouldn't happen, but just in case + msg_toc = msg; + } else if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TAG) { + if (msg_tag) gst_message_unref(msg_tag); + msg_tag = msg; + } + } + + // Handle TOC message: get tracks duration + if (msg_toc) { + GstToc *toc; + gst_message_parse_toc (msg_toc, &toc, nullptr); + if (toc) { + GList *entries = gst_toc_get_entries(toc); + if (entries && songs.size() <= g_list_length (entries)) { + int i = 0; + for (GList *node = entries; node != nullptr; node = node->next) { + GstTocEntry *entry = static_cast(node->data); + quint64 duration = 0; + gint64 start, stop; + if (gst_toc_entry_get_start_stop_times (entry, &start, &stop)) duration = stop - start; + songs[i++].set_length_nanosec(duration); + } + } + } + gst_message_unref(msg_toc); + } + emit SongsDurationLoaded(songs); + + // Handle TAG message: generate MusicBrainz DiscId + if (msg_tag) { + GstTagList *tags = nullptr; + gst_message_parse_tag(msg_tag, &tags); + char *string_mb = nullptr; + if (gst_tag_list_get_string(tags, GST_TAG_CDDA_MUSICBRAINZ_DISCID, &string_mb)) { + QString musicbrainz_discid(string_mb); + qLog(Info) << "MusicBrainz discid: " << musicbrainz_discid; + + MusicBrainzClient *musicbrainz_client = new MusicBrainzClient; + connect(musicbrainz_client, SIGNAL(Finished(const QString&, const QString&, MusicBrainzClient::ResultList)), SLOT(AudioCDTagsLoaded(const QString&, const QString&, MusicBrainzClient::ResultList))); + musicbrainz_client->StartDiscIdRequest(musicbrainz_discid); + g_free(string_mb); + gst_message_unref(msg_tag); + gst_tag_list_free(tags); + } + } + + gst_element_set_state(pipeline, GST_STATE_NULL); + // This will also cause cdda_ to be unref'd. + gst_object_unref(pipeline); + +} + +void CddaSongLoader::AudioCDTagsLoaded(const QString &artist, const QString &album, const MusicBrainzClient::ResultList &results) { + + MusicBrainzClient *musicbrainz_client = qobject_cast(sender()); + musicbrainz_client->deleteLater(); + SongList songs; + if (results.size() == 0) return; + int track_number = 1; + for (const MusicBrainzClient::Result &ret : results) { + Song song; + song.set_artist(artist); + song.set_album(album); + song.set_title(ret.title_); + song.set_length_nanosec(ret.duration_msec_ * kNsecPerMsec); + song.set_track(track_number); + song.set_year(ret.year_); + song.set_id(track_number); + song.set_filetype(Song::Type_Cdda); + song.set_valid(true); + // We need to set url: that's how playlist will find the correct item to + // update + song.set_url(GetUrlFromTrack(track_number++)); + songs << song; + } + emit SongsMetadataLoaded(songs); + +} + +bool CddaSongLoader::HasChanged() { + + if ((cdio_ && cdda_) && cdio_get_media_changed(cdio_) != 1) { + return false; + } + // Check if mutex is already token (i.e. init is already taking place) + if (!mutex_load_.tryLock()) { + return false; + } + mutex_load_.unlock(); + return true; + +} + diff --git a/src/device/cddasongloader.h b/src/device/cddasongloader.h new file mode 100644 index 00000000..150e46a6 --- /dev/null +++ b/src/device/cddasongloader.h @@ -0,0 +1,72 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef CDDASONGLOADER_H +#define CDDASONGLOADER_H + +#include "config.h" + +#include +#include +#include + +// These must come after Qt includes (issue 3247) +#include +#include + +#include "core/song.h" +#include "musicbrainz/musicbrainzclient.h" + +// This class provides a (hopefully) nice, high level interface to get CD +// information and load tracks +class CddaSongLoader : public QObject { + Q_OBJECT + + public: + CddaSongLoader( + // Url of the CD device. Will use the default device if empty + const QUrl &url = QUrl(), + QObject *parent = nullptr); + ~CddaSongLoader(); + + // Load songs. + // Signals declared below will be emitted anytime new information will be available. + void LoadSongs(); + bool HasChanged(); + + signals: + void SongsLoaded(const SongList &songs); + void SongsDurationLoaded(const SongList &songs); + void SongsMetadataLoaded(const SongList &songs); + + private slots: + void AudioCDTagsLoaded(const QString &artist, const QString &album, const MusicBrainzClient::ResultList &results); + + private: + QUrl GetUrlFromTrack(int track_number) const; + + QUrl url_; + GstElement *cdda_; + CdIo_t *cdio_; + QMutex mutex_load_; +}; + +#endif // CDDASONGLOADER_H + diff --git a/src/device/connecteddevice.cpp b/src/device/connecteddevice.cpp new file mode 100644 index 00000000..6f2efe56 --- /dev/null +++ b/src/device/connecteddevice.cpp @@ -0,0 +1,121 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "connecteddevice.h" +#include "devicelister.h" +#include "devicemanager.h" +#include "core/application.h" +#include "core/database.h" +#include "core/logging.h" +#include "collection/collection.h" +#include "collection/collectionbackend.h" +#include "collection/collectionmodel.h" + +#include + +ConnectedDevice::ConnectedDevice(const QUrl &url, DeviceLister *lister, const QString &unique_id, DeviceManager *manager, Application *app, int database_id, bool first_time) + : QObject(manager), + app_(app), + url_(url), + first_time_(first_time), + lister_(lister), + unique_id_(unique_id), + database_id_(database_id), + manager_(manager), + backend_(nullptr), + model_(nullptr), + song_count_(0) { + + qLog(Info) << "connected" << url << unique_id << first_time; + + // Create the backend in the database thread. + backend_ = new CollectionBackend(); + backend_->moveToThread(app_->database()->thread()); + + connect(backend_, SIGNAL(TotalSongCountUpdated(int)), SLOT(BackendTotalSongCountUpdated(int))); + + backend_->Init(app_->database(), + QString("device_%1_songs").arg(database_id), + QString("device_%1_directories").arg(database_id), + QString("device_%1_subdirectories").arg(database_id), + QString("device_%1_fts").arg(database_id)); + + // Create the model + model_ = new CollectionModel(backend_, app_, this); +} + +ConnectedDevice::~ConnectedDevice() { backend_->deleteLater(); } + +void ConnectedDevice::InitBackendDirectory(const QString &mount_point, bool first_time, bool rewrite_path) { + + if (first_time || backend_->GetAllDirectories().isEmpty()) { + backend_->AddDirectory(mount_point); + } + else { + if (rewrite_path) { + // This is a bit of a hack. The device might not be mounted at the same + // path each time, so if it's different we have to munge all the paths in + // the database to fix it. This can be done entirely in sqlite so it's + // relatively fast... + + // Get the directory it was mounted at last time. Devices only have one + // directory (the root). + Directory dir = backend_->GetAllDirectories()[0]; + if (dir.path != mount_point) { + // The directory is different, commence the munging. + qLog(Info) << "Changing path from" << dir.path << "to" << mount_point; + backend_->ChangeDirPath(dir.id, dir.path, mount_point); + } + } + + // Load the directory properly now + backend_->LoadDirectoriesAsync(); + } +} + +void ConnectedDevice::Eject() { + manager_->UnmountAsync(manager_->FindDeviceById(unique_id_)); +} + +void ConnectedDevice::FinishCopy(bool) { + lister_->UpdateDeviceFreeSpace(unique_id_); +} + +void ConnectedDevice::FinishDelete(bool) { + lister_->UpdateDeviceFreeSpace(unique_id_); +} + +MusicStorage::TranscodeMode ConnectedDevice::GetTranscodeMode() const { + int index = manager_->FindDeviceById(unique_id_); + return MusicStorage::TranscodeMode(manager_->index(index).data(DeviceManager::Role_TranscodeMode).toInt()); +} + +Song::FileType ConnectedDevice::GetTranscodeFormat() const { + int index = manager_->FindDeviceById(unique_id_); + return Song::FileType(manager_->index(index).data(DeviceManager::Role_TranscodeFormat).toInt()); +} + +void ConnectedDevice::BackendTotalSongCountUpdated(int count) { + song_count_ = count; + emit SongCountUpdated(count); +} + diff --git a/src/device/connecteddevice.h b/src/device/connecteddevice.h new file mode 100644 index 00000000..2aac69e9 --- /dev/null +++ b/src/device/connecteddevice.h @@ -0,0 +1,97 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef CONNECTEDDEVICE_H +#define CONNECTEDDEVICE_H + +#include "config.h" + +#include + +#include +#include +#include + +#include "core/musicstorage.h" +#include "core/song.h" + +class Application; +class Database; +class DeviceLister; +class DeviceManager; +class CollectionBackend; +class CollectionModel; + +class ConnectedDevice : public QObject, + public virtual MusicStorage, + public std::enable_shared_from_this { + Q_OBJECT + + public: + ConnectedDevice(const QUrl &url, DeviceLister *lister, const QString &unique_id, DeviceManager *manager, Application *app, int database_id, bool first_time); + ~ConnectedDevice(); + + virtual void Init() = 0; + // For some devices (e.g. CD devices) we don't have callbacks to be notified + // when something change: we can call this method to refresh device's state + virtual void Refresh() {} + + virtual TranscodeMode GetTranscodeMode() const; + virtual Song::FileType GetTranscodeFormat() const; + + DeviceLister *lister() const { return lister_; } + QString unique_id() const { return unique_id_; } + CollectionModel *model() const { return model_; } + QUrl url() const { return url_; } + int song_count() const { return song_count_; } + + virtual void FinishCopy(bool success); + virtual void FinishDelete(bool success); + + virtual void Eject(); + +signals: + void TaskStarted(int id); + void SongCountUpdated(int count); + + protected: + void InitBackendDirectory(const QString &mount_point, bool first_time, bool rewrite_path = true); + + protected: + Application *app_; + + QUrl url_; + bool first_time_; + DeviceLister *lister_; + QString unique_id_; + int database_id_; + DeviceManager *manager_; + + CollectionBackend *backend_; + CollectionModel *model_; + + int song_count_; + + private slots: + void BackendTotalSongCountUpdated(int count); +}; + +#endif // CONNECTEDDEVICE_H + diff --git a/src/device/devicedatabasebackend.cpp b/src/device/devicedatabasebackend.cpp new file mode 100644 index 00000000..1f2b9171 --- /dev/null +++ b/src/device/devicedatabasebackend.cpp @@ -0,0 +1,146 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "devicedatabasebackend.h" +#include "core/database.h" +#include "core/scopedtransaction.h" + +#include +#include +#include + +const int DeviceDatabaseBackend::kDeviceSchemaVersion = 0; + +DeviceDatabaseBackend::DeviceDatabaseBackend(QObject *parent) + : QObject(parent) {} + +void DeviceDatabaseBackend::Init(Database* db) { db_ = db; } + +DeviceDatabaseBackend::DeviceList DeviceDatabaseBackend::GetAllDevices() { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + DeviceList ret; + + QSqlQuery q(db); + q.prepare("SELECT ROWID, unique_id, friendly_name, size, icon, transcode_mode, transcode_format FROM devices"); + q.exec(); + if (db_->CheckErrors(q)) return ret; + + while (q.next()) { + Device dev; + dev.id_ = q.value(0).toInt(); + dev.unique_id_ = q.value(1).toString(); + dev.friendly_name_ = q.value(2).toString(); + dev.size_ = q.value(3).toLongLong(); + dev.icon_name_ = q.value(4).toString(); + dev.transcode_mode_ = MusicStorage::TranscodeMode(q.value(5).toInt()); + dev.transcode_format_ = Song::FileType(q.value(6).toInt()); + ret << dev; + } + return ret; + +} + +int DeviceDatabaseBackend::AddDevice(const Device &device) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + ScopedTransaction t(&db); + + // Insert the device into the devices table + QSqlQuery q(db); + q.prepare("INSERT INTO devices (unique_id, friendly_name, size, icon, transcode_mode, transcode_format) VALUES (:unique_id, :friendly_name, :size, :icon, :transcode_mode, :transcode_format)"); + q.bindValue(":unique_id", device.unique_id_); + q.bindValue(":friendly_name", device.friendly_name_); + q.bindValue(":size", device.size_); + q.bindValue(":icon", device.icon_name_); + q.bindValue(":transcode_mode", device.transcode_mode_); + q.bindValue(":transcode_format", device.transcode_format_); + q.exec(); + if (db_->CheckErrors(q)) return -1; + int id = q.lastInsertId().toInt(); + + // Create the songs tables for the device + QString filename(":schema/device-schema.sql"); + QFile schema_file(filename); + if (!schema_file.open(QIODevice::ReadOnly)) + qFatal("Couldn't open schema file %s", filename.toUtf8().constData()); + QString schema = QString::fromUtf8(schema_file.readAll()); + schema.replace("%deviceid", QString::number(id)); + + db_->ExecSchemaCommands(db, schema, 0, true); + + t.Commit(); + return id; + +} + +void DeviceDatabaseBackend::RemoveDevice(int id) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + ScopedTransaction t(&db); + + // Remove the device from the devices table + QSqlQuery q(db); + q.prepare("DELETE FROM devices WHERE ROWID=:id"); + q.bindValue(":id", id); + q.exec(); + if (db_->CheckErrors(q)) return; + + // Remove the songs tables for the device + db.exec(QString("DROP TABLE device_%1_songs").arg(id)); + db.exec(QString("DROP TABLE device_%1_fts").arg(id)); + db.exec(QString("DROP TABLE device_%1_directories").arg(id)); + db.exec(QString("DROP TABLE device_%1_subdirectories").arg(id)); + + t.Commit(); + +} + +void DeviceDatabaseBackend::SetDeviceOptions(int id, const QString &friendly_name, const QString &icon_name, MusicStorage::TranscodeMode mode, Song::FileType format) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + QSqlQuery q(db); + q.prepare( + "UPDATE devices" + " SET friendly_name=:friendly_name," + " icon=:icon_name," + " transcode_mode=:transcode_mode," + " transcode_format=:transcode_format" + " WHERE ROWID=:id"); + q.bindValue(":friendly_name", friendly_name); + q.bindValue(":icon_name", icon_name); + q.bindValue(":transcode_mode", mode); + q.bindValue(":transcode_format", format); + q.bindValue(":id", id); + q.exec(); + db_->CheckErrors(q); + +} + diff --git a/src/device/devicedatabasebackend.h b/src/device/devicedatabasebackend.h new file mode 100644 index 00000000..5992ac10 --- /dev/null +++ b/src/device/devicedatabasebackend.h @@ -0,0 +1,68 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef DEVICEDATABASEBACKEND_H +#define DEVICEDATABASEBACKEND_H + +#include "config.h" + +#include + +#include "core/musicstorage.h" +#include "core/song.h" + +class Database; + +class DeviceDatabaseBackend : public QObject { + Q_OBJECT + + public: + Q_INVOKABLE DeviceDatabaseBackend(QObject *parent = nullptr); + + struct Device { + Device() : id_(-1) {} + + int id_; + QString unique_id_; + QString friendly_name_; + quint64 size_; + QString icon_name_; + + MusicStorage::TranscodeMode transcode_mode_; + Song::FileType transcode_format_; + }; + typedef QList DeviceList; + + static const int kDeviceSchemaVersion; + + void Init(Database *db); + Database *db() const { return db_; } + + DeviceList GetAllDevices(); + int AddDevice(const Device& device); + void RemoveDevice(int id); + + void SetDeviceOptions(int id, const QString &friendly_name, const QString &icon_name, MusicStorage::TranscodeMode mode, Song::FileType format); + + private: + Database *db_; +}; + +#endif // DEVICEDATABASEBACKEND_H diff --git a/src/device/devicekitlister.cpp b/src/device/devicekitlister.cpp new file mode 100644 index 00000000..52a41660 --- /dev/null +++ b/src/device/devicekitlister.cpp @@ -0,0 +1,290 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include + +#include + +#include "config.h" + +#include "devicekitlister.h" +#include "filesystemdevice.h" +#include "core/logging.h" +#include "core/utilities.h" +#include "dbus/udisks.h" +#include "dbus/udisksdevice.h" + +#ifdef HAVE_LIBGPOD +#include "gpoddevice.h" +#endif + +DeviceKitLister::DeviceKitLister() {} +DeviceKitLister::~DeviceKitLister() {} +//qLog(Debug) << __PRETTY_FUNCTION__; + +QString DeviceKitLister::DeviceData::unique_id() const { + return QString("DeviceKit/%1/%2/%3/%4").arg(drive_serial, drive_vendor, drive_model).arg(device_size); +} + +void DeviceKitLister::Init() { + + interface_.reset(new OrgFreedesktopUDisksInterface(OrgFreedesktopUDisksInterface::staticInterfaceName(), "/org/freedesktop/UDisks", QDBusConnection::systemBus())); + + // Get all the devices currently attached + QDBusPendingReply > reply = interface_->EnumerateDevices(); + reply.waitForFinished(); + + if (!reply.isValid()) { + qLog(Warning) << "Error enumerating DeviceKit-disks devices:" << reply.error().name() << reply.error().message(); + interface_.reset(); + return; + } + + // Listen for changes + connect(interface_.get(), SIGNAL(DeviceAdded(QDBusObjectPath)), SLOT(DBusDeviceAdded(QDBusObjectPath))); + connect(interface_.get(), SIGNAL(DeviceRemoved(QDBusObjectPath)), SLOT(DBusDeviceRemoved(QDBusObjectPath))); + connect(interface_.get(), SIGNAL(DeviceChanged(QDBusObjectPath)), SLOT(DBusDeviceChanged(QDBusObjectPath))); + + // Get information about each one + QMap device_data; + for (const QDBusObjectPath &path : reply.value()) { + DeviceData data = ReadDeviceData(path); + if (data.suitable) device_data[data.unique_id()] = data; + } + + // Update the internal cache + { + QMutexLocker l(&mutex_); + device_data_ = device_data; + } + + // Notify about the changes + for (const QString &id : device_data.keys()) { + emit DeviceAdded(id); + } + +} + +QStringList DeviceKitLister::DeviceUniqueIDs() { + QMutexLocker l(&mutex_); + return device_data_.keys(); +} + +QVariantList DeviceKitLister::DeviceIcons(const QString &id) { + + QString path = LockAndGetDeviceInfo(id, &DeviceData::device_mount_paths)[0]; + return QVariantList() + << GuessIconForPath(path) + << GuessIconForModel(DeviceManufacturer(id), DeviceModel(id)) + << LockAndGetDeviceInfo(id, &DeviceData::device_presentation_icon_name); +} + +QString DeviceKitLister::DeviceManufacturer(const QString &id) { + return LockAndGetDeviceInfo(id, &DeviceData::drive_vendor); +} + +QString DeviceKitLister::DeviceModel(const QString &id) { + return LockAndGetDeviceInfo(id, &DeviceData::drive_model); +} + +quint64 DeviceKitLister::DeviceCapacity(const QString &id) { + return LockAndGetDeviceInfo(id, &DeviceData::device_size); +} + +quint64 DeviceKitLister::DeviceFreeSpace(const QString &id) { + return LockAndGetDeviceInfo(id, &DeviceData::free_space); +} + +QVariantMap DeviceKitLister::DeviceHardwareInfo(const QString &id) { + + QVariantMap ret; + + QMutexLocker l(&mutex_); + if (!device_data_.contains(id)) return ret; + const DeviceData &data = device_data_[id]; + + ret[QT_TR_NOOP("DBus path")] = data.dbus_path; + ret[QT_TR_NOOP("Serial number")] = data.drive_serial; + ret[QT_TR_NOOP("Mount points")] = data.device_mount_paths.join(", "); + ret[QT_TR_NOOP("Device")] = data.device_file; + return ret; + +} + +QString DeviceKitLister::MakeFriendlyName(const QString &id) { + + QMutexLocker l(&mutex_); + if (!device_data_.contains(id)) return QString(); + const DeviceData &data = device_data_[id]; + + if (!data.device_presentation_name.isEmpty()) + return data.device_presentation_name; + if (!data.drive_model.isEmpty() && !data.drive_vendor.isEmpty()) + return data.drive_vendor + " " + data.drive_model; + if (!data.drive_model.isEmpty()) return data.drive_model; + return data.drive_serial; + +} + +DeviceKitLister::DeviceData DeviceKitLister::ReadDeviceData(const QDBusObjectPath &path) const { + + DeviceData ret; + + OrgFreedesktopUDisksDeviceInterface device(OrgFreedesktopUDisksInterface::staticInterfaceName(), path.path(), QDBusConnection::systemBus()); + if (!device.isValid()) { + qLog(Warning) << "Error connecting to the device interface on" << path.path(); + return ret; + } + + // Don't do anything with internal drives, hidden drives, or partition tables + if (device.deviceIsSystemInternal() || device.devicePresentationHide() || device.deviceMountPaths().isEmpty() || device.deviceIsPartitionTable()) { + return ret; + } + + ret.suitable = true; + ret.dbus_path = path.path(); + ret.drive_serial = device.driveSerial(); + ret.drive_model = device.driveModel(); + ret.drive_vendor = device.driveVendor(); + ret.device_file = device.deviceFile(); + ret.device_presentation_name = device.devicePresentationName(); + ret.device_presentation_icon_name = device.devicePresentationIconName(); + ret.device_size = device.deviceSize(); + ret.device_mount_paths = device.deviceMountPaths(); + + // Get free space info + if (!ret.device_mount_paths.isEmpty()) + ret.free_space = Utilities::FileSystemFreeSpace(ret.device_mount_paths[0]); + + return ret; + +} + +void DeviceKitLister::DBusDeviceAdded(const QDBusObjectPath &path) { + + DeviceData data = ReadDeviceData(path); + if (!data.suitable) return; + + { + QMutexLocker l(&mutex_); + device_data_[data.unique_id()] = data; + } + + emit DeviceAdded(data.unique_id()); + +} + +void DeviceKitLister::DBusDeviceRemoved(const QDBusObjectPath &path) { + + QString id; + { + QMutexLocker l(&mutex_); + id = FindUniqueIdByPath(path); + if (id.isNull()) return; + + device_data_.remove(id); + } + + emit DeviceRemoved(id); + +} + +void DeviceKitLister::DBusDeviceChanged(const QDBusObjectPath &path) { + + bool already_known = false; + { + QMutexLocker l(&mutex_); + already_known = !FindUniqueIdByPath(path).isNull(); + } + + DeviceData data = ReadDeviceData(path); + + if (already_known && !data.suitable) + DBusDeviceRemoved(path); + else if (!already_known && data.suitable) + DBusDeviceAdded(path); + else if (already_known && data.suitable) { + { + QMutexLocker l(&mutex_); + device_data_[data.unique_id()] = data; + } + emit DeviceChanged(data.unique_id()); + } + +} + +QString DeviceKitLister::FindUniqueIdByPath(const QDBusObjectPath &path) const { + + for (const DeviceData &data : device_data_) { + if (data.dbus_path == path.path()) return data.unique_id(); + } + return QString(); + +} + +QList DeviceKitLister::MakeDeviceUrls(const QString &id) { + + QString mount_point = LockAndGetDeviceInfo(id, &DeviceData::device_mount_paths)[0]; + + return QList() << MakeUrlFromLocalPath(mount_point); +} + +void DeviceKitLister::UnmountDevice(const QString &id) { + + QString path = LockAndGetDeviceInfo(id, &DeviceData::dbus_path); + + OrgFreedesktopUDisksDeviceInterface device(OrgFreedesktopUDisksInterface::staticInterfaceName(), path, QDBusConnection::systemBus()); + if (!device.isValid()) { + qLog(Warning) << "Error connecting to the device interface on" << path; + return; + } + + // Get the device's parent drive + QString drive_path = device.partitionSlave().path(); + OrgFreedesktopUDisksDeviceInterface drive(OrgFreedesktopUDisksInterface::staticInterfaceName(), drive_path, QDBusConnection::systemBus()); + if (!drive.isValid()) { + qLog(Warning) << "Error connecting to the drive interface on" << drive_path; + return; + } + + // Unmount the filesystem + QDBusPendingReply<> reply = device.FilesystemUnmount(QStringList()); + reply.waitForFinished(); + + // Eject the drive + drive.DriveEject(QStringList()); + // Don't bother waiting for the eject to finish + +} + +void DeviceKitLister::UpdateDeviceFreeSpace(const QString &id) { + +{ + QMutexLocker l(&mutex_); + if (!device_data_.contains(id)) return; + + DeviceData &data = device_data_[id]; + if (!data.device_mount_paths.isEmpty()) + data.free_space = Utilities::FileSystemFreeSpace(data.device_mount_paths[0]); + } + + emit DeviceChanged(id); +} + diff --git a/src/device/devicekitlister.h b/src/device/devicekitlister.h new file mode 100644 index 00000000..1d8d2b7e --- /dev/null +++ b/src/device/devicekitlister.h @@ -0,0 +1,110 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef DEVICEKITLISTER_H +#define DEVICEKITLISTER_H + +#include "config.h" + +#include + +#include +#include + +#include "devicelister.h" + +class OrgFreedesktopUDisksInterface; + +class QDBusObjectPath; + +class DeviceKitLister : public DeviceLister { + Q_OBJECT + + public: + DeviceKitLister(); + ~DeviceKitLister(); + + QStringList DeviceUniqueIDs(); + QVariantList DeviceIcons(const QString &id); + QString DeviceManufacturer(const QString &id); + QString DeviceModel(const QString &id); + quint64 DeviceCapacity(const QString &id); + quint64 DeviceFreeSpace(const QString &id); + QVariantMap DeviceHardwareInfo(const QString &id); + + QString MakeFriendlyName(const QString &id); + QList MakeDeviceUrls(const QString &id); + + void UnmountDevice(const QString &id); + + public slots: + void UpdateDeviceFreeSpace(const QString &id); + + protected: + void Init(); + + private slots: + void DBusDeviceAdded(const QDBusObjectPath &path); + void DBusDeviceRemoved(const QDBusObjectPath &path); + void DBusDeviceChanged(const QDBusObjectPath &path); + + private: + struct DeviceData { + DeviceData() : suitable(false), device_size(0), free_space(0) {} + + QString unique_id() const; + + bool suitable; + QString dbus_path; + QString drive_serial; + QString drive_model; + QString drive_vendor; + QString device_file; + QString device_presentation_name; + QString device_presentation_icon_name; + QStringList device_mount_paths; + quint64 device_size; + quint64 free_space; + }; + + DeviceData ReadDeviceData(const QDBusObjectPath &path) const; + + // You MUST hold the mutex while calling this function + QString FindUniqueIdByPath(const QDBusObjectPath &path) const; + + template + T LockAndGetDeviceInfo(const QString &id, T DeviceData::*field); + + private: + std::unique_ptr interface_; + + QMutex mutex_; + QMap device_data_; +}; + +template +T DeviceKitLister::LockAndGetDeviceInfo(const QString &id, T DeviceData::*field) { + QMutexLocker l(&mutex_); + if (!device_data_.contains(id)) return T(); + + return device_data_[id].*field; +} + +#endif // DEVICEKITLISTER_H diff --git a/src/device/devicelister.cpp b/src/device/devicelister.cpp new file mode 100644 index 00000000..a8c3c2eb --- /dev/null +++ b/src/device/devicelister.cpp @@ -0,0 +1,234 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "devicelister.h" + +#ifdef HAVE_LIBGPOD +#include +#endif + +DeviceLister::DeviceLister() : thread_(nullptr) {} + +DeviceLister::~DeviceLister() { + + if (thread_) { + thread_->quit(); + thread_->wait(1000); + } + +} + +void DeviceLister::Start() { + + thread_ = new QThread; + connect(thread_, SIGNAL(started()), SLOT(ThreadStarted())); + + moveToThread(thread_); + thread_->start(); + +} + +void DeviceLister::ThreadStarted() { Init(); } + +namespace { + +#ifdef HAVE_LIBGPOD + +QString GetIpodColour(Itdb_IpodModel model) { + + switch (model) { + case ITDB_IPOD_MODEL_MINI_GREEN: + case ITDB_IPOD_MODEL_NANO_GREEN: + case ITDB_IPOD_MODEL_SHUFFLE_GREEN: + return "green"; + + case ITDB_IPOD_MODEL_MINI_BLUE: + case ITDB_IPOD_MODEL_NANO_BLUE: + case ITDB_IPOD_MODEL_SHUFFLE_BLUE: + return "blue"; + + case ITDB_IPOD_MODEL_MINI_PINK: + case ITDB_IPOD_MODEL_NANO_PINK: + case ITDB_IPOD_MODEL_SHUFFLE_PINK: + return "pink"; + + case ITDB_IPOD_MODEL_MINI_GOLD: + return "gold"; + + case ITDB_IPOD_MODEL_NANO_WHITE: + case ITDB_IPOD_MODEL_VIDEO_WHITE: + return "white"; + + case ITDB_IPOD_MODEL_NANO_SILVER: + case ITDB_IPOD_MODEL_CLASSIC_SILVER: + return "silver"; + + case ITDB_IPOD_MODEL_NANO_RED: + case ITDB_IPOD_MODEL_SHUFFLE_RED: + return "red"; + + case ITDB_IPOD_MODEL_NANO_YELLOW: + return "yellow"; + + case ITDB_IPOD_MODEL_NANO_PURPLE: + case ITDB_IPOD_MODEL_SHUFFLE_PURPLE: + return "purple"; + + case ITDB_IPOD_MODEL_NANO_ORANGE: + case ITDB_IPOD_MODEL_SHUFFLE_ORANGE: + return "orange"; + + case ITDB_IPOD_MODEL_NANO_BLACK: + case ITDB_IPOD_MODEL_VIDEO_BLACK: + case ITDB_IPOD_MODEL_CLASSIC_BLACK: + return "black"; + + default: + return QString(); + } + +} + +QString GetIpodModel(Itdb_IpodModel model) { + + switch (model) { + case ITDB_IPOD_MODEL_MINI: + case ITDB_IPOD_MODEL_MINI_BLUE: + case ITDB_IPOD_MODEL_MINI_PINK: + case ITDB_IPOD_MODEL_MINI_GREEN: + case ITDB_IPOD_MODEL_MINI_GOLD: + return "mini"; + + case ITDB_IPOD_MODEL_NANO_WHITE: + case ITDB_IPOD_MODEL_NANO_BLACK: + case ITDB_IPOD_MODEL_NANO_SILVER: + case ITDB_IPOD_MODEL_NANO_BLUE: + case ITDB_IPOD_MODEL_NANO_GREEN: + case ITDB_IPOD_MODEL_NANO_PINK: + case ITDB_IPOD_MODEL_NANO_RED: + case ITDB_IPOD_MODEL_NANO_YELLOW: + case ITDB_IPOD_MODEL_NANO_PURPLE: + case ITDB_IPOD_MODEL_NANO_ORANGE: + return "nano"; + + case ITDB_IPOD_MODEL_SHUFFLE: + case ITDB_IPOD_MODEL_SHUFFLE_SILVER: + case ITDB_IPOD_MODEL_SHUFFLE_PINK: + case ITDB_IPOD_MODEL_SHUFFLE_BLUE: + case ITDB_IPOD_MODEL_SHUFFLE_GREEN: + case ITDB_IPOD_MODEL_SHUFFLE_ORANGE: + case ITDB_IPOD_MODEL_SHUFFLE_RED: + return "shuffle"; + + case ITDB_IPOD_MODEL_COLOR: + case ITDB_IPOD_MODEL_REGULAR: + case ITDB_IPOD_MODEL_CLASSIC_SILVER: + case ITDB_IPOD_MODEL_CLASSIC_BLACK: + return "standard"; + + case ITDB_IPOD_MODEL_COLOR_U2: + case ITDB_IPOD_MODEL_REGULAR_U2: + return "U2"; + + default: + return QString(); + } + +} + +#endif +} + +QUrl DeviceLister::MakeUrlFromLocalPath(const QString &path) const { + + if (IsIpod(path)) { + QUrl ret; + ret.setScheme("ipod"); + ret.setPath(QDir::fromNativeSeparators(path)); + return ret; + } + + return QUrl::fromLocalFile(path); + +} + +bool DeviceLister::IsIpod(const QString &path) const { + return QFile::exists(path + "/iTunes_Control") || + QFile::exists(path + "/iPod_Control") || + QFile::exists(path + "/iTunes/iTunes_Control"); +} + +QStringList DeviceLister::GuessIconForPath(const QString &path) { + + QStringList ret; + +#ifdef HAVE_LIBGPOD + if (IsIpod(path)) { + Itdb_Device* device = itdb_device_new(); + itdb_device_set_mountpoint(device, path.toLocal8Bit().constData()); + const Itdb_IpodInfo* info = itdb_device_get_ipod_info(device); + + QString colour = GetIpodColour(info->ipod_model); + QString model = GetIpodModel(info->ipod_model); + + itdb_device_free(device); + + if (!colour.isEmpty()) { + QString colour_icon = "multimedia-player-ipod-%1-%2"; + ret << colour_icon.arg(model, colour); + } + + if (!model.isEmpty()) { + QString model_icon = "multimedia-player-ipod-%1"; + ret << model_icon.arg(model); + } + } +#endif + + return ret; + +} + +QStringList DeviceLister::GuessIconForModel(const QString &vendor, const QString &model) { + + QStringList ret; + if (vendor.startsWith("Google") && model.contains("Nexus")) { + ret << "phone-google-nexus-one"; + } + return ret; + +} + +int DeviceLister::MountDevice(const QString &id) { + + const int ret = next_mount_request_id_++; + emit DeviceMounted(id, ret, true); + return ret; + +} + diff --git a/src/device/devicelister.h b/src/device/devicelister.h new file mode 100644 index 00000000..6ac93b1e --- /dev/null +++ b/src/device/devicelister.h @@ -0,0 +1,98 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef DEVICELISTER_H +#define DEVICELISTER_H + +#include "config.h" + +#include +#include + +class ConnectedDevice; +class DeviceManager; + +class DeviceLister : public QObject { + Q_OBJECT + + public: + DeviceLister(); + virtual ~DeviceLister(); + + // Tries to start the thread and initialise the engine. This object will be + // moved to the new thread. + void Start(); + + // If two listers know about the same device, then the metadata will get + // taken from the one with the highest priority. + virtual int priority() const { return 100; } + + // Query information about the devices that are available. Must be thread-safe. + virtual QStringList DeviceUniqueIDs() = 0; + virtual QVariantList DeviceIcons(const QString &id) = 0; + virtual QString DeviceManufacturer(const QString &id) = 0; + virtual QString DeviceModel(const QString &id) = 0; + virtual quint64 DeviceCapacity(const QString &id) = 0; + virtual quint64 DeviceFreeSpace(const QString &id) = 0; + virtual QVariantMap DeviceHardwareInfo(const QString &id) = 0; + virtual bool DeviceNeedsMount(const QString &id) { return false; } + + // When connecting to a device for the first time, do we want an user's + // confirmation for scanning it? (by default yes) + virtual bool AskForScan(const QString&) const { return true; } + + virtual QString MakeFriendlyName(const QString &id) = 0; + virtual QList MakeDeviceUrls(const QString &id) = 0; + + // Ensure the device is mounted. This should run asynchronously and emit + // DeviceMounted when it's done. + virtual int MountDevice(const QString &id); + + // Do whatever needs to be done to safely remove the device. + virtual void UnmountDevice(const QString &id) = 0; + + public slots: + virtual void UpdateDeviceFreeSpace(const QString &id) = 0; + virtual void ShutDown() {} + +signals: + void DeviceAdded(const QString &id); + void DeviceRemoved(const QString &id); + void DeviceChanged(const QString &id); + void DeviceMounted(const QString &id, int request_id, bool success); + + protected: + virtual void Init() = 0; + QUrl MakeUrlFromLocalPath(const QString &path) const; + bool IsIpod(const QString &path) const; + + QStringList GuessIconForPath(const QString &path); + QStringList GuessIconForModel(const QString &vendor, const QString &model); + + protected: + QThread* thread_; + int next_mount_request_id_; + + private slots: + void ThreadStarted(); +}; + +#endif // DEVICELISTER_H + diff --git a/src/device/devicemanager.cpp b/src/device/devicemanager.cpp new file mode 100644 index 00000000..c11fc3dc --- /dev/null +++ b/src/device/devicemanager.cpp @@ -0,0 +1,789 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "devicemanager.h" + +#include "devicedatabasebackend.h" +#include "devicekitlister.h" +#include "devicestatefiltermodel.h" +#include "filesystemdevice.h" +#include "core/application.h" +#include "core/concurrentrun.h" +#include "core/database.h" +#include "core/logging.h" +#include "core/musicstorage.h" +#include "core/taskmanager.h" +#include "core/utilities.h" +#include "core/iconloader.h" + +#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER) +#include "cddalister.h" +#include "cddadevice.h" +#endif + +#if defined(Q_OS_DARWIN) and defined(HAVE_LIBMTP) +#include "macdevicelister.h" +#endif +#ifdef HAVE_LIBGPOD +#include "gpoddevice.h" +#endif +#ifdef HAVE_GIO +#include "giolister.h" +#endif +#ifdef HAVE_IMOBILEDEVICE +# include "afcdevice.h" +# include "ilister.h" +#endif +#ifdef HAVE_LIBMTP +#include "mtpdevice.h" +#endif +#ifdef HAVE_UDISKS2 +#include "udisks2lister.h" +#endif + +using std::bind; + +const int DeviceManager::kDeviceIconSize = 32; +const int DeviceManager::kDeviceIconOverlaySize = 16; + +DeviceManager::DeviceInfo::DeviceInfo() + : database_id_(-1), + transcode_mode_(MusicStorage::Transcode_Unsupported), + transcode_format_(Song::Type_Unknown), + task_percentage_(-1) {} + +DeviceDatabaseBackend::Device DeviceManager::DeviceInfo::SaveToDb() const { + + DeviceDatabaseBackend::Device ret; + ret.friendly_name_ = friendly_name_; + ret.size_ = size_; + ret.id_ = database_id_; + ret.icon_name_ = icon_name_; + ret.transcode_mode_ = transcode_mode_; + ret.transcode_format_ = transcode_format_; + + QStringList unique_ids; + for (const Backend &backend : backends_) { + unique_ids << backend.unique_id_; + } + ret.unique_id_ = unique_ids.join(","); + + return ret; + +} + +void DeviceManager::DeviceInfo::InitFromDb(const DeviceDatabaseBackend::Device &dev) { + + database_id_ = dev.id_; + friendly_name_ = dev.friendly_name_; + size_ = dev.size_; + transcode_mode_ = dev.transcode_mode_; + transcode_format_ = dev.transcode_format_; + + QStringList icon_names = dev.icon_name_.split(','); + QVariantList icons; + for (const QString &icon_name : icon_names) { + icons << icon_name; + } + + LoadIcon(icons, friendly_name_); + + QStringList unique_ids = dev.unique_id_.split(','); + for (const QString &id : unique_ids) { + backends_ << Backend(nullptr, id); + } + +} + +void DeviceManager::DeviceInfo::LoadIcon(const QVariantList &icons, const QString &name_hint) { + + icon_name_ = "device"; + + if (icons.isEmpty()) { + icon_ = IconLoader::Load(icon_name_); + return; + } + + // Try to load the icon with that exact name first + for (const QVariant &icon : icons) { + if (!icon.value().isNull()) { + icon_ = QIcon(icon.value()); + return; + } + else { + if (!icon.toString().isEmpty()) icon_ = IconLoader::Load(icon.toString()); + if (!icon_.isNull()) { + icon_name_ = icon.toString(); + return; + } + } + } + + QString hint = QString(icons.first().toString() + name_hint).toLower(); + + if (hint.contains("phone")) + icon_name_ = "device-phone"; + else if (hint.contains("ipod") || hint.contains("apple")) + icon_name_ = "device-ipod"; + else if ((hint.contains("usb")) && (hint.contains("reader"))) + icon_name_ = "device-usb-flash"; + else if (hint.contains("usb")) + icon_name_ = "device-usb-drive"; + else + icon_name_ = "device"; + + icon_ = IconLoader::Load(icon_name_); + +} + +const DeviceManager::DeviceInfo::Backend *DeviceManager::DeviceInfo::BestBackend() const { + + int best_priority = -1; + const Backend *ret = nullptr; + + for (int i = 0; i < backends_.count(); ++i) { + if (backends_[i].lister_ && backends_[i].lister_->priority() > best_priority) { + best_priority = backends_[i].lister_->priority(); + ret = &(backends_[i]); + } + } + + if (!ret && !backends_.isEmpty()) return &(backends_[0]); + return ret; +} + +DeviceManager::DeviceManager(Application *app, QObject *parent) + : QAbstractListModel(parent), + app_(app), + not_connected_overlay_(IconLoader::Load("edit-delete")) +{ + thread_pool_.setMaxThreadCount(1); + connect(app_->task_manager(), SIGNAL(TasksChanged()), SLOT(TasksChanged())); + + // Create the backend in the database thread + backend_ = new DeviceDatabaseBackend; + backend_->moveToThread(app_->database()->thread()); + backend_->Init(app_->database()); + + // This reads from the database and contends on the database mutex, which can + // be very slow on startup. + ConcurrentRun::Run(&thread_pool_, bind(&DeviceManager::LoadAllDevices, this)); + + // This proxy model only shows connected devices + connected_devices_model_ = new DeviceStateFilterModel(this); + connected_devices_model_->setSourceModel(this); + +// CD devices are detected via the DiskArbitration framework instead on Darwin. +#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER) && !defined(Q_OS_DARWIN) + AddLister(new CddaLister); +#endif +#ifdef HAVE_DEVICEKIT + AddLister(new DeviceKitLister); +#endif +#ifdef HAVE_UDISKS2 + AddLister(new Udisks2Lister); +#endif +#ifdef HAVE_GIO + AddLister(new GioLister); +#endif +#if defined(Q_OS_DARWIN) and defined(HAVE_LIBMTP) + AddLister(new MacDeviceLister); +#endif + + AddDeviceClass(); + +#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER) + AddDeviceClass(); +#endif + +#ifdef HAVE_LIBGPOD + AddDeviceClass(); +#endif + +#ifdef HAVE_LIBMTP + AddDeviceClass(); +#endif + +} + +DeviceManager::~DeviceManager() { + + for (DeviceLister *lister : listers_) { + lister->ShutDown(); + delete lister; + } + + backend_->deleteLater(); + +} + +void DeviceManager::LoadAllDevices() { + + Q_ASSERT(QThread::currentThread() != qApp->thread()); + DeviceDatabaseBackend::DeviceList devices = backend_->GetAllDevices(); + for (const DeviceDatabaseBackend::Device &device : devices) { + DeviceInfo info; + info.InitFromDb(device); + + beginInsertRows(QModelIndex(), devices_.count(), devices_.count()); + devices_ << info; + endInsertRows(); + } + +} + +int DeviceManager::rowCount(const QModelIndex&) const { + return devices_.count(); +} + +QVariant DeviceManager::data(const QModelIndex &index, int role) const { + + if (!index.isValid() || index.column() != 0) return QVariant(); + + const DeviceInfo &info = devices_[index.row()]; + + switch (role) { + case Qt::DisplayRole: { + QString text; + if (!info.friendly_name_.isEmpty()) + text = info.friendly_name_; + else + text = info.BestBackend()->unique_id_; + + if (info.size_) + text = text + QString(" (%1)").arg(Utilities::PrettySize(info.size_)); + if (info.device_.get()) info.device_->Refresh(); + return text; + } + + case Qt::DecorationRole: { + QPixmap pixmap = info.icon_.pixmap(kDeviceIconSize); + + if (info.backends_.isEmpty() || !info.BestBackend()->lister_) { + // Disconnected but remembered + QPainter p(&pixmap); + p.drawPixmap(kDeviceIconSize - kDeviceIconOverlaySize, kDeviceIconSize - kDeviceIconOverlaySize, not_connected_overlay_.pixmap(kDeviceIconOverlaySize)); + } + + return pixmap; + } + + case Role_FriendlyName: + return info.friendly_name_; + + case Role_UniqueId: + return info.BestBackend()->unique_id_; + + case Role_IconName: + return info.icon_name_; + + case Role_Capacity: + case MusicStorage::Role_Capacity: + return info.size_; + + case Role_FreeSpace: + case MusicStorage::Role_FreeSpace: + return info.BestBackend()->lister_ ? info.BestBackend()->lister_->DeviceFreeSpace(info.BestBackend()->unique_id_) : QVariant(); + + case Role_State: + if (info.device_) return State_Connected; + if (info.BestBackend()->lister_) { + if (info.BestBackend()->lister_->DeviceNeedsMount(info.BestBackend()->unique_id_)) return State_NotMounted; + return State_NotConnected; + } + return State_Remembered; + + case Role_UpdatingPercentage: + if (info.task_percentage_ == -1) return QVariant(); + return info.task_percentage_; + + case MusicStorage::Role_Storage: + if (!info.device_ && info.database_id_ != -1) + const_cast(this)->Connect(index.row()); + if (!info.device_) return QVariant(); + return QVariant::fromValue>(info.device_); + + case MusicStorage::Role_StorageForceConnect: + if (!info.device_) { + if (info.database_id_ == -1 && !info.BestBackend()->lister_->DeviceNeedsMount(info.BestBackend()->unique_id_)) { + + if (info.BestBackend()->lister_->AskForScan(info.BestBackend()->unique_id_)) { + std::unique_ptr dialog(new QMessageBox(QMessageBox::Information, tr("Connect device"), tr("This is the first time you have connected this device. Strawberry will now scan the device to find music files - this may take some time."), QMessageBox::Cancel)); + QPushButton *connect = dialog->addButton(tr("Connect device"), QMessageBox::AcceptRole); + dialog->exec(); + + if (dialog->clickedButton() != connect) return QVariant(); + } + } + + const_cast(this)->Connect(index.row()); + } + if (!info.device_) return QVariant(); + return QVariant::fromValue>(info.device_); + + case Role_MountPath: { + if (!info.device_) return QVariant(); + + QString ret = info.device_->url().path(); +#ifdef Q_OS_WIN32 + if (ret.startsWith('/')) ret.remove(0, 1); +#endif + return QDir::toNativeSeparators(ret); + } + + case Role_TranscodeMode: + return info.transcode_mode_; + + case Role_TranscodeFormat: + return info.transcode_format_; + + case Role_SongCount: + if (!info.device_) return QVariant(); + return info.device_->song_count(); + + default: + return QVariant(); + } +} + +void DeviceManager::AddLister(DeviceLister *lister) { + + listers_ << lister; + connect(lister, SIGNAL(DeviceAdded(QString)), SLOT(PhysicalDeviceAdded(QString))); + connect(lister, SIGNAL(DeviceRemoved(QString)), SLOT(PhysicalDeviceRemoved(QString))); + connect(lister, SIGNAL(DeviceChanged(QString)), SLOT(PhysicalDeviceChanged(QString))); + + lister->Start(); + +} + +int DeviceManager::FindDeviceById(const QString &id) const { + + for (int i = 0; i < devices_.count(); ++i) { + for (const DeviceInfo::Backend &backend : devices_[i].backends_) { + if (backend.unique_id_ == id) return i; + } + } + return -1; + +} + +int DeviceManager::FindDeviceByUrl(const QList &urls) const { + + if (urls.isEmpty()) return -1; + + for (int i = 0; i < devices_.count(); ++i) { + for (const DeviceInfo::Backend &backend : devices_[i].backends_) { + if (!backend.lister_) continue; + + QList device_urls = + backend.lister_->MakeDeviceUrls(backend.unique_id_); + for (const QUrl &url : device_urls) { + if (urls.contains(url)) return i; + } + } + } + return -1; + +} + +void DeviceManager::PhysicalDeviceAdded(const QString &id) { + + DeviceLister *lister = qobject_cast(sender()); + + qLog(Info) << "Device added:" << id; + + // Do we have this device already? + int i = FindDeviceById(id); + if (i != -1) { + DeviceInfo &info = devices_[i]; + for (int backend_index = 0 ; backend_index < info.backends_.count() ; ++backend_index) { + if (info.backends_[backend_index].unique_id_ == id) { + info.backends_[backend_index].lister_ = lister; + break; + } + } + + emit dataChanged(index(i, 0), index(i, 0)); + } else { + // Check if we have another device with the same URL + i = FindDeviceByUrl(lister->MakeDeviceUrls(id)); + if (i != -1) { + // Add this device's lister to the existing device + DeviceInfo &info = devices_[i]; + info.backends_ << DeviceInfo::Backend(lister, id); + + // If the user hasn't saved the device in the DB yet then overwrite the + // device's name and icon etc. + if (info.database_id_ == -1 && info.BestBackend()->lister_ == lister) { + info.friendly_name_ = lister->MakeFriendlyName(id); + info.size_ = lister->DeviceCapacity(id); + info.LoadIcon(lister->DeviceIcons(id), info.friendly_name_); + } + + emit dataChanged(index(i, 0), index(i, 0)); + } else { + // It's a completely new device + DeviceInfo info; + info.backends_ << DeviceInfo::Backend(lister, id); + info.friendly_name_ = lister->MakeFriendlyName(id); + info.size_ = lister->DeviceCapacity(id); + info.LoadIcon(lister->DeviceIcons(id), info.friendly_name_); + + beginInsertRows(QModelIndex(), devices_.count(), devices_.count()); + devices_ << info; + endInsertRows(); + } + } + +} + +void DeviceManager::PhysicalDeviceRemoved(const QString &id) { + + DeviceLister *lister = qobject_cast(sender()); + + qLog(Info) << "Device removed:" << id; + + int i = FindDeviceById(id); + if (i == -1) { + // Shouldn't happen + return; + } + + DeviceInfo &info = devices_[i]; + + if (info.database_id_ != -1) { + // Keep the structure around, but just "disconnect" it + for (int backend_index = 0 ; backend_index < info.backends_.count() ; ++backend_index) { + if (info.backends_[backend_index].unique_id_ == id) { + info.backends_[backend_index].lister_ = nullptr; + break; + } + } + + if (info.device_ && info.device_->lister() == lister) info.device_.reset(); + + if (!info.device_) emit DeviceDisconnected(i); + + emit dataChanged(index(i, 0), index(i, 0)); + } + else { + // If this was the last lister for the device then remove it from the model + for (int backend_index = 0 ; backend_index < info.backends_.count() ; ++backend_index) { + if (info.backends_[backend_index].unique_id_ == id) { + info.backends_.removeAt(backend_index); + break; + } + } + + if (info.backends_.isEmpty()) { + beginRemoveRows(QModelIndex(), i, i); + devices_.removeAt(i); + + for (const QModelIndex &idx : persistentIndexList()) { + if (idx.row() == i) + changePersistentIndex(idx, QModelIndex()); + else if (idx.row() > i) + changePersistentIndex(idx, index(idx.row() - 1, idx.column())); + } + + endRemoveRows(); + } + } + +} + +void DeviceManager::PhysicalDeviceChanged(const QString &id) { + + DeviceLister *lister = qobject_cast(sender()); + Q_UNUSED(lister); + + int i = FindDeviceById(id); + if (i == -1) { + // Shouldn't happen + return; + } + + // TODO +} + +std::shared_ptr DeviceManager::Connect(int row) { + + DeviceInfo &info = devices_[row]; + if (info.device_) // Already connected + return info.device_; + + std::shared_ptr ret; + + if (!info.BestBackend()->lister_) // Not physically connected + return ret; + + if (info.BestBackend()->lister_->DeviceNeedsMount(info.BestBackend()->unique_id_)) { + // Mount the device + info.BestBackend()->lister_->MountDevice(info.BestBackend()->unique_id_); + return ret; + } + + bool first_time = (info.database_id_ == -1); + if (first_time) { + // We haven't stored this device in the database before + info.database_id_ = backend_->AddDevice(info.SaveToDb()); + } + + // Get the device URLs + QList urls = info.BestBackend()->lister_->MakeDeviceUrls(info.BestBackend()->unique_id_); + if (urls.isEmpty()) return ret; + + // Take the first URL that we have a handler for + QUrl device_url; + for (const QUrl &url : urls) { + qLog(Info) << "Connecting" << url; + + // Find a device class for this URL's scheme + if (device_classes_.contains(url.scheme())) { + device_url = url; + break; + } + + // If we get here it means that this URL scheme wasn't supported. If it + // was "ipod" or "mtp" then the user compiled out support and the device + // won't work properly. + if (url.scheme() == "mtp" || url.scheme() == "gphoto2") { + if (QMessageBox::critical(nullptr, tr("This device will not work properly"), + tr("This is an MTP device, but you compiled Strawberry without libmtp support.") + " " + + tr("If you continue, this device will work slowly and songs copied to it may not work."), + QMessageBox::Abort, QMessageBox::Ignore) == QMessageBox::Abort) + return ret; + } + + if (url.scheme() == "ipod") { + if (QMessageBox::critical(nullptr, tr("This device will not work properly"), + tr("This is an iPod, but you compiled Strawberry without libgpod support.") + " " + + tr("If you continue, this device will work slowly and songs copied to it may not work."), + QMessageBox::Abort, QMessageBox::Ignore) == QMessageBox::Abort) + return ret; + } + } + + if (device_url.isEmpty()) { + // Munge the URL list into a string list + QStringList url_strings; + for (const QUrl &url : urls) { + url_strings << url.toString(); + } + + app_->AddError(tr("This type of device is not supported: %1").arg(url_strings.join(", "))); + return ret; + } + + QMetaObject meta_object = device_classes_.value(device_url.scheme()); + QObject *instance = meta_object.newInstance( + Q_ARG(QUrl, device_url), + Q_ARG(DeviceLister*, info.BestBackend()->lister_), + Q_ARG(QString, info.BestBackend()->unique_id_), + Q_ARG(DeviceManager*, this), Q_ARG(Application*, app_), + Q_ARG(int, info.database_id_), Q_ARG(bool, first_time)); + ret.reset(static_cast(instance)); + + if (!ret) { + qLog(Warning) << "Could not create device for" << device_url.toString(); + } + else { + ret->Init(); + + info.device_ = ret; + emit dataChanged(index(row), index(row)); + connect(info.device_.get(), SIGNAL(TaskStarted(int)), SLOT(DeviceTaskStarted(int))); + connect(info.device_.get(), SIGNAL(SongCountUpdated(int)), SLOT(DeviceSongCountUpdated(int))); + } + + emit DeviceConnected(row); + + return ret; + +} + +std::shared_ptr DeviceManager::GetConnectedDevice(int row) + const { + return devices_[row].device_; +} + +int DeviceManager::GetDatabaseId(int row) const { + return devices_[row].database_id_; +} + +DeviceLister *DeviceManager::GetLister(int row) const { + return devices_[row].BestBackend()->lister_; +} + +void DeviceManager::Disconnect(int row) { + + DeviceInfo &info = devices_[row]; + if (!info.device_) // Already disconnected + return; + + info.device_.reset(); + emit DeviceDisconnected(row); + emit dataChanged(index(row), index(row)); + +} + +void DeviceManager::Forget(int row) { + + DeviceInfo &info = devices_[row]; + if (info.database_id_ == -1) return; + + if (info.device_) Disconnect(row); + + backend_->RemoveDevice(info.database_id_); + info.database_id_ = -1; + + if (!info.BestBackend()->lister_) { + // It's not attached any more so remove it from the list + beginRemoveRows(QModelIndex(), row, row); + devices_.removeAt(row); + + for (const QModelIndex &idx : persistentIndexList()) { + if (idx.row() == row) + changePersistentIndex(idx, QModelIndex()); + else if (idx.row() > row) + changePersistentIndex(idx, index(idx.row() - 1, idx.column())); + } + + endRemoveRows(); + } + else { + // It's still attached, set the name and icon back to what they were + // originally + const QString id = info.BestBackend()->unique_id_; + + info.friendly_name_ = info.BestBackend()->lister_->MakeFriendlyName(id); + info.LoadIcon(info.BestBackend()->lister_->DeviceIcons(id), info.friendly_name_); + + dataChanged(index(row, 0), index(row, 0)); + } + +} + +void DeviceManager::SetDeviceOptions(int row, const QString &friendly_name, const QString &icon_name, MusicStorage::TranscodeMode mode, Song::FileType format) { + + DeviceInfo &info = devices_[row]; + info.friendly_name_ = friendly_name; + info.LoadIcon(QVariantList() << icon_name, friendly_name); + info.transcode_mode_ = mode; + info.transcode_format_ = format; + + emit dataChanged(index(row, 0), index(row, 0)); + + if (info.database_id_ != -1) + backend_->SetDeviceOptions(info.database_id_, friendly_name, icon_name, mode, format); + +} + +void DeviceManager::DeviceTaskStarted(int id) { + + ConnectedDevice *device = qobject_cast(sender()); + if (!device) return; + + for (int i = 0; i < devices_.count(); ++i) { + DeviceInfo &info = devices_[i]; + if (info.device_.get() == device) { + active_tasks_[id] = index(i); + info.task_percentage_ = 0; + emit dataChanged(index(i), index(i)); + return; + } + } + +} + +void DeviceManager::TasksChanged() { + + QList tasks = app_->task_manager()->GetTasks(); + QList finished_tasks = active_tasks_.values(); + + for (const TaskManager::Task &task : tasks) { + if (!active_tasks_.contains(task.id)) continue; + + QPersistentModelIndex index = active_tasks_[task.id]; + if (!index.isValid()) continue; + + DeviceInfo &info = devices_[index.row()]; + if (task.progress_max) + info.task_percentage_ = float(task.progress) / task.progress_max * 100; + else + info.task_percentage_ = 0; + emit dataChanged(index, index); + finished_tasks.removeAll(index); + } + + for (const QPersistentModelIndex &index : finished_tasks) { + if (!index.isValid()) continue; + + DeviceInfo &info = devices_[index.row()]; + info.task_percentage_ = -1; + emit dataChanged(index, index); + + active_tasks_.remove(active_tasks_.key(index)); + } + +} + +void DeviceManager::UnmountAsync(int row) { + Q_ASSERT(QMetaObject::invokeMethod(this, "Unmount", Q_ARG(int, row))); +} + +void DeviceManager::Unmount(int row) { + + DeviceInfo &info = devices_[row]; + if (info.database_id_ != -1 && !info.device_) return; + + if (info.device_) Disconnect(row); + + if (info.BestBackend()->lister_) + info.BestBackend()->lister_->UnmountDevice(info.BestBackend()->unique_id_); + +} + +void DeviceManager::DeviceSongCountUpdated(int count) { + + ConnectedDevice *device = qobject_cast(sender()); + if (!device) return; + + int row = FindDeviceById(device->unique_id()); + if (row == -1) return; + + emit dataChanged(index(row), index(row)); + +} + diff --git a/src/device/devicemanager.h b/src/device/devicemanager.h new file mode 100644 index 00000000..ae1435e8 --- /dev/null +++ b/src/device/devicemanager.h @@ -0,0 +1,198 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef DEVICEMANAGER_H +#define DEVICEMANAGER_H + +#include "config.h" + +#include + +#include +#include +#include + +#include "devicedatabasebackend.h" + +#include "collection/collectionmodel.h" + +class Application; +class ConnectedDevice; +class Database; +class DeviceLister; +class DeviceStateFilterModel; +class TaskManager; + +class DeviceManager : public QAbstractListModel { + Q_OBJECT + + public: + DeviceManager(Application *app, QObject *parent = nullptr); + ~DeviceManager(); + + enum Role { + Role_State = CollectionModel::LastRole, + Role_UniqueId, + Role_FriendlyName, + Role_Capacity, + Role_FreeSpace, + Role_IconName, + Role_UpdatingPercentage, + Role_MountPath, + Role_TranscodeMode, + Role_TranscodeFormat, + Role_SongCount, + LastRole, + }; + + enum State { + State_Remembered, + State_NotMounted, + State_NotConnected, + State_Connected, + }; + + static const int kDeviceIconSize; + static const int kDeviceIconOverlaySize; + + DeviceStateFilterModel *connected_devices_model() const { return connected_devices_model_; } + + // Get info about devices + int GetDatabaseId(int row) const; + DeviceLister *GetLister(int row) const; + std::shared_ptr GetConnectedDevice(int row) const; + + int FindDeviceById(const QString &id) const; + int FindDeviceByUrl(const QList &url) const; + + // Actions on devices + std::shared_ptr Connect(int row); + void Disconnect(int row); + void Forget(int row); + void UnmountAsync(int row); + + void SetDeviceOptions(int row, const QString &friendly_name, const QString &icon_name, MusicStorage::TranscodeMode mode, Song::FileType format); + + // QAbstractListModel + int rowCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + + public slots: + void Unmount(int row); + +signals: + void DeviceConnected(int row); + void DeviceDisconnected(int row); + + private slots: + void PhysicalDeviceAdded(const QString &id); + void PhysicalDeviceRemoved(const QString &id); + void PhysicalDeviceChanged(const QString &id); + void DeviceTaskStarted(int id); + void TasksChanged(); + void DeviceSongCountUpdated(int count); + void LoadAllDevices(); + + private: + // Devices can be in three different states: + // 1) Remembered in the database but not physically connected at the moment. + // database_id valid, lister null, device null + // 2) Physically connected but the user hasn't "connected" it to Strawberry + // yet. + // database_id == -1, lister valid, device null + // 3) Physically connected and connected to Strawberry + // database_id valid, lister valid, device valid + // Devices in all states will have a unique_id. + struct DeviceInfo { + DeviceInfo(); + + // A device can be discovered in different ways (devicekit, gio, etc.) + // Sometimes the same device is discovered more than once. In this case + // the device will have multiple "backends". + struct Backend { + Backend(DeviceLister *lister = nullptr, const QString &id = QString()) + : lister_(lister), unique_id_(id) {} + + DeviceLister *lister_; // nullptr if not physically connected + QString unique_id_; + }; + + // Serialising to the database + void InitFromDb(const DeviceDatabaseBackend::Device &dev); + DeviceDatabaseBackend::Device SaveToDb() const; + + // Tries to load a good icon for the device. Sets icon_name_ and icon_. + void LoadIcon(const QVariantList &icons, const QString &name_hint); + + // Gets the best backend available (the one with the highest priority) + const Backend *BestBackend() const; + + int database_id_; // -1 if not remembered in the database + std::shared_ptr + device_; // nullptr if not connected + QList backends_; + + QString friendly_name_; + quint64 size_; + + QString icon_name_; + QIcon icon_; + + MusicStorage::TranscodeMode transcode_mode_; + Song::FileType transcode_format_; + + int task_percentage_; + }; + + void AddLister(DeviceLister *lister); + template void AddDeviceClass(); + + DeviceDatabaseBackend::Device InfoToDatabaseDevice(const DeviceInfo &info) const; + + private: + Application *app_; + DeviceDatabaseBackend *backend_; + + DeviceStateFilterModel *connected_devices_model_; + + QIcon not_connected_overlay_; + + QList listers_; + QList devices_; + + QMultiMap device_classes_; + + // Map of task ID to device index + QMap active_tasks_; + + QThreadPool thread_pool_; +}; + +template +void DeviceManager::AddDeviceClass() { + QStringList schemes = T::url_schemes(); + QMetaObject obj = T::staticMetaObject; + + for (const QString &scheme : schemes) { + device_classes_.insert(scheme, obj); + } +} + +#endif // DEVICEMANAGER_H diff --git a/src/device/deviceproperties.cpp b/src/device/deviceproperties.cpp new file mode 100644 index 00000000..5278b906 --- /dev/null +++ b/src/device/deviceproperties.cpp @@ -0,0 +1,301 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "deviceproperties.h" +#include "ui_deviceproperties.h" + +#include +#include + +#include +#include +#include + +#include "connecteddevice.h" +#include "devicelister.h" +#include "devicemanager.h" +#include "core/utilities.h" +#include "core/iconloader.h" +#include "transcoder/transcoder.h" + +DeviceProperties::DeviceProperties(QWidget *parent) + : QDialog(parent), + ui_(new Ui_DeviceProperties), + manager_(nullptr), + updating_formats_(false) { + ui_->setupUi(this); + + connect(ui_->open_device, SIGNAL(clicked()), SLOT(OpenDevice())); + + // Maximum height of the icon widget + ui_->icon->setMaximumHeight( + ui_->icon->iconSize().height() + + ui_->icon->horizontalScrollBar()->sizeHint().height() + + ui_->icon->spacing() * 2 + 5); +} + +DeviceProperties::~DeviceProperties() { delete ui_; } + +void DeviceProperties::SetDeviceManager(DeviceManager *manager) { + + manager_ = manager; + connect(manager_, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(ModelChanged())); + connect(manager_, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(ModelChanged())); + connect(manager_, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(ModelChanged())); + +} + +void DeviceProperties::ShowDevice(int row) { + + if (ui_->icon->count() == 0) { + // Only load the icons the first time the dialog is shown + QStringList icon_names = QStringList() + << "device" + << "device-usb-drive" + << "device-usb-flash" + << "cd" + << "device-ipod" + << "device-ipod-nano" + << "device-phone"; + + + for (const QString &icon_name : icon_names) { + QListWidgetItem *item = new QListWidgetItem(IconLoader::Load(icon_name), QString(), ui_->icon); + item->setData(Qt::UserRole, icon_name); + } + +#ifdef HAVE_GSTREAMER + // Load the transcode formats the first time the dialog is shown + for (const TranscoderPreset &preset : Transcoder::GetAllPresets()) { + ui_->transcode_format->addItem(preset.name_, preset.type_); + } + ui_->transcode_format->model()->sort(0); +#endif + } + + index_ = manager_->index(row); + + // Basic information + ui_->name->setText(index_.data(DeviceManager::Role_FriendlyName).toString()); + + // Find the right icon + QString icon_name = index_.data(DeviceManager::Role_IconName).toString(); + for (int i = 0; i < ui_->icon->count(); ++i) { + if (ui_->icon->item(i)->data(Qt::UserRole).toString() == icon_name) { + ui_->icon->setCurrentRow(i); + break; + } + } + + UpdateHardwareInfo(); + UpdateFormats(); + + show(); + +} + +void DeviceProperties::AddHardwareInfo(int row, const QString &key, const QString &value) { + ui_->hardware_info->setItem(row, 0, new QTableWidgetItem(key)); + ui_->hardware_info->setItem(row, 1, new QTableWidgetItem(value)); +} + +void DeviceProperties::ModelChanged() { + + if (!isVisible()) return; + + if (!index_.isValid()) + reject(); // Device went away + else { + UpdateHardwareInfo(); + UpdateFormats(); + } + +} + +void DeviceProperties::UpdateHardwareInfo() { + + // Hardware information + QString id = index_.data(DeviceManager::Role_UniqueId).toString(); + if (DeviceLister *lister = manager_->GetLister(index_.row())) { + QVariantMap info = lister->DeviceHardwareInfo(id); + + // Remove empty items + for (const QString &key : info.keys()) { + if (info[key].isNull() || info[key].toString().isEmpty()) + info.remove(key); + } + + ui_->hardware_info_stack->setCurrentWidget(ui_->hardware_info_page); + ui_->hardware_info->clear(); + ui_->hardware_info->setRowCount(2 + info.count()); + + int row = 0; + AddHardwareInfo(row++, tr("Model"), lister->DeviceModel(id)); + AddHardwareInfo(row++, tr("Manufacturer"), lister->DeviceManufacturer(id)); + for (const QString &key : info.keys()) { + AddHardwareInfo(row++, tr(key.toLatin1()), info[key].toString()); + } + + ui_->hardware_info->sortItems(0); + } + else { + ui_->hardware_info_stack->setCurrentWidget(ui_->hardware_info_not_connected_page); + } + + // Size + quint64 total = index_.data(DeviceManager::Role_Capacity).toLongLong(); + + QVariant free_var = index_.data(DeviceManager::Role_FreeSpace); + if (free_var.isValid()) { + quint64 free = free_var.toLongLong(); + + ui_->free_space_bar->set_total_bytes(total); + ui_->free_space_bar->set_free_bytes(free); + ui_->free_space_bar->show(); + } + else { + ui_->free_space_bar->hide(); + } + +} + +void DeviceProperties::UpdateFormats() { + + QString id = index_.data(DeviceManager::Role_UniqueId).toString(); + DeviceLister *lister = manager_->GetLister(index_.row()); + std::shared_ptr device = + manager_->GetConnectedDevice(index_.row()); + + // Transcode mode + MusicStorage::TranscodeMode mode = MusicStorage::TranscodeMode( + index_.data(DeviceManager::Role_TranscodeMode).toInt()); + switch (mode) { + case MusicStorage::Transcode_Always: + ui_->transcode_all->setChecked(true); + break; + + case MusicStorage::Transcode_Never: + ui_->transcode_off->setChecked(true); + break; + + case MusicStorage::Transcode_Unsupported: + default: + ui_->transcode_unsupported->setChecked(true); + break; + } + + // If there's no lister then the device is physically disconnected + if (!lister) { + ui_->formats_stack->setCurrentWidget(ui_->formats_page_not_connected); + ui_->open_device->setEnabled(false); + return; + } + + // If there's a lister but no device then the user just needs to open the + // device. This will cause a rescan so we don't do it automatically. + if (!device) { + ui_->formats_stack->setCurrentWidget(ui_->formats_page_not_connected); + ui_->open_device->setEnabled(true); + return; + } + + if (!updating_formats_) { + // Get the device's supported formats list. This takes a long time and it + // blocks, so do it in the background. + supported_formats_.clear(); + + QFuture future = QtConcurrent::run(std::bind(&ConnectedDevice::GetSupportedFiletypes, device, &supported_formats_)); + NewClosure(future, this, SLOT(UpdateFormatsFinished(QFuture)), future); + + ui_->formats_stack->setCurrentWidget(ui_->formats_page_loading); + updating_formats_ = true; + } + +} + +void DeviceProperties::accept() { + + QDialog::accept(); + + // Transcode mode + MusicStorage::TranscodeMode mode = MusicStorage::Transcode_Unsupported; + if (ui_->transcode_all->isChecked()) + mode = MusicStorage::Transcode_Always; + else if (ui_->transcode_off->isChecked()) + mode = MusicStorage::Transcode_Never; + else if (ui_->transcode_unsupported->isChecked()) + mode = MusicStorage::Transcode_Unsupported; + + // Transcode format + Song::FileType format = Song::FileType(ui_->transcode_format->itemData(ui_->transcode_format->currentIndex()).toInt()); + + // By default no icon is selected and thus no current item is selected + QString icon_name; + if (ui_->icon->currentItem() != nullptr) { + icon_name = ui_->icon->currentItem()->data(Qt::UserRole).toString(); + } + + manager_->SetDeviceOptions(index_.row(), ui_->name->text(), icon_name, mode, format); + +} + +void DeviceProperties::OpenDevice() { manager_->Connect(index_.row()); } + +void DeviceProperties::UpdateFormatsFinished(QFuture future) { + + updating_formats_ = false; + + if (!future.result()) { + supported_formats_.clear(); + } + + // Hide widgets if there are no supported types + ui_->supported_formats_container->setVisible(!supported_formats_.isEmpty()); + ui_->transcode_unsupported->setEnabled(!supported_formats_.isEmpty()); + + if (ui_->transcode_unsupported->isChecked() && supported_formats_.isEmpty()) { + ui_->transcode_off->setChecked(true); + } + + // Populate supported types list + ui_->supported_formats->clear(); + for (Song::FileType type : supported_formats_) { + QListWidgetItem *item = new QListWidgetItem(Song::TextForFiletype(type)); + ui_->supported_formats->addItem(item); + } + ui_->supported_formats->sortItems(); + +#ifdef HAVE_GSTREAMER + // Set the format combobox item + TranscoderPreset preset = Transcoder::PresetForFileType(Song::FileType(index_.data(DeviceManager::Role_TranscodeFormat).toInt())); + if (preset.type_ == Song::Type_Unknown) { + // The user hasn't chosen a format for this device yet, so work our way down + // a list of some preferred formats, picking the first one that is supported + preset = Transcoder::PresetForFileType(Transcoder::PickBestFormat(supported_formats_)); + } + ui_->transcode_format->setCurrentIndex(ui_->transcode_format->findText(preset.name_)); +#endif + + ui_->formats_stack->setCurrentWidget(ui_->formats_page); + +} + diff --git a/src/device/deviceproperties.h b/src/device/deviceproperties.h new file mode 100644 index 00000000..486469a3 --- /dev/null +++ b/src/device/deviceproperties.h @@ -0,0 +1,68 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef DEVICEPROPERTIES_H +#define DEVICEPROPERTIES_H + +#include "config.h" + +#include +#include +#include + +#include "core/song.h" + +class DeviceManager; +class Ui_DeviceProperties; + +class DeviceProperties : public QDialog { + Q_OBJECT + + public: + DeviceProperties(QWidget *parent = nullptr); + ~DeviceProperties(); + + void SetDeviceManager(DeviceManager *manager); + void ShowDevice(int row); + + public slots: + void accept(); + + private: + void UpdateHardwareInfo(); + void AddHardwareInfo(int row, const QString &key, const QString &value); + void UpdateFormats(); + + private slots: + void ModelChanged(); + void OpenDevice(); + void UpdateFormatsFinished(QFuture future); + + private: + Ui_DeviceProperties *ui_; + + DeviceManager *manager_; + QPersistentModelIndex index_; + + bool updating_formats_; + QList supported_formats_; +}; + +#endif // DEVICEPROPERTIES_H diff --git a/src/device/deviceproperties.ui b/src/device/deviceproperties.ui new file mode 100644 index 00000000..fdce0104 --- /dev/null +++ b/src/device/deviceproperties.ui @@ -0,0 +1,446 @@ + + + DeviceProperties + + + + 0 + 0 + 514 + 488 + + + + Device Properties + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + + + + 0 + + + + Information + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Name + + + + + + + + + + Icon + + + + + + + Qt::ScrollBarAlwaysOff + + + + 48 + 48 + + + + QListView::Static + + + QListView::LeftToRight + + + false + + + 5 + + + QListView::IconMode + + + true + + + + + + + + + + + + Hardware information + + + + + + 1 + + + + + 0 + + + 0 + + + + + Hardware information is only available while the device is connected. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + 0 + + + 0 + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + false + + + 2 + + + false + + + true + + + false + + + + + + + + + + + + + + + + + File formats + + + + + + 0 + + + + + 0 + + + + + Supported formats + + + + + + This device supports the following file formats: + + + + + + + QAbstractItemView::NoSelection + + + + + + + + + + Strawberry can automatically convert the music you copy to this device into a format that it can play. + + + true + + + + + + + Do not convert any music + + + + + + + Convert any music that the device can't play + + + true + + + + + + + Convert all music + + + + + + + + + Preferred format + + + + + + + + 0 + 0 + + + + + + + + + + Qt::Vertical + + + + 20 + 183 + + + + + + + + + + 0 + + + + + This device must be connected and opened before Strawberry can see what file formats it supports. + + + true + + + + + + + + + Open device + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 309 + + + + + + + + + + 0 + + + + + + + + + + + 0 + 0 + + + + Querying device... + + + + + + + + + Qt::Vertical + + + + 20 + 358 + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + FreeSpaceBar + QWidget +
widgets/freespacebar.h
+ 1 +
+ + BusyIndicator + QWidget +
widgets/busyindicator.h
+
+
+ + name + icon + hardware_info + tabWidget + supported_formats + transcode_format + buttonBox + + + + + + + buttonBox + accepted() + DeviceProperties + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DeviceProperties + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/device/devicestatefiltermodel.cpp b/src/device/devicestatefiltermodel.cpp new file mode 100644 index 00000000..125daa80 --- /dev/null +++ b/src/device/devicestatefiltermodel.cpp @@ -0,0 +1,47 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "devicestatefiltermodel.h" + +DeviceStateFilterModel::DeviceStateFilterModel(QObject *parent, DeviceManager::State state) + : QSortFilterProxyModel(parent), + state_(state) +{ + connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(ProxyRowCountChanged())); + connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(ProxyRowCountChanged())); + connect(this, SIGNAL(modelReset()), SLOT(ProxyRowCountChanged())); +} + +bool DeviceStateFilterModel::filterAcceptsRow(int row, const QModelIndex&) const { + return sourceModel()->index(row, 0).data(DeviceManager::Role_State).toInt() != state_; +} + +void DeviceStateFilterModel::ProxyRowCountChanged() { + emit IsEmptyChanged(rowCount() == 0); +} + +void DeviceStateFilterModel::setSourceModel(QAbstractItemModel* sourceModel) { + QSortFilterProxyModel::setSourceModel(sourceModel); + setDynamicSortFilter(true); + setSortCaseSensitivity(Qt::CaseInsensitive); + sort(0); +} diff --git a/src/device/devicestatefiltermodel.h b/src/device/devicestatefiltermodel.h new file mode 100644 index 00000000..fab70165 --- /dev/null +++ b/src/device/devicestatefiltermodel.h @@ -0,0 +1,52 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef DEVICESTATEFILTERMODEL_H +#define DEVICESTATEFILTERMODEL_H + +#include "config.h" + +#include + +#include "devicemanager.h" + +class DeviceStateFilterModel : public QSortFilterProxyModel { + Q_OBJECT + +public: + DeviceStateFilterModel(QObject *parent, DeviceManager::State state = DeviceManager::State_Remembered); + + void setSourceModel(QAbstractItemModel *sourceModel); + +signals: + void IsEmptyChanged(bool is_empty); + +protected: + bool filterAcceptsRow(int row, const QModelIndex &parent) const; + +private slots: + void ProxyRowCountChanged(); + +private: + DeviceManager::State state_; +}; + +#endif // DEVICESTATEFILTERMODEL_H + diff --git a/src/device/deviceview.cpp b/src/device/deviceview.cpp new file mode 100644 index 00000000..fe8ce04e --- /dev/null +++ b/src/device/deviceview.cpp @@ -0,0 +1,443 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "deviceview.h" + +#include "connecteddevice.h" +#include "devicelister.h" +#include "devicemanager.h" +#include "deviceproperties.h" +#include "core/application.h" +#include "core/deletefiles.h" +#include "core/mergedproxymodel.h" +#include "core/mimedata.h" +#include "core/iconloader.h" +#include "collection/collectiondirectorymodel.h" +#include "collection/collectionmodel.h" +#include "collection/collectionview.h" +#include "dialogs/organisedialog.h" +#include "dialogs/organiseerrordialog.h" + +const int DeviceItemDelegate::kIconPadding = 6; + +DeviceItemDelegate::DeviceItemDelegate(QObject *parent) + : CollectionItemDelegate(parent) {} + +void DeviceItemDelegate::paint(QPainter *p, const QStyleOptionViewItem &opt, const QModelIndex &index) const { + + // Is it a device or a collection item? + if (index.data(DeviceManager::Role_State).isNull()) { + CollectionItemDelegate::paint(p, opt, index); + return; + } + + // Draw the background + const QStyleOptionViewItemV3 *vopt = qstyleoption_cast(&opt); + const QWidget *widget = vopt->widget; + QStyle *style = widget->style() ? widget->style() : QApplication::style(); + style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, p, widget); + + p->save(); + + // Font for the status line + QFont status_font(opt.font); + +#ifdef Q_OS_WIN32 + status_font.setPointSize(status_font.pointSize() - 1); +#else + status_font.setPointSize(status_font.pointSize() - 2); +#endif + + const int text_height = QFontMetrics(opt.font).height() + QFontMetrics(status_font).height(); + + QRect line1(opt.rect); + QRect line2(opt.rect); + line1.setTop(line1.top() + (opt.rect.height() - text_height) / 2); + line2.setTop(line1.top() + QFontMetrics(opt.font).height()); + line1.setLeft(line1.left() + DeviceManager::kDeviceIconSize + kIconPadding); + line2.setLeft(line2.left() + DeviceManager::kDeviceIconSize + kIconPadding); + + // Change the color for selected items + if (opt.state & QStyle::State_Selected) { + p->setPen(opt.palette.color(QPalette::HighlightedText)); + } + + // Draw the icon + p->drawPixmap(opt.rect.topLeft(), index.data(Qt::DecorationRole).value()); + + // Draw the first line (device name) + p->drawText(line1, Qt::AlignLeft | Qt::AlignTop, index.data().toString()); + + // Draw the second line (status) + DeviceManager::State state = static_cast(index.data(DeviceManager::Role_State).toInt()); + QVariant progress = index.data(DeviceManager::Role_UpdatingPercentage); + QString status_text; + + if (progress.isValid()) { + status_text = tr("Updating %1%...").arg(progress.toInt()); + } + else { + switch (state) { + case DeviceManager::State_Remembered: + status_text = tr("Not connected"); + break; + + case DeviceManager::State_NotMounted: + status_text = tr("Not mounted - double click to mount"); + break; + + case DeviceManager::State_NotConnected: + status_text = tr("Double click to open"); + break; + + case DeviceManager::State_Connected: { + QVariant song_count = index.data(DeviceManager::Role_SongCount); + if (song_count.isValid()) { + int count = song_count.toInt(); + if (count == 1) // TODO: Fix this properly + status_text = tr("%1 song").arg(count); + else + status_text = tr("%1 songs").arg(count); + } + else { + status_text = index.data(DeviceManager::Role_MountPath).toString(); + } + break; + } + } + } + + if (opt.state & QStyle::State_Selected) + p->setPen(opt.palette.color(QPalette::HighlightedText)); + else + p->setPen(opt.palette.color(QPalette::Dark)); + p->setFont(status_font); + p->drawText(line2, Qt::AlignLeft | Qt::AlignTop, status_text); + + p->restore(); +} + +DeviceView::DeviceView(QWidget *parent) + : AutoExpandingTreeView(parent), + app_(nullptr), + merged_model_(nullptr), + sort_model_(nullptr), + properties_dialog_(new DeviceProperties), + device_menu_(nullptr), + collection_menu_(nullptr) { + setItemDelegate(new DeviceItemDelegate(this)); + SetExpandOnReset(false); + setAttribute(Qt::WA_MacShowFocusRect, false); + setHeaderHidden(true); + setAllColumnsShowFocus(true); + setDragEnabled(true); + setDragDropMode(QAbstractItemView::DragOnly); + setSelectionMode(QAbstractItemView::ExtendedSelection); +} + +DeviceView::~DeviceView() {} + +void DeviceView::SetApplication(Application *app) { + + Q_ASSERT(app_ == nullptr); + app_ = app; + + connect(app_->device_manager(), SIGNAL(DeviceConnected(int)), SLOT(DeviceConnected(int))); + connect(app_->device_manager(), SIGNAL(DeviceDisconnected(int)), SLOT(DeviceDisconnected(int))); + + sort_model_ = new QSortFilterProxyModel(this); + sort_model_->setSourceModel(app_->device_manager()); + sort_model_->setDynamicSortFilter(true); + sort_model_->setSortCaseSensitivity(Qt::CaseInsensitive); + sort_model_->sort(0); + + merged_model_ = new MergedProxyModel(this); + merged_model_->setSourceModel(sort_model_); + + connect(merged_model_, SIGNAL(SubModelReset(QModelIndex, QAbstractItemModel*)), SLOT(RecursivelyExpand(QModelIndex))); + + setModel(merged_model_); + properties_dialog_->SetDeviceManager(app_->device_manager()); + +#ifdef HAVE_GSTREAMER + organise_dialog_.reset(new OrganiseDialog(app_->task_manager())); + organise_dialog_->SetDestinationModel(app_->collection_model()->directory_model()); +#endif + +} + +void DeviceView::contextMenuEvent(QContextMenuEvent *e) { + + if (!device_menu_) { + device_menu_ = new QMenu(this); + collection_menu_ = new QMenu(this); + + // Device menu + eject_action_ = device_menu_->addAction(IconLoader::Load("media-eject"), tr("Safely remove device"), this, SLOT(Unmount())); + forget_action_ = device_menu_->addAction(IconLoader::Load("list-remove"), tr("Forget device"), this, SLOT(Forget())); + device_menu_->addSeparator(); + properties_action_ = device_menu_->addAction(IconLoader::Load("configure"), tr("Device properties..."), this, SLOT(Properties())); + + // Collection menu + add_to_playlist_action_ = collection_menu_->addAction(IconLoader::Load("media-play"), tr("Append to current playlist"), this, SLOT(AddToPlaylist())); + load_action_ = collection_menu_->addAction(IconLoader::Load("media-play"), tr("Replace current playlist"), this, SLOT(Load())); + open_in_new_playlist_ = collection_menu_->addAction(IconLoader::Load("document-new"), tr("Open in new playlist"), this, SLOT(OpenInNewPlaylist())); + + collection_menu_->addSeparator(); +#ifdef HAVE_GSTREAMER + organise_action_ = collection_menu_->addAction(IconLoader::Load("edit-copy"), tr("Copy to collection..."), this, SLOT(Organise())); +#endif + delete_action_ = collection_menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete from device..."), this, SLOT(Delete())); + } + + menu_index_ = currentIndex(); + + const QModelIndex device_index = MapToDevice(menu_index_); + const QModelIndex collection_index = MapToCollection(menu_index_); + + if (device_index.isValid()) { + const bool is_plugged_in = app_->device_manager()->GetLister(device_index.row()); + const bool is_remembered = app_->device_manager()->GetDatabaseId(device_index.row()) != -1; + + forget_action_->setEnabled(is_remembered); + eject_action_->setEnabled(is_plugged_in); + + device_menu_->popup(e->globalPos()); + } else if (collection_index.isValid()) { + const QModelIndex parent_device_index = FindParentDevice(menu_index_); + + bool is_filesystem_device = false; + if (parent_device_index.isValid()) { + std::shared_ptr device = + app_->device_manager()->GetConnectedDevice(parent_device_index.row()); + if (device && !device->LocalPath().isEmpty()) is_filesystem_device = true; + } + + organise_action_->setEnabled(is_filesystem_device); + + collection_menu_->popup(e->globalPos()); + } + +} + +QModelIndex DeviceView::MapToDevice(const QModelIndex &merged_model_index) const { + + QModelIndex sort_model_index = merged_model_->mapToSource(merged_model_index); + if (sort_model_index.model() != sort_model_) + return QModelIndex(); + + return sort_model_->mapToSource(sort_model_index); + +} + +QModelIndex DeviceView::FindParentDevice(const QModelIndex &merged_model_index) const { + + QModelIndex index = merged_model_->FindSourceParent(merged_model_index); + if (index.model() != sort_model_) return QModelIndex(); + + return sort_model_->mapToSource(index); + +} + +QModelIndex DeviceView::MapToCollection(const QModelIndex &merged_model_index) const { + + QModelIndex sort_model_index = merged_model_->mapToSource(merged_model_index); + if (const QSortFilterProxyModel *sort_model = qobject_cast(sort_model_index.model())) { + return sort_model->mapToSource(sort_model_index); + } + + return QModelIndex(); + +} + +void DeviceView::Connect() { + QModelIndex device_idx = MapToDevice(menu_index_); + app_->device_manager()->data(device_idx, MusicStorage::Role_StorageForceConnect); +} + +void DeviceView::DeviceConnected(int row) { + + std::shared_ptr device = + app_->device_manager()->GetConnectedDevice(row); + if (!device) return; + + QModelIndex sort_idx = sort_model_->mapFromSource(app_->device_manager()->index(row)); + + QSortFilterProxyModel *sort_model = new QSortFilterProxyModel(device->model()); + sort_model->setSourceModel(device->model()); + sort_model->setSortRole(CollectionModel::Role_SortText); + sort_model->setDynamicSortFilter(true); + sort_model->sort(0); + merged_model_->AddSubModel(sort_idx, sort_model); + + expand(menu_index_); +} + +void DeviceView::DeviceDisconnected(int row) { + merged_model_->RemoveSubModel(sort_model_->mapFromSource(app_->device_manager()->index(row))); +} + +void DeviceView::Forget() { + + QModelIndex device_idx = MapToDevice(menu_index_); + QString unique_id = app_->device_manager()->data(device_idx, DeviceManager::Role_UniqueId).toString(); + if (app_->device_manager()->GetLister(device_idx.row()) && app_->device_manager()->GetLister(device_idx.row())->AskForScan(unique_id)) { + std::unique_ptr dialog(new QMessageBox( + QMessageBox::Question, tr("Forget device"), + tr("Forgetting a device will remove it from this list and Strawberry will have to rescan all the songs again next time you connect it."), + QMessageBox::Cancel, this)); + QPushButton *forget = dialog->addButton(tr("Forget device"), QMessageBox::DestructiveRole); + dialog->exec(); + + if (dialog->clickedButton() != forget) return; + } + + app_->device_manager()->Forget(device_idx.row()); + +} + +void DeviceView::Properties() { + properties_dialog_->ShowDevice(MapToDevice(menu_index_).row()); +} + +void DeviceView::mouseDoubleClickEvent(QMouseEvent *event) { + + AutoExpandingTreeView::mouseDoubleClickEvent(event); + + QModelIndex merged_index = indexAt(event->pos()); + QModelIndex device_index = MapToDevice(merged_index); + if (device_index.isValid()) { + if (!app_->device_manager()->GetConnectedDevice(device_index.row())) { + menu_index_ = merged_index; + Connect(); + } + } + +} + +SongList DeviceView::GetSelectedSongs() const { + + QModelIndexList selected_merged_indexes = selectionModel()->selectedRows(); + SongList songs; + for (const QModelIndex &merged_index : selected_merged_indexes) { + QModelIndex collection_index = MapToCollection(merged_index); + if (!collection_index.isValid()) continue; + + const CollectionModel *collection = qobject_cast(collection_index.model()); + if (!collection) continue; + + songs << collection->GetChildSongs(collection_index); + } + return songs; + +} + +void DeviceView::Load() { + + QMimeData *data = model()->mimeData(selectedIndexes()); + if (MimeData *mime_data = qobject_cast(data)) { + mime_data->clear_first_ = true; + } + emit AddToPlaylistSignal(data); +} + +void DeviceView::AddToPlaylist() { + emit AddToPlaylistSignal(model()->mimeData(selectedIndexes())); +} + +void DeviceView::OpenInNewPlaylist() { + QMimeData *data = model()->mimeData(selectedIndexes()); + if (MimeData *mime_data = qobject_cast(data)) { + mime_data->open_in_new_playlist_ = true; + } + emit AddToPlaylistSignal(data); +} + +#ifdef HAVE_GSTREAMER +void DeviceView::Delete() { + + if (selectedIndexes().isEmpty()) return; + + // Take the device of the first selected item + QModelIndex device_index = FindParentDevice(selectedIndexes()[0]); + if (!device_index.isValid()) return; + + if (QMessageBox::question(this, tr("Delete files"), + tr("These files will be deleted from the device, are you sure you want to continue?"), + QMessageBox::Yes, QMessageBox::Cancel) != QMessageBox::Yes) + return; + + std::shared_ptr storage = device_index.data(MusicStorage::Role_Storage).value>(); + + DeleteFiles *delete_files = new DeleteFiles(app_->task_manager(), storage); + connect(delete_files, SIGNAL(Finished(SongList)), SLOT(DeleteFinished(SongList))); + delete_files->Start(GetSelectedSongs()); + +} + +void DeviceView::Organise() { + + SongList songs = GetSelectedSongs(); + QStringList filenames; + for (const Song &song : songs) { + filenames << song.url().toLocalFile(); + } + + organise_dialog_->SetCopy(true); + organise_dialog_->SetFilenames(filenames); + organise_dialog_->show(); + +} +#endif + +void DeviceView::Unmount() { + QModelIndex device_idx = MapToDevice(menu_index_); + app_->device_manager()->Unmount(device_idx.row()); +} + +#ifdef HAVE_GSTREAMER +void DeviceView::DeleteFinished(const SongList &songs_with_errors) { + + if (songs_with_errors.isEmpty()) return; + + OrganiseErrorDialog *dialog = new OrganiseErrorDialog(this); + dialog->Show(OrganiseErrorDialog::Type_Delete, songs_with_errors); + // It deletes itself when the user closes it + +} +#endif + +bool DeviceView::CanRecursivelyExpand(const QModelIndex &index) const { + // Never expand devices + return index.parent().isValid(); +} + diff --git a/src/device/deviceview.h b/src/device/deviceview.h new file mode 100644 index 00000000..021654c7 --- /dev/null +++ b/src/device/deviceview.h @@ -0,0 +1,121 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef DEVICEVIEW_H +#define DEVICEVIEW_H + +#include "config.h" + +#include + +#include "core/song.h" +#include "collection/collectionview.h" +#include "widgets/autoexpandingtreeview.h" + +class QAction; +class QMenu; +class QSortFilterProxyModel; + +class Application; +class DeviceManager; +class DeviceProperties; +class CollectionModel; +class MergedProxyModel; + +class DeviceItemDelegate : public CollectionItemDelegate { + public: + DeviceItemDelegate(QObject *parent); + + static const int kIconPadding; + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; +}; + +class DeviceView : public AutoExpandingTreeView { + Q_OBJECT + + public: + DeviceView(QWidget *parent = nullptr); + ~DeviceView(); + + void SetApplication(Application *app); + + protected: + void contextMenuEvent(QContextMenuEvent*); + void mouseDoubleClickEvent(QMouseEvent *event); + + private slots: + // Device menu actions + void Connect(); + void Unmount(); + void Forget(); + void Properties(); + + // Collection menu actions + void Load(); + void AddToPlaylist(); + void OpenInNewPlaylist(); +#ifdef HAVE_GSTREAMER + void Organise(); + void Delete(); +#endif + + void DeviceConnected(int row); + void DeviceDisconnected(int row); + +#ifdef HAVE_GSTREAMER + void DeleteFinished(const SongList &songs_with_errors); +#endif + + // AutoExpandingTreeView + bool CanRecursivelyExpand(const QModelIndex &index) const; + + private: + QModelIndex MapToDevice(const QModelIndex &merged_model_index) const; + QModelIndex MapToCollection(const QModelIndex &merged_model_index) const; + QModelIndex FindParentDevice(const QModelIndex &merged_model_index) const; + SongList GetSelectedSongs() const; + + private: + Application *app_; + MergedProxyModel *merged_model_; + QSortFilterProxyModel *sort_model_; + + std::unique_ptr properties_dialog_; +#ifdef HAVE_GSTREAMER + std::unique_ptr organise_dialog_; +#endif + + QMenu *device_menu_; + QAction *eject_action_; + QAction *forget_action_; + QAction *properties_action_; + + QMenu *collection_menu_; + QAction *load_action_; + QAction *add_to_playlist_action_; + QAction *open_in_new_playlist_; + QAction *organise_action_; + QAction *delete_action_; + + QModelIndex menu_index_; +}; + +#endif // DEVICEVIEW_H diff --git a/src/device/deviceviewcontainer.cpp b/src/device/deviceviewcontainer.cpp new file mode 100644 index 00000000..68d5d09a --- /dev/null +++ b/src/device/deviceviewcontainer.cpp @@ -0,0 +1,59 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "deviceviewcontainer.h" +#include "ui_deviceviewcontainer.h" + +#include "core/iconloader.h" + +DeviceViewContainer::DeviceViewContainer(QWidget *parent) : QWidget(parent), ui_(new Ui::DeviceViewContainer), loaded_icons_(false) { + + ui_->setupUi(this); + + QPalette palette(ui_->windows_is_broken_frame->palette()); + palette.setColor(QPalette::Background, QColor(255, 255, 222)); + ui_->windows_is_broken_frame->setPalette(palette); + +#ifdef Q_OS_WIN + ui_->windows_is_broken_frame->show(); +#else + ui_->windows_is_broken_frame->hide(); +#endif + +} + +DeviceViewContainer::~DeviceViewContainer() { delete ui_; } + +void DeviceViewContainer::showEvent(QShowEvent *e) { + + if (!loaded_icons_) { + loaded_icons_ = true; + + ui_->close_frame_button->setIcon(IconLoader::Load("edit-delete")); + ui_->warning_icon->setPixmap(IconLoader::Load("dialog-warning").pixmap(22)); + } + + QWidget::showEvent(e); + +} + +DeviceView *DeviceViewContainer::view() const { return ui_->view; } diff --git a/src/device/deviceviewcontainer.h b/src/device/deviceviewcontainer.h new file mode 100644 index 00000000..0f90d8f0 --- /dev/null +++ b/src/device/deviceviewcontainer.h @@ -0,0 +1,51 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef DEVICEVIEWCONTAINER_H +#define DEVICEVIEWCONTAINER_H + +#include "config.h" + +#include + +namespace Ui { +class DeviceViewContainer; +} + +class DeviceView; + +class DeviceViewContainer : public QWidget { + Q_OBJECT + + public: + explicit DeviceViewContainer(QWidget *parent = nullptr); + ~DeviceViewContainer(); + + DeviceView* view() const; + + protected: + void showEvent(QShowEvent *); + + private: + Ui::DeviceViewContainer *ui_; + bool loaded_icons_; +}; + +#endif // DEVICEVIEWCONTAINER_H diff --git a/src/device/deviceviewcontainer.ui b/src/device/deviceviewcontainer.ui new file mode 100644 index 00000000..d2e4dcf1 --- /dev/null +++ b/src/device/deviceviewcontainer.ui @@ -0,0 +1,99 @@ + + + DeviceViewContainer + + + + 0 + 0 + 391 + 396 + + + + Form + + + + 0 + + + 0 + + + + + true + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + + 0 + 0 + + + + iPods and USB devices currently don't work on Windows. Sorry! + + + true + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + DeviceView + QWidget +
device/deviceview.h
+ 1 +
+
+ + + + close_frame_button + clicked() + windows_is_broken_frame + hide() + + + 362 + 31 + + + 369 + 40 + + + + +
diff --git a/src/device/filesystemdevice.cpp b/src/device/filesystemdevice.cpp new file mode 100644 index 00000000..bb533a47 --- /dev/null +++ b/src/device/filesystemdevice.cpp @@ -0,0 +1,67 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "devicelister.h" +#include "devicemanager.h" +#include "filesystemdevice.h" +#include "core/application.h" +#include "collection/collectionbackend.h" +#include "collection/collectionmodel.h" +#include "collection/collectionwatcher.h" + +#include +#include + +FilesystemDevice::FilesystemDevice(const QUrl &url, DeviceLister *lister, const QString &unique_id, DeviceManager *manager, Application *app, int database_id, bool first_time) + : FilesystemMusicStorage(url.toLocalFile()), ConnectedDevice(url, lister, unique_id, manager, app, database_id, first_time), watcher_(new CollectionWatcher), watcher_thread_(new QThread(this)) +{ + + watcher_->moveToThread(watcher_thread_); + watcher_thread_->start(QThread::IdlePriority); + + watcher_->set_device_name(manager->data(manager->index(manager->FindDeviceById(unique_id)), DeviceManager::Role_FriendlyName).toString()); + watcher_->set_backend(backend_); + watcher_->set_task_manager(app_->task_manager()); + + connect(backend_, SIGNAL(DirectoryDiscovered(Directory,SubdirectoryList)), watcher_, SLOT(AddDirectory(Directory,SubdirectoryList))); + connect(backend_, SIGNAL(DirectoryDeleted(Directory)), watcher_, SLOT(RemoveDirectory(Directory))); + connect(watcher_, SIGNAL(NewOrUpdatedSongs(SongList)), backend_, SLOT(AddOrUpdateSongs(SongList))); + connect(watcher_, SIGNAL(SongsMTimeUpdated(SongList)), backend_, SLOT(UpdateMTimesOnly(SongList))); + connect(watcher_, SIGNAL(SongsDeleted(SongList)), backend_, SLOT(DeleteSongs(SongList))); + connect(watcher_, SIGNAL(SubdirsDiscovered(SubdirectoryList)), backend_, SLOT(AddOrUpdateSubdirs(SubdirectoryList))); + connect(watcher_, SIGNAL(SubdirsMTimeUpdated(SubdirectoryList)), backend_, SLOT(AddOrUpdateSubdirs(SubdirectoryList))); + connect(watcher_, SIGNAL(CompilationsNeedUpdating()), backend_, SLOT(UpdateCompilations())); + connect(watcher_, SIGNAL(ScanStarted(int)), SIGNAL(TaskStarted(int))); + +} + +void FilesystemDevice::Init() { + InitBackendDirectory(url_.toLocalFile(), first_time_); + model_->Init(); +} + +FilesystemDevice::~FilesystemDevice() { + watcher_->deleteLater(); + watcher_thread_->exit(); + watcher_thread_->wait(); +} + diff --git a/src/device/filesystemdevice.h b/src/device/filesystemdevice.h new file mode 100644 index 00000000..38499e71 --- /dev/null +++ b/src/device/filesystemdevice.h @@ -0,0 +1,52 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef FILESYSTEMDEVICE_H +#define FILESYSTEMDEVICE_H + +#include "config.h" + +#include "connecteddevice.h" +#include "core/filesystemmusicstorage.h" + +class DeviceManager; +class CollectionWatcher; + +class FilesystemDevice : public ConnectedDevice, public virtual FilesystemMusicStorage { + Q_OBJECT + +public: + Q_INVOKABLE FilesystemDevice( + const QUrl &url, DeviceLister *lister, + const QString &unique_id, DeviceManager *manager, + Application *app, + int database_id, bool first_time); + ~FilesystemDevice(); + + void Init(); + + static QStringList url_schemes() { return QStringList() << "file"; } + +private: + CollectionWatcher *watcher_; + QThread *watcher_thread_; +}; + +#endif // FILESYSTEMDEVICE_H diff --git a/src/device/giolister.cpp b/src/device/giolister.cpp new file mode 100644 index 00000000..de296f6b --- /dev/null +++ b/src/device/giolister.cpp @@ -0,0 +1,580 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include + +#include +#include +#include + +#include "giolister.h" +#include "core/logging.h" +#include "core/signalchecker.h" + +using std::placeholders::_1; +using std::placeholders::_2; +using std::placeholders::_3; + +QString GioLister::DeviceInfo::unique_id() const { + + if (mount) + return QString("Gio/%1/%2/%3").arg(mount_uuid, filesystem_type).arg(filesystem_size); + + return QString("Gio/unmounted/%1").arg((qulonglong)volume.get()); + +} + +bool GioLister::DeviceInfo::is_suitable() const { + + if (!volume) return false; // This excludes smb or ssh mounts + + if (drive && !drive_removable) return false; // This excludes internal drives + + if (filesystem_type.isEmpty()) return true; + + return filesystem_type != "udf" && + filesystem_type != "smb" && + filesystem_type != "cifs" && + filesystem_type != "ssh" && + filesystem_type != "isofs"; + +} + +template +void OperationFinished(F f, GObject* object, GAsyncResult* result) { + + T* obj = reinterpret_cast(object); + GError* error = nullptr; + + f(obj, result, &error); + + if (error) { + qLog(Error) << "Mount/unmount error:" << error->message; + g_error_free(error); + } + +} + +void GioLister::VolumeMountFinished(GObject* object, GAsyncResult* result, gpointer) { + OperationFinished(std::bind(g_volume_mount_finish, _1, _2, _3), object, result); +} + +void GioLister::Init() { + + monitor_.reset_without_add(g_volume_monitor_get()); + + // Get existing volumes + GList* const volumes = g_volume_monitor_get_volumes(monitor_); + for (GList* p = volumes; p; p = p->next) { + GVolume* volume = static_cast(p->data); + + VolumeAdded(volume); + g_object_unref(volume); + } + g_list_free(volumes); + + // Get existing mounts + GList* const mounts = g_volume_monitor_get_mounts(monitor_); + for (GList* p = mounts; p; p = p->next) { + GMount* mount = static_cast(p->data); + + MountAdded(mount); + g_object_unref(mount); + } + g_list_free(mounts); + + // Connect signals from the monitor + signals_.append(CHECKED_GCONNECT(monitor_, "volume-added", &VolumeAddedCallback, this)); + signals_.append(CHECKED_GCONNECT(monitor_, "volume-removed", &VolumeRemovedCallback, this)); + signals_.append(CHECKED_GCONNECT(monitor_, "mount-added", &MountAddedCallback, this)); + signals_.append(CHECKED_GCONNECT(monitor_, "mount-changed", &MountChangedCallback, this)); + signals_.append(CHECKED_GCONNECT(monitor_, "mount-removed", &MountRemovedCallback, this)); + +} + +GioLister::~GioLister() { + for (gulong signal : signals_) { + g_signal_handler_disconnect(monitor_, signal); + } +} + +QStringList GioLister::DeviceUniqueIDs() { + QMutexLocker l(&mutex_); + return devices_.keys(); +} + +QVariantList GioLister::DeviceIcons(const QString& id) { + + QVariantList ret; + QMutexLocker l(&mutex_); + if (!devices_.contains(id)) return ret; + + const DeviceInfo& info = devices_[id]; + + if (info.mount) { + ret << DeviceLister::GuessIconForPath(info.mount_path); + ret << info.mount_icon_names; + } + + ret << DeviceLister::GuessIconForModel(QString(), info.mount_name); + + return ret; + +} + +QString GioLister::DeviceManufacturer(const QString& id) { return QString(); } + +QString GioLister::DeviceModel(const QString& id) { + + QMutexLocker l(&mutex_); + if (!devices_.contains(id)) return QString(); + const DeviceInfo& info = devices_[id]; + + return info.drive_name.isEmpty() ? info.volume_name : info.drive_name; + +} + +quint64 GioLister::DeviceCapacity(const QString& id) { + return LockAndGetDeviceInfo(id, &DeviceInfo::filesystem_size); +} + +quint64 GioLister::DeviceFreeSpace(const QString& id) { + return LockAndGetDeviceInfo(id, &DeviceInfo::filesystem_free); +} + +QString GioLister::MakeFriendlyName(const QString& id) { + return DeviceModel(id); +} + +QVariantMap GioLister::DeviceHardwareInfo(const QString& id) { + + QVariantMap ret; + + QMutexLocker l(&mutex_); + if (!devices_.contains(id)) return ret; + const DeviceInfo& info = devices_[id]; + + ret[QT_TR_NOOP("Mount point")] = info.mount_path; + ret[QT_TR_NOOP("Device")] = info.volume_unix_device; + ret[QT_TR_NOOP("URI")] = info.mount_uri; + return ret; + +} + +QList GioLister::MakeDeviceUrls(const QString& id) { + + QString mount_point; + QString uri; + { + QMutexLocker l(&mutex_); + mount_point = devices_[id].mount_path; + uri = devices_[id].mount_uri; + } + + // gphoto2 gives invalid hostnames with []:, characters in + uri.replace(QRegExp("//\\[usb:(\\d+),(\\d+)\\]"), "//usb-\\1-\\2"); + + QUrl url(uri); + + QList ret; + + // Special case for file:// GIO URIs - we have to check whether they point + // to an ipod. + if (url.isValid() && url.scheme() == "file") { + ret << MakeUrlFromLocalPath(url.path()); + } else { + ret << url; + } + + ret << MakeUrlFromLocalPath(mount_point); + return ret; + +} + +void GioLister::VolumeAddedCallback(GVolumeMonitor*, GVolume* v, gpointer d) { + static_cast(d)->VolumeAdded(v); +} + +void GioLister::VolumeRemovedCallback(GVolumeMonitor*, GVolume* v, gpointer d) { + static_cast(d)->VolumeRemoved(v); +} + +void GioLister::MountAddedCallback(GVolumeMonitor*, GMount* m, gpointer d) { + static_cast(d)->MountAdded(m); +} + +void GioLister::MountChangedCallback(GVolumeMonitor*, GMount* m, gpointer d) { + static_cast(d)->MountChanged(m); +} + +void GioLister::MountRemovedCallback(GVolumeMonitor*, GMount* m, gpointer d) { + static_cast(d)->MountRemoved(m); +} + +void GioLister::VolumeAdded(GVolume* volume) { + + g_object_ref(volume); + + DeviceInfo info; + info.ReadVolumeInfo(volume); +#ifdef HAVE_AUDIOCD + if (info.volume_root_uri.startsWith("cdda")) + // Audio CD devices are already handled by CDDA lister + return; +#endif + info.ReadDriveInfo(g_volume_get_drive(volume)); + info.ReadMountInfo(g_volume_get_mount(volume)); + if (!info.is_suitable()) return; + + { + QMutexLocker l(&mutex_); + devices_[info.unique_id()] = info; + } + + emit DeviceAdded(info.unique_id()); + +} + +void GioLister::VolumeRemoved(GVolume* volume) { + + QString id; + + { + QMutexLocker l(&mutex_); + id = FindUniqueIdByVolume(volume); + if (id.isNull()) return; + + devices_.remove(id); + } + + emit DeviceRemoved(id); +} + +void GioLister::MountAdded(GMount* mount) { + + g_object_ref(mount); + + DeviceInfo info; + info.ReadVolumeInfo(g_mount_get_volume(mount)); +#ifdef HAVE_AUDIOCD + if (info.volume_root_uri.startsWith("cdda")) + // Audio CD devices are already handled by CDDA lister + return; +#endif + info.ReadMountInfo(mount); + info.ReadDriveInfo(g_mount_get_drive(mount)); + if (!info.is_suitable()) return; + + QString old_id; + { + QMutexLocker l(&mutex_); + + // The volume might already exist - either mounted or unmounted. + for (const QString& id : devices_.keys()) { + if (devices_[id].volume == info.volume) { + old_id = id; + break; + } + } + + if (!old_id.isEmpty() && old_id != info.unique_id()) { + // If the ID has changed (for example, after it's been mounted), we need + // to remove the old device. + devices_.remove(old_id); + emit DeviceRemoved(old_id); + + old_id = QString(); + } + devices_[info.unique_id()] = info; + } + + if (!old_id.isEmpty()) + emit DeviceChanged(old_id); + else { + emit DeviceAdded(info.unique_id()); + } + +} + +void GioLister::MountChanged(GMount* mount) { + + QString id; + { + QMutexLocker l(&mutex_); + id = FindUniqueIdByMount(mount); + if (id.isNull()) return; + + g_object_ref(mount); + + DeviceInfo new_info; + new_info.ReadMountInfo(mount); + new_info.ReadVolumeInfo(g_mount_get_volume(mount)); + new_info.ReadDriveInfo(g_mount_get_drive(mount)); + + // Ignore the change if the new info is useless + if (new_info.invalid_enclosing_mount || + (devices_[id].filesystem_size != 0 && new_info.filesystem_size == 0) || + (!devices_[id].filesystem_type.isEmpty() && new_info.filesystem_type.isEmpty())) + return; + + devices_[id] = new_info; + } + + emit DeviceChanged(id); + +} + +void GioLister::MountRemoved(GMount* mount) { + + QString id; + { + QMutexLocker l(&mutex_); + id = FindUniqueIdByMount(mount); + if (id.isNull()) return; + + devices_.remove(id); + } + + emit DeviceRemoved(id); + +} + +QString GioLister::DeviceInfo::ConvertAndFree(char* str) { + QString ret = QString::fromUtf8(str); + g_free(str); + return ret; +} + +void GioLister::DeviceInfo::ReadMountInfo(GMount* mount) { + + // Get basic information + this->mount.reset_without_add(mount); + if (!mount) return; + + mount_name = ConvertAndFree(g_mount_get_name(mount)); + + // Get the icon name(s) + mount_icon_names.clear(); + GIcon* icon = g_mount_get_icon(mount); + if (G_IS_THEMED_ICON(icon)) { + const char* const* icons = g_themed_icon_get_names(G_THEMED_ICON(icon)); + for (const char* const* p = icons; *p; ++p) { + mount_icon_names << QString::fromUtf8(*p); + } + } + g_object_unref(icon); + + GFile* root = g_mount_get_root(mount); + + // Get the mount path + mount_path = ConvertAndFree(g_file_get_path(root)); + mount_uri = ConvertAndFree(g_file_get_uri(root)); + + // Do a sanity check to make sure the root is actually this mount - when a + // device is unmounted GIO sends a changed signal before the removed signal, + // and we end up reading information about the / filesystem by mistake. + GError* error = nullptr; + GMount* actual_mount = g_file_find_enclosing_mount(root, nullptr, &error); + if (error || !actual_mount) { + g_error_free(error); + invalid_enclosing_mount = true; + } + else if (actual_mount) { + g_object_unref(actual_mount); + } + + // Query the filesystem info for size, free space, and type + error = nullptr; + GFileInfo* info = g_file_query_filesystem_info( + root, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE + "," G_FILE_ATTRIBUTE_FILESYSTEM_FREE "," G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, + nullptr, &error); + if (error) { + qLog(Warning) << error->message; + g_error_free(error); + } + else { + filesystem_size = g_file_info_get_attribute_uint64( + info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE); + filesystem_free = g_file_info_get_attribute_uint64( + info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); + filesystem_type = QString::fromUtf8(g_file_info_get_attribute_string( + info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE)); + g_object_unref(info); + } + + // Query the file's info for a filesystem ID + // Only afc devices (that I know of) give reliably unique IDs + if (filesystem_type == "afc") { + error = nullptr; + info = g_file_query_info(root, G_FILE_ATTRIBUTE_ID_FILESYSTEM, G_FILE_QUERY_INFO_NONE, nullptr, &error); + if (error) { + qLog(Warning) << error->message; + g_error_free(error); + } + else { + mount_uuid = QString::fromUtf8(g_file_info_get_attribute_string(info, G_FILE_ATTRIBUTE_ID_FILESYSTEM)); + g_object_unref(info); + } + } + + g_object_unref(root); + +} + +void GioLister::DeviceInfo::ReadVolumeInfo(GVolume* volume) { + + this->volume.reset_without_add(volume); + if (!volume) return; + + volume_name = ConvertAndFree(g_volume_get_name(volume)); + volume_uuid = ConvertAndFree(g_volume_get_uuid(volume)); + volume_unix_device = ConvertAndFree(g_volume_get_identifier(volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE)); + + GFile* root = g_volume_get_activation_root(volume); + if (root) { + volume_root_uri = g_file_get_uri(root); + g_object_unref(root); + } + +} + +void GioLister::DeviceInfo::ReadDriveInfo(GDrive* drive) { + this->drive.reset_without_add(drive); + if (!drive) return; + + drive_name = ConvertAndFree(g_drive_get_name(drive)); + drive_removable = g_drive_is_media_removable(drive); +} + +QString GioLister::FindUniqueIdByMount(GMount* mount) const { + for (const DeviceInfo& info : devices_) { + if (info.mount == mount) return info.unique_id(); + } + return QString(); +} + +QString GioLister::FindUniqueIdByVolume(GVolume* volume) const { + for (const DeviceInfo& info : devices_) { + if (info.volume == volume) return info.unique_id(); + } + return QString(); +} + +void GioLister::VolumeEjectFinished(GObject* object, GAsyncResult* result, gpointer) { + OperationFinished(std::bind(g_volume_eject_with_operation_finish, _1, _2, _3), object, result); +} + +void GioLister::MountEjectFinished(GObject* object, GAsyncResult* result, gpointer) { + OperationFinished(std::bind(g_mount_eject_with_operation_finish, _1, _2, _3), object, result); +} + +void GioLister::MountUnmountFinished(GObject* object, GAsyncResult* result, gpointer) { + OperationFinished(std::bind(g_mount_unmount_with_operation_finish, _1, _2, _3), object, result); +} + +void GioLister::UnmountDevice(const QString& id) { + + QMutexLocker l(&mutex_); + if (!devices_.contains(id)) return; + + const DeviceInfo& info = devices_[id]; + + if (!info.mount) return; + + if (info.volume) { + if (g_volume_can_eject(info.volume)) { + g_volume_eject_with_operation(info.volume, G_MOUNT_UNMOUNT_NONE, nullptr, nullptr, (GAsyncReadyCallback)VolumeEjectFinished, nullptr); + g_object_unref(info.volume); + return; + } + } + + if (g_mount_can_eject(info.mount)) { + g_mount_eject_with_operation(info.mount, G_MOUNT_UNMOUNT_NONE, nullptr, nullptr, (GAsyncReadyCallback)MountEjectFinished, nullptr); + } + else if (g_mount_can_unmount(info.mount)) { + g_mount_unmount_with_operation(info.mount, G_MOUNT_UNMOUNT_NONE, nullptr, nullptr, (GAsyncReadyCallback)MountUnmountFinished, nullptr); + } + +} + +void GioLister::UpdateDeviceFreeSpace(const QString& id) { + + { + QMutexLocker l(&mutex_); + if (!devices_.contains(id)) return; + + DeviceInfo& device_info = devices_[id]; + + GFile* root = g_mount_get_root(device_info.mount); + + GError* error = nullptr; + GFileInfo* info = g_file_query_filesystem_info(root, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, nullptr, &error); + if (error) { + qLog(Warning) << error->message; + g_error_free(error); + } + else { + device_info.filesystem_free = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); + g_object_unref(info); + } + + g_object_unref(root); + } + + emit DeviceChanged(id); + +} + +bool GioLister::DeviceNeedsMount(const QString& id) { + QMutexLocker l(&mutex_); + return devices_.contains(id) && !devices_[id].mount; +} + +int GioLister::MountDevice(const QString& id) { + const int request_id = next_mount_request_id_++; + metaObject()->invokeMethod(this, "DoMountDevice", Qt::QueuedConnection, Q_ARG(QString, id), Q_ARG(int, request_id)); + return request_id; +} + +void GioLister::DoMountDevice(const QString& id, int request_id) { + + QMutexLocker l(&mutex_); + if (!devices_.contains(id)) { + emit DeviceMounted(id, request_id, false); + return; + } + + const DeviceInfo& info = devices_[id]; + if (info.mount) { + // Already mounted + emit DeviceMounted(id, request_id, true); + return; + } + + g_volume_mount(info.volume, G_MOUNT_MOUNT_NONE, nullptr, nullptr, VolumeMountFinished, nullptr); + emit DeviceMounted(id, request_id, true); + +} + diff --git a/src/device/giolister.h b/src/device/giolister.h new file mode 100644 index 00000000..d5d8ffd2 --- /dev/null +++ b/src/device/giolister.h @@ -0,0 +1,152 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef GIOLISTER_H +#define GIOLISTER_H + +#include "config.h" + +#include "devicelister.h" +#include "core/scopedgobject.h" + +// Work around compile issue with glib >= 2.25 +#ifdef signals +#undef signals +#endif + +#include + +#include +#include + +class GioLister : public DeviceLister { + Q_OBJECT + + public: + GioLister() {} + ~GioLister(); + + int priority() const { return 50; } + + QStringList DeviceUniqueIDs(); + QVariantList DeviceIcons(const QString &id); + QString DeviceManufacturer(const QString &id); + QString DeviceModel(const QString &id); + quint64 DeviceCapacity(const QString &id); + quint64 DeviceFreeSpace(const QString &id); + QVariantMap DeviceHardwareInfo(const QString &id); + bool DeviceNeedsMount(const QString &id); + + QString MakeFriendlyName(const QString &id); + QList MakeDeviceUrls(const QString &id); + + int MountDevice(const QString &id); + void UnmountDevice(const QString &id); + + public slots: + void UpdateDeviceFreeSpace(const QString &id); + + protected: + void Init(); + + private: + struct DeviceInfo { + DeviceInfo() : drive_removable(false), filesystem_size(0),filesystem_free(0), invalid_enclosing_mount(false) {} + + QString unique_id() const; + bool is_suitable() const; + + static QString ConvertAndFree(char *str); + void ReadDriveInfo(GDrive *drive); + void ReadVolumeInfo(GVolume *volume); + void ReadMountInfo(GMount *mount); + + // Only available if it's a physical drive + ScopedGObject volume; + QString volume_name; + QString volume_unix_device; + QString volume_root_uri; + QString volume_uuid; + + // Only available if it's a physical drive + ScopedGObject drive; + QString drive_name; + bool drive_removable; + + // Only available if it's mounted + ScopedGObject mount; + QString mount_path; + QString mount_uri; + QString mount_name; + QStringList mount_icon_names; + QString mount_uuid; + quint64 filesystem_size; + quint64 filesystem_free; + QString filesystem_type; + + bool invalid_enclosing_mount; + }; + + void VolumeAdded(GVolume *volume); + void VolumeRemoved(GVolume *volume); + + void MountAdded(GMount *mount); + void MountChanged(GMount *mount); + void MountRemoved(GMount *mount); + + static void VolumeAddedCallback(GVolumeMonitor*, GVolume*, gpointer); + static void VolumeRemovedCallback(GVolumeMonitor*, GVolume*, gpointer); + + static void MountAddedCallback(GVolumeMonitor*, GMount*, gpointer); + static void MountChangedCallback(GVolumeMonitor*, GMount*, gpointer); + static void MountRemovedCallback(GVolumeMonitor*, GMount*, gpointer); + + static void VolumeMountFinished(GObject *object, GAsyncResult *result, gpointer); + static void VolumeEjectFinished(GObject *object, GAsyncResult *result, gpointer); + static void MountEjectFinished(GObject *object, GAsyncResult *result, gpointer); + static void MountUnmountFinished(GObject *object, GAsyncResult *result, gpointer); + + // You MUST hold the mutex while calling this function + QString FindUniqueIdByMount(GMount *mount) const; + QString FindUniqueIdByVolume(GVolume *volume) const; + + template + T LockAndGetDeviceInfo(const QString &id, T DeviceInfo::*field); + + private slots: + void DoMountDevice(const QString &id, int request_id); + + private: + ScopedGObject monitor_; + QList signals_; + + QMutex mutex_; + QMap devices_; +}; + +template +T GioLister::LockAndGetDeviceInfo(const QString &id, T DeviceInfo::*field) { + QMutexLocker l(&mutex_); + if (!devices_.contains(id)) return T(); + + return devices_[id].*field; +} + +#endif // GIOLISTER_H diff --git a/src/device/gpoddevice.cpp b/src/device/gpoddevice.cpp new file mode 100644 index 00000000..814d3af1 --- /dev/null +++ b/src/device/gpoddevice.cpp @@ -0,0 +1,245 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include +#include +#include +#include + +#include "devicemanager.h" +#include "gpoddevice.h" +#include "gpodloader.h" +#include "core/logging.h" +#include "core/application.h" +#include "collection/collectionbackend.h" +#include "collection/collectionmodel.h" + +GPodDevice::GPodDevice(const QUrl &url, DeviceLister *lister, const QString &unique_id, DeviceManager *manager, Application *app, int database_id, bool first_time) + : ConnectedDevice(url, lister, unique_id, manager, app, database_id, first_time), + loader_thread_(new QThread(this)), + loader_(nullptr), + db_(nullptr) {} + +void GPodDevice::Init() { + + InitBackendDirectory(url_.path(), first_time_); + model_->Init(); + + loader_ = new GPodLoader(url_.path(), app_->task_manager(), backend_, shared_from_this()); + loader_->moveToThread(loader_thread_); + + connect(loader_, SIGNAL(Error(QString)), SIGNAL(Error(QString))); + connect(loader_, SIGNAL(TaskStarted(int)), SIGNAL(TaskStarted(int))); + connect(loader_, SIGNAL(LoadFinished(Itdb_iTunesDB*)), SLOT(LoadFinished(Itdb_iTunesDB*))); + connect(loader_thread_, SIGNAL(started()), loader_, SLOT(LoadDatabase())); + loader_thread_->start(); + +} + +GPodDevice::~GPodDevice() {} + +void GPodDevice::LoadFinished(Itdb_iTunesDB *db) { + + QMutexLocker l(&db_mutex_); + db_ = db; + db_wait_cond_.wakeAll(); + + loader_->deleteLater(); + loader_ = nullptr; + +} + +bool GPodDevice::StartCopy(QList *supported_filetypes) { + + { + // Wait for the database to be loaded + QMutexLocker l(&db_mutex_); + if (!db_) db_wait_cond_.wait(&db_mutex_); + } + + // Ensure only one "organise files" can be active at any one time + db_busy_.lock(); + + if (supported_filetypes) GetSupportedFiletypes(supported_filetypes); + return true; + +} + +Itdb_Track *GPodDevice::AddTrackToITunesDb(const Song &metadata) { + + // Create the track + Itdb_Track *track = itdb_track_new(); + metadata.ToItdb(track); + + // Add it to the DB and the master playlist + // The DB takes ownership of the track + itdb_track_add(db_, track, -1); + Itdb_Playlist *mpl = itdb_playlist_mpl(db_); + itdb_playlist_add_track(mpl, track, -1); + + return track; + +} + +void GPodDevice::AddTrackToModel(Itdb_Track *track, const QString &prefix) { + + // Add it to our CollectionModel + Song metadata_on_device; + metadata_on_device.InitFromItdb(track, prefix); + metadata_on_device.set_directory_id(1); + songs_to_add_ << metadata_on_device; + +} + +bool GPodDevice::CopyToStorage(const CopyJob &job) { + + Q_ASSERT(db_); + + Itdb_Track *track = AddTrackToITunesDb(job.metadata_); + + // Copy the file + GError *error = nullptr; + itdb_cp_track_to_ipod(track, QDir::toNativeSeparators(job.source_).toLocal8Bit().constData(), &error); + if (error) { + qLog(Error) << "copying failed:" << error->message; + app_->AddError(QString::fromUtf8(error->message)); + g_error_free(error); + + // Need to remove the track from the db again + itdb_track_remove(track); + return false; + } + + AddTrackToModel(track, url_.path()); + + // Remove the original if it was requested + if (job.remove_original_) { + QFile::remove(job.source_); + } + + return true; + +} + +void GPodDevice::WriteDatabase(bool success) { + + if (success) { + // Write the itunes database + GError *error = nullptr; + itdb_write(db_, &error); + if (error) { + qLog(Error) << "writing database failed:" << error->message; + app_->AddError(QString::fromUtf8(error->message)); + g_error_free(error); + } + else { + FinaliseDatabase(); + + // Update the collection model + if (!songs_to_add_.isEmpty()) backend_->AddOrUpdateSongs(songs_to_add_); + if (!songs_to_remove_.isEmpty()) backend_->DeleteSongs(songs_to_remove_); + } + } + + songs_to_add_.clear(); + songs_to_remove_.clear(); + db_busy_.unlock(); + +} + +void GPodDevice::FinishCopy(bool success) { + WriteDatabase(success); + ConnectedDevice::FinishCopy(success); +} + +void GPodDevice::StartDelete() { StartCopy(nullptr); } + +bool GPodDevice::RemoveTrackFromITunesDb(const QString &path, const QString &relative_to) { + + QString ipod_filename = path; + if (!relative_to.isEmpty() && path.startsWith(relative_to)) + ipod_filename.remove(0, relative_to.length() + (relative_to.endsWith('/') ? -1 : 0)); + + ipod_filename.replace('/', ':'); + + // Find the track in the itdb, identify it by its filename + Itdb_Track *track = nullptr; + for (GList *tracks = db_->tracks; tracks != nullptr; tracks = tracks->next) { + Itdb_Track *t = static_cast(tracks->data); + + if (t->ipod_path == ipod_filename) { + track = t; + break; + } + } + + if (track == nullptr) { + qLog(Warning) << "Couldn't find song" << path << "in iTunesDB"; + return false; + } + + // Remove the track from all playlists + for (GList *playlists = db_->playlists ; playlists != nullptr ; playlists = playlists->next) { + Itdb_Playlist *playlist = static_cast(playlists->data); + + if (itdb_playlist_contains_track(playlist, track)) { + itdb_playlist_remove_track(playlist, track); + } + } + + // Remove the track from the database, this frees the struct too + itdb_track_remove(track); + + return true; + +} + +bool GPodDevice::DeleteFromStorage(const DeleteJob &job) { + + Q_ASSERT(db_); + + if (!RemoveTrackFromITunesDb(job.metadata_.url().toLocalFile(), url_.path())) + return false; + + // Remove the file + if (!QFile::remove(job.metadata_.url().toLocalFile())) return false; + + // Remove it from our collection model + songs_to_remove_ << job.metadata_; + + return true; + +} + +void GPodDevice::FinishDelete(bool success) { + WriteDatabase(success); + ConnectedDevice::FinishDelete(success); +} + +bool GPodDevice::GetSupportedFiletypes(QList *ret) { + *ret << Song::Type_Mp4; + *ret << Song::Type_Mpeg; + return true; +} + diff --git a/src/device/gpoddevice.h b/src/device/gpoddevice.h new file mode 100644 index 00000000..d6bc81c1 --- /dev/null +++ b/src/device/gpoddevice.h @@ -0,0 +1,87 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef GPODDEVICE_H +#define GPODDEVICE_H + +#include "config.h" + +#include "connecteddevice.h" +#include "core/musicstorage.h" + +#include +#include + +#include + +class GPodLoader; + +class GPodDevice : public ConnectedDevice, public virtual MusicStorage { + Q_OBJECT + +public: + Q_INVOKABLE GPodDevice( + const QUrl &url, DeviceLister *lister, + const QString &unique_id, DeviceManager *manager, + Application *app, + int database_id, bool first_time); + ~GPodDevice(); + + void Init(); + + static QStringList url_schemes() { return QStringList() << "ipod"; } + + bool GetSupportedFiletypes(QList *ret); + + bool StartCopy(QList *supported_types); + bool CopyToStorage(const CopyJob &job); + void FinishCopy(bool success); + + void StartDelete(); + bool DeleteFromStorage(const DeleteJob &job); + void FinishDelete(bool success); + +protected slots: + void LoadFinished(Itdb_iTunesDB *db); + +protected: + Itdb_Track *AddTrackToITunesDb(const Song &metadata); + void AddTrackToModel(Itdb_Track *track, const QString &prefix); + bool RemoveTrackFromITunesDb(const QString &path, const QString &relative_to = QString()); + virtual void FinaliseDatabase() {} + +private: + void WriteDatabase(bool success); + +protected: + QThread *loader_thread_; + GPodLoader *loader_; + + QWaitCondition db_wait_cond_; + QMutex db_mutex_; + Itdb_iTunesDB *db_; + + QMutex db_busy_; + SongList songs_to_add_; + SongList songs_to_remove_; +}; + +#endif // GPODDEVICE_H + diff --git a/src/device/gpodloader.cpp b/src/device/gpodloader.cpp new file mode 100644 index 00000000..c1dbacfb --- /dev/null +++ b/src/device/gpodloader.cpp @@ -0,0 +1,97 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include +#include + +#include "connecteddevice.h" +#include "gpodloader.h" +#include "core/logging.h" +#include "core/song.h" +#include "core/taskmanager.h" +#include "collection/collectionbackend.h" + +GPodLoader::GPodLoader(const QString &mount_point, TaskManager *task_manager, CollectionBackend *backend, std::shared_ptr device) + : QObject(nullptr), + device_(device), + mount_point_(mount_point), + type_(Song::Type_Unknown), + task_manager_(task_manager), + backend_(backend) { + original_thread_ = thread(); +} + +GPodLoader::~GPodLoader() {} + +void GPodLoader::LoadDatabase() { + + int task_id = task_manager_->StartTask(tr("Loading iPod database")); + emit TaskStarted(task_id); + + // Load the iTunes database + GError *error = nullptr; + Itdb_iTunesDB *db = itdb_parse(QDir::toNativeSeparators(mount_point_).toLocal8Bit(), &error); + + // Check for errors + if (!db) { + if (error) { + qLog(Error) << "loading database failed:" << error->message; + emit Error(QString::fromUtf8(error->message)); + g_error_free(error); + } else { + emit Error(tr("An error occurred loading the iTunes database")); + } + + task_manager_->SetTaskFinished(task_id); + return; + } + + // Convert all the tracks from libgpod structs into Song classes + const QString prefix = path_prefix_.isEmpty() ? QDir::fromNativeSeparators(mount_point_) : path_prefix_; + + SongList songs; + for (GList *tracks = db->tracks; tracks != nullptr; tracks = tracks->next) { + Itdb_Track *track = static_cast(tracks->data); + + Song song; + song.InitFromItdb(track, prefix); + song.set_directory_id(1); + + if (type_ != Song::Type_Unknown) song.set_filetype(type_); + songs << song; + } + + // Need to remove all the existing songs in the database first + backend_->DeleteSongs(backend_->FindSongsInDirectory(1)); + + // Add the songs we've just loaded + backend_->AddOrUpdateSongs(songs); + + moveToThread(original_thread_); + + task_manager_->SetTaskFinished(task_id); + emit LoadFinished(db); + +} + diff --git a/src/device/gpodloader.h b/src/device/gpodloader.h new file mode 100644 index 00000000..66f2b1f0 --- /dev/null +++ b/src/device/gpodloader.h @@ -0,0 +1,68 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef GPODLOADER_H +#define GPODLOADER_H + +#include "config.h" + +#include + +#include + +#include + +#include "core/song.h" + +class ConnectedDevice; +class CollectionBackend; +class TaskManager; + +class GPodLoader : public QObject { + Q_OBJECT + + public: + GPodLoader(const QString &mount_point, TaskManager *task_manager, CollectionBackend *backend, std::shared_ptr device); + ~GPodLoader(); + + void set_music_path_prefix(const QString &prefix) { path_prefix_ = prefix; } + void set_song_type(Song::FileType type) { type_ = type; } + + public slots: + void LoadDatabase(); + +signals: + void Error(const QString &message); + void TaskStarted(int task_id); + void LoadFinished(Itdb_iTunesDB *db); + + private: + std::shared_ptr device_; + QThread *original_thread_; + + QString mount_point_; + QString path_prefix_; + Song::FileType type_; + TaskManager *task_manager_; + CollectionBackend *backend_; +}; + +#endif // GPODLOADER_H + diff --git a/src/device/ilister.cpp b/src/device/ilister.cpp new file mode 100644 index 00000000..b54ce73d --- /dev/null +++ b/src/device/ilister.cpp @@ -0,0 +1,229 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include + +#include "config.h" +#include "ilister.h" +#include "imobiledeviceconnection.h" + +iLister::iLister() { +} + +iLister::~iLister() { +} + +void iLister::Init() { + idevice_event_subscribe(&EventCallback, reinterpret_cast(this)); +} + +void iLister::EventCallback(const idevice_event_t *event, void *context) { + iLister *me = reinterpret_cast(context); + +#ifdef IMOBILEDEVICE_USES_UDIDS + const char *uuid = event->udid; +#else + const char *uuid = event->uuid; +#endif + + switch (event->event) { + case IDEVICE_DEVICE_ADD: + me->DeviceAddedCallback(uuid); + break; + + case IDEVICE_DEVICE_REMOVE: + me->DeviceRemovedCallback(uuid); + break; + } +} + + +void iLister::DeviceAddedCallback(const char *uuid) { + + DeviceInfo info = ReadDeviceInfo(uuid); + QString id = UniqueId(uuid); + + QString name = MakeFriendlyName(id); + if (info.product_type == "iPhone 3,1" || info.product_type.startsWith("iPad")) { + // iPhone 4 and iPad unsupported by libgpod as of 0.7.94. + return; + } + + { + QMutexLocker l(&mutex_); + devices_[id] = info; + } + + emit DeviceAdded(id); + +} + +void iLister::DeviceRemovedCallback(const char *uuid) { + + QString id = UniqueId(uuid); + { + QMutexLocker l(&mutex_); + if (!devices_.contains(id)) + return; + + devices_.remove(id); + } + + emit DeviceRemoved(id); + +} + +QString iLister::UniqueId(const char *uuid) { + return "ithing/" + QString::fromUtf8(uuid); +} + +QStringList iLister::DeviceUniqueIDs() { + return devices_.keys(); +} + +QVariantList iLister::DeviceIcons(const QString &id) { + return QVariantList() << "ipodtouchicon"; +} + +QString iLister::DeviceManufacturer(const QString &id) { + return "Apple"; +} + +QString iLister::DeviceModel(const QString &id) { + return LockAndGetDeviceInfo(id, &DeviceInfo::product_type); +} + +quint64 iLister::DeviceCapacity(const QString &id) { + return LockAndGetDeviceInfo(id, &DeviceInfo::total_bytes); +} + +quint64 iLister::DeviceFreeSpace(const QString &id) { + return LockAndGetDeviceInfo(id, &DeviceInfo::free_bytes); +} + +QVariantMap iLister::DeviceHardwareInfo(const QString &id) { + + QVariantMap ret; + ret[tr("Color")] = LockAndGetDeviceInfo(id, &DeviceInfo::colour); + ret["IMEI"] = LockAndGetDeviceInfo(id, &DeviceInfo::imei); + ret[tr("Password Protected")] = LockAndGetDeviceInfo(id, &DeviceInfo::password_protected); + ret[tr("Timezone")] = LockAndGetDeviceInfo(id, &DeviceInfo::timezone); + ret[tr("WiFi MAC Address")] = LockAndGetDeviceInfo(id, &DeviceInfo::wifi_mac); + ret[tr("Bluetooth MAC Address")] = LockAndGetDeviceInfo(id, &DeviceInfo::bt_mac); + + return ret; + +} + +QString iLister::MakeFriendlyName(const QString &id) { + + QString name = LockAndGetDeviceInfo(id, &DeviceInfo::name); + if (!name.isEmpty()) { + return name; + } + + QString model_id = LockAndGetDeviceInfo(id, &DeviceInfo::product_type); + + if (model_id.startsWith("iPhone")) { + QString version = model_id.right(3); + QChar major = version[0]; + QChar minor = version[2]; + if (major == '1' && minor == '1') { + return "iPhone"; + } + if (major == '1' && minor == '2') { + return "iPhone 3G"; + } + if (major == '2' && minor == '1') { + return "iPhone 3GS"; + } + if (major == '3' && minor == '1') { + return "iPhone 4"; + } + } + else if (model_id.startsWith("iPod")) { + return "iPod Touch"; + } + else if (model_id.startsWith("iPad")) { + return "iPad"; + } + return model_id; + +} + +QList iLister::MakeDeviceUrls(const QString &id) { + + QList ret; + + QString uuid = LockAndGetDeviceInfo(id, &DeviceInfo::uuid); + if (uuid.isEmpty()) + return ret; + + ret << QUrl("afc://" + uuid + "/"); + + return ret; + +} + +void iLister::UnmountDevice(const QString &id) { } + +iLister::DeviceInfo iLister::ReadDeviceInfo(const char *uuid) { + + DeviceInfo ret; + + iMobileDeviceConnection conn(uuid); + ret.uuid = uuid; + ret.product_type = conn.GetProperty("ProductType").toString(); + ret.free_bytes = conn.GetProperty("AmountDataAvailable", "com.apple.disk_usage").toULongLong(); + ret.total_bytes = conn.GetProperty("TotalDataCapacity", "com.apple.disk_usage").toULongLong(); + ret.name = conn.GetProperty("DeviceName").toString(); + + ret.colour = conn.GetProperty("DeviceColor").toString(); + ret.imei = conn.GetProperty("InternationalMobileEquipmentIdentity").toString(); + ret.hardware = conn.GetProperty("HardwareModel").toString(); + ret.password_protected = conn.GetProperty("PasswordProtected").toBool(); + ret.os_version = conn.GetProperty("ProductVersion").toString(); + ret.timezone = conn.GetProperty("TimeZone").toString(); + ret.wifi_mac = conn.GetProperty("WiFiAddress").toString(); + ret.bt_mac = conn.GetProperty("BluetoothAddress").toString(); + + return ret; + +} + +void iLister::UpdateDeviceFreeSpace(const QString &id) { + + { + QMutexLocker l(&mutex_); + if (!devices_.contains(id)) + return; + + DeviceInfo &info = devices_[id]; + iMobileDeviceConnection conn(info.uuid); + + info.free_bytes = conn.GetProperty("AmountDataAvailable", "com.apple.disk_usage").toULongLong(); + } + + emit DeviceChanged(id); + +} diff --git a/src/device/ilister.h b/src/device/ilister.h new file mode 100644 index 00000000..f52a6187 --- /dev/null +++ b/src/device/ilister.h @@ -0,0 +1,102 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ILISTER_H +#define ILISTER_H + +#include "config.h" + +#include + +#include + +#include "devicelister.h" + +class iLister : public DeviceLister { + Q_OBJECT + public: + iLister(); + ~iLister(); + + int priority() const { return 120; } + + virtual QStringList DeviceUniqueIDs(); + virtual QVariantList DeviceIcons(const QString &id); + virtual QString DeviceManufacturer(const QString &id); + virtual QString DeviceModel(const QString &id); + virtual quint64 DeviceCapacity(const QString &id); + virtual quint64 DeviceFreeSpace(const QString &id); + virtual QVariantMap DeviceHardwareInfo(const QString &id); + virtual QString MakeFriendlyName(const QString &id); + virtual QList MakeDeviceUrls(const QString &id); + virtual void UnmountDevice(const QString &id); + + public slots: + virtual void UpdateDeviceFreeSpace(const QString &id); + + private: + struct DeviceInfo { + DeviceInfo() : free_bytes(0), total_bytes(0) {} + + QString uuid; + QString product_type; + quint64 free_bytes; + quint64 total_bytes; + QString name; // Name given to the iDevice by the user. + + // Extra information. + QString colour; + QString imei; + QString hardware; + bool password_protected; + QString os_version; + QString timezone; + QString wifi_mac; + QString bt_mac; + }; + + virtual void Init(); + + static void EventCallback(const idevice_event_t *event, void *context); + + void DeviceAddedCallback(const char *uuid); + void DeviceRemovedCallback(const char *uuid); + + DeviceInfo ReadDeviceInfo(const char *uuid); + static QString UniqueId(const char *uuid); + + template + T LockAndGetDeviceInfo(const QString &id, T DeviceInfo::*field); + +private: + QMutex mutex_; + QMap devices_; +}; + +template +T iLister::LockAndGetDeviceInfo(const QString &id, T DeviceInfo::*field) { + QMutexLocker l(&mutex_); + if (!devices_.contains(id)) + return T(); + + return devices_[id].*field; +} + +#endif diff --git a/src/device/imobiledeviceconnection.cpp b/src/device/imobiledeviceconnection.cpp new file mode 100644 index 00000000..09d8c3ee --- /dev/null +++ b/src/device/imobiledeviceconnection.cpp @@ -0,0 +1,248 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include "imobiledeviceconnection.h" +#include "core/logging.h" + +iMobileDeviceConnection::iMobileDeviceConnection(const QString &uuid) : device_(NULL), afc_(NULL), afc_port_(0) { + + idevice_error_t err = idevice_new(&device_, uuid.toUtf8().constData()); + if (err != IDEVICE_E_SUCCESS) { + qLog(Warning) << "idevice error:" << err; + return; + } + + lockdownd_client_t lockdown; + + QByteArray label_ascii = QCoreApplication::applicationName().toLatin1(); + const char *label = label_ascii.constData(); + lockdownd_error_t lockdown_err = lockdownd_client_new_with_handshake(device_, &lockdown, label); + if (lockdown_err != LOCKDOWN_E_SUCCESS) { + qLog(Warning) << "lockdown error:" << lockdown_err; + return; + } + + lockdown_err = lockdownd_start_service(lockdown, "com.apple.afc", &afc_port_); + if (lockdown_err != LOCKDOWN_E_SUCCESS) { + qLog(Warning) << "lockdown error:" << lockdown_err; + lockdownd_client_free(lockdown); + return; + } + + afc_error_t afc_err = afc_client_new(device_, afc_port_, &afc_); + if (afc_err != 0) { + qLog(Warning) << "afc error:" << afc_err; + lockdownd_client_free(lockdown); + return; + } + + lockdownd_client_free(lockdown); + +} + +iMobileDeviceConnection::~iMobileDeviceConnection() { + + if (afc_) { + afc_client_free(afc_); + } + if (device_) { + idevice_free(device_); + } + +} + +template +T GetPListValue(plist_t node, F f) { + T ret; + f(node, &ret); + return ret; +} + +QVariant iMobileDeviceConnection::GetProperty(const QString &property, const QString &domain) { + + lockdownd_client_t lockdown; + QByteArray label_ascii = QCoreApplication::applicationName().toLatin1(); + const char *label = label_ascii.constData(); + + lockdownd_error_t lockdown_err = lockdownd_client_new_with_handshake(device_, &lockdown, label); + if (lockdown_err != LOCKDOWN_E_SUCCESS) { + qLog(Warning) << "lockdown error:" << lockdown_err; + return QVariant(); + } + + plist_t node = NULL; + QByteArray domain_ascii = domain.toLatin1(); + const char *d = domain_ascii.isEmpty() ? NULL : domain_ascii.constData(); + //const char *d = domain.isEmpty() ? NULL : "com.apple.disk_usage"; + lockdownd_get_value(lockdown, d, property.toLatin1().constData(), &node); + lockdownd_client_free(lockdown); + + if (!node) { + qLog(Warning) << "get_value failed" << property << domain; + return QVariant(); + } + + switch (plist_get_node_type(node)) { + case PLIST_BOOLEAN: + return bool(GetPListValue(node, plist_get_bool_val)); + + case PLIST_UINT: + return QVariant::fromValue(GetPListValue(node, plist_get_uint_val)); + + case PLIST_STRING: { + char *data = GetPListValue(node, plist_get_string_val); + QString ret = QString::fromUtf8(data); + free(data); + return ret; + } + + default: + qLog(Warning) << "Unhandled PList type"; + return QVariant(); + } + +} + +QStringList iMobileDeviceConnection::ReadDirectory(const QString &path, QDir::Filters filters) { + + char **list = NULL; + afc_error_t err = afc_read_directory(afc_, path.toUtf8().constData(), &list); + if (err != AFC_E_SUCCESS || !list) { + return QStringList(); + } + + QStringList ret; + for (char **p = list ; *p != NULL ; ++p) { + QString filename = QString::fromUtf8(*p); + free(*p); + + if (filters == QDir::NoFilter) + ret << filename; + else { + if (filters & QDir::NoDotAndDotDot && (filename == "." || filename == "..")) + continue; + if (!(filters & QDir::Hidden) && filename.startsWith(".")) + continue; + + QString filetype = GetFileInfo(path + "/" + filename, "st_ifmt"); + if ((filetype == "S_IFREG" && (filters & QDir::Files)) || (filetype == "S_IFDIR" && (filters & QDir::Dirs)) || (filetype == "S_IFLNK" && (!(filters & QDir::NoSymLinks)))) + ret << filename; + } + } + free(list); + + return ret; + +} + +bool iMobileDeviceConnection::MkDir(const QString &path) { + afc_error_t err = afc_make_directory(afc_, path.toUtf8().constData()); + return err == AFC_E_SUCCESS; +} + +QString iMobileDeviceConnection::GetFileInfo(const QString &path, const QString &key) { + + QString ret; + char **infolist = NULL; + afc_error_t err = afc_get_file_info(afc_, path.toUtf8().constData(), &infolist); + if (err != AFC_E_SUCCESS || !infolist) { + return ret; + } + + QString last_key; + for (char **p = infolist ; *p != NULL ; ++p) { + if (last_key.isNull()) { + last_key = QString::fromUtf8(*p); + } + else { + if (last_key == key) + ret = QString::fromUtf8(*p); + last_key = QString(); + } + free(*p); + } + free(infolist); + return ret; + +} + +bool iMobileDeviceConnection::Exists(const QString &path) { + return !GetFileInfo(path, "st_ifmt").isNull(); +} + +QString iMobileDeviceConnection::GetUnusedFilename(Itdb_iTunesDB *itdb, const Song &metadata) { + + // This function does the same as itdb_cp_get_dest_filename, except it + // accesses the device's filesystem through imobiledevice. + + // Get the total number of F.. directories + int total_musicdirs = 0; + for ( ; ; ++total_musicdirs) { + QString dir; + dir.sprintf("/iTunes_Control/Music/F%02d", total_musicdirs); + + if (!Exists(dir)) + break; + } + + if (total_musicdirs <= 0) { + qLog(Warning) << "No 'F..'' directories found on iPod"; + return QString(); + } + + // Pick one at random + const int dir_num = qrand() % total_musicdirs; + QString dir; + dir.sprintf("/iTunes_Control/Music/F%02d", dir_num); + + if (!Exists(dir)) { + qLog(Warning) << "Music directory doesn't exist:" << dir; + return QString(); + } + + // Use the same file extension as the original file, default to mp3. + QString extension = metadata.url().path().section('.', -1, -1).toLower(); + if (extension.isEmpty()) + extension = "mp3"; + + // Loop until we find an unused filename. + // Use the same naming convention as libgpod, which is + // "libgpod" + 6-digit random number + static const int kRandMax = 999999; + QString filename; + forever { + filename.sprintf("libgpod%06d", qrand() % kRandMax); + filename += "." + extension; + + if (!Exists(dir + "/" + filename)) + break; + } + + return dir + "/" + filename; + +} diff --git a/src/device/imobiledeviceconnection.h b/src/device/imobiledeviceconnection.h new file mode 100644 index 00000000..4e292c80 --- /dev/null +++ b/src/device/imobiledeviceconnection.h @@ -0,0 +1,63 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef IMOBILEDEVICECONNECTION_H +#define IMOBILEDEVICECONNECTION_H + +#include "config.h" + +#include +#include +#include + +#include + +#include +#include +#include + +#include "core/song.h" + +class iMobileDeviceConnection { +public: + iMobileDeviceConnection(const QString &uuid); + ~iMobileDeviceConnection(); + + afc_client_t afc() { return afc_; } + + QVariant GetProperty(const QString &property, const QString &domain = QString()); + QStringList ReadDirectory(const QString &path, QDir::Filters filters = QDir::NoFilter); + bool MkDir(const QString &path); + + QString GetFileInfo(const QString &path, const QString &key); + bool Exists(const QString &path); + + QString GetUnusedFilename(Itdb_iTunesDB *itdb, const Song &metadata); + +private: + Q_DISABLE_COPY(iMobileDeviceConnection); + + idevice_t device_; + afc_client_t afc_; + + uint16_t afc_port_; +}; + +#endif // IMOBILEDEVICECONNECTION_H diff --git a/src/device/macdevicelister.h b/src/device/macdevicelister.h new file mode 100644 index 00000000..a88c2096 --- /dev/null +++ b/src/device/macdevicelister.h @@ -0,0 +1,89 @@ +#ifndef MACDEVICELISTER_H +#define MACDEVICELISTER_H + +#include "config.h" + +#include +#include +#include + +#include +#include +#include + +#include "devicelister.h" + +class MacDeviceLister : public DeviceLister { + Q_OBJECT + public: + MacDeviceLister(); + ~MacDeviceLister(); + + virtual QStringList DeviceUniqueIDs(); + virtual QVariantList DeviceIcons(const QString &id); + virtual QString DeviceManufacturer(const QString &id); + virtual QString DeviceModel(const QString &id); + virtual quint64 DeviceCapacity(const QString &id); + virtual quint64 DeviceFreeSpace(const QString &id); + virtual QVariantMap DeviceHardwareInfo(const QString &id); + virtual bool AskForScan(const QString &serial) const; + virtual QString MakeFriendlyName(const QString &id); + virtual QList MakeDeviceUrls(const QString &id); + + virtual void UnmountDevice(const QString &id); + virtual void UpdateDeviceFreeSpace(const QString &id); + + struct MTPDevice { + MTPDevice() : capacity(0), free_space(0) {} + QString vendor; + QString product; + quint16 vendor_id; + quint16 product_id; + + int quirks; + int bus; + int address; + + quint64 capacity; + quint64 free_space; + }; + + public slots: + virtual void ShutDown(); + + private: + virtual void Init(); + + static void DiskAddedCallback(DADiskRef disk, void* context); + static void DiskRemovedCallback(DADiskRef disk, void* context); + static void USBDeviceAddedCallback(void* refcon, io_iterator_t it); + static void USBDeviceRemovedCallback(void* refcon, io_iterator_t it); + + static void DiskUnmountCallback(DADiskRef disk, DADissenterRef dissenter, void* context); + + void FoundMTPDevice(const MTPDevice& device, const QString& serial); + void RemovedMTPDevice(const QString& serial); + + quint64 GetFreeSpace(const QUrl& url); + quint64 GetCapacity(const QUrl& url); + + bool IsCDDevice(const QString& serial) const; + + DASessionRef loop_session_; + CFRunLoopRef run_loop_; + + QMap current_devices_; + QMap mtp_devices_; + QSet cd_devices_; + + QMutex libmtp_mutex_; + + static QSet sMTPDeviceList; +}; + +uint qHash(const MacDeviceLister::MTPDevice& device); +inline bool operator==(const MacDeviceLister::MTPDevice& a, const MacDeviceLister::MTPDevice& b) { + return (a.vendor_id == b.vendor_id) && (a.product_id == b.product_id); +} + +#endif diff --git a/src/device/macdevicelister.mm b/src/device/macdevicelister.mm new file mode 100644 index 00000000..a320500a --- /dev/null +++ b/src/device/macdevicelister.mm @@ -0,0 +1,823 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "config.h" +#include "macdevicelister.h" +#include "mtpconnection.h" +#include "core/logging.h" +#include "core/scoped_cftyperef.h" +#include "core/scoped_nsautorelease_pool.h" +#include "core/scoped_nsobject.h" + +#import +#import +#import +#import +#import +#import + +#ifndef kUSBSerialNumberString +#define kUSBSerialNumberString "USB Serial Number" +#endif + +#ifndef kUSBVendorString +#define kUSBVendorString "USB Vendor Name" +#endif + +#ifndef kUSBProductString +#define kUSBProductString "USB Product Name" +#endif + +// io_object_t, io_service_t, io_iterator_t etc. are all typedef'd to unsigned int, +// hence the lack of templating here. +class ScopedIOObject { + public: + explicit ScopedIOObject(io_object_t object = 0) : object_(object) {} + + ~ScopedIOObject() { + if (object_) IOObjectRelease(object_); + } + + io_object_t get() const { return object_; } + + private: + io_object_t object_; + + Q_DISABLE_COPY(ScopedIOObject); +}; + +// Helpful MTP & USB links: +// Apple USB device interface guide: +// http://developer.apple.com/mac/collection/documentation/DeviceDrivers/Conceptual/USBBook/USBDeviceInterfaces/USBDevInterfaces.html +// Example Apple code for requesting a USB device descriptor: +// http://www.opensource.apple.com/source/IOUSBFamily/IOUSBFamily-208.4.5/USBProber/BusProbeClass.m +// Libmtp's detection code: +// http://libmtp.cvs.sourceforge.net/viewvc/libmtp/libmtp/src/libusb-glue.c?view=markup +// Libusb's Mac code: +// http://www.libusb.org/browser/libusb/libusb/os/darwin_usb.c +// Microsoft OS Descriptors: +// http://www.microsoft.com/whdc/connect/usb/os_desc.mspx +// Symbian docs for implementing the device side: +// http://developer.symbian.org/main/documentation/reference/s3/pdk/GUID-3FF0F248-EDF0-5348-BC43-869CE1B5B415.html +// Libgphoto2 MTP detection code: +// http://www.sfr-fresh.com/unix/privat/libgphoto2-2.4.10.1.tar.gz:a/libgphoto2-2.4.10.1/libgphoto2_port/usb/check-mtp-device.c + +QSet MacDeviceLister::sMTPDeviceList; + +uint qHash(const MacDeviceLister::MTPDevice& d) { + return qHash(d.vendor_id) ^ qHash(d.product_id); +} + +MacDeviceLister::MacDeviceLister() {} + +MacDeviceLister::~MacDeviceLister() { CFRelease(loop_session_); } + +void MacDeviceLister::Init() { + ScopedNSAutoreleasePool pool; + + // Populate MTP Device list. + if (sMTPDeviceList.empty()) { + LIBMTP_device_entry_t* devices = nullptr; + int num = 0; + if (LIBMTP_Get_Supported_Devices_List(&devices, &num) != 0) { + qLog(Warning) << "Failed to get MTP device list"; + } + else { + for (int i = 0; i < num; ++i) { + LIBMTP_device_entry_t device = devices[i]; + MTPDevice d; + d.vendor = QString::fromAscii(device.vendor); + d.vendor_id = device.vendor_id; + d.product = QString::fromAscii(device.product); + d.product_id = device.product_id; + d.quirks = device.device_flags; + sMTPDeviceList << d; + } + } + + MTPDevice d; + d.vendor = "SanDisk"; + d.vendor_id = 0x781; + d.product = "Sansa Clip+"; + d.product_id = 0x74d0; + + d.quirks = 0x2 | 0x4 | 0x40 | 0x4000; + sMTPDeviceList << d; + } + + run_loop_ = CFRunLoopGetCurrent(); + + // Register for disk mounts/unmounts. + loop_session_ = DASessionCreate(kCFAllocatorDefault); + DARegisterDiskAppearedCallback(loop_session_, kDADiskDescriptionMatchVolumeMountable, &DiskAddedCallback, reinterpret_cast(this)); + DARegisterDiskDisappearedCallback(loop_session_, nullptr, &DiskRemovedCallback, reinterpret_cast(this)); + DASessionScheduleWithRunLoop(loop_session_, run_loop_, kCFRunLoopDefaultMode); + + // Register for USB device connection/disconnection. + IONotificationPortRef notification_port = IONotificationPortCreate(kIOMasterPortDefault); + CFMutableDictionaryRef matching_dict = IOServiceMatching(kIOUSBDeviceClassName); + // IOServiceAddMatchingNotification decreases reference count. + CFRetain(matching_dict); + io_iterator_t it; + kern_return_t err = IOServiceAddMatchingNotification( + notification_port, + kIOFirstMatchNotification, + matching_dict, + &USBDeviceAddedCallback, + reinterpret_cast(this), + &it); + if (err == KERN_SUCCESS) { + USBDeviceAddedCallback(this, it); + } else { + qLog(Warning) << "Could not add notification on USB device connection"; + } + + err = IOServiceAddMatchingNotification( + notification_port, + kIOTerminatedNotification, + matching_dict, + &USBDeviceRemovedCallback, + reinterpret_cast(this), + &it); + if (err == KERN_SUCCESS) { + USBDeviceRemovedCallback(this, it); + } else { + qLog(Warning) << "Could not add notification USB device removal"; + } + + CFRunLoopSourceRef io_source = IONotificationPortGetRunLoopSource(notification_port); + CFRunLoopAddSource(run_loop_, io_source, kCFRunLoopDefaultMode); + + CFRunLoopRun(); +} + +void MacDeviceLister::ShutDown() { CFRunLoopStop(run_loop_); } + +// IOKit helpers. +namespace { + +// Caller is responsible for calling CFRelease(). +CFTypeRef GetUSBRegistryEntry(io_object_t device, CFStringRef key) { + io_iterator_t it; + if (IORegistryEntryGetParentIterator(device, kIOServicePlane, &it) == KERN_SUCCESS) { + io_object_t next; + while ((next = IOIteratorNext(it))) { + CFTypeRef registry_entry = (CFStringRef)IORegistryEntryCreateCFProperty( + next, key, kCFAllocatorDefault, 0); + if (registry_entry) { + IOObjectRelease(next); + IOObjectRelease(it); + return registry_entry; + } + + CFTypeRef ret = GetUSBRegistryEntry(next, key); + if (ret) { + IOObjectRelease(next); + IOObjectRelease(it); + return ret; + } + + IOObjectRelease(next); + } + } + + IOObjectRelease(it); + return nullptr; +} + +QString GetUSBRegistryEntryString(io_object_t device, CFStringRef key) { + ScopedCFTypeRef registry_string((CFStringRef)GetUSBRegistryEntry(device, key)); + if (registry_string) { + return QString::fromUtf8([(NSString*)registry_string.get() UTF8String]); + } + + return QString(); +} + +NSObject* GetPropertyForDevice(io_object_t device, CFStringRef key) { + CFMutableDictionaryRef properties; + kern_return_t ret = IORegistryEntryCreateCFProperties(device, &properties, kCFAllocatorDefault, 0); + + if (ret != KERN_SUCCESS) { + return nil; + } + + scoped_nsobject dict((NSDictionary*)properties); // Takes ownership. + NSObject* prop = [dict objectForKey:(NSString*)key]; + if (prop) { + // The dictionary goes out of scope so we should retain this object. + [prop retain]; + return prop; + } + + io_object_t parent; + ret = IORegistryEntryGetParentEntry(device, kIOServicePlane, &parent); + if (ret == KERN_SUCCESS) { + return GetPropertyForDevice(parent, key); + } + + return nil; +} + +int GetUSBDeviceClass(io_object_t device) { + ScopedCFTypeRef interface_class(IORegistryEntrySearchCFProperty( + device, + kIOServicePlane, + CFSTR(kUSBInterfaceClass), + kCFAllocatorDefault, + kIORegistryIterateRecursively)); + NSNumber* number = (NSNumber*)interface_class.get(); + if (number) { + int ret = [number unsignedShortValue]; + return ret; + } + return 0; +} + +QString GetIconForDevice(io_object_t device) { + scoped_nsobject media_icon((NSDictionary*)GetPropertyForDevice(device, CFSTR("IOMediaIcon"))); + if (media_icon) { + NSString* bundle = (NSString*)[media_icon objectForKey:@"CFBundleIdentifier"]; + NSString* file = (NSString*)[media_icon objectForKey:@"IOBundleResourceFile"]; + + scoped_nsobject bundle_url((NSURL*)KextManagerCreateURLForBundleIdentifier(kCFAllocatorDefault, (CFStringRef)bundle)); + + QString path = QString::fromUtf8([[bundle_url path] UTF8String]); + path += "/Contents/Resources/"; + path += QString::fromUtf8([file UTF8String]); + return path; + } + + return QString(); +} + +QString GetSerialForDevice(io_object_t device) { + QString serial = GetUSBRegistryEntryString(device, CFSTR(kUSBSerialNumberString)); + if (!serial.isEmpty()) { + return "USB/" + serial; + } + return QString(); +} + +QString GetSerialForMTPDevice(io_object_t device) { + scoped_nsobject serial((NSString*) GetPropertyForDevice(device, CFSTR(kUSBSerialNumberString))); + return QString(QString("MTP/") + QString::fromUtf8([serial UTF8String])); +} + +QString FindDeviceProperty(const QString& bsd_name, CFStringRef property) { + ScopedCFTypeRef session(DASessionCreate(kCFAllocatorDefault)); + ScopedCFTypeRef disk(DADiskCreateFromBSDName( + kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData())); + + ScopedIOObject device(DADiskCopyIOMedia(disk.get())); + QString ret = GetUSBRegistryEntryString(device.get(), property); + return ret; +} +} + +quint64 MacDeviceLister::GetFreeSpace(const QUrl& url) { + QMutexLocker l(&libmtp_mutex_); + MtpConnection connection(url); + if (!connection.is_valid()) { + qLog(Warning) << "Error connecting to MTP device, couldn't get device free space"; + return -1; + } + LIBMTP_devicestorage_t* storage = connection.device()->storage; + quint64 free_bytes = 0; + while (storage) { + free_bytes += storage->FreeSpaceInBytes; + storage = storage->next; + } + return free_bytes; +} + +quint64 MacDeviceLister::GetCapacity(const QUrl& url) { + QMutexLocker l(&libmtp_mutex_); + MtpConnection connection(url); + if (!connection.is_valid()) { + qLog(Warning) << "Error connecting to MTP device, couldn't get device capacity"; + return -1; + } + LIBMTP_devicestorage_t* storage = connection.device()->storage; + quint64 capacity_bytes = 0; + while (storage) { + capacity_bytes += storage->MaxCapacity; + storage = storage->next; + } + return capacity_bytes; +} + +void MacDeviceLister::DiskAddedCallback(DADiskRef disk, void* context) { + MacDeviceLister* me = reinterpret_cast(context); + + scoped_nsobject properties((NSDictionary*)DADiskCopyDescription(disk)); + + NSString* kind = [properties objectForKey:(NSString*)kDADiskDescriptionMediaKindKey]; +#ifdef HAVE_AUDIOCD + if (kind && strcmp([kind UTF8String], kIOCDMediaClass) == 0) { + // CD inserted. + QString bsd_name = QString::fromAscii(DADiskGetBSDName(disk)); + me->cd_devices_ << bsd_name; + emit me->DeviceAdded(bsd_name); + return; + } +#endif + + NSURL* volume_path = + [[properties objectForKey:(NSString*)kDADiskDescriptionVolumePathKey] copy]; + + if (volume_path) { + ScopedIOObject device(DADiskCopyIOMedia(disk)); + ScopedCFTypeRef class_name(IOObjectCopyClass(device.get())); + if (class_name && CFStringCompare(class_name.get(), CFSTR(kIOMediaClass), 0) == kCFCompareEqualTo) { + QString vendor = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBVendorString)); + QString product = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBProductString)); + + CFMutableDictionaryRef cf_properties; + kern_return_t ret = IORegistryEntryCreateCFProperties( + device.get(), &cf_properties, kCFAllocatorDefault, 0); + + if (ret == KERN_SUCCESS) { + scoped_nsobject dict((NSDictionary*)cf_properties); // Takes ownership. + if ([[dict objectForKey:@"Removable"] intValue] == 1) { + QString serial = GetSerialForDevice(device.get()); + if (!serial.isEmpty()) { + me->current_devices_[serial] = QString(DADiskGetBSDName(disk)); + emit me->DeviceAdded(serial); + } + } + } + } + } +} + +void MacDeviceLister::DiskRemovedCallback(DADiskRef disk, void* context) { + MacDeviceLister* me = reinterpret_cast(context); + // We cannot access the USB tree when the disk is removed but we still get + // the BSD disk name. + + QString bsd_name = QString::fromAscii(DADiskGetBSDName(disk)); + if (me->cd_devices_.remove(bsd_name)) { + emit me->DeviceRemoved(bsd_name); + return; + } + + for (QMap::iterator it = me->current_devices_.begin(); + it != me->current_devices_.end(); ++it) { + if (it.value() == bsd_name) { + emit me->DeviceRemoved(it.key()); + me->current_devices_.erase(it); + break; + } + } +} + +bool DeviceRequest(IOUSBDeviceInterface** dev, + quint8 direction, + quint8 type, + quint8 recipient, + quint8 request_code, + quint16 value, + quint16 index, + quint16 length, + QByteArray* data) { + IOUSBDevRequest req; + req.bmRequestType = USBmakebmRequestType(direction, type, recipient); + req.bRequest = request_code; + req.wValue = value; + req.wIndex = index; + req.wLength = length; + data->resize(256); + req.pData = data->data(); + kern_return_t err = (*dev)->DeviceRequest(dev, &req); + if (err != kIOReturnSuccess) { + return false; + } + data->resize(req.wLenDone); + return true; +} + +int GetBusNumber(io_object_t o) { + io_iterator_t it; + kern_return_t err = IORegistryEntryGetParentIterator(o, kIOServicePlane, &it); + if (err != KERN_SUCCESS) { + return -1; + } + while ((o = IOIteratorNext(it))) { + NSObject* bus = GetPropertyForDevice(o, CFSTR("USBBusNumber")); + if (bus) { + NSNumber* bus_num = (NSNumber*)bus; + return [bus_num intValue]; + } + } + + return -1; +} + +void MacDeviceLister::USBDeviceAddedCallback(void* refcon, io_iterator_t it) { + MacDeviceLister* me = reinterpret_cast(refcon); + + io_object_t object; + while ((object = IOIteratorNext(it))) { + ScopedCFTypeRef class_name(IOObjectCopyClass(object)); + BOOST_SCOPE_EXIT((object)) { + IOObjectRelease(object); + } BOOST_SCOPE_EXIT_END + + if (CFStringCompare(class_name.get(), CFSTR(kIOUSBDeviceClassName), 0) == kCFCompareEqualTo) { + NSString* vendor = (NSString*)GetPropertyForDevice(object, CFSTR(kUSBVendorString)); + NSString* product = (NSString*)GetPropertyForDevice(object, CFSTR(kUSBProductString)); + NSNumber* vendor_id = (NSNumber*)GetPropertyForDevice(object, CFSTR(kUSBVendorID)); + NSNumber* product_id = (NSNumber*)GetPropertyForDevice(object, CFSTR(kUSBProductID)); + int interface_class = GetUSBDeviceClass(object); + qLog(Debug) << "Interface class:" << interface_class; + + QString serial = GetSerialForMTPDevice(object); + + MTPDevice device; + device.vendor = QString::fromUtf8([vendor UTF8String]); + device.product = QString::fromUtf8([product UTF8String]); + device.vendor_id = [vendor_id unsignedShortValue]; + device.product_id = [product_id unsignedShortValue]; + device.quirks = 0; + + device.bus = -1; + device.address = -1; + + if (device.vendor_id == kAppleVendorID || // I think we can safely skip Apple products. + // Blacklist ilok2 as this probe may be breaking it. + (device.vendor_id == 0x088e && device.product_id == 0x5036) || + // Blacklist eLicenser + (device.vendor_id == 0x0819 && device.product_id == 0x0101) || + // Skip HID devices, printers and hubs. + interface_class == kUSBHIDInterfaceClass || + interface_class == kUSBPrintingInterfaceClass || + interface_class == kUSBHubClass) { + continue; + } + + NSNumber* addr = (NSNumber*)GetPropertyForDevice(object, CFSTR("USB Address")); + int bus = GetBusNumber(object); + if (!addr || bus == -1) { + // Failed to get bus or address number. + continue; + } + device.bus = bus; + device.address = [addr intValue]; + + // First check the libmtp device list. + QSet::const_iterator it = sMTPDeviceList.find(device); + if (it != sMTPDeviceList.end()) { + // Fill in quirks flags from libmtp. + device.quirks = it->quirks; + me->FoundMTPDevice(device, GetSerialForMTPDevice(object)); + continue; + } + + IOCFPlugInInterface** plugin_interface = nullptr; + SInt32 score; + kern_return_t err = IOCreatePlugInInterfaceForService( + object, + kIOUSBDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, + &plugin_interface, + &score); + if (err != KERN_SUCCESS) { + continue; + } + + IOUSBDeviceInterface** dev = nullptr; + HRESULT result = (*plugin_interface)->QueryInterface(plugin_interface, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID*)&dev); + + (*plugin_interface)->Release(plugin_interface); + + if (result || !dev) { + continue; + } + + err = (*dev)->USBDeviceOpen(dev); + if (err != kIOReturnSuccess) { + continue; + } + + // Automatically close & release usb device at scope exit. + BOOST_SCOPE_EXIT((dev)) { + (*dev)->USBDeviceClose(dev); + (*dev)->Release(dev); + } BOOST_SCOPE_EXIT_END + + // Request the string descriptor at 0xee. + // This is a magic string that indicates whether this device supports MTP. + QByteArray data; + bool ret = DeviceRequest(dev, kUSBIn, kUSBStandard, kUSBDevice, kUSBRqGetDescriptor, (kUSBStringDesc << 8) | 0xee, 0x0409, 2, &data); + if (!ret) continue; + + UInt8 string_len = data[0]; + + ret = DeviceRequest(dev, kUSBIn, kUSBStandard, kUSBDevice, kUSBRqGetDescriptor, (kUSBStringDesc << 8) | 0xee, 0x0409, string_len, &data); + if (!ret) continue; + + // The device actually returned something. That's a good sign. + // Because this was designed by MS, the characters are in UTF-16 (LE?). + QString str = QString::fromUtf16(reinterpret_cast(data.data() + 2), (data.size() / 2) - 2); + + if (str.startsWith("MSFT100")) { + // We got the OS descriptor! + char vendor_code = data[16]; + ret = DeviceRequest(dev, kUSBIn, kUSBVendor, kUSBDevice, vendor_code, 0, 4, 256, &data); + if (!ret || data.at(0) != 0x28) + continue; + + if (QString::fromAscii(data.data() + 0x12, 3) != "MTP") { + // Not quite. + continue; + } + + ret = DeviceRequest(dev, kUSBIn, kUSBVendor, kUSBDevice, vendor_code, 0, 5, 256, &data); + if (!ret || data.at(0) != 0x28) { + continue; + } + + if (QString::fromAscii(data.data() + 0x12, 3) != "MTP") { + // Not quite. + continue; + } + // Hurray! We made it! + me->FoundMTPDevice(device, serial); + } + } + } +} + +void MacDeviceLister::USBDeviceRemovedCallback(void* refcon, io_iterator_t it) { + MacDeviceLister* me = reinterpret_cast(refcon); + io_object_t object; + while ((object = IOIteratorNext(it))) { + ScopedCFTypeRef class_name(IOObjectCopyClass(object)); + BOOST_SCOPE_EXIT((object)) { IOObjectRelease(object); } + BOOST_SCOPE_EXIT_END + + if (CFStringCompare(class_name.get(), CFSTR(kIOUSBDeviceClassName), 0) == kCFCompareEqualTo) { + NSString* vendor = (NSString*)GetPropertyForDevice(object, CFSTR(kUSBVendorString)); + NSString* product = (NSString*)GetPropertyForDevice(object, CFSTR(kUSBProductString)); + NSNumber* vendor_id = (NSNumber*)GetPropertyForDevice(object, CFSTR(kUSBVendorID)); + NSNumber* product_id = (NSNumber*)GetPropertyForDevice(object, CFSTR(kUSBProductID)); + QString serial = GetSerialForMTPDevice(object); + + MTPDevice device; + device.vendor = QString::fromUtf8([vendor UTF8String]); + device.product = QString::fromUtf8([product UTF8String]); + device.vendor_id = [vendor_id unsignedShortValue]; + device.product_id = [product_id unsignedShortValue]; + + me->RemovedMTPDevice(serial); + } + } +} + +void MacDeviceLister::RemovedMTPDevice(const QString& serial) { + int count = mtp_devices_.remove(serial); + if (count) { + qLog(Debug) << "MTP device removed:" << serial; + emit DeviceRemoved(serial); + } +} + +void MacDeviceLister::FoundMTPDevice(const MTPDevice& device, const QString& serial) { + qLog(Debug) << "New MTP device detected!" << device.bus << device.address; + mtp_devices_[serial] = device; + QList urls = MakeDeviceUrls(serial); + MTPDevice* d = &mtp_devices_[serial]; + d->capacity = GetCapacity(urls[0]); + d->free_space = GetFreeSpace(urls[0]); + emit DeviceAdded(serial); +} + +bool IsMTPSerial(const QString& serial) { return serial.startsWith("MTP"); } + +bool MacDeviceLister::IsCDDevice(const QString& serial) const { + return cd_devices_.contains(serial); +} + +QString MacDeviceLister::MakeFriendlyName(const QString& serial) { + if (IsMTPSerial(serial)) { + const MTPDevice& device = mtp_devices_[serial]; + if (device.vendor.isEmpty()) { + return device.product; + } else { + return device.vendor + " " + device.product; + } + } + + QString bsd_name = IsCDDevice(serial) ? *cd_devices_.find(serial) : current_devices_[serial]; + ScopedCFTypeRef session(DASessionCreate(kCFAllocatorDefault)); + ScopedCFTypeRef disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData())); + + if (IsCDDevice(serial)) { + scoped_nsobject properties((NSDictionary*)DADiskCopyDescription(disk.get())); + NSString* device_name = (NSString*)[properties.get() objectForKey:(NSString*)kDADiskDescriptionMediaNameKey]; + + return QString::fromUtf8([device_name UTF8String]); + } + + ScopedIOObject device(DADiskCopyIOMedia(disk)); + + QString vendor = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBVendorString)); + QString product = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBProductString)); + + if (vendor.isEmpty()) { + return product; + } + return vendor + " " + product; +} + +QList MacDeviceLister::MakeDeviceUrls(const QString& serial) { + if (IsMTPSerial(serial)) { + const MTPDevice& device = mtp_devices_[serial]; + QString str; + str.sprintf("gphoto2://usb-%d-%d/", device.bus, device.address); + QUrl url(str); + url.addQueryItem("vendor", device.vendor); + url.addQueryItem("vendor_id", QString::number(device.vendor_id)); + url.addQueryItem("product", device.product); + url.addQueryItem("product_id", QString::number(device.product_id)); + url.addQueryItem("quirks", QString::number(device.quirks)); + return QList() << url; + } + + if (IsCDDevice(serial)) { + return QList() << QUrl(QString("cdda:///dev/r" + serial)); + } + + QString bsd_name = current_devices_[serial]; + ScopedCFTypeRef session(DASessionCreate(kCFAllocatorDefault)); + ScopedCFTypeRef disk(DADiskCreateFromBSDName( + kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData())); + + scoped_nsobject properties((NSDictionary*)DADiskCopyDescription(disk.get())); + scoped_nsobject volume_path([[properties objectForKey:(NSString*)kDADiskDescriptionVolumePathKey] copy]); + + QString path = QString::fromUtf8([[volume_path path] UTF8String]); + QUrl ret = MakeUrlFromLocalPath(path); + + return QList() << ret; +} + +QStringList MacDeviceLister::DeviceUniqueIDs() { + return current_devices_.keys() + mtp_devices_.keys(); +} + +QVariantList MacDeviceLister::DeviceIcons(const QString& serial) { + if (IsMTPSerial(serial)) { + return QVariantList(); + } + + if (IsCDDevice(serial)) { + return QVariantList() << "cd"; + } + + QString bsd_name = current_devices_[serial]; + ScopedCFTypeRef session(DASessionCreate(kCFAllocatorDefault)); + ScopedCFTypeRef disk(DADiskCreateFromBSDName( + kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData())); + + ScopedIOObject device(DADiskCopyIOMedia(disk.get())); + QString icon = GetIconForDevice(device.get()); + + scoped_nsobject properties((NSDictionary*)DADiskCopyDescription(disk)); + scoped_nsobject volume_path([[properties objectForKey:(NSString*)kDADiskDescriptionVolumePathKey] copy]); + + QString path = QString::fromUtf8([[volume_path path] UTF8String]); + + QVariantList ret; + ret << GuessIconForPath(path); + ret << GuessIconForModel(DeviceManufacturer(serial), DeviceModel(serial)); + if (!icon.isEmpty()) { + ret << icon; + } + return ret; +} + +QString MacDeviceLister::DeviceManufacturer(const QString& serial) { + if (IsMTPSerial(serial)) { + return mtp_devices_[serial].vendor; + } + return FindDeviceProperty(current_devices_[serial], CFSTR(kUSBVendorString)); +} + +QString MacDeviceLister::DeviceModel(const QString& serial) { + if (IsMTPSerial(serial)) { + return mtp_devices_[serial].product; + } + return FindDeviceProperty(current_devices_[serial], CFSTR(kUSBProductString)); +} + +quint64 MacDeviceLister::DeviceCapacity(const QString& serial) { + if (IsMTPSerial(serial)) { + QList urls = MakeDeviceUrls(serial); + return mtp_devices_[serial].capacity; + } + QString bsd_name = current_devices_[serial]; + ScopedCFTypeRef session(DASessionCreate(kCFAllocatorDefault)); + ScopedCFTypeRef disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData())); + + io_object_t device = DADiskCopyIOMedia(disk); + + NSNumber* capacity = (NSNumber*)GetPropertyForDevice(device, CFSTR("Size")); + + quint64 ret = [capacity unsignedLongLongValue]; + + IOObjectRelease(device); + + return ret; +} + +quint64 MacDeviceLister::DeviceFreeSpace(const QString& serial) { + if (IsMTPSerial(serial)) { + QList urls = MakeDeviceUrls(serial); + return mtp_devices_[serial].free_space; + } + QString bsd_name = current_devices_[serial]; + ScopedCFTypeRef session(DASessionCreate(kCFAllocatorDefault)); + ScopedCFTypeRef disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData())); + + scoped_nsobject properties((NSDictionary*)DADiskCopyDescription(disk)); + scoped_nsobject volume_path([[properties objectForKey:(NSString*)kDADiskDescriptionVolumePathKey] copy]); + + NSNumber* value = nil; + NSError* error = nil; + if ([volume_path getResourceValue:&value forKey: NSURLVolumeAvailableCapacityKey error: &error] && value) { + return [value unsignedLongLongValue]; + } + return 0; +} + +QVariantMap MacDeviceLister::DeviceHardwareInfo(const QString& serial){return QVariantMap();} + +bool MacDeviceLister::AskForScan(const QString& serial) const { + return !IsCDDevice(serial); +} + +void MacDeviceLister::UnmountDevice(const QString& serial) { + if (IsMTPSerial(serial)) return; + + QString bsd_name = current_devices_[serial]; + ScopedCFTypeRef disk(DADiskCreateFromBSDName(kCFAllocatorDefault, loop_session_, bsd_name.toLatin1().constData())); + + DADiskUnmount(disk, kDADiskUnmountOptionDefault, &DiskUnmountCallback, this); +} + +void MacDeviceLister::DiskUnmountCallback(DADiskRef disk, DADissenterRef dissenter, void* context) { + if (dissenter) { + qLog(Warning) << "Another app blocked the unmount"; + } + else { + DiskRemovedCallback(disk, context); + } +} + +void MacDeviceLister::UpdateDeviceFreeSpace(const QString& serial) { + if (IsMTPSerial(serial)) { + if (mtp_devices_.contains(serial)) { + QList urls = MakeDeviceUrls(serial); + MTPDevice* d = &mtp_devices_[serial]; + d->free_space = GetFreeSpace(urls[0]); + } + } + emit DeviceChanged(serial); +} + diff --git a/src/device/mtpconnection.cpp b/src/device/mtpconnection.cpp new file mode 100644 index 00000000..c8613cd5 --- /dev/null +++ b/src/device/mtpconnection.cpp @@ -0,0 +1,93 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include + +#include "mtpconnection.h" +#include "core/logging.h" + +MtpConnection::MtpConnection(const QUrl &url) : device_(nullptr) { + + QString hostname = url.host(); + // Parse the URL + QRegExp host_re("^usb-(\\d+)-(\\d+)$"); + + if (host_re.indexIn(hostname) == -1) { + qLog(Warning) << "Invalid MTP device:" << hostname; + return; + } + + const unsigned int bus_location = host_re.cap(1).toInt(); + const unsigned int device_num = host_re.cap(2).toInt(); + + QUrlQuery url_query(url); + if (url_query.hasQueryItem("vendor")) { + LIBMTP_raw_device_t *raw_device = (LIBMTP_raw_device_t*)malloc(sizeof(LIBMTP_raw_device_t)); + raw_device->device_entry.vendor = url_query.queryItemValue("vendor").toLatin1().data(); + raw_device->device_entry.product = url_query.queryItemValue("product").toLatin1().data(); + raw_device->device_entry.vendor_id = url_query.queryItemValue("vendor_id").toUShort(); + raw_device->device_entry.product_id = url_query.queryItemValue("product_id").toUShort(); + raw_device->device_entry.device_flags = url_query.queryItemValue("quirks").toUInt(); + + raw_device->bus_location = bus_location; + raw_device->devnum = device_num; + + device_ = LIBMTP_Open_Raw_Device(raw_device); + return; + } + + // Get a list of devices from libmtp and figure out which one is ours + int count = 0; + LIBMTP_raw_device_t *raw_devices = nullptr; + LIBMTP_error_number_t err = LIBMTP_Detect_Raw_Devices(&raw_devices, &count); + if (err != LIBMTP_ERROR_NONE) { + qLog(Warning) << "MTP error:" << err; + return; + } + + LIBMTP_raw_device_t *raw_device = nullptr; + for (int i = 0; i < count; ++i) { + if (raw_devices[i].bus_location == bus_location && raw_devices[i].devnum == device_num) { + raw_device = &raw_devices[i]; + break; + } + } + + if (!raw_device) { + qLog(Warning) << "MTP device not found"; + free(raw_devices); + return; + } + + // Connect to the device + device_ = LIBMTP_Open_Raw_Device(raw_device); + + free(raw_devices); + +} + +MtpConnection::~MtpConnection() { + if (device_) LIBMTP_Release_Device(device_); +} + diff --git a/src/device/mtpconnection.h b/src/device/mtpconnection.h new file mode 100644 index 00000000..f7e6cd70 --- /dev/null +++ b/src/device/mtpconnection.h @@ -0,0 +1,44 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MTPCONNECTION_H +#define MTPCONNECTION_H + +#include "config.h" + +#include + +#include + +class MtpConnection { +public: + MtpConnection(const QUrl &url); + ~MtpConnection(); + + bool is_valid() const { return device_; } + LIBMTP_mtpdevice_t *device() const { return device_; } + +private: + Q_DISABLE_COPY(MtpConnection); + + LIBMTP_mtpdevice_t *device_; +}; + +#endif // MTPCONNECTION_H diff --git a/src/device/mtpdevice.cpp b/src/device/mtpdevice.cpp new file mode 100644 index 00000000..9b3bf358 --- /dev/null +++ b/src/device/mtpdevice.cpp @@ -0,0 +1,223 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include +#include + +#include "devicemanager.h" +#include "mtpconnection.h" +#include "mtpdevice.h" +#include "mtploader.h" +#include "core/application.h" +#include "core/logging.h" +#include "collection/collectionbackend.h" +#include "collection/collectionmodel.h" + +bool MtpDevice::sInitialisedLibMTP = false; + +MtpDevice::MtpDevice(const QUrl &url, DeviceLister *lister, const QString &unique_id, DeviceManager *manager, Application *app, int database_id, bool first_time) + : ConnectedDevice(url, lister, unique_id, manager, app, database_id, first_time), loader_thread_(new QThread(this)), loader_(nullptr) { + + if (!sInitialisedLibMTP) { + LIBMTP_Init(); + sInitialisedLibMTP = true; + } + +} + +MtpDevice::~MtpDevice() {} + +void MtpDevice::Init() { + + InitBackendDirectory("/", first_time_, false); + model_->Init(); + + loader_ = new MtpLoader(url_, app_->task_manager(), backend_, shared_from_this()); + loader_->moveToThread(loader_thread_); + + connect(loader_, SIGNAL(Error(QString)), SIGNAL(Error(QString))); + connect(loader_, SIGNAL(TaskStarted(int)), SIGNAL(TaskStarted(int))); + connect(loader_, SIGNAL(LoadFinished()), SLOT(LoadFinished())); + connect(loader_thread_, SIGNAL(started()), loader_, SLOT(LoadDatabase())); + + db_busy_.lock(); + loader_thread_->start(); + +} + +void MtpDevice::LoadFinished() { + + loader_->deleteLater(); + loader_ = nullptr; + db_busy_.unlock(); + +} + +bool MtpDevice::StartCopy(QList *supported_types) { + + // Ensure only one "organise files" can be active at any one time + db_busy_.lock(); + + // Connect to the device + connection_.reset(new MtpConnection(url_)); + + // Did the caller want a list of supported types? + if (supported_types) { + if (!GetSupportedFiletypes(supported_types, connection_->device())) { + FinishCopy(false); + return false; + } + } + + return true; + +} + +static int ProgressCallback(uint64_t const sent, uint64_t const total, void const *const data) { + const MusicStorage::CopyJob *job = reinterpret_cast(data); + job->progress_(float(sent) / total); + + return 0; +} + +bool MtpDevice::CopyToStorage(const CopyJob &job) { + + if (!connection_->is_valid()) return false; + + // Convert metadata + LIBMTP_track_t track; + job.metadata_.ToMTP(&track); + + // Send the file + int ret = LIBMTP_Send_Track_From_File(connection_->device(), job.source_.toUtf8().constData(), &track, ProgressCallback, &job); + if (ret != 0) return false; + + // Add it to our CollectionModel + Song metadata_on_device; + metadata_on_device.InitFromMTP(&track, url_.host()); + metadata_on_device.set_directory_id(1); + songs_to_add_ << metadata_on_device; + + // Remove the original if requested + if (job.remove_original_) { + if (!QFile::remove(job.source_)) return false; + } + + return true; + +} + +void MtpDevice::FinishCopy(bool success) { + + if (success) { + if (!songs_to_add_.isEmpty()) backend_->AddOrUpdateSongs(songs_to_add_); + if (!songs_to_remove_.isEmpty()) backend_->DeleteSongs(songs_to_remove_); + } + + songs_to_add_.clear(); + songs_to_remove_.clear(); + + connection_.reset(); + + db_busy_.unlock(); + + ConnectedDevice::FinishCopy(success); + +} + +void MtpDevice::StartDelete() { StartCopy(nullptr); } + +bool MtpDevice::DeleteFromStorage(const DeleteJob &job) { + + // Extract the ID from the song's URL + QString filename = job.metadata_.url().path(); + filename.remove('/'); + + bool ok = false; + uint32_t id = filename.toUInt(&ok); + if (!ok) return false; + + // Remove the file + int ret = LIBMTP_Delete_Object(connection_->device(), id); + if (ret != 0) return false; + + // Remove it from our collection model + songs_to_remove_ << job.metadata_; + + return true; + +} + +void MtpDevice::FinishDelete(bool success) { FinishCopy(success); } + +bool MtpDevice::GetSupportedFiletypes(QList *ret) { + + QMutexLocker l(&db_busy_); + MtpConnection connection(url_); + if (!connection.is_valid()) { + qLog(Warning) << "Error connecting to MTP device, couldn't get list of supported filetypes"; + return false; + } + + return GetSupportedFiletypes(ret, connection.device()); + +} + +bool MtpDevice::GetSupportedFiletypes(QList *ret, LIBMTP_mtpdevice_t *device) { + + uint16_t *list = nullptr; + uint16_t length = 0; + + if (LIBMTP_Get_Supported_Filetypes(device, &list, &length) || !list || !length) + return false; + + for (int i = 0; i < length; ++i) { + switch (LIBMTP_filetype_t(list[i])) { + case LIBMTP_FILETYPE_WAV: *ret << Song::Type_Wav; break; + case LIBMTP_FILETYPE_MP2: + case LIBMTP_FILETYPE_MP3: *ret << Song::Type_Mpeg; break; + case LIBMTP_FILETYPE_WMA: *ret << Song::Type_Asf; break; + case LIBMTP_FILETYPE_MP4: + case LIBMTP_FILETYPE_M4A: + case LIBMTP_FILETYPE_AAC: *ret << Song::Type_Mp4; break; + case LIBMTP_FILETYPE_FLAC: + *ret << Song::Type_Flac; + *ret << Song::Type_OggFlac; + break; + case LIBMTP_FILETYPE_OGG: + *ret << Song::Type_OggVorbis; + *ret << Song::Type_OggSpeex; + *ret << Song::Type_OggFlac; + break; + default: + qLog(Error) << "Unknown MTP file format" << LIBMTP_Get_Filetype_Description(LIBMTP_filetype_t(list[i])); + break; + } + } + + free(list); + return true; + +} + diff --git a/src/device/mtpdevice.h b/src/device/mtpdevice.h new file mode 100644 index 00000000..0cfec959 --- /dev/null +++ b/src/device/mtpdevice.h @@ -0,0 +1,82 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MTPDEVICE_H +#define MTPDEVICE_H + +#include "config.h" + +#include + +#include +#include + +#include "connecteddevice.h" + +struct LIBMTP_mtpdevice_struct; + +class MtpConnection; +class MtpLoader; + +class MtpDevice : public ConnectedDevice { + Q_OBJECT + + public: + Q_INVOKABLE MtpDevice(const QUrl &url, DeviceLister *lister, const QString &unique_id, DeviceManager *manager, Application *app, int database_id, bool first_time); + ~MtpDevice(); + + static QStringList url_schemes() { return QStringList() << "mtp" << "gphoto2"; } + + void Init(); + + bool GetSupportedFiletypes(QList* ret); + int GetFreeSpace(); + int GetCapacity(); + + bool StartCopy(QList* supported_types); + bool CopyToStorage(const CopyJob& job); + void FinishCopy(bool success); + + void StartDelete(); + bool DeleteFromStorage(const DeleteJob& job); + void FinishDelete(bool success); + + private slots: + void LoadFinished(); + + private: + bool GetSupportedFiletypes(QList *ret, LIBMTP_mtpdevice_struct *device); + int GetFreeSpace(LIBMTP_mtpdevice_struct* device); + int GetCapacity(LIBMTP_mtpdevice_struct* device); + + private: + static bool sInitialisedLibMTP; + + QThread *loader_thread_; + MtpLoader *loader_; + + QMutex db_busy_; + SongList songs_to_add_; + SongList songs_to_remove_; + + std::unique_ptr connection_; +}; + +#endif // MTPDEVICE_H diff --git a/src/device/mtploader.cpp b/src/device/mtploader.cpp new file mode 100644 index 00000000..0934ad64 --- /dev/null +++ b/src/device/mtploader.cpp @@ -0,0 +1,90 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "mtploader.h" + +#include + +#include "connecteddevice.h" +#include "mtpconnection.h" +#include "core/song.h" +#include "core/taskmanager.h" +#include "collection/collectionbackend.h" + +MtpLoader::MtpLoader(const QUrl &url, TaskManager *task_manager, CollectionBackend *backend, std::shared_ptr device) + : QObject(nullptr), + device_(device), + url_(url), + task_manager_(task_manager), + backend_(backend) { + original_thread_ = thread(); +} + +MtpLoader::~MtpLoader() {} + +void MtpLoader::LoadDatabase() { + + int task_id = task_manager_->StartTask(tr("Loading MTP device")); + emit TaskStarted(task_id); + + TryLoad(); + + moveToThread(original_thread_); + + task_manager_->SetTaskFinished(task_id); + emit LoadFinished(); + +} + +bool MtpLoader::TryLoad() { + + MtpConnection dev(url_); + if (!dev.is_valid()) { + emit Error(tr("Error connecting MTP device")); + return false; + } + + // Load the list of songs on the device + SongList songs; + LIBMTP_track_t *tracks = LIBMTP_Get_Tracklisting_With_Callback(dev.device(), nullptr, nullptr); + while (tracks) { + LIBMTP_track_t *track = tracks; + + Song song; + song.InitFromMTP(track, url_.host()); + song.set_directory_id(1); + songs << song; + + tracks = tracks->next; + LIBMTP_destroy_track_t(track); + } + + // Need to remove all the existing songs in the database first + backend_->DeleteSongs(backend_->FindSongsInDirectory(1)); + + // Add the songs we've just loaded + backend_->AddOrUpdateSongs(songs); + + return true; + +} + diff --git a/src/device/mtploader.h b/src/device/mtploader.h new file mode 100644 index 00000000..aac826d4 --- /dev/null +++ b/src/device/mtploader.h @@ -0,0 +1,63 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MTPLOADER_H +#define MTPLOADER_H + +#include "config.h" + +#include + +#include +#include + +class ConnectedDevice; +class CollectionBackend; +class TaskManager; + +class MtpLoader : public QObject { + Q_OBJECT + + public: + MtpLoader(const QUrl &url, TaskManager *task_manager, CollectionBackend *backend, std::shared_ptr device); + ~MtpLoader(); + + public slots: + void LoadDatabase(); + +signals: + void Error(const QString &message); + void TaskStarted(int task_id); + void LoadFinished(); + + private: + bool TryLoad(); + + private: + std::shared_ptr device_; + QThread* original_thread_; + + QUrl url_; + TaskManager *task_manager_; + CollectionBackend *backend_; +}; + +#endif // MTPLOADER_H + diff --git a/src/device/udisks2lister.cpp b/src/device/udisks2lister.cpp new file mode 100644 index 00000000..bd93a3e4 --- /dev/null +++ b/src/device/udisks2lister.cpp @@ -0,0 +1,363 @@ +/* This file is part of Clementine. + Copyright 2016, Valeriy Malov + + 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 "config.h" + +#include "udisks2lister.h" + +#include + +#include "core/logging.h" +#include "core/utilities.h" +#include "dbus/objectmanager.h" +#include "dbus/udisks2block.h" +#include "dbus/udisks2drive.h" +#include "dbus/udisks2filesystem.h" +#include "dbus/udisks2job.h" + +constexpr char Udisks2Lister::udisks2_service_[]; + +Udisks2Lister::Udisks2Lister() {} + +Udisks2Lister::~Udisks2Lister() {} + +QStringList Udisks2Lister::DeviceUniqueIDs() { + QReadLocker locker(&device_data_lock_); + return device_data_.keys(); +} + +QVariantList Udisks2Lister::DeviceIcons(const QString &id) { + return QVariantList(); +} + +QString Udisks2Lister::DeviceManufacturer(const QString &id) { + + QReadLocker locker(&device_data_lock_); + if (!device_data_.contains(id)) return ""; + return device_data_[id].vendor; + +} + +QString Udisks2Lister::DeviceModel(const QString &id) { + + QReadLocker locker(&device_data_lock_); + if (!device_data_.contains(id)) return ""; + return device_data_[id].model; + +} + +quint64 Udisks2Lister::DeviceCapacity(const QString &id) { + + QReadLocker locker(&device_data_lock_); + if (!device_data_.contains(id)) return 0; + return device_data_[id].capacity; + +} + +quint64 Udisks2Lister::DeviceFreeSpace(const QString &id) { + + QReadLocker locker(&device_data_lock_); + if (!device_data_.contains(id)) return 0; + return device_data_[id].free_space; + +} + +QVariantMap Udisks2Lister::DeviceHardwareInfo(const QString &id) { + + QReadLocker locker(&device_data_lock_); + if (!device_data_.contains(id)) return QVariantMap(); + + QVariantMap result; + + const auto &data = device_data_[id]; + result[QT_TR_NOOP("DBus path")] = data.dbus_path; + result[QT_TR_NOOP("Serial number")] = data.serial; + result[QT_TR_NOOP("Mount points")] = data.mount_paths.join(", "); + result[QT_TR_NOOP("Partition label")] = data.label; + result[QT_TR_NOOP("UUID")] = data.uuid; + + return result; + +} + +QString Udisks2Lister::MakeFriendlyName(const QString &id) { + QReadLocker locker(&device_data_lock_); + if (!device_data_.contains(id)) return ""; + return device_data_[id].friendly_name; +} + +QList Udisks2Lister::MakeDeviceUrls(const QString &id) { + QReadLocker locker(&device_data_lock_); + if (!device_data_.contains(id)) return QList(); + return QList() << QUrl::fromLocalFile( + device_data_[id].mount_paths.at(0)); +} + +void Udisks2Lister::UnmountDevice(const QString &id) { + + QReadLocker locker(&device_data_lock_); + if (!device_data_.contains(id)) return; + + OrgFreedesktopUDisks2FilesystemInterface filesystem( + udisks2_service_, device_data_[id].dbus_path, + QDBusConnection::systemBus()); + + if (filesystem.isValid()) { + auto unmount_result = filesystem.Unmount(QVariantMap()); + unmount_result.waitForFinished(); + + if (unmount_result.isError()) { + qLog(Warning) << "Failed to unmount " << id << ": " << unmount_result.error(); + return; + } + + OrgFreedesktopUDisks2DriveInterface drive(udisks2_service_, device_data_[id].dbus_drive_path, QDBusConnection::systemBus()); + + if (drive.isValid()) { + auto eject_result = drive.Eject(QVariantMap()); + eject_result.waitForFinished(); + + if (eject_result.isError()) + qLog(Warning) << "Failed to eject " << id << ": " << eject_result.error(); + } + + device_data_.remove(id); + DeviceRemoved(id); + } + +} + +void Udisks2Lister::UpdateDeviceFreeSpace(const QString &id) { + QWriteLocker locker(&device_data_lock_); + device_data_[id].free_space = + Utilities::FileSystemFreeSpace(device_data_[id].mount_paths.at(0)); + + emit DeviceChanged(id); +} + +void Udisks2Lister::Init() { + + udisks2_interface_.reset(new OrgFreedesktopDBusObjectManagerInterface(udisks2_service_, "/org/freedesktop/UDisks2", QDBusConnection::systemBus())); + + QDBusPendingReply reply = udisks2_interface_->GetManagedObjects(); + reply.waitForFinished(); + + if (!reply.isValid()) { + qLog(Warning) << "Error enumerating udisks2 devices:" << reply.error().name() << reply.error().message(); + udisks2_interface_.reset(); + return; + } + + for (const QDBusObjectPath &path : reply.value().keys()) { + auto partition_data = ReadPartitionData(path); + + if (!partition_data.dbus_path.isEmpty()) { + QWriteLocker locker(&device_data_lock_); + device_data_[partition_data.unique_id()] = partition_data; + } + } + + for (const auto &id : device_data_.keys()) { + emit DeviceAdded(id); + } + + connect(udisks2_interface_.get(), SIGNAL(InterfacesAdded(QDBusObjectPath, InterfacesAndProperties)), SLOT(DBusInterfaceAdded(QDBusObjectPath, InterfacesAndProperties))); + connect(udisks2_interface_.get(), SIGNAL(InterfacesRemoved(QDBusObjectPath, QStringList)), SLOT(DBusInterfaceRemoved(QDBusObjectPath, QStringList))); +} + +void Udisks2Lister::DBusInterfaceAdded(const QDBusObjectPath &path, const InterfacesAndProperties &interfaces) { + + for (auto interface = interfaces.constBegin(); interface != interfaces.constEnd(); ++interface) { + + if (interface.key() != "org.freedesktop.UDisks2.Job") continue; + + std::shared_ptr job = std::make_shared(udisks2_service_, path.path(), QDBusConnection::systemBus()); + + if (!job->isValid()) continue; + + bool is_mount_job = false; + if (job->operation() == "filesystem-mount") { + is_mount_job = true; + } + else if (job->operation() == "filesystem-unmount") { + is_mount_job = false; + } + else { + continue; + } + + auto mounted_partitions = job->objects(); + + if (mounted_partitions.isEmpty()) { + qLog(Warning) << "Empty Udisks2 mount/umount job " << path.path(); + continue; + } + + { + QMutexLocker locker(&jobs_lock_); + qLog(Debug) << "Adding pending job | DBus Path = " << job->path() << " | IsMountJob = " << is_mount_job << " | First partition = " << mounted_partitions.at(0).path(); + mounting_jobs_[path].dbus_interface = job; + mounting_jobs_[path].is_mount = is_mount_job; + mounting_jobs_[path].mounted_partitions = mounted_partitions; + connect(job.get(), SIGNAL(Completed(bool, const QString&)), SLOT(JobCompleted(bool, const QString&))); + } + } +} + +void Udisks2Lister::DBusInterfaceRemoved(const QDBusObjectPath &path, const QStringList &ifaces) { + if (!isPendingJob(path)) RemoveDevice(path); +} + +bool Udisks2Lister::isPendingJob(const QDBusObjectPath &job_path) { + QMutexLocker locker(&jobs_lock_); + + if (!mounting_jobs_.contains(job_path)) return false; + + mounting_jobs_.remove(job_path); + return true; +} + +void Udisks2Lister::RemoveDevice(const QDBusObjectPath &device_path) { + + QWriteLocker locker(&device_data_lock_); + QString id; + for (const auto &data : device_data_) { + if (data.dbus_path == device_path.path()) { + id = data.unique_id(); + break; + } + } + + if (id.isEmpty()) return; + + qLog(Debug) << "UDisks2 device removed: " << device_path.path(); + device_data_.remove(id); + DeviceRemoved(id); + +} + +QList Udisks2Lister::GetMountedPartitionsFromDBusArgument(const QDBusArgument &input) { + + QList result; + + input.beginArray(); + while (!input.atEnd()) { + QDBusObjectPath extractedPath; + input >> extractedPath; + result.push_back(extractedPath); + } + input.endArray(); + + return result; + +} + +void Udisks2Lister::JobCompleted(bool success, const QString &message) { + + auto job = qobject_cast(sender()); + QDBusObjectPath jobPath(job->path()); + + if (!job->isValid() || !success || !mounting_jobs_.contains(jobPath)) return; + + qLog(Debug) << "Pending Job Completed | Path = " << job->path() << " | Mount? = " << mounting_jobs_[jobPath].is_mount << " | Success = " << success; + + for (const auto &mounted_object : mounting_jobs_[jobPath].mounted_partitions) { + auto partition_data = ReadPartitionData(mounted_object); + if (partition_data.dbus_path.isEmpty()) continue; + + mounting_jobs_[jobPath].is_mount ? HandleFinishedMountJob(partition_data) : HandleFinishedUnmountJob(partition_data, mounted_object); + } +} + +void Udisks2Lister::HandleFinishedMountJob(const Udisks2Lister::PartitionData &partition_data) { + + qLog(Debug) << "UDisks2 mount job finished: Drive = " << partition_data.dbus_drive_path << " | Partition = " << partition_data.dbus_path; + QWriteLocker locker(&device_data_lock_); + device_data_[partition_data.unique_id()] = partition_data; + DeviceAdded(partition_data.unique_id()); + +} + +void Udisks2Lister::HandleFinishedUnmountJob(const Udisks2Lister::PartitionData &partition_data, const QDBusObjectPath &mounted_object) { + + QWriteLocker locker(&device_data_lock_); + QString id; + for (auto &data : device_data_) { + if (data.mount_paths.contains(mounted_object.path())) { + qLog(Debug) << "UDisks2 umount job finished, found corresponding device: Drive = " << data.dbus_drive_path << " | Partition = " << data.dbus_path; + data.mount_paths.removeOne(mounted_object.path()); + if (data.mount_paths.empty()) id = data.unique_id(); + break; + } + } + + if (!id.isEmpty()) { + qLog(Debug) << "Partition " << partition_data.dbus_path + << " has no more mount points, removing it from device list"; + device_data_.remove(id); + DeviceRemoved(id); + } + +} + +Udisks2Lister::PartitionData Udisks2Lister::ReadPartitionData(const QDBusObjectPath &path) { + + PartitionData result; + OrgFreedesktopUDisks2FilesystemInterface filesystem(udisks2_service_, path.path(), QDBusConnection::systemBus()); + OrgFreedesktopUDisks2BlockInterface block(udisks2_service_, path.path(), QDBusConnection::systemBus()); + + if (filesystem.isValid() && block.isValid() && !filesystem.mountPoints().empty()) { + OrgFreedesktopUDisks2DriveInterface drive(udisks2_service_, block.drive().path(), QDBusConnection::systemBus()); + + if (drive.isValid() && drive.mediaRemovable()) { + result.dbus_path = path.path(); + result.dbus_drive_path = block.drive().path(); + + result.serial = drive.serial(); + result.vendor = drive.vendor(); + result.model = drive.model(); + + result.label = block.idLabel(); + result.uuid = block.idUUID(); + result.capacity = drive.size(); + + if (!result.label.isEmpty()) + result.friendly_name = result.label; + else + result.friendly_name = result.model + " " + result.uuid; + + for (const auto &path : filesystem.mountPoints()) + result.mount_paths.push_back(path); + + result.free_space = Utilities::FileSystemFreeSpace(result.mount_paths.at(0)); + } + } + + return result; + +} + +QString Udisks2Lister::PartitionData::unique_id() const { + return QString("Udisks2/%1/%2/%3/%4/%5") + .arg(serial, vendor, model) + .arg(capacity) + .arg(uuid); +} + +Udisks2Lister::Udisks2Job::Udisks2Job() : is_mount(true) {} + +Udisks2Lister::PartitionData::PartitionData() : capacity(0), free_space(0) {} diff --git a/src/device/udisks2lister.h b/src/device/udisks2lister.h new file mode 100644 index 00000000..4a7fe4a2 --- /dev/null +++ b/src/device/udisks2lister.h @@ -0,0 +1,118 @@ +/* This file is part of Clementine. + Copyright 2016, Valeriy Malov + + 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 UDISKS2LISTER_H +#define UDISKS2LISTER_H + +#include "config.h" + +#include + +#include +#include +#include +#include + +#include "dbus/metatypes.h" +#include "devicelister.h" + +class OrgFreedesktopDBusObjectManagerInterface; +class OrgFreedesktopUDisks2JobInterface; + +class Udisks2Lister : public DeviceLister { + Q_OBJECT + + public: + Udisks2Lister(); + ~Udisks2Lister(); + + QStringList DeviceUniqueIDs() override; + QVariantList DeviceIcons(const QString &id) override; + QString DeviceManufacturer(const QString &id) override; + QString DeviceModel(const QString &id) override; + quint64 DeviceCapacity(const QString &id) override; + quint64 DeviceFreeSpace(const QString &id) override; + QVariantMap DeviceHardwareInfo(const QString &id) override; + + QString MakeFriendlyName(const QString &id) override; + QList MakeDeviceUrls(const QString &id) override; + + void UnmountDevice(const QString &id) override; + + public slots: + void UpdateDeviceFreeSpace(const QString &id) override; + + protected: + void Init() override; + + private slots: + void DBusInterfaceAdded(const QDBusObjectPath &path, const InterfacesAndProperties &ifaces); + void DBusInterfaceRemoved(const QDBusObjectPath &path, const QStringList &ifaces); + void JobCompleted(bool success, const QString &message); + + private: + bool isPendingJob(const QDBusObjectPath &job_path); + void RemoveDevice(const QDBusObjectPath &device_path); + QList GetMountedPartitionsFromDBusArgument( const QDBusArgument &input); + + struct Udisks2Job { + Udisks2Job(); + bool is_mount; + QList mounted_partitions; + std::shared_ptr dbus_interface; + }; + + QMutex jobs_lock_; + QMap mounting_jobs_; + + private: + struct PartitionData { + PartitionData(); + + QString unique_id() const; + + QString dbus_path; + QString friendly_name; + + // Device + QString serial; + QString vendor; + QString model; + quint64 capacity; + QString dbus_drive_path; + + // Paritition + QString label; + QString uuid; + quint64 free_space; + QStringList mount_paths; + }; + + PartitionData ReadPartitionData(const QDBusObjectPath &path); + void HandleFinishedMountJob(const Udisks2Lister::PartitionData &partition_data); + void HandleFinishedUnmountJob(const Udisks2Lister::PartitionData &partition_data, const QDBusObjectPath &mounted_object); + + QReadWriteLock device_data_lock_; + QMap device_data_; + + private: + std::unique_ptr udisks2_interface_; + + static constexpr char udisks2_service_[] = "org.freedesktop.UDisks2"; +}; + +#endif // UDISKS2LISTER_H diff --git a/src/dialogs/about.cpp b/src/dialogs/about.cpp new file mode 100644 index 00000000..2829a347 --- /dev/null +++ b/src/dialogs/about.cpp @@ -0,0 +1,119 @@ +/* + * Strawberry Music Player + * Copyright 2013, Jonas Kvinge + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "about.h" +#include "ui_about.h" + +#include +#include + +About::About(QWidget *parent):QDialog(parent) { + + authors_ \ + << Person("Jonas Kvinge", "jonas@strawbs.net"); + + clementine_authors_ + << Person("David Sansome", "me@davidsansome.com") + << Person("John Maguire", "john.maguire@gmail.com") + << Person(QString::fromUtf8("Paweł Bara"), "keirangtp@gmail.com") + << Person("Arnaud Bienner", "arnaud.bienner@gmail.com"); + + thanks_to_ \ + << Person("Mark Kretschmann", "kretschmann@kde.org") + << Person("Max Howell", "max.howell@methylblue.com") + << Person(QString::fromUtf8("Bartłomiej Burdukiewicz"), "dev.strikeu@gmail.com") + << Person("Jakub Stachowski", "qbast@go2.pl") + << Person("Paul Cifarelli", "paul@cifarelli.net") + << Person("Felipe Rivera", "liebremx@users.sourceforge.net") + << Person("Alexander Peitz") + << Person("Artur Rona", "artur.rona@gmail.com") + << Person("Andreas Muttscheller", "asfa194@gmail.com"); + + QString Title = ""; + + ui_.setupUi(this); + setWindowFlags(this->windowFlags()|Qt::WindowStaysOnTopHint); + setWindowTitle(tr("About Strawberry")); + + Title = QString("About Strawberry"); + + ui_.title->setText(Title); + + QFont title_font; + title_font.setBold(true); + title_font.setPointSize(title_font.pointSize() + 4); + ui_.title->setFont(title_font); + + + + ui_.text->setWordWrap(true); + ui_.text->setText(MakeHtml()); + + ui_.buttonBox->button(QDialogButtonBox::Close)->setShortcut(QKeySequence::Close); + +} + +QString About::MakeHtml() const { + + QString ret = ""; + + ret = tr("

Version %1

").arg(QCoreApplication::applicationVersion()); + + ret += tr("

"); + ret += tr("Strawberry is a fork of Clementine created in 2013, it's written in C++ and Qt5. So far it works on Linux, it is currently untested on Mac OS X and Windows.
"); + ret += tr("The main goal was to create a player for playing local music files that looked a bit more like Amarok 1.4."); + ret += tr("

"); + + //ret += tr("

%2

%3:").arg(kUrl, kUrl, tr("Authors")); + + ret += QString("

%1").arg(tr("Authors")); + + for (const Person &person : authors_) { + ret += "
" + MakeHtml(person); + } + + ret += QString("

%3:").arg(tr("Clementine Authors")); + + for (const Person &person : clementine_authors_) { + ret += "
" + MakeHtml(person); + } + + ret += QString("

%3:").arg(tr("Thanks to")); + + for (const Person &person : thanks_to_) { + ret += "
" + MakeHtml(person); + } + + ret += QString("
%1

").arg(tr("...and all the Amarok and Clementine contributors")); + + return ret; + +} + +QString About::MakeHtml(const Person &person) const { + + if (person.email.isNull()) + return person.name; + else + return QString("%1 <%3>").arg(person.name, person.email, person.email); +} diff --git a/src/dialogs/about.h b/src/dialogs/about.h new file mode 100644 index 00000000..8074a165 --- /dev/null +++ b/src/dialogs/about.h @@ -0,0 +1,57 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ABOUT_H +#define ABOUT_H + +#include "config.h" + +#include + +#include "ui_about.h" + +class About : public QDialog { + Q_OBJECT + + public: + About(QWidget *parent = nullptr); + + struct Person { + Person(const QString &n, const QString &e = QString()) : name(n), email(e) {} + + bool operator<(const Person& other) const { return name < other.name; } + + QString name; + QString email; + }; + + private: + QString MakeHtml() const; + QString MakeHtml(const Person& person) const; + + private: + Ui::About ui_; + + QList authors_; + QList clementine_authors_; + QList thanks_to_; +}; + +#endif // ABOUT_H diff --git a/src/dialogs/about.ui b/src/dialogs/about.ui new file mode 100644 index 00000000..ec53a0fc --- /dev/null +++ b/src/dialogs/about.ui @@ -0,0 +1,186 @@ + + + About + + + + 0 + 0 + 600 + 650 + + + + + 0 + 0 + + + + + 600 + 650 + + + + + 600 + 650 + + + + + 0 + 0 + + + + Qt::StrongFocus + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + #line { + color: lightgrey; +} + + + + + QLayout::SetDefaultConstraint + + + + + + + + + :/icons/64x64/strawberry.png + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + 2 + 2 + + + + + 1 + 1 + + + + QFrame::Plain + + + Qt::Vertical + + + + + + + + + + 16777215 + 20 + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + + + + + + + buttonBox + accepted() + About + accept() + + + 257 + 460 + + + 157 + 274 + + + + + buttonBox + rejected() + About + reject() + + + 325 + 460 + + + 286 + 274 + + + + + diff --git a/src/dialogs/console.cpp b/src/dialogs/console.cpp new file mode 100644 index 00000000..f99a1d0a --- /dev/null +++ b/src/dialogs/console.cpp @@ -0,0 +1,71 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "console.h" + +#include "core/application.h" +#include "core/database.h" + +Console::Console(Application *app, QWidget *parent) : QDialog(parent), app_(app) { + + ui_.setupUi(this); + connect(ui_.run, SIGNAL(clicked()), SLOT(RunQuery())); + + QFont font("Monospace"); + font.setStyleHint(QFont::TypeWriter); + + ui_.output->setFont(font); + ui_.query->setFont(font); + +} + +void Console::RunQuery() { + + QSqlDatabase db = app_->database()->Connect(); + QSqlQuery query = db.exec(ui_.query->text()); + //ui_.query->clear(); + + ui_.output->append("> " + query.executedQuery() + ""); + + query.next(); + + while (query.isValid()) { + QSqlRecord record = query.record(); + QStringList values; + for (int i = 0; i < record.count(); ++i) { + values.append(record.value(i).toString()); + } + + ui_.output->append(values.join("|")); + + query.next(); + } + + ui_.output->verticalScrollBar()->setValue(ui_.output->verticalScrollBar()->maximum()); + +} diff --git a/src/dialogs/console.h b/src/dialogs/console.h new file mode 100644 index 00000000..097da219 --- /dev/null +++ b/src/dialogs/console.h @@ -0,0 +1,45 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef CONSOLE_H +#define CONSOLE_H + +#include "config.h" + +#include + +#include "ui_console.h" + +class Application; + +class Console : public QDialog { + Q_OBJECT + public: + Console(Application *app, QWidget *parent = nullptr); + + private slots: + void RunQuery(); + + private: + Ui::Console ui_; + Application *app_; +}; + +#endif // CONSOLE_H diff --git a/src/dialogs/console.ui b/src/dialogs/console.ui new file mode 100644 index 00000000..7666ebaa --- /dev/null +++ b/src/dialogs/console.ui @@ -0,0 +1,47 @@ + + + Console + + + + 0 + 0 + 545 + 347 + + + + Console + + + + + + + + + + + + + + + + Run + + + + + + + + + + + query + run + output + + + + diff --git a/src/dialogs/edittagdialog.cpp b/src/dialogs/edittagdialog.cpp new file mode 100644 index 00000000..7be31800 --- /dev/null +++ b/src/dialogs/edittagdialog.cpp @@ -0,0 +1,910 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "edittagdialog.h" +#include "trackselectiondialog.h" +#include "ui_edittagdialog.h" +#include "core/application.h" +#include "core/logging.h" +#include "core/tagreaderclient.h" +#include "core/utilities.h" +#include "collection/collection.h" +#include "collection/collectionbackend.h" +#include "playlist/playlistdelegates.h" +#include "covermanager/albumcovermanager.h" +#include "covermanager/albumcoverloader.h" +#include "covermanager/coverproviders.h" +#include "covermanager/albumcoverchoicecontroller.h" +#include "covermanager/coverfromurldialog.h" + +const char *EditTagDialog::kHintText = QT_TR_NOOP("(different across multiple songs)"); +const char *EditTagDialog::kSettingsGroup = "EditTagDialog"; + +EditTagDialog::EditTagDialog(Application *app, QWidget *parent) + : QDialog(parent), + ui_(new Ui_EditTagDialog), + app_(app), + album_cover_choice_controller_(new AlbumCoverChoiceController(this)), + loading_(false), + ignore_edits_(false), +#ifdef HAVE_GSTREAMER + tag_fetcher_(new TagFetcher(this)), +#endif + cover_art_id_(0), + cover_art_is_set_(false), + results_dialog_(new TrackSelectionDialog(this)) +{ + +// qLog(Debug) << __PRETTY_FUNCTION__; + + //QIcon nocover = IconLoader::Load("nocover"); + //cover_options_.default_output_image_ = AlbumCoverLoader::ScaleAndPad(cover_options_, nocover.pixmap(nocover.availableSizes().last()).toImage()); + cover_options_.default_output_image_ = AlbumCoverLoader::ScaleAndPad(cover_options_, QImage(":/pictures/noalbumart.png")); + + connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64,QImage,QImage)), SLOT(ArtLoaded(quint64,QImage,QImage))); + +#ifdef HAVE_GSTREAMER + connect(tag_fetcher_, SIGNAL(ResultAvailable(Song, SongList)), results_dialog_, SLOT(FetchTagFinished(Song, SongList)), Qt::QueuedConnection); + connect(tag_fetcher_, SIGNAL(Progress(Song,QString)), results_dialog_, SLOT(FetchTagProgress(Song,QString))); + connect(results_dialog_, SIGNAL(SongChosen(Song, Song)), SLOT(FetchTagSongChosen(Song, Song))); + connect(results_dialog_, SIGNAL(finished(int)), tag_fetcher_, SLOT(Cancel())); +#endif + + album_cover_choice_controller_->SetApplication(app_); + + ui_->setupUi(this); + ui_->splitter->setSizes(QList() << 200 << width() - 200); + ui_->loading_label->hide(); + + ui_->fetch_tag->setIcon(QPixmap::fromImage(QImage(":/pictures/musicbrainz.png"))); + + // An editable field is one that has a label as a buddy. The label is + // important because it gets turned bold when the field is changed. + for (QLabel *label : findChildren()) { + QWidget *widget = label->buddy(); + if (widget) { + // Store information about the field + fields_ << FieldData(label, widget, widget->objectName()); + + // Connect the Reset signal + if (dynamic_cast(widget)) { + connect(widget, SIGNAL(Reset()), SLOT(ResetField())); + } + + // Connect the edited signal + if (qobject_cast(widget)) { + connect(widget, SIGNAL(textChanged(QString)), SLOT(FieldValueEdited())); + } + else if (qobject_cast(widget)) { + connect(widget, SIGNAL(textChanged()), SLOT(FieldValueEdited())); + } + else if (qobject_cast(widget)) { + connect(widget, SIGNAL(valueChanged(int)), SLOT(FieldValueEdited())); + } + } + } + + // Set the colour of all the labels on the summary page + const bool light = palette().color(QPalette::Base).value() > 128; + const QColor color = palette().color(QPalette::WindowText); + QPalette summary_label_palette(palette()); + summary_label_palette.setColor(QPalette::WindowText, light ? color.lighter(150) : color.darker(150)); + + for (QLabel *label : ui_->summary_tab->findChildren()) { + if (label->property("field_label").toBool()) { + label->setPalette(summary_label_palette); + } + } + + // Pretend the summary text is just a label + ui_->summary->setMaximumHeight(ui_->art->height() - ui_->summary_art_button->height() - 4); + + connect(ui_->song_list->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(SelectionChanged())); + connect(ui_->button_box, SIGNAL(clicked(QAbstractButton*)), SLOT(ButtonClicked(QAbstractButton*))); + //connect(ui_->rating, SIGNAL(RatingChanged(float)), SLOT(SongRated(float))); + connect(ui_->playcount_reset, SIGNAL(clicked()), SLOT(ResetPlayCounts())); + connect(ui_->fetch_tag, SIGNAL(clicked()), SLOT(FetchTag())); + + // Set up the album cover menu + cover_menu_ = new QMenu(this); + + QList actions = album_cover_choice_controller_->GetAllActions(); + + connect(album_cover_choice_controller_->cover_from_file_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromFile())); + connect(album_cover_choice_controller_->cover_to_file_action(), SIGNAL(triggered()), this, SLOT(SaveCoverToFile())); + connect(album_cover_choice_controller_->cover_from_url_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromURL())); + connect(album_cover_choice_controller_->search_for_cover_action(), SIGNAL(triggered()), this, SLOT(SearchForCover())); + connect(album_cover_choice_controller_->unset_cover_action(), SIGNAL(triggered()), this, SLOT(UnsetCover())); + connect(album_cover_choice_controller_->show_cover_action(), SIGNAL(triggered()), this, SLOT(ShowCover())); + + cover_menu_->addActions(actions); + + ui_->summary_art_button->setMenu(cover_menu_); + + ui_->art->installEventFilter(this); + ui_->art->setAcceptDrops(true); + + // Add the next/previous buttons + previous_button_ = new QPushButton(IconLoader::Load("go-previous"), tr("Previous"), this); + next_button_ = new QPushButton(IconLoader::Load("go-next"), tr("Next"), this); + ui_->button_box->addButton(previous_button_, QDialogButtonBox::ResetRole); + ui_->button_box->addButton(next_button_, QDialogButtonBox::ResetRole); + + connect(previous_button_, SIGNAL(clicked()), SLOT(PreviousSong())); + connect(next_button_, SIGNAL(clicked()), SLOT(NextSong())); + + // Set some shortcuts for the buttons + new QShortcut(QKeySequence::Back, previous_button_, SLOT(click())); + new QShortcut(QKeySequence::Forward, next_button_, SLOT(click())); + new QShortcut(QKeySequence::MoveToPreviousPage, previous_button_, SLOT(click())); + new QShortcut(QKeySequence::MoveToNextPage, next_button_, SLOT(click())); + + // Show the shortcuts as tooltips + previous_button_->setToolTip(QString("%1 (%2 / %3)").arg( + previous_button_->text(), + QKeySequence(QKeySequence::Back).toString(QKeySequence::NativeText), + QKeySequence(QKeySequence::MoveToPreviousPage).toString(QKeySequence::NativeText))); + next_button_->setToolTip(QString("%1 (%2 / %3)").arg( + next_button_->text(), + QKeySequence(QKeySequence::Forward).toString(QKeySequence::NativeText), + QKeySequence(QKeySequence::MoveToNextPage).toString(QKeySequence::NativeText))); + + new TagCompleter(app_->collection_backend(), Playlist::Column_Artist, ui_->artist); + new TagCompleter(app_->collection_backend(), Playlist::Column_Album, ui_->album); + new TagCompleter(app_->collection_backend(), Playlist::Column_AlbumArtist, ui_->albumartist); + new TagCompleter(app_->collection_backend(), Playlist::Column_Genre, ui_->genre); + new TagCompleter(app_->collection_backend(), Playlist::Column_Composer, ui_->composer); + new TagCompleter(app_->collection_backend(), Playlist::Column_Performer, ui_->performer); + new TagCompleter(app_->collection_backend(), Playlist::Column_Grouping, ui_->grouping); + +} + +EditTagDialog::~EditTagDialog() { + delete ui_; +} + +bool EditTagDialog::SetLoading(const QString &message) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + const bool loading = !message.isEmpty(); + if (loading == loading_) return false; + loading_ = loading; + + ui_->button_box->setEnabled(!loading); + ui_->tab_widget->setEnabled(!loading); + ui_->song_list->setEnabled(!loading); + ui_->fetch_tag->setEnabled(!loading); + ui_->loading_label->setVisible(loading); + ui_->loading_label->set_text(message); + return true; +} + +QList EditTagDialog::LoadData(const SongList &songs) const { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + QList ret; + + for (const Song &song : songs) { + if (song.IsEditable()) { + // Try reloading the tags from file + Song copy(song); + TagReaderClient::Instance()->ReadFileBlocking(copy.url().toLocalFile(), ©); + + if (copy.is_valid()) { + copy.MergeUserSetData(song); + ret << Data(copy); + } + } + } + + return ret; +} + +void EditTagDialog::SetSongs(const SongList &s, const PlaylistItemList &items) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + // Show the loading indicator + if (!SetLoading(tr("Loading tracks") + "...")) return; + + data_.clear(); + playlist_items_ = items; + ui_->song_list->clear(); + + // Reload tags in the background + QFuture> future = QtConcurrent::run(this, &EditTagDialog::LoadData, s); + NewClosure(future, this, SLOT(SetSongsFinished(QFuture>)), future); +} + +void EditTagDialog::SetSongsFinished(QFuture> future) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + if (!SetLoading(QString())) return; + + data_ = future.result(); + if (data_.count() == 0) { + // If there were no valid songs, disable everything + ui_->song_list->setEnabled(false); + ui_->tab_widget->setEnabled(false); + + // Show a summary with empty information + UpdateSummaryTab(Song()); + ui_->tab_widget->setCurrentWidget(ui_->summary_tab); + + SetSongListVisibility(false); + return; + } + + // Add the filenames to the list + for (const Data &data : data_) { + ui_->song_list->addItem(data.current_.basefilename()); + } + + // Select all + ui_->song_list->setCurrentRow(0); + ui_->song_list->selectAll(); + + // Hide the list if there's only one song in it + SetSongListVisibility(data_.count() != 1); +} + +void EditTagDialog::SetSongListVisibility(bool visible) { + ui_->song_list->setVisible(visible); + previous_button_->setEnabled(visible); + next_button_->setEnabled(visible); +} + +QVariant EditTagDialog::Data::value(const Song &song, const QString &id) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + if (id == "title") return song.title(); + if (id == "artist") return song.artist(); + if (id == "album") return song.album(); + if (id == "albumartist") return song.albumartist(); + if (id == "composer") return song.composer(); + if (id == "performer") return song.performer(); + if (id == "grouping") return song.grouping(); + if (id == "genre") return song.genre(); + if (id == "comment") return song.comment(); + if (id == "track") return song.track(); + if (id == "disc") return song.disc(); + if (id == "year") return song.year(); + qLog(Warning) << "Unknown ID" << id; + return QVariant(); + +} + +void EditTagDialog::Data::set_value(const QString &id, const QVariant &value) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + if (id == "title") current_.set_title(value.toString()); + else if (id == "artist") current_.set_artist(value.toString()); + else if (id == "album") current_.set_album(value.toString()); + else if (id == "albumartist") current_.set_albumartist(value.toString()); + else if (id == "composer") current_.set_composer(value.toString()); + else if (id == "performer") current_.set_performer(value.toString()); + else if (id == "grouping") current_.set_grouping(value.toString()); + else if (id == "genre") current_.set_genre(value.toString()); + else if (id == "comment") current_.set_comment(value.toString()); + else if (id == "track") current_.set_track(value.toInt()); + else if (id == "disc") current_.set_disc(value.toInt()); + else if (id == "year") current_.set_year(value.toInt()); + else qLog(Warning) << "Unknown ID" << id; + +} + +bool EditTagDialog::DoesValueVary(const QModelIndexList &sel, const QString &id) const { + QVariant value = data_[sel.first().row()].current_value(id); + for (int i = 1; i < sel.count(); ++i) { + if (value != data_[sel[i].row()].current_value(id)) return true; + } + return false; +} + +bool EditTagDialog::IsValueModified(const QModelIndexList &sel, const QString &id) const { + + for (const QModelIndex &i : sel) { + if (data_[i.row()].original_value(id) != data_[i.row()].current_value(id)) + return true; + } + return false; +} + +void EditTagDialog::InitFieldValue(const FieldData &field, const QModelIndexList &sel) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + const bool varies = DoesValueVary(sel, field.id_); +// const bool modified = IsValueModified(sel, field.id_); + + if (ExtendedEditor *editor = dynamic_cast(field.editor_)) { + editor->clear(); + editor->clear_hint(); + if (varies) { + editor->set_hint(tr(EditTagDialog::kHintText)); + } + else { + editor->set_text(data_[sel[0].row()].current_value(field.id_).toString()); + } + } + + UpdateModifiedField(field, sel); +} + +void EditTagDialog::UpdateFieldValue(const FieldData &field, const QModelIndexList &sel) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + // Get the value from the field + QVariant value; + if (ExtendedEditor *editor = dynamic_cast(field.editor_)) { + value = editor->text(); + } + + // Did we get it? + if (!value.isValid()) { + return; + } + + // Set it in each selected song + for (const QModelIndex &i : sel) { + data_[i.row()].set_value(field.id_, value); + } + + UpdateModifiedField(field, sel); +} + +void EditTagDialog::UpdateModifiedField(const FieldData &field, const QModelIndexList &sel) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + const bool modified = IsValueModified(sel, field.id_); + + // Update the boldness + QFont new_font(font()); + new_font.setBold(modified); + field.label_->setFont(new_font); + field.editor_->setFont(new_font); +} + +void EditTagDialog::ResetFieldValue(const FieldData &field, const QModelIndexList &sel) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + // Reset each selected song + for (const QModelIndex &i : sel) { + Data &data = data_[i.row()]; + data.set_value(field.id_, data.original_value(field.id_)); + } + + // Reset the field + InitFieldValue(field, sel); +} + +void EditTagDialog::SelectionChanged() { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes(); + if (sel.isEmpty()) + return; + + // Set the editable fields + UpdateUI(sel); + + // If we're editing multiple songs then we have to disable certain tabs + const bool multiple = sel.count() > 1; + ui_->tab_widget->setTabEnabled(ui_->tab_widget->indexOf(ui_->summary_tab), !multiple); + + if (!multiple) { + const Song &song = data_[sel.first().row()].original_; + UpdateSummaryTab(song); + UpdateStatisticsTab(song); + } +} + +void EditTagDialog::UpdateUI(const QModelIndexList &sel){ + +// qLog(Debug) << __PRETTY_FUNCTION__; + + ignore_edits_ = true; + for (const FieldData &field : fields_) { + InitFieldValue(field, sel); + } + ignore_edits_ = false; +} + +static void SetText(QLabel *label, int value, const QString &suffix, const QString &def = QString()) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + label->setText(value <= 0 ? def : (QString::number(value) + " " + suffix)); +} + +static void SetDate(QLabel *label, uint time) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + if (time == std::numeric_limits::max()) { // -1 + label->setText(QObject::tr("Unknown")); + } + else { + label->setText(QDateTime::fromTime_t(time).toString(QLocale::system().dateTimeFormat(QLocale::LongFormat))); + } +} + +void EditTagDialog::UpdateSummaryTab(const Song &song) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + cover_art_id_ = app_->album_cover_loader()->LoadImageAsync(cover_options_, song); + + QString summary = "" + song.PrettyTitleWithArtist().toHtmlEscaped() + "
"; + + bool art_is_set = true; + if (song.has_manually_unset_cover()) { + summary += tr("Cover art manually unset").toHtmlEscaped(); + art_is_set = false; + } + else if (!song.art_manual().isEmpty()) { + summary += tr("Cover art set from %1").arg(song.art_manual()).toHtmlEscaped(); + } + else if (song.has_embedded_cover()) { + summary += tr("Cover art from embedded image"); + } + else if (!song.art_automatic().isEmpty()) { + summary += tr("Cover art loaded automatically from %1").arg(song.art_automatic()).toHtmlEscaped(); + } + else { + summary += tr("Cover art not set").toHtmlEscaped(); + art_is_set = false; + } + + ui_->summary->setText(summary); + + album_cover_choice_controller_->unset_cover_action()->setEnabled(art_is_set); + album_cover_choice_controller_->show_cover_action()->setEnabled(art_is_set); + ui_->summary_art_button->setEnabled(song.id() != -1); + + ui_->length->setText(Utilities::PrettyTimeNanosec(song.length_nanosec())); + + SetText(ui_->samplerate, song.samplerate(), "Hz"); + SetText(ui_->bitdepth, song.bitdepth(), "Bit"); + SetText(ui_->bitrate, song.bitrate(), tr("kbps")); + SetDate(ui_->mtime, song.mtime()); + SetDate(ui_->ctime, song.ctime()); + + if (song.filesize() == -1) { + ui_->filesize->setText(tr("Unknown")); + } + else { + ui_->filesize->setText(Utilities::PrettySize(song.filesize())); + } + + ui_->filetype->setText(song.TextForFiletype()); + + if (song.url().scheme() == "file") + ui_->filename->setText(QDir::toNativeSeparators(song.url().toLocalFile())); + else + ui_->filename->setText(song.url().toString()); + + album_cover_choice_controller_->search_for_cover_action()->setEnabled(app_->cover_providers()->HasAnyProviders()); + +} + +void EditTagDialog::UpdateStatisticsTab(const Song &song) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + ui_->playcount->setText(QString::number(qMax(0, song.playcount()))); + ui_->skipcount->setText(QString::number(qMax(0, song.skipcount()))); + + ui_->lastplayed->setText(song.lastplayed() <= 0 ? tr("Never") : QDateTime::fromTime_t(song.lastplayed()).toString(QLocale::system().dateTimeFormat(QLocale::LongFormat))); +} + +void EditTagDialog::ArtLoaded(quint64 id, const QImage &scaled, const QImage &original) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + if (id == cover_art_id_) { + ui_->art->setPixmap(QPixmap::fromImage(scaled)); + original_ = original; + } +} + +void EditTagDialog::FieldValueEdited() { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + if (ignore_edits_) return; + + const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes(); + if (sel.isEmpty()) + return; + + QWidget *w = qobject_cast(sender()); + + // Find the field + for (const FieldData &field : fields_) { + if (field.editor_ == w) { + UpdateFieldValue(field, sel); + return; + } + } +} + +void EditTagDialog::ResetField() { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes(); + if (sel.isEmpty()) + return; + + QWidget *w = qobject_cast(sender()); + + // Find the field + for (const FieldData &field : fields_) { + if (field.editor_ == w) { + ignore_edits_ = true; + ResetFieldValue(field, sel); + ignore_edits_ = false; + return; + } + } +} + +Song *EditTagDialog::GetFirstSelected() { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes(); + if (sel.isEmpty()) return nullptr; + return &data_[sel.first().row()].original_; +} + +void EditTagDialog::LoadCoverFromFile() { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + Song *song = GetFirstSelected(); + if (!song) return; + + const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes(); + + QString cover = album_cover_choice_controller_->LoadCoverFromFile(song); + + if (!cover.isEmpty()) UpdateCoverOf(*song, sel, cover); +} + +void EditTagDialog::SaveCoverToFile() { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + Song *song = GetFirstSelected(); + if (!song) return; + + album_cover_choice_controller_->SaveCoverToFile(*song, original_); +} + +void EditTagDialog::LoadCoverFromURL() { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + Song *song = GetFirstSelected(); + if (!song) return; + + const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes(); + + QString cover = album_cover_choice_controller_->LoadCoverFromURL(song); + + if (!cover.isEmpty()) UpdateCoverOf(*song, sel, cover); +} + +void EditTagDialog::SearchForCover() { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + Song *song = GetFirstSelected(); + if (!song) return; + + const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes(); + + QString cover = album_cover_choice_controller_->SearchForCover(song); + + if (!cover.isEmpty()) UpdateCoverOf(*song, sel, cover); +} + +void EditTagDialog::UnsetCover() { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + Song *song = GetFirstSelected(); + if (!song) return; + + const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes(); + + QString cover = album_cover_choice_controller_->UnsetCover(song); + UpdateCoverOf(*song, sel, cover); +} + +void EditTagDialog::ShowCover() { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + Song *song = GetFirstSelected(); + if (!song) { + return; + } + + album_cover_choice_controller_->ShowCover(*song); +} + +void EditTagDialog::UpdateCoverOf(const Song &selected, const QModelIndexList &sel, const QString &cover) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + if (!selected.is_valid() || selected.id() == -1) return; + + UpdateSummaryTab(selected); + + // Now check if we have any other songs cached that share that artist and + // album (and would therefore be changed as well) + for (int i = 0; i < data_.count(); ++i) { + if (i == sel.first().row()) // Already changed this one + continue; + + Song *other_song = &data_[i].original_; + if (selected.artist() == other_song->artist() && selected.album() == other_song->album()) { + other_song->set_art_manual(cover); + } + } + +} + +void EditTagDialog::NextSong() { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + if (ui_->song_list->count() == 0) { + return; + } + + int row = (ui_->song_list->currentRow() + 1) % ui_->song_list->count(); + ui_->song_list->setCurrentRow(row); +} + +void EditTagDialog::PreviousSong() { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + if (ui_->song_list->count() == 0) { + return; + } + + int row = (ui_->song_list->currentRow() - 1 + ui_->song_list->count()) % ui_->song_list->count(); + ui_->song_list->setCurrentRow(row); +} + +void EditTagDialog::ButtonClicked(QAbstractButton *button) { + if (button == ui_->button_box->button(QDialogButtonBox::Discard)) { + reject(); + } +} + +void EditTagDialog::SaveData(const QList &data) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + for (int i = 0; i < data.count(); ++i) { + const Data &ref = data[i]; + if (ref.current_.IsMetadataEqual(ref.original_)) continue; + + if (!TagReaderClient::Instance()->SaveFileBlocking(ref.current_.url().toLocalFile(), ref.current_)) { + emit Error(tr("An error occurred writing metadata to '%1'").arg(ref.current_.url().toLocalFile())); + } + } +} + +void EditTagDialog::accept() { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + // Show the loading indicator + if (!SetLoading(tr("Saving tracks") + "...")) return; + + // Save tags in the background + QFuture future = QtConcurrent::run(this, &EditTagDialog::SaveData, data_); + NewClosure(future, this, SLOT(AcceptFinished())); +} + +void EditTagDialog::AcceptFinished() { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + if (!SetLoading(QString())) return; + QDialog::accept(); + +} + +bool EditTagDialog::eventFilter(QObject *o, QEvent *e) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + if (o == ui_->art) { + switch (e->type()) { + case QEvent::MouseButtonRelease: + cover_menu_->popup(static_cast(e)->globalPos()); + break; + + case QEvent::DragEnter: { + QDragEnterEvent *event = static_cast(e); + if (AlbumCoverChoiceController::CanAcceptDrag(event)) { + event->acceptProposedAction(); + } + break; + } + + case QEvent::Drop: { + const QDropEvent *event = static_cast(e); + const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes(); + Song *song = GetFirstSelected(); + + const QString cover = album_cover_choice_controller_->SaveCover(song, event); + if (!cover.isEmpty()) { + UpdateCoverOf(*song, sel, cover); + } + + break; + } + + default: + break; + } + } + return false; +} + +void EditTagDialog::showEvent(QShowEvent *e) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + // Set the dialog's height to the smallest possible + resize(width(), sizeHint().height()); + + // Restore the tab that was current last time. + QSettings s; + s.beginGroup(kSettingsGroup); + ui_->tab_widget->setCurrentIndex(s.value("current_tab").toInt()); + + QDialog::showEvent(e); +} + +void EditTagDialog::hideEvent(QHideEvent *e) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + // Save the current tab + QSettings s; + s.beginGroup(kSettingsGroup); + s.setValue("current_tab", ui_->tab_widget->currentIndex()); + + QDialog::hideEvent(e); +} + +void EditTagDialog::ResetPlayCounts() { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes(); + if (sel.isEmpty()) + return; + Song *song = &data_[sel.first().row()].original_; + if (!song->is_valid() || song->id() == -1) return; + + if (QMessageBox::question(this, tr("Reset play counts"), tr("Are you sure you want to reset this song's statistics?"), QMessageBox::Reset, QMessageBox::Cancel) != QMessageBox::Reset) { + return; + } + + song->set_playcount(0); + song->set_skipcount(0); + song->set_lastplayed(-1); + + app_->collection_backend()->ResetStatisticsAsync(song->id()); + UpdateStatisticsTab(*song); +} + +#ifdef HAVE_GSTREAMER +void EditTagDialog::FetchTag() { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes(); + + SongList songs; + + for (const QModelIndex &index : sel) { + Song song = data_[index.row()].original_; + if (!song.is_valid()) { + continue; + } + + songs << song; + } + + if (songs.isEmpty()) return; + + results_dialog_->Init(songs); + tag_fetcher_->StartFetch(songs); + + results_dialog_->show(); +} + +void EditTagDialog::FetchTagSongChosen(const Song &original_song, const Song &new_metadata) { + +// qLog(Debug) << __PRETTY_FUNCTION__; + + const QString filename = original_song.url().toLocalFile(); + + // Find the song with this filename + auto data_it = + std::find_if(data_.begin(), data_.end(), [&filename](const Data &d) { + return d.original_.url().toLocalFile() == filename; + }); + if (data_it == data_.end()) { + qLog(Warning) << "Could not find song to filename: " << filename; + return; + } + + // Update song data + data_it->current_.set_title(new_metadata.title()); + data_it->current_.set_artist(new_metadata.artist()); + data_it->current_.set_album(new_metadata.album()); + data_it->current_.set_track(new_metadata.track()); + data_it->current_.set_year(new_metadata.year()); + + // Is it currently being displayed in the UI? + if (ui_->song_list->currentRow() == std::distance(data_.begin(), data_it)) { + // Yes! Additionally update UI + const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes(); + UpdateUI(sel); + } +} +#endif diff --git a/src/dialogs/edittagdialog.h b/src/dialogs/edittagdialog.h new file mode 100644 index 00000000..8d1eba9b --- /dev/null +++ b/src/dialogs/edittagdialog.h @@ -0,0 +1,180 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef EDITTAGDIALOG_H +#define EDITTAGDIALOG_H + +#include "config.h" + +#include +#include + +#include "core/song.h" +#include "covermanager/albumcoverloaderoptions.h" +#include "musicbrainz/tagfetcher.h" +#include "playlist/playlistitem.h" +#include "widgets/lineedit.h" +#include "trackselectiondialog.h" + +class Application; +class AlbumCoverChoiceController; +class CollectionBackend; +class Ui_EditTagDialog; + +class QAbstractButton; +class QItemSelection; +class QLabel; +class QPushButton; + +class EditTagDialog : public QDialog { + Q_OBJECT + + public: + EditTagDialog(Application *app, QWidget *parent = nullptr); + ~EditTagDialog(); + + static const char *kHintText; + static const char *kSettingsGroup; + + void SetSongs(const SongList &songs, const PlaylistItemList &items = PlaylistItemList()); + + PlaylistItemList playlist_items() const { return playlist_items_; } + + void accept(); + +signals: + void Error(const QString &message); + +protected: + bool eventFilter(QObject *o, QEvent *e); + void showEvent(QShowEvent*); + void hideEvent(QHideEvent*); + + private: + struct Data { + Data(const Song &song = Song()) : original_(song), current_(song) {} + + static QVariant value(const Song &song, const QString &id); + QVariant original_value(const QString &id) const { + return value(original_, id); + } + QVariant current_value(const QString &id) const { + return value(current_, id); + } + + void set_value(const QString &id, const QVariant &value); + + Song original_; + Song current_; + }; + + private slots: + void SetSongsFinished(QFuture> future); + void AcceptFinished(); + + void SelectionChanged(); + void FieldValueEdited(); + void ResetField(); + void ButtonClicked(QAbstractButton *button); + void ResetPlayCounts(); +#ifdef HAVE_GSTREAMER + void FetchTag(); + void FetchTagSongChosen(const Song &original_song, const Song &new_metadata); +#endif + + void ArtLoaded(quint64 id, const QImage &scaled, const QImage &original); + + void LoadCoverFromFile(); + void SaveCoverToFile(); + void LoadCoverFromURL(); + void SearchForCover(); + void UnsetCover(); + void ShowCover(); + + void PreviousSong(); + void NextSong(); + + private: + struct FieldData { + FieldData(QLabel *label = nullptr, QWidget *editor = nullptr, const QString &id = QString()) + : label_(label), editor_(editor), id_(id) {} + + QLabel *label_; + QWidget *editor_; + QString id_; + }; + + Song *GetFirstSelected(); + void UpdateCoverOf(const Song &selected, const QModelIndexList &sel, const QString &cover); + + bool DoesValueVary(const QModelIndexList &sel, const QString &id) const; + bool IsValueModified(const QModelIndexList &sel, const QString &id) const; + + void InitFieldValue(const FieldData &field, const QModelIndexList &sel); + void UpdateFieldValue(const FieldData &field, const QModelIndexList &sel); + void UpdateModifiedField(const FieldData &field, const QModelIndexList &sel); + void ResetFieldValue(const FieldData &field, const QModelIndexList &sel); + + void UpdateSummaryTab(const Song &song); + void UpdateStatisticsTab(const Song &song); + + void UpdateUI(const QModelIndexList &sel); + + bool SetLoading(const QString &message); + void SetSongListVisibility(bool visible); + + // Called by QtConcurrentRun + QList LoadData(const SongList &songs) const; + void SaveData(const QList &data); + +private: + Ui_EditTagDialog *ui_; + + Application *app_; + AlbumCoverChoiceController *album_cover_choice_controller_; + + bool loading_; + + PlaylistItemList playlist_items_; + QList data_; + QList fields_; + + bool ignore_edits_; + +#ifdef HAVE_GSTREAMER + TagFetcher *tag_fetcher_; +#endif + + AlbumCoverLoaderOptions cover_options_; + quint64 cover_art_id_; + bool cover_art_is_set_; + + // A copy of the original, unscaled album cover. + QImage original_; + + QMenu *cover_menu_; + + QPushButton *previous_button_; + QPushButton *next_button_; + + TrackSelectionDialog *results_dialog_; +}; + +#endif // EDITTAGDIALOG_H diff --git a/src/dialogs/edittagdialog.ui b/src/dialogs/edittagdialog.ui new file mode 100644 index 00000000..a3c75a19 --- /dev/null +++ b/src/dialogs/edittagdialog.ui @@ -0,0 +1,917 @@ + + + EditTagDialog + + + + 0 + 0 + 863 + 635 + + + + Edit track information + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + + + + Qt::Horizontal + + + + QAbstractItemView::ExtendedSelection + + + + + 0 + + + + Summary + + + + + + + + + 0 + 0 + + + + + 124 + 124 + + + + QFrame::StyledPanel + + + + + + Qt::AlignCenter + + + 2 + + + + + + + + + + 0 + 0 + + + + QTextEdit { + background: transparent; +} + + + QFrame::NoFrame + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + 0 + 0 + + + + Change cover art + + + + + + + + 0 + 0 + + + + Reset play counts + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + Qt::Horizontal + + + + + + + 18 + + + + + + 0 + 0 + + + + Length + + + true + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + + 150 + 0 + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + Play count + + + true + + + + + + + + 0 + 0 + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + Skip count + + + true + + + + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + Bit rate + + + true + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + Last played + + + true + + + + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + 0 + 0 + + + + Sample rate + + + true + + + + + + + + 0 + 0 + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + 0 + 0 + + + + Bit depth + + + true + + + + + + + + 0 + 0 + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + + + + + + 0 + 0 + + + + File size + + + true + + + + + + + + 0 + 0 + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + File type + + + true + + + + + + + + 0 + 0 + + + + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + Date modified + + + true + + + + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + Date created + + + true + + + + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + File name + + + true + + + + + + + QLineEdit { + background: transparent; +} + + + false + + + true + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Edit tags + + + + + + Title + + + title + + + + + + + true + + + false + + + + + + + Track + + + track + + + + + + + QAbstractSpinBox::CorrectToNearestValue + + + 9999 + + + false + + + true + + + + + + + Artist + + + artist + + + + + + + true + + + false + + + + + + + Disc + + + disc + + + + + + + QAbstractSpinBox::CorrectToNearestValue + + + 9999 + + + false + + + true + + + + + + + Album + + + album + + + + + + + true + + + false + + + + + + + Year + + + year + + + + + + + QAbstractSpinBox::CorrectToNearestValue + + + 9999 + + + false + + + true + + + + + + + Album artist + + + albumartist + + + + + + + true + + + false + + + + + + + Composer + + + composer + + + + + + + true + + + false + + + + + + + Performer + + + performer + + + + + + + true + + + false + + + + + + + Grouping + + + grouping + + + + + + + true + + + false + + + + + + + Genre + + + genre + + + + + + + true + + + false + + + + + + + Complete tags automatically + + + + :/pictures/musicbrainz.png:/pictures/musicbrainz.png + + + + 38 + 22 + + + + + + + + Comment + + + comment + + + + + + + true + + + false + + + + + + + + + + + + + + + QDialogButtonBox::Discard|QDialogButtonBox::Save + + + + + + + + BusyIndicator + QWidget +
widgets/busyindicator.h
+
+ + LineEdit + QLineEdit +
widgets/lineedit.h
+
+ + RatingWidget + QWidget +
widgets/ratingwidget.h
+ 1 +
+ + TextEdit + QTextEdit +
widgets/lineedit.h
+
+ + SpinBox + QSpinBox +
widgets/lineedit.h
+
+
+ + + + + + button_box + accepted() + EditTagDialog + accept() + + + 65 + 552 + + + 84 + 533 + + + + + button_box + rejected() + EditTagDialog + reject() + + + 236 + 542 + + + 242 + 532 + + + + +
diff --git a/src/dialogs/errordialog.cpp b/src/dialogs/errordialog.cpp new file mode 100644 index 00000000..4b6de0e3 --- /dev/null +++ b/src/dialogs/errordialog.cpp @@ -0,0 +1,75 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "errordialog.h" +#include "ui_errordialog.h" + +ErrorDialog::ErrorDialog(QWidget *parent) + : QDialog(parent), + ui_(new Ui_ErrorDialog) +{ + + ui_->setupUi(this); + + QIcon warning_icon(style()->standardIcon(QStyle::SP_MessageBoxWarning)); + QPixmap warning_pixmap(warning_icon.pixmap(48)); + + QPalette messages_palette(ui_->messages->palette()); + messages_palette.setColor(QPalette::Base, messages_palette.color(QPalette::Background)); + + ui_->messages->setPalette(messages_palette); + ui_->icon->setPixmap(warning_pixmap); + +} + +ErrorDialog::~ErrorDialog() { + delete ui_; +} + +void ErrorDialog::ShowMessage(const QString &message) { + + current_messages_ << message; + UpdateContent(); + + show(); + raise(); + activateWindow(); + +} + +void ErrorDialog::hideEvent(QHideEvent *) { + current_messages_.clear(); + UpdateContent(); +} + +void ErrorDialog::UpdateContent() { + + QString html; + for (const QString& message : current_messages_) { + if (!html.isEmpty()) + html += "
"; + html += message.toHtmlEscaped(); + } + ui_->messages->setHtml(html); + +} + diff --git a/src/dialogs/errordialog.h b/src/dialogs/errordialog.h new file mode 100644 index 00000000..d5540e7c --- /dev/null +++ b/src/dialogs/errordialog.h @@ -0,0 +1,52 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ERRORDIALOG_H +#define ERRORDIALOG_H + +#include "config.h" + +#include + +class Ui_ErrorDialog; + +class ErrorDialog : public QDialog { + Q_OBJECT + + public: + ErrorDialog(QWidget *parent = nullptr); + ~ErrorDialog(); + +public slots: + void ShowMessage(const QString& message); + +protected: + void hideEvent(QHideEvent *); + +private: + void UpdateContent(); + + Ui_ErrorDialog *ui_; + + QStringList current_messages_; +}; + +#endif // ERRORDIALOG_H + diff --git a/src/dialogs/errordialog.ui b/src/dialogs/errordialog.ui new file mode 100644 index 00000000..1f7574ac --- /dev/null +++ b/src/dialogs/errordialog.ui @@ -0,0 +1,107 @@ + + + ErrorDialog + + + + 0 + 0 + 411 + 180 + + + + Strawberry Error + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOff + + + true + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + + + buttonBox + accepted() + ErrorDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ErrorDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/dialogs/organisedialog.cpp b/src/dialogs/organisedialog.cpp new file mode 100644 index 00000000..f953d912 --- /dev/null +++ b/src/dialogs/organisedialog.cpp @@ -0,0 +1,382 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "organisedialog.h" +#include "ui_organisedialog.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "organiseerrordialog.h" +#include "core/musicstorage.h" +#include "core/organise.h" +#include "core/tagreaderclient.h" +#include "core/utilities.h" +#include "core/iconloader.h" + +const char *OrganiseDialog::kDefaultFormat = "%artist/%album{ (Disc %disc)}/{%track - }%title.%extension"; +const char *OrganiseDialog::kSettingsGroup = "OrganiseDialog"; + +OrganiseDialog::OrganiseDialog(TaskManager *task_manager, QWidget *parent) + : QDialog(parent), + ui_(new Ui_OrganiseDialog), + task_manager_(task_manager), + total_size_(0), + resized_by_user_(false) { + + ui_->setupUi(this); + connect(ui_->button_box->button(QDialogButtonBox::Reset), SIGNAL(clicked()), SLOT(Reset())); + + ui_->aftercopying->setItemIcon(1, IconLoader::Load("edit-delete")); + + // Valid tags + QMap tags; + tags[tr("Title")] = "title"; + tags[tr("Album")] = "album"; + tags[tr("Artist")] = "artist"; + tags[tr("Artist's initial")] = "artistinitial"; + tags[tr("Album artist")] = "albumartist"; + tags[tr("Composer")] = "composer"; + tags[tr("Performer")] = "performer"; + tags[tr("Grouping")] = "grouping"; + tags[tr("Track")] = "track"; + tags[tr("Disc")] = "disc"; + tags[tr("Year")] = "year"; + tags[tr("Original year")] = "originalyear"; + tags[tr("Genre")] = "genre"; + tags[tr("Comment")] = "comment"; + tags[tr("Length")] = "length"; + tags[tr("Bitrate", "Refers to bitrate in file organise dialog.")] = "bitrate"; + tags[tr("Samplerate")] = "samplerate"; + tags[tr("File extension")] = "extension"; + + // Naming scheme input field + new OrganiseFormat::SyntaxHighlighter(ui_->naming); + + connect(ui_->destination, SIGNAL(currentIndexChanged(int)), SLOT(UpdatePreviews())); + connect(ui_->naming, SIGNAL(textChanged()), SLOT(UpdatePreviews())); + connect(ui_->replace_ascii, SIGNAL(toggled(bool)), SLOT(UpdatePreviews())); + connect(ui_->replace_the, SIGNAL(toggled(bool)), SLOT(UpdatePreviews())); + connect(ui_->replace_spaces, SIGNAL(toggled(bool)), SLOT(UpdatePreviews())); + + // Get the titles of the tags to put in the insert menu + QStringList tag_titles = tags.keys(); + qStableSort(tag_titles); + + // Build the insert menu + QMenu *tag_menu = new QMenu(this); + QSignalMapper *tag_mapper = new QSignalMapper(this); + for (const QString &title : tag_titles) { + QAction *action = tag_menu->addAction(title, tag_mapper, SLOT(map())); + tag_mapper->setMapping(action, tags[title]); + } + + connect(tag_mapper, SIGNAL(mapped(QString)), SLOT(InsertTag(QString))); + ui_->insert->setMenu(tag_menu); +} + +OrganiseDialog::~OrganiseDialog() { + delete ui_; +} + +void OrganiseDialog::SetDestinationModel(QAbstractItemModel *model, bool devices) { + + ui_->destination->setModel(model); + + ui_->eject_after->setVisible(devices); +} + +bool OrganiseDialog::SetSongs(const SongList &songs) { + + total_size_ = 0; + songs_.clear(); + + for (const Song &song : songs) { + if (song.url().scheme() != "file") { + continue; + } + + if (song.filesize() > 0) total_size_ += song.filesize(); + + songs_ << song; + } + + ui_->free_space->set_additional_bytes(total_size_); + UpdatePreviews(); + SetLoadingSongs(false); + + if (songs_future_.isRunning()) { + songs_future_.cancel(); + } + songs_future_ = QFuture(); + + return songs_.count(); + +} + +bool OrganiseDialog::SetUrls(const QList &urls) { + + QStringList filenames; + + // Only add file:// URLs + for (const QUrl &url : urls) { + if (url.scheme() == "file") { + filenames << url.toLocalFile(); + } + } + + return SetFilenames(filenames); + +} + +bool OrganiseDialog::SetFilenames(const QStringList &filenames) { + + songs_future_ = QtConcurrent::run(this, &OrganiseDialog::LoadSongsBlocking, filenames); + NewClosure(songs_future_, [=]() { SetSongs(songs_future_.result()); }); + + SetLoadingSongs(true); + return true; + +} + +void OrganiseDialog::SetLoadingSongs(bool loading) { + + if (loading) { + ui_->preview_stack->setCurrentWidget(ui_->loading_page); + ui_->button_box->button(QDialogButtonBox::Ok)->setEnabled(false); + } + else { + ui_->preview_stack->setCurrentWidget(ui_->preview_page); + // The Ok button is enabled by UpdatePreviews + } + +} + +SongList OrganiseDialog::LoadSongsBlocking(const QStringList &filenames) { + + SongList songs; + Song song; + + QStringList filenames_copy = filenames; + while (!filenames_copy.isEmpty()) { + const QString filename = filenames_copy.takeFirst(); + + // If it's a directory, add all the files inside. + if (QFileInfo(filename).isDir()) { + const QDir dir(filename); + for (const QString &entry : dir.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Readable)) { + filenames_copy << dir.filePath(entry); + } + continue; + } + + TagReaderClient::Instance()->ReadFileBlocking(filename, &song); + if (song.is_valid()) songs << song; + } + + return songs; + +} + +void OrganiseDialog::SetCopy(bool copy) { + ui_->aftercopying->setCurrentIndex(copy ? 0 : 1); +} + +void OrganiseDialog::InsertTag(const QString &tag) { + ui_->naming->insertPlainText("%" + tag); +} + +Organise::NewSongInfoList OrganiseDialog::ComputeNewSongsFilenames(const SongList &songs, const OrganiseFormat &format) { + + // Check if we will have multiple files with the same name. + // If so, they will erase each other if the overwrite flag is set. + // Better to rename them: e.g. foo.bar -> foo(2).bar + QHash filenames; + Organise::NewSongInfoList new_songs_info; + + for (const Song &song : songs) { + QString new_filename = format.GetFilenameForSong(song); + if (filenames.contains(new_filename)) { + QString song_number = QString::number(++filenames[new_filename]); + new_filename = Utilities::PathWithoutFilenameExtension(new_filename) + "(" + song_number + ")." + QFileInfo(new_filename).suffix(); + } + filenames.insert(new_filename, 1); + new_songs_info << Organise::NewSongInfo(song, new_filename); + } + return new_songs_info; + +} + +void OrganiseDialog::UpdatePreviews() { + + if (songs_future_.isRunning()) { + return; + } + + const QModelIndex destination = ui_->destination->model()->index(ui_->destination->currentIndex(), 0); + std::shared_ptr storage; + bool has_local_destination = false; + + if (destination.isValid()) { + storage = destination.data(MusicStorage::Role_Storage).value>(); + if (storage) { + has_local_destination = !storage->LocalPath().isEmpty(); + } + } + + // Update the free space bar + quint64 capacity = destination.data(MusicStorage::Role_Capacity).toLongLong(); + quint64 free = destination.data(MusicStorage::Role_FreeSpace).toLongLong(); + + if (!capacity) { + ui_->free_space->hide(); + } + else { + ui_->free_space->show(); + ui_->free_space->set_free_bytes(free); + ui_->free_space->set_total_bytes(capacity); + } + + // Update the format object + format_.set_format(ui_->naming->toPlainText()); + format_.set_replace_non_ascii(ui_->replace_ascii->isChecked()); + format_.set_replace_spaces(ui_->replace_spaces->isChecked()); + format_.set_replace_the(ui_->replace_the->isChecked()); + + const bool format_valid = !has_local_destination || format_.IsValid(); + + // Are we gonna enable the ok button? + bool ok = format_valid && !songs_.isEmpty(); + if (capacity != 0 && total_size_ > free) ok = false; + + ui_->button_box->button(QDialogButtonBox::Ok)->setEnabled(ok); + if (!format_valid) return; + + new_songs_info_ = ComputeNewSongsFilenames(songs_, format_); + + // Update the previews + ui_->preview->clear(); + ui_->preview_group->setVisible(has_local_destination); + ui_->naming_group->setVisible(has_local_destination); + if (has_local_destination) { + for (const Organise::NewSongInfo &song_info : new_songs_info_) { + QString filename = storage->LocalPath() + "/" + song_info.new_filename_; + ui_->preview->addItem(QDir::toNativeSeparators(filename)); + } + } + + if (!resized_by_user_) { + adjustSize(); + } + +} + +QSize OrganiseDialog::sizeHint() const { return QSize(650, 0); } + +void OrganiseDialog::Reset() { + + ui_->naming->setPlainText(kDefaultFormat); + ui_->replace_ascii->setChecked(false); + ui_->replace_spaces->setChecked(false); + ui_->replace_the->setChecked(false); + ui_->overwrite->setChecked(false); + ui_->mark_as_listened->setChecked(false); + ui_->eject_after->setChecked(false); + +} + +void OrganiseDialog::showEvent(QShowEvent*) { + + resized_by_user_ = false; + + QSettings s; + s.beginGroup(kSettingsGroup); + ui_->naming->setPlainText(s.value("format", kDefaultFormat).toString()); + ui_->replace_ascii->setChecked(s.value("replace_ascii", false).toBool()); + ui_->replace_spaces->setChecked(s.value("replace_spaces", false).toBool()); + ui_->replace_the->setChecked(s.value("replace_the", false).toBool()); + ui_->overwrite->setChecked(s.value("overwrite", false).toBool()); + ui_->mark_as_listened->setChecked(s.value("mark_as_listened", false).toBool()); + ui_->eject_after->setChecked(s.value("eject_after", false).toBool()); + + QString destination = s.value("destination").toString(); + int index = ui_->destination->findText(destination); + if (index != -1 && !destination.isEmpty()) { + ui_->destination->setCurrentIndex(index); + } + +} + +void OrganiseDialog::accept() { + + QSettings s; + + s.beginGroup(kSettingsGroup); + s.setValue("format", ui_->naming->toPlainText()); + s.setValue("replace_ascii", ui_->replace_ascii->isChecked()); + s.setValue("replace_spaces", ui_->replace_spaces->isChecked()); + s.setValue("replace_the", ui_->replace_the->isChecked()); + s.setValue("overwrite", ui_->overwrite->isChecked()); + s.setValue("mark_as_listened", ui_->overwrite->isChecked()); + s.setValue("destination", ui_->destination->currentText()); + s.setValue("eject_after", ui_->eject_after->isChecked()); + + const QModelIndex destination = ui_->destination->model()->index(ui_->destination->currentIndex(), 0); + std::shared_ptr storage = destination.data(MusicStorage::Role_StorageForceConnect).value>(); + + if (!storage) return; + + // It deletes itself when it's finished. + const bool copy = ui_->aftercopying->currentIndex() == 0; + Organise *organise = new Organise(task_manager_, storage, format_, copy, ui_->overwrite->isChecked(), ui_->mark_as_listened->isChecked(), new_songs_info_, ui_->eject_after->isChecked()); + connect(organise, SIGNAL(Finished(QStringList)), SLOT(OrganiseFinished(QStringList))); + connect(organise, SIGNAL(FileCopied(int)), this, SIGNAL(FileCopied(int))); + organise->Start(); + + QDialog::accept(); +} + +void OrganiseDialog::OrganiseFinished(const QStringList &files_with_errors) { + if (files_with_errors.isEmpty()) return; + + error_dialog_.reset(new OrganiseErrorDialog); + error_dialog_->Show(OrganiseErrorDialog::Type_Copy, files_with_errors); +} + +void OrganiseDialog::resizeEvent(QResizeEvent *e) { + if (e->spontaneous()) { + resized_by_user_ = true; + } + + QDialog::resizeEvent(e); +} + diff --git a/src/dialogs/organisedialog.h b/src/dialogs/organisedialog.h new file mode 100644 index 00000000..4c317e5d --- /dev/null +++ b/src/dialogs/organisedialog.h @@ -0,0 +1,110 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ORGANISEDIALOG_H +#define ORGANISEDIALOG_H + +#include "config.h" + +#include + +#include +#include +#include +#include + +#include "gtest/gtest_prod.h" + +#include "core/organise.h" +#include "core/organiseformat.h" +#include "core/song.h" + +class CollectionWatcher; +class OrganiseErrorDialog; +class TaskManager; +class Ui_OrganiseDialog; + +class QAbstractItemModel; + +class OrganiseDialog : public QDialog { + Q_OBJECT + + public: + OrganiseDialog(TaskManager *task_manager, QWidget *parent = nullptr); + ~OrganiseDialog(); + + static const char *kDefaultFormat; + static const char *kSettingsGroup; + + QSize sizeHint() const; + + void SetDestinationModel(QAbstractItemModel *model, bool devices = false); + + // These functions return true if any songs were actually added to the dialog. + // SetSongs returns immediately, SetUrls and SetFilenames load the songs in + // the background. + bool SetSongs(const SongList &songs); + bool SetUrls(const QList &urls); + bool SetFilenames(const QStringList &filenames); + + void SetCopy(bool copy); + + signals: + void FileCopied(int); + + public slots: + void accept(); + + protected: + void showEvent(QShowEvent *); + void resizeEvent(QResizeEvent *); + + private slots: + void Reset(); + + void InsertTag(const QString &tag); + void UpdatePreviews(); + + void OrganiseFinished(const QStringList &files_with_errors); + + private: + SongList LoadSongsBlocking(const QStringList &filenames); + void SetLoadingSongs(bool loading); + + static Organise::NewSongInfoList ComputeNewSongsFilenames(const SongList &songs, const OrganiseFormat &format); + + Ui_OrganiseDialog *ui_; + TaskManager *task_manager_; + + OrganiseFormat format_; + + QFuture songs_future_; + SongList songs_; + Organise::NewSongInfoList new_songs_info_; + quint64 total_size_; + + std::unique_ptr error_dialog_; + + bool resized_by_user_; + + FRIEND_TEST(OrganiseDialogTest, ComputeNewSongsFilenamesTest); +}; + +#endif // ORGANISEDIALOG_H diff --git a/src/dialogs/organisedialog.ui b/src/dialogs/organisedialog.ui new file mode 100644 index 00000000..7e40ab7d --- /dev/null +++ b/src/dialogs/organisedialog.ui @@ -0,0 +1,291 @@ + + + OrganiseDialog + + + + 0 + 0 + 588 + 525 + + + + Organise Files + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + + + + + + Destination + + + + + + + + + + After copying... + + + + + + + + Keep the original files + + + + + Delete the original files + + + + + + + + + + + + + Safely remove the device after copying + + + + + + + Naming options + + + + + + + + <p>Tokens start with %, for example: %artist %album %title </p> + +<p>If you surround sections of text that contain a token with curly-braces, that section will be hidden if the token is empty.</p> + + + QTextEdit::NoWrap + + + false + + + + + + + Insert... + + + QToolButton::InstantPopup + + + + + + + + + Ignore "The" in artist names + + + + + + + Replaces spaces with underscores + + + + + + + Restrict to ASCII characters + + + + + + + Overwrite existing files + + + + + + + Mark as listened + + + + + + + + + + Preview + + + + + + 0 + + + + + 0 + + + 0 + + + + + + + + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 264 + 20 + + + + + + + + Loading... + + + + + + + Qt::Horizontal + + + + 264 + 20 + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset + + + + + + + + FreeSpaceBar + QWidget +
widgets/freespacebar.h
+ 1 +
+ + LineTextEdit + QTextEdit +
widgets/linetextedit.h
+
+ + BusyIndicator + QWidget +
widgets/busyindicator.h
+ 1 +
+
+ + destination + aftercopying + eject_after + naming + insert + replace_the + replace_spaces + replace_ascii + overwrite + button_box + + + + + + + button_box + accepted() + OrganiseDialog + accept() + + + 257 + 487 + + + 157 + 274 + + + + + button_box + rejected() + OrganiseDialog + reject() + + + 325 + 487 + + + 286 + 274 + + + + +
diff --git a/src/dialogs/organiseerrordialog.cpp b/src/dialogs/organiseerrordialog.cpp new file mode 100644 index 00000000..457538d4 --- /dev/null +++ b/src/dialogs/organiseerrordialog.cpp @@ -0,0 +1,73 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include "organiseerrordialog.h" +#include "ui_organiseerrordialog.h" + +OrganiseErrorDialog::OrganiseErrorDialog(QWidget *parent) : QDialog(parent), ui_(new Ui_OrganiseErrorDialog) { + + ui_->setupUi(this); + + const int icon_size = style()->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, this); + QIcon icon = style()->standardIcon(QStyle::SP_MessageBoxCritical, 0, this); + + ui_->icon->setPixmap(icon.pixmap(icon_size)); + +} + +OrganiseErrorDialog::~OrganiseErrorDialog() { + delete ui_; +} + +void OrganiseErrorDialog::Show(OperationType type, const SongList &songs_with_errors) { + + QStringList files; + for (const Song &song : songs_with_errors) { + files << song.url().toLocalFile(); + } + Show(type, files); + +} + +void OrganiseErrorDialog::Show(OperationType type, const QStringList &files_with_errors) { + + QStringList sorted_files = files_with_errors; + qStableSort(sorted_files); + + switch (type) { + case Type_Copy: + setWindowTitle(tr("Error copying songs")); + ui_->label->setText(tr("There were problems copying some songs. The following files could not be copied:")); + break; + + case Type_Delete: + setWindowTitle(tr("Error deleting songs")); + ui_->label->setText(tr("There were problems deleting some songs. The following files could not be deleted:")); + break; + } + + ui_->list->addItems(sorted_files); + + show(); +} diff --git a/src/dialogs/organiseerrordialog.h b/src/dialogs/organiseerrordialog.h new file mode 100644 index 00000000..795e478e --- /dev/null +++ b/src/dialogs/organiseerrordialog.h @@ -0,0 +1,51 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ORGANISEERRORDIALOG_H +#define ORGANISEERRORDIALOG_H + +#include "config.h" + +#include + +#include "core/song.h" + +class Ui_OrganiseErrorDialog; + +class OrganiseErrorDialog : public QDialog { + Q_OBJECT + + public: + OrganiseErrorDialog(QWidget *parent = nullptr); + ~OrganiseErrorDialog(); + + enum OperationType { + Type_Copy, + Type_Delete, + }; + + void Show(OperationType type, const SongList& songs_with_errors); + void Show(OperationType type, const QStringList &files_with_errors); + +private: + Ui_OrganiseErrorDialog *ui_; +}; + +#endif // ORGANISEERRORDIALOG_H diff --git a/src/dialogs/organiseerrordialog.ui b/src/dialogs/organiseerrordialog.ui new file mode 100644 index 00000000..b78a421e --- /dev/null +++ b/src/dialogs/organiseerrordialog.ui @@ -0,0 +1,91 @@ + + + OrganiseErrorDialog + + + + 0 + 0 + 779 + 355 + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + + + buttonBox + accepted() + OrganiseErrorDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + OrganiseErrorDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/dialogs/trackselectiondialog.cpp b/src/dialogs/trackselectiondialog.cpp new file mode 100644 index 00000000..17e3beff --- /dev/null +++ b/src/dialogs/trackselectiondialog.cpp @@ -0,0 +1,316 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "trackselectiondialog.h" +#include "ui_trackselectiondialog.h" + +#include "core/tagreaderclient.h" +#include "core/iconloader.h" + + +TrackSelectionDialog::TrackSelectionDialog(QWidget *parent) + : QDialog(parent), + ui_(new Ui_TrackSelectionDialog), + save_on_close_(false) +{ + + // Setup dialog window + ui_->setupUi(this); + + connect(ui_->song_list, SIGNAL(currentRowChanged(int)), SLOT(UpdateStack())); + connect(ui_->results, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), SLOT(ResultSelected())); + + ui_->splitter->setSizes(QList() << 200 << width() - 200); + SetLoading(QString()); + + // Add the next/previous buttons + previous_button_ = new QPushButton(IconLoader::Load("go-previous"), tr("Previous"), this); + next_button_ = new QPushButton(IconLoader::Load("go-next"), tr("Next"), this); + ui_->button_box->addButton(previous_button_, QDialogButtonBox::ResetRole); + ui_->button_box->addButton(next_button_, QDialogButtonBox::ResetRole); + + connect(previous_button_, SIGNAL(clicked()), SLOT(PreviousSong())); + connect(next_button_, SIGNAL(clicked()), SLOT(NextSong())); + + // Set some shortcuts for the buttons + new QShortcut(QKeySequence::Back, previous_button_, SLOT(click())); + new QShortcut(QKeySequence::Forward, next_button_, SLOT(click())); + new QShortcut(QKeySequence::MoveToPreviousPage, previous_button_, SLOT(click())); + new QShortcut(QKeySequence::MoveToNextPage, next_button_, SLOT(click())); + + // Resize columns + ui_->results->setColumnWidth(0, 50); // Track column + ui_->results->setColumnWidth(1, 50); // Year column + ui_->results->setColumnWidth(2, 160); // Title column + ui_->results->setColumnWidth(3, 160); // Artist column + ui_->results->setColumnWidth(4, 160); // Album column + +} + +TrackSelectionDialog::~TrackSelectionDialog() { + delete ui_; +} + +void TrackSelectionDialog::Init(const SongList &songs) { + + ui_->song_list->clear(); + ui_->stack->setCurrentWidget(ui_->loading_page); + data_.clear(); + + for (const Song &song : songs) { + Data data; + data.original_song_ = song; + data_ << data; + + QListWidgetItem *item = new QListWidgetItem(ui_->song_list); + item->setText(QFileInfo(song.url().toLocalFile()).fileName()); + item->setForeground(palette().color(QPalette::Disabled, QPalette::Text)); + } + + const bool multiple = songs.count() > 1; + ui_->song_list->setVisible(multiple); + next_button_->setEnabled(multiple); + previous_button_->setEnabled(multiple); + + ui_->song_list->setCurrentRow(0); + +} + +void TrackSelectionDialog::FetchTagProgress(const Song &original_song, const QString &progress) { + + // Find the item with this filename + int row = -1; + for (int i = 0; i < data_.count(); ++i) { + if (data_[i].original_song_.url() == original_song.url()) { + row = i; + break; + } + } + + if (row == -1) return; + + data_[row].progress_string_ = progress; + + // If it's the current item, update the display + if (ui_->song_list->currentIndex().row() == row) { + UpdateStack(); + } + +} + +void TrackSelectionDialog::FetchTagFinished(const Song &original_song, const SongList &songs_guessed) { + + // Find the item with this filename + int row = -1; + for (int i = 0; i < data_.count(); ++i) { + if (data_[i].original_song_.url() == original_song.url()) { + row = i; + break; + } + } + + if (row == -1) return; + + // Set the color back to black + ui_->song_list->item(row)->setForeground(palette().text()); + + // Add the results to the list + data_[row].pending_ = false; + data_[row].results_ = songs_guessed; + + // If it's the current item, update the display + if (ui_->song_list->currentIndex().row() == row) { + UpdateStack(); + } + +} + +void TrackSelectionDialog::UpdateStack() { + + const int row = ui_->song_list->currentRow(); + if (row < 0 || row >= data_.count()) return; + + const Data &data = data_[row]; + + if (data.pending_) { + ui_->stack->setCurrentWidget(ui_->loading_page); + ui_->progress->set_text(data.progress_string_ + "..."); + return; + } + else if (data.results_.isEmpty()) { + ui_->stack->setCurrentWidget(ui_->error_page); + return; + } + ui_->stack->setCurrentWidget(ui_->results_page); + + // Clear tree widget + ui_->results->clear(); + + // Put the original tags at the top + AddDivider(tr("Original tags"), ui_->results); + AddSong(data.original_song_, -1, ui_->results); + + // Fill tree view with songs + AddDivider(tr("Suggested tags"), ui_->results); + + int song_index = 0; + for (const Song &song : data.results_) { + AddSong(song, song_index++, ui_->results); + } + + // Find the item that was selected last time + for (int i = 0; i < ui_->results->model()->rowCount(); ++i) { + const QModelIndex index = ui_->results->model()->index(i, 0); + const QVariant id = index.data(Qt::UserRole); + if (!id.isNull() && id.toInt() == data.selected_result_) { + ui_->results->setCurrentIndex(index); + break; + } + } +} + +void TrackSelectionDialog::AddDivider(const QString &text, QTreeWidget *parent) const { + + QTreeWidgetItem *item = new QTreeWidgetItem(parent); + item->setFirstColumnSpanned(true); + item->setText(0, text); + item->setFlags(Qt::NoItemFlags); + item->setForeground(0, palette().color(QPalette::Disabled, QPalette::Text)); + + QFont bold_font(font()); + bold_font.setBold(true); + item->setFont(0, bold_font); + +} + +void TrackSelectionDialog::AddSong(const Song &song, int result_index, QTreeWidget *parent) const { + + QStringList values; + values << ((song.track() > 0) ? QString::number(song.track()) : QString()) << ((song.year() > 0) ? QString::number(song.year()) : QString()) << song.title() << song.artist() << song.album(); + + QTreeWidgetItem *item = new QTreeWidgetItem(parent, values); + item->setData(0, Qt::UserRole, result_index); + item->setData(0, Qt::TextAlignmentRole, Qt::AlignRight); +} + +void TrackSelectionDialog::ResultSelected() { + + if (!ui_->results->currentItem()) return; + + const int song_row = ui_->song_list->currentRow(); + if (song_row == -1) return; + + const int result_index = ui_->results->currentItem()->data(0, Qt::UserRole).toInt(); + data_[song_row].selected_result_ = result_index; + +} + +void TrackSelectionDialog::SetLoading(const QString &message) { + + const bool loading = !message.isEmpty(); + + ui_->button_box->setEnabled(!loading); + ui_->splitter->setEnabled(!loading); + ui_->loading_label->setVisible(loading); + ui_->loading_label->set_text(message); + +} + +void TrackSelectionDialog::SaveData(const QList &data) { + + for (int i = 0; i < data.count(); ++i) { + const Data &ref = data[i]; + if (ref.pending_ || ref.results_.isEmpty() || ref.selected_result_ == -1) + continue; + + const Song &new_metadata = ref.results_[ref.selected_result_]; + + Song copy(ref.original_song_); + copy.set_title(new_metadata.title()); + copy.set_artist(new_metadata.artist()); + copy.set_album(new_metadata.album()); + copy.set_track(new_metadata.track()); + copy.set_year(new_metadata.year()); + + if (!TagReaderClient::Instance()->SaveFileBlocking(copy.url().toLocalFile(), copy)) { + qLog(Warning) << "Failed to write new auto-tags to" << copy.url().toLocalFile(); + } + } + +} + +void TrackSelectionDialog::accept() { + + if (save_on_close_) { + SetLoading(tr("Saving tracks") + "..."); + + // Save tags in the background + QFuture future = QtConcurrent::run(&TrackSelectionDialog::SaveData, data_); + QFutureWatcher *watcher = new QFutureWatcher(this); + watcher->setFuture(future); + connect(watcher, SIGNAL(finished()), SLOT(AcceptFinished())); + + return; + } + + QDialog::accept(); + + for (const Data &data : data_) { + if (data.pending_ || data.results_.isEmpty() || data.selected_result_ == -1) + continue; + + const Song &new_metadata = data.results_[data.selected_result_]; + + emit SongChosen(data.original_song_, new_metadata); + } + +} + +void TrackSelectionDialog::AcceptFinished() { + + QFutureWatcher *watcher = dynamic_cast*>(sender()); + if (!watcher) return; + watcher->deleteLater(); + + SetLoading(QString()); + QDialog::accept(); + +} + +void TrackSelectionDialog::NextSong() { + int row = (ui_->song_list->currentRow() + 1) % ui_->song_list->count(); + ui_->song_list->setCurrentRow(row); +} + +void TrackSelectionDialog::PreviousSong() { + int row = (ui_->song_list->currentRow() - 1 + ui_->song_list->count()) % ui_->song_list->count(); + ui_->song_list->setCurrentRow(row); +} diff --git a/src/dialogs/trackselectiondialog.h b/src/dialogs/trackselectiondialog.h new file mode 100644 index 00000000..2b776fec --- /dev/null +++ b/src/dialogs/trackselectiondialog.h @@ -0,0 +1,93 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TRACKSELECTIONDIALOG_H +#define TRACKSELECTIONDIALOG_H + +#include "config.h" + +#include + +#include "core/song.h" + +class Ui_TrackSelectionDialog; +class QTreeWidget; + +class TrackSelectionDialog : public QDialog { + Q_OBJECT + + public: + TrackSelectionDialog(QWidget *parent = nullptr); + ~TrackSelectionDialog(); + + void set_save_on_close(bool save_on_close) { save_on_close_ = save_on_close; } + + void Init(const SongList &songs); + +public slots: + void FetchTagProgress(const Song &original_song, const QString &progress); + void FetchTagFinished(const Song &original_song, const SongList &songs_guessed); + + // QDialog + void accept(); + +signals: + void SongChosen(const Song &original_song, const Song &new_metadata); + +private slots: + void UpdateStack(); + + void NextSong(); + void PreviousSong(); + + void ResultSelected(); + void AcceptFinished(); + +private: + Ui_TrackSelectionDialog *ui_; + + struct Data { + Data() : pending_(true), selected_result_(0) {} + + Song original_song_; + bool pending_; + QString progress_string_; + SongList results_; + int selected_result_; + }; + + void AddDivider(const QString &text, QTreeWidget *parent) const; + void AddSong(const Song &song, int result_index, QTreeWidget *parent) const; + + void SetLoading(const QString &message); + static void SaveData(const QList &data); + +private: + QList data_; + + QPushButton *previous_button_; + QPushButton *next_button_; + + bool save_on_close_; +}; + +#endif // TRACKSELECTIONDIALOG_H + + diff --git a/src/dialogs/trackselectiondialog.ui b/src/dialogs/trackselectiondialog.ui new file mode 100644 index 00000000..1d385081 --- /dev/null +++ b/src/dialogs/trackselectiondialog.ui @@ -0,0 +1,290 @@ + + + TrackSelectionDialog + + + + 0 + 0 + 773 + 375 + + + + Tag fetcher + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + true + + + + + 0 + + + + + + + Qt::Vertical + + + + 20 + 133 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 133 + + + + + + + + + + + + Qt::Vertical + + + + 20 + 124 + + + + + + + + QLabel { font-weight: bold; } + + + Sorry + + + Qt::AlignCenter + + + + + + + Strawberry was unable to find results for this file + + + Qt::AlignCenter + + + true + + + + + + + Qt::Vertical + + + + 20 + 124 + + + + + + + + + + 0 + + + + + QLabel { font-weight: bold; } + + + Select best possible match + + + + + + + QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed + + + false + + + 150 + + + 50 + + + + Track + + + + + Year + + + + + Title + + + + + Artist + + + + + Album + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + BusyIndicator + QWidget +
widgets/busyindicator.h
+
+
+ + + + + + button_box + accepted() + TrackSelectionDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + button_box + rejected() + TrackSelectionDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/engine/alsadevicefinder.cpp b/src/engine/alsadevicefinder.cpp new file mode 100644 index 00000000..ac06de9b --- /dev/null +++ b/src/engine/alsadevicefinder.cpp @@ -0,0 +1,118 @@ +/* + * Strawberry Music Player + * Copyright 2017, Jonas Kvinge + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "alsadevicefinder.h" + +#include + +AlsaDeviceFinder::AlsaDeviceFinder() + : DeviceFinder("alsa") { + +} + +QList AlsaDeviceFinder::ListDevices() { + + QList ret; + + //register int err; + int card = -1; + int dev = -1; + int result = -1; + snd_ctl_card_info_t *cardinfo; + snd_pcm_info_t *pcminfo; + snd_ctl_t *handle; + + snd_ctl_card_info_alloca(&cardinfo); + snd_pcm_info_alloca(&pcminfo); + + card = -1; + + snd_pcm_stream_name(SND_PCM_STREAM_PLAYBACK); + + while (true) { + + result = snd_card_next(&card); + if (result < 0) { + qLog(Error) << "Unable to get soundcard: " << snd_strerror(result); + return ret; + } + if (card < 0) return ret; + + char name[32]; + sprintf(name, "hw:%d", card); + result = snd_ctl_open(&handle, name, 0); + if (result < 0) { + qLog(Error) << "Unable to open soundcard " << card << ": " << snd_strerror(result); + continue; + } + result = snd_ctl_card_info(handle, cardinfo); + if (result < 0) { + qLog(Error) << "Control hardware failure for card " << card << ": " << snd_strerror(result); + snd_ctl_close(handle); + continue; + } + + dev = -1; + while (true) { + + result = snd_ctl_pcm_next_device(handle, &dev); + if (result < 0) { + qLog(Error) << "Unable to get PCM for card " << card << ": " << snd_strerror(result); + continue; + } + if (dev < 0) break; + + snd_pcm_info_set_device(pcminfo, dev); + snd_pcm_info_set_subdevice(pcminfo, 0); + snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK); + result = snd_ctl_pcm_info(handle, pcminfo); + if (result < 0) { + if (result != -ENOENT) qLog(Error) << "Unable to get control digital audio info for card " << card << ": " << snd_strerror(result); + continue; + } + + snd_pcm_info_get_name(pcminfo); + + Device device; + device.card = card; + device.device = dev; + device.description = QString("%1 %2").arg(snd_ctl_card_info_get_name(cardinfo)).arg(snd_pcm_info_get_name(pcminfo)); + device.string = QString("hw:%1,%2").arg(card).arg(dev); + device.device_property_value = QString("hw:%1,%2").arg(card).arg(dev); + device.iconname = GuessIconName("", device.description); + ret.append(device); + + } + snd_ctl_close(handle); + } + + snd_pcm_info_free(pcminfo); pcminfo = NULL; + snd_ctl_card_info_free(cardinfo); cardinfo = NULL; + + snd_config_update_free_global(); + + return ret; +} diff --git a/src/engine/alsadevicefinder.h b/src/engine/alsadevicefinder.h new file mode 100644 index 00000000..14357aa0 --- /dev/null +++ b/src/engine/alsadevicefinder.h @@ -0,0 +1,35 @@ +/* + * Strawberry Music Player + * Copyright 2017, Jonas Kvinge + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ALSADEVICEFINDER_H +#define ALSADEVICEFINDER_H + +#include "config.h" + +#include "engine/devicefinder.h" + +class AlsaDeviceFinder : public DeviceFinder { + public: + AlsaDeviceFinder(); + + virtual bool Initialise() { return true; } + virtual QList ListDevices(); +}; + +#endif // ALSADEVICEFINDER_H diff --git a/src/engine/bufferconsumer.h b/src/engine/bufferconsumer.h new file mode 100644 index 00000000..358d83d1 --- /dev/null +++ b/src/engine/bufferconsumer.h @@ -0,0 +1,40 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef BUFFERCONSUMER_H +#define BUFFERCONSUMER_H + +#include "config.h" + +#include + +class GstEnginePipeline; + +class BufferConsumer { +public: + virtual ~BufferConsumer() {} + + // This is called in some unspecified GStreamer thread. + // Ownership of the buffer is transferred to the BufferConsumer and it should + // gst_buffer_unref it. + virtual void ConsumeBuffer(GstBuffer* buffer, int pipeline_id) = 0; +}; + +#endif // BUFFERCONSUMER_H diff --git a/src/engine/devicefinder.cpp b/src/engine/devicefinder.cpp new file mode 100644 index 00000000..6a5f5f02 --- /dev/null +++ b/src/engine/devicefinder.cpp @@ -0,0 +1,57 @@ +/* + * Strawberry Music Player + * Copyright 2017, Jonas Kvinge + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "devicefinder.h" + +#include "core/logging.h" + +DeviceFinder::DeviceFinder(const QString &output): output_(output) { + //qLog(Debug) << __PRETTY_FUNCTION__ << output; +} + +QString DeviceFinder::GuessIconName(const QString &name, const QString &description) { + + //qLog(Debug) << __PRETTY_FUNCTION__ << name << description; + + QString description_lower = description.toLower(); + + if (description_lower.contains("mcintosh")) { + return "mcintosh"; + } + if (description_lower.contains("electrocompaniet")) { + return "electrocompaniet"; + } + if (description_lower.contains("intel")) { + return "intel"; + } + if (description_lower.contains("realtek")) { + return "realtek"; + } + if (description_lower.contains("nvidia")) { + return "nvidia"; + } + if (description_lower.contains("headset")) { + return "headset"; + } + + return "soundcard"; + +} diff --git a/src/engine/devicefinder.h b/src/engine/devicefinder.h new file mode 100644 index 00000000..d7c248f8 --- /dev/null +++ b/src/engine/devicefinder.h @@ -0,0 +1,64 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2014, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef DEVICEFINDER_H +#define DEVICEFINDER_H + +#include "config.h" + +#include +#include + +// Finds audio output devices +class DeviceFinder { + + public: + struct Device { + int card; + int device; + QVariant device_property_value; + QString string; + QString description; + QString iconname; + }; + + virtual ~DeviceFinder() {} + + // The name of the gstreamer sink element that devices found by this class can be used with. + QString output() const { return output_; } + + // Does any necessary setup, returning false if this DeviceFinder cannot be used. + virtual bool Initialise() = 0; + + // Returns a list of available devices. + virtual QList ListDevices() = 0; + + protected: + explicit DeviceFinder(const QString &output); + + static QString GuessIconName(const QString &, const QString &); + + private: + QString output_; + +}; + +#endif // DEVICEFINDER_H + diff --git a/src/engine/directsounddevicefinder.cpp b/src/engine/directsounddevicefinder.cpp new file mode 100644 index 00000000..17a9fdce --- /dev/null +++ b/src/engine/directsounddevicefinder.cpp @@ -0,0 +1,58 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2014, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifdef INTERFACE +#undef INTERFACE +#endif + +#include "config.h" + +#include + +#include + +#include "directsounddevicefinder.h" + +DirectSoundDeviceFinder::DirectSoundDeviceFinder() + : DeviceFinder("directsoundsink") { +} + +QList DirectSoundDeviceFinder::ListDevices() { + State state; + DirectSoundEnumerateA(&DirectSoundDeviceFinder::EnumerateCallback, &state); + return state.devices; +} + +BOOL DirectSoundDeviceFinder::EnumerateCallback(LPGUID guid, LPCSTR description, LPCSTR module, LPVOID state_voidptr) { + + State *state = reinterpret_cast(state_voidptr); + + if (guid) { + Device dev; + dev.description = QString::fromUtf8(description); + dev.device_property_value = QUuid(*guid).toByteArray(); + dev.icon_name = GuessIconName(dev.plugin_name, dev.description); + state->devices.append(dev); + } + + return 1; + +} + diff --git a/src/engine/directsounddevicefinder.h b/src/engine/directsounddevicefinder.h new file mode 100644 index 00000000..6c0f24cc --- /dev/null +++ b/src/engine/directsounddevicefinder.h @@ -0,0 +1,49 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2014, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef DIRECTSOUNDDEVICEFINDER_H +#define DIRECTSOUNDDEVICEFINDER_H + +#include "config.h" + +#include + +#include "engine/devicefinder.h" + +class DirectSoundDeviceFinder : public DeviceFinder { + public: + DirectSoundDeviceFinder(); + + virtual bool Initialise() { return true; } + virtual QList ListDevices(); + + private: + struct State { + QList devices; + }; + + static BOOL EnumerateCallback(LPGUID guid, + LPCSTR description, + LPCSTR module, + LPVOID state_voidptr) __attribute__((stdcall)); +}; + +#endif // DIRECTSOUNDDEVICEFINDER_H + diff --git a/src/engine/engine_fwd.h b/src/engine/engine_fwd.h new file mode 100644 index 00000000..6159d03e --- /dev/null +++ b/src/engine/engine_fwd.h @@ -0,0 +1,42 @@ +#ifndef ENGINE_FWD_H +#define ENGINE_FWD_H + +#include + +/// Used by eg engineobserver.h, and thus we reduce header dependencies on enginebase.h + +namespace Engine { + +struct SimpleMetaBundle; +class Base; + +/** + * You should return: + * Playing when playing, + * Paused when paused + * Idle when you still have a URL loaded (ie you have not been told to stop()) + * Empty when you have been told to stop(), + * Error when an error occurred and you stopped yourself + * + * It is vital to be Idle just after the track has ended! + */ +enum State { Empty, Idle, Playing, Paused, Error }; + +enum TrackChangeType { + // One of: + First = 0x01, + Manual = 0x02, + Auto = 0x04, + Intro = 0x08, + + // Any of: + SameAlbum = 0x10, +}; + +Q_DECLARE_FLAGS(TrackChangeFlags, TrackChangeType); + +} + +typedef Engine::Base EngineBase; + +#endif diff --git a/src/engine/enginebase.cpp b/src/engine/enginebase.cpp new file mode 100644 index 00000000..7c4e139c --- /dev/null +++ b/src/engine/enginebase.cpp @@ -0,0 +1,105 @@ +/* + * Strawberry Music Player + * This file was part of Amarok / Clementine. + * Copyright 2003 Mark Kretschmann + * Copyright 2004, 2005 Max Howell, + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR 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 "config.h" + +#include +#include + +#include "enginebase.h" +#include "enginedevice.h" +#include "core/timeconstants.h" + +#include "settings/playbacksettingspage.h" + +Engine::Base::Base() + : volume_(50), + beginning_nanosec_(0), + end_nanosec_(0), + scope_(kScopeSize), + fadeout_enabled_(true), + fadeout_duration_nanosec_(2 * kNsecPerSec), // 2s + crossfade_enabled_(true), + autocrossfade_enabled_(false), + crossfade_same_album_(false), + next_background_stream_id_(0), + about_to_end_emitted_(false) {} + +Engine::Base::~Base() {} + +bool Engine::Base::Load(const QUrl &url, TrackChangeFlags, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) { + + Q_UNUSED(force_stop_at_end); + + url_ = url; + beginning_nanosec_ = beginning_nanosec; + end_nanosec_ = end_nanosec; + + about_to_end_emitted_ = false; + return true; + +} + +void Engine::Base::SetVolume(uint value) { + + volume_ = value; + + SetVolumeSW(MakeVolumeLogarithmic(value)); + +} + +uint Engine::Base::MakeVolumeLogarithmic(uint volume) { + // We're using a logarithmic function to make the volume ramp more natural. + return static_cast( 100 - 100.0 * std::log10( ( 100 - volume ) * 0.09 + 1.0 ) ); +} + +void Engine::Base::ReloadSettings() { + + QSettings s; + s.beginGroup(PlaybackSettingsPage::kSettingsGroup); + + fadeout_enabled_ = s.value("FadeoutEnabled", false).toBool(); + fadeout_duration_nanosec_ = s.value("FadeoutDuration", 2000).toLongLong() * kNsecPerMsec; + crossfade_enabled_ = s.value("CrossfadeEnabled", false).toBool(); + autocrossfade_enabled_ = s.value("AutoCrossfadeEnabled", false).toBool(); + crossfade_same_album_ = !s.value("NoCrossfadeSameAlbum", true).toBool(); + fadeout_pause_enabled_ = s.value("FadeoutPauseEnabled", false).toBool(); + fadeout_pause_duration_nanosec_ = s.value("FadeoutPauseDuration", 250).toLongLong() * kNsecPerMsec; + +} + +void Engine::Base::EmitAboutToEnd() { + + if (about_to_end_emitted_) + return; + + about_to_end_emitted_ = true; + emit TrackAboutToEnd(); +} + +bool Engine::Base::Play(const QUrl &u, TrackChangeFlags c, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) { + + if (!Load(u, c, force_stop_at_end, beginning_nanosec, end_nanosec)) + return false; + + return Play(0); +} diff --git a/src/engine/enginebase.h b/src/engine/enginebase.h new file mode 100644 index 00000000..242d5444 --- /dev/null +++ b/src/engine/enginebase.h @@ -0,0 +1,186 @@ +/* + * Strawberry Music Player + * This file was part of Amarok / Clementine. + * Copyright 2003 Mark Kretschmann + * Copyright 2004, 2005 Max Howell, + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR 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 ENGINEBASE_H +#define ENGINEBASE_H + +#include "config.h" + +#include +#include + +#include + +#include +#include +#include +#include + +#include "enginetype.h" +#include "engine_fwd.h" + +namespace Engine { + +typedef std::vector Scope; + +class Base : public QObject { + Q_OBJECT + + public: + + virtual ~Base(); + + virtual bool Init() = 0; + + virtual void StartPreloading(const QUrl&, bool, qint64, qint64) {} + virtual bool Play(quint64 offset_nanosec) = 0; + virtual void Stop(bool stop_after = false) = 0; + virtual void Pause() = 0; + virtual void Unpause() = 0; + virtual void Seek(quint64 offset_nanosec) = 0; + + virtual State state() const = 0; + virtual qint64 position_nanosec() const = 0; + virtual qint64 length_nanosec() const = 0; + + // Subclasses should respect given markers (beginning and end) which are in miliseconds. + virtual bool Load(const QUrl &url, TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec); + // Sets new values for the beginning and end markers of the currently playing song. + // This doesn't change the state of engine or the stream's current position. + virtual void RefreshMarkers(quint64 beginning_nanosec, qint64 end_nanosec) { + beginning_nanosec_ = beginning_nanosec; + end_nanosec_ = end_nanosec; + } + + // Plays a media stream represented with the URL 'u' from the given 'beginning' + // to the given 'end' (usually from 0 to a song's length). Both markers + // should be passed in nanoseconds. 'end' can be negative, indicating that the + // real length of 'u' stream is unknown. + bool Play(const QUrl &u, TrackChangeFlags c, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec); + + void SetVolume(uint value); + + // Simple accessors + EngineType type() const { return type_; } + inline uint volume() const { return volume_; } + virtual const Scope &scope(int chunk_length) { return scope_; } + bool is_fadeout_enabled() const { return fadeout_enabled_; } + bool is_crossfade_enabled() const { return crossfade_enabled_; } + bool is_autocrossfade_enabled() const { return autocrossfade_enabled_; } + bool crossfade_same_album() const { return crossfade_same_album_; } + + static const int kScopeSize = 1024; + + struct PluginDetails { + QString name; + QString description; + QString iconname; + }; + typedef QList PluginDetailsList; + + struct OutputDetails { + QString name; + QString description; + QString iconname; + QVariant device_property_value; + }; + typedef QList OutputDetailsList; + + public slots: + virtual void ReloadSettings(); + + virtual void SetEqualizerEnabled(bool) {} + virtual void SetEqualizerParameters(int preamp, const QList &bandGains) {} + virtual void SetStereoBalance(float value) {} + +signals: + // Emitted when crossfading is enabled and the track is crossfade_duration_ away from finishing + void TrackAboutToEnd(); + + void TrackEnded(); + + void FadeoutFinishedSignal(); + + void StatusText(const QString&); + void Error(const QString&); + + // Emitted when Engine was unable to play a song with the given QUrl. + void InvalidSongRequested(const QUrl&); + // Emitted when Engine successfully started playing a song with the given QUrl. + void ValidSongRequested(const QUrl&); + + void MetaData(const Engine::SimpleMetaBundle&); + + // Signals that the engine's state has changed (a stream was stopped for example). + // Always use the state from event, because it's not guaranteed that immediate + // subsequent call to state() won't return a stale value. + void StateChanged(Engine::State); + + protected: + Base(); + + virtual void SetVolumeSW(uint percent) = 0; + static uint MakeVolumeLogarithmic(uint volume); + void EmitAboutToEnd(); + + protected: + EngineType type_; + uint volume_; + quint64 beginning_nanosec_; + qint64 end_nanosec_; + QUrl url_; + Scope scope_; + + bool fadeout_enabled_; + qint64 fadeout_duration_nanosec_; + bool crossfade_enabled_; + bool autocrossfade_enabled_; + bool crossfade_same_album_; + int next_background_stream_id_; + bool fadeout_pause_enabled_; + qint64 fadeout_pause_duration_nanosec_; + + private: + bool about_to_end_emitted_; + Q_DISABLE_COPY(Base); + +}; +//Q_DECLARE_METATYPE(EngineBase::PluginDetails); +Q_DECLARE_METATYPE(EngineBase::OutputDetails); + +struct SimpleMetaBundle { + QString title; + QString artist; + QString album; + QString comment; + QString genre; + QString bitrate; + QString samplerate; + QString bitdepth; + QString length; + QString year; + QString tracknr; +}; + +} // namespace + +#endif diff --git a/src/engine/enginedevice.cpp b/src/engine/enginedevice.cpp new file mode 100644 index 00000000..c6aff207 --- /dev/null +++ b/src/engine/enginedevice.cpp @@ -0,0 +1,93 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2014, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "enginedevice.h" +#include "devicefinder.h" + +#include "core/logging.h" + +#include +#include + +#ifdef Q_OS_LINUX + #include "engine/alsadevicefinder.h" +#endif + +#ifdef HAVE_LIBPULSE + #include "engine/pulsedevicefinder.h" +#endif + +#ifdef Q_OS_DARWIN + #include "engine/osxdevicefinder.h" +#endif + +#ifdef Q_OS_WIN32 + #include "engine/directsounddevicefinder.h" +#endif + +#include "settings/backendsettingspage.h" + +EngineDevice::EngineDevice(QObject *parent) : QObject(parent) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + +} + +EngineDevice::~EngineDevice() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + qDeleteAll(device_finders_); + +} + +void EngineDevice::Init() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QList device_finders; + +#ifdef Q_OS_LINUX + device_finders.append(new AlsaDeviceFinder); +#endif +#ifdef HAVE_LIBPULSE + device_finders.append(new PulseDeviceFinder); +#endif +#ifdef Q_OS_DARWIN + device_finders.append(new OsxDeviceFinder); +#endif +#ifdef Q_OS_WIN32 + device_finders.append(new DirectSoundDeviceFinder); +#endif + + for (DeviceFinder *finder : device_finders) { + if (!finder->Initialise()) { + qLog(Warning) << "Failed to initialise DeviceFinder for" << finder->output(); + delete finder; + continue; + } + + device_finders_.append(finder); + } + +} + diff --git a/src/engine/enginedevice.h b/src/engine/enginedevice.h new file mode 100644 index 00000000..607cc411 --- /dev/null +++ b/src/engine/enginedevice.h @@ -0,0 +1,54 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2014, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ENGINEDEVICE_H +#define ENGINEDEVICE_H + +#include "config.h" + +#include +#include + +#include "enginebase.h" +#include "devicefinder.h" + +class DeviceFinder; + +class EngineDevice : public QObject { + Q_OBJECT + + public: + explicit EngineDevice(QObject *parent = nullptr); + ~EngineDevice(); + + void Init(); + + QList device_finders_; + + protected: + //static QString GuessIconName(const QString &, const QString &); + + private: + QString output_; + +}; + +#endif // ENGINEDEVICE_H + diff --git a/src/engine/enginetype.cpp b/src/engine/enginetype.cpp new file mode 100644 index 00000000..ab96ee54 --- /dev/null +++ b/src/engine/enginetype.cpp @@ -0,0 +1,53 @@ +/* + * Strawberry Music Player + * Copyright 2013, Jonas Kvinge + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include + +#include "enginetype.h" + +namespace Engine { + +QString EngineNameFromType(Engine::EngineType enginetype) { + switch (enginetype) { + case Engine::Xine: return QObject::tr("Xine"); + case Engine::GStreamer: return QObject::tr("GStreamer"); + case Engine::Phonon: return QObject::tr("Phonon"); + case Engine::VLC: return QObject::tr("VLC"); + case Engine::None: + default: return QObject::tr("None"); + + } +} + +Engine::EngineType EngineTypeFromName(QString enginename) { + + QString lower = enginename.toLower(); + + if (lower == "xine") return Engine::Xine; + else if (lower == "gstreamer") return Engine::GStreamer; + else if (lower == "phonon") return Engine::Phonon; + else if (lower == "vlc") return Engine::VLC; + else return Engine::None; + +} + +} diff --git a/src/engine/enginetype.h b/src/engine/enginetype.h new file mode 100644 index 00000000..5138cd99 --- /dev/null +++ b/src/engine/enginetype.h @@ -0,0 +1,44 @@ +/* + * Strawberry Music Player + * Copyright 2013, Jonas Kvinge + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ENGINETYPE_H +#define ENGINETYPE_H + +#include "config.h" + +#include +#include + +namespace Engine { + +enum EngineType { + None, + GStreamer, + VLC, + Xine, + Phonon +}; +Q_DECLARE_METATYPE(Engine::EngineType); + +QString EngineNameFromType(Engine::EngineType enginetype); +Engine::EngineType EngineTypeFromName(QString enginename); + +} + +#endif diff --git a/src/engine/gstelementdeleter.cpp b/src/engine/gstelementdeleter.cpp new file mode 100644 index 00000000..3cdb0ced --- /dev/null +++ b/src/engine/gstelementdeleter.cpp @@ -0,0 +1,35 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include "gstelementdeleter.h" + +GstElementDeleter::GstElementDeleter(QObject *parent) : QObject(parent) {} + +void GstElementDeleter::DeleteElementLater(GstElement* element) { + metaObject()->invokeMethod(this, "DeleteElement", Qt::QueuedConnection, Q_ARG(GstElement *, element)); +} + +void GstElementDeleter::DeleteElement(GstElement *element) { + gst_element_set_state(element, GST_STATE_NULL); +} diff --git a/src/engine/gstelementdeleter.h b/src/engine/gstelementdeleter.h new file mode 100644 index 00000000..92a0ba67 --- /dev/null +++ b/src/engine/gstelementdeleter.h @@ -0,0 +1,48 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef GSTBINDELETER_H +#define GSTBINDELETER_H + +#include "config.h" + +#include + +#include + +class GstElementDeleter : public QObject { + Q_OBJECT + + public: + GstElementDeleter(QObject *parent = nullptr); + + // If you call this function with any gstreamer element, the element will get + // deleted in the main thread. This is useful if you need to delete an + // element from its own callback. + // It's in a separate object so *your* object (GstEnginePipeline) can be + // destroyed, and the element that you scheduled for deletion is still + // deleted later regardless. + void DeleteElementLater(GstElement *element); + + private slots: + void DeleteElement(GstElement *element); +}; + +#endif // GSTBINDELETER_H diff --git a/src/engine/gstengine.cpp b/src/engine/gstengine.cpp new file mode 100644 index 00000000..1a653bca --- /dev/null +++ b/src/engine/gstengine.cpp @@ -0,0 +1,983 @@ +/*************************************************************************** + * Copyright (C) 2003-2005 by Mark Kretschmann * + * Copyright (C) 2005 by Jakub Stachowski * + * Copyright (C) 2006 Paul Cifarelli * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "enginetype.h" +#include "enginebase.h" + +#include "gstengine.h" + +#include "devicefinder.h" +#include "gstenginepipeline.h" +#include "core/closure.h" +#include "core/logging.h" +#include "core/taskmanager.h" +#include "core/timeconstants.h" +#include "core/utilities.h" + +#ifdef Q_OS_LINUX + #include "engine/alsadevicefinder.h" +#endif + +#ifdef HAVE_LIBPULSE + #include "engine/pulsedevicefinder.h" +#endif + +#ifdef Q_OS_DARWIN + #include "engine/osxdevicefinder.h" +#endif + +#ifdef Q_OS_WIN32 + #include "engine/directsounddevicefinder.h" +#endif + +#include "settings/backendsettingspage.h" + +using std::shared_ptr; +using std::vector; + +const char *GstEngine::kAutoSink = "autoaudiosink"; +const char *GstEngine::kALSASink = "alsasink"; +const char *GstEngine::kOSSSink = "osssink"; +const char *GstEngine::kOSS4Sink = "oss4sink"; +const char *GstEngine::kJackAudioSink = "jackaudiosink"; +const char *GstEngine::kPulseSink = "pulsesink"; +const char *GstEngine::kA2DPSink = "a2dpsink"; +const char *GstEngine::kAVDTPSink = "avdtpsink"; + +const char *GstEngine::kEnterprisePipeline = + "audiotestsrc wave=5 ! " + "audiocheblimit mode=0 cutoff=120"; + +GstEngine::GstEngine(TaskManager *task_manager) + : Engine::Base(), + task_manager_(task_manager), + buffering_task_id_(-1), + latest_buffer_(nullptr), + equalizer_enabled_(false), + stereo_balance_(0.0f), + rg_enabled_(false), + rg_mode_(0), + rg_preamp_(0.0), + rg_compression_(true), + buffer_duration_nanosec_(1 * kNsecPerSec), // 1s + buffer_min_fill_(33), + mono_playback_(false), + seek_timer_(new QTimer(this)), + timer_id_(-1), + next_element_id_(0), + is_fading_out_to_pause_(false), + has_faded_out_(false), + scope_chunk_(0), + have_new_buffer_(false) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + seek_timer_->setSingleShot(true); + seek_timer_->setInterval(kSeekDelayNanosec / kNsecPerMsec); + connect(seek_timer_, SIGNAL(timeout()), SLOT(SeekNow())); + + ReloadSettings(); + +#ifdef Q_OS_DARWIN + QDir resources_dir(mac::GetResourcesPath()); + QString ca_cert_path = resources_dir.filePath("cacert.pem"); + GError* error = nullptr; + tls_database_ = g_tls_file_database_new(ca_cert_path.toUtf8().data(), &error); +#endif + +} + +GstEngine::~GstEngine() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + EnsureInitialised(); + + current_pipeline_.reset(); + + // Save configuration + //gst_deinit(); + + //qDeleteAll(device_finders_); + +#ifdef Q_OS_DARWIN + g_object_unref(tls_database_); +#endif +} + +bool GstEngine::Init() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + SetEnvironment(); + + type_ = Engine::GStreamer; + initialising_ = QtConcurrent::run(this, &GstEngine::InitialiseGStreamer); + return true; + +} + +void GstEngine::InitialiseGStreamer() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + +#if 0 + gst_init(nullptr, nullptr); + gst_pb_utils_init(); +#endif + +#if 0 + QSet plugin_names; + for (const PluginDetails &plugin : GetPluginList("Sink/Audio")) { + plugin_names.insert(plugin.name); + } +#endif + +#if 0 + QList device_finders; + +#ifdef Q_OS_LINUX + device_finders.append(new AlsaDeviceFinder); +#endif +#ifdef HAVE_LIBPULSE + device_finders.append(new PulseDeviceFinder); +#endif +#ifdef Q_OS_DARWIN + device_finders.append(new OsxDeviceFinder); +#endif +#ifdef Q_OS_WIN32 + device_finders.append(new DirectSoundDeviceFinder); +#endif + + for (DeviceFinder *finder : device_finders) { + if (!plugin_names.contains(finder->gstreamer_sink())) { + qLog(Info) << "Skipping DeviceFinder for" << finder->gstreamer_sink() << "known plugins:" << plugin_names; + delete finder; + continue; + } + if (!finder->Initialise()) { + qLog(Warning) << "Failed to initialise DeviceFinder for" << finder->gstreamer_sink(); + delete finder; + continue; + } + + device_finders_.append(finder); + } + +#endif + +} + +void GstEngine::SetEnvironment() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QString scanner_path; + QString plugin_path; + QString registry_filename; + + // On windows and mac we bundle the gstreamer plugins with strawberry +#if defined(Q_OS_DARWIN) + scanner_path = QCoreApplication::applicationDirPath() + "/../PlugIns/gst-plugin-scanner"; + plugin_path = QCoreApplication::applicationDirPath() + "/../PlugIns/gstreamer"; +#elif defined(Q_OS_WIN32) + plugin_path = QCoreApplication::applicationDirPath() + "/gstreamer-plugins"; +#endif + +#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN) + registry_filename = Utilities::GetConfigPath(Utilities::Path_GstreamerRegistry); +#endif + + if (!scanner_path.isEmpty()) Utilities::SetEnv("GST_PLUGIN_SCANNER", scanner_path); + + if (!plugin_path.isEmpty()) { + Utilities::SetEnv("GST_PLUGIN_PATH", plugin_path); + // Never load plugins from anywhere else. + Utilities::SetEnv("GST_PLUGIN_SYSTEM_PATH", plugin_path); + } + + if (!registry_filename.isEmpty()) { + Utilities::SetEnv("GST_REGISTRY", registry_filename); + } + +#ifdef Q_OS_DARWIN + Utilities::SetEnv("GIO_EXTRA_MODULES", QCoreApplication::applicationDirPath() + "/../PlugIns/gio-modules"); +#endif + + Utilities::SetEnv("PULSE_PROP_media.role", "music"); + +} + +void GstEngine::ReloadSettings() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + Engine::Base::ReloadSettings(); + + QSettings s; + s.beginGroup(BackendSettingsPage::kSettingsGroup); + + sink_ = s.value("output", kAutoSink).toString(); + device_ = s.value("device"); + + if (sink_.isEmpty()) sink_ = kAutoSink; + + rg_enabled_ = s.value("rgenabled", false).toBool(); + rg_mode_ = s.value("rgmode", 0).toInt(); + rg_preamp_ = s.value("rgpreamp", 0.0).toDouble(); + rg_compression_ = s.value("rgcompression", true).toBool(); + + buffer_duration_nanosec_ = s.value("bufferduration", 4000).toLongLong() * kNsecPerMsec; + buffer_min_fill_ = s.value("bufferminfill", 33).toInt(); + mono_playback_ = s.value("monoplayback", false).toBool(); + +} + +qint64 GstEngine::position_nanosec() const { + + if (!current_pipeline_) return 0; + + const qint64 result = current_pipeline_->position() - beginning_nanosec_; + return qint64(qMax(0ll, result)); + +} + +qint64 GstEngine::length_nanosec() const { + + if (!current_pipeline_) return 0; + + const qint64 result = end_nanosec_ - beginning_nanosec_; + + if (result > 0) { + return result; + } + else { + // Get the length from the pipeline if we don't know. + return current_pipeline_->length(); + } + +} + +Engine::State GstEngine::state() const { + + if (!current_pipeline_) return url_.isEmpty() ? Engine::Empty : Engine::Idle; + + switch (current_pipeline_->state()) { + case GST_STATE_NULL: + return Engine::Empty; + case GST_STATE_READY: + return Engine::Idle; + case GST_STATE_PLAYING: + return Engine::Playing; + case GST_STATE_PAUSED: + return Engine::Paused; + default: + return Engine::Empty; + } +} + +void GstEngine::ConsumeBuffer(GstBuffer *buffer, int pipeline_id) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // Schedule this to run in the GUI thread. The buffer gets added to the + // queue and unreffed by UpdateScope. + if (!QMetaObject::invokeMethod(this, "AddBufferToScope", Q_ARG(GstBuffer*, buffer), Q_ARG(int, pipeline_id))) { + qLog(Warning) << "Failed to invoke AddBufferToScope on GstEngine"; + } + +} + +void GstEngine::AddBufferToScope(GstBuffer *buf, int pipeline_id) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (!current_pipeline_ || current_pipeline_->id() != pipeline_id) { + gst_buffer_unref(buf); + return; + } + + if (latest_buffer_ != nullptr) { + gst_buffer_unref(latest_buffer_); + } + + latest_buffer_ = buf; + have_new_buffer_ = true; +} + +const Engine::Scope &GstEngine::scope(int chunk_length) { + + // the new buffer could have a different size + if (have_new_buffer_) { + if (latest_buffer_ != nullptr) { + scope_chunks_ = ceil(((double)GST_BUFFER_DURATION(latest_buffer_) / (double)(chunk_length * kNsecPerMsec))); + } + + // if the buffer is shorter than the chunk length + if (scope_chunks_ <= 0) { + scope_chunks_ = 1; + } + + scope_chunk_ = 0; + have_new_buffer_ = false; + } + + if (latest_buffer_ != nullptr) { + UpdateScope(chunk_length); + } + + return scope_; +} + +void GstEngine::UpdateScope(int chunk_length) { + + typedef Engine::Scope::value_type sample_type; + + // prevent dbz or invalid chunk size + if (!GST_CLOCK_TIME_IS_VALID(GST_BUFFER_DURATION(latest_buffer_))) return; + if (GST_BUFFER_DURATION(latest_buffer_) == 0) return; + + GstMapInfo map; + gst_buffer_map(latest_buffer_, &map, GST_MAP_READ); + + // determine where to split the buffer + int chunk_density = (map.size * kNsecPerMsec) / GST_BUFFER_DURATION(latest_buffer_); + + int chunk_size = chunk_length * chunk_density; + + // in case a buffer doesn't arrive in time + if (scope_chunk_ >= scope_chunks_) { + scope_chunk_ = 0; + return; + } + + const sample_type *source = reinterpret_cast(map.data); + sample_type *dest = scope_.data(); + source += (chunk_size / sizeof(sample_type)) * scope_chunk_; + + int bytes = 0; + + // make sure we don't go beyond the end of the buffer + if (scope_chunk_ == scope_chunks_ - 1) { + bytes = qMin(static_cast(map.size - (chunk_size * scope_chunk_)), scope_.size() * sizeof(sample_type)); + } + else { + bytes = qMin(static_cast(chunk_size), scope_.size() * sizeof(sample_type)); + } + + scope_chunk_++; + memcpy(dest, source, bytes); + + gst_buffer_unmap(latest_buffer_, &map); + + if (scope_chunk_ == scope_chunks_) { + gst_buffer_unref(latest_buffer_); + latest_buffer_ = nullptr; + } +} + +void GstEngine::StartPreloading(const QUrl &url, bool force_stop_at_end, qint64 beginning_nanosec, qint64 end_nanosec) { + + EnsureInitialised(); + + QUrl gst_url = FixupUrl(url); + + // No crossfading, so we can just queue the new URL in the existing + // pipeline and get gapless playback (hopefully) + if (current_pipeline_) + current_pipeline_->SetNextUrl(gst_url, beginning_nanosec, force_stop_at_end ? end_nanosec : 0); +} + +QUrl GstEngine::FixupUrl(const QUrl &url) { + + QUrl copy = url; + + // It's a file:// url with a hostname set. QUrl::fromLocalFile does this + // when given a \\host\share\file path on Windows. Munge it back into a + // path that gstreamer will recognise. + if (url.scheme() == "file" && !url.host().isEmpty()) { + copy.setPath("//" + copy.host() + copy.path()); + copy.setHost(QString()); + } + + return copy; +} + +bool GstEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + EnsureInitialised(); + + Engine::Base::Load(url, change, force_stop_at_end, beginning_nanosec, end_nanosec); + + QUrl gst_url = FixupUrl(url); + + bool crossfade = current_pipeline_ && ((crossfade_enabled_ && change & Engine::Manual) || (autocrossfade_enabled_ && change & Engine::Auto) || ((crossfade_enabled_ || autocrossfade_enabled_) && change & Engine::Intro)); + + if (change & Engine::Auto && change & Engine::SameAlbum && !crossfade_same_album_) + crossfade = false; + + if (!crossfade && current_pipeline_ && current_pipeline_->url() == gst_url && change & Engine::Auto) { + // We're not crossfading, and the pipeline is already playing the URI we want, so just do nothing. + return true; + } + + //SetEqualizerEnabled(equalizer_enabled_); + //SetEqualizerParameters(equalizer_preamp_, equalizer_gains_); + //SetStereoBalance(stereo_balance_); + + shared_ptr pipeline = CreatePipeline(gst_url, force_stop_at_end ? end_nanosec : 0); + if (!pipeline) return false; + + if (crossfade) StartFadeout(); + + BufferingFinished(); + current_pipeline_ = pipeline; + + SetVolume(volume_); + SetEqualizerEnabled(equalizer_enabled_); + SetEqualizerParameters(equalizer_preamp_, equalizer_gains_); + SetStereoBalance(stereo_balance_); + + // Maybe fade in this track + if (crossfade) + current_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Forward); + + return true; +} + +void GstEngine::StartFadeout() { + + if (is_fading_out_to_pause_) return; + + fadeout_pipeline_ = current_pipeline_; + disconnect(fadeout_pipeline_.get(), 0, 0, 0); + fadeout_pipeline_->RemoveAllBufferConsumers(); + + fadeout_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Backward); + connect(fadeout_pipeline_.get(), SIGNAL(FaderFinished()), SLOT(FadeoutFinished())); +} + +void GstEngine::StartFadeoutPause() { + + fadeout_pause_pipeline_ = current_pipeline_; + disconnect(fadeout_pause_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0); + + fadeout_pause_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Backward, QTimeLine::EaseInOutCurve, false); + if (fadeout_pipeline_ && fadeout_pipeline_->state() == GST_STATE_PLAYING) { + fadeout_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Backward, QTimeLine::LinearCurve, false); + } + connect(fadeout_pause_pipeline_.get(), SIGNAL(FaderFinished()), SLOT(FadeoutPauseFinished())); + is_fading_out_to_pause_ = true; +} + +bool GstEngine::Play(quint64 offset_nanosec) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + EnsureInitialised(); + + if (!current_pipeline_ || current_pipeline_->is_buffering()) return false; + + QFuture future = current_pipeline_->SetState(GST_STATE_PLAYING); + NewClosure(future, this, SLOT(PlayDone(QFuture, quint64, int)), future, offset_nanosec, current_pipeline_->id()); + + if (is_fading_out_to_pause_) { + current_pipeline_->SetState(GST_STATE_PAUSED); + } + + return true; +} + +void GstEngine::PlayDone(QFuture future, const quint64 offset_nanosec, const int pipeline_id) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + GstStateChangeReturn ret = future.result(); + + if (!current_pipeline_ || pipeline_id != current_pipeline_->id()) { + return; + } + + if (ret == GST_STATE_CHANGE_FAILURE) { + // Failure, but we got a redirection URL - try loading that instead + QUrl redirect_url = current_pipeline_->redirect_url(); + if (!redirect_url.isEmpty() && redirect_url != current_pipeline_->url()) { + qLog(Info) << "Redirecting to" << redirect_url; + current_pipeline_ = CreatePipeline(redirect_url, end_nanosec_); + Play(offset_nanosec); + return; + } + + // Failure - give up + qLog(Warning) << "Could not set thread to PLAYING."; + current_pipeline_.reset(); + BufferingFinished(); + return; + } + + StartTimers(); + + // initial offset + if (offset_nanosec != 0 || beginning_nanosec_ != 0) { + Seek(offset_nanosec); + } + + emit StateChanged(Engine::Playing); + // we've successfully started playing a media stream with this url + emit ValidSongRequested(url_); +} + +void GstEngine::Stop(bool stop_after) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + StopTimers(); + + url_ = QUrl(); // To ensure we return Empty from state() + beginning_nanosec_ = end_nanosec_ = 0; + + // Check if we started a fade out. If it isn't finished yet and the user + // pressed stop, we cancel the fader and just stop the playback. + if (is_fading_out_to_pause_) { + disconnect(current_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0); + is_fading_out_to_pause_ = false; + has_faded_out_ = true; + + fadeout_pause_pipeline_.reset(); + fadeout_pipeline_.reset(); + } + + if (fadeout_enabled_ && current_pipeline_ && !stop_after) StartFadeout(); + + current_pipeline_.reset(); + BufferingFinished(); + emit StateChanged(Engine::Empty); +} + +void GstEngine::FadeoutFinished() { + fadeout_pipeline_.reset(); + emit FadeoutFinishedSignal(); +} + +void GstEngine::FadeoutPauseFinished() { + fadeout_pause_pipeline_->SetState(GST_STATE_PAUSED); + current_pipeline_->SetState(GST_STATE_PAUSED); + emit StateChanged(Engine::Paused); + StopTimers(); + + is_fading_out_to_pause_ = false; + has_faded_out_ = true; + fadeout_pause_pipeline_.reset(); + fadeout_pipeline_.reset(); + + emit FadeoutFinishedSignal(); +} + +void GstEngine::Pause() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (!current_pipeline_ || current_pipeline_->is_buffering()) return; + + // Check if we started a fade out. If it isn't finished yet and the user + // pressed play, we inverse the fader and resume the playback. + if (is_fading_out_to_pause_) { + disconnect(current_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0); + current_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Forward, QTimeLine::EaseInOutCurve, false); + is_fading_out_to_pause_ = false; + has_faded_out_ = false; + emit StateChanged(Engine::Playing); + return; + } + + if (current_pipeline_->state() == GST_STATE_PLAYING) { + if (fadeout_pause_enabled_) { + StartFadeoutPause(); + } + else { + current_pipeline_->SetState(GST_STATE_PAUSED); + emit StateChanged(Engine::Paused); + StopTimers(); + } + } +} + +void GstEngine::Unpause() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (!current_pipeline_ || current_pipeline_->is_buffering()) return; + + if (current_pipeline_->state() == GST_STATE_PAUSED) { + current_pipeline_->SetState(GST_STATE_PLAYING); + + // Check if we faded out last time. If yes, fade in no matter what the + // settings say. If we pause with fadeout, deactivate fadeout and resume + // playback, the player would be muted if not faded in. + if (has_faded_out_) { + disconnect(current_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0); + current_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Forward, QTimeLine::EaseInOutCurve, false); + has_faded_out_ = false; + } + + emit StateChanged(Engine::Playing); + + StartTimers(); + } +} + +void GstEngine::Seek(quint64 offset_nanosec) { + + if (!current_pipeline_) return; + + seek_pos_ = beginning_nanosec_ + offset_nanosec; + waiting_to_seek_ = true; + + if (!seek_timer_->isActive()) { + SeekNow(); + seek_timer_->start(); // Stop us from seeking again for a little while + } +} + +void GstEngine::SeekNow() { + + if (!waiting_to_seek_) return; + waiting_to_seek_ = false; + + if (!current_pipeline_) return; + + if (!current_pipeline_->Seek(seek_pos_)) { + qLog(Warning) << "Seek failed"; + } +} + +void GstEngine::SetEqualizerEnabled(bool enabled) { + + //qLog(Debug) << "equalizer ENABLED: " << enabled; + + equalizer_enabled_ = enabled; + + if (current_pipeline_) current_pipeline_->SetEqualizerEnabled(enabled); +} + +void GstEngine::SetEqualizerParameters(int preamp, const QList &band_gains) { + + equalizer_preamp_ = preamp; + equalizer_gains_ = band_gains; + + if (current_pipeline_) + current_pipeline_->SetEqualizerParams(preamp, band_gains); +} + +void GstEngine::SetStereoBalance(float value) { + + stereo_balance_ = value; + + if (current_pipeline_) current_pipeline_->SetStereoBalance(value); +} + +void GstEngine::SetVolumeSW(uint percent) { + if (current_pipeline_) current_pipeline_->SetVolume(percent); +} + +void GstEngine::StartTimers() { + StopTimers(); + + timer_id_ = startTimer(kTimerIntervalNanosec / kNsecPerMsec); +} + +void GstEngine::StopTimers() { + if (timer_id_ != -1) { + killTimer(timer_id_); + timer_id_ = -1; + } +} + +void GstEngine::timerEvent(QTimerEvent *e) { + + if (e->timerId() != timer_id_) return; + + if (current_pipeline_) { + const qint64 current_position = position_nanosec(); + const qint64 current_length = length_nanosec(); + + const qint64 remaining = current_length - current_position; + + const qint64 fudge = kTimerIntervalNanosec + 100 * kNsecPerMsec; // Mmm fudge + const qint64 gap = buffer_duration_nanosec_ + (autocrossfade_enabled_ ? fadeout_duration_nanosec_ : kPreloadGapNanosec); + + // only if we know the length of the current stream... + if (current_length > 0) { + // emit TrackAboutToEnd when we're a few seconds away from finishing + if (remaining < gap + fudge) { + EmitAboutToEnd(); + } + } + } +} + +void GstEngine::HandlePipelineError(int pipeline_id, const QString &message, int domain, int error_code) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id) + return; + + qLog(Warning) << "Gstreamer error:" << message; + + current_pipeline_.reset(); + + BufferingFinished(); + emit StateChanged(Engine::Error); + // unable to play media stream with this url + emit InvalidSongRequested(url_); + + // TODO: the types of errors listed below won't be shown to user - they will + // get logged and the current song will be skipped; instead of maintaining + // the list we should probably: + // - don't report any engine's errors to user (always just log and skip) + // - come up with a less intrusive error box (not a dialog but a notification + // popup of some kind) and then report all errors + if (!(domain == GST_RESOURCE_ERROR && + error_code == GST_RESOURCE_ERROR_NOT_FOUND) && + !(domain == GST_STREAM_ERROR && + error_code == GST_STREAM_ERROR_TYPE_NOT_FOUND) && + !(domain == GST_RESOURCE_ERROR && + error_code == GST_RESOURCE_ERROR_OPEN_READ)) { + emit Error(message); + } +} + +void GstEngine::EndOfStreamReached(int pipeline_id, bool has_next_track) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id) + return; + + if (!has_next_track) { + current_pipeline_.reset(); + BufferingFinished(); + } + emit TrackEnded(); +} + +void GstEngine::NewMetaData(int pipeline_id, const Engine::SimpleMetaBundle &bundle) { + + if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id) + return; + + emit MetaData(bundle); +} + +GstElement *GstEngine::CreateElement(const QString &factoryName, GstElement *bin) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // Make a unique name + QString name = factoryName + "-" + QString::number(next_element_id_++); + + GstElement *element = gst_element_factory_make(factoryName.toLatin1().constData(), name.toLatin1().constData()); + + if (!element) { + emit Error(QString("GStreamer could not create the element: %1. Please make sure that you have installed all necessary GStreamer plugins").arg(factoryName)); + gst_object_unref(GST_OBJECT(bin)); + return nullptr; + } + + if (bin) gst_bin_add(GST_BIN(bin), element); + + return element; +} + +GstEngine::PluginDetailsList GstEngine::GetPluginList(const QString &classname) const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + PluginDetailsList ret; + + GstRegistry *registry = gst_registry_get(); + GList *const features = gst_registry_get_feature_list(registry, GST_TYPE_ELEMENT_FACTORY); + + //qLog(Debug) << __PRETTY_FUNCTION__ << registry << features; + + GList *p = features; + while (p) { + GstElementFactory *factory = GST_ELEMENT_FACTORY(p->data); + if (QString(gst_element_factory_get_klass(factory)).contains(classname)) {; + PluginDetails details; + details.name = QString::fromUtf8(gst_plugin_feature_get_name(p->data)); + details.description = QString::fromUtf8(gst_element_factory_get_metadata(factory, GST_ELEMENT_METADATA_DESCRIPTION)); + ret << details; + //qLog(Debug) << details.name << details.description; + } + p = g_list_next(p); + } + + gst_plugin_feature_list_free(features); + return ret; + +} + +shared_ptr GstEngine::CreatePipeline() { + + //qLog(Debug) << __PRETTY_FUNCTION__ << sink_ << device_; + + EnsureInitialised(); + + shared_ptr ret(new GstEnginePipeline(this)); + ret->set_output_device(sink_, device_); + ret->set_replaygain(rg_enabled_, rg_mode_, rg_preamp_, rg_compression_); + ret->set_buffer_duration_nanosec(buffer_duration_nanosec_); + ret->set_buffer_min_fill(buffer_min_fill_); + ret->set_mono_playback(mono_playback_); + + ret->AddBufferConsumer(this); + for (BufferConsumer *consumer : buffer_consumers_) { + ret->AddBufferConsumer(consumer); + } + + connect(ret.get(), SIGNAL(EndOfStreamReached(int, bool)), SLOT(EndOfStreamReached(int, bool))); + connect(ret.get(), SIGNAL(Error(int, QString, int, int)), SLOT(HandlePipelineError(int, QString, int, int))); + connect(ret.get(), SIGNAL(MetadataFound(int, Engine::SimpleMetaBundle)), SLOT(NewMetaData(int, Engine::SimpleMetaBundle))); + connect(ret.get(), SIGNAL(BufferingStarted()), SLOT(BufferingStarted())); + connect(ret.get(), SIGNAL(BufferingProgress(int)), SLOT(BufferingProgress(int))); + connect(ret.get(), SIGNAL(BufferingFinished()), SLOT(BufferingFinished())); + + return ret; +} + +shared_ptr GstEngine::CreatePipeline(const QUrl &url, qint64 end_nanosec) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + shared_ptr ret = CreatePipeline(); + + if (url.scheme() == "enterprise") { + ret->InitFromString(kEnterprisePipeline); + return ret; + } + + if (!ret->InitFromUrl(url, end_nanosec)) ret.reset(); + + return ret; +} + +bool GstEngine::ALSADeviceSupport(const QString &name) { + + return (name == kALSASink || name == kOSSSink || name == kPulseSink); + +} + +void GstEngine::AddBufferConsumer(BufferConsumer *consumer) { + buffer_consumers_ << consumer; + if (current_pipeline_) current_pipeline_->AddBufferConsumer(consumer); +} + +void GstEngine::RemoveBufferConsumer(BufferConsumer *consumer) { + buffer_consumers_.removeAll(consumer); + if (current_pipeline_) current_pipeline_->RemoveBufferConsumer(consumer); +} + +void GstEngine::BufferingStarted() { + + if (buffering_task_id_ != -1) { + task_manager_->SetTaskFinished(buffering_task_id_); + } + + buffering_task_id_ = task_manager_->StartTask(tr("Buffering")); + task_manager_->SetTaskProgress(buffering_task_id_, 0, 100); +} + +void GstEngine::BufferingProgress(int percent) { + task_manager_->SetTaskProgress(buffering_task_id_, percent, 100); +} + +void GstEngine::BufferingFinished() { + if (buffering_task_id_ != -1) { + task_manager_->SetTaskFinished(buffering_task_id_); + buffering_task_id_ = -1; + } +} + +EngineBase::OutputDetailsList GstEngine::GetOutputsList() const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + //const_cast(this)->EnsureInitialised(); + + EngineBase::OutputDetailsList ret; + +#if 0 + for (DeviceFinder *finder : device_finders_) { + for (const DeviceFinder::Device &device : finder->ListDevices()) { + OutputDetails output; + output.description = device.description; + output.icon_name = device.icon_name; + output.name = finder->gstreamer_sink(); + output.device_property_value = device.device_property_value; + ret.append(output); + } + } +#endif + + PluginDetailsList plugins = GetPluginList("Sink/Audio"); + for (const PluginDetails &plugin : plugins) { + OutputDetails output; + output.name = plugin.name; + output.description = plugin.description; + if (plugin.name == kAutoSink) output.iconname = "soundcard"; + else if ((plugin.name == kALSASink) || (plugin.name == kOSS4Sink) || (plugin.name == kOSS4Sink)) output.iconname = "alsa"; + else if (plugin.name== kJackAudioSink) output.iconname = "jack"; + else if (plugin.name == kPulseSink) output.iconname = "pulseaudio"; + else if ((plugin.name == kA2DPSink) || (plugin.name == kAVDTPSink)) output.iconname = "bluetooth"; + else output.iconname = "soundcard"; + ret.append(output); + } + + return ret; +} + diff --git a/src/engine/gstengine.h b/src/engine/gstengine.h new file mode 100644 index 00000000..9b965743 --- /dev/null +++ b/src/engine/gstengine.h @@ -0,0 +1,220 @@ +/*************************************************************************** + * Copyright (C) 2003-2005 by Mark Kretschmann * + * Copyright (C) 2005 by Jakub Stachowski * + * Portions Copyright (C) 2006 Paul Cifarelli * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef GSTENGINE_H +#define GSTENGINE_H + +#include "config.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "bufferconsumer.h" +#include "enginebase.h" +#include "core/timeconstants.h" + +class QTimer; +class QTimerEvent; + +class GstEnginePipeline; +class TaskManager; + +#ifdef Q_OS_DARWIN +struct _GTlsDatabase; +typedef struct _GTlsDatabase GTlsDatabase; +#endif + +/** + * @class GstEngine + * @short GStreamer engine plugin + * @author Mark Kretschmann + */ +class GstEngine : public Engine::Base, public BufferConsumer { + Q_OBJECT + + public: + GstEngine(TaskManager *task_manager); + ~GstEngine(); + + static const char *kAutoSink; + static const char *kALSASink; + static const char *kOSSSink; + static const char *kOSS4Sink; + static const char *kJackAudioSink; + static const char *kPulseSink; + static const char *kA2DPSink; + static const char *kAVDTPSink; + + bool Init(); + void EnsureInitialised() { initialising_.waitForFinished(); } + void InitialiseGStreamer(); + void SetEnvironment(); + + OutputDetailsList GetOutputsList() const; + + qint64 position_nanosec() const; + qint64 length_nanosec() const; + Engine::State state() const; + const Engine::Scope &scope(int chunk_length); + + static bool ALSADeviceSupport(const QString &name); + + GstElement *CreateElement(const QString &factoryName, GstElement *bin = 0); + + // BufferConsumer + void ConsumeBuffer(GstBuffer *buffer, int pipeline_id); + + bool IsEqualizerEnabled() { return equalizer_enabled_; } + + public slots: + void StartPreloading(const QUrl &url, bool force_stop_at_end, qint64 beginning_nanosec, qint64 end_nanosec); + bool Load(const QUrl &, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec); + bool Play(quint64 offset_nanosec); + void Stop(bool stop_after = false); + void Pause(); + void Unpause(); + void Seek(quint64 offset_nanosec); + + /** Set whether equalizer is enabled */ + void SetEqualizerEnabled(bool); + + /** Set equalizer preamp and gains, range -100..100. Gains are 10 values. */ + void SetEqualizerParameters(int preamp, const QList &bandGains); + + /** Set Stereo balance, range -1.0f..1.0f */ + void SetStereoBalance(float value); + + void ReloadSettings(); + + void AddBufferConsumer(BufferConsumer *consumer); + void RemoveBufferConsumer(BufferConsumer *consumer); + +#ifdef Q_OS_DARWIN + GTlsDatabase *tls_database() const { return tls_database_; } +#endif + + protected: + void SetVolumeSW(uint percent); + void timerEvent(QTimerEvent*); + + private slots: + void EndOfStreamReached(int pipeline_id, bool has_next_track); + void HandlePipelineError(int pipeline_id, const QString &message, int domain, int error_code); + void NewMetaData(int pipeline_id, const Engine::SimpleMetaBundle &bundle); + void AddBufferToScope(GstBuffer *buf, int pipeline_id); + void FadeoutFinished(); + void FadeoutPauseFinished(); + void SeekNow(); + void PlayDone(QFuture future, const quint64, const int); + + void BufferingStarted(); + void BufferingProgress(int percent); + void BufferingFinished(); + + private: + + PluginDetailsList GetPluginList(const QString &classname) const; + + void StartFadeout(); + void StartFadeoutPause(); + + void StartTimers(); + void StopTimers(); + + std::shared_ptr CreatePipeline(); + std::shared_ptr CreatePipeline(const QUrl &url, qint64 end_nanosec); + + void UpdateScope(int chunk_length); + + static QUrl FixupUrl(const QUrl &url); + + private: + static const qint64 kTimerIntervalNanosec = 1000 *kNsecPerMsec; // 1s + static const qint64 kPreloadGapNanosec = 2000 *kNsecPerMsec; // 2s + static const qint64 kSeekDelayNanosec = 100 *kNsecPerMsec; // 100msec + + static const char *kHypnotoadPipeline; + static const char *kEnterprisePipeline; + + TaskManager *task_manager_; + int buffering_task_id_; + + QFuture initialising_; + + QString sink_; + QVariant device_; + + std::shared_ptr current_pipeline_; + std::shared_ptr fadeout_pipeline_; + std::shared_ptr fadeout_pause_pipeline_; + QUrl preloaded_url_; + + QList buffer_consumers_; + + GstBuffer *latest_buffer_; + + bool equalizer_enabled_; + int equalizer_preamp_; + QList equalizer_gains_; + float stereo_balance_; + + bool rg_enabled_; + int rg_mode_; + float rg_preamp_; + bool rg_compression_; + + qint64 buffer_duration_nanosec_; + + int buffer_min_fill_; + + bool mono_playback_; + + mutable bool can_decode_success_; + mutable bool can_decode_last_; + + // Hack to stop seeks happening too often + QTimer *seek_timer_; + bool waiting_to_seek_; + quint64 seek_pos_; + + int timer_id_; + int next_element_id_; + + bool is_fading_out_to_pause_; + bool has_faded_out_; + + int scope_chunk_; + bool have_new_buffer_; + int scope_chunks_; + +}; + +//Q_DECLARE_METATYPE(GstEngine::OutputDetails) + +#endif /* GSTENGINE_H */ diff --git a/src/engine/gstenginepipeline.cpp b/src/engine/gstenginepipeline.cpp new file mode 100644 index 00000000..fea133d6 --- /dev/null +++ b/src/engine/gstenginepipeline.cpp @@ -0,0 +1,1199 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include +#include +#include +#include +#include + +#include "bufferconsumer.h" +#include "gstelementdeleter.h" +#include "gstengine.h" +#include "gstenginepipeline.h" +#include "core/concurrentrun.h" +#include "core/logging.h" +#include "core/mac_startup.h" +#include "core/signalchecker.h" +#include "core/utilities.h" + +const int GstEnginePipeline::kGstStateTimeoutNanosecs = 10000000; +const int GstEnginePipeline::kFaderFudgeMsec = 2000; + +const int GstEnginePipeline::kEqBandCount = 10; +const int GstEnginePipeline::kEqBandFrequencies[] = { + 60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000}; + +int GstEnginePipeline::sId = 1; +GstElementDeleter *GstEnginePipeline::sElementDeleter = nullptr; + +GstEnginePipeline::GstEnginePipeline(GstEngine *engine) + : QObject(nullptr), + engine_(engine), + id_(sId++), + valid_(false), + sink_(GstEngine::kAutoSink), + segment_start_(0), + segment_start_received_(false), + emit_track_ended_on_stream_start_(false), + emit_track_ended_on_time_discontinuity_(false), + last_buffer_offset_(0), + eq_enabled_(false), + eq_preamp_(0), + stereo_balance_(0.0f), + rg_enabled_(false), + rg_mode_(0), + rg_preamp_(0.0), + rg_compression_(true), + buffer_duration_nanosec_(1 * kNsecPerSec), + buffer_min_fill_(33), + buffering_(false), + mono_playback_(false), + end_offset_nanosec_(-1), + next_beginning_offset_nanosec_(-1), + next_end_offset_nanosec_(-1), + ignore_next_seek_(false), + ignore_tags_(false), + pipeline_is_initialised_(false), + pipeline_is_connected_(false), + pending_seek_nanosec_(-1), + last_known_position_ns_(0), + volume_percent_(100), + volume_modifier_(1.0), + pipeline_(nullptr), + uridecodebin_(nullptr), + audiobin_(nullptr), + queue_(nullptr), + audioconvert_(nullptr), + rgvolume_(nullptr), + rglimiter_(nullptr), + audioconvert2_(nullptr), + equalizer_(nullptr), + stereo_panorama_(nullptr), + volume_(nullptr), + audioscale_(nullptr), + audiosink_(nullptr) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + if (!sElementDeleter) { + sElementDeleter = new GstElementDeleter; + } + + for (int i = 0; i < kEqBandCount; ++i) eq_band_gains_ << 0; + +} + +void GstEnginePipeline::set_output_device(const QString &sink, const QVariant &device) { + + //qLog(Info) << __PRETTY_FUNCTION__ << sink << device ; + + sink_ = sink; + device_ = device; + +} + +void GstEnginePipeline::set_replaygain(bool enabled, int mode, float preamp, bool compression) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + rg_enabled_ = enabled; + rg_mode_ = mode; + rg_preamp_ = preamp; + rg_compression_ = compression; +} + +void GstEnginePipeline::set_buffer_duration_nanosec(qint64 buffer_duration_nanosec) { + buffer_duration_nanosec_ = buffer_duration_nanosec; +} + +void GstEnginePipeline::set_buffer_min_fill(int percent) { + buffer_min_fill_ = percent; +} + +void GstEnginePipeline::set_mono_playback(bool enabled) { + mono_playback_ = enabled; +} + +bool GstEnginePipeline::ReplaceDecodeBin(GstElement *new_bin) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + if (!new_bin) return false; + + // Destroy the old elements if they are set + // Note that the caller to this function MUST schedule the old uridecodebin_ + // for deletion in the main thread. + if (uridecodebin_) { + gst_bin_remove(GST_BIN(pipeline_), uridecodebin_); + } + + uridecodebin_ = new_bin; + segment_start_ = 0; + segment_start_received_ = false; + pipeline_is_connected_ = false; + gst_bin_add(GST_BIN(pipeline_), uridecodebin_); + + return true; +} + +bool GstEnginePipeline::ReplaceDecodeBin(const QUrl& url) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + GstElement *new_bin = nullptr; + + new_bin = engine_->CreateElement("uridecodebin"); + g_object_set(G_OBJECT(new_bin), "uri", url.toEncoded().constData(), nullptr); + CHECKED_GCONNECT(G_OBJECT(new_bin), "drained", &SourceDrainedCallback, this); + CHECKED_GCONNECT(G_OBJECT(new_bin), "pad-added", &NewPadCallback, this); + CHECKED_GCONNECT(G_OBJECT(new_bin), "notify::source", &SourceSetupCallback, this); + + return ReplaceDecodeBin(new_bin); +} + +GstElement *GstEnginePipeline::CreateDecodeBinFromString(const char *pipeline) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + GError *error = nullptr; + GstElement *bin = gst_parse_bin_from_description(pipeline, TRUE, &error); + + if (error) { + QString message = QString::fromLocal8Bit(error->message); + int domain = error->domain; + int code = error->code; + g_error_free(error); + + qLog(Warning) << message; + emit Error(id(), message, domain, code); + + return nullptr; + } + else { + return bin; + } +} + +bool GstEnginePipeline::Init() { + + //qLog(Info) << __PRETTY_FUNCTION__; + + // Here we create all the parts of the gstreamer pipeline - from the source to the sink. The parts of the pipeline are split up into bins: + // uri decode bin -> audio bin + // The uri decode bin is a gstreamer builtin that automatically picks the right type of source and decoder for the URI. + + // The audio bin gets created here and contains: + // queue ! audioconvert ! ! ( rgvolume ! rglimiter ! audioconvert2 ) ! tee + // rgvolume and rglimiter are only created when replaygain is enabled. + + // After the tee the pipeline splits. One split is converted to 16-bit int samples for the scope, the other is kept as float32 and sent to the speaker. + // tee1 ! probe_queue ! probe_converter ! ! probe_sink + // tee2 ! audio_queue ! equalizer_preamp ! equalizer ! volume ! audioscale + // ! convert ! audiosink + + gst_segment_init(&last_decodebin_segment_, GST_FORMAT_TIME); + + // Audio bin + audiobin_ = gst_bin_new("audiobin"); + //audiobin_ = gst_bin_new("playbackbin"); + gst_bin_add(GST_BIN(pipeline_), audiobin_); + + // Create the sink + if (!(audiosink_ = engine_->CreateElement(sink_, audiobin_))) return false; + + //if (GstEngine::SinkDeviceSupport(sink_) && !device_.isEmpty()) + //g_object_set(G_OBJECT(audiosink_), "device", device_.toUtf8().constData(), NULL); + + + if (g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink_), "device") && !device_.toString().isEmpty()) { + + switch (device_.type()) { + case QVariant::Int: + g_object_set(G_OBJECT(audiosink_), "device", device_.toInt(), nullptr); + break; + case QVariant::String: + //qLog(Debug) << device_.toString().toUtf8().constData(); + //qLog(Info) << "g_object_set: " << device_.toString().toUtf8().constData(); + //g_object_set(G_OBJECT(audiosink_), "device", device_.toString().toUtf8().constData(), nullptr); + g_object_set(audiosink_, "device", device_.toString().toUtf8().constData(), nullptr); + //g_object_set(G_OBJECT(audiosink_), "device", "hw:0,0", nullptr); + break; + +#ifdef Q_OS_WIN32 + case QVariant::ByteArray: { + GUID guid = QUuid(device_.toByteArray()); + g_object_set(G_OBJECT(audiosink_), "device", &guid, nullptr); + break; + } +#endif // Q_OS_WIN32 + + default: + qLog(Warning) << "Unknown device type" << device_; + break; + } + } + + // Create all the other elements + GstElement *tee, *probe_queue, *probe_converter, *probe_sink, *audio_queue, *convert; + + queue_ = engine_->CreateElement("queue2", audiobin_); + audioconvert_ = engine_->CreateElement("audioconvert", audiobin_); + tee = engine_->CreateElement("tee", audiobin_); + + probe_queue = engine_->CreateElement("queue", audiobin_); + probe_converter = engine_->CreateElement("audioconvert", audiobin_); + probe_sink = engine_->CreateElement("fakesink", audiobin_); + + audio_queue = engine_->CreateElement("queue", audiobin_); + equalizer_preamp_ = engine_->CreateElement("volume", audiobin_); + equalizer_ = engine_->CreateElement("equalizer-nbands", audiobin_); + stereo_panorama_ = engine_->CreateElement("audiopanorama", audiobin_); + volume_ = engine_->CreateElement("volume", audiobin_); + audioscale_ = engine_->CreateElement("audioresample", audiobin_); + convert = engine_->CreateElement("audioconvert", audiobin_); + + if (!queue_ || !audioconvert_ || !tee || !probe_queue || !probe_converter || !probe_sink || !audio_queue || !equalizer_preamp_ || !equalizer_ || !stereo_panorama_ || !volume_ || !audioscale_ || !convert) { + return false; + } + + // Create the replaygain elements if it's enabled. event_probe is the + // audioconvert element we attach the probe to, which will change depending + // on whether replaygain is enabled. convert_sink is the element after the + // first audioconvert, which again will change. + GstElement *event_probe = audioconvert_; + GstElement *convert_sink = tee; + + if (rg_enabled_) { + rgvolume_ = engine_->CreateElement("rgvolume", audiobin_); + rglimiter_ = engine_->CreateElement("rglimiter", audiobin_); + audioconvert2_ = engine_->CreateElement("audioconvert", audiobin_); + event_probe = audioconvert2_; + convert_sink = rgvolume_; + + if (!rgvolume_ || !rglimiter_ || !audioconvert2_) { + return false; + } + + // Set replaygain settings + g_object_set(G_OBJECT(rgvolume_), "album-mode", rg_mode_, nullptr); + g_object_set(G_OBJECT(rgvolume_), "pre-amp", double(rg_preamp_), nullptr); + g_object_set(G_OBJECT(rglimiter_), "enabled", int(rg_compression_), nullptr); + } + + // Create a pad on the outside of the audiobin and connect it to the pad of the first element. + GstPad *pad = gst_element_get_static_pad(queue_, "sink"); + gst_element_add_pad(audiobin_, gst_ghost_pad_new("sink", pad)); + gst_object_unref(pad); + + // Add a data probe on the src pad of the audioconvert element for our scope. + // We do it here because we want pre-equalized and pre-volume samples + // so that our visualization are not be affected by them. + pad = gst_element_get_static_pad(event_probe, "src"); + gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, &EventHandoffCallback, this, NULL); + gst_object_unref(pad); + + // Configure the fakesink properly + g_object_set(G_OBJECT(probe_sink), "sync", TRUE, nullptr); + + // Setting the equalizer bands: + // + // GStreamer's GstIirEqualizerNBands sets up shelve filters for the first and + // last bands as corner cases. That was causing the "inverted slider" bug. + // As a workaround, we create two dummy bands at both ends of the spectrum. + // This causes the actual first and last adjustable bands to be + // implemented using band-pass filters. + + g_object_set(G_OBJECT(equalizer_), "num-bands", 10 + 2, nullptr); + + // Dummy first band (bandwidth 0, cutting below 20Hz): + GstObject *first_band = GST_OBJECT(gst_child_proxy_get_child_by_index(GST_CHILD_PROXY(equalizer_), 0)); + g_object_set(G_OBJECT(first_band), "freq", 20.0, "bandwidth", 0, "gain", 0.0f, nullptr); + g_object_unref(G_OBJECT(first_band)); + + // Dummy last band (bandwidth 0, cutting over 20KHz): + GstObject *last_band = GST_OBJECT(gst_child_proxy_get_child_by_index(GST_CHILD_PROXY(equalizer_), kEqBandCount + 1)); + g_object_set(G_OBJECT(last_band), "freq", 20000.0, "bandwidth", 0, "gain", 0.0f, nullptr); + g_object_unref(G_OBJECT(last_band)); + + int last_band_frequency = 0; + for (int i = 0; i < kEqBandCount; ++i) { + const int index_in_eq = i + 1; + GstObject *band = GST_OBJECT(gst_child_proxy_get_child_by_index(GST_CHILD_PROXY(equalizer_), index_in_eq)); + + const float frequency = kEqBandFrequencies[i]; + const float bandwidth = frequency - last_band_frequency; + last_band_frequency = frequency; + + g_object_set(G_OBJECT(band), "freq", frequency, "bandwidth", bandwidth, "gain", 0.0f, nullptr); + g_object_unref(G_OBJECT(band)); + } + + // Set the stereo balance. + g_object_set(G_OBJECT(stereo_panorama_), "panorama", stereo_balance_, nullptr); + + // Set the buffer duration. We set this on this queue instead of the + // decode bin (in ReplaceDecodeBin()) because setting it on the decode bin + // only affects network sources. + // Disable the default buffer and byte limits, so we only buffer based on + // time. + + g_object_set(G_OBJECT(queue_), "max-size-buffers", 0, nullptr); + g_object_set(G_OBJECT(queue_), "max-size-bytes", 0, nullptr); + g_object_set(G_OBJECT(queue_), "max-size-time", buffer_duration_nanosec_, nullptr); + g_object_set(G_OBJECT(queue_), "low-percent", buffer_min_fill_, nullptr); + + if (buffer_duration_nanosec_ > 0) { + g_object_set(G_OBJECT(queue_), "use-buffering", true, nullptr); + } + + gst_element_link_many(queue_, audioconvert_, convert_sink, nullptr); + gst_element_link(probe_converter, probe_sink); + + // Link the outputs of tee to the queues on each path. + gst_pad_link(gst_element_get_request_pad(tee, "src_%u"), gst_element_get_static_pad(probe_queue, "sink")); + gst_pad_link(gst_element_get_request_pad(tee, "src_%u"), gst_element_get_static_pad(audio_queue, "sink")); + + // Link replaygain elements if enabled. + if (rg_enabled_) { + gst_element_link_many(rgvolume_, rglimiter_, audioconvert2_, tee, nullptr); + } + + // Link the analyzer output of the tee and force 16 bit caps + //GstCaps* caps16 = gst_caps_new_simple("audio/x-raw", "format", G_TYPE_STRING, "S16LE", NULL); + //gst_element_link_filtered(probe_queue, probe_converter, caps16); + //gst_caps_unref(caps16); + + // Don't force 16 bit. + gst_element_link(probe_queue, probe_converter); + + if (engine_->IsEqualizerEnabled()) gst_element_link_many(audio_queue, equalizer_preamp_, equalizer_, stereo_panorama_, volume_, audioscale_, convert, nullptr); + else gst_element_link_many(audio_queue, volume_, audioscale_, convert, nullptr); + + // Let the audio output of the tee autonegotiate the bit depth and format. + //GstCaps *caps = gst_caps_new_simple("audio/x-raw", "format", G_TYPE_STRING, "F32LE", NULL); + GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw"); + + gst_element_link_filtered(convert, audiosink_, caps); + gst_caps_unref(caps); + + // Add probes and handlers. + gst_pad_add_probe(gst_element_get_static_pad(probe_converter, "src"), GST_PAD_PROBE_TYPE_BUFFER, HandoffCallback, this, nullptr); + gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), BusCallbackSync, this, nullptr); + bus_cb_id_ = gst_bus_add_watch(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), BusCallback, this); + + MaybeLinkDecodeToAudio(); + + return true; + +} + +void GstEnginePipeline::MaybeLinkDecodeToAudio() { + + //qLog(Info) << __PRETTY_FUNCTION__; + + if (!uridecodebin_ || !audiobin_) return; + + GstPad *pad = gst_element_get_static_pad(uridecodebin_, "src"); + if (!pad) return; + + gst_object_unref(pad); + gst_element_link(uridecodebin_, audiobin_); +} + +bool GstEnginePipeline::InitFromString(const QString &pipeline) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + pipeline_ = gst_pipeline_new("pipeline"); + + GstElement *new_bin = CreateDecodeBinFromString(pipeline.toLatin1().constData()); + if (!new_bin) { + return false; + } + + if (!ReplaceDecodeBin(new_bin)) return false; + + if (!Init()) return false; + return gst_element_link(new_bin, audiobin_); +} + +bool GstEnginePipeline::InitFromUrl(const QUrl &url, qint64 end_nanosec) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + pipeline_ = gst_pipeline_new("pipeline"); + + if (url.scheme() == "cdda" && !url.path().isEmpty()) { + // Currently, Gstreamer can't handle input CD devices inside cdda URL. So + // we handle them ourselve: we extract the track number and re-create an + // URL with only cdda:// + the track number (which can be handled by + // Gstreamer). We keep the device in mind, and we will set it later using + // SourceSetupCallback + QStringList path = url.path().split('/'); + url_ = QUrl(QString("cdda://%1").arg(path.takeLast())); + source_device_ = path.join("/"); + } + else { + url_ = url; + } + end_offset_nanosec_ = end_nanosec; + + // Decode bin + if (!ReplaceDecodeBin(url_)) return false; + + return Init(); + +} + +GstEnginePipeline::~GstEnginePipeline() { + + //qLog(Info) << __PRETTY_FUNCTION__; + + if (pipeline_) { + gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), nullptr, nullptr, nullptr); + g_source_remove(bus_cb_id_); + gst_element_set_state(pipeline_, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(pipeline_)); + } +} + +gboolean GstEnginePipeline::BusCallback(GstBus*, GstMessage *msg, gpointer self) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + GstEnginePipeline *instance = reinterpret_cast(self); + + qLog(Debug) << instance->id() << "bus message" << GST_MESSAGE_TYPE_NAME(msg); + + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_ERROR: + instance->ErrorMessageReceived(msg); + break; + + case GST_MESSAGE_TAG: + instance->TagMessageReceived(msg); + break; + + case GST_MESSAGE_STATE_CHANGED: + instance->StateChangedMessageReceived(msg); + break; + + default: + break; + } + + return FALSE; + +} + +GstBusSyncReply GstEnginePipeline::BusCallbackSync(GstBus*, GstMessage *msg, gpointer self) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + GstEnginePipeline *instance = reinterpret_cast(self); + + qLog(Debug) << instance->id() << "sync bus message" << GST_MESSAGE_TYPE_NAME(msg); + + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: + emit instance->EndOfStreamReached(instance->id(), false); + break; + + case GST_MESSAGE_TAG: + instance->TagMessageReceived(msg); + break; + + case GST_MESSAGE_ERROR: + instance->ErrorMessageReceived(msg); + break; + + case GST_MESSAGE_ELEMENT: + instance->ElementMessageReceived(msg); + break; + + case GST_MESSAGE_STATE_CHANGED: + instance->StateChangedMessageReceived(msg); + break; + + case GST_MESSAGE_BUFFERING: + instance->BufferingMessageReceived(msg); + break; + + case GST_MESSAGE_STREAM_STATUS: + instance->StreamStatusMessageReceived(msg); + break; + + case GST_MESSAGE_STREAM_START: + if (instance->emit_track_ended_on_stream_start_) { + qLog(Debug) << "New segment started, EOS will signal on next buffer discontinuity"; + instance->emit_track_ended_on_stream_start_ = false; + instance->emit_track_ended_on_time_discontinuity_ = true; + } + break; + + default: + break; + } + + return GST_BUS_PASS; + +} + +void GstEnginePipeline::StreamStatusMessageReceived(GstMessage *msg) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + GstStreamStatusType type; + GstElement *owner; + gst_message_parse_stream_status(msg, &type, &owner); + + if (type == GST_STREAM_STATUS_TYPE_CREATE) { + const GValue *val = gst_message_get_stream_status_object(msg); + if (G_VALUE_TYPE(val) == GST_TYPE_TASK) { + GstTask *task = static_cast(g_value_get_object(val)); + gst_task_set_enter_callback(task, &TaskEnterCallback, this, NULL); + } + } + +} + +void GstEnginePipeline::TaskEnterCallback(GstTask*, GThread*, gpointer) { + + //qLog(Info) << __PRETTY_FUNCTION__; + +// Bump the priority of the thread only on OS X + +#ifdef Q_OS_DARWIN + sched_param param; + memset(¶m, 0, sizeof(param)); + + param.sched_priority = 99; + pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); +#endif + +} + +void GstEnginePipeline::ElementMessageReceived(GstMessage *msg) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + const GstStructure *structure = gst_message_get_structure(msg); + + if (gst_structure_has_name(structure, "redirect")) { + const char *uri = gst_structure_get_string(structure, "new-location"); + + // Set the redirect URL. In mmssrc redirect messages come during the + // initial state change to PLAYING, so callers can pick up this URL after + // the state change has failed. + redirect_url_ = QUrl::fromEncoded(uri); + } + +} + +void GstEnginePipeline::ErrorMessageReceived(GstMessage *msg) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + GError *error; + gchar *debugs; + + gst_message_parse_error(msg, &error, &debugs); + QString message = QString::fromLocal8Bit(error->message); + QString debugstr = QString::fromLocal8Bit(debugs); + int domain = error->domain; + int code = error->code; + g_error_free(error); + free(debugs); + + if (!redirect_url_.isEmpty() && debugstr.contains("A redirect message was posted on the bus and should have been handled by the application.")) { + // mmssrc posts a message on the bus *and* makes an error message when it + // wants to do a redirect. We handle the message, but now we have to + // ignore the error too. + return; + } + + qLog(Error) << __FUNCTION__ << id() << debugstr; + + emit Error(id(), message, domain, code); + +} + +void GstEnginePipeline::TagMessageReceived(GstMessage *msg) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + GstTagList *taglist = nullptr; + gst_message_parse_tag(msg, &taglist); + + Engine::SimpleMetaBundle bundle; + bundle.title = ParseTag(taglist, GST_TAG_TITLE); + bundle.artist = ParseTag(taglist, GST_TAG_ARTIST); + bundle.comment = ParseTag(taglist, GST_TAG_COMMENT); + bundle.album = ParseTag(taglist, GST_TAG_ALBUM); + + gst_tag_list_free(taglist); + + if (ignore_tags_) return; + + if (!bundle.title.isEmpty() || !bundle.artist.isEmpty() || !bundle.comment.isEmpty() || !bundle.album.isEmpty()) + emit MetadataFound(id(), bundle); + +} + +QString GstEnginePipeline::ParseTag(GstTagList *list, const char *tag) const { + + //qLog(Info) << __PRETTY_FUNCTION__; + + gchar *data = nullptr; + bool success = gst_tag_list_get_string(list, tag, &data); + + QString ret; + if (success && data) { + ret = QString::fromUtf8(data); + g_free(data); + } + return ret.trimmed(); + +} + +void GstEnginePipeline::StateChangedMessageReceived(GstMessage *msg) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + if (msg->src != GST_OBJECT(pipeline_)) { + // We only care about state changes of the whole pipeline. + return; + } + + GstState old_state, new_state, pending; + gst_message_parse_state_changed(msg, &old_state, &new_state, &pending); + + if (!pipeline_is_initialised_ && (new_state == GST_STATE_PAUSED || new_state == GST_STATE_PLAYING)) { + pipeline_is_initialised_ = true; + if (pending_seek_nanosec_ != -1 && pipeline_is_connected_) { + QMetaObject::invokeMethod(this, "Seek", Qt::QueuedConnection, Q_ARG(qint64, pending_seek_nanosec_)); + } + } + + if (pipeline_is_initialised_ && new_state != GST_STATE_PAUSED && new_state != GST_STATE_PLAYING) { + pipeline_is_initialised_ = false; + } + +} + +void GstEnginePipeline::BufferingMessageReceived(GstMessage *msg) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + // Only handle buffering messages from the queue2 element in audiobin - not + // the one that's created automatically by uridecodebin. + if (GST_ELEMENT(GST_MESSAGE_SRC(msg)) != queue_) { + return; + } + + // If we are loading new next track, we don't have to pause the playback. + // The buffering is for the next track and not the current one. + if (emit_track_ended_on_stream_start_) { + qLog(Debug) << "Buffering next track"; + return; + } + + int percent = 0; + gst_message_parse_buffering(msg, &percent); + + const GstState current_state = state(); + + if (percent == 0 && current_state == GST_STATE_PLAYING && !buffering_) { + buffering_ = true; + emit BufferingStarted(); + + SetState(GST_STATE_PAUSED); + } + else if (percent == 100 && buffering_) { + buffering_ = false; + emit BufferingFinished(); + + SetState(GST_STATE_PLAYING); + } + else if (buffering_) { + emit BufferingProgress(percent); + } + +} + +void GstEnginePipeline::NewPadCallback(GstElement*, GstPad *pad, gpointer self) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + GstEnginePipeline *instance = reinterpret_cast(self); + GstPad *const audiopad = gst_element_get_static_pad(instance->audiobin_, "sink"); + + // Link decodebin's sink pad to audiobin's src pad. + if (GST_PAD_IS_LINKED(audiopad)) { + qLog(Warning) << instance->id() << "audiopad is already linked, unlinking old pad"; + gst_pad_unlink(audiopad, GST_PAD_PEER(audiopad)); + } + + gst_pad_link(pad, audiopad); + gst_object_unref(audiopad); + + // Offset the timestamps on all the buffers coming out of the decodebin so + // they line up exactly with the end of the last buffer from the old + // decodebin. + // "Running time" is the time since the last flushing seek. + GstClockTime running_time = gst_segment_to_running_time(&instance->last_decodebin_segment_, GST_FORMAT_TIME, instance->last_decodebin_segment_.position); + gst_pad_set_offset(pad, running_time); + + // Add a probe to the pad so we can update last_decodebin_segment_. + gst_pad_add_probe(pad, static_cast(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH), DecodebinProbe, instance, nullptr); + + instance->pipeline_is_connected_ = true; + if (instance->pending_seek_nanosec_ != -1 && instance->pipeline_is_initialised_) { + QMetaObject::invokeMethod(instance, "Seek", Qt::QueuedConnection, Q_ARG(qint64, instance->pending_seek_nanosec_)); + } + +} + +GstPadProbeReturn GstEnginePipeline::DecodebinProbe(GstPad *pad, GstPadProbeInfo *info, gpointer data) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + GstEnginePipeline *instance = reinterpret_cast(data); + const GstPadProbeType info_type = GST_PAD_PROBE_INFO_TYPE(info); + + if (info_type & GST_PAD_PROBE_TYPE_BUFFER) { + // The decodebin produced a buffer. Record its end time, so we can offset + // the buffers produced by the next decodebin when transitioning to the next + // song. + GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER(info); + + GstClockTime timestamp = GST_BUFFER_TIMESTAMP(buffer); + GstClockTime duration = GST_BUFFER_DURATION(buffer); + if (timestamp == GST_CLOCK_TIME_NONE) { + timestamp = instance->last_decodebin_segment_.position; + } + + if (duration != GST_CLOCK_TIME_NONE) { + timestamp += duration; + } + + instance->last_decodebin_segment_.position = timestamp; + } + else if (info_type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM) { + GstEvent *event = GST_PAD_PROBE_INFO_EVENT(info); + GstEventType event_type = GST_EVENT_TYPE(event); + + if (event_type == GST_EVENT_SEGMENT) { + // A new segment started, we need to save this to calculate running time + // offsets later. + gst_event_copy_segment(event, &instance->last_decodebin_segment_); + } + else if (event_type == GST_EVENT_FLUSH_START) { + // A flushing seek resets the running time to 0, so remove any offset + // we set on this pad before. + gst_pad_set_offset(pad, 0); + } + } + + return GST_PAD_PROBE_OK; + +} + +GstPadProbeReturn GstEnginePipeline::HandoffCallback(GstPad*, GstPadProbeInfo *info, gpointer self) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + GstEnginePipeline *instance = reinterpret_cast(self); + GstBuffer *buf = gst_pad_probe_info_get_buffer(info); + + QList consumers; + { + QMutexLocker l(&instance->buffer_consumers_mutex_); + consumers = instance->buffer_consumers_; + } + + for (BufferConsumer *consumer : consumers) { + gst_buffer_ref(buf); + consumer->ConsumeBuffer(buf, instance->id()); + } + + // Calculate the end time of this buffer so we can stop playback if it's + // after the end time of this song. + if (instance->end_offset_nanosec_ > 0) { + quint64 start_time = GST_BUFFER_TIMESTAMP(buf) - instance->segment_start_; + quint64 duration = GST_BUFFER_DURATION(buf); + quint64 end_time = start_time + duration; + + if (end_time > instance->end_offset_nanosec_) { + if (instance->has_next_valid_url()) { + if (instance->next_url_ == instance->url_ && instance->next_beginning_offset_nanosec_ == instance->end_offset_nanosec_) { + // The "next" song is actually the next segment of this file - so + // cheat and keep on playing, but just tell the Engine we've moved on. + instance->end_offset_nanosec_ = instance->next_end_offset_nanosec_; + instance->next_url_ = QUrl(); + instance->next_beginning_offset_nanosec_ = 0; + instance->next_end_offset_nanosec_ = 0; + + // GstEngine will try to seek to the start of the new section, but + // we're already there so ignore it. + instance->ignore_next_seek_ = true; + emit instance->EndOfStreamReached(instance->id(), true); + } + else { + // We have a next song but we can't cheat, so move to it normally. + instance->TransitionToNext(); + } + } + else { + // There's no next song + emit instance->EndOfStreamReached(instance->id(), false); + } + } + } + + if (instance->emit_track_ended_on_time_discontinuity_) { + if (GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DISCONT) || GST_BUFFER_OFFSET(buf) < instance->last_buffer_offset_) { + qLog(Debug) << "Buffer discontinuity - emitting EOS"; + instance->emit_track_ended_on_time_discontinuity_ = false; + emit instance->EndOfStreamReached(instance->id(), true); + } + } + + instance->last_buffer_offset_ = GST_BUFFER_OFFSET(buf); + + return GST_PAD_PROBE_OK; + +} + +GstPadProbeReturn GstEnginePipeline::EventHandoffCallback(GstPad*, GstPadProbeInfo *info, gpointer self) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + GstEnginePipeline *instance = reinterpret_cast(self); + GstEvent *e = gst_pad_probe_info_get_event(info); + + qLog(Debug) << instance->id() << "event" << GST_EVENT_TYPE_NAME(e); + + switch (GST_EVENT_TYPE(e)) { + case GST_EVENT_SEGMENT: + if (!instance->segment_start_received_) { + // The segment start time is used to calculate the proper offset of data + // buffers from the start of the stream + const GstSegment *segment = nullptr; + gst_event_parse_segment(e, &segment); + instance->segment_start_ = segment->start; + instance->segment_start_received_ = true; + } + break; + + default: + break; + } + + return GST_PAD_PROBE_OK; + +} + +void GstEnginePipeline::SourceDrainedCallback(GstURIDecodeBin *bin, gpointer self) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + GstEnginePipeline *instance = reinterpret_cast(self); + + if (instance->has_next_valid_url()) { + instance->TransitionToNext(); + } + +} + +void GstEnginePipeline::SourceSetupCallback(GstURIDecodeBin *bin, GParamSpec *pspec, gpointer self) { + + //qLog(Info) << __PRETTY_FUNCTION__; + + GstEnginePipeline *instance = reinterpret_cast(self); + GstElement *element; + g_object_get(bin, "source", &element, nullptr); + if (!element) { + return; + } + + if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "device") && !instance->source_device().isEmpty()) { + // Gstreamer is not able to handle device in URL (refering to Gstreamer + // documentation, this might be added in the future). Despite that, for now + // we include device inside URL: we decompose it during Init and set device + // here, when this callback is called. + g_object_set(element, "device", instance->source_device().toLocal8Bit().constData(), nullptr); + } + + if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "user-agent")) { + QString user_agent = QString("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion()); g_object_set(element, "user-agent", user_agent.toUtf8().constData(), nullptr); + +#ifdef Q_OS_DARWIN + g_object_set(element, "tls-database", instance->engine_->tls_database(), nullptr); + g_object_set(element, "ssl-use-system-ca-file", false, nullptr); + g_object_set(element, "ssl-strict", TRUE, nullptr); +#endif + } + +} + +void GstEnginePipeline::TransitionToNext() { + + //qLog(Info) << __PRETTY_FUNCTION__; + + GstElement *old_decode_bin = uridecodebin_; + + ignore_tags_ = true; + + ReplaceDecodeBin(next_url_); + gst_element_set_state(uridecodebin_, GST_STATE_PLAYING); + MaybeLinkDecodeToAudio(); + + url_ = next_url_; + end_offset_nanosec_ = next_end_offset_nanosec_; + next_url_ = QUrl(); + next_beginning_offset_nanosec_ = 0; + next_end_offset_nanosec_ = 0; + + // This function gets called when the source has been drained, even if the + // song hasn't finished playing yet. We'll get a new stream when it really + // does finish, so emit TrackEnded then. + emit_track_ended_on_stream_start_ = true; + + // This has to happen *after* the gst_element_set_state on the new bin to + // fix an occasional race condition deadlock. + sElementDeleter->DeleteElementLater(old_decode_bin); + + ignore_tags_ = false; + +} + +qint64 GstEnginePipeline::position() const { + if (pipeline_is_initialised_) + gst_element_query_position(pipeline_, GST_FORMAT_TIME, &last_known_position_ns_); + + return last_known_position_ns_; + +} + +qint64 GstEnginePipeline::length() const { + gint64 value = 0; + gst_element_query_duration(pipeline_, GST_FORMAT_TIME, &value); + + return value; + +} + +GstState GstEnginePipeline::state() const { + + GstState s, sp; + if (gst_element_get_state(pipeline_, &s, &sp, kGstStateTimeoutNanosecs) == GST_STATE_CHANGE_FAILURE) + return GST_STATE_NULL; + + return s; + +} + +QFuture GstEnginePipeline::SetState(GstState state) { + + return ConcurrentRun::Run(&set_state_threadpool_, &gst_element_set_state, pipeline_, state); + +} + +bool GstEnginePipeline::Seek(qint64 nanosec) { + + if (ignore_next_seek_) { + ignore_next_seek_ = false; + return true; + } + + if (!pipeline_is_connected_ || !pipeline_is_initialised_) { + pending_seek_nanosec_ = nanosec; + return true; + } + + pending_seek_nanosec_ = -1; + last_known_position_ns_ = nanosec; + return gst_element_seek_simple(pipeline_, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, nanosec); + +} + +void GstEnginePipeline::SetEqualizerEnabled(bool enabled) { + + //qLog(Info) << __PRETTY_FUNCTION__ << enabled; + + eq_enabled_ = enabled; + UpdateEqualizer(); + +} + +void GstEnginePipeline::SetEqualizerParams(int preamp, const QList& band_gains) { + + eq_preamp_ = preamp; + eq_band_gains_ = band_gains; + UpdateEqualizer(); + +} + +void GstEnginePipeline::SetStereoBalance(float value) { + + stereo_balance_ = value; + UpdateStereoBalance(); + +} + +void GstEnginePipeline::UpdateEqualizer() { + + // Update band gains + for (int i = 0; i < kEqBandCount; ++i) { + float gain = eq_enabled_ ? eq_band_gains_[i] : 0.0; + if (gain < 0) + gain *= 0.24; + else + gain *= 0.12; + + const int index_in_eq = i + 1; + // Offset because of the first dummy band we created. + GstObject *band = GST_OBJECT(gst_child_proxy_get_child_by_index(GST_CHILD_PROXY(equalizer_), index_in_eq)); + g_object_set(G_OBJECT(band), "gain", gain, nullptr); + g_object_unref(G_OBJECT(band)); + } + + // Update preamp + float preamp = 1.0; + if (eq_enabled_) + preamp = float(eq_preamp_ + 100) * 0.01; // To scale from 0.0 to 2.0 + + g_object_set(G_OBJECT(equalizer_preamp_), "volume", preamp, nullptr); + +} + +void GstEnginePipeline::UpdateStereoBalance() { + if (stereo_panorama_) { + g_object_set(G_OBJECT(stereo_panorama_), "panorama", stereo_balance_, nullptr); + } +} + +void GstEnginePipeline::SetVolume(int percent) { + volume_percent_ = percent; + UpdateVolume(); +} + +void GstEnginePipeline::SetVolumeModifier(qreal mod) { + volume_modifier_ = mod; + UpdateVolume(); +} + +void GstEnginePipeline::UpdateVolume() { + float vol = double(volume_percent_) * 0.01 * volume_modifier_; + g_object_set(G_OBJECT(volume_), "volume", vol, nullptr); +} + +void GstEnginePipeline::StartFader(qint64 duration_nanosec, QTimeLine::Direction direction, QTimeLine::CurveShape shape, bool use_fudge_timer) { + + const int duration_msec = duration_nanosec / kNsecPerMsec; + + // If there's already another fader running then start from the same time + // that one was already at. + int start_time = direction == QTimeLine::Forward ? 0 : duration_msec; + if (fader_ && fader_->state() == QTimeLine::Running) { + if (duration_msec == fader_->duration()) { + start_time = fader_->currentTime(); + } + else { + // Calculate the position in the new fader with the same value from + // the old fader, so no volume jumps appear + qreal time = qreal(duration_msec) * (qreal(fader_->currentTime()) / qreal(fader_->duration())); + start_time = qRound(time); + } + } + + fader_.reset(new QTimeLine(duration_msec, this)); + connect(fader_.get(), SIGNAL(valueChanged(qreal)), SLOT(SetVolumeModifier(qreal))); + connect(fader_.get(), SIGNAL(finished()), SLOT(FaderTimelineFinished())); + fader_->setDirection(direction); + fader_->setCurveShape(shape); + fader_->setCurrentTime(start_time); + fader_->resume(); + + fader_fudge_timer_.stop(); + use_fudge_timer_ = use_fudge_timer; + + SetVolumeModifier(fader_->currentValue()); + +} + +void GstEnginePipeline::FaderTimelineFinished() { + + fader_.reset(); + + // Wait a little while longer before emitting the finished signal (and + // probably distroying the pipeline) to account for delays in the audio + // server/driver. + if (use_fudge_timer_) { + fader_fudge_timer_.start(kFaderFudgeMsec, this); + } + else { + // Even here we cannot emit the signal directly, as it result in a + // stutter when resuming playback. So use a quest small time, so you + // won't notice the difference when resuming playback + // (You get here when the pause fading is active) + fader_fudge_timer_.start(250, this); + } + +} + +void GstEnginePipeline::timerEvent(QTimerEvent *e) { + + if (e->timerId() == fader_fudge_timer_.timerId()) { + fader_fudge_timer_.stop(); + emit FaderFinished(); + return; + } + + QObject::timerEvent(e); +} + +void GstEnginePipeline::AddBufferConsumer(BufferConsumer *consumer) { + QMutexLocker l(&buffer_consumers_mutex_); + buffer_consumers_ << consumer; +} + +void GstEnginePipeline::RemoveBufferConsumer(BufferConsumer *consumer) { + QMutexLocker l(&buffer_consumers_mutex_); + buffer_consumers_.removeAll(consumer); +} + +void GstEnginePipeline::RemoveAllBufferConsumers() { + QMutexLocker l(&buffer_consumers_mutex_); + buffer_consumers_.clear(); +} + +void GstEnginePipeline::SetNextUrl(const QUrl& url, qint64 beginning_nanosec, qint64 end_nanosec) { + next_url_ = url; + next_beginning_offset_nanosec_ = beginning_nanosec; + next_end_offset_nanosec_ = end_nanosec; +} + diff --git a/src/engine/gstenginepipeline.h b/src/engine/gstenginepipeline.h new file mode 100644 index 00000000..356822e9 --- /dev/null +++ b/src/engine/gstenginepipeline.h @@ -0,0 +1,300 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef GSTENGINEPIPELINE_H +#define GSTENGINEPIPELINE_H + +#include "config.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "engine_fwd.h" + +class GstElementDeleter; +class GstEngine; +class BufferConsumer; + +struct GstQueue; +struct GstURIDecodeBin; + +class GstEnginePipeline : public QObject { + Q_OBJECT + + public: + GstEnginePipeline(GstEngine *engine); + ~GstEnginePipeline(); + + // Globally unique across all pipelines. + int id() const { return id_; } + + // Call these setters before Init + void set_output_device(const QString &sink, const QVariant &device); + void set_replaygain(bool enabled, int mode, float preamp, bool compression); + void set_buffer_duration_nanosec(qint64 duration_nanosec); + void set_buffer_min_fill(int percent); + void set_mono_playback(bool enabled); + + // Creates the pipeline, returns false on error + bool InitFromUrl(const QUrl &url, qint64 end_nanosec); + bool InitFromString(const QString &pipeline); + + // BufferConsumers get fed audio data. Thread-safe. + void AddBufferConsumer(BufferConsumer *consumer); + void RemoveBufferConsumer(BufferConsumer *consumer); + void RemoveAllBufferConsumers(); + + // Control the music playback + QFuture SetState(GstState state); + Q_INVOKABLE bool Seek(qint64 nanosec); + void SetEqualizerEnabled(bool enabled); + void SetEqualizerParams(int preamp, const QList &band_gains); + void SetVolume(int percent); + void SetStereoBalance(float value); + void StartFader(qint64 duration_nanosec, QTimeLine::Direction direction = QTimeLine::Forward, QTimeLine::CurveShape shape = QTimeLine::LinearCurve, bool use_fudge_timer = true); + + // If this is set then it will be loaded automatically when playback finishes + // for gapless playback + void SetNextUrl(const QUrl &url, qint64 beginning_nanosec, qint64 end_nanosec); + bool has_next_valid_url() const { return next_url_.isValid(); } + + // Get information about the music playback + QUrl url() const { return url_; } + bool is_valid() const { return valid_; } + // Please note that this method (unlike GstEngine's.position()) is + // multiple-section media unaware. + qint64 position() const; + // Please note that this method (unlike GstEngine's.length()) is + // multiple-section media unaware. + qint64 length() const; + // Returns this pipeline's state. May return GST_STATE_NULL if the state check + // timed out. The timeout value is a reasonable default. + GstState state() const; + qint64 segment_start() const { return segment_start_; } + + // Don't allow the user to change the playback state (playing/paused) while + // the pipeline is buffering. + bool is_buffering() const { return buffering_; } + + QUrl redirect_url() const { return redirect_url_; } + + QString source_device() const { return source_device_; } + + public slots: + void SetVolumeModifier(qreal mod); + +signals: + void EndOfStreamReached(int pipeline_id, bool has_next_track); + void MetadataFound(int pipeline_id, const Engine::SimpleMetaBundle &bundle); + // This indicates an error, delegated from GStreamer, in the pipeline. + // The message, domain and error_code are related to GStreamer's GError. + void Error(int pipeline_id, const QString &message, int domain, int error_code); + void FaderFinished(); + + void BufferingStarted(); + void BufferingProgress(int percent); + void BufferingFinished(); + + protected: + void timerEvent(QTimerEvent*); + + private: + // Static callbacks. The GstEnginePipeline instance is passed in the last + // argument. + static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage*, gpointer); + static gboolean BusCallback(GstBus*, GstMessage*, gpointer); + static void NewPadCallback(GstElement*, GstPad*, gpointer); + static GstPadProbeReturn HandoffCallback(GstPad*, GstPadProbeInfo*, gpointer); + static GstPadProbeReturn EventHandoffCallback(GstPad*, GstPadProbeInfo*, gpointer); + static GstPadProbeReturn DecodebinProbe(GstPad*, GstPadProbeInfo*, gpointer); + static void SourceDrainedCallback(GstURIDecodeBin*, gpointer); + static void SourceSetupCallback(GstURIDecodeBin*, GParamSpec *pspec, gpointer); + static void TaskEnterCallback(GstTask*, GThread*, gpointer); + + void TagMessageReceived(GstMessage*); + void ErrorMessageReceived(GstMessage*); + void ElementMessageReceived(GstMessage*); + void StateChangedMessageReceived(GstMessage*); + void BufferingMessageReceived(GstMessage*); + void StreamStatusMessageReceived(GstMessage*); + + QString ParseTag(GstTagList *list, const char *tag) const; + + bool Init(); + GstElement *CreateDecodeBinFromString(const char *pipeline); + + void UpdateVolume(); + void UpdateEqualizer(); + void UpdateStereoBalance(); + bool ReplaceDecodeBin(GstElement *new_bin); + bool ReplaceDecodeBin(const QUrl &url); + + void TransitionToNext(); + + // If the decodebin is special (ie. not really a uridecodebin) then it'll have + // a src pad immediately and we can link it after everything's created. + void MaybeLinkDecodeToAudio(); + + private slots: + void FaderTimelineFinished(); + + private: + static const int kGstStateTimeoutNanosecs; + static const int kFaderFudgeMsec; + static const int kEqBandCount; + static const int kEqBandFrequencies[]; + + static GstElementDeleter *sElementDeleter; + + GstEngine *engine_; + + // Using == to compare two pipelines is a bad idea, because new ones often + // get created in the same address as old ones. This ID will be unique for + // each pipeline. + // Threading warning: access to the static ID field isn't protected by a + // mutex because all pipeline creation is currently done in the main thread. + static int sId; + int id_; + + // General settings for the pipeline + bool valid_; + QString sink_; + QVariant device_; + + // These get called when there is a new audio buffer available + QList buffer_consumers_; + QMutex buffer_consumers_mutex_; + qint64 segment_start_; + bool segment_start_received_; + bool emit_track_ended_on_stream_start_; + bool emit_track_ended_on_time_discontinuity_; + qint64 last_buffer_offset_; + + // Equalizer + bool eq_enabled_; + int eq_preamp_; + QList eq_band_gains_; + + // Stereo balance. + // From -1.0 - 1.0 + // -1.0 is left, 1.0 is right. + float stereo_balance_; + + // ReplayGain + bool rg_enabled_; + int rg_mode_; + float rg_preamp_; + bool rg_compression_; + + // Buffering + quint64 buffer_duration_nanosec_; + int buffer_min_fill_; + bool buffering_; + + bool mono_playback_; + + // The URL that is currently playing, and the URL that is to be preloaded + // when the current track is close to finishing. + QUrl url_; + QUrl next_url_; + + // If this is > 0 then the pipeline will be forced to stop when playback goes + // past this position. + qint64 end_offset_nanosec_; + + // We store the beginning and end for the preloading song too, so we can just + // carry on without reloading the file if the sections carry on from each + // other. + qint64 next_beginning_offset_nanosec_; + qint64 next_end_offset_nanosec_; + + // Set temporarily when moving to the next contiguous section in a multi-part + // file. + bool ignore_next_seek_; + + // Set temporarily when switching out the decode bin, so metadata doesn't + // get sent while the Player still thinks it's playing the last song + bool ignore_tags_; + + // When the gstreamer source requests a redirect we store the URL here and + // callers can pick it up after the state change to PLAYING fails. + QUrl redirect_url_; + + // When we need to specify the device to use as source (for CD device) + QString source_device_; + + // Seeking while the pipeline is in the READY state doesn't work, so we have + // to wait until it goes to PAUSED or PLAYING. + // Also we have to wait for the decodebin to be connected. + bool pipeline_is_initialised_; + bool pipeline_is_connected_; + qint64 pending_seek_nanosec_; + + // We can only use gst_element_query_position() when the pipeline is in + // PAUSED nor PLAYING state. Whenever we get a new position (e.g. after a + // correct call to gst_element_query_position() or after a seek), we store + // it here so that we can use it when using gst_element_query_position() is + // not possible. + mutable gint64 last_known_position_ns_; + + int volume_percent_; + qreal volume_modifier_; + + std::unique_ptr fader_; + QBasicTimer fader_fudge_timer_; + bool use_fudge_timer_; + + GstElement *pipeline_; + + // Bins + // uridecodebin ! audiobin + GstElement *uridecodebin_; + GstElement *audiobin_; + + // Elements in the audiobin. See comments in Init()'s definition. + GstElement *queue_; + GstElement *audioconvert_; + GstElement *rgvolume_; + GstElement *rglimiter_; + GstElement *audioconvert2_; + GstElement *equalizer_preamp_; + GstElement *equalizer_; + GstElement *stereo_panorama_; + GstElement *volume_; + GstElement *audioscale_; + GstElement *audiosink_; + + uint bus_cb_id_; + + QThreadPool set_state_threadpool_; + + GstSegment last_decodebin_segment_; +}; + +#endif // GSTENGINEPIPELINE_H diff --git a/src/engine/osxdevicefinder.cpp b/src/engine/osxdevicefinder.cpp new file mode 100644 index 00000000..276a8880 --- /dev/null +++ b/src/engine/osxdevicefinder.cpp @@ -0,0 +1,110 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2014, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include + +#include "osxdevicefinder.h" +#include "core/logging.h" +#include "core/scoped_cftyperef.h" + +namespace { + +template +std::unique_ptr GetProperty(const AudioDeviceID& device_id, const AudioObjectPropertyAddress& address, UInt32* size_bytes_out = nullptr) { + + UInt32 size_bytes = 0; + OSStatus status = AudioObjectGetPropertyDataSize(device_id, &address, 0, NULL, &size_bytes); + if (status != kAudioHardwareNoError) { + qLog(Warning) << "AudioObjectGetPropertyDataSize failed:" << status; + return std::unique_ptr(); + } + + std::unique_ptr ret(reinterpret_cast(malloc(size_bytes))); + + status = AudioObjectGetPropertyData(device_id, &address, 0, NULL, &size_bytes, ret.get()); + if (status != kAudioHardwareNoError) { + qLog(Warning) << "AudioObjectGetPropertyData failed:" << status; + return std::unique_ptr(); + } + + if (size_bytes_out) { + *size_bytes_out = size_bytes; + } + + return ret; +} + +} // namespace + + +OsxDeviceFinder::OsxDeviceFinder() + : DeviceFinder("osxaudiosink") { +} + +QList OsxDeviceFinder::ListDevices() { + + QList ret; + + AudioObjectPropertyAddress address = { + kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + UInt32 device_size_bytes = 0; + std::unique_ptr devices = GetProperty(kAudioObjectSystemObject, address, &device_size_bytes); + if (!devices.get()) { + return ret; + } + const int device_count = device_size_bytes / sizeof(AudioDeviceID); + + address.mScope = kAudioDevicePropertyScopeOutput; + for (UInt32 i = 0; i < device_count; ++i) { + const AudioDeviceID id = devices.get()[i]; + + // Query device name + address.mSelector = kAudioDevicePropertyDeviceNameCFString; + std::unique_ptr device_name = GetProperty(id, address); + ScopedCFTypeRef scoped_device_name(*device_name.get()); + if (!device_name.get()) { + continue; + } + + // Determine if the device is an output device (it is an output device if + // it has output channels) + address.mSelector = kAudioDevicePropertyStreamConfiguration; + std::unique_ptr buffer_list = GetProperty(id, address); + if (!buffer_list.get() || buffer_list->mNumberBuffers == 0) { + continue; + } + + Device dev; + dev.description = QString::fromUtf8(CFStringGetCStringPtr(*device_name, CFStringGetSystemEncoding())); + dev.device_property_value = id; + dev.icon_name = GuessIconName(dev.description); + ret.append(dev); + } + return ret; +} + diff --git a/src/engine/osxdevicefinder.h b/src/engine/osxdevicefinder.h new file mode 100644 index 00000000..bd46c652 --- /dev/null +++ b/src/engine/osxdevicefinder.h @@ -0,0 +1,37 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2014, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef OSXDEVICEFINDER_H +#define OSXDEVICEFINDER_H + +#include "config.h" + +#include "engine/devicefinder.h" + +class OsxDeviceFinder : public DeviceFinder { + public: + OsxDeviceFinder(); + + virtual bool Initialise() { return true; } + virtual QList ListDevices(); +}; + +#endif // OSXDEVICEFINDER_H + diff --git a/src/engine/phononengine.cpp b/src/engine/phononengine.cpp new file mode 100644 index 00000000..7381dd66 --- /dev/null +++ b/src/engine/phononengine.cpp @@ -0,0 +1,159 @@ +/* This file is part of Strawberry. + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#include "config.h" + +#include + +#include "phononengine.h" + +#include "core/logging.h" +#include "core/taskmanager.h" + +PhononEngine::PhononEngine(TaskManager *task_manager) + : media_object_(new Phonon::MediaObject(this)), + audio_output_(new Phonon::AudioOutput(Phonon::MusicCategory, this)), + state_timer_(new QTimer(this)), + seek_offset_(-1) +{ + + Phonon::createPath(media_object_, audio_output_); + + connect(media_object_, SIGNAL(finished()), SLOT(PhononFinished())); + connect(media_object_, SIGNAL(stateChanged(Phonon::State,Phonon::State)), SLOT(PhononStateChanged(Phonon::State))); + + state_timer_->setSingleShot(true); + connect(state_timer_, SIGNAL(timeout()), SLOT(StateTimeoutExpired())); + +} + +PhononEngine::~PhononEngine() { + delete media_object_; + delete audio_output_; +} + +bool PhononEngine::Init() { + //qLog(Debug) << __PRETTY_FUNCTION__; + type_ = Engine::Phonon; + return true; +} + +bool PhononEngine::CanDecode(const QUrl &url) { + // TODO + return true; +} + +bool PhononEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) { + media_object_->setCurrentSource(Phonon::MediaSource(url)); + return true; +} + +bool PhononEngine::Play(quint64 offset_nanosec) { + + // The seek happens in PhononStateChanged - phonon doesn't seem to change + // currentTime() if we seek before we start playing :S + seek_offset_ = offset_nanosec; + + media_object_->play(); + return true; + +} + +void PhononEngine::Stop(bool stop_after) { + media_object_->stop(); +} + +void PhononEngine::Pause() { + media_object_->pause(); +} + +void PhononEngine::Unpause() { + media_object_->play(); +} + +Engine::State PhononEngine::state() const { + + switch (media_object_->state()) { + case Phonon::LoadingState: + case Phonon::PlayingState: + case Phonon::BufferingState: + return Engine::Playing; + + case Phonon::PausedState: + return Engine::Paused; + + case Phonon::StoppedState: + case Phonon::ErrorState: + default: + return Engine::Empty; + } + +} + +uint PhononEngine::position() const { + return media_object_->currentTime(); +} + +uint PhononEngine::length() const { + return media_object_->totalTime(); +} + +void PhononEngine::Seek(quint64 offset_nanosec) { + media_object_->seek(offset_nanosec); +} + +void PhononEngine::SetVolumeSW(uint volume) { + audio_output_->setVolume(volume); +} + +void PhononEngine::PhononFinished() { + emit TrackEnded(); +} + +void PhononEngine::PhononStateChanged(Phonon::State new_state) { + + if (new_state == Phonon::ErrorState) { + emit Error(media_object_->errorString()); + } + if (new_state == Phonon::PlayingState && seek_offset_ != -1) { + media_object_->seek(seek_offset_); + seek_offset_ = -1; + } + + // Don't emit the state change straight away + state_timer_->start(100); + +} + +void PhononEngine::StateTimeoutExpired() { + emit StateChanged(state()); +} + +qint64 PhononEngine::position_nanosec() const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + return 0; + +} + +qint64 PhononEngine::length_nanosec() const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + return 0; + +} diff --git a/src/engine/phononengine.h b/src/engine/phononengine.h new file mode 100644 index 00000000..8bc5bc9b --- /dev/null +++ b/src/engine/phononengine.h @@ -0,0 +1,73 @@ +/* This file is part of Strawberry. + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#ifndef PHONONENGINE_H +#define PHONONENGINE_H + +#include "config.h" + +#include "enginebase.h" + +#include +#include + +class QTimer; +class TaskManager; + +class PhononEngine : public Engine::Base { + Q_OBJECT + + public: + PhononEngine(TaskManager *task_manager); + ~PhononEngine(); + + bool Init(); + + bool CanDecode(const QUrl &url); + + bool Load(const QUrl &, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec); + bool Play(quint64 offset_nanosec); + void Stop(bool stop_after = false); + void Pause(); + void Unpause(); + + Engine::State state() const; + uint position() const; + uint length() const; + + void Seek(quint64 offset_nanosec); + + qint64 position_nanosec() const; + qint64 length_nanosec() const; + + protected: + void SetVolumeSW( uint percent ); + + private slots: + void PhononFinished(); + void PhononStateChanged(Phonon::State new_state); + void StateTimeoutExpired(); + + private: + Phonon::MediaObject *media_object_; + Phonon::AudioOutput *audio_output_; + + QTimer *state_timer_; + + qint64 seek_offset_; +}; + +#endif // PHONONENGINE_H diff --git a/src/engine/pulsedevicefinder.cpp b/src/engine/pulsedevicefinder.cpp new file mode 100644 index 00000000..e85a188d --- /dev/null +++ b/src/engine/pulsedevicefinder.cpp @@ -0,0 +1,142 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2014, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include "core/logging.h" +#include "engine/pulsedevicefinder.h" + +PulseDeviceFinder::PulseDeviceFinder() : DeviceFinder("pulsesink"), mainloop_(nullptr), context_(nullptr) { +} + +bool PulseDeviceFinder::Initialise() { + + mainloop_ = pa_mainloop_new(); + if (!mainloop_) { + qLog(Warning) << "Failed to create pulseaudio mainloop"; + return false; + } + + return Reconnect(); +} + +bool PulseDeviceFinder::Reconnect() { + + if (context_) { + pa_context_disconnect(context_); + pa_context_unref(context_); + } + + context_ = pa_context_new(pa_mainloop_get_api(mainloop_), "Strawberry device finder"); + if (!context_) { + qLog(Warning) << "Failed to create pulseaudio context"; + return false; + } + + if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0) { + qLog(Warning) << "Failed to connect pulseaudio context"; + return false; + } + + // Wait for the context to be connected. + forever { + const pa_context_state state = pa_context_get_state(context_); + if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + qLog(Warning) << "Connection to pulseaudio failed"; + return false; + } + + if (state == PA_CONTEXT_READY) { + return true; + } + + pa_mainloop_iterate(mainloop_, true, nullptr); + } +} + +QList PulseDeviceFinder::ListDevices() { + + if (!context_ || pa_context_get_state(context_) != PA_CONTEXT_READY) { + return QList(); + } + +retry: + ListDevicesState state; + pa_context_get_sink_info_list(context_, &PulseDeviceFinder::GetSinkInfoCallback, &state); + + forever { + if (state.finished) { + return state.devices; + } + + switch (pa_context_get_state(context_)) { + case PA_CONTEXT_READY: + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + // Maybe pulseaudio died. Try reconnecting. + if (Reconnect()) { + goto retry; + } + return state.devices; + default: + return state.devices; + } + + pa_mainloop_iterate(mainloop_, true, nullptr); + } +} + +void PulseDeviceFinder::GetSinkInfoCallback(pa_context *c, const pa_sink_info *info, int eol, void *state_voidptr) { + + ListDevicesState *state = reinterpret_cast(state_voidptr); + + if (info) { + Device dev; + dev.device_property_value = QString::fromUtf8(info->name); + dev.description = QString::fromUtf8(info->description); + dev.iconname = QString::fromUtf8(pa_proplist_gets(info->proplist, "device.icon_name")); + + state->devices.append(dev); + } + + if (eol) { + state->finished = true; + } +} + +PulseDeviceFinder::~PulseDeviceFinder() { + + if (context_) { + pa_context_disconnect(context_); + pa_context_unref(context_); + } + + if (mainloop_) { + pa_mainloop_free(mainloop_); + } +} + diff --git a/src/engine/pulsedevicefinder.h b/src/engine/pulsedevicefinder.h new file mode 100644 index 00000000..81a1fc2b --- /dev/null +++ b/src/engine/pulsedevicefinder.h @@ -0,0 +1,60 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2014, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PULSEDEVICEFINDER_H +#define PULSEDEVICEFINDER_H + +#include "config.h" + +#include +#include + +#include +#include +#include + +#include "engine/devicefinder.h" + +class PulseDeviceFinder : public DeviceFinder { + public: + PulseDeviceFinder(); + ~PulseDeviceFinder(); + + virtual bool Initialise(); + virtual QList ListDevices(); + + private: + struct ListDevicesState { + ListDevicesState() : finished(false) {} + + bool finished; + QList devices; + }; + + bool Reconnect(); + + static void GetSinkInfoCallback(pa_context *c, const pa_sink_info *info, int eol, void *state_voidptr); + + pa_mainloop *mainloop_; + pa_context *context_; + +}; + +#endif // PULSEDEVICEFINDER_H diff --git a/src/engine/vlcengine.cpp b/src/engine/vlcengine.cpp new file mode 100644 index 00000000..0441afb3 --- /dev/null +++ b/src/engine/vlcengine.cpp @@ -0,0 +1,335 @@ +/* This file is part of Clementine. + + 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 "config.h" + +#include + +#include +#include +#include +#include + +#include "vlcengine.h" +#include "vlcscopedref.h" + +#include "core/taskmanager.h" +#include "core/logging.h" + +VLCEngine* VLCEngine::sInstance = NULL; + +VLCEngine::VLCEngine(TaskManager *task_manager) + : instance_(NULL), + player_(NULL), + scope_data_(4096), + state_(Engine::Empty) +{ + + //qLog(Debug) << __PRETTY_FUNCTION__; + +#if 1 + static const char * const args[] = { + "-I", "dummy", // Don't use any interface + "--ignore-config", // Don't use VLC's config + "--extraintf=logger", // log anything + "--verbose=2", // be much more verbose then normal for debugging purpose + + // Our scope plugin + "--audio-filter=clementine_scope", + "--no-plugins-cache", + + // Try to stop audio stuttering + "--file-caching=500", // msec + "--http-caching=500", + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) + "--aout=alsa", // The default, pulseaudio, is buggy +#endif + }; +#endif + + // Create the VLC instance + //libvlc_exception_init(&exception_); + instance_ = libvlc_new(sizeof(args) / sizeof(args[0]), args); + HandleErrors(); + + // Create the media player + player_ = libvlc_media_player_new(instance_); + HandleErrors(); + + // Add event handlers + libvlc_event_manager_t* player_em = libvlc_media_player_event_manager(player_); + HandleErrors(); + + AttachCallback(player_em, libvlc_MediaPlayerEncounteredError, StateChangedCallback); + AttachCallback(player_em, libvlc_MediaPlayerNothingSpecial, StateChangedCallback); + AttachCallback(player_em, libvlc_MediaPlayerOpening, StateChangedCallback); + AttachCallback(player_em, libvlc_MediaPlayerBuffering, StateChangedCallback); + AttachCallback(player_em, libvlc_MediaPlayerPlaying, StateChangedCallback); + AttachCallback(player_em, libvlc_MediaPlayerPaused, StateChangedCallback); + AttachCallback(player_em, libvlc_MediaPlayerStopped, StateChangedCallback); + AttachCallback(player_em, libvlc_MediaPlayerEndReached, StateChangedCallback); + HandleErrors(); + + sInstance = this; +} + +VLCEngine::~VLCEngine() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + libvlc_media_player_stop(player_); + libvlc_media_player_release(player_); + libvlc_release(instance_); + HandleErrors(); +} + +void VLCEngine::AttachCallback(libvlc_event_manager_t* em, libvlc_event_type_t type, libvlc_callback_t callback) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + libvlc_event_attach(em, type, callback, this); + HandleErrors(); +} + +void VLCEngine::StateChangedCallback(const libvlc_event_t* e, void* data) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + VLCEngine* engine = reinterpret_cast(data); + + switch (e->type) { + case libvlc_MediaPlayerNothingSpecial: + case libvlc_MediaPlayerStopped: + case libvlc_MediaPlayerEncounteredError: + engine->state_ = Engine::Empty; + break; + + case libvlc_MediaPlayerOpening: + case libvlc_MediaPlayerBuffering: + case libvlc_MediaPlayerPlaying: + engine->state_ = Engine::Playing; + break; + + case libvlc_MediaPlayerPaused: + engine->state_ = Engine::Paused; + break; + + case libvlc_MediaPlayerEndReached: + engine->state_ = Engine::Idle; + emit engine->TrackEnded(); + return; // Don't emit state changed here + } + + emit engine->StateChanged(engine->state_); +} + +bool VLCEngine::Init() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + type_ = Engine::VLC; + + return true; +} + +bool VLCEngine::CanDecode(const QUrl &url) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // TODO + return true; +} + +bool VLCEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // Create the media object + VlcScopedRef media(libvlc_media_new_location(instance_, url.toEncoded().constData())); + //if (libvlc_exception_raised(&exception_)) + //return false; + + libvlc_media_player_set_media(player_, media); + //if (libvlc_exception_raised(&exception_)) + //return false; + + return true; +} + +bool VLCEngine::Play(quint64 offset_nanosec) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + libvlc_media_player_play(player_); + //if (libvlc_exception_raised(&exception_)) + //return false; + + Seek(offset_nanosec); + + return true; + + +} + +void VLCEngine::Stop(bool stop_after) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + libvlc_media_player_stop(player_); + HandleErrors(); +} + +void VLCEngine::Pause() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + libvlc_media_player_pause(player_); + HandleErrors(); +} + +void VLCEngine::Unpause() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + libvlc_media_player_play(player_); + HandleErrors(); +} + +uint VLCEngine::position() const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + //bool is_playing = libvlc_media_player_is_playing(player_, const_cast(&exception_)); + bool is_playing = libvlc_media_player_is_playing(player_); + HandleErrors(); + + if (!is_playing) + return 0; + + //float pos = libvlc_media_player_get_position(player_, const_cast(&exception_)); + float pos = libvlc_media_player_get_position(player_); + HandleErrors(); + + return pos * length(); + +} + +uint VLCEngine::length() const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + //bool is_playing = libvlc_media_player_is_playing(player_, const_cast(&exception_)); + bool is_playing = libvlc_media_player_is_playing(player_); + HandleErrors(); + + if (!is_playing) + return 0; + + //libvlc_time_t len = libvlc_media_player_get_length(player_, const_cast(&exception_)); + libvlc_time_t len = libvlc_media_player_get_length(player_); + HandleErrors(); + + return len; + +} + +void VLCEngine::Seek(quint64 offset_nanosec) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + uint len = length(); + if (len == 0) + return; + + float pos = float(offset_nanosec) / len; + + libvlc_media_player_set_position(player_, pos); + HandleErrors(); + +} + +void VLCEngine::SetVolumeSW(uint percent) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + libvlc_audio_set_volume(player_, percent); + HandleErrors(); +} + +void VLCEngine::HandleErrors() const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + //if (libvlc_exception_raised(&exception_)) { + //qFatal("libvlc error: %s", libvlc_exception_get_message(&exception_)); + //} + +} + +void VLCEngine::SetScopeData(float* data, int size) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (!sInstance) + return; + + QMutexLocker l(&sInstance->scope_mutex_); + + // This gets called by our VLC plugin. Just push the data on to the end of + // the circular buffer and let it get consumed by scope() + for (int i=0 ; iscope_data_.push_back(data[i]); + } +} + +const Engine::Scope& VLCEngine::Scope() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QMutexLocker l(&scope_mutex_); + + // Leave the scope unchanged if there's not enough data + if (scope_data_.size() < uint(kScopeSize)) + return scope_; + + // Take the samples off the front of the circular buffer + for (uint i=0 ; i. +*/ + +#ifndef VLCENGINE_H +#define VLCENGINE_H + +#include "config.h" + +#include "enginebase.h" + +#include +#include + +#include + +class QTimer; +class TaskManager; + +class VLCEngine : public Engine::Base { + Q_OBJECT + + public: + VLCEngine(TaskManager *task_manager); + ~VLCEngine(); + + bool Init(); + + virtual qint64 position_nanosec() const; + virtual qint64 length_nanosec() const; + + bool CanDecode( const QUrl &url ); + + bool Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec); + bool Play(quint64 offset_nanosec); + void Stop(bool stop_after = false); + void Pause(); + void Unpause(); + + Engine::State state() const { return state_; } + uint position() const; + uint length() const; + + void Seek(quint64 offset_nanosec); + + static void SetScopeData(float* data, int size); + const Engine::Scope& Scope(); + + protected: + void SetVolumeSW(uint percent); + + private: + void HandleErrors() const; + void AttachCallback(libvlc_event_manager_t* em, libvlc_event_type_t type, libvlc_callback_t callback); + static void StateChangedCallback(const libvlc_event_t* e, void* data); + + private: + // The callbacks need access to this + static VLCEngine *sInstance; + + // VLC bits and pieces + //libvlc_exception_t exception_; + libvlc_instance_t *instance_; + libvlc_media_player_t *player_; + + // Our clementine_scope VLC plugin puts data in here + QMutex scope_mutex_; + boost::circular_buffer scope_data_; + + Engine::State state_; +}; + +#endif // VLCENGINE_H diff --git a/src/engine/vlcscopedref.h b/src/engine/vlcscopedref.h new file mode 100644 index 00000000..2580f315 --- /dev/null +++ b/src/engine/vlcscopedref.h @@ -0,0 +1,67 @@ +/* This file is part of Clementine. + + 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 VLCSCOPEDREF_H +#define VLCSCOPEDREF_H + +#include "config.h" + +#include + +template +class VlcScopedRef { + public: + VlcScopedRef(T* ptr); + ~VlcScopedRef(); + + operator T* () const { return ptr_; } + operator bool () const { return ptr_; } + T* operator ->() const { return ptr_; } + + private: + VlcScopedRef(VlcScopedRef&) {} + VlcScopedRef& operator =(const VlcScopedRef&) { return *this; } + + T* ptr_; +}; + + +#define VLCSCOPEDREF_DEFINE2(type, dtor) \ + template <> void VlcScopedRef_Release(libvlc_##type##_t* ptr) { \ + dtor(ptr); \ + } +#define VLCSCOPEDREF_DEFINE(type) VLCSCOPEDREF_DEFINE2(type, libvlc_##type##_release) + +template +void VlcScopedRef_Release(T* ptr); + +VLCSCOPEDREF_DEFINE2(instance, libvlc_release); +VLCSCOPEDREF_DEFINE(media_player); +VLCSCOPEDREF_DEFINE(media); + +template <> void VlcScopedRef_Release(char* ptr) { free(ptr); } + +template +VlcScopedRef::VlcScopedRef(T* ptr) + : ptr_(ptr) { +} + +template +VlcScopedRef::~VlcScopedRef() { + VlcScopedRef_Release(ptr_); +} + +#endif // VLCSCOPEDREF_H diff --git a/src/engine/xineengine.cpp b/src/engine/xineengine.cpp new file mode 100644 index 00000000..78356098 --- /dev/null +++ b/src/engine/xineengine.cpp @@ -0,0 +1,1437 @@ +/*************************************************************************** + * Copyright (C) 2005 Christophe Thommeret * + * (C) 2005 Ian Monroe * + * (C) 2005,6 Mark Kretschmann * + * (C) 2004,5 Max Howell * + * (C) 2003,4 J. Kofler * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#define DEBUG_PREFIX "xine-engine" + +#include "config.h" + +#include "xineengine.h" +#include "xinescope.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "enginetype.h" +#include "enginebase.h" + +#include "core/logging.h" + +extern "C" +{ +#include +} + +#include "settings/backendsettingspage.h" + +#ifndef LLONG_MAX +#define LLONG_MAX 9223372036854775807LL +#endif + + +//define this to use xine in a more standard way +#ifdef Q_OS_WIN32 +#define XINE_SAFE_MODE +#endif + + +///some logging static globals +namespace Log +{ + static uint bufferCount = 0; + static uint scopeCallCount = 1; //prevent divideByZero + static uint noSuitableBuffer = 0; +} + +static Fader *s_fader = 0; +static OutFader *s_outfader = 0; + + +XineEngine::XineEngine(TaskManager *task_manager) + : EngineBase() + , xine_( 0 ) + , stream_( 0 ) + , audioPort_( 0 ) + , eventQueue_( 0 ) + , post_( 0 ) + , preamp_( 1.0 ) + , stopFader_( false ) + , fadeOutRunning_ ( false ) + , equalizerEnabled_( false ) + , prune_(NULL) +{ + + //qLog(Debug) << __PRETTY_FUNCTION__; + + ReloadSettings(); +} + +XineEngine::~XineEngine() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // Wait until the fader thread is done + if( s_fader ) { + stopFader_ = true; + s_fader->resume(); // safety call if the engine is in the pause state + s_fader->wait(); + } + + // Wait until the prune scope thread is done + if (prune_) { + prune_->exit(); + prune_->wait(); + } + + delete s_fader; + delete s_outfader; + delete prune_; + + if( fadeoutOnExit_ ) { + bool terminateFader = false; + fadeOut( fadeoutDuration_, &terminateFader, true ); // true == exiting + } + + //if( xine_ ) xine_config_save( xine_, configPath() ); + + if( stream_ ) xine_close( stream_ ); + if( eventQueue_ ) xine_event_dispose_queue( eventQueue_ ); + if( stream_ ) xine_dispose( stream_ ); + if( audioPort_ ) xine_close_audio_driver( xine_, audioPort_ ); + if( post_ ) xine_post_dispose( xine_, post_ ); + if( xine_ ) xine_exit( xine_ ); + + qDebug() << "xine closed"; + + qDebug() << "Scope statistics:"; + qDebug() << "Average list size: " << Log::bufferCount / Log::scopeCallCount; + qDebug() << "Buffer failure: " << double(Log::noSuitableBuffer*100) / Log::scopeCallCount << "%"; +} + +void XineEngine::ReloadSettings() { + + QSettings s; + + //qLog(Debug) << __PRETTY_FUNCTION__; + + Engine::Base::ReloadSettings(); + + s.beginGroup(BackendSettingsPage::kSettingsGroup); + currentAudioPlugin_ = s.value("output", "auto").toString(); + if (currentAudioPlugin_ == "") currentAudioPlugin_ = "auto"; + currentAudioDevice_ = s.value("device", "").toString(); + fadeoutEnabled_ = s.value("FadeoutEnabled", false).toBool(); + fadeoutOnExit_ = s.value("FadeoutOnExit", false).toBool(); + fadeoutDuration_ = s.value("FadeoutDuration", 2000).toInt(); + crossfadeEnabled_ = s.value("CrossfadeEnabled", false).toBool(); + s.endGroup(); + + qLog(Debug) << "OUTPUT: " << currentAudioPlugin_; + +} + +bool XineEngine::Init() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + type_ = Engine::Xine; + +#ifdef Q_OS_WIN32 + putenv(QString("XINE_PLUGIN_PATH=" + QCoreApplication::applicationDirPath() + "/xine/plugins").toLatin1().constData()); +#endif // Q_OS_WIN32 + +#ifdef Q_OS_DARWIN + setenv("XINE_PLUGIN_PATH", QString(QCoreApplication::applicationDirPath() + "/../PlugIns/xine").toLatin1().constData(), 1); +#endif // Q_OS_DARWIN + + QMutexLocker l(&initMutex_); + + xine_ = xine_new(); + + if (!xine_) { + emit Error("Could not initialize xine."); + return false; + } + +#ifdef XINE_SAFE_MODE + xine_engine_set_param( xine_, XINE_ENGINE_PARAM_VERBOSITY, 99 ); +#endif + + //xine_config_load( xine_, configPath() ); + //debug() << "w00t" << configPath() << endl; + + xine_init(xine_); + + makeNewStream(); + +#ifndef XINE_SAFE_MODE + prune_ = new PruneScopeThread(this); + prune_->start(); +#endif + + return true; +} + +bool XineEngine::makeNewStream() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + audioPort_ = xine_open_audio_driver(xine_, currentAudioPlugin_.toLocal8Bit().constData(), NULL); + if( !audioPort_ ) { + //TODO make engine method that is the same but parents the dialog for us + emit Error("xine was unable to initialize any audio drivers."); + return false; + } + + stream_ = xine_stream_new( xine_, audioPort_, NULL ); + if( !stream_ ) { + xine_close_audio_driver( xine_, audioPort_ ); + audioPort_ = NULL; + emit Error("Could not create a new xine stream"); + return false; + } + + if( eventQueue_ ) + xine_event_dispose_queue( eventQueue_ ); + + xine_event_create_listener_thread( + eventQueue_ = xine_event_new_queue( stream_ ), + &XineEngine::XineEventListener, + (void*)this ); + +#ifndef XINE_SAFE_MODE + //implemented in xine-scope.h + post_ = scope_plugin_new( xine_, audioPort_ ); + + xine_set_param( stream_, XINE_PARAM_METRONOM_PREBUFFER, 6000 ); + xine_set_param( stream_, XINE_PARAM_IGNORE_VIDEO, 1 ); +#endif +#ifdef XINE_PARAM_EARLY_FINISHED_EVENT + if ( xine_check_version(1,1,1) && !(xfadeLength_ > 0) ) { + // enable gapless playback + qDebug() << "gapless playback enabled."; + //xine_set_param(stream_, XINE_PARAM_EARLY_FINISHED_EVENT, 1 ); + } +#endif + return true; +} + +// Makes sure an audio port and a stream exist. +bool XineEngine::ensureStream() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if(!stream_) return makeNewStream(); + + return true; + +} + +bool XineEngine::Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if( !ensureStream() ) + return false; + + Engine::Base::Load(url, change, force_stop_at_end, beginning_nanosec, end_nanosec); + + if( s_outfader ) { + s_outfader->finish(); + delete s_outfader; + } + + if( xfadeLength_ > 0 && xine_get_status( stream_ ) == XINE_STATUS_PLAY && + url.scheme().toLower() == "file" && + xine_get_param( stream_, XINE_PARAM_SPEED ) != XINE_SPEED_PAUSE && + ( xfadeNextTrack_ || //set by engine controller when switching tracks automatically + crossfadeEnabled_)) + { + xfadeNextTrack_ = false; + // Stop a probably running fader + if( s_fader ) { + stopFader_ = true; + s_fader->finish(); // makes the fader stop abruptly + delete s_fader; + } + s_fader = new Fader( this, xfadeLength_ ); + setEqualizerParameters( intPreamp_, equalizerGains_ ); + } + + // for users who stubbonly refuse to use DMIX or buy a good soundcard + // why doesn't xine do this? I cannot say. + xine_close( stream_ ); + + qDebug() << "Before xine_open() *****"; + + if( xine_open( stream_, url.toEncoded() ) ) { + + qDebug() << "After xine_open() *****"; + +#ifndef XINE_SAFE_MODE + xine_post_out_t *source = xine_get_audio_source( stream_ ); + xine_post_in_t *target = (xine_post_in_t*)xine_post_input( post_, const_cast("audio in") ); + xine_post_wire( source, target ); +#endif + + playlistChanged(); + + return true; + } + else + { +#ifdef XINE_PARAM_GAPLESS_SWITCH + //if ( xine_check_version(1,1,1) && !(xfadeLength_ > 0) ) + //xine_set_param( stream_, XINE_PARAM_GAPLESS_SWITCH, 0); +#endif + } + + // FAILURE to load! + //s_fader will delete itself + determineAndShowErrorMessage(); + + return false; +} + +bool XineEngine::Play(quint64 offset_nanosec) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if( !ensureStream() ) + return false; + + const bool has_audio = xine_get_stream_info( stream_, XINE_STREAM_INFO_HAS_AUDIO ); + const bool audio_handled = xine_get_stream_info( stream_, XINE_STREAM_INFO_AUDIO_HANDLED ); + + if (has_audio && audio_handled && xine_play( stream_, 0, offset_nanosec)) { + if( s_fader ) s_fader->start( QThread::LowestPriority ); + + emit StateChanged( Engine::Playing ); + + return true; + } + + //we need to stop the track that is prepped for crossfade + delete s_fader; + + emit StateChanged( Engine::Empty ); + + determineAndShowErrorMessage(); + + xine_close( stream_ ); + + return false; +} + +void XineEngine::determineAndShowErrorMessage() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QString body; + + switch (xine_get_error( stream_ )) { + case XINE_ERROR_NO_INPUT_PLUGIN: + body = "No suitable input plugin. This often means that the url's protocol is not supported. Network failures are other possible causes."; + break; + + case XINE_ERROR_NO_DEMUX_PLUGIN: + body = "No suitable demux plugin. This often means that the file format is not supported."; + break; + + case XINE_ERROR_DEMUX_FAILED: + body = "Demuxing failed."; + break; + + case XINE_ERROR_INPUT_FAILED: + body = "Could not open file."; + break; + + case XINE_ERROR_MALFORMED_MRL: + body = "The location is malformed."; + break; + + case XINE_ERROR_NONE: + // xine is thick. xine doesn't think there is an error + // but there may be! We check for other errors below. + + default: + if (!xine_get_stream_info( stream_, XINE_STREAM_INFO_AUDIO_HANDLED )) + { + // xine can read the plugin but it didn't find any codec + // THUS xine=daft for telling us it could handle the format in canDecode! + body = "There is no available decoder."; + QString const ext = QFileInfo(url_.path()).completeSuffix(); + // TODO + //if (ext == "mp3" && EngineController::installDistroCodec( "xine-engine" )) + // return; + } + else if (!xine_get_stream_info( stream_, XINE_STREAM_INFO_HAS_AUDIO )) + body = "There is no audio channel!"; + break; + } + + // TODO + qWarning() << body; +} + +void XineEngine::Stop(bool stop_after) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if( s_fader && s_fader->isRunning()) + s_fader->resume(); // safety call if the engine is in the pause state + + if ( !stream_ ) + return; + + if( (fadeoutEnabled_ && !fadeOutRunning_) || state() == Engine::Paused ) + { + s_outfader = new OutFader( this, fadeoutDuration_ ); + s_outfader->start(); + ::usleep( 100 ); //to be sure engine state won't be changed before it is checked in fadeOut() + url_ = QUrl(); //to ensure we return Empty from state() + + std::fill( scope_.begin(), scope_.end(), 0 ); + } + else if( !fadeOutRunning_ ) + { + xine_stop( stream_ ); + xine_close( stream_ ); + xine_set_param( stream_, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1); + } + + emit StateChanged( Engine::Empty ); +} + +void XineEngine::Pause() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if ( !stream_ ) + return; + + if (xine_get_param( stream_, XINE_PARAM_SPEED ) != XINE_SPEED_PAUSE ) { + if( s_fader && s_fader->isRunning() ) + s_fader->pause(); + + xine_set_param( stream_, XINE_PARAM_SPEED, XINE_SPEED_PAUSE ); + xine_set_param( stream_, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1); + emit StateChanged( Engine::Paused ); + + } + +} + +void XineEngine::Unpause() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if ( !stream_ ) + return; + + if( xine_get_param( stream_, XINE_PARAM_SPEED ) == XINE_SPEED_PAUSE ) { + if( s_fader && s_fader->isRunning() ) + s_fader->resume(); + + xine_set_param( stream_, XINE_PARAM_SPEED, XINE_SPEED_NORMAL ); + emit StateChanged( Engine::Playing ); + } + +} + +Engine::State XineEngine::state() const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if ( !stream_ || fadeOutRunning_ ) + return Engine::Empty; + + switch( xine_get_status( stream_ ) ) + { + case XINE_STATUS_PLAY: return xine_get_param( stream_, XINE_PARAM_SPEED ) != XINE_SPEED_PAUSE ? Engine::Playing : Engine::Paused; + case XINE_STATUS_IDLE: return Engine::Empty; + case XINE_STATUS_STOP: + default: return url_.isEmpty() ? Engine::Empty : Engine::Idle; + } +} + +uint XineEngine::position() const { + + if ( state() == Engine::Empty ) + return 0; + + int pos; + int time = 0; + int length; + + // Workaround for problems when you seek too quickly, see BUG 99808 + int tmp = 0, i = 0; + while( ++i < 4 ) + { + xine_get_pos_length( stream_, &pos, &time, &length ); + if( time > tmp ) break; + usleep( 100000 ); + } + + // Here we check for new metadata periodically, because xine does not emit an event + // in all cases (e.g. with ogg streams). See BUG 122505 + if ( state() != Engine::Idle && state() != Engine::Empty ) + { + const Engine::SimpleMetaBundle bundle = fetchMetaData(); + if( bundle.title != currentBundle_.title || bundle.artist != currentBundle_.artist ) { + qDebug() << "Metadata received."; + currentBundle_ = bundle; + + XineEngine* p = const_cast( this ); + p->emit MetaData( bundle ); + } + } + + return time; +} + +uint XineEngine::length() const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if ( !stream_ ) + return 0; + + // xine often delivers nonsense values for VBR files and such, so we only + // use the length for remote files + + if( url_.scheme().toLower() == "file" ) + return 0; + + else { + int pos; + int time; + int length = 0; + + xine_get_pos_length( stream_, &pos, &time, &length ); + if( length < 0 ) + length=0; + + return length; + } +} + +void XineEngine::Seek(quint64 offset_nanosec) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if( !ensureStream() ) + return; + + if( xine_get_param( stream_, XINE_PARAM_SPEED ) == XINE_SPEED_PAUSE ) { + // FIXME this is a xine API issue really, they need to add a seek function + xine_play( stream_, 0, (int) offset_nanosec); + xine_set_param( stream_, XINE_PARAM_SPEED, XINE_SPEED_PAUSE ); + } + else + xine_play( stream_, 0, (int)offset_nanosec ); +} + +void XineEngine::SetVolumeSW( uint vol ) { + + if ( !stream_ ) + return; + if( !s_fader ) + xine_set_param( stream_, XINE_PARAM_AUDIO_AMP_LEVEL, static_cast( vol * preamp_ ) ); +} + +void XineEngine::fadeOut( uint fadeLength, bool* terminate, bool exiting ) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if( fadeOutRunning_ ) //Let us not start another fadeout... + return; + + fadeOutRunning_ = !fadeOutRunning_; + const bool isPlaying = stream_ && ( xine_get_status( stream_ ) == XINE_STATUS_PLAY ); + const float originalVol = Engine::Base::MakeVolumeLogarithmic( volume_ ) * preamp_; + + // On shutdown, limit fadeout to 3 secs max, so that we don't risk getting killed + const int length = exiting ? qMin( fadeLength, 3000u ) : fadeLength; + + if( length > 0 && isPlaying ) { + // fader-class doesn't work in this spot as is, so some parts need to be copied here... (ugly) + uint stepsCount = length < 1000 ? length / 10 : 100; + uint stepSizeUs = (int)( 1000.0 * (float)length / (float)stepsCount ); + + ::usleep( stepSizeUs ); + QTime t; + t.start(); + float mix = 0.0; + while ( mix < 1.0 ) + { + if( *terminate ) break; + + ::usleep( stepSizeUs ); + float vol = Engine::Base::MakeVolumeLogarithmic( volume_ ) * preamp_; + float mix = (float)t.elapsed() / (float)length; + if ( mix > 1.0 ) + { + break; + } + if ( stream_ ) + { + float v = 4.0 * (1.0 - mix) / 3.0; + xine_set_param( stream_, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)( v < 1.0 ? vol * v : vol ) ); + } + } + } + if( fadeOutRunning_ && stream_ ) + xine_set_param( stream_, XINE_PARAM_AUDIO_AMP_LEVEL, (uint) originalVol ); + fadeOutRunning_ = !fadeOutRunning_; +} + +void XineEngine::setEqualizerEnabled( bool enable ) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if ( !stream_ ) + return; + + equalizerEnabled_ = enable; + + if( !enable ) { + QList gains; + for( uint x = 0; x < 10; x++ ) + gains << -101; // sets eq gains to zero. + + setEqualizerParameters( 0, gains ); + } +} + +/* + sets the eq params for xine engine - have to rescale eq params to fitting range (adapted from kaffeine and xfmedia) + + preamp +pre: (-100..100) +post: (0.1..1.9) - this is not really a preamp but we use the xine preamp parameter for our normal volume. so we make a postamp. + +gains +pre: (-100..100) +post: (1..200) - (1 = down, 100 = middle, 200 = up, 0 = off) + */ +void XineEngine::setEqualizerParameters( int preamp, const QList &gains ) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if ( !stream_ ) + return; + + equalizerGains_ = gains; + intPreamp_ = preamp; + QList::ConstIterator it = gains.begin(); + + xine_set_param( stream_, XINE_PARAM_EQ_30HZ, int( (*it )*0.995 + 100 ) ); + xine_set_param( stream_, XINE_PARAM_EQ_60HZ, int( (*++it)*0.995 + 100 ) ); + xine_set_param( stream_, XINE_PARAM_EQ_125HZ, int( (*++it)*0.995 + 100 ) ); + xine_set_param( stream_, XINE_PARAM_EQ_250HZ, int( (*++it)*0.995 + 100 ) ); + xine_set_param( stream_, XINE_PARAM_EQ_500HZ, int( (*++it)*0.995 + 100 ) ); + xine_set_param( stream_, XINE_PARAM_EQ_1000HZ, int( (*++it)*0.995 + 100 ) ); + xine_set_param( stream_, XINE_PARAM_EQ_2000HZ, int( (*++it)*0.995 + 100 ) ); + xine_set_param( stream_, XINE_PARAM_EQ_4000HZ, int( (*++it)*0.995 + 100 ) ); + xine_set_param( stream_, XINE_PARAM_EQ_8000HZ, int( (*++it)*0.995 + 100 ) ); + xine_set_param( stream_, XINE_PARAM_EQ_16000HZ, int( (*++it)*0.995 + 100 ) ); + + preamp_ = ( preamp - 0.1 * preamp + 100 ) / 100.0; + SetVolume( volume_ ); +} + +bool XineEngine::CanDecode( const QUrl &url ) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + static QStringList list; + if (list.isEmpty()) { + + QMutexLocker l(&const_cast(this)->initMutex_); + + if (list.isEmpty()) { + char* exts = xine_get_file_extensions( xine_ ); + list = QString(exts).split(' '); + free( exts ); exts = NULL; + //images + list.removeAll("png"); + list.removeAll("jpg"); + list.removeAll("jpeg"); + list.removeAll("gif"); + list.removeAll("ilbm"); + list.removeAll("iff"); + //subtitles + list.removeAll("asc"); + list.removeAll("txt"); + list.removeAll("sub"); + list.removeAll("srt"); + list.removeAll("smi"); + list.removeAll("ssa"); + //HACK we also check for m4a because xine plays them but + //for some reason doesn't return the extension + if(!list.contains("m4a")) + list << "m4a"; + } + } + + if (url.scheme() == "cdda") + // play audio CDs pls + return true; + + QString path = url.path(); + + // partial downloads from Konqi and other browsers + // tend to have a .part extension + if (path.endsWith( ".part" )) + path = path.left( path.length() - 5 ); + + const QString ext = path.mid( path.lastIndexOf( '.' ) + 1 ).toLower(); + + return list.contains( ext ); +} + +const Engine::Scope & XineEngine::scope(int chunk_length) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if( !post_ || !stream_ || xine_get_status( stream_ ) != XINE_STATUS_PLAY ) + return scope_; + + MyNode* const myList = scope_plugin_list( post_ ); + metronom_t* const myMetronom = scope_plugin_metronom( post_ ); + const int myChannels = scope_plugin_channels( post_ ); + int scopeidx = 0; + + if (myChannels > 2) + return scope_; + + for( int n, frame = 0; frame < 512; ) { + MyNode *best_node = 0; + + for( MyNode *node = myList->next; node != myList; node = node->next, Log::bufferCount++ ) + if( node->vpts <= currentVpts_ && (!best_node || node->vpts > best_node->vpts) ) + best_node = node; + + if( !best_node || best_node->vpts_end < currentVpts_ ) { + Log::noSuitableBuffer++; break; } + + int64_t + diff = currentVpts_; + diff -= best_node->vpts; + diff *= 1<<16; + diff /= myMetronom->pts_per_smpls; + + const int16_t* + data16 = best_node->mem; + data16 += diff; + + diff += diff % myChannels; //important correction to ensure we don't overflow the buffer + diff /= myChannels; //use units of frames, not samples + + //calculate the number of available samples in this buffer + n = best_node->num_frames; + n -= diff; + n += frame; //clipping for # of frames we need + + if( n > 512 ) + n = 512; //we don't want more than 512 frames + + //int a, c + for( int c; frame < n; ++frame, data16 += myChannels ) { + //a = c = 0 + for( c=0; c < myChannels; ++c ) + { + // we now give interleaved pcm to the scope + scope_[scopeidx++] = data16[c]; + if (myChannels == 1) // duplicate mono samples + scope_[scopeidx++] = data16[c]; + } + } + + currentVpts_ = best_node->vpts_end; + currentVpts_++; //FIXME needs to be done for some reason, or you get situations where it uses same buffer again and again + } + + Log::scopeCallCount++; + + return scope_; +} + +void XineEngine::PruneScope() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (!stream_) return; + + //here we prune the buffer list regularly + + MyNode *myList = scope_plugin_list( post_ ); + + if ( ! myList ) return; + + //we operate on a subset of the list for thread-safety + MyNode * const first_node = myList->next; + MyNode const * const list_end = myList; + + currentVpts_ = (xine_get_status( stream_ ) == XINE_STATUS_PLAY) + ? xine_get_current_vpts( stream_ ) + : LLONG_MAX; //if state is not playing OR paused, empty the list + //: std::numeric_limits::max(); //TODO don't support crappy gcc 2.95 + + for( MyNode *prev = first_node, *node = first_node->next; node != list_end; node = node->next ) + { + //we never delete first_node + //this maintains thread-safety + if( node->vpts_end < currentVpts_ ) { + prev->next = node->next; + + free( node->mem ); + free( node ); + + node = prev; + } + + prev = node; + } +} + +bool XineEngine::event( QEvent *e ) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + +#define message static_cast(static_cast(e)->data()) +#if 0 + switch(e->type()) { + + case XineEvent::PlaybackFinished: //XINE_EVENT_UI_PLAYBACK_FINISHED + emit TrackEnded(); + return true; + + case XineEvent::InfoMessage: + emit InfoMessage( (*message).arg( url_.toString() ) ); + delete message; + return true; + + case XineEvent::StatusMessage: + emit StatusText( *message ); + delete message; + return true; + + case XineEvent::MetaInfoChanged: { //meta info has changed + qDebug() << "Metadata received."; + const Engine::SimpleMetaBundle bundle = fetchMetaData(); + if( bundle.title != currentBundle_.title || bundle.artist != currentBundle_.artist ) { + currentBundle_ = bundle; + + emit MetaData( bundle ); + } + return true; + } + + case XineEvent::Redirecting: + emit StatusText( QString("Redirecting to: ").arg( *message ) ); + Load(QUrl(*message), Engine::Auto, 0, 0, 0); + Play(0); + delete message; + return true; + case XineEvent::LastFMTrackChanged: + emit LastFmTrackChange(); + return true; + default: + ; + } +#endif + +#undef message + return false; +} + +//SLOT +void XineEngine::playlistChanged() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // TODO + /*#ifdef XINE_PARAM_EARLY_FINISHED_EVENT +#ifdef XINE_PARAM_GAPLESS_SWITCH +if ( xine_check_version(1,1,1) && !(xfadeLength_ > 0) +&& url_.isLocalFile() && Playlist::instance()->isTrackAfter() ) +{ +xine_set_param(stream_, XINE_PARAM_EARLY_FINISHED_EVENT, 1 ); +debug() << "XINE_PARAM_EARLY_FINISHED_EVENT enabled" << endl; +} +else +{ + //we don't want an early finish event if there is no track after the current one + xine_set_param(stream_, XINE_PARAM_EARLY_FINISHED_EVENT, 0 ); + debug() << "XINE_PARAM_EARLY_FINISHED_EVENT disabled" << endl; + } +#endif +#endif*/ +} + +static time_t last_error_time = 0; // hysteresis on xine errors +static int last_error = XINE_MSG_NO_ERROR; + +void XineEngine::XineEventListener( void *p, const xine_event_t* xineEvent ) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + time_t current; + + if( !p ) return; + +#define xe static_cast(p) + + switch( xineEvent->type ) + { + case XINE_EVENT_UI_SET_TITLE: + + qDebug() << "XINE_EVENT_UI_SET_TITLE"; + + QApplication::postEvent( xe, new XineEvent( XineEvent::MetaInfoChanged ) ); + + break; + + case XINE_EVENT_UI_PLAYBACK_FINISHED: + qDebug() << "XINE_EVENT_UI_PLAYBACK_FINISHED"; + +#ifdef XINE_PARAM_GAPLESS_SWITCH + // TODO + /*if ( xine_check_version(1,1,1) && xe->url_.isLocalFile() //Remote media break with gapless + //don't prepare for a track that isn't coming + && Playlist::instance() + && Playlist::instance()->isTrackAfter() + && !AmarokConfig::crossfade() ) + xine_set_param( xe->stream_, XINE_PARAM_GAPLESS_SWITCH, 1);*/ +#endif + //emit signal from GUI thread + QApplication::postEvent( xe, new XineEvent(XineEvent::PlaybackFinished) ); + break; + + case XINE_EVENT_PROGRESS: { + xine_progress_data_t* pd = (xine_progress_data_t*)xineEvent->data; + + QString + msg = "%1 %2%"; + msg = msg.arg( QString::fromUtf8( pd->description ) ) + .arg( QString::number(pd->percent) + QLocale::system().percent() ); + + XineEvent *e = new XineEvent( XineEvent::StatusMessage ); + e->setData( new QString( msg ) ); + + QApplication::postEvent( xe, e ); + + } break; + + case XINE_EVENT_MRL_REFERENCE_EXT: { + /// xine has read the stream and found it actually links to something else + /// so we need to play that instead + + QString message = QString::fromUtf8( + static_cast(xineEvent->data)->mrl); + XineEvent *e = new XineEvent( XineEvent::Redirecting ); + e->setData( new QString( message ) ); + + QApplication::postEvent( xe, e ); + + } break; + + case XINE_EVENT_UI_MESSAGE: + { + qDebug() << "message received from xine"; + + xine_ui_message_data_t *data = (xine_ui_message_data_t *)xineEvent->data; + QString message; + + switch( data->type ) + { + case XINE_MSG_NO_ERROR: + { + //series of \0 separated strings, terminated with a \0\0 + char str[2000]; + char *p = str; + for( char *msg = data->messages; !(*msg == '\0' && *(msg+1) == '\0'); ++msg, ++p ) + *p = *msg == '\0' ? '\n' : *msg; + *p = '\0'; + + qDebug() << str; + + break; + } + + case XINE_MSG_ENCRYPTED_SOURCE: + break; + + case XINE_MSG_UNKNOWN_HOST: + message = "The host is unknown for the URL: %1"; goto param; + case XINE_MSG_UNKNOWN_DEVICE: + message = "The device name you specified seems invalid."; goto param; + case XINE_MSG_NETWORK_UNREACHABLE: + message = "The network appears unreachable."; goto param; + case XINE_MSG_AUDIO_OUT_UNAVAILABLE: + message = "Audio output unavailable; the device is busy."; goto param; + case XINE_MSG_CONNECTION_REFUSED: + message = "The connection was refused for the URL: %1"; goto param; + case XINE_MSG_FILE_NOT_FOUND: + message = "xine could not find the URL: %1"; goto param; + case XINE_MSG_PERMISSION_ERROR: + message = "Access was denied for the URL: %1"; goto param; + case XINE_MSG_READ_ERROR: + message = "The source cannot be read for the URL: %1"; goto param; + case XINE_MSG_LIBRARY_LOAD_ERROR: + message = "A problem occurred while loading a library or decoder."; goto param; + + case XINE_MSG_GENERAL_WARNING: + message = "General Warning"; goto explain; + case XINE_MSG_SECURITY: + message = "Security Warning"; goto explain; + default: + message = "Unknown Error"; goto explain; + + + explain: + + // Don't flood the user with error messages + if( (last_error_time + 10) > time( ¤t ) && + data->type == last_error ) + { + last_error_time = current; + return; + } + last_error_time = current; + last_error = data->type; + + if( data->explanation ) + { + message.prepend( "" ); + message += ":

"; + message += QString::fromUtf8( (char*)data + data->explanation ); + } + else break; //if no explanation then why bother! + + //FALL THROUGH + + param: + + // Don't flood the user with error messages + if((last_error_time + 10) > time(¤t) && + data->type == last_error) + { + last_error_time = current; + return; + } + last_error_time = current; + last_error = data->type; + + message.prepend( "

" ); + message += "

"; + + if(data->explanation) + { + message += "xine parameters: "; + message += QString::fromUtf8( (char*)data + data->parameters ); + message += ""; + } + else message += "Sorry, no additional information is available."; + + QApplication::postEvent( xe, new XineEvent(XineEvent::InfoMessage, new QString(message)) ); + } + + } //case + case XINE_EVENT_UI_CHANNELS_CHANGED: //Flameeyes used this for last.fm track changes + QApplication::postEvent( xe, new XineEvent(XineEvent::LastFMTrackChanged) ); + break; + } //switch + +#undef xe +} + +Engine::SimpleMetaBundle +XineEngine::fetchMetaData() const +{ + Engine::SimpleMetaBundle bundle; + bundle.title = QString::fromUtf8( xine_get_meta_info( stream_, XINE_META_INFO_TITLE ) ); + bundle.artist = QString::fromUtf8( xine_get_meta_info( stream_, XINE_META_INFO_ARTIST ) ); + bundle.album = QString::fromUtf8( xine_get_meta_info( stream_, XINE_META_INFO_ALBUM ) ); + bundle.comment = QString::fromUtf8( xine_get_meta_info( stream_, XINE_META_INFO_COMMENT ) ); + bundle.genre = QString::fromUtf8( xine_get_meta_info( stream_, XINE_META_INFO_GENRE ) ); + bundle.bitrate = QString::number( xine_get_stream_info( stream_, XINE_STREAM_INFO_AUDIO_BITRATE ) / 1000 ); + bundle.samplerate = QString::number( xine_get_stream_info( stream_, XINE_STREAM_INFO_AUDIO_SAMPLERATE ) ); + bundle.year = QString::fromUtf8( xine_get_meta_info( stream_, XINE_META_INFO_YEAR ) ); + bundle.tracknr = QString::fromUtf8( xine_get_meta_info( stream_, XINE_META_INFO_TRACK_NUMBER ) ); + + return bundle; +} + +bool XineEngine::metaDataForUrl(const QUrl &url, Engine::SimpleMetaBundle &b) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + bool result = false; + xine_stream_t* tmpstream = xine_stream_new(xine_, NULL, NULL); + if (xine_open(tmpstream, QFile::encodeName(url.toString()))) { + QString audioCodec = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_SYSTEMLAYER)); + + if (audioCodec == "CDDA") { + QString title = QString::fromUtf8( + xine_get_meta_info(tmpstream, XINE_META_INFO_TITLE)); + if ((!title.isNull()) && (!title.isEmpty())) { //no meta info + b.title = title; + b.artist = + QString::fromUtf8( + xine_get_meta_info(tmpstream, XINE_META_INFO_ARTIST)); + b.album = + QString::fromUtf8( + xine_get_meta_info(tmpstream, XINE_META_INFO_ALBUM)); + b.genre = + QString::fromUtf8( + xine_get_meta_info(tmpstream, XINE_META_INFO_GENRE)); + b.year = + QString::fromUtf8( + xine_get_meta_info(tmpstream, XINE_META_INFO_YEAR)); + b.tracknr = + QString::fromUtf8( + xine_get_meta_info(tmpstream, XINE_META_INFO_TRACK_NUMBER)); + if( b.tracknr.isEmpty() ) + b.tracknr = QFileInfo(url.path()).fileName(); + } else { + b.title = QString("Track %1").arg(QFileInfo(url.path()).fileName()); + b.album = "AudioCD"; + } + } + + if (audioCodec == "CDDA" || audioCodec == "WAV") { + result = true; + int samplerate = xine_get_stream_info( tmpstream, XINE_STREAM_INFO_AUDIO_SAMPLERATE ); + + // xine would provide a XINE_STREAM_INFO_AUDIO_BITRATE, but unfortunately not for CDDA or WAV + // so we calculate the bitrate by our own + int bitsPerSample = xine_get_stream_info( tmpstream, XINE_STREAM_INFO_AUDIO_BITS ); + int nbrChannels = xine_get_stream_info( tmpstream, XINE_STREAM_INFO_AUDIO_CHANNELS ); + int bitrate = (samplerate * bitsPerSample * nbrChannels) / 1000; + + b.bitrate = QString::number(bitrate); + b.samplerate = QString::number(samplerate); + int pos, time, length = 0; + xine_get_pos_length(tmpstream, &pos, &time, &length); + b.length = QString::number(length / 1000); + } + xine_close(tmpstream); + } + xine_dispose(tmpstream); + return result; +} + +bool XineEngine::getAudioCDContents(const QString &device, QList &urls) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + const char * const *xine_urls = NULL; + int num; + int i = 0; + + if (!device.isNull()) { + qDebug() << "xine-engine setting CD Device to: " << device; + xine_cfg_entry_t config; + if (!xine_config_lookup_entry(xine_, "input.cdda_device", &config)) { + emit StatusText("Failed CD device lookup in xine engine"); + return false; + } + config.str_value = (char *)device.toLatin1().constData(); + xine_config_update_entry(xine_, &config); + } + + emit StatusText("Getting AudioCD contents..."); + + xine_urls = xine_get_autoplay_mrls(xine_, "CD", &num); + + if (xine_urls) { + while (xine_urls[i]) { + urls << QUrl(xine_urls[i]); + ++i; + } + } + else emit StatusText("Could not read AudioCD"); + + return true; +} + +bool XineEngine::flushBuffer() +{ + return false; +} + +bool XineEngine::lastFmProxyRequired() { + return !( xine_check_version(1,1,9) ); +} + +////////////////////////////////////////////////////////////////////////////// +/// class Fader +////////////////////////////////////////////////////////////////////////////// + +Fader::Fader( XineEngine *engine, uint fadeMs ) + : QThread(engine) + , engine_( engine ) + , xine_( engine->xine_ ) + , decrease_( engine->stream_ ) + , increase_( 0 ) + , port_( engine->audioPort_ ) + , post_( engine->post_ ) + , fadeLength_( fadeMs ) + , paused_( false ) + , terminated_( false ) +{ + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if( engine->makeNewStream() ) { + increase_ = engine->stream_; + + xine_set_param( increase_, XINE_PARAM_AUDIO_AMP_LEVEL, 0 ); + } + else { + s_fader = 0; + deleteLater(); + } +} + +Fader::~Fader() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + wait(); + + xine_close( decrease_ ); + xine_dispose( decrease_ ); + xine_close_audio_driver( xine_, port_ ); + if( post_ ) xine_post_dispose( xine_, post_ ); + + if( !engine_->stopFader_ ) + engine_->SetVolume( engine_->volume() ); + + engine_->stopFader_ = false; + s_fader = 0; +} + +void Fader::run() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // do a volume change in 100 steps (or every 10ms) + uint stepsCount = fadeLength_ < 1000 ? fadeLength_ / 10 : 100; + uint stepSizeUs = (int)( 1000.0 * (float)fadeLength_ / (float)stepsCount ); + + float mix = 0.0; + float elapsedUs = 0.0; + while ( mix < 1.0 ) + { + if ( terminated_ ) + break; + // sleep a constant amount of time + QThread::usleep( stepSizeUs ); + + if ( paused_ ) + continue; + + elapsedUs += stepSizeUs; + + // get volume (amarok main * equalizer preamp) + float vol = Engine::Base::MakeVolumeLogarithmic( engine_->volume_ ) * engine_->preamp_; + + // compute the mix factor as the percentage of time spent since fade begun + float mix = (elapsedUs / 1000.0) / (float)fadeLength_; + if ( mix > 1.0 ) + { + if ( increase_ ) + xine_set_param( increase_, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)vol ); + break; + } + + // change volume of streams (using dj-like cross-fade profile) + if ( decrease_ ) + { + //xine_set_param( decrease_, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)(vol * (1.0 - mix)) ); // linear + float v = 4.0 * (1.0 - mix) / 3.0; + xine_set_param( decrease_, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)( v < 1.0 ? vol * v : vol ) ); + } + if ( increase_ ) + { + //xine_set_param( increase_, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)(vol * mix) ); //linear + float v = 4.0 * mix / 3.0; + xine_set_param( increase_, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)( v < 1.0 ? vol * v : vol ) ); + } + } + + //stop using cpu! + xine_stop( decrease_ ); + + deleteLater(); +} + +void Fader::pause() { + paused_ = true; +} + +void Fader::resume() { + paused_ = false; +} + +void Fader::finish() { + terminated_ = true; +} + +////////////////////////////////////////////////////////////////////////////// +/// class OutFader +////////////////////////////////////////////////////////////////////////////// + +OutFader::OutFader( XineEngine *engine, uint fadeLength ) + : QThread(engine) + , engine_( engine ) + , terminated_( false ) + , fadeLength_( fadeLength ) +{ +} + +OutFader::~OutFader() { + wait(); + + s_outfader = 0; +} + +void OutFader::run() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + engine_->fadeOut( fadeLength_, &terminated_ ); + + xine_stop( engine_->stream_ ); + xine_close( engine_->stream_ ); + xine_set_param( engine_->stream_, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1); + + deleteLater(); +} + +void OutFader::finish() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + terminated_ = true; +} + +PruneScopeThread::PruneScopeThread(XineEngine *parent) + : engine_(parent) +{ +} + +void PruneScopeThread::run() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QTimer timer; + connect(&timer, SIGNAL(timeout()), engine_, SLOT(PruneScope()), Qt::DirectConnection); + timer.start(1000); + + exec(); +} + +qint64 XineEngine::position_nanosec() const { + return 0; +} + +qint64 XineEngine::length_nanosec() const { + return 0; +} + +EngineBase::PluginDetailsList XineEngine::GetPluginList() const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + PluginDetailsList ret; + const char *const *plugins = xine_list_audio_output_plugins(xine_); + + { + PluginDetails details; + details.name = "auto"; + details.description = "Automatically detected"; + ret << details; + } + + for(int i =0 ; plugins[i] ; ++i) { + PluginDetails details; + details.name = QString::fromUtf8(plugins[i]); + if (details.name == "alsa") details.description = "ALSA"; + else if (details.name == "oss") details.description = "OSS"; + else if (details.name == "pulseaudio") details.description = "PulseAudio"; + else if (details.name == "file") details.description = "File"; + else if (details.name == "none") details.description = "None"; + else details.description = QString::fromUtf8(plugins[i]); + ret << details; + qLog(Debug) << details.name << details.description; + } + + return ret; + +} + +EngineBase::OutputDetailsList XineEngine::GetOutputsList() const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + OutputDetailsList ret; + + PluginDetailsList plugins = GetPluginList(); + for (const PluginDetails &plugin : plugins) { + OutputDetails output; + output.name = plugin.name; + output.description = plugin.description; + if (plugin.name == "auto") output.iconname = "soundcard"; + else if ((plugin.name == "alsa")||(plugin.name == "oss")) output.iconname = "alsa"; + else if (plugin.name== "jack") output.iconname = "jack"; + else if (plugin.name == "pulseaudio") output.iconname = "pulseaudio"; + else if (plugin.name == "bluetooth") output.iconname = "bluetooth"; + else if (plugin.name == "file") output.iconname = "document-new"; + else output.iconname = "soundcard"; + ret.append(output); + } + + return ret; +} + +bool XineEngine::ALSADeviceSupport(const QString &name) { + + return (name == "alsa" || name == "oss"); + +} + +#if 0 +void XineEngine::SetOutput(QString output, QString device) { + + currentAudioPlugin_ = output; + currentAudioDevice_ = device; + +} + +#endif diff --git a/src/engine/xineengine.h b/src/engine/xineengine.h new file mode 100644 index 00000000..1cdd431e --- /dev/null +++ b/src/engine/xineengine.h @@ -0,0 +1,211 @@ +/*************************************************************************** + * Copyright (C) 2004,5 Max Howell * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef XINE_ENGINE_H +#define XINE_ENGINE_H + +#include "config.h" + +#include "enginebase.h" +#include +#include +#include +#include + +extern "C" +{ + #include + #include +} + +class XineConfigDialog; +//class DeviceFinder; +class TaskManager; + +class XineEvent : public QEvent { +public: + enum EventType { + PlaybackFinished = QEvent::User + 1, + InfoMessage, + StatusMessage, + MetaInfoChanged, + Redirecting, + LastFMTrackChanged, + }; + + XineEvent(EventType type, void* data = NULL) : QEvent(QEvent::Type(type)), data_(data) {} + + void setData(void* data) { data_ = data; } + void* data() const { return data_; } + +private: + void* data_; +}; + +class PruneScopeThread; + +class XineEngine : public Engine::Base { + Q_OBJECT + +public: + + XineEngine(TaskManager *task_manager); + ~XineEngine(); + + friend class Fader; + friend class OutFader; + friend class PruneScopeThread; + + virtual bool Init(); + + virtual qint64 position_nanosec() const; + virtual qint64 length_nanosec() const; + + virtual bool CanDecode( const QUrl &); + virtual bool Load(const QUrl &url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec); + virtual bool Play(quint64 offset_nanosec); + virtual void Stop(bool stop_after = false); + virtual void Pause(); + virtual void Unpause(); + virtual uint position() const; + virtual uint length() const; + virtual void Seek(quint64 offset_nanosec); + + virtual bool metaDataForUrl(const QUrl &url, Engine::SimpleMetaBundle &b); + virtual bool getAudioCDContents(const QString &device, QList &urls); + virtual bool flushBuffer(); + + virtual Engine::State state() const; + virtual const Engine::Scope& scope(int chunk_length); + + virtual void setEqualizerEnabled( bool ); + virtual void setEqualizerParameters( int preamp, const QList& ); + virtual void SetVolumeSW( uint ); + virtual void fadeOut( uint fadeLength, bool* terminate, bool exiting = false ); + + static void XineEventListener( void*, const xine_event_t* ); + virtual bool event( QEvent* ); + + virtual void playlistChanged(); + virtual void ReloadSettings(); + + Engine::SimpleMetaBundle fetchMetaData() const; + + virtual bool lastFmProxyRequired(); + + bool makeNewStream(); + bool ensureStream(); + + void determineAndShowErrorMessage(); //call after failure to load/play + + //static void SetOutput(QString output, QString device); + + xine_t *xine_; + xine_stream_t *stream_; + xine_audio_port_t *audioPort_; + xine_event_queue_t *eventQueue_; + xine_post_t *post_; + + int64_t currentVpts_; + float preamp_; + + bool stopFader_; + bool fadeOutRunning_; + + QString currentAudioPlugin_; //to see if audio plugin has been changed need to save these for when the audio plugin is changed and xine reloaded + QString currentAudioDevice_; + bool equalizerEnabled_; + int intPreamp_; + QList equalizerGains_; + + QMutex initMutex_; + + //QSettings settings_; + bool fadeoutOnExit_; + bool fadeoutEnabled_; + bool crossfadeEnabled_; + int fadeoutDuration_; + int xfadeLength_; + bool xfadeNextTrack_; + QUrl url_; + + PruneScopeThread* prune_; + + mutable Engine::SimpleMetaBundle currentBundle_; + + XineEngine(); + + OutputDetailsList GetOutputsList() const; + PluginDetailsList GetPluginList() const; + static bool ALSADeviceSupport(const QString &name); + +private: + + + +private slots: + void PruneScope(); + +signals: + void resetConfig(xine_t *xine); + void InfoMessage(const QString&); + void LastFmTrackChange(); +}; + +class Fader : public QThread { + + XineEngine *engine_; + xine_t *xine_; + xine_stream_t *decrease_; + xine_stream_t *increase_; + xine_audio_port_t *port_; + xine_post_t *post_; + uint fadeLength_; + bool paused_; + bool terminated_; + + virtual void run(); + +public: + Fader( XineEngine *, uint fadeLengthMs ); + ~Fader(); + void pause(); + void resume(); + void finish(); +}; + +class OutFader : public QThread { + + XineEngine *engine_; + bool terminated_; + uint fadeLength_; + + virtual void run(); + +public: + OutFader( XineEngine *, uint fadeLengthMs ); + ~OutFader(); + + void finish(); +}; + +class PruneScopeThread : public QThread { +public: + PruneScopeThread(XineEngine *parent); + +protected: + virtual void run(); + +private: + XineEngine* engine_; + +}; + +#endif diff --git a/src/engine/xinescope.c b/src/engine/xinescope.c new file mode 100644 index 00000000..d844ca02 --- /dev/null +++ b/src/engine/xinescope.c @@ -0,0 +1,188 @@ +/* Author: Max Howell , (C) 2004 + Copyright: See COPYING file that comes with this distribution + + This has to be a c file or for some reason it won't link! (GCC 3.4.1) +*/ + +/* gcc doesn't like inline for me */ +#define inline +/* need access to port_ticket */ +#define XINE_ENGINE_INTERNAL + +#include "config.h" + +#include "xinescope.h" +#include +#include + +typedef struct scope_plugin_s scope_plugin_t; + +struct scope_plugin_s +{ + post_plugin_t post; + + metronom_t metronom; + int channels; + MyNode *list; +}; + +/************************* + * post plugin functions * + *************************/ + +static int +scope_port_open( xine_audio_port_t *port_gen, xine_stream_t *stream, uint32_t bits, uint32_t rate, int mode ) +{ + #define port ((post_audio_port_t*)port_gen) + #define this ((scope_plugin_t*)((post_audio_port_t*)port_gen)->post) + + _x_post_rewire( (post_plugin_t*)port->post ); + _x_post_inc_usage( port ); + + port->stream = stream; + port->bits = bits; + port->rate = rate; + port->mode = mode; + + this->channels = _x_ao_mode2channels( mode ); + + return port->original_port->open( port->original_port, stream, bits, rate, mode ); +} + +static void +scope_port_close( xine_audio_port_t *port_gen, xine_stream_t *stream ) +{ + MyNode *node; + + /* ensure the buffers are deleted during the next XineEngine::timerEvent() */ + for( node = this->list->next; node != this->list; node = node->next ) + node->vpts = node->vpts_end = -1; + + port->stream = NULL; + port->original_port->close( port->original_port, stream ); + + _x_post_dec_usage( port ); +} + +static void +scope_port_put_buffer( xine_audio_port_t *port_gen, audio_buffer_t *buf, xine_stream_t *stream ) +{ +/* FIXME With 8-bit samples the scope won't work correctly. For a special 8-bit code path, + the sample size could be checked like this: if( port->bits == 8 ) */ + + const int num_samples = buf->num_frames * this->channels; + metronom_t *myMetronom = &this->metronom; + MyNode *new_node; + + /* I keep my own metronom because xine wouldn't for some reason */ + memcpy( &this->metronom, stream->metronom, sizeof(metronom_t) ); + + new_node = malloc( sizeof(MyNode) ); + new_node->vpts = myMetronom->got_audio_samples( myMetronom, buf->vpts, buf->num_frames ); + new_node->num_frames = buf->num_frames; + new_node->mem = malloc( num_samples * 2 ); + memcpy( new_node->mem, buf->mem, num_samples * 2 ); + + { + int64_t + K = myMetronom->pts_per_smpls; /*smpls = 1<<16 samples*/ + K *= num_samples; + K /= (1<<16); + K += new_node->vpts; + + new_node->vpts_end = K; + } + + port->original_port->put_buffer( port->original_port, buf, stream ); + + /* finally we should append the current buffer to the list + * this is thread-safe due to the way we handle the list in the GUI thread */ + new_node->next = this->list->next; + this->list->next = new_node; + + #undef port + #undef this +} + +static void +scope_dispose( post_plugin_t *this ) +{ + MyNode *list = ((scope_plugin_t*)this)->list; + MyNode *prev; + MyNode *node = list; + + /* Free all elements of the list (a ring buffer) */ + do { + prev = node->next; + + free( node->mem ); + free( node ); + + node = prev; + } + while( node != list ); + + + free( this ); +} + + +/************************ + * plugin init function * + ************************/ + +xine_post_t* +scope_plugin_new( xine_t *xine, xine_audio_port_t *audio_target ) +{ + scope_plugin_t *scope_plugin = calloc( 1, sizeof(scope_plugin_t) ); + post_plugin_t *post_plugin = (post_plugin_t*)scope_plugin; + + { + post_in_t *input; + post_out_t *output; + post_audio_port_t *port; + + _x_post_init( post_plugin, 1, 0 ); + + port = _x_post_intercept_audio_port( post_plugin, audio_target, &input, &output ); + port->new_port.open = scope_port_open; + port->new_port.close = scope_port_close; + port->new_port.put_buffer = scope_port_put_buffer; + + post_plugin->xine_post.audio_input[0] = &port->new_port; + post_plugin->xine_post.type = PLUGIN_POST; + + post_plugin->dispose = scope_dispose; + } + + /* code is straight from xine_init_post() + can't use that function as it only dlopens the plugins + and our plugin is statically linked in */ + + post_plugin->running_ticket = xine->port_ticket; + post_plugin->xine = xine; + + /* scope_plugin_t init */ + scope_plugin->list = calloc( 1, sizeof(MyNode) ); + scope_plugin->list->next = scope_plugin->list; + + return &post_plugin->xine_post; +} + +MyNode* +scope_plugin_list( void *post ) +{ + return ((scope_plugin_t*)post)->list; +} + +int +scope_plugin_channels( void *post ) +{ + return ((scope_plugin_t*)post)->channels; +} + +metronom_t* +scope_plugin_metronom( void *post ) +{ + return &((scope_plugin_t*)post)->metronom; +} diff --git a/src/engine/xinescope.h b/src/engine/xinescope.h new file mode 100644 index 00000000..c78be2aa --- /dev/null +++ b/src/engine/xinescope.h @@ -0,0 +1,52 @@ +/* Author: Max Howell , (C) 2004 + Copyright: See COPYING file that comes with this distribution + + This has to be a c file or for some reason it won't link! (GCC 3.4.1) +*/ + +#ifndef XINESCOPE_H +#define XINESCOPE_H + +/* need access to some stuff for scope time stamping */ +#define METRONOM_INTERNAL + +#include "config.h" + +#include +#include + +typedef struct my_node_s MyNode; + +struct my_node_s +{ + MyNode *next; + int16_t *mem; + int num_frames; + int64_t vpts; + int64_t vpts_end; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + xine_post_t* + scope_plugin_new( xine_t*, xine_audio_port_t* ); + + /* we sacrifice type-safety here because some GCCs appear broken + * and choke on redefining the xine_post_t typedef + */ + + MyNode* + scope_plugin_list( void* ); + + int + scope_plugin_channels( void* ); + + metronom_t* + scope_plugin_metronom( void* ); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/equalizer/equalizer.cpp b/src/equalizer/equalizer.cpp new file mode 100644 index 00000000..d32b617c --- /dev/null +++ b/src/equalizer/equalizer.cpp @@ -0,0 +1,349 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "equalizer.h" +#include "ui_equalizer.h" + +#include +#include +#include +#include +#include + +#include "core/iconloader.h" +#include "core/logging.h" +#include "equalizerslider.h" + +const char *Equalizer::kGainText[] = { "60", "170", "310", "600", "1k", "3k", "6k", "12k", "14k", "16k" }; + +const char *Equalizer::kSettingsGroup = "Equalizer"; + +Equalizer::Equalizer(QWidget *parent) + : QDialog(parent), ui_(new Ui_Equalizer), loading_(false) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + ui_->setupUi(this); + + // Icons + ui_->preset_del->setIcon(IconLoader::Load("list-remove")); + ui_->preset_save->setIcon(IconLoader::Load("document-save")); + + preamp_ = AddSlider(tr("Pre-amp")); + + QFrame* line = new QFrame(ui_->slider_container); + line->setFrameShape(QFrame::VLine); + line->setFrameShadow(QFrame::Sunken); + ui_->slider_container->layout()->addWidget(line); + + for (int i = 0; i < kBands; ++i) gain_[i] = AddSlider(kGainText[i]); + + // Must be done before the signals are connected + ReloadSettings(); + + connect(ui_->enable, SIGNAL(toggled(bool)), SIGNAL(EnabledChanged(bool))); + connect(ui_->enable, SIGNAL(toggled(bool)), ui_->slider_container, SLOT(setEnabled(bool))); + connect(ui_->enable, SIGNAL(toggled(bool)), SLOT(Save())); + connect(ui_->preset, SIGNAL(currentIndexChanged(int)), SLOT(PresetChanged(int))); + connect(ui_->preset_save, SIGNAL(clicked()), SLOT(SavePreset())); + connect(ui_->preset_del, SIGNAL(clicked()), SLOT(DelPreset())); + connect(ui_->balance_slider, SIGNAL(valueChanged(int)), SLOT(StereoSliderChanged(int))); + + QShortcut* close = new QShortcut(QKeySequence::Close, this); + connect(close, SIGNAL(activated()), SLOT(close())); + +} + +Equalizer::~Equalizer() { + delete ui_; +} + +void Equalizer::ReloadSettings() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QSettings s; + s.beginGroup(kSettingsGroup); + + presets_.clear(); + ui_->preset->clear(); + + // Load presets + int count = s.beginReadArray("presets"); + for (int i = 0; i < count; ++i) { + s.setArrayIndex(i); + AddPreset(s.value("name").toString(), s.value("params").value()); + } + s.endArray(); + + if (count == 0) LoadDefaultPresets(); + + // Selected preset + QString selected_preset = s.value("selected_preset", "Custom").toString(); + QString selected_preset_display_name = QString(tr(qPrintable(selected_preset))); + int selected_index = ui_->preset->findText(selected_preset_display_name); + if (selected_index != -1) ui_->preset->setCurrentIndex(selected_index); + + // Enabled? + ui_->enable->setChecked(s.value("enabled", false).toBool()); + ui_->slider_container->setEnabled(ui_->enable->isChecked()); + + int stereo_balance = s.value("stereo_balance", 0).toInt(); + ui_->balance_slider->setValue(stereo_balance); + StereoSliderChanged(stereo_balance); + + PresetChanged(selected_preset); + +} + +void Equalizer::LoadDefaultPresets() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Custom"), Params(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Classical"), Params(0, 0, 0, 0, 0, 0, -40, -40, -40, -50)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Club"), Params(0, 0, 20, 30, 30, 30, 20, 0, 0, 0)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Dance"), Params(50, 35, 10, 0, 0, -30, -40, -40, 0, 0)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Full Bass"), Params(70, 70, 70, 40, 20, -45, -50, -55, -55, -55)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Full Treble"), Params(-50, -50, -50, -25, 15, 55, 80, 80, 80, 85)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Full Bass + Treble"), Params(35, 30, 0, -40, -25, 10, 45, 55, 60, 60)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Laptop/Headphones"), Params(25, 50, 25, -20, 0, -30, -40, -40, 0, 0)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Large Hall"), Params(50, 50, 30, 30, 0, -25, -25, -25, 0, 0)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Live"), Params(-25, 0, 20, 25, 30, 30, 20, 15, 15, 10)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Party"), Params(35, 35, 0, 0, 0, 0, 0, 0, 35, 35)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Pop"), Params(-10, 25, 35, 40, 25, -5, -15, -15, -10, -10)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Reggae"), Params(0, 0, -5, -30, 0, -35, -35, 0, 0, 0)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Rock"), Params(40, 25, -30, -40, -20, 20, 45, 55, 55, 55)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Soft"), Params(25, 10, -5, -15, -5, 20, 45, 50, 55, 60)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Ska"), Params(-15, -25, -25, -5, 20, 30, 45, 50, 55, 50)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Soft Rock"), Params(20, 20, 10, -5, -25, -30, -20, -5, 15, 45)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Techno"), Params(40, 30, 0, -30, -25, 0, 40, 50, 50, 45)); + AddPreset(QT_TRANSLATE_NOOP("Equalizer", "Zero"), Params(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + +} + +void Equalizer::AddPreset(const QString& name, const Params& params) { + + QString name_displayed = tr(qPrintable(name)); + presets_[name] = params; + + if (ui_->preset->findText(name_displayed) == -1) { + ui_->preset->addItem(name_displayed, // name to display (translated) + QVariant(name) // original name + ); + } +} + +void Equalizer::PresetChanged(int index) { + + PresetChanged(ui_->preset->itemData(index).toString()); +} + +void Equalizer::PresetChanged(const QString& name) { + + if (presets_.contains(last_preset_)) { + if (presets_[last_preset_] != current_params()) { + SaveCurrentPreset(); + } + } + last_preset_ = name; + + Params& p = presets_[name]; + + loading_ = true; + preamp_->set_value(p.preamp); + for (int i = 0; i < kBands; ++i) gain_[i]->set_value(p.gain[i]); + loading_ = false; + + ParametersChanged(); + Save(); + +} + +void Equalizer::SavePreset() { + + QString name = SaveCurrentPreset(); + if (!name.isEmpty()) { + last_preset_ = name; + ui_->preset->setCurrentIndex(ui_->preset->findText(tr(qPrintable(name)))); + } + +} + +QString Equalizer::SaveCurrentPreset() { + + QString name = QInputDialog::getText(this, tr("Save preset"), tr("Name"), QLineEdit::Normal, tr(qPrintable(last_preset_)));; + if (name.isEmpty()) + return QString(); + + AddPreset(name, current_params()); + Save(); + return name; + +} + +void Equalizer::DelPreset() { + + QString name = ui_->preset->itemData(ui_->preset->currentIndex()).toString(); + QString name_displayed = ui_->preset->currentText(); + if (!presets_.contains(name) || name.isEmpty()) return; + + int ret = QMessageBox::question( + this, tr("Delete preset"), + tr("Are you sure you want to delete the \"%1\" preset?").arg(name_displayed), + QMessageBox::Yes, QMessageBox::No); + + if (ret == QMessageBox::No) return; + + presets_.remove(name); + ui_->preset->removeItem(ui_->preset->currentIndex()); + Save(); + +} + +EqualizerSlider* Equalizer::AddSlider(const QString &label) { + + EqualizerSlider* ret = new EqualizerSlider(label, ui_->slider_container); + ui_->slider_container->layout()->addWidget(ret); + connect(ret, SIGNAL(ValueChanged(int)), SLOT(ParametersChanged())); + + return ret; + +} + +bool Equalizer::is_enabled() const { + return ui_->enable->isChecked(); +} + +int Equalizer::preamp_value() const { + return preamp_->value(); +} + +QList Equalizer::gain_values() const { + QList ret; + for (int i = 0; i < kBands; ++i) { + ret << gain_[i]->value(); + } + return ret; +} + +Equalizer::Params Equalizer::current_params() const { + + QList gains = gain_values(); + + Params ret; + ret.preamp = preamp_value(); + std::copy(gains.begin(), gains.end(), ret.gain); + return ret; + +} + +float Equalizer::stereo_balance() const { + return qBound(-1.0f, ui_->balance_slider->value() / 100.0f, 1.0f); +} + +void Equalizer::ParametersChanged() { + if (loading_) return; + + emit ParametersChanged(preamp_value(), gain_values()); +} + +void Equalizer::Save() { + + QSettings s; + s.beginGroup(kSettingsGroup); + + // Presets + s.beginWriteArray("presets", presets_.count()); + int i = 0; + for (const QString& name : presets_.keys()) { + s.setArrayIndex(i++); + s.setValue("name", name); + s.setValue("params", QVariant::fromValue(presets_[name])); + } + s.endArray(); + + // Selected preset + s.setValue("selected_preset", ui_->preset->itemData(ui_->preset->currentIndex()).toString()); + + // Enabled? + s.setValue("enabled", ui_->enable->isChecked()); + + s.setValue("stereo_balance", ui_->balance_slider->value()); + +} + +void Equalizer::closeEvent(QCloseEvent* e) { + QString name = ui_->preset->currentText(); + if (!presets_.contains(name)) return; + + if (presets_[name] == current_params()) return; + + SavePreset(); +} + +Equalizer::Params::Params() : preamp(0) { + for (int i = 0; i < Equalizer::kBands; ++i) gain[i] = 0; +} + +Equalizer::Params::Params(int g0, int g1, int g2, int g3, int g4, int g5, int g6, int g7, int g8, int g9, int pre) + : preamp(pre) { + gain[0] = g0; + gain[1] = g1; + gain[2] = g2; + gain[3] = g3; + gain[4] = g4; + gain[5] = g5; + gain[6] = g6; + gain[7] = g7; + gain[8] = g8; + gain[9] = g9; +} + +bool Equalizer::Params::operator ==(const Equalizer::Params& other) const { + if (preamp != other.preamp) return false; + for (int i = 0; i < Equalizer::kBands; ++i) { + if (gain[i] != other.gain[i]) return false; + } + return true; +} + +bool Equalizer::Params::operator !=(const Equalizer::Params& other) const { + return ! (*this == other); +} + +void Equalizer::StereoSliderChanged(int value) { + emit StereoBalanceChanged(stereo_balance()); + Save(); +} + +QDataStream &operator<<(QDataStream& s, const Equalizer::Params& p) { + s << p.preamp; + for (int i = 0; i < Equalizer::kBands; ++i) s << p.gain[i]; + return s; +} + +QDataStream &operator>>(QDataStream& s, Equalizer::Params& p) { + s >> p.preamp; + for (int i = 0; i < Equalizer::kBands; ++i) s >> p.gain[i]; + return s; +} diff --git a/src/equalizer/equalizer.h b/src/equalizer/equalizer.h new file mode 100644 index 00000000..4f859e62 --- /dev/null +++ b/src/equalizer/equalizer.h @@ -0,0 +1,101 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef EQUALIZER_H +#define EQUALIZER_H + +#include "config.h" + +#include +#include +#include + +class EqualizerSlider; +class Ui_Equalizer; + +class Equalizer : public QDialog { + Q_OBJECT + + public: + Equalizer(QWidget* parent = nullptr); + ~Equalizer(); + + static const int kBands = 10; + static const char *kGainText[kBands]; + static const char *kSettingsGroup; + + struct Params { + Params(); + Params(int g0, int g1, int g2, int g3, int g4, int g5, int g6, int g7, int g8, int g9, int pre = 0); + + bool operator ==(const Params &other) const; + bool operator !=(const Params &other) const; + + int preamp; + int gain[kBands]; + }; + + bool is_enabled() const; + int preamp_value() const; + QList gain_values() const; + Params current_params() const; + float stereo_balance() const; + + signals: + void EnabledChanged(bool enabled); + void ParametersChanged(int preamp, const QList &band_gains); + void StereoBalanceChanged(float balance); + + protected: + void closeEvent(QCloseEvent *); + + private slots: + void ParametersChanged(); + void PresetChanged(const QString &name); + void PresetChanged(int index); + void SavePreset(); + void DelPreset(); + void Save(); + void StereoSliderChanged(int value); + + private: + EqualizerSlider *AddSlider(const QString &label); + void LoadDefaultPresets(); + void AddPreset(const QString &name, const Params ¶ms); + void ReloadSettings(); + QString SaveCurrentPreset(); + + private: + Ui_Equalizer *ui_; + bool loading_; + + QString last_preset_; + + EqualizerSlider *preamp_; + EqualizerSlider *gain_[kBands]; + + QMap presets_; +}; +Q_DECLARE_METATYPE(Equalizer::Params); + +QDataStream &operator<<(QDataStream &s, const Equalizer::Params &p); +QDataStream &operator>>(QDataStream &s, Equalizer::Params &p); + +#endif // EQUALIZER_H diff --git a/src/equalizer/equalizer.ui b/src/equalizer/equalizer.ui new file mode 100644 index 00000000..a9de1a59 --- /dev/null +++ b/src/equalizer/equalizer.ui @@ -0,0 +1,163 @@ + + + Equalizer + + + + 0 + 0 + 435 + 265 + + + + Equalizer + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + + + + + + Preset: + + + + + + + + 0 + 0 + + + + + + + + Save preset + + + + + + + Delete preset + + + + + + + + + Qt::Horizontal + + + + + + + Enable equalizer + + + + + + + false + + + + 0 + 0 + + + + + + + + + + + Left + + + Qt::AlignLeft | Qt::AlignBottom + + + + 11 + + + + + + + + Balance + + + Qt::AlignCenter + + + + + + + Right + + + Qt::AlignRight | Qt::AlignBottom + + + + 11 + + + + + + + + + + 100 + + + -100 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 10 + + + 10 + + + 100 + + + + + + + preset + preset_save + + + + + + diff --git a/src/equalizer/equalizerslider.cpp b/src/equalizer/equalizerslider.cpp new file mode 100644 index 00000000..6be103fb --- /dev/null +++ b/src/equalizer/equalizerslider.cpp @@ -0,0 +1,47 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "equalizerslider.h" +#include "ui_equalizerslider.h" + +EqualizerSlider::EqualizerSlider(const QString &label, QWidget *parent) + : QWidget(parent), + ui_(new Ui_EqualizerSlider) +{ + ui_->setupUi(this); + ui_->label->setText(label); + + connect(ui_->slider, SIGNAL(valueChanged(int)), SIGNAL(ValueChanged(int))); +} + +EqualizerSlider::~EqualizerSlider() { + delete ui_; +} + +int EqualizerSlider::value() const { + return ui_->slider->value(); +} + +void EqualizerSlider::set_value(int value) { + ui_->slider->setValue(value); +} + diff --git a/src/equalizer/equalizerslider.h b/src/equalizer/equalizerslider.h new file mode 100644 index 00000000..4aad3c84 --- /dev/null +++ b/src/equalizer/equalizerslider.h @@ -0,0 +1,50 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef EQUALISERSLIDER_H +#define EQUALISERSLIDER_H + +#include "config.h" + +#include + +class Ui_EqualizerSlider; + +// Contains the slider and the label +class EqualizerSlider : public QWidget { + + Q_OBJECT + + public: + EqualizerSlider(const QString &label, QWidget *parent = nullptr); + ~EqualizerSlider(); + + int value() const; + void set_value(int value); + + signals: + void ValueChanged(int value); + + private: + Ui_EqualizerSlider *ui_; + +}; + +#endif // EQUALISERSLIDER_H diff --git a/src/equalizer/equalizerslider.ui b/src/equalizer/equalizerslider.ui new file mode 100644 index 00000000..8390fe7e --- /dev/null +++ b/src/equalizer/equalizerslider.ui @@ -0,0 +1,93 @@ + + + EqualizerSlider + + + + 0 + 0 + 33 + 224 + + + + + 0 + 100 + + + + Form + + + + 0 + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + -100 + + + 100 + + + Qt::Vertical + + + 0 + + + 10 + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + Qt::AlignCenter + + + + + + + + StickySlider + QSlider +

widgets/stickyslider.h
+ + + + + diff --git a/src/globalshortcuts/globalshortcutbackend.cpp b/src/globalshortcuts/globalshortcutbackend.cpp new file mode 100644 index 00000000..5f1081c4 --- /dev/null +++ b/src/globalshortcuts/globalshortcutbackend.cpp @@ -0,0 +1,40 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "globalshortcutbackend.h" + +#include "globalshortcuts.h" + +GlobalShortcutBackend::GlobalShortcutBackend(GlobalShortcuts *parent) + : QObject(parent), manager_(parent), active_(false) {} + +bool GlobalShortcutBackend::Register() { + bool ret = DoRegister(); + if (ret) active_ = true; + return ret; +} + +void GlobalShortcutBackend::Unregister() { + DoUnregister(); + active_ = false; +} + diff --git a/src/globalshortcuts/globalshortcutbackend.h b/src/globalshortcuts/globalshortcutbackend.h new file mode 100644 index 00000000..4f6ba3f7 --- /dev/null +++ b/src/globalshortcuts/globalshortcutbackend.h @@ -0,0 +1,54 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef GLOBALSHORTCUTBACKEND_H +#define GLOBALSHORTCUTBACKEND_H + +#include "config.h" + +#include + +class GlobalShortcuts; + +class GlobalShortcutBackend : public QObject { + Q_OBJECT + + public: + explicit GlobalShortcutBackend(GlobalShortcuts *parent = nullptr); + virtual ~GlobalShortcutBackend() {} + + bool is_active() const { return active_; } + + bool Register(); + void Unregister(); + +signals: + void RegisterFinished(bool success); + + protected: + virtual bool DoRegister() = 0; + virtual void DoUnregister() = 0; + + GlobalShortcuts* manager_; + bool active_; +}; + +#endif // GLOBALSHORTCUTBACKEND_H + diff --git a/src/globalshortcuts/globalshortcutgrabber.cpp b/src/globalshortcuts/globalshortcutgrabber.cpp new file mode 100644 index 00000000..acd6cf4f --- /dev/null +++ b/src/globalshortcuts/globalshortcutgrabber.cpp @@ -0,0 +1,99 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "globalshortcutgrabber.h" +#include "ui_globalshortcutgrabber.h" + +#include +#include + +GlobalShortcutGrabber::GlobalShortcutGrabber(QWidget *parent) + : QDialog(parent), ui_(new Ui::GlobalShortcutGrabber) { + ui_->setupUi(this); + + modifier_keys_ << Qt::Key_Shift << Qt::Key_Control << Qt::Key_Meta << Qt::Key_Alt << Qt::Key_AltGr; +} + +GlobalShortcutGrabber::~GlobalShortcutGrabber() { + delete ui_; +} + +QKeySequence GlobalShortcutGrabber::GetKey(const QString &name) { + + ui_->label->setText(tr("Press a key combination to use for %1...").arg(name)); + ui_->combo->clear(); + + ret_ = QKeySequence(); + + if (exec() == QDialog::Rejected) return QKeySequence(); + return ret_; + +} + +void GlobalShortcutGrabber::showEvent(QShowEvent *e) { + grabKeyboard(); + QDialog::showEvent(e); +} + +void GlobalShortcutGrabber::hideEvent(QHideEvent *e) { + releaseKeyboard(); + QDialog::hideEvent(e); +} + +void GlobalShortcutGrabber::grabKeyboard() { +#ifdef Q_OS_DARWIN + SetupMacEventHandler(); +#endif + QDialog::grabKeyboard(); +} + +void GlobalShortcutGrabber::releaseKeyboard() { +#ifdef Q_OS_DARWIN + TeardownMacEventHandler(); +#endif + QDialog::releaseKeyboard(); +} + +bool GlobalShortcutGrabber::event(QEvent *e) { + + if (e->type() == QEvent::ShortcutOverride) { + QKeyEvent *ke = static_cast(e); + + if (modifier_keys_.contains(ke->key())) + ret_ = QKeySequence(ke->modifiers()); + else + ret_ = QKeySequence(ke->modifiers() | ke->key()); + + UpdateText(); + + if (!modifier_keys_.contains(ke->key())) accept(); + return true; + } + return QDialog::event(e); + +} + +void GlobalShortcutGrabber::UpdateText() { + ui_->combo->setText("" + ret_.toString(QKeySequence::NativeText) + ""); +} + + diff --git a/src/globalshortcuts/globalshortcutgrabber.h b/src/globalshortcuts/globalshortcutgrabber.h new file mode 100644 index 00000000..b4262aca --- /dev/null +++ b/src/globalshortcuts/globalshortcutgrabber.h @@ -0,0 +1,67 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef GLOBALSHORTCUTGRABBER_H +#define GLOBALSHORTCUTGRABBER_H + +#include "config.h" + +#include + +class MacMonitorWrapper; +class Ui_GlobalShortcutGrabber; + +#ifdef __OBJC__ +@class NSEvent; +#else +class NSEvent; +#endif + +class GlobalShortcutGrabber : public QDialog { + Q_OBJECT + + public: + GlobalShortcutGrabber(QWidget *parent = nullptr); + ~GlobalShortcutGrabber(); + + QKeySequence GetKey(const QString& name); + + protected: + bool event(QEvent *); + void showEvent(QShowEvent *); + void hideEvent(QHideEvent *); + void grabKeyboard(); + void releaseKeyboard(); + + private: + void UpdateText(); + void SetupMacEventHandler(); + void TeardownMacEventHandler(); + bool HandleMacEvent(NSEvent*); + + Ui_GlobalShortcutGrabber *ui_; + QKeySequence ret_; + + QList modifier_keys_; + + MacMonitorWrapper *wrapper_; +}; + +#endif // GLOBALSHORTCUTGRABBER_H diff --git a/src/globalshortcuts/globalshortcutgrabber.mm b/src/globalshortcuts/globalshortcutgrabber.mm new file mode 100644 index 00000000..d60c2a56 --- /dev/null +++ b/src/globalshortcuts/globalshortcutgrabber.mm @@ -0,0 +1,62 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2011, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + + #include "config.h" + +#include "globalshortcutgrabber.h" + +#import +#import +#import +#import + +#include + +#import "core/mac_utilities.h" + +class MacMonitorWrapper { + public: + explicit MacMonitorWrapper(id monitor) : local_monitor_(monitor) {} + + ~MacMonitorWrapper() { [NSEvent removeMonitor:local_monitor_]; } + + private: + id local_monitor_; + Q_DISABLE_COPY(MacMonitorWrapper); +}; + +bool GlobalShortcutGrabber::HandleMacEvent(NSEvent* event) { + ret_ = mac::KeySequenceFromNSEvent(event); + UpdateText(); + if ([[event charactersIgnoringModifiers] length] != 0) { + accept(); + return true; + } + return ret_ == QKeySequence(Qt::Key_Escape); +} + +void GlobalShortcutGrabber::SetupMacEventHandler() { + id monitor = [NSEvent addLocalMonitorForEventsMatchingMask: NSKeyDownMask handler:^(NSEvent* event) { + return HandleMacEvent(event) ? event : nil; + }]; + wrapper_ = new MacMonitorWrapper(monitor); +} + +void GlobalShortcutGrabber::TeardownMacEventHandler() { delete wrapper_; } diff --git a/src/globalshortcuts/globalshortcutgrabber.ui b/src/globalshortcuts/globalshortcutgrabber.ui new file mode 100644 index 00000000..adc63413 --- /dev/null +++ b/src/globalshortcuts/globalshortcutgrabber.ui @@ -0,0 +1,90 @@ + + + GlobalShortcutGrabber + + + + 0 + 0 + 418 + 110 + + + + Press a key + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + + + + Press a key combination to use for %1... + + + Qt::AlignCenter + + + + + + + Qt::RichText + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel + + + + + + + + + + + buttonBox + accepted() + GlobalShortcutGrabber + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + GlobalShortcutGrabber + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/globalshortcuts/globalshortcuts.cpp b/src/globalshortcuts/globalshortcuts.cpp new file mode 100644 index 00000000..3562c03e --- /dev/null +++ b/src/globalshortcuts/globalshortcuts.cpp @@ -0,0 +1,151 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "globalshortcuts.h" +#include "gnomeglobalshortcutbackend.h" +#include "macglobalshortcutbackend.h" +#include "qxtglobalshortcutbackend.h" +#include "settings/shortcutssettingspage.h" + +#include "core/mac_startup.h" + +#include +#include +#include +#include + +#ifdef QT_DBUS_LIB +#include +#endif + +GlobalShortcuts::GlobalShortcuts(QWidget* parent) + : QWidget(parent), + gnome_backend_(nullptr), + system_backend_(nullptr), + use_gnome_(false) { + + settings_.beginGroup(GlobalShortcutsSettingsPage::kSettingsGroup); + + // Create actions + AddShortcut("play", tr("Play"), SIGNAL(Play())); + AddShortcut("pause", tr("Pause"), SIGNAL(Pause())); + AddShortcut("play_pause", tr("Play/Pause"), SIGNAL(PlayPause()), QKeySequence(Qt::Key_MediaPlay)); + AddShortcut("stop", tr("Stop"), SIGNAL(Stop()), QKeySequence(Qt::Key_MediaStop)); + AddShortcut("stop_after", tr("Stop playing after current track"), SIGNAL(StopAfter())); + AddShortcut("next_track", tr("Next track"), SIGNAL(Next()), QKeySequence(Qt::Key_MediaNext)); + AddShortcut("prev_track", tr("Previous track"), SIGNAL(Previous()), QKeySequence(Qt::Key_MediaPrevious)); + AddShortcut("inc_volume", tr("Increase volume"), SIGNAL(IncVolume())); + AddShortcut("dec_volume", tr("Decrease volume"), SIGNAL(DecVolume())); + AddShortcut("mute", tr("Mute"), SIGNAL(Mute())); + AddShortcut("seek_forward", tr("Seek forward"), SIGNAL(SeekForward())); + AddShortcut("seek_backward", tr("Seek backward"), SIGNAL(SeekBackward())); + AddShortcut("show_hide", tr("Show/Hide"), SIGNAL(ShowHide())); + AddShortcut("show_osd", tr("Show OSD"), SIGNAL(ShowOSD())); + AddShortcut("toggle_pretty_osd", tr("Toggle Pretty OSD"), SIGNAL(TogglePrettyOSD())); // Toggling possible only for pretty OSD + AddShortcut("shuffle_mode", tr("Change shuffle mode"), SIGNAL(CycleShuffleMode())); + AddShortcut("repeat_mode", tr("Change repeat mode"), SIGNAL(CycleRepeatMode())); + + // Create backends - these do the actual shortcut registration + gnome_backend_ = new GnomeGlobalShortcutBackend(this); + +#ifndef Q_OS_DARWIN + system_backend_ = new QxtGlobalShortcutBackend(this); +#else + system_backend_ = new MacGlobalShortcutBackend(this); +#endif + + ReloadSettings(); + +} + +void GlobalShortcuts::AddShortcut(const QString& id, const QString& name, const char* signal, const QKeySequence& default_key) { + + Shortcut shortcut = AddShortcut(id, name, default_key); + connect(shortcut.action, SIGNAL(triggered()), this, signal); +} + +GlobalShortcuts::Shortcut GlobalShortcuts::AddShortcut(const QString& id, const QString& name, const QKeySequence& default_key) { + + Shortcut shortcut; + shortcut.action = new QAction(name, this); + QKeySequence key_sequence = QKeySequence::fromString(settings_.value(id, default_key.toString()).toString()); + shortcut.action->setShortcut(key_sequence); + shortcut.id = id; + shortcut.default_key = default_key; + + // Create application wide QShortcut to hide keyevents mapped to global + // shortcuts from widgets. + shortcut.shortcut = new QShortcut(key_sequence, this); + shortcut.shortcut->setContext(Qt::ApplicationShortcut); + + shortcuts_[id] = shortcut; + + return shortcut; + +} + +bool GlobalShortcuts::IsGsdAvailable() const { + +#ifdef QT_DBUS_LIB + return QDBusConnection::sessionBus().interface()->isServiceRegistered( + GnomeGlobalShortcutBackend::kGsdService); +#else // QT_DBUS_LIB + return false; +#endif + +} + +void GlobalShortcuts::ReloadSettings() { + + // The actual shortcuts have been set in our actions for us by the config + // dialog already - we just need to reread the gnome settings. + use_gnome_ = settings_.value("use_gnome", true).toBool(); + + Unregister(); + Register(); + +} + +void GlobalShortcuts::Unregister() { + if (gnome_backend_->is_active()) gnome_backend_->Unregister(); + if (system_backend_->is_active()) system_backend_->Unregister(); +} + +void GlobalShortcuts::Register() { + if (use_gnome_ && gnome_backend_->Register()) return; + system_backend_->Register(); +} + +bool GlobalShortcuts::IsMacAccessibilityEnabled() const { +#ifdef Q_OS_MAC + return static_cast(system_backend_)->IsAccessibilityEnabled(); +#else + return true; +#endif +} + +void GlobalShortcuts::ShowMacAccessibilityDialog() { +#ifdef Q_OS_MAC + static_cast(system_backend_)->ShowAccessibilityDialog(); +#endif +} + diff --git a/src/globalshortcuts/globalshortcuts.h b/src/globalshortcuts/globalshortcuts.h new file mode 100644 index 00000000..cf72915b --- /dev/null +++ b/src/globalshortcuts/globalshortcuts.h @@ -0,0 +1,97 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef GLOBALSHORTCUTS_H +#define GLOBALSHORTCUTS_H + +#include "config.h" + +#include +#include +#include +#include + +class QAction; +class QShortcut; + +class GlobalShortcutBackend; +class QSignalMapper; + +class GlobalShortcuts : public QWidget { + Q_OBJECT + + public: + explicit GlobalShortcuts(QWidget* parent = nullptr); + + struct Shortcut { + QString id; + QKeySequence default_key; + QAction *action; + QShortcut *shortcut; + }; + + QMap shortcuts() const { return shortcuts_; } + bool IsGsdAvailable() const; + bool IsMacAccessibilityEnabled() const; + + public slots: + void ReloadSettings(); + void ShowMacAccessibilityDialog(); + + void Unregister(); + void Register(); + +signals: + void Play(); + void Pause(); + void PlayPause(); + void Stop(); + void StopAfter(); + void Next(); + void Previous(); + void IncVolume(); + void DecVolume(); + void Mute(); + void SeekForward(); + void SeekBackward(); + void ShowHide(); + void ShowOSD(); + void TogglePrettyOSD(); + void CycleShuffleMode(); + void CycleRepeatMode(); + void RemoveCurrentSong(); + + private: + void AddShortcut(const QString &id, const QString &name, const char *signal, const QKeySequence &default_key = QKeySequence(0)); + Shortcut AddShortcut(const QString &id, const QString &name, const QKeySequence &default_key); + + private: + GlobalShortcutBackend *gnome_backend_; + GlobalShortcutBackend *system_backend_; + + QMap shortcuts_; + QSettings settings_; + + bool use_gnome_; + QSignalMapper *rating_signals_mapper_; +}; + +#endif + diff --git a/src/globalshortcuts/gnomeglobalshortcutbackend.cpp b/src/globalshortcuts/gnomeglobalshortcutbackend.cpp new file mode 100644 index 00000000..c3ec5d8c --- /dev/null +++ b/src/globalshortcuts/gnomeglobalshortcutbackend.cpp @@ -0,0 +1,113 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "gnomeglobalshortcutbackend.h" +#include "globalshortcuts.h" +#include "core/closure.h" +#include "core/logging.h" + +#ifdef QT_DBUS_LIB +#include "dbus/gnomesettingsdaemon.h" +#endif + +#include +#include +#include +#include + +#ifdef QT_DBUS_LIB +#include +#endif + +const char *GnomeGlobalShortcutBackend::kGsdService = "org.gnome.SettingsDaemon"; +const char *GnomeGlobalShortcutBackend::kGsdPath = "/org/gnome/SettingsDaemon/MediaKeys"; +const char *GnomeGlobalShortcutBackend::kGsdInterface = "org.gnome.SettingsDaemon.MediaKeys"; + +GnomeGlobalShortcutBackend::GnomeGlobalShortcutBackend(GlobalShortcuts *parent) + : GlobalShortcutBackend(parent), + interface_(nullptr), + is_connected_(false) {} + +bool GnomeGlobalShortcutBackend::DoRegister() { +#ifdef QT_DBUS_LIB + qLog(Debug) << "registering"; + // Check if the GSD service is available + if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(kGsdService)) { + qLog(Warning) << "gnome settings daemon not registered"; + return false; + } + + if (!interface_) { + interface_ = new OrgGnomeSettingsDaemonMediaKeysInterface(kGsdService, kGsdPath, QDBusConnection::sessionBus(), this); + } + + QDBusPendingReply<> reply = interface_->GrabMediaPlayerKeys(QCoreApplication::applicationName(), QDateTime::currentDateTime().toTime_t()); + + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + NewClosure(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(RegisterFinished(QDBusPendingCallWatcher*)), watcher); + + return true; +#else // QT_DBUS_LIB + qLog(Warning) << "dbus not available"; + return false; +#endif +} + +void GnomeGlobalShortcutBackend::RegisterFinished(QDBusPendingCallWatcher *watcher) { +#ifdef QT_DBUS_LIB + QDBusMessage reply = watcher->reply(); + watcher->deleteLater(); + + if (reply.type() == QDBusMessage::ErrorMessage) { + qLog(Warning) << "Failed to grab media keys" << reply.errorName() <isServiceRegistered(kGsdService)) + return; + if (!interface_ || !is_connected_) return; + + is_connected_ = false; + + interface_->ReleaseMediaPlayerKeys(QCoreApplication::applicationName()); + disconnect(interface_, SIGNAL(MediaPlayerKeyPressed(QString,QString)), this, SLOT(GnomeMediaKeyPressed(QString,QString))); +#endif +} + +void GnomeGlobalShortcutBackend::GnomeMediaKeyPressed(const QString&, const QString& key) { + if (key == "Play") manager_->shortcuts()["play_pause"].action->trigger(); + if (key == "Stop") manager_->shortcuts()["stop"].action->trigger(); + if (key == "Next") manager_->shortcuts()["next_track"].action->trigger(); + if (key == "Previous") manager_->shortcuts()["prev_track"].action->trigger(); +} + diff --git a/src/globalshortcuts/gnomeglobalshortcutbackend.h b/src/globalshortcuts/gnomeglobalshortcutbackend.h new file mode 100644 index 00000000..45094efd --- /dev/null +++ b/src/globalshortcuts/gnomeglobalshortcutbackend.h @@ -0,0 +1,58 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef GNOMEGLOBALSHORTCUTBACKEND_H +#define GNOMEGLOBALSHORTCUTBACKEND_H + +#include "config.h" + +#include "globalshortcutbackend.h" + +class OrgGnomeSettingsDaemonMediaKeysInterface; + +class QDBusPendingCallWatcher; + +class GnomeGlobalShortcutBackend : public GlobalShortcutBackend { + Q_OBJECT + +public: + explicit GnomeGlobalShortcutBackend(GlobalShortcuts *parent); + + static const char *kGsdService; + static const char *kGsdPath; + static const char *kGsdInterface; + +protected: + bool RegisterInNewThread() const { return true; } + bool DoRegister(); + void DoUnregister(); + +private slots: + void RegisterFinished(QDBusPendingCallWatcher *watcher); + + void GnomeMediaKeyPressed(const QString& application, const QString& key); + +private: + OrgGnomeSettingsDaemonMediaKeysInterface *interface_; + bool is_connected_; +}; + +#endif // GNOMEGLOBALSHORTCUTBACKEND_H + diff --git a/src/globalshortcuts/macglobalshortcutbackend.h b/src/globalshortcuts/macglobalshortcutbackend.h new file mode 100644 index 00000000..c979f145 --- /dev/null +++ b/src/globalshortcuts/macglobalshortcutbackend.h @@ -0,0 +1,62 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MACGLOBALSHORTCUTBACKEND_H +#define MACGLOBALSHORTCUTBACKEND_H + +#include "config.h" + +#include + +#include "globalshortcutbackend.h" + +#include +#include + +class MacGlobalShortcutBackendPrivate; +class QAction; + +class MacGlobalShortcutBackend : public GlobalShortcutBackend { + Q_OBJECT + + public: + explicit MacGlobalShortcutBackend(GlobalShortcuts* parent); + virtual ~MacGlobalShortcutBackend(); + + bool IsAccessibilityEnabled() const; + void ShowAccessibilityDialog(); + + void MacMediaKeyPressed(int key); + + protected: + bool DoRegister(); + void DoUnregister(); + + private: + bool KeyPressed(const QKeySequence &sequence); + + QMap shortcuts_; + + friend class MacGlobalShortcutBackendPrivate; + std::unique_ptr p_; +}; + +#endif // MACGLOBALSHORTCUTBACKEND_H + diff --git a/src/globalshortcuts/macglobalshortcutbackend.mm b/src/globalshortcuts/macglobalshortcutbackend.mm new file mode 100644 index 00000000..b75abd07 --- /dev/null +++ b/src/globalshortcuts/macglobalshortcutbackend.mm @@ -0,0 +1,163 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + + #include "config.h" + +#include "macglobalshortcutbackend.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "config.h" +#include "core/globalshortcuts.h" +#include "core/logging.h" +#include "core/mac_startup.h" +#include "core/utilities.h" + +#import "core/mac_utilities.h" +#import "mac/SBSystemPreferences.h" + +class MacGlobalShortcutBackendPrivate : boost::noncopyable { + public: + explicit MacGlobalShortcutBackendPrivate(MacGlobalShortcutBackend* backend) + : global_monitor_(nil), local_monitor_(nil), backend_(backend) {} + + bool Register() { + global_monitor_ = [NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask + handler:^(NSEvent* event) { + HandleKeyEvent(event); + }]; + local_monitor_ = [NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask + handler:^(NSEvent* event) { + // Filter event if we handle it as a global shortcut. + return HandleKeyEvent(event) ? nil : event; + }]; + return true; + } + + void Unregister() { + [NSEvent removeMonitor:global_monitor_]; + [NSEvent removeMonitor:local_monitor_]; + } + + private: + bool HandleKeyEvent(NSEvent* event) { + QKeySequence sequence = mac::KeySequenceFromNSEvent(event); + return backend_->KeyPressed(sequence); + } + + id global_monitor_; + id local_monitor_; + MacGlobalShortcutBackend* backend_; +}; + +MacGlobalShortcutBackend::MacGlobalShortcutBackend(GlobalShortcuts* parent) + : GlobalShortcutBackend(parent), + p_(new MacGlobalShortcutBackendPrivate(this)) {} + +MacGlobalShortcutBackend::~MacGlobalShortcutBackend() {} + +bool MacGlobalShortcutBackend::DoRegister() { + // Always enable media keys. + mac::SetShortcutHandler(this); + + for (const GlobalShortcuts::Shortcut& shortcut : + manager_->shortcuts().values()) { + shortcuts_[shortcut.action->shortcut()] = shortcut.action; + } + } + + return p_->Register(); + +} + +void MacGlobalShortcutBackend::DoUnregister() { + p_->Unregister(); + shortcuts_.clear(); +} + +void MacGlobalShortcutBackend::MacMediaKeyPressed(int key) { + switch (key) { + case NX_KEYTYPE_PLAY: + KeyPressed(Qt::Key_MediaPlay); + break; + case NX_KEYTYPE_FAST: + KeyPressed(Qt::Key_MediaNext); + break; + case NX_KEYTYPE_REWIND: + KeyPressed(Qt::Key_MediaPrevious); + break; + } +} + +bool MacGlobalShortcutBackend::KeyPressed(const QKeySequence& sequence) { + if (sequence.isEmpty()) { + return false; + } + QAction* action = shortcuts_[sequence]; + if (action) { + action->trigger(); + return true; + } + return false; +} + +bool MacGlobalShortcutBackend::IsAccessibilityEnabled() const { + return AXAPIEnabled(); +} + +void MacGlobalShortcutBackend::ShowAccessibilityDialog() { + NSArray* paths = NSSearchPathForDirectoriesInDomains( + NSPreferencePanesDirectory, NSSystemDomainMask, YES); + if ([paths count] == 1) { + SBSystemPreferencesApplication* system_prefs = [SBApplication + applicationWithBundleIdentifier:@"com.apple.systempreferences"]; + [system_prefs activate]; + + SBElementArray* panes = [system_prefs panes]; + SBSystemPreferencesPane* security_pane = nil; + for (SBSystemPreferencesPane* pane : panes) { + if ([[pane id] isEqualToString:@"com.apple.preference.security"]) { + security_pane = pane; + break; + } + } + } + [system_prefs setCurrentPane:security_pane]; + + SBElementArray* anchors = [security_pane anchors]; + for (SBSystemPreferencesAnchor* anchor : anchors) { + if ([[anchor name] isEqualToString:@"Privacy_Accessibility"]) { + [anchor reveal]; + } + } + } + } +} diff --git a/src/globalshortcuts/qxtglobalshortcutbackend.cpp b/src/globalshortcuts/qxtglobalshortcutbackend.cpp new file mode 100644 index 00000000..382a9a45 --- /dev/null +++ b/src/globalshortcuts/qxtglobalshortcutbackend.cpp @@ -0,0 +1,61 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "globalshortcuts.h" +#include "qxtglobalshortcutbackend.h" +#include "qxtglobalshortcut.h" +#include "core/logging.h" + +#include +#include + +QxtGlobalShortcutBackend::QxtGlobalShortcutBackend(GlobalShortcuts* parent) : GlobalShortcutBackend(parent) {} + +bool QxtGlobalShortcutBackend::DoRegister() { + + qLog(Debug) << "registering"; + for (const GlobalShortcuts::Shortcut& shortcut : manager_->shortcuts().values()) { + AddShortcut(shortcut.action); + } + + return true; + +} + +void QxtGlobalShortcutBackend::AddShortcut(QAction *action) { + + if (action->shortcut().isEmpty()) return; + + QxtGlobalShortcut *shortcut = new QxtGlobalShortcut(action->shortcut(), this); + connect(shortcut, SIGNAL(activated()), action, SLOT(trigger())); + shortcuts_ << shortcut; + +} + +void QxtGlobalShortcutBackend::DoUnregister() { + + qLog(Debug) << "unregistering"; + qDeleteAll(shortcuts_); + shortcuts_.clear(); + +} + diff --git a/src/globalshortcuts/qxtglobalshortcutbackend.h b/src/globalshortcuts/qxtglobalshortcutbackend.h new file mode 100644 index 00000000..c371bcc8 --- /dev/null +++ b/src/globalshortcuts/qxtglobalshortcutbackend.h @@ -0,0 +1,44 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef QXTGLOBALSHORTCUTBACKEND_H +#define QXTGLOBALSHORTCUTBACKEND_H + +#include "config.h" + +#include "globalshortcutbackend.h" + +class QxtGlobalShortcut; + +class QxtGlobalShortcutBackend : public GlobalShortcutBackend { + public: + explicit QxtGlobalShortcutBackend(GlobalShortcuts *parent = nullptr); + + protected: + bool DoRegister(); + void DoUnregister(); + + private: + void AddShortcut(QAction *action); + QList shortcuts_; +}; + +#endif // QXTGLOBALSHORTCUTBACKEND_H + diff --git a/src/musicbrainz/acoustidclient.cpp b/src/musicbrainz/acoustidclient.cpp new file mode 100644 index 00000000..e096fd43 --- /dev/null +++ b/src/musicbrainz/acoustidclient.cpp @@ -0,0 +1,159 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +//#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "acoustidclient.h" + +#include "core/closure.h" +#include "core/logging.h" +#include "core/network.h" +#include "core/timeconstants.h" + +const char *AcoustidClient::kClientId = "76JYVgslck"; +const char *AcoustidClient::kUrl = "http://api.acoustid.org/v2/lookup"; +const int AcoustidClient::kDefaultTimeout = 5000; // msec + +AcoustidClient::AcoustidClient(QObject *parent) + : QObject(parent), + network_(new NetworkAccessManager(this)), + timeouts_(new NetworkTimeouts(kDefaultTimeout, this)) {} + +void AcoustidClient::SetTimeout(int msec) { timeouts_->SetTimeout(msec); } + +void AcoustidClient::Start(int id, const QString& fingerprint, int duration_msec) { + + typedef QPair Param; + + QList parameters; + parameters << Param("format", "json") + << Param("client", kClientId) + << Param("duration", QString::number(duration_msec / kMsecPerSec)) + << Param("meta", "recordingids+sources") + << Param("fingerprint", fingerprint); + + QUrl url(kUrl); + QUrlQuery url_query; + url_query.setQueryItems(parameters); + url.setQuery(url_query); + QNetworkRequest req(url); + + QNetworkReply *reply = network_->get(req); + NewClosure(reply, SIGNAL(finished()), this, SLOT(RequestFinished(QNetworkReply*, int)), reply, id); + requests_[id] = reply; + + timeouts_->AddReply(reply); +} + +void AcoustidClient::Cancel(int id) { delete requests_.take(id); } + +void AcoustidClient::CancelAll() { + qDeleteAll(requests_.values()); + requests_.clear(); +} + +namespace { +// Struct used when extracting results in RequestFinished +struct IdSource { + IdSource(const QString& id, int source) + : id_(id), nb_sources_(source) {} + + bool operator<(const IdSource& other) const { + // We want the items with more sources to be at the beginning of the list + return nb_sources_ > other.nb_sources_; + } + + QString id_; + int nb_sources_; +}; +} + +void AcoustidClient::RequestFinished(QNetworkReply *reply, int request_id) { + + reply->deleteLater(); + requests_.remove(request_id); + + if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) { + emit Finished(request_id, QStringList()); + return; + } + + QJsonParseError error; + QJsonDocument json_document = QJsonDocument::fromJson(reply->readAll(), &error); + + if (error.error != QJsonParseError::NoError) { + emit Finished(request_id, QStringList()); + return; + } + + QJsonObject json_object = json_document.object(); + + QString status = json_object["status"].toString(); + if (status != "ok") { + emit Finished(request_id, QStringList()); + return; + } + + // Get the results: + // -in a first step, gather ids and their corresponding number of sources + // -then sort results by number of sources (the results are originally + // unsorted but results with more sources are likely to be more accurate) + // -keep only the ids, as sources where useful only to sort the results + QJsonArray json_results = json_object["results"].toArray(); + + // List of pairs + QList id_source_list; + + for (const QJsonValue& v : json_results) { + QJsonObject r = v.toObject(); + if (!r["recordings"].isUndefined()) { + QJsonArray json_recordings = r["recordings"].toArray(); + for (const QJsonValue& recording : json_recordings) { + QJsonObject o = recording.toObject(); + if (!o["id"].isUndefined()) { + id_source_list << IdSource(o["id"].toString(), o["sources"].toInt()); + } + } + } + } + + qStableSort(id_source_list); + + QList id_list; + for (const IdSource& is : id_source_list) { + id_list << is.id_; + } + + emit Finished(request_id, id_list); + +} + diff --git a/src/musicbrainz/acoustidclient.h b/src/musicbrainz/acoustidclient.h new file mode 100644 index 00000000..f3ac7787 --- /dev/null +++ b/src/musicbrainz/acoustidclient.h @@ -0,0 +1,81 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ACOUSTIDCLIENT_H +#define ACOUSTIDCLIENT_H + +#include "config.h" + +#include +#include + +class NetworkTimeouts; + +class QNetworkAccessManager; +class QNetworkReply; + +class AcoustidClient : public QObject { + + Q_OBJECT + + // Gets a MBID from a Chromaprint fingerprint. + // A fingerprint identifies one particular encoding of a song and is created + // by Fingerprinter. An MBID identifies the actual song and can be passed to + // Musicbrainz to get metadata. + // You can create one AcoustidClient and make multiple requests using it. + // IDs are provided by the caller when a request is started and included in + // the Finished signal - they have no meaning to AcoustidClient. + + public: + AcoustidClient(QObject *parent = nullptr); + + // Network requests will be aborted after this interval. + void SetTimeout(int msec); + + // Starts a request and returns immediately. Finished() will be emitted + // later with the same ID. + void Start(int id, const QString& fingerprint, int duration_msec); + + // Cancels the request with the given ID. Finished() will never be emitted + // for that ID. Does nothing if there is no request with the given ID. + void Cancel(int id); + + // Cancels all requests. Finished() will never be emitted for any pending + // requests. + void CancelAll(); + +signals: + void Finished(int id, const QStringList &mbid_list); + + private slots: + void RequestFinished(QNetworkReply *reply, int id); + + private: + static const char *kClientId; + static const char *kUrl; + static const int kDefaultTimeout; + + QNetworkAccessManager *network_; + NetworkTimeouts *timeouts_; + QMap requests_; +}; + +#endif // ACOUSTIDCLIENT_H + diff --git a/src/musicbrainz/chromaprinter.cpp b/src/musicbrainz/chromaprinter.cpp new file mode 100644 index 00000000..21f268bb --- /dev/null +++ b/src/musicbrainz/chromaprinter.cpp @@ -0,0 +1,221 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "chromaprinter.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "core/logging.h" +#include "core/signalchecker.h" + +static const int kDecodeRate = 11025; +static const int kDecodeChannels = 1; +static const int kPlayLengthSecs = 30; +static const int kTimeoutSecs = 10; + +Chromaprinter::Chromaprinter(const QString &filename) + : filename_(filename), + convert_element_(nullptr) {} + +Chromaprinter::~Chromaprinter() {} + +GstElement *Chromaprinter::CreateElement(const QString &factory_name, GstElement *bin) { + + GstElement *ret = gst_element_factory_make( factory_name.toLatin1().constData(), factory_name.toLatin1().constData()); + + if (ret && bin) gst_bin_add(GST_BIN(bin), ret); + + if (!ret) { + qLog(Warning) << "Couldn't create the gstreamer element" << factory_name; + } + + return ret; + +} + +QString Chromaprinter::CreateFingerprint() { + + Q_ASSERT(QThread::currentThread() != qApp->thread()); + + buffer_.open(QIODevice::WriteOnly); + + GstElement *pipeline = gst_pipeline_new("pipeline"); + GstElement *src = CreateElement("filesrc", pipeline); + GstElement *decode = CreateElement("decodebin", pipeline); + GstElement *convert = CreateElement("audioconvert", pipeline); + GstElement *resample = CreateElement("audioresample", pipeline); + GstElement *sink = CreateElement("appsink", pipeline); + + if (!src || !decode || !convert || !resample || !sink) { + return QString(); + } + + convert_element_ = convert; + + // Connect the elements + gst_element_link_many(src, decode, nullptr); + gst_element_link_many(convert, resample, nullptr); + + // Chromaprint expects mono 16-bit ints at a sample rate of 11025Hz. + GstCaps *caps = gst_caps_new_simple( + "audio/x-raw", + "format", G_TYPE_STRING, "S16LE", + "channels", G_TYPE_INT, kDecodeChannels, + "rate", G_TYPE_INT, kDecodeRate, + NULL); + gst_element_link_filtered(resample, sink, caps); + gst_caps_unref(caps); + + GstAppSinkCallbacks callbacks; + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.new_sample = NewBufferCallback; + gst_app_sink_set_callbacks(reinterpret_cast(sink), &callbacks, + this, nullptr); + g_object_set(G_OBJECT(sink), "sync", FALSE, nullptr); + g_object_set(G_OBJECT(sink), "emit-signals", TRUE, nullptr); + + // Set the filename + g_object_set(src, "location", filename_.toUtf8().constData(), nullptr); + + // Connect signals + GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); + CHECKED_GCONNECT(decode, "pad-added", &NewPadCallback, this); + + // Play only first x seconds + gst_element_set_state(pipeline, GST_STATE_PAUSED); + // wait for state change before seeking + gst_element_get_state(pipeline, nullptr, nullptr, kTimeoutSecs * GST_SECOND); + gst_element_seek(pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 0 * GST_SECOND, GST_SEEK_TYPE_SET, kPlayLengthSecs * GST_SECOND); + + QTime time; + time.start(); + + // Start playing + gst_element_set_state(pipeline, GST_STATE_PLAYING); + + // Wait until EOS or error + GstMessage *msg = gst_bus_timed_pop_filtered(bus, kTimeoutSecs * GST_SECOND, static_cast(GST_MESSAGE_EOS | GST_MESSAGE_ERROR)); + + if (msg != nullptr) { + if (msg->type == GST_MESSAGE_ERROR) { + // Report error + GError *error = nullptr; + gchar *debugs = nullptr; + + gst_message_parse_error(msg, &error, &debugs); + QString message = QString::fromLocal8Bit(error->message); + + g_error_free(error); + free(debugs); + + qLog(Debug) << "Error processing" << filename_ << ":" << message; + } + gst_message_unref(msg); + } + + int decode_time = time.restart(); + + buffer_.close(); + + // Generate fingerprint from recorded buffer data + QByteArray data = buffer_.data(); + + ChromaprintContext *chromaprint = chromaprint_new(CHROMAPRINT_ALGORITHM_DEFAULT); + chromaprint_start(chromaprint, kDecodeRate, kDecodeChannels); + chromaprint_feed(chromaprint, reinterpret_cast(data.data()), data.size() / 2); + chromaprint_finish(chromaprint); + + int size = 0; + +#if CHROMAPRINT_VERSION_MAJOR >= 1 && CHROMAPRINT_VERSION_MINOR >= 4 + u_int32_t *fprint = nullptr; + char *encoded = nullptr; +#else + void *fprint = nullptr; + void *encoded = nullptr; +#endif + + int ret = chromaprint_get_raw_fingerprint(chromaprint, &fprint, &size); + QByteArray fingerprint; + if (ret == 1) { + + int encoded_size = 0; + chromaprint_encode_fingerprint(fprint, size, CHROMAPRINT_ALGORITHM_DEFAULT, &encoded, &encoded_size, 1); + + fingerprint.append(reinterpret_cast(encoded), encoded_size); + + chromaprint_dealloc(fprint); + chromaprint_dealloc(encoded); + } + chromaprint_free(chromaprint); + int codegen_time = time.elapsed(); + + qLog(Debug) << "Decode time:" << decode_time << "Codegen time:" << codegen_time; + + // Cleanup + callbacks.new_sample = nullptr; + gst_object_unref(bus); + gst_element_set_state(pipeline, GST_STATE_NULL); + gst_object_unref(pipeline); + + return fingerprint; + +} + +void Chromaprinter::NewPadCallback(GstElement*, GstPad *pad, gpointer data) { + + Chromaprinter *instance = reinterpret_cast(data); + GstPad *const audiopad = gst_element_get_static_pad(instance->convert_element_, "sink"); + + if (GST_PAD_IS_LINKED(audiopad)) { + qLog(Warning) << "audiopad is already linked, unlinking old pad"; + gst_pad_unlink(audiopad, GST_PAD_PEER(audiopad)); + } + + gst_pad_link(pad, audiopad); + gst_object_unref(audiopad); + +} + +GstFlowReturn Chromaprinter::NewBufferCallback(GstAppSink *app_sink, gpointer self) { + + Chromaprinter *me = reinterpret_cast(self); + + GstSample *sample = gst_app_sink_pull_sample(app_sink); + GstBuffer *buffer = gst_sample_get_buffer(sample); + GstMapInfo map; + gst_buffer_map(buffer, &map, GST_MAP_READ); + me->buffer_.write(reinterpret_cast(map.data), map.size); + gst_buffer_unmap(buffer, &map); + gst_buffer_unref(buffer); + + return GST_FLOW_OK; + +} + diff --git a/src/musicbrainz/chromaprinter.h b/src/musicbrainz/chromaprinter.h new file mode 100644 index 00000000..95c18b98 --- /dev/null +++ b/src/musicbrainz/chromaprinter.h @@ -0,0 +1,64 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef CHROMAPRINTER_H +#define CHROMAPRINTER_H + +#include "config.h" + +#include +#include + +#include +#include + +class Chromaprinter { + // Creates a Chromaprint fingerprint from a song. + // Uses GStreamer to open and decode the file as PCM data and passes this + // to Chromaprint's code generator. The generated code can be used to identify + // a song via Acoustid. + // You should create one Chromaprinter for each file you want to fingerprint. + // This class works well with QtConcurrentMap. + + public: + Chromaprinter(const QString& filename); + ~Chromaprinter(); + + // Creates a fingerprint from the song. This method is blocking, so you want + // to call it in another thread. Returns an empty string if no fingerprint + // could be created. + QString CreateFingerprint(); + + private: + GstElement *CreateElement(const QString &factory_name, GstElement *bin = nullptr); + + static void NewPadCallback(GstElement*, GstPad *pad, gpointer data); + static GstFlowReturn NewBufferCallback(GstAppSink *app_sink, gpointer self); + + private: + QString filename_; + + GstElement *convert_element_; + + QBuffer buffer_; + +}; + +#endif // CHROMAPRINTER_H diff --git a/src/musicbrainz/musicbrainzclient.cpp b/src/musicbrainz/musicbrainzclient.cpp new file mode 100644 index 00000000..d20cfdfb --- /dev/null +++ b/src/musicbrainz/musicbrainzclient.cpp @@ -0,0 +1,408 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "musicbrainzclient.h" + +#include +#include +#include +#include +#include + +#include "core/closure.h" +#include "core/logging.h" +#include "core/network.h" +#include "core/utilities.h" + +const char *MusicBrainzClient::kTrackUrl = "http://musicbrainz.org/ws/2/recording/"; +const char *MusicBrainzClient::kDiscUrl = "http://musicbrainz.org/ws/2/discid/"; +const char *MusicBrainzClient::kDateRegex = "^[12]\\d{3}"; +const int MusicBrainzClient::kDefaultTimeout = 5000; // msec +const int MusicBrainzClient::kMaxRequestPerTrack = 3; + +MusicBrainzClient::MusicBrainzClient(QObject *parent, QNetworkAccessManager *network) + : QObject(parent), + network_(network ? network : new NetworkAccessManager(this)), + timeouts_(new NetworkTimeouts(kDefaultTimeout, this)) {} + +void MusicBrainzClient::Start(int id, const QStringList &mbid_list) { + + typedef QPair Param; + + int request_number = 0; + for (const QString &mbid : mbid_list) { + QList parameters; + parameters << Param("inc", "artists+releases+media"); + + QUrl url(kTrackUrl + mbid); + QUrlQuery url_query; + url_query.setQueryItems(parameters); + url.setQuery(url_query); + QNetworkRequest req(url); + + QNetworkReply *reply = network_->get(req); + NewClosure(reply, SIGNAL(finished()), this, SLOT(RequestFinished(QNetworkReply*, int, int)), reply, id, request_number++); + requests_.insert(id, reply); + + timeouts_->AddReply(reply); + + if (request_number >= kMaxRequestPerTrack) { + break; + } + } +} + +void MusicBrainzClient::StartDiscIdRequest(const QString &discid) { + + typedef QPair Param; + + QList parameters; + parameters << Param("inc", "artists+recordings"); + + QUrl url(kDiscUrl + discid); + QUrlQuery url_query; + url_query.setQueryItems(parameters); + url.setQuery(url_query); + QNetworkRequest req(url); + + QNetworkReply *reply = network_->get(req); + NewClosure(reply, SIGNAL(finished()), this, SLOT(DiscIdRequestFinished(const QString&, QNetworkReply*)), discid, reply); + + timeouts_->AddReply(reply); +} + +void MusicBrainzClient::Cancel(int id) { delete requests_.take(id); } + +void MusicBrainzClient::CancelAll() { + qDeleteAll(requests_.values()); + requests_.clear(); +} + +void MusicBrainzClient::DiscIdRequestFinished(const QString &discid, QNetworkReply *reply) { + + reply->deleteLater(); + + ResultList ret; + QString artist; + QString album; + int year = 0; + + if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) { + qLog(Error) << "Error:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() << "http status code received"; + qLog(Error) << reply->readAll(); + emit Finished(artist, album, ret); + return; + } + + // Parse xml result: + // -get title + // -get artist + // -get year + // -get all the tracks' tags + // Note: If there are multiple releases for the discid, the first + // release is chosen. + QXmlStreamReader reader(reply); + while (!reader.atEnd()) { + QXmlStreamReader::TokenType type = reader.readNext(); + if (type == QXmlStreamReader::StartElement) { + QStringRef name = reader.name(); + if (name == "title") { + album = reader.readElementText(); + } + else if (name == "date") { + QRegExp regex(kDateRegex); + if (regex.indexIn(reader.readElementText()) == 0) { + year = regex.cap(0).toInt(); + } + } + else if (name == "artist-credit") { + ParseArtist(&reader, &artist); + } + else if (name == "medium-list") { + break; + } + } + } + + while (!reader.atEnd()) { + QXmlStreamReader::TokenType token = reader.readNext(); + if (token == QXmlStreamReader::StartElement && reader.name() == "medium") { + // Get the medium with a matching discid. + if (MediumHasDiscid(discid, &reader)) { + ResultList tracks = ParseMedium(&reader); + for (const Result &track : tracks) { + if (!track.title_.isEmpty()) { + ret << track; + } + } + } + else { + Utilities::ConsumeCurrentElement(&reader); + } + } + else if (token == QXmlStreamReader::EndElement && reader.name() == "medium-list") { + break; + } + } + + // If we parsed a year, copy it to the tracks. + if (year > 0) { + for (ResultList::iterator it = ret.begin(); it != ret.end(); ++it) { + it->year_ = year; + } + } + + emit Finished(artist, album, UniqueResults(ret, SortResults)); +} + +void MusicBrainzClient::RequestFinished(QNetworkReply *reply, int id, int request_number) { + + reply->deleteLater(); + + const int nb_removed = requests_.remove(id, reply); + if (nb_removed != 1) { + qLog(Error) << "Error: unknown reply received:" << nb_removed << + "requests removed, while only one was supposed to be removed"; + } + + if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) { + QXmlStreamReader reader(reply); + ResultList res; + while (!reader.atEnd()) { + if (reader.readNext() == QXmlStreamReader::StartElement && reader.name() == "recording") { + ResultList tracks = ParseTrack(&reader); + for (const Result &track : tracks) { + if (!track.title_.isEmpty()) { + res << track; + } + } + } + } + pending_results_[id] << PendingResults(request_number, res); + } + else { + qLog(Error) << "Error:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() << "http status code received"; + qLog(Error) << reply->readAll(); + } + + // No more pending requests for this id: emit the results we have. + if (!requests_.contains(id)) { + // Merge the results we have + ResultList ret; + QList result_list_list = pending_results_.take(id); + qSort(result_list_list); + for (const PendingResults &result_list : result_list_list) { + ret << result_list.results_; + } + emit Finished(id, UniqueResults(ret, KeepOriginalOrder)); + } +} + +bool MusicBrainzClient::MediumHasDiscid(const QString &discid, QXmlStreamReader *reader) { + + while (!reader->atEnd()) { + QXmlStreamReader::TokenType type = reader->readNext(); + + if (type == QXmlStreamReader::StartElement && reader->name() == "disc" && reader->attributes().value("id").toString() == discid) { + return true; + } + else if (type == QXmlStreamReader::EndElement && reader->name() == "disc-list") { + return false; + } + } + qLog(Debug) << "Reached end of xml stream without encountering "; + return false; +} + +MusicBrainzClient::ResultList MusicBrainzClient::ParseMedium(QXmlStreamReader *reader) { + + ResultList ret; + while (!reader->atEnd()) { + QXmlStreamReader::TokenType type = reader->readNext(); + + if (type == QXmlStreamReader::StartElement) { + if (reader->name() == "track") { + Result result; + result = ParseTrackFromDisc(reader); + ret << result; + } + } + + if (type == QXmlStreamReader::EndElement && reader->name() == "track-list") { + break; + } + } + + return ret; + +} + +MusicBrainzClient::Result MusicBrainzClient::ParseTrackFromDisc(QXmlStreamReader *reader) { + + Result result; + + while (!reader->atEnd()) { + QXmlStreamReader::TokenType type = reader->readNext(); + + if (type == QXmlStreamReader::StartElement) { + QStringRef name = reader->name(); + if (name == "position") { + result.track_ = reader->readElementText().toInt(); + } + else if (name == "length") { + result.duration_msec_ = reader->readElementText().toInt(); + } + else if (name == "title") { + result.title_ = reader->readElementText(); + } + } + + if (type == QXmlStreamReader::EndElement && reader->name() == "track") { + break; + } + } + + return result; +} + +MusicBrainzClient::ResultList MusicBrainzClient::ParseTrack(QXmlStreamReader *reader) { + + Result result; + QList releases; + + while (!reader->atEnd()) { + QXmlStreamReader::TokenType type = reader->readNext(); + + if (type == QXmlStreamReader::StartElement) { + QStringRef name = reader->name(); + + if (name == "title") { + result.title_ = reader->readElementText(); + } + else if (name == "length") { + result.duration_msec_ = reader->readElementText().toInt(); + } + else if (name == "artist-credit") { + ParseArtist(reader, &result.artist_); + } + else if (name == "release") { + releases << ParseRelease(reader); + } + } + + if (type == QXmlStreamReader::EndElement && reader->name() == "recording") { + break; + } + } + + ResultList ret; + if (releases.isEmpty()) { + ret << result; + } + else { + qStableSort(releases); + for (const Release &release : releases) { + ret << release.CopyAndMergeInto(result); + } + } + return ret; +} + +// Parse the artist. Multiple artists are joined together with the +// joinphrase from musicbrainz. +void MusicBrainzClient::ParseArtist(QXmlStreamReader *reader, QString *artist) { + + QString join_phrase; + + while (!reader->atEnd()) { + QXmlStreamReader::TokenType type = reader->readNext(); + + if (type == QXmlStreamReader::StartElement && reader->name() == "name-credit") { + join_phrase = reader->attributes().value("joinphrase").toString(); + } + + if (type == QXmlStreamReader::StartElement && reader->name() == "name") { + *artist += reader->readElementText() + join_phrase; + } + + if (type == QXmlStreamReader::EndElement && reader->name() == "artist-credit") { + return; + } + } +} + +MusicBrainzClient::Release MusicBrainzClient::ParseRelease(QXmlStreamReader *reader) { + + Release ret; + + while (!reader->atEnd()) { + QXmlStreamReader::TokenType type = reader->readNext(); + + if (type == QXmlStreamReader::StartElement) { + QStringRef name = reader->name(); + if (name == "title") { + ret.album_ = reader->readElementText(); + } + else if (name == "status") { + ret.SetStatusFromString(reader->readElementText()); + } + else if (name == "date") { + QRegExp regex(kDateRegex); + if (regex.indexIn(reader->readElementText()) == 0) { + ret.year_ = regex.cap(0).toInt(); + } + } + else if (name == "track-list") { + ret.track_ = reader->attributes().value("offset").toString().toInt() + 1; + Utilities::ConsumeCurrentElement(reader); + } + } + + if (type == QXmlStreamReader::EndElement && reader->name() == "release") { + break; + } + } + + return ret; + +} + +MusicBrainzClient::ResultList MusicBrainzClient::UniqueResults(const ResultList& results, UniqueResultsSortOption opt) { + + ResultList ret; + if (opt == SortResults) { + ret = QSet::fromList(results).toList(); + qSort(ret); + } + else { // KeepOriginalOrder + // Qt doesn't provide a ordered set (QSet "stores values in an unspecified + // order" according to Qt documentation). + // We might use std::set instead, but it's probably faster to use ResultList + // directly to avoid converting from one structure to another. + for (const Result& res : results) { + if (!ret.contains(res)) { + ret << res; + } + } + } + return ret; +} diff --git a/src/musicbrainz/musicbrainzclient.h b/src/musicbrainz/musicbrainzclient.h new file mode 100644 index 00000000..c9b79466 --- /dev/null +++ b/src/musicbrainz/musicbrainzclient.h @@ -0,0 +1,210 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MUSICBRAINZCLIENT_H +#define MUSICBRAINZCLIENT_H + +#include "config.h" + +#include +#include +#include +#include + +class NetworkTimeouts; + +class QNetworkAccessManager; +class QNetworkReply; + +class MusicBrainzClient : public QObject { + Q_OBJECT + + // Gets metadata for a particular MBID. + // An MBID is created from a fingerprint using MusicDnsClient. + // You can create one MusicBrainzClient and make multiple requests using it. + // IDs are provided by the caller when a request is started and included in + // the Finished signal - they have no meaning to MusicBrainzClient. + + public: + // The second argument allows for specifying a custom network access + // manager. It is used in tests. The ownership of network + // is not transferred. + MusicBrainzClient(QObject* parent = nullptr, + QNetworkAccessManager* network = nullptr); + + struct Result { + Result() : duration_msec_(0), track_(0), year_(-1) {} + + bool operator<(const Result& other) const { +#define cmp(field) \ + if (field < other.field) return true; \ + if (field > other.field) return false; + + cmp(track_); + cmp(year_); + cmp(title_); + cmp(artist_); + return false; + +#undef cmp + } + + bool operator==(const Result& other) const { + return + title_ == other.title_ && + artist_ == other.artist_ && + album_ == other.album_ && + duration_msec_ == other.duration_msec_ && + track_ == other.track_ && + year_ == other.year_; + } + + QString title_; + QString artist_; + QString album_; + int duration_msec_; + int track_; + int year_; + }; + typedef QList ResultList; + + // Starts a request and returns immediately. Finished() will be emitted + // later with the same ID. + void Start(int id, const QStringList& mbid); + void StartDiscIdRequest(const QString& discid); + + // Cancels the request with the given ID. Finished() will never be emitted + // for that ID. Does nothing if there is no request with the given ID. + void Cancel(int id); + + // Cancels all requests. Finished() will never be emitted for any pending + // requests. + void CancelAll(); + +signals: + // Finished signal emitted when fechting songs tags + void Finished(int id, const MusicBrainzClient::ResultList& result); + // Finished signal emitted when fechting album's songs tags using DiscId + void Finished(const QString& artist, const QString album, + const MusicBrainzClient::ResultList& result); + + private slots: + // id identifies the track, and request_number means it's the + // 'request_number'th request for this track + void RequestFinished(QNetworkReply* reply, int id, int request_number); + void DiscIdRequestFinished(const QString& discid, QNetworkReply* reply); + + private: + // Used as parameter for UniqueResults + enum UniqueResultsSortOption { + SortResults = 0, + KeepOriginalOrder + }; + + struct Release { + + enum Status { + Status_Unknown = 0, + Status_PseudoRelease, + Status_Bootleg, + Status_Promotional, + Status_Official + }; + + Release() : track_(0), year_(0), status_(Status_Unknown) {} + + Result CopyAndMergeInto(const Result& orig) const { + Result ret(orig); + ret.album_ = album_; + ret.track_ = track_; + ret.year_ = year_; + return ret; + } + + void SetStatusFromString(const QString& s) { + if (s.compare("Official", Qt::CaseInsensitive) == 0) { + status_ = Status_Official; + } + else if (s.compare("Promotion", Qt::CaseInsensitive) == 0) { + status_ = Status_Promotional; + } + else if (s.compare("Bootleg", Qt::CaseInsensitive) == 0) { + status_ = Status_Bootleg; + } + else if (s.compare("Pseudo-release", Qt::CaseInsensitive) == 0) { + status_ = Status_PseudoRelease; + } + else { + status_ = Status_Unknown; + } + } + + bool operator<(const Release& other) const { + // Compare status so that "best" status (e.g. Official) will be first + // when sorting a list of releases. + return status_ > other.status_; + } + + QString album_; + int track_; + int year_; + Status status_; + }; + + struct PendingResults { + PendingResults(int sort_id, const ResultList& results) : sort_id_(sort_id), results_(results) {} + + bool operator<(const PendingResults& other) const { + return sort_id_ < other.sort_id_; + } + + int sort_id_; + ResultList results_; + }; + + static bool MediumHasDiscid(const QString& discid, QXmlStreamReader* reader); + static ResultList ParseMedium(QXmlStreamReader* reader); + static Result ParseTrackFromDisc(QXmlStreamReader* reader); + static ResultList ParseTrack(QXmlStreamReader* reader); + static void ParseArtist(QXmlStreamReader* reader, QString* artist); + static Release ParseRelease(QXmlStreamReader* reader); + static ResultList UniqueResults(const ResultList& results, UniqueResultsSortOption opt = SortResults); + + + private: + static const char* kTrackUrl; + static const char* kDiscUrl; + static const char* kDateRegex; + static const int kDefaultTimeout; + static const int kMaxRequestPerTrack; + + QNetworkAccessManager* network_; + NetworkTimeouts* timeouts_; + QMultiMap requests_; + // Results we received so far, kept here until all the replies are finished + QMap> pending_results_; +}; + +inline uint qHash(const MusicBrainzClient::Result& result) { + return qHash(result.album_) ^ qHash(result.artist_) ^ result.duration_msec_ ^ + qHash(result.title_) ^ result.track_ ^ result.year_; +} + +#endif // MUSICBRAINZCLIENT_H diff --git a/src/musicbrainz/tagfetcher.cpp b/src/musicbrainz/tagfetcher.cpp new file mode 100644 index 00000000..e460b3a5 --- /dev/null +++ b/src/musicbrainz/tagfetcher.cpp @@ -0,0 +1,140 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "tagfetcher.h" + +#include "acoustidclient.h" +#include "chromaprinter.h" +#include "musicbrainzclient.h" +#include "core/timeconstants.h" + +#include +#include +#include +#include + +TagFetcher::TagFetcher(QObject* parent) + : QObject(parent), + fingerprint_watcher_(nullptr), + acoustid_client_(new AcoustidClient(this)), + musicbrainz_client_(new MusicBrainzClient(this)) { + + connect(acoustid_client_, SIGNAL(Finished(int, QStringList)), SLOT(PuidsFound(int, QStringList))); + connect(musicbrainz_client_, SIGNAL(Finished(int, MusicBrainzClient::ResultList)), SLOT(TagsFetched(int, MusicBrainzClient::ResultList))); + +} + +QString TagFetcher::GetFingerprint(const Song &song) { + return Chromaprinter(song.url().toLocalFile()).CreateFingerprint(); +} + +void TagFetcher::StartFetch(const SongList &songs) { + + Cancel(); + + songs_ = songs; + + QFuture future = QtConcurrent::mapped(songs_, GetFingerprint); + fingerprint_watcher_ = new QFutureWatcher(this); + fingerprint_watcher_->setFuture(future); + connect(fingerprint_watcher_, SIGNAL(resultReadyAt(int)), SLOT(FingerprintFound(int))); + + for (const Song &song : songs) { + emit Progress(song, tr("Fingerprinting song")); + } + +} + +void TagFetcher::Cancel() { + + if (fingerprint_watcher_) { + fingerprint_watcher_->cancel(); + + delete fingerprint_watcher_; + fingerprint_watcher_ = nullptr; + } + + acoustid_client_->CancelAll(); + musicbrainz_client_->CancelAll(); + songs_.clear(); + +} + +void TagFetcher::FingerprintFound(int index) { + + QFutureWatcher* watcher = reinterpret_cast*>(sender()); + if (!watcher || index >= songs_.count()) { + return; + } + + const QString fingerprint = watcher->resultAt(index); + const Song &song = songs_[index]; + + if (fingerprint.isEmpty()) { + emit ResultAvailable(song, SongList()); + return; + } + + emit Progress(song, tr("Identifying song")); + acoustid_client_->Start(index, fingerprint, song.length_nanosec() / kNsecPerMsec); + +} + +void TagFetcher::PuidsFound(int index, const QStringList &puid_list) { + + if (index >= songs_.count()) { + return; + } + + const Song &song = songs_[index]; + + if (puid_list.isEmpty()) { + emit ResultAvailable(song, SongList()); + return; + } + + emit Progress(song, tr("Downloading metadata")); + musicbrainz_client_->Start(index, puid_list); + +} + +void TagFetcher::TagsFetched(int index, const MusicBrainzClient::ResultList &results) { + + if (index >= songs_.count()) { + return; + } + + const Song &original_song = songs_[index]; + SongList songs_guessed; + + for (const MusicBrainzClient::Result &result : results) { + Song song; + song.Init(result.title_, result.artist_, result.album_, result.duration_msec_ * kNsecPerMsec); + song.set_track(result.track_); + song.set_year(result.year_); + songs_guessed << song; + } + + emit ResultAvailable(original_song, songs_guessed); + +} + diff --git a/src/musicbrainz/tagfetcher.h b/src/musicbrainz/tagfetcher.h new file mode 100644 index 00000000..9eb0badd --- /dev/null +++ b/src/musicbrainz/tagfetcher.h @@ -0,0 +1,67 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TAGFETCHER_H +#define TAGFETCHER_H + +#include "config.h" + +#include "musicbrainzclient.h" +#include "core/song.h" + +#include +#include + +class AcoustidClient; + +class TagFetcher : public QObject { + Q_OBJECT + + // High level interface to Fingerprinter, AcoustidClient and + // MusicBrainzClient. + + public: + TagFetcher(QObject *parent = nullptr); + + void StartFetch(const SongList &songs); + + public slots: + void Cancel(); + +signals: + void Progress(const Song &original_song, const QString &stage); + void ResultAvailable(const Song &original_song, const SongList &songs_guessed); + + private slots: + void FingerprintFound(int index); + void PuidsFound(int index, const QStringList &puid_list); + void TagsFetched(int index, const MusicBrainzClient::ResultList &result); + + private: + static QString GetFingerprint(const Song &song); + + QFutureWatcher *fingerprint_watcher_; + AcoustidClient *acoustid_client_; + MusicBrainzClient *musicbrainz_client_; + + SongList songs_; +}; + +#endif // TAGFETCHER_H diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp new file mode 100644 index 00000000..19346489 --- /dev/null +++ b/src/playlist/playlist.cpp @@ -0,0 +1,1895 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "playlist.h" + +#include "playlistbackend.h" +#include "playlistfilter.h" +#include "playlistitemmimedata.h" +#include "playlistundocommands.h" +#include "playlistview.h" +#include "queue.h" +#include "songloaderinserter.h" +#include "songmimedata.h" +#include "songplaylistitem.h" +#include "core/application.h" +#include "core/closure.h" +#include "core/logging.h" +//#include "core/qhash_qurl.h" +#include "core/tagreaderclient.h" +#include "core/timeconstants.h" +#include "collection/collection.h" +#include "collection/collectionbackend.h" +#include "collection/collectionmodel.h" +#include "collection/collectionplaylistitem.h" + +using std::placeholders::_1; +using std::placeholders::_2; +using std::shared_ptr; +using std::unordered_map; + +const char *Playlist::kCddaMimeType = "x-content/audio-cdda"; +const char *Playlist::kRowsMimetype = "application/x-strawberry-playlist-rows"; +const char *Playlist::kPlayNowMimetype = "application/x-strawberry-play-now"; + +const int Playlist::kInvalidSongPriority = 200; +const QRgb Playlist::kInvalidSongColor = qRgb(0xC0, 0xC0, 0xC0); + +const int Playlist::kDynamicHistoryPriority = 100; +const QRgb Playlist::kDynamicHistoryColor = qRgb(0x80, 0x80, 0x80); + +const char *Playlist::kSettingsGroup = "Playlist"; + +const char *Playlist::kPathType = "path_type"; +const char *Playlist::kWriteMetadata = "write_metadata"; + +const int Playlist::kUndoStackSize = 20; +const int Playlist::kUndoItemLimit = 500; + +Playlist::Playlist(PlaylistBackend *backend, TaskManager *task_manager, CollectionBackend *collection, int id, const QString &special_type, bool favorite, QObject *parent) + : QAbstractListModel(parent), + is_loading_(false), + proxy_(new PlaylistFilter(this)), + queue_(new Queue(this)), + backend_(backend), + task_manager_(task_manager), + collection_(collection), + id_(id), + favorite_(favorite), + current_is_paused_(false), + current_virtual_index_(-1), + is_shuffled_(false), + playlist_sequence_(nullptr), + ignore_sorting_(false), + undo_stack_(new QUndoStack(this)), + special_type_(special_type), + cancel_restore_(false) { + + undo_stack_->setUndoLimit(kUndoStackSize); + + connect(this, SIGNAL(rowsInserted(const QModelIndex&, int, int)), SIGNAL(PlaylistChanged())); + connect(this, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), SIGNAL(PlaylistChanged())); + + Restore(); + + proxy_->setSourceModel(this); + queue_->setSourceModel(this); + + connect(queue_, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), SLOT(TracksAboutToBeDequeued(QModelIndex, int, int))); + connect(queue_, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(TracksDequeued())); + + connect(queue_, SIGNAL(rowsInserted(const QModelIndex&, int, int)), SLOT(TracksEnqueued(const QModelIndex&, int, int))); + + connect(queue_, SIGNAL(layoutChanged()), SLOT(QueueLayoutChanged())); + + column_alignments_ = PlaylistView::DefaultColumnAlignment(); + +} + +Playlist::~Playlist() { + items_.clear(); + collection_items_by_id_.clear(); +} + +template +void Playlist::InsertSongItems(const SongList &songs, int pos, bool play_now, bool enqueue) { + + PlaylistItemList items; + + for (const Song &song : songs) { + items << PlaylistItemPtr(new T(song)); + } + + InsertItems(items, pos, play_now, enqueue); + +} + +QVariant Playlist::headerData(int section, Qt::Orientation, int role) const { + + if (role != Qt::DisplayRole && role != Qt::ToolTipRole) return QVariant(); + + const QString name = column_name((Playlist::Column)section); + if (!name.isEmpty()) return name; + + return QVariant(); + +} + +bool Playlist::column_is_editable(Playlist::Column column) { + + switch (column) { + case Column_Title: + case Column_Artist: + case Column_Album: + case Column_AlbumArtist: + case Column_Composer: + case Column_Performer: + case Column_Grouping: + case Column_Track: + case Column_Disc: + case Column_Year: + case Column_Genre: + case Column_Comment: + return true; + default: + break; + } + return false; + +} + +bool Playlist::set_column_value(Song &song, Playlist::Column column, const QVariant &value) { + + if (!song.IsEditable()) return false; + + switch (column) { + case Column_Title: + song.set_title(value.toString()); + break; + case Column_Artist: + song.set_artist(value.toString()); + break; + case Column_Album: + song.set_album(value.toString()); + break; + case Column_AlbumArtist: + song.set_albumartist(value.toString()); + break; + case Column_Composer: + song.set_composer(value.toString()); + break; + case Column_Performer: + song.set_performer(value.toString()); + break; + case Column_Grouping: + song.set_grouping(value.toString()); + break; + case Column_Track: + song.set_track(value.toInt()); + break; + case Column_Disc: + song.set_disc(value.toInt()); + break; + case Column_Year: + song.set_year(value.toInt()); + break; + case Column_Genre: + song.set_genre(value.toString()); + break; + case Column_Comment: + song.set_comment(value.toString()); + break; + default: + break; + } + return true; + +} + +QVariant Playlist::data(const QModelIndex &index, int role) const { + + switch (role) { + case Role_IsCurrent: + return current_item_index_.isValid() && index.row() == current_item_index_.row(); + + case Role_IsPaused: + return current_is_paused_; + + case Role_StopAfter: + return stop_after_.isValid() && stop_after_.row() == index.row(); + + case Role_QueuePosition: + return queue_->PositionOf(index); + + case Qt::EditRole: + case Qt::ToolTipRole: + case Qt::DisplayRole: { + PlaylistItemPtr item = items_[index.row()]; + Song song = item->Metadata(); + + // Don't forget to change Playlist::CompareItems when adding new columns + switch (index.column()) { + case Column_Title: return song.PrettyTitle(); + case Column_Artist: return song.artist(); + case Column_Album: return song.album(); + case Column_Length: return song.length_nanosec(); + case Column_Track: return song.track(); + case Column_Disc: return song.disc(); + case Column_Year: return song.year(); + case Column_OriginalYear: return song.effective_originalyear(); + case Column_Genre: return song.genre(); + case Column_AlbumArtist: return song.playlist_albumartist(); + case Column_Composer: return song.composer(); + case Column_Performer: return song.performer(); + case Column_Grouping: return song.grouping(); + + case Column_PlayCount: return song.playcount(); + case Column_SkipCount: return song.skipcount(); + case Column_LastPlayed: return song.lastplayed(); + + case Column_Samplerate: return song.samplerate(); + case Column_Bitdepth: return song.bitdepth(); + case Column_Bitrate: return song.bitrate(); + case Column_SamplerateBitdepth: return song.SampleRateBitDepthToText(); + + case Column_Filename: return song.url(); + case Column_BaseFilename: return song.basefilename(); + case Column_Filesize: return song.filesize(); + case Column_Filetype: return song.filetype(); + case Column_DateModified: return song.mtime(); + case Column_DateCreated: return song.ctime(); + + case Column_Comment: + if (role == Qt::DisplayRole) return song.comment().simplified(); + return song.comment(); + + //case Column_Source: return item->Url(); + + } + + return QVariant(); + } + + case Qt::TextAlignmentRole: + return QVariant(column_alignments_.value(index.column(), (Qt::AlignLeft | Qt::AlignVCenter))); + + case Qt::ForegroundRole: + if (data(index, Role_IsCurrent).toBool()) { + // Ignore any custom colours for the currently playing item - they might + // clash with the glowing current track indicator. + return QVariant(); + } + + if (items_[index.row()]->HasCurrentForegroundColor()) { + return QBrush(items_[index.row()]->GetCurrentForegroundColor()); + } + //if (index.row() < dynamic_history_length()) { + //return QBrush(kDynamicHistoryColor); + //} + return QVariant(); + + case Qt::BackgroundRole: + if (data(index, Role_IsCurrent).toBool()) { + // Ignore any custom colours for the currently playing item - they might + // clash with the glowing current track indicator. + return QVariant(); + } + + if (items_[index.row()]->HasCurrentBackgroundColor()) { + return QBrush(items_[index.row()]->GetCurrentBackgroundColor()); + } + return QVariant(); + + case Qt::FontRole: + if (items_[index.row()]->GetShouldSkip()) { + QFont track_font; + track_font.setStrikeOut(true); + return track_font; + } + return QVariant(); + + default: + return QVariant(); + } + +} + +bool Playlist::setData(const QModelIndex &index, const QVariant &value, int role) { + + int row = index.row(); + PlaylistItemPtr item = item_at(row); + Song song = item->Metadata(); + + if (index.data() == value) return false; + + if (!set_column_value(song, (Column)index.column(), value)) return false; + + TagReaderReply *reply = TagReaderClient::Instance()->SaveFile( song.url().toLocalFile(), song); + NewClosure(reply, SIGNAL(Finished(bool)), this, SLOT(SongSaveComplete(TagReaderReply*,QPersistentModelIndex)), reply, QPersistentModelIndex(index)); + + return true; + +} + +void Playlist::SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &index) { + + if (reply->is_successful() && index.isValid()) { + if (reply->message().save_file_response().success()) { + QFuture future = item_at(index.row())->BackgroundReload(); + NewClosure(future, this, SLOT(ItemReloadComplete(QPersistentModelIndex)), index); + } + else { + emit Error(tr("An error occurred writing metadata to '%1'").arg(QString::fromStdString(reply->request_message().save_file_request().filename()))); + } + } + + reply->deleteLater(); + +} + +void Playlist::ItemReloadComplete(const QPersistentModelIndex &index) { + + if (index.isValid()) { + emit dataChanged(index, index); + emit EditingFinished(index); + } + +} + +int Playlist::current_row() const { + return current_item_index_.isValid() ? current_item_index_.row() : -1; +} + +const QModelIndex Playlist::current_index() const { + return current_item_index_; +} + +int Playlist::last_played_row() const { + return last_played_item_index_.isValid() ? last_played_item_index_.row() : -1; +} + +void Playlist::ShuffleModeChanged(PlaylistSequence::ShuffleMode mode) { + is_shuffled_ = (mode != PlaylistSequence::Shuffle_Off); + ReshuffleIndices(); +} + +bool Playlist::FilterContainsVirtualIndex(int i) const { + if (i < 0 || i >= virtual_items_.count()) return false; + + return proxy_->filterAcceptsRow(virtual_items_[i], QModelIndex()); +} + +int Playlist::NextVirtualIndex(int i, bool ignore_repeat_track) const { + + PlaylistSequence::RepeatMode repeat_mode = playlist_sequence_->repeat_mode(); + PlaylistSequence::ShuffleMode shuffle_mode = playlist_sequence_->shuffle_mode(); + bool album_only = repeat_mode == PlaylistSequence::Repeat_Album || shuffle_mode == PlaylistSequence::Shuffle_InsideAlbum; + + // This one's easy - if we have to repeat the current track then just return i + if (repeat_mode == PlaylistSequence::Repeat_Track && !ignore_repeat_track) { + if (!FilterContainsVirtualIndex(i)) + return virtual_items_.count(); // It's not in the filter any more + return i; + } + + // If we're not bothered about whether a song is on the same album then + // return the next virtual index, whatever it is. + if (!album_only) { + ++i; + + // Advance i until we find any track that is in the filter, skipping + // the selected to be skipped + while (i < virtual_items_.count() && (!FilterContainsVirtualIndex(i) || item_at(virtual_items_[i])->GetShouldSkip())) { + ++i; + } + return i; + } + + // We need to advance i until we get something else on the same album + Song last_song = current_item_metadata(); + for (int j = i + 1; j < virtual_items_.count(); ++j) { + if (item_at(virtual_items_[j])->GetShouldSkip()) { + continue; + } + Song this_song = item_at(virtual_items_[j])->Metadata(); + if (((last_song.is_compilation() && this_song.is_compilation()) || + last_song.artist() == this_song.artist()) && + last_song.album() == this_song.album() && + FilterContainsVirtualIndex(j)) { + return j; // Found one + } + } + + // Couldn't find one - return past the end of the list + return virtual_items_.count(); + +} + +int Playlist::PreviousVirtualIndex(int i, bool ignore_repeat_track) const { + + PlaylistSequence::RepeatMode repeat_mode = playlist_sequence_->repeat_mode(); + PlaylistSequence::ShuffleMode shuffle_mode = playlist_sequence_->shuffle_mode(); + bool album_only = repeat_mode == PlaylistSequence::Repeat_Album || shuffle_mode == PlaylistSequence::Shuffle_InsideAlbum; + + // This one's easy - if we have to repeat the current track then just return i + if (repeat_mode == PlaylistSequence::Repeat_Track && !ignore_repeat_track) { + if (!FilterContainsVirtualIndex(i)) return -1; + return i; + } + + // If we're not bothered about whether a song is on the same album then + // return the previous virtual index, whatever it is. + if (!album_only) { + --i; + + // Decrement i until we find any track that is in the filter + while (i >= 0 && (!FilterContainsVirtualIndex(i) || item_at(virtual_items_[i])->GetShouldSkip())) --i; + return i; + } + + // We need to decrement i until we get something else on the same album + Song last_song = current_item_metadata(); + for (int j = i - 1; j >= 0; --j) { + if (item_at(virtual_items_[j])->GetShouldSkip()) { + continue; + } + Song this_song = item_at(virtual_items_[j])->Metadata(); + if (((last_song.is_compilation() && this_song.is_compilation()) || last_song.artist() == this_song.artist()) && last_song.album() == this_song.album() && FilterContainsVirtualIndex(j)) { + return j; // Found one + } + } + + // Couldn't find one - return before the start of the list + return -1; + +} + +int Playlist::next_row(bool ignore_repeat_track) const { + + // Any queued items take priority + if (!queue_->is_empty()) { + return queue_->PeekNext(); + } + + int next_virtual_index = NextVirtualIndex(current_virtual_index_, ignore_repeat_track); + if (next_virtual_index >= virtual_items_.count()) { + // We've gone off the end of the playlist. + + switch (playlist_sequence_->repeat_mode()) { + case PlaylistSequence::Repeat_Off: + case PlaylistSequence::Repeat_Intro: + return -1; + case PlaylistSequence::Repeat_Track: + next_virtual_index = current_virtual_index_; + break; + + default: + next_virtual_index = NextVirtualIndex(-1, ignore_repeat_track); + break; + } + } + + // Still off the end? Then just give up + if (next_virtual_index < 0 || next_virtual_index >= virtual_items_.count()) return -1; + + return virtual_items_[next_virtual_index]; + +} + +int Playlist::previous_row(bool ignore_repeat_track) const { + + int prev_virtual_index = PreviousVirtualIndex(current_virtual_index_,ignore_repeat_track); + + if (prev_virtual_index < 0) { + // We've gone off the beginning of the playlist. + + switch (playlist_sequence_->repeat_mode()) { + case PlaylistSequence::Repeat_Off: + return -1; + case PlaylistSequence::Repeat_Track: + prev_virtual_index = current_virtual_index_; + break; + + default: + prev_virtual_index = PreviousVirtualIndex(virtual_items_.count(),ignore_repeat_track); + break; + } + } + + // Still off the beginning? Then just give up + if (prev_virtual_index < 0) return -1; + + return virtual_items_[prev_virtual_index]; + +} + +void Playlist::set_current_row(int i, bool is_stopping) { + + QModelIndex old_current_item_index = current_item_index_; + //ClearStreamMetadata(); + + current_item_index_ = QPersistentModelIndex(index(i, 0, QModelIndex())); + + // if the given item is the first in the queue, remove it from the queue + if (current_item_index_.row() == queue_->PeekNext()) { + queue_->TakeNext(); + } + + if (current_item_index_ == old_current_item_index) return; + + if (old_current_item_index.isValid()) { + emit dataChanged(old_current_item_index, old_current_item_index.sibling(old_current_item_index.row(), ColumnCount - 1)); + } + + // Update the virtual index + if (i == -1) { + current_virtual_index_ = -1; + } + else if (is_shuffled_ && current_virtual_index_ == -1) { + // This is the first thing we're playing so we want to make sure the array + // is shuffled + ReshuffleIndices(); + + // Bring the one we've been asked to play to the start of the list + virtual_items_.takeAt(virtual_items_.indexOf(i)); + virtual_items_.prepend(i); + current_virtual_index_ = 0; + } + else if (is_shuffled_) { + current_virtual_index_ = virtual_items_.indexOf(i); + } + else { + current_virtual_index_ = i; + } + + if (current_item_index_.isValid() && !is_stopping) { + InformOfCurrentSongChange(); + } + + if (current_item_index_.isValid()) { + last_played_item_index_ = current_item_index_; + Save(); + } + +} + +Qt::ItemFlags Playlist::flags(const QModelIndex &index) const { + + Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; + + if (column_is_editable((Column)index.column())) flags |= Qt::ItemIsEditable; + + if (index.isValid()) return flags | Qt::ItemIsDragEnabled; + + return Qt::ItemIsDropEnabled; + +} + +QStringList Playlist::mimeTypes() const { + + return QStringList() << "text/uri-list" << kRowsMimetype; + +} + +Qt::DropActions Playlist::supportedDropActions() const { + return Qt::MoveAction | Qt::CopyAction | Qt::LinkAction; +} + +bool Playlist::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int, const QModelIndex&) { + + if (action == Qt::IgnoreAction) return false; + + bool play_now = false; + bool enqueue_now = false; + + if (const MimeData *mime_data = qobject_cast(data)) { + if (mime_data->clear_first_) { + Clear(); + } + play_now = mime_data->play_now_; + enqueue_now = mime_data->enqueue_now_; + } + + if (const SongMimeData *song_data = qobject_cast(data)) { + // Dragged from a collection + // We want to check if these songs are from the actual local file backend, + // if they are we treat them differently. + if (song_data->backend && song_data->backend->songs_table() == Collection::kSongsTable) + InsertSongItems(song_data->songs, row, play_now, enqueue_now); + else + InsertSongItems(song_data->songs, row, play_now, enqueue_now); + + } + else if (const PlaylistItemMimeData *item_data = qobject_cast(data)) { + InsertItems(item_data->items_, row, play_now, enqueue_now); + } + else if (data->hasFormat(kRowsMimetype)) { + // Dragged from the playlist + // Rearranging it is tricky... + + // Get the list of rows that were moved + QList source_rows; + Playlist *source_playlist = nullptr; + qint64 pid = 0; + qint64 own_pid = QCoreApplication::applicationPid(); + + QDataStream stream(data->data(kRowsMimetype)); + stream.readRawData(reinterpret_cast(&source_playlist), sizeof(source_playlist)); + stream >> source_rows; + if (!stream.atEnd()) { + stream.readRawData((char*)&pid, sizeof(pid)); + } + else { + pid = !own_pid; + } + + qStableSort(source_rows); // Make sure we take them in order + + if (source_playlist == this) { + // Dragged from this playlist - rearrange the items + undo_stack_->push(new PlaylistUndoCommands::MoveItems(this, source_rows, row)); + } + else if (pid == own_pid) { + // Drag from a different playlist + PlaylistItemList items; + for (int row : source_rows) items << source_playlist->item_at(row); + + if (items.count() > kUndoItemLimit) { + // Too big to keep in the undo stack. Also clear the stack because it + // might have been invalidated. + InsertItemsWithoutUndo(items, row, false); + undo_stack_->clear(); + } + else { + undo_stack_->push(new PlaylistUndoCommands::InsertItems(this, items, row)); + } + + // Remove the items from the source playlist if it was a move event + if (action == Qt::MoveAction) { + for (int row : source_rows) { + source_playlist->undo_stack()->push(new PlaylistUndoCommands::RemoveItems(source_playlist, row, 1)); + } + } + } + } + else if (data->hasFormat(kCddaMimeType)) { + SongLoaderInserter *inserter = new SongLoaderInserter(task_manager_, collection_, backend_->app()->player()); + connect(inserter, SIGNAL(Error(QString)), SIGNAL(Error(QString))); + inserter->LoadAudioCD(this, row, play_now, enqueue_now); + } + else if (data->hasUrls()) { + // URL list dragged from the file list or some other app + InsertUrls(data->urls(), row, play_now, enqueue_now); + } + + return true; + +} + +void Playlist::InsertUrls(const QList &urls, int pos, bool play_now, bool enqueue) { + + SongLoaderInserter *inserter = new SongLoaderInserter(task_manager_, collection_, backend_->app()->player()); + connect(inserter, SIGNAL(Error(QString)), SIGNAL(Error(QString))); + + inserter->Load(this, pos, play_now, enqueue, urls); + +} + +void Playlist::MoveItemWithoutUndo(int source, int dest) { + MoveItemsWithoutUndo(QList() << source, dest); +} + +void Playlist::MoveItemsWithoutUndo(const QList &source_rows, int pos) { + + layoutAboutToBeChanged(); + PlaylistItemList moved_items; + + if (pos < 0) { + pos = items_.count(); + } + + // Take the items out of the list first, keeping track of whether the + // insertion point changes + int offset = 0; + int start = pos; + for (int source_row : source_rows) { + moved_items << items_.takeAt(source_row - offset); + if (pos > source_row) { + start--; + } + offset++; + } + + // Put the items back in + for (int i = start; i < start + moved_items.count(); ++i) { + moved_items[i - start]->RemoveForegroundColor(kDynamicHistoryPriority); + items_.insert(i, moved_items[i - start]); + } + + // Update persistent indexes + for (const QModelIndex &pidx : persistentIndexList()) { + const int dest_offset = source_rows.indexOf(pidx.row()); + if (dest_offset != -1) { + // This index was moved + changePersistentIndex(pidx, index(start + dest_offset, pidx.column(), QModelIndex())); + } else { + int d = 0; + for (int source_row : source_rows) { + if (pidx.row() > source_row) d--; + } + if (pidx.row() + d >= start) d += source_rows.count(); + + changePersistentIndex(pidx, index(pidx.row() + d, pidx.column(), QModelIndex())); + } + } + current_virtual_index_ = virtual_items_.indexOf(current_row()); + + layoutChanged(); + Save(); + +} + +void Playlist::MoveItemsWithoutUndo(int start, const QList &dest_rows) { + + layoutAboutToBeChanged(); + PlaylistItemList moved_items; + + int pos = start; + for (int dest_row : dest_rows) { + if (dest_row < pos) start--; + } + + if (start < 0) { + start = items_.count() - dest_rows.count(); + } + + // Take the items out of the list first + for (int i = 0; i < dest_rows.count(); i++) + moved_items << items_.takeAt(start); + + // Put the items back in + int offset = 0; + for (int dest_row : dest_rows) { + items_.insert(dest_row, moved_items[offset]); + offset++; + } + + // Update persistent indexes + for (const QModelIndex &pidx : persistentIndexList()) { + if (pidx.row() >= start && pidx.row() < start + dest_rows.count()) { + // This index was moved + const int i = pidx.row() - start; + changePersistentIndex(pidx, index(dest_rows[i], pidx.column(), QModelIndex())); + } else { + int d = 0; + if (pidx.row() >= start + dest_rows.count()) + d -= dest_rows.count(); + + for (int dest_row : dest_rows) { + if (pidx.row() + d > dest_row) d++; + } + + changePersistentIndex(pidx, index(pidx.row() + d, pidx.column(), QModelIndex())); + } + } + current_virtual_index_ = virtual_items_.indexOf(current_row()); + + layoutChanged(); + Save(); + +} + +void Playlist::InsertItems(const PlaylistItemList &itemsIn, int pos, bool play_now, bool enqueue) { + + if (itemsIn.isEmpty()) + return; + + PlaylistItemList items = itemsIn; + + // exercise vetoes + SongList songs; + + for (PlaylistItemPtr item : items) { + songs << item->Metadata(); + } + + const int song_count = songs.length(); + QSet vetoed; + for (SongInsertVetoListener *listener : veto_listeners_) { + for (const Song &song : + listener->AboutToInsertSongs(GetAllSongs(), songs)) { + // avoid veto-ing a song multiple times + vetoed.insert(song); + } + if (vetoed.count() == song_count) { + // all songs were vetoed and there's nothing more to do (there's no need for an undo step) + return; + } + } + + if (!vetoed.isEmpty()) { + QMutableListIterator it(items); + while (it.hasNext()) { + PlaylistItemPtr item = it.next(); + const Song ¤t = item->Metadata(); + + if (vetoed.contains(current)) { + vetoed.remove(current); + it.remove(); + } + } + + // check for empty items once again after veto + if (items.isEmpty()) { + return; + } + } + + const int start = pos == -1 ? items_.count() : pos; + + if (items.count() > kUndoItemLimit) { + // Too big to keep in the undo stack. Also clear the stack because it + // might have been invalidated. + InsertItemsWithoutUndo(items, pos, enqueue); + undo_stack_->clear(); + } else { + undo_stack_->push(new PlaylistUndoCommands::InsertItems(this, items, pos, enqueue)); + } + + if (play_now) emit PlayRequested(index(start, 0)); + +} + +void Playlist::InsertItemsWithoutUndo(const PlaylistItemList &items, int pos, bool enqueue) { + + if (items.isEmpty()) return; + + const int start = pos == -1 ? items_.count() : pos; + const int end = start + items.count() - 1; + + beginInsertRows(QModelIndex(), start, end); + for (int i = start; i <= end; ++i) { + PlaylistItemPtr item = items[i - start]; + items_.insert(i, item); + virtual_items_ << virtual_items_.count(); + + if (item->type() == "Collection") { + int id = item->Metadata().id(); + if (id != -1) { + collection_items_by_id_.insertMulti(id, item); + } + } + + if (item == current_item()) { + // It's one we removed before that got re-added through an undo + current_item_index_ = index(i, 0); + last_played_item_index_ = current_item_index_; + } + } + endInsertRows(); + + if (enqueue) { + QModelIndexList indexes; + for (int i = start; i <= end; ++i) { + indexes << index(i, 0); + } + queue_->ToggleTracks(indexes); + } + + Save(); + ReshuffleIndices(); + +} + +void Playlist::InsertCollectionItems(const SongList &songs, int pos, bool play_now, bool enqueue) { + InsertSongItems(songs, pos, play_now, enqueue); +} + +void Playlist::InsertSongs(const SongList &songs, int pos, bool play_now, bool enqueue) { + InsertSongItems(songs, pos, play_now, enqueue); +} + +void Playlist::InsertSongsOrCollectionItems(const SongList &songs, int pos, bool play_now, bool enqueue) { + + PlaylistItemList items; + for (const Song &song : songs) { + if (song.is_collection_song()) { + items << PlaylistItemPtr(new CollectionPlaylistItem(song)); + } else { + items << PlaylistItemPtr(new SongPlaylistItem(song)); + } + } + InsertItems(items, pos, play_now, enqueue); + +} + +void Playlist::UpdateItems(const SongList &songs) { + + qLog(Debug) << "Updating playlist with new tracks' info"; + // We first convert our songs list into a linked list (a 'real' list), + // because removals are faster with QLinkedList. + // Next, we walk through the list of playlist's items then the list of songs + // we want to update: if an item corresponds to the song (we rely on URL for + // this), we update the item with the new metadata, then we remove song from + // our list because we will not need to check it again. + // And we also update undo actions. + QLinkedList songs_list; + for (const Song &song : songs) songs_list.append(song); + + for (int i = 0; i < items_.size(); i++) { + // Update current items list + QMutableLinkedListIterator it(songs_list); + while (it.hasNext()) { + const Song &song = it.next(); + PlaylistItemPtr &item = items_[i]; + if (item->Metadata().url() == song.url() && + (item->Metadata().filetype() == Song::Type_Unknown || + // And CD tracks as well (tags are loaded in a second step) + item->Metadata().filetype() == Song::Type_Cdda)) { + PlaylistItemPtr new_item; + if (song.is_collection_song()) { + new_item = PlaylistItemPtr(new CollectionPlaylistItem(song)); + collection_items_by_id_.insertMulti(song.id(), new_item); + } else { + new_item = PlaylistItemPtr(new SongPlaylistItem(song)); + } + items_[i] = new_item; + emit dataChanged(index(i, 0), index(i, ColumnCount - 1)); + // Also update undo actions + for (int i = 0; i < undo_stack_->count(); i++) { + QUndoCommand *undo_action = const_cast(undo_stack_->command(i)); + PlaylistUndoCommands::InsertItems *undo_action_insert = dynamic_cast(undo_action); + if (undo_action_insert) { + bool found_and_updated = undo_action_insert->UpdateItem(new_item); + if (found_and_updated) break; + } + } + it.remove(); + break; + } + } + } + Save(); + +} + +QMimeData *Playlist::mimeData(const QModelIndexList &indexes) const { + + if (indexes.isEmpty()) return nullptr; + + // We only want one index per row, but we can't just take column 0 because + // the user might have hidden it. + const int first_column = indexes.first().column(); + + QMimeData *data = new QMimeData; + + QList urls; + QList rows; + for (const QModelIndex &index : indexes) { + if (index.column() != first_column) continue; + + urls << items_[index.row()]->Url(); + rows << index.row(); + } + + QBuffer buf; + buf.open(QIODevice::WriteOnly); + QDataStream stream(&buf); + + const Playlist *self = this; + const qint64 pid = QCoreApplication::applicationPid(); + + stream.writeRawData(reinterpret_cast(&self), sizeof(self)); + stream << rows; + stream.writeRawData((char*)&pid, sizeof(pid)); + buf.close(); + + data->setUrls(urls); + data->setData(kRowsMimetype, buf.data()); + + return data; + +} + +bool Playlist::CompareItems(int column, Qt::SortOrder order, shared_ptr _a, shared_ptr _b) { + + shared_ptr a = order == Qt::AscendingOrder ? _a : _b; + shared_ptr b = order == Qt::AscendingOrder ? _b : _a; + +#define cmp(field) return a->Metadata().field() < b->Metadata().field() +#define strcmp(field) return QString::localeAwareCompare(a->Metadata().field().toLower(), b->Metadata().field().toLower()) < 0; + + switch (column) { + + case Column_Title: strcmp(title); + case Column_Artist: strcmp(artist); + case Column_Album: strcmp(album); + case Column_Length: cmp(length_nanosec); + case Column_Track: cmp(track); + case Column_Disc: cmp(disc); + case Column_Year: cmp(year); + case Column_OriginalYear: cmp(originalyear); + case Column_Genre: strcmp(genre); + case Column_AlbumArtist: strcmp(playlist_albumartist); + case Column_Composer: strcmp(composer); + case Column_Performer: strcmp(performer); + case Column_Grouping: strcmp(grouping); + + case Column_PlayCount: cmp(playcount); + case Column_SkipCount: cmp(skipcount); + case Column_LastPlayed: cmp(lastplayed); + + case Column_Bitrate: cmp(bitrate); + case Column_Samplerate: cmp(samplerate); + case Column_Bitdepth: cmp(bitdepth); + case Column_SamplerateBitdepth: + return QString::localeAwareCompare(a->Metadata().SampleRateBitDepthToText().toLower(), b->Metadata().SampleRateBitDepthToText().toLower()) < 0; + case Column_Filename: + return (QString::localeAwareCompare(a->Url().path().toLower(), b->Url().path().toLower()) < 0); + case Column_BaseFilename: cmp(basefilename); + case Column_Filesize: cmp(filesize); + case Column_Filetype: cmp(filetype); + case Column_DateModified: cmp(mtime); + case Column_DateCreated: cmp(ctime); + + case Column_Comment: strcmp(comment); + //case Column_Source: cmp(url); + } + +#undef cmp +#undef strcmp + + return false; + +} + +bool Playlist::ComparePathDepths(Qt::SortOrder order, shared_ptr _a, shared_ptr _b) { + + shared_ptr a = order == Qt::AscendingOrder ? _a : _b; + shared_ptr b = order == Qt::AscendingOrder ? _b : _a; + + int a_dir_level = a->Url().path().count('/'); + int b_dir_level = b->Url().path().count('/'); + + return a_dir_level < b_dir_level; + +} + +QString Playlist::column_name(Column column) { + + switch (column) { + case Column_Title: return tr("Title"); + case Column_Artist: return tr("Artist"); + case Column_Album: return tr("Album"); + case Column_Track: return tr("Track"); + case Column_Disc: return tr("Disc"); + case Column_Length: return tr("Length"); + case Column_Year: return tr("Year"); + case Column_OriginalYear: return tr("Original year"); + case Column_Genre: return tr("Genre"); + case Column_AlbumArtist: return tr("Album artist"); + case Column_Composer: return tr("Composer"); + case Column_Performer: return tr("Performer"); + case Column_Grouping: return tr("Grouping"); + + case Column_PlayCount: return tr("Play count"); + case Column_SkipCount: return tr("Skip count"); + case Column_LastPlayed: return tr("Last played"); + + case Column_Samplerate: return tr("Sample rate"); + case Column_Bitdepth: return tr("Bit depth"); + case Column_SamplerateBitdepth: return tr("Sample rate B"); + case Column_Bitrate: return tr("Bit rate"); + + case Column_Filename: return tr("File name"); + case Column_BaseFilename: return tr("File name (without path)"); + case Column_Filesize: return tr("File size"); + case Column_Filetype: return tr("File type"); + case Column_DateModified: return tr("Date modified"); + case Column_DateCreated: return tr("Date created"); + + case Column_Comment: return tr("Comment"); + //case Column_Source: return tr("Source"); + default: return QString(); + } + return ""; + +} + +QString Playlist::abbreviated_column_name(Column column) { + + const QString &column_name = Playlist::column_name(column); + + switch (column) { + case Column_Disc: + case Column_PlayCount: + case Column_SkipCount: + case Column_Track: + return QString("%1#").arg(column_name[0]); + default: + return column_name; + } + return ""; + +} + +void Playlist::sort(int column, Qt::SortOrder order) { + + if (ignore_sorting_) return; + + PlaylistItemList new_items(items_); + PlaylistItemList::iterator begin = new_items.begin(); + + if (column == Column_Album) { + // When sorting by album, also take into account discs and tracks. + qStableSort(begin, new_items.end(), std::bind(&Playlist::CompareItems, Column_Track, order, _1, _2)); + qStableSort(begin, new_items.end(), std::bind(&Playlist::CompareItems, Column_Disc, order, _1, _2)); + qStableSort(begin, new_items.end(), std::bind(&Playlist::CompareItems, Column_Album, order, _1, _2)); + } + else if (column == Column_Filename) { + // When sorting by full paths we also expect a hierarchical order. This + // returns a breath-first ordering of paths. + qStableSort(begin, new_items.end(), std::bind(&Playlist::CompareItems, Column_Filename, order, _1, _2)); + qStableSort(begin, new_items.end(), std::bind(&Playlist::ComparePathDepths, order, _1, _2)); + } + else { + qStableSort(begin, new_items.end(), std::bind(&Playlist::CompareItems, column, order, _1, _2)); + } + + undo_stack_->push(new PlaylistUndoCommands::SortItems(this, column, order, new_items)); + + ReshuffleIndices(); + +} + +void Playlist::ReOrderWithoutUndo(const PlaylistItemList &new_items) { + + layoutAboutToBeChanged(); + + PlaylistItemList old_items = items_; + items_ = new_items; + + QMap new_rows; + for (int i = 0; i < new_items.length(); ++i) { + new_rows[new_items[i].get()] = i; + } + + for (const QModelIndex &idx : persistentIndexList()) { + const PlaylistItem *item = old_items[idx.row()].get(); + changePersistentIndex(idx, index(new_rows[item], idx.column(), idx.parent())); + } + + layoutChanged(); + + emit PlaylistChanged(); + Save(); + +} + +void Playlist::Playing() { SetCurrentIsPaused(false); } + +void Playlist::Paused() { SetCurrentIsPaused(true); } + +void Playlist::Stopped() { SetCurrentIsPaused(false); } + +void Playlist::SetCurrentIsPaused(bool paused) { + + if (paused == current_is_paused_) return; + + current_is_paused_ = paused; + + if (current_item_index_.isValid()) + dataChanged(index(current_item_index_.row(), 0), index(current_item_index_.row(), ColumnCount - 1)); +} + +void Playlist::Save() const { + if (!backend_ || is_loading_) return; + + backend_->SavePlaylistAsync(id_, items_, last_played_row()); + +} + +//namespace { +//typedef QFutureWatcher> PlaylistItemFutureWatcher; +//} + +void Playlist::Restore() { + + if (!backend_) return; + + items_.clear(); + virtual_items_.clear(); + collection_items_by_id_.clear(); + + cancel_restore_ = false; + QFuture> future = QtConcurrent::run(backend_, &PlaylistBackend::GetPlaylistItems, id_); + NewClosure(future, this, SLOT(ItemsLoaded(QFuture)), future); + +} + +void Playlist::ItemsLoaded(QFuture future) { + + if (cancel_restore_) return; + + PlaylistItemList items = future.result(); + + // backend returns empty elements for collection items which it couldn't + // match (because they got deleted); we don't need those + QMutableListIterator it(items); + while (it.hasNext()) { + PlaylistItemPtr item = it.next(); + + if (item->IsLocalCollectionItem() && item->Metadata().url().isEmpty()) { + it.remove(); + } + } + + is_loading_ = true; + InsertItems(items, 0); + is_loading_ = false; + + PlaylistBackend::Playlist p = backend_->GetPlaylist(id_); + + // the newly loaded list of items might be shorter than it was before so + // look out for a bad last_played index + last_played_item_index_ = p.last_played == -1 || p.last_played >= rowCount() ? QModelIndex() : index(p.last_played); + + emit RestoreFinished(); + + QSettings s; + s.beginGroup(kSettingsGroup); + + // should we gray out deleted songs asynchronously on startup? + if (s.value("greyoutdeleted", false).toBool()) { + QtConcurrent::run(this, &Playlist::InvalidateDeletedSongs); + } + +} + +static bool DescendingIntLessThan(int a, int b) { return a > b; } + +void Playlist::RemoveItemsWithoutUndo(const QList &indicesIn) { + + // Sort the indices descending because removing elements 'backwards' + // is easier - indices don't 'move' in the process. + QList indices = indicesIn; + qSort(indices.begin(), indices.end(), DescendingIntLessThan); + + for (int j = 0; j < indices.count(); j++) { + int beginning = indices[j], end = indices[j]; + + // Splits the indices into sequences. For example this: [1, 2, 4], + // will get split into [1, 2] and [4]. + while (j != indices.count() - 1 && indices[j] == indices[j + 1] + 1) { + beginning--; + j++; + } + + // Remove the current sequence. + removeRows(beginning, end - beginning + 1); + } + +} + +bool Playlist::removeRows(int row, int count, const QModelIndex &parent) { + + if (row < 0 || row >= items_.size() || row + count > items_.size()) { + return false; + } + + if (count > kUndoItemLimit) { + // Too big to keep in the undo stack. Also clear the stack because it + // might have been invalidated. + RemoveItemsWithoutUndo(row, count); + undo_stack_->clear(); + } + else if (parent == QModelIndex()) { + RemoveItemsWithoutUndo(row, count); + } + else { + undo_stack_->push(new PlaylistUndoCommands::RemoveItems(this, row, count)); + } + + return true; + +} + +bool Playlist::removeRows(QList &rows) { + + if (rows.isEmpty()) { + return false; + } + + // start from the end to be sure that indices won't 'move' during + // the removal process + qSort(rows.begin(), rows.end(), qGreater()); + + QList part; + while (!rows.isEmpty()) { + // we're splitting the input list into sequences of consecutive + // numbers + part.append(rows.takeFirst()); + while (!rows.isEmpty() && rows.first() == part.last() - 1) { + part.append(rows.takeFirst()); + } + + // and now we're removing the current sequence + if (!removeRows(part.last(), part.size())) { + return false; + } + + part.clear(); + } + + return true; + +} + +PlaylistItemList Playlist::RemoveItemsWithoutUndo(int row, int count) { + + if (row < 0 || row >= items_.size() || row + count > items_.size()) { + return PlaylistItemList(); + } + beginRemoveRows(QModelIndex(), row, row + count - 1); + + // Remove items + PlaylistItemList ret; + for (int i = 0; i < count; ++i) { + PlaylistItemPtr item(items_.takeAt(row)); + ret << item; + + if (item->type() == "Collection") { + int id = item->Metadata().id(); + if (id != -1) { + collection_items_by_id_.remove(id, item); + } + } + } + + endRemoveRows(); + + QList::iterator it = virtual_items_.begin(); + int i = 0; + while (it != virtual_items_.end()) { + if (*it >= items_.count()) + it = virtual_items_.erase(it); + else + ++it; + ++i; + } + + // Reset current_virtual_index_ + if (current_row() == -1) + if (row - 1 > 0 && row - 1 < items_.size()) { + current_virtual_index_ = virtual_items_.indexOf(row - 1); + } + else { + current_virtual_index_ = -1; + } + else + current_virtual_index_ = virtual_items_.indexOf(current_row()); + + Save(); + return ret; + +} + +void Playlist::StopAfter(int row) { + + QModelIndex old_stop_after = stop_after_; + + if ((stop_after_.isValid() && stop_after_.row() == row) || row == -1) + stop_after_ = QModelIndex(); + else + stop_after_ = index(row, 0); + + if (old_stop_after.isValid()) + emit dataChanged(old_stop_after, old_stop_after.sibling(old_stop_after.row(), ColumnCount - 1)); + if (stop_after_.isValid()) + emit dataChanged(stop_after_, stop_after_.sibling(stop_after_.row(), ColumnCount - 1)); + +} + +void Playlist::SetStreamMetadata(const QUrl &url, const Song &song) { + + //qLog(Debug) << "Setting metadata for" << url << "to" << song.artist() << song.title(); + + if (!current_item()) return; + + if (current_item()->Url() != url) return; + + // Don't update the metadata if it's only a minor change from before + if (current_item()->Metadata().artist() == song.artist() && current_item()->Metadata().title() == song.title()) return; + + current_item()->SetTemporaryMetadata(song); + + InformOfCurrentSongChange(); + +} + +void Playlist::ClearStreamMetadata() { + + if (!current_item()) return; + + current_item()->ClearTemporaryMetadata(); + + emit dataChanged(index(current_item_index_.row(), 0), index(current_item_index_.row(), ColumnCount-1)); + +} + +bool Playlist::stop_after_current() const { + + PlaylistSequence::RepeatMode repeat_mode = playlist_sequence_->repeat_mode(); + if (repeat_mode == PlaylistSequence::Repeat_OneByOne) { + return true; + } + + return stop_after_.isValid() && current_item_index_.isValid() && stop_after_.row() == current_item_index_.row(); + +} + +PlaylistItemPtr Playlist::current_item() const { + + // QList[] runs in constant time, so no need to cache current_item + if (current_item_index_.isValid() && current_item_index_.row() <= items_.length()) + return items_[current_item_index_.row()]; + return PlaylistItemPtr(); + +} + +PlaylistItem::Options Playlist::current_item_options() const { + if (!current_item()) return PlaylistItem::Default; + return current_item()->options(); +} + +Song Playlist::current_item_metadata() const { + if (!current_item()) return Song(); + return current_item()->Metadata(); +} + +void Playlist::Clear() { + + // If loading songs from session restore async, don't insert them + cancel_restore_ = true; + + const int count = items_.count(); + + if (count > kUndoItemLimit) { + // Too big to keep in the undo stack. Also clear the stack because it + // might have been invalidated. + RemoveItemsWithoutUndo(0, count); + undo_stack_->clear(); + } else { + undo_stack_->push(new PlaylistUndoCommands::RemoveItems(this, 0, count)); + } + + Save(); + +} + +void Playlist::RemoveItemsNotInQueue() { + + if (queue_->is_empty() && !current_item_index_.isValid()) { + RemoveItemsWithoutUndo(0, items_.count()); + return; + } + + int start = 0; + forever { + // Find a place to start - first row that isn't in the queue + forever { + if (start >= rowCount()) return; + if (!queue_->ContainsSourceRow(start) && current_row() != start) break; + start++; + } + + // Figure out how many rows to remove - keep going until we find a row + // that is in the queue + int count = 1; + forever { + if (start + count >= rowCount()) break; + if (queue_->ContainsSourceRow(start + count) || current_row() == start + count) break; + count++; + } + + RemoveItemsWithoutUndo(start, count); + start++; + } +} + +void Playlist::ReloadItems(const QList &rows) { + + for (int row : rows) { + PlaylistItemPtr item = item_at(row); + + item->Reload(); + + if (row == current_row()) { + InformOfCurrentSongChange(); + } + else { + emit dataChanged(index(row, 0), index(row, ColumnCount - 1)); + } + } + + Save(); +} + +void Playlist::AddSongInsertVetoListener(SongInsertVetoListener *listener) { + veto_listeners_.append(listener); + connect(listener, SIGNAL(destroyed()), this, SLOT(SongInsertVetoListenerDestroyed())); +} + +void Playlist::RemoveSongInsertVetoListener(SongInsertVetoListener *listener) { + disconnect(listener, SIGNAL(destroyed()), this, SLOT(SongInsertVetoListenerDestroyed())); + veto_listeners_.removeAll(listener); +} + +void Playlist::SongInsertVetoListenerDestroyed() { + veto_listeners_.removeAll(qobject_cast(sender())); +} + +void Playlist::Shuffle() { + + PlaylistItemList new_items(items_); + + int begin = 0; + + const int count = items_.count(); + for (int i = begin; i < count; ++i) { + int new_pos = i + (rand() % (count - i)); + + std::swap(new_items[i], new_items[new_pos]); + } + + undo_stack_->push(new PlaylistUndoCommands::ShuffleItems(this, new_items)); +} + +namespace { +bool AlbumShuffleComparator(const QMap &album_key_positions, const QMap &album_keys, int left, int right) { + + const int left_pos = album_key_positions[album_keys[left]]; + const int right_pos = album_key_positions[album_keys[right]]; + + if (left_pos == right_pos) return left < right; + return left_pos < right_pos; + +} +} + +void Playlist::ReshuffleIndices() { + + if (!playlist_sequence_) { + return; + } + + if (playlist_sequence_->shuffle_mode() == PlaylistSequence::Shuffle_Off) { + // No shuffling - sort the virtual item list normally. + std::sort(virtual_items_.begin(), virtual_items_.end()); + if (current_row() != -1) + current_virtual_index_ = virtual_items_.indexOf(current_row()); + return; + } + + // If the user is already playing a song, advance the begin iterator to + // only shuffle items that haven't been played yet. + QList::iterator begin = virtual_items_.begin(); + QList::iterator end = virtual_items_.end(); + if (current_virtual_index_ != -1) + std::advance(begin, current_virtual_index_ + 1); + + switch (playlist_sequence_->shuffle_mode()) { + case PlaylistSequence::Shuffle_Off: + // Handled above. + break; + + case PlaylistSequence::Shuffle_All: + case PlaylistSequence::Shuffle_InsideAlbum: + std::random_shuffle(begin, end); + break; + + case PlaylistSequence::Shuffle_Albums: { + QMap album_keys; // real index -> key + QSet album_key_set; // unique keys + + // Find all the unique albums in the playlist + for (QList::iterator it = begin; it != end; ++it) { + const int index = *it; + const QString key = items_[index]->Metadata().AlbumKey(); + album_keys[index] = key; + album_key_set << key; + } + + // Shuffle them + QStringList shuffled_album_keys = album_key_set.toList(); + std::random_shuffle(shuffled_album_keys.begin(), shuffled_album_keys.end()); + + // If the user is currently playing a song, force its album to be first + // Or if the song was not playing but it was selected, force its album + // to be first. + if (current_virtual_index_ != -1 || current_row() != -1) { + const QString key = items_[current_row()]->Metadata().AlbumKey(); + const int pos = shuffled_album_keys.indexOf(key); + if (pos >= 1) { + std::swap(shuffled_album_keys[0], shuffled_album_keys[pos]); + } + } + + // Create album key -> position mapping for fast lookup + QMap album_key_positions; + for (int i = 0; i < shuffled_album_keys.count(); ++i) { + album_key_positions[shuffled_album_keys[i]] = i; + } + + // Sort the virtual items + std::stable_sort(begin, end, std::bind(AlbumShuffleComparator, album_key_positions, album_keys, _1, _2)); + + break; + } + } + +} + +void Playlist::set_sequence(PlaylistSequence *v) { + + playlist_sequence_ = v; + connect(v, SIGNAL(ShuffleModeChanged(PlaylistSequence::ShuffleMode)), SLOT(ShuffleModeChanged(PlaylistSequence::ShuffleMode))); + + ShuffleModeChanged(v->shuffle_mode()); + +} + +QSortFilterProxyModel *Playlist::proxy() const { return proxy_; } + +SongList Playlist::GetAllSongs() const { + SongList ret; + for (PlaylistItemPtr item : items_) { + ret << item->Metadata(); + } + return ret; +} + +PlaylistItemList Playlist::GetAllItems() const { return items_; } + +quint64 Playlist::GetTotalLength() const { + quint64 ret = 0; + for (PlaylistItemPtr item : items_) { + quint64 length = item->Metadata().length_nanosec(); + if (length > 0) ret += length; + } + return ret; +} + +PlaylistItemList Playlist::collection_items_by_id(int id) const { + return collection_items_by_id_.values(id); +} + +void Playlist::TracksAboutToBeDequeued(const QModelIndex&, int begin, int end) { + for (int i = begin; i <= end; ++i) { + temp_dequeue_change_indexes_ << queue_->mapToSource(queue_->index(i, Column_Title)); + } +} + +void Playlist::TracksDequeued() { + for (const QModelIndex &index : temp_dequeue_change_indexes_) { + emit dataChanged(index, index); + } + temp_dequeue_change_indexes_.clear(); + emit QueueChanged(); +} + +void Playlist::TracksEnqueued(const QModelIndex&, int begin, int end) { + const QModelIndex &b = queue_->mapToSource(queue_->index(begin, Column_Title)); + const QModelIndex &e = queue_->mapToSource(queue_->index(end, Column_Title)); + emit dataChanged(b, e); +} + +void Playlist::QueueLayoutChanged() { + for (int i = 0; i < queue_->rowCount(); ++i) { + const QModelIndex &index = queue_->mapToSource(queue_->index(i, Column_Title)); + emit dataChanged(index, index); + } +} + +void Playlist::ItemChanged(PlaylistItemPtr item) { + for (int row = 0; row < items_.count(); ++row) { + if (items_[row] == item) { + emit dataChanged(index(row, 0), index(row, ColumnCount - 1)); + return; + } + } +} + +void Playlist::InformOfCurrentSongChange() { + + emit dataChanged(index(current_item_index_.row(), 0), index(current_item_index_.row(), ColumnCount - 1)); + + // if the song is invalid, we won't play it - there's no point in + // informing anybody about the change + const Song metadata(current_item_metadata()); + if (metadata.is_valid()) { + emit CurrentSongChanged(metadata); + } + +} + +void Playlist::InvalidateDeletedSongs() { + + QList invalidated_rows; + + for (int row = 0; row < items_.count(); ++row) { + PlaylistItemPtr item = items_[row]; + Song song = item->Metadata(); + + bool exists = QFile::exists(song.url().toLocalFile()); + + if (!exists && !item->HasForegroundColor(kInvalidSongPriority)) { + // gray out the song if it's not there + item->SetForegroundColor(kInvalidSongPriority, kInvalidSongColor); + invalidated_rows.append(row); + } + else if (exists && item->HasForegroundColor(kInvalidSongPriority)) { + item->RemoveForegroundColor(kInvalidSongPriority); + invalidated_rows.append(row); + } + } + + ReloadItems(invalidated_rows); + +} + +void Playlist::RemoveDeletedSongs() { + QList rows_to_remove; + + for (int row = 0; row < items_.count(); ++row) { + PlaylistItemPtr item = items_[row]; + Song song = item->Metadata(); + + if (!QFile::exists(song.url().toLocalFile())) { + rows_to_remove.append(row); + } + } + + removeRows(rows_to_remove); + +} + +struct SongSimilarHash { + long operator() (const Song &song) const { + return HashSimilar(song); + } +}; + +struct SongSimilarEqual { + long operator()(const Song &song1, const Song &song2) const { + return song1.IsSimilar(song2); + } +}; + +void Playlist::RemoveDuplicateSongs() { + + QList rows_to_remove; + unordered_map unique_songs; + + for (int row = 0; row < items_.count(); ++row) { + PlaylistItemPtr item = items_[row]; + const Song &song = item->Metadata(); + + bool found_duplicate = false; + + auto uniq_song_it = unique_songs.find(song); + if (uniq_song_it != unique_songs.end()) { + const Song &uniq_song = uniq_song_it->first; + + if (song.bitrate() > uniq_song.bitrate()) { + rows_to_remove.append(unique_songs[uniq_song]); + unique_songs.erase(uniq_song); + unique_songs.insert(std::make_pair(song, row)); + } + else { + rows_to_remove.append(row); + } + found_duplicate = true; + } + + if (!found_duplicate) { + unique_songs.insert(std::make_pair(song, row)); + } + } + + removeRows(rows_to_remove); + +} + +void Playlist::RemoveUnavailableSongs() { + + QList rows_to_remove; + for (int row = 0; row < items_.count(); ++row) { + PlaylistItemPtr item = items_[row]; + const Song &song = item->Metadata(); + + // check only local files + if (song.url().isLocalFile() && !QFile::exists(song.url().toLocalFile())) { + rows_to_remove.append(row); + } + } + + removeRows(rows_to_remove); + +} + +bool Playlist::ApplyValidityOnCurrentSong(const QUrl &url, bool valid) { + + PlaylistItemPtr current = current_item(); + + if (current) { + Song current_song = current->Metadata(); + + // if validity has changed, reload the item + if(!current_song.is_cdda() && current_song.url() == url && current_song.is_valid() != QFile::exists(current_song.url().toLocalFile())) { + ReloadItems(QList() << current_row()); + } + + // gray out the song if it's now broken; otherwise undo the gray color + if (valid) { + current->RemoveForegroundColor(kInvalidSongPriority); + } else { + current->SetForegroundColor(kInvalidSongPriority, kInvalidSongColor); + } + } + + return static_cast(current); + +} + +void Playlist::SetColumnAlignment(const ColumnAlignmentMap &alignment) { + column_alignments_ = alignment; +} + +void Playlist::SkipTracks(const QModelIndexList &source_indexes) { + + for (const QModelIndex &source_index : source_indexes) { + PlaylistItemPtr track_to_skip = item_at(source_index.row()); + track_to_skip->SetShouldSkip(!((track_to_skip)->GetShouldSkip())); + emit dataChanged(source_index, source_index); + } + +} + diff --git a/src/playlist/playlist.h b/src/playlist/playlist.h new file mode 100644 index 00000000..f3768f61 --- /dev/null +++ b/src/playlist/playlist.h @@ -0,0 +1,373 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLIST_H +#define PLAYLIST_H + +#include "config.h" + +#include +#include + +#include "playlistitem.h" +#include "playlistsequence.h" +#include "core/tagreaderclient.h" +#include "core/song.h" + +class CollectionBackend; +class PlaylistBackend; +class PlaylistFilter; +class Queue; +class TaskManager; + +class QSortFilterProxyModel; +class QUndoStack; + +namespace PlaylistUndoCommands { +class InsertItems; +class RemoveItems; +class MoveItems; +class ReOrderItems; +class SortItems; +class ShuffleItems; +} + +typedef QMap ColumnAlignmentMap; +Q_DECLARE_METATYPE(Qt::Alignment); +Q_DECLARE_METATYPE(ColumnAlignmentMap); + +// Objects that may prevent a song being added to the playlist. When there +// is something about to be inserted into it, Playlist notifies all of it's +// listeners about the fact and every one of them picks 'invalid' songs. +class SongInsertVetoListener : public QObject { + Q_OBJECT + + public: + // Listener returns a list of 'invalid' songs. 'old_songs' are songs that are + // currently in the playlist and 'new_songs' are the songs about to be added if + // nobody exercises a veto. + virtual SongList AboutToInsertSongs(const SongList& old_songs, const SongList& new_songs) = 0; +}; + +class Playlist : public QAbstractListModel { + Q_OBJECT + + friend class PlaylistUndoCommands::InsertItems; + friend class PlaylistUndoCommands::RemoveItems; + friend class PlaylistUndoCommands::MoveItems; + friend class PlaylistUndoCommands::ReOrderItems; + + public: + Playlist(PlaylistBackend *backend, TaskManager *task_manager, CollectionBackend *collection, int id, const QString& special_type = QString(), bool favorite = false, QObject *parent = nullptr); + ~Playlist(); + + void SkipTracks(const QModelIndexList& source_indexes); + + // Always add new columns to the end of this enum - the values are persisted + enum Column { + Column_Title = 0, + Column_Artist, + Column_Album, + Column_AlbumArtist, + Column_Performer, + Column_Composer, + Column_Year, + Column_OriginalYear, + Column_Track, + Column_Disc, + Column_Length, + Column_Genre, + Column_Samplerate, + Column_Bitdepth, + Column_SamplerateBitdepth, + Column_Bitrate, + Column_Filename, + Column_BaseFilename, + Column_Filesize, + Column_Filetype, + Column_DateCreated, + Column_DateModified, + Column_PlayCount, + Column_SkipCount, + Column_LastPlayed, + Column_Comment, + Column_Grouping, + ColumnCount + }; + + enum Role { + Role_IsCurrent = Qt::UserRole + 1, + Role_IsPaused, + Role_StopAfter, + Role_QueuePosition + }; + + enum Path { + Path_Automatic = 0, // Automatically select path type + Path_Absolute, // Always use absolute paths + Path_Relative, // Always use relative paths + Path_Ask_User, // Only used in preferences: to ask user which of the + // previous values he wants to use. + }; + + static const char *kCddaMimeType; + static const char *kRowsMimetype; + static const char *kPlayNowMimetype; + + static const int kInvalidSongPriority; + static const QRgb kInvalidSongColor; + + static const int kDynamicHistoryPriority; + static const QRgb kDynamicHistoryColor; + + static const char *kSettingsGroup; + + static const char *kPathType; + static const char *kWriteMetadata; + + static const int kUndoStackSize; + static const int kUndoItemLimit; + + static bool CompareItems(int column, Qt::SortOrder order, PlaylistItemPtr a, PlaylistItemPtr b); + + static QString column_name(Column column); + static QString abbreviated_column_name(Column column); + + static bool column_is_editable(Playlist::Column column); + static bool set_column_value(Song& song, Column column, const QVariant& value); + + // Persistence + void Save() const; + void Restore(); + + // Accessors + QSortFilterProxyModel *proxy() const; + Queue *queue() const { return queue_; } + + int id() const { return id_; } + const QString& ui_path() const { return ui_path_; } + void set_ui_path(const QString &path) { ui_path_ = path; } + bool is_favorite() const { return favorite_; } + void set_favorite(bool favorite) { favorite_ = favorite; } + + int current_row() const; + int last_played_row() const; + int next_row(bool ignore_repeat_track = false) const; + int previous_row(bool ignore_repeat_track = false) const; + + const QModelIndex current_index() const; + + bool stop_after_current() const; + + QString special_type() const { return special_type_; } + void set_special_type(const QString &v) { special_type_ = v; } + + const PlaylistItemPtr &item_at(int index) const { return items_[index]; } + const bool has_item_at(int index) const { return index >= 0 && index < rowCount(); } + + PlaylistItemPtr current_item() const; + + PlaylistItem::Options current_item_options() const; + Song current_item_metadata() const; + + PlaylistItemList collection_items_by_id(int id) const; + + SongList GetAllSongs() const; + PlaylistItemList GetAllItems() const; + quint64 GetTotalLength() const; // in seconds + + void set_sequence(PlaylistSequence *v); + PlaylistSequence *sequence() const { return playlist_sequence_; } + + QUndoStack *undo_stack() const { return undo_stack_; } + + // Changing the playlist + void InsertItems (const PlaylistItemList &items, int pos = -1, bool play_now = false, bool enqueue = false); + void InsertCollectionItems (const SongList &items, int pos = -1, bool play_now = false, bool enqueue = false); + void InsertSongs (const SongList &items, int pos = -1, bool play_now = false, bool enqueue = false); + void InsertSongsOrCollectionItems (const SongList &items, int pos = -1, bool play_now = false, bool enqueue = false); + + void ReshuffleIndices(); + + // If this playlist contains the current item, this method will apply the "valid" flag on it. + // If the "valid" flag is false, the song will be greyed out. Otherwise the grey color will + // be undone. + // If the song is a local file and it's valid but non existent or invalid but exists, the + // song will be reloaded to even out the situation because obviously something has changed. + // This returns true if this playlist had current item when the method was invoked. + bool ApplyValidityOnCurrentSong(const QUrl &url, bool valid); + // Grays out and reloads all deleted songs in all playlists. Also, "ungreys" those songs + // which were once deleted but now got restored somehow. + void InvalidateDeletedSongs(); + // Removes from the playlist all local files that don't exist anymore. + void RemoveDeletedSongs(); + + void StopAfter(int row); + void ReloadItems(const QList &rows); + void InformOfCurrentSongChange(); + + // Registers an object which will get notifications when new songs + // are about to be inserted into this playlist. + void AddSongInsertVetoListener(SongInsertVetoListener *listener); + // Unregisters a SongInsertVetoListener object. + void RemoveSongInsertVetoListener(SongInsertVetoListener *listener); + + // QAbstractListModel + int rowCount(const QModelIndex& = QModelIndex()) const { return items_.count(); } + int columnCount(const QModelIndex& = QModelIndex()) const { return ColumnCount; } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role); + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + QStringList mimeTypes() const; + Qt::DropActions supportedDropActions() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + void sort(int column, Qt::SortOrder order); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + + static bool ComparePathDepths(Qt::SortOrder, PlaylistItemPtr, PlaylistItemPtr); + + public slots: + void set_current_row(int index, bool is_stopping = false); + void Paused(); + void Playing(); + void Stopped(); + void IgnoreSorting(bool value) { ignore_sorting_ = value; } + + void ClearStreamMetadata(); + void SetStreamMetadata(const QUrl &url, const Song &song); + void ItemChanged(PlaylistItemPtr item); + void UpdateItems(const SongList &songs); + + void Clear(); + void RemoveDuplicateSongs(); + void RemoveUnavailableSongs(); + void Shuffle(); + + void ShuffleModeChanged(PlaylistSequence::ShuffleMode mode); + + void SetColumnAlignment(const ColumnAlignmentMap &alignment); + + void InsertUrls(const QList &urls, int pos = -1, bool play_now = false, bool enqueue = false); + // Removes items with given indices from the playlist. This operation is not undoable. + void RemoveItemsWithoutUndo(const QList &indices); + +signals: + void RestoreFinished(); + void CurrentSongChanged(const Song &metadata); + void EditingFinished(const QModelIndex &index); + void PlayRequested(const QModelIndex &index); + + // Signals that the underlying list of items was changed, meaning that + // something was added to it, removed from it or the ordering changed. + void PlaylistChanged(); + void DynamicModeChanged(bool dynamic); + + void Error(const QString &message); + + // Signals that the queue has changed, meaning that the remaining queued + // items should update their position. + void QueueChanged(); + +private: + void SetCurrentIsPaused(bool paused); + int NextVirtualIndex(int i, bool ignore_repeat_track) const; + int PreviousVirtualIndex(int i, bool ignore_repeat_track) const; + bool FilterContainsVirtualIndex(int i) const; + + template + void InsertSongItems(const SongList &songs, int pos, bool play_now, bool enqueue); + + // Modify the playlist without changing the undo stack. These are used by + // our friends in PlaylistUndoCommands + void InsertItemsWithoutUndo(const PlaylistItemList &items, int pos, bool enqueue = false); + PlaylistItemList RemoveItemsWithoutUndo(int pos, int count); + void MoveItemsWithoutUndo(const QList &source_rows, int pos); + void MoveItemWithoutUndo(int source, int dest); + void MoveItemsWithoutUndo(int start, const QList &dest_rows); + void ReOrderWithoutUndo(const PlaylistItemList &new_items); + + void RemoveItemsNotInQueue(); + + // Removes rows with given indices from this playlist. + bool removeRows(QList &rows); + + private slots: + void TracksAboutToBeDequeued(const QModelIndex&, int begin, int end); + void TracksDequeued(); + void TracksEnqueued(const QModelIndex&, int begin, int end); + void QueueLayoutChanged(); + void SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &index); + void ItemReloadComplete(const QPersistentModelIndex &index); + void ItemsLoaded(QFuture future); + void SongInsertVetoListenerDestroyed(); + +private: + bool is_loading_; + PlaylistFilter *proxy_; + Queue *queue_; + + QList temp_dequeue_change_indexes_; + + PlaylistBackend *backend_; + TaskManager *task_manager_; + CollectionBackend *collection_; + int id_; + QString ui_path_; + bool favorite_; + + PlaylistItemList items_; + QList virtual_items_; // Contains the indices into items_ in the order + // that they will be played. + // A map of collection ID to playlist item - for fast lookups when collection + // items change. + QMultiMap collection_items_by_id_; + + QPersistentModelIndex current_item_index_; + QPersistentModelIndex last_played_item_index_; + QPersistentModelIndex stop_after_; + bool current_is_paused_; + int current_virtual_index_; + + bool is_shuffled_; + + PlaylistSequence *playlist_sequence_; + + // Hack to stop QTreeView::setModel sorting the playlist + bool ignore_sorting_; + + QUndoStack *undo_stack_; + + ColumnAlignmentMap column_alignments_; + + QList veto_listeners_; + + QString special_type_; + + // Cancel async restore if songs are already replaced + bool cancel_restore_; +}; + +// QDataStream& operator <<(QDataStream&, const Playlist*); +// QDataStream& operator >>(QDataStream&, Playlist*&); + +#endif // PLAYLIST_H + diff --git a/src/playlist/playlistbackend.cpp b/src/playlist/playlistbackend.cpp new file mode 100644 index 00000000..8ef3a052 --- /dev/null +++ b/src/playlist/playlistbackend.cpp @@ -0,0 +1,449 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "playlistbackend.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "core/application.h" +#include "core/database.h" +#include "core/logging.h" +#include "core/scopedtransaction.h" +#include "core/song.h" +#include "collection/collectionbackend.h" +#include "collection/sqlrow.h" +#include "playlist/songplaylistitem.h" +#include "playlistparsers/cueparser.h" + +using std::placeholders::_1; +using std::shared_ptr; + +const int PlaylistBackend::kSongTableJoins = 2; + +PlaylistBackend::PlaylistBackend(Application* app, QObject* parent) + : QObject(parent), app_(app), db_(app_->database()) {} + +PlaylistBackend::PlaylistList PlaylistBackend::GetAllPlaylists() { + return GetPlaylists(GetPlaylists_All); +} + +PlaylistBackend::PlaylistList PlaylistBackend::GetAllOpenPlaylists() { + return GetPlaylists(GetPlaylists_OpenInUi); +} + +PlaylistBackend::PlaylistList PlaylistBackend::GetAllFavoritePlaylists() { + return GetPlaylists(GetPlaylists_Favorite); +} + +PlaylistBackend::PlaylistList PlaylistBackend::GetPlaylists(GetPlaylistsFlags flags) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + PlaylistList ret; + + QStringList condition_list; + if (flags & GetPlaylists_OpenInUi) { + condition_list << "ui_order != -1"; + } + if (flags & GetPlaylists_Favorite) { + condition_list << "is_favorite != 0"; + } + QString condition; + if (!condition_list.isEmpty()) { + condition = " WHERE " + condition_list.join(" OR "); + } + + QSqlQuery q(db); + q.prepare("SELECT ROWID, name, last_played, special_type, ui_path, is_favorite FROM playlists " + condition + " ORDER BY ui_order"); + q.exec(); + if (db_->CheckErrors(q)) return ret; + + while (q.next()) { + Playlist p; + p.id = q.value(0).toInt(); + p.name = q.value(1).toString(); + p.last_played = q.value(2).toInt(); + p.special_type = q.value(3).toString(); + p.ui_path = q.value(4).toString(); + p.favorite = q.value(5).toBool(); + ret << p; + } + + return ret; + +} + +PlaylistBackend::Playlist PlaylistBackend::GetPlaylist(int id) { + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + + QSqlQuery q(db); + q.prepare("SELECT ROWID, name, last_played, special_type, ui_path, is_favorite FROM playlists WHERE ROWID=:id"); + + q.bindValue(":id", id); + q.exec(); + if (db_->CheckErrors(q)) return Playlist(); + + q.next(); + + Playlist p; + p.id = q.value(0).toInt(); + p.name = q.value(1).toString(); + p.last_played = q.value(2).toInt(); + p.special_type = q.value(3).toString(); + p.ui_path = q.value(4).toString(); + p.favorite = q.value(5).toBool(); + + return p; +} + +QSqlQuery PlaylistBackend::GetPlaylistRows(int playlist) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + QString query = "SELECT songs.ROWID, " + Song::JoinSpec("songs") + + "," + " p.ROWID, " + + Song::JoinSpec("p") + + "," + " p.type" + " FROM playlist_items AS p" + " LEFT JOIN songs" + " ON p.collection_id = songs.ROWID" + " WHERE p.playlist = :playlist"; + QSqlQuery q(db); + // Forward iterations only may be faster + q.setForwardOnly(true); + q.prepare(query); + q.bindValue(":playlist", playlist); + q.exec(); + + return q; + +} + +QList PlaylistBackend::GetPlaylistItems(int playlist) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QSqlQuery q = GetPlaylistRows(playlist); + // Note that as this only accesses the query, not the db, we don't need the + // mutex. + if (db_->CheckErrors(q)) return QList(); + + // it's probable that we'll have a few songs associated with the + // same CUE so we're caching results of parsing CUEs + std::shared_ptr state_ptr(new NewSongFromQueryState()); + QList playlistitems; + while (q.next()) { + playlistitems << NewPlaylistItemFromQuery(SqlRow(q), state_ptr); + } + return playlistitems; + +} + +QList PlaylistBackend::GetPlaylistSongs(int playlist) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QSqlQuery q = GetPlaylistRows(playlist); + // Note that as this only accesses the query, not the db, we don't need the + // mutex. + if (db_->CheckErrors(q)) return QList(); + + // it's probable that we'll have a few songs associated with the + // same CUE so we're caching results of parsing CUEs + std::shared_ptr state_ptr(new NewSongFromQueryState()); + QList songs; + while (q.next()) { + songs << NewSongFromQuery(SqlRow(q), state_ptr); + } + return songs; + +} + +PlaylistItemPtr PlaylistBackend::NewPlaylistItemFromQuery(const SqlRow &row, std::shared_ptr state) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // The song tables get joined first, plus one each for the song ROWIDs + const int playlist_row = (Song::kColumns.count() + 1) * kSongTableJoins; + + //qLog(Debug) << row.value(playlist_row).toString(); + + PlaylistItemPtr item(PlaylistItem::NewFromType(row.value(playlist_row).toString())); + if (item) { + item->InitFromQuery(row); + return RestoreCueData(item, state); + } + else { + return item; + } + +} + +Song PlaylistBackend::NewSongFromQuery(const SqlRow &row, std::shared_ptr state) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + return NewPlaylistItemFromQuery(row, state)->Metadata(); + +} + +// If song had a CUE and the CUE still exists, the metadata from it will be applied here. + +PlaylistItemPtr PlaylistBackend::RestoreCueData(PlaylistItemPtr item, std::shared_ptr state) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + // we need collection to run a CueParser; also, this method applies only to file-type PlaylistItems + if (item->type() != "File") return item; + + CueParser cue_parser(app_->collection_backend()); + + Song song = item->Metadata(); + // we're only interested in .cue songs here + if (!song.has_cue()) return item; + + QString cue_path = song.cue_path(); + // if .cue was deleted - reload the song + if (!QFile::exists(cue_path)) { + item->Reload(); + return item; + } + + SongList song_list; + { + QMutexLocker locker(&state->mutex_); + + if (!state->cached_cues_.contains(cue_path)) { + QFile cue(cue_path); + cue.open(QIODevice::ReadOnly); + + song_list = cue_parser.Load(&cue, cue_path, QDir(cue_path.section('/', 0, -2))); + state->cached_cues_[cue_path] = song_list; + } else { + song_list = state->cached_cues_[cue_path]; + } + } + + for (const Song& from_list : song_list) { + if (from_list.url().toEncoded() == song.url().toEncoded() && + from_list.beginning_nanosec() == song.beginning_nanosec()) { + // we found a matching section; replace the input + // item with a new one containing CUE metadata + return PlaylistItemPtr(new SongPlaylistItem(from_list)); + } + } + + // there's no such section in the related .cue -> reload the song + item->Reload(); + return item; + +} + +void PlaylistBackend::SavePlaylistAsync(int playlist, const PlaylistItemList &items, int last_played) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + metaObject()->invokeMethod(this, "SavePlaylist", Qt::QueuedConnection, Q_ARG(int, playlist), Q_ARG(PlaylistItemList, items), Q_ARG(int, last_played)); + +} + +void PlaylistBackend::SavePlaylist(int playlist, const PlaylistItemList& items, int last_played) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + qLog(Debug) << "Saving playlist" << playlist; + + QSqlQuery clear(db); + clear.prepare("DELETE FROM playlist_items WHERE playlist = :playlist"); + QSqlQuery insert(db); + insert.prepare("INSERT INTO playlist_items (playlist, type, collection_id, " + Song::kColumnSpec + ") VALUES (:playlist, :type, :collection_id, " + Song::kBindSpec + ")"); + QSqlQuery update(db); + update.prepare("UPDATE playlists SET last_played=:last_played WHERE ROWID=:playlist"); + + ScopedTransaction transaction(&db); + + // Clear the existing items in the playlist + clear.bindValue(":playlist", playlist); + clear.exec(); + if (db_->CheckErrors(clear)) return; + + // Save the new ones + for (PlaylistItemPtr item : items) { + insert.bindValue(":playlist", playlist); + item->BindToQuery(&insert); + + insert.exec(); + db_->CheckErrors(insert); + } + + // Update the last played track number + update.bindValue(":last_played", last_played); + update.bindValue(":playlist", playlist); + update.exec(); + if (db_->CheckErrors(update)) return; + + transaction.Commit(); + +} + +int PlaylistBackend::CreatePlaylist(const QString& name, const QString& special_type) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + + QSqlQuery q(db); + q.prepare("INSERT INTO playlists (name, special_type) VALUES (:name, :special_type)"); + q.bindValue(":name", name); + q.bindValue(":special_type", special_type); + q.exec(); + if (db_->CheckErrors(q)) return -1; + + return q.lastInsertId().toInt(); + +} + +void PlaylistBackend::RemovePlaylist(int id) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + QSqlQuery delete_playlist(db); + delete_playlist.prepare("DELETE FROM playlists WHERE ROWID=:id"); + QSqlQuery delete_items(db); + delete_items.prepare("DELETE FROM playlist_items WHERE playlist=:id"); + + delete_playlist.bindValue(":id", id); + delete_items.bindValue(":id", id); + + ScopedTransaction transaction(&db); + + delete_playlist.exec(); + if (db_->CheckErrors(delete_playlist)) return; + + delete_items.exec(); + if (db_->CheckErrors(delete_items)) return; + + transaction.Commit(); + +} + +void PlaylistBackend::RenamePlaylist(int id, const QString &new_name) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + QSqlQuery q(db); + q.prepare("UPDATE playlists SET name=:name WHERE ROWID=:id"); + q.bindValue(":name", new_name); + q.bindValue(":id", id); + + q.exec(); + db_->CheckErrors(q); + +} + +void PlaylistBackend::FavoritePlaylist(int id, bool is_favorite) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + QSqlQuery q(db); + q.prepare("UPDATE playlists SET is_favorite=:is_favorite WHERE ROWID=:id"); + q.bindValue(":is_favorite", is_favorite ? 1 : 0); + q.bindValue(":id", id); + + q.exec(); + db_->CheckErrors(q); + +} + +void PlaylistBackend::SetPlaylistOrder(const QList &ids) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + ScopedTransaction transaction(&db); + + QSqlQuery q(db); + q.prepare("UPDATE playlists SET ui_order=-1"); + q.exec(); + if (db_->CheckErrors(q)) return; + + q.prepare("UPDATE playlists SET ui_order=:index WHERE ROWID=:id"); + for (int i = 0; i < ids.count(); ++i) { + q.bindValue(":index", i); + q.bindValue(":id", ids[i]); + q.exec(); + if (db_->CheckErrors(q)) return; + } + + transaction.Commit(); + +} + +void PlaylistBackend::SetPlaylistUiPath(int id, const QString &path) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QMutexLocker l(db_->Mutex()); + QSqlDatabase db(db_->Connect()); + QSqlQuery q(db); + q.prepare("UPDATE playlists SET ui_path=:path WHERE ROWID=:id"); + + ScopedTransaction transaction(&db); + + q.bindValue(":path", path); + q.bindValue(":id", id); + q.exec(); + if (db_->CheckErrors(q)) return; + + transaction.Commit(); + +} + diff --git a/src/playlist/playlistbackend.h b/src/playlist/playlistbackend.h new file mode 100644 index 00000000..8b16ff99 --- /dev/null +++ b/src/playlist/playlistbackend.h @@ -0,0 +1,104 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTBACKEND_H +#define PLAYLISTBACKEND_H + +#include "config.h" + +#include +#include +#include +#include + +#include "playlistitem.h" + +class Application; +class Database; + +class PlaylistBackend : public QObject { + Q_OBJECT + + public: + Q_INVOKABLE PlaylistBackend(Application *app, QObject *parent = nullptr); + + struct Playlist { + Playlist() : id(-1), favorite(false), last_played(0) {} + + int id; + QString name; + QString ui_path; + bool favorite; + int last_played; + + // Special playlists have different behaviour, eg. the "spotify-search" + // type has a spotify search box at the top, replacing the ordinary filter. + QString special_type; + }; + typedef QList PlaylistList; + + static const int kSongTableJoins; + + PlaylistList GetAllPlaylists(); + PlaylistList GetAllOpenPlaylists(); + PlaylistList GetAllFavoritePlaylists(); + PlaylistBackend::Playlist GetPlaylist(int id); + + QList GetPlaylistItems(int playlist); + QList GetPlaylistSongs(int playlist); + + void SetPlaylistOrder(const QList &ids); + void SetPlaylistUiPath(int id, const QString &path); + + int CreatePlaylist(const QString &name, const QString &special_type); + void SavePlaylistAsync(int playlist, const PlaylistItemList &items, int last_played); + void RenamePlaylist(int id, const QString &new_name); + void FavoritePlaylist(int id, bool is_favorite); + void RemovePlaylist(int id); + + Application *app() const { return app_; } + + public slots: + void SavePlaylist(int playlist, const PlaylistItemList &items, int last_played); + + private: + struct NewSongFromQueryState { + QHash cached_cues_; + QMutex mutex_; + }; + + QSqlQuery GetPlaylistRows(int playlist); + + Song NewSongFromQuery(const SqlRow &row, std::shared_ptr state); + PlaylistItemPtr NewPlaylistItemFromQuery(const SqlRow &row, std::shared_ptr state); + PlaylistItemPtr RestoreCueData(PlaylistItemPtr item, std::shared_ptr state); + + enum GetPlaylistsFlags { + GetPlaylists_OpenInUi = 1, + GetPlaylists_Favorite = 2, + GetPlaylists_All = GetPlaylists_OpenInUi | GetPlaylists_Favorite + }; + PlaylistList GetPlaylists(GetPlaylistsFlags flags); + + Application *app_; + Database *db_; +}; + +#endif // PLAYLISTBACKEND_H diff --git a/src/playlist/playlistcontainer.cpp b/src/playlist/playlistcontainer.cpp new file mode 100644 index 00000000..ef1dcde8 --- /dev/null +++ b/src/playlist/playlistcontainer.cpp @@ -0,0 +1,448 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "playlistcontainer.h" +#include "playlistmanager.h" +#include "ui_playlistcontainer.h" +#include "core/logging.h" +#include "core/iconloader.h" +#include "playlistparsers/playlistparser.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const char *PlaylistContainer::kSettingsGroup = "Playlist"; +const int PlaylistContainer::kFilterDelayMs = 100; +const int PlaylistContainer::kFilterDelayPlaylistSizeThreshold = 5000; + +PlaylistContainer::PlaylistContainer(QWidget *parent) + : QWidget(parent), + ui_(new Ui_PlaylistContainer), + manager_(nullptr), + undo_(nullptr), + redo_(nullptr), + playlist_(nullptr), + starting_up_(true), + tab_bar_visible_(false), + tab_bar_animation_(new QTimeLine(500, this)), + no_matches_label_(nullptr), + filter_timer_(new QTimer(this)) { + ui_->setupUi(this); + + no_matches_label_ = new QLabel(ui_->playlist); + no_matches_label_->setAlignment(Qt::AlignTop | Qt::AlignHCenter); + no_matches_label_->setAttribute(Qt::WA_TransparentForMouseEvents); + no_matches_label_->setWordWrap(true); + no_matches_label_->raise(); + no_matches_label_->hide(); + + // Set the colour of the no matches label to the disabled text colour + QPalette no_matches_palette = no_matches_label_->palette(); + const QColor no_matches_color = no_matches_palette.color(QPalette::Disabled, QPalette::Text); + no_matches_palette.setColor(QPalette::Normal, QPalette::WindowText, no_matches_color); + no_matches_palette.setColor(QPalette::Inactive, QPalette::WindowText, no_matches_color); + no_matches_label_->setPalette(no_matches_palette); + + // Remove QFrame border + ui_->toolbar->setStyleSheet("QFrame { border: 0px; }"); + + // Make it bold + QFont no_matches_font = no_matches_label_->font(); + no_matches_font.setBold(true); + no_matches_label_->setFont(no_matches_font); + + settings_.beginGroup(kSettingsGroup); + + // Tab bar + ui_->tab_bar->setExpanding(false); + ui_->tab_bar->setMovable(true); + + connect(tab_bar_animation_, SIGNAL(frameChanged(int)), SLOT(SetTabBarHeight(int))); + ui_->tab_bar->setMaximumHeight(0); + + // Connections + connect(ui_->tab_bar, SIGNAL(currentChanged(int)), SLOT(Save())); + connect(ui_->tab_bar, SIGNAL(Save(int)), SLOT(SavePlaylist(int))); + + // set up timer for delayed filter updates + filter_timer_->setSingleShot(true); + filter_timer_->setInterval(kFilterDelayMs); + connect(filter_timer_, SIGNAL(timeout()), this, SLOT(UpdateFilter())); + + // Replace playlist search filter with native search box. + connect(ui_->filter, SIGNAL(textChanged(QString)), SLOT(MaybeUpdateFilter())); + connect(ui_->playlist, SIGNAL(FocusOnFilterSignal(QKeyEvent*)), SLOT(FocusOnFilter(QKeyEvent*))); + ui_->filter->installEventFilter(this); + +} + +PlaylistContainer::~PlaylistContainer() { delete ui_; } + +PlaylistView *PlaylistContainer::view() const { return ui_->playlist; } + +void PlaylistContainer::SetActions(QAction *new_playlist, QAction *load_playlist, QAction *save_playlist, QAction *clear_playlist, QAction *next_playlist, QAction *previous_playlist) { + + ui_->create_new->setDefaultAction(new_playlist); + ui_->load->setDefaultAction(load_playlist); + ui_->save->setDefaultAction(save_playlist); + ui_->clear->setDefaultAction(clear_playlist); + + ui_->tab_bar->SetActions(new_playlist, load_playlist); + + connect(new_playlist, SIGNAL(triggered()), SLOT(NewPlaylist())); + connect(save_playlist, SIGNAL(triggered()), SLOT(SavePlaylist())); + connect(load_playlist, SIGNAL(triggered()), SLOT(LoadPlaylist())); + connect(clear_playlist, SIGNAL(triggered()), SLOT(ClearPlaylist())); + connect(next_playlist, SIGNAL(triggered()), SLOT(GoToNextPlaylistTab())); + connect(previous_playlist, SIGNAL(triggered()), SLOT(GoToPreviousPlaylistTab())); + connect(clear_playlist, SIGNAL(triggered()), SLOT(ClearPlaylist())); + +} + +void PlaylistContainer::SetManager(PlaylistManager *manager) { + + manager_ = manager; + ui_->tab_bar->SetManager(manager); + + connect(ui_->tab_bar, SIGNAL(CurrentIdChanged(int)), manager, SLOT(SetCurrentPlaylist(int))); + connect(ui_->tab_bar, SIGNAL(Rename(int, QString)), manager, SLOT(Rename(int, QString))); + connect(ui_->tab_bar, SIGNAL(Close(int)), manager, SLOT(Close(int))); + connect(ui_->tab_bar, SIGNAL(PlaylistFavorited(int, bool)), manager, SLOT(Favorite(int, bool))); + + connect(ui_->tab_bar, SIGNAL(PlaylistOrderChanged(QList)), manager, SLOT(ChangePlaylistOrder(QList))); + + connect(manager, SIGNAL(CurrentChanged(Playlist*)), SLOT(SetViewModel(Playlist*))); + connect(manager, SIGNAL(PlaylistAdded(int, QString, bool)), SLOT(PlaylistAdded(int, QString, bool))); + connect(manager, SIGNAL(PlaylistManagerInitialized()), SLOT(Started())); + connect(manager, SIGNAL(PlaylistClosed(int)), SLOT(PlaylistClosed(int))); + connect(manager, SIGNAL(PlaylistRenamed(int, QString)), SLOT(PlaylistRenamed(int, QString))); + +} + +void PlaylistContainer::SetViewModel(Playlist *playlist) { + + if (view()->selectionModel()) { + disconnect(view()->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(SelectionChanged())); + } + if (playlist_ && playlist_->proxy()) { + disconnect(playlist_->proxy(), SIGNAL(modelReset()), this, SLOT(UpdateNoMatchesLabel())); + disconnect(playlist_->proxy(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(UpdateNoMatchesLabel())); + disconnect(playlist_->proxy(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(UpdateNoMatchesLabel())); + } + if (playlist_) { + disconnect(playlist_, SIGNAL(modelReset()), this, SLOT(UpdateNoMatchesLabel())); + disconnect(playlist_, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(UpdateNoMatchesLabel())); + disconnect(playlist_, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(UpdateNoMatchesLabel())); + } + + playlist_ = playlist; + + // Set the view + playlist->IgnoreSorting(true); + view()->setModel(playlist->proxy()); + view()->SetItemDelegates(manager_->collection_backend()); + view()->SetPlaylist(playlist); + view()->selectionModel()->select(manager_->current_selection(), QItemSelectionModel::ClearAndSelect); + playlist->IgnoreSorting(false); + + connect(view()->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(SelectionChanged())); + emit ViewSelectionModelChanged(); + + // Update filter + ui_->filter->setText(playlist->proxy()->filterRegExp().pattern()); + + // Update the no matches label + connect(playlist_->proxy(), SIGNAL(modelReset()), SLOT(UpdateNoMatchesLabel())); + connect(playlist_->proxy(), SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(UpdateNoMatchesLabel())); + connect(playlist_->proxy(), SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(UpdateNoMatchesLabel())); + connect(playlist_, SIGNAL(modelReset()), SLOT(UpdateNoMatchesLabel())); + connect(playlist_, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(UpdateNoMatchesLabel())); + connect(playlist_, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(UpdateNoMatchesLabel())); + UpdateNoMatchesLabel(); + + // Ensure that tab is current + if (ui_->tab_bar->current_id() != manager_->current_id()) + ui_->tab_bar->set_current_id(manager_->current_id()); + + // Sort out the undo/redo actions + delete undo_; + delete redo_; + undo_ = playlist->undo_stack()->createUndoAction(this); + redo_ = playlist->undo_stack()->createRedoAction(this); + undo_->setIcon(IconLoader::Load("edit-undo")); + undo_->setShortcut(QKeySequence::Undo); + redo_->setIcon(IconLoader::Load("edit-redo")); + redo_->setShortcut(QKeySequence::Redo); + + ui_->undo->setDefaultAction(undo_); + ui_->redo->setDefaultAction(redo_); + + emit UndoRedoActionsChanged(undo_, redo_); + +} + +void PlaylistContainer::ActivePlaying() { + UpdateActiveIcon(QIcon(":/pictures/tiny-play.png")); +} + +void PlaylistContainer::ActivePaused() { + UpdateActiveIcon(QIcon(":/pictures/tiny-pause.png")); +} + +void PlaylistContainer::ActiveStopped() { UpdateActiveIcon(QIcon()); } + +void PlaylistContainer::UpdateActiveIcon(const QIcon &icon) { + + // Unset all existing icons + for (int i = 0; i < ui_->tab_bar->count(); ++i) { + ui_->tab_bar->setTabIcon(i, QIcon()); + } + + // Set our icon + if (!icon.isNull()) ui_->tab_bar->set_icon_by_id(manager_->active_id(), icon); +} + +void PlaylistContainer::PlaylistAdded(int id, const QString &name, bool favorite) { + + const int index = ui_->tab_bar->count(); + ui_->tab_bar->InsertTab(id, index, name, favorite); + + // Are we startup up, should we select this tab? + if (starting_up_ && settings_.value("current_playlist", 1).toInt() == id) { + starting_up_ = false; + ui_->tab_bar->set_current_id(id); + } + + if (ui_->tab_bar->count() > 1) { + // Have to do this here because sizeHint() is only valid when there's a + // tab in the bar. + tab_bar_animation_->setFrameRange(0, ui_->tab_bar->sizeHint().height()); + + if (!isVisible()) { + // Skip the animation since the window is hidden (eg. if we're still + // loading the UI). + tab_bar_visible_ = true; + ui_->tab_bar->setMaximumHeight(tab_bar_animation_->endFrame()); + } else { + SetTabBarVisible(true); + } + } +} + +void PlaylistContainer::Started() { starting_up_ = false; } + +void PlaylistContainer::PlaylistClosed(int id) { + ui_->tab_bar->RemoveTab(id); + + if (ui_->tab_bar->count() <= 1) SetTabBarVisible(false); +} + +void PlaylistContainer::PlaylistRenamed(int id, const QString &new_name) { + ui_->tab_bar->set_text_by_id(id, new_name); +} + +void PlaylistContainer::NewPlaylist() { manager_->New(tr("Playlist")); } + +void PlaylistContainer::LoadPlaylist() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QString filename = settings_.value("last_load_playlist").toString(); + filename = QFileDialog::getOpenFileName(this, tr("Load playlist"), filename, manager_->parser()->filters()); + + if (filename.isNull()) return; + + settings_.setValue("last_load_playlist", filename); + + manager_->Load(filename); +} + +void PlaylistContainer::SavePlaylist(int id = -1) { + // Use the tab name as the suggested name + QString suggested_name = ui_->tab_bar->tabText(ui_->tab_bar->currentIndex()); + + manager_->SaveWithUI(id, suggested_name); +} + +void PlaylistContainer::ClearPlaylist() { +} + +void PlaylistContainer::GoToNextPlaylistTab() { + // Get the next tab' id + int id_next = ui_->tab_bar->id_of((ui_->tab_bar->currentIndex() + 1) % + ui_->tab_bar->count()); + // Switch to next tab + manager_->SetCurrentPlaylist(id_next); +} + +void PlaylistContainer::GoToPreviousPlaylistTab() { + // Get the next tab' id + int id_previous = ui_->tab_bar->id_of((ui_->tab_bar->currentIndex()+ui_->tab_bar->count()-1) % ui_->tab_bar->count()); + // Switch to next tab + manager_->SetCurrentPlaylist(id_previous); +} + +void PlaylistContainer::Save() { + + if (starting_up_) return; + + settings_.setValue("current_playlist", ui_->tab_bar->current_id()); +} + +void PlaylistContainer::SetTabBarVisible(bool visible) { + if (tab_bar_visible_ == visible) return; + tab_bar_visible_ = visible; + + tab_bar_animation_->setDirection(visible ? QTimeLine::Forward : QTimeLine::Backward); + tab_bar_animation_->start(); +} + +void PlaylistContainer::SetTabBarHeight(int height) { + ui_->tab_bar->setMaximumHeight(height); +} + +void PlaylistContainer::MaybeUpdateFilter() { + + // delaying the filter update on small playlists is undesirable + // and an empty filter applies very quickly, too + if (manager_->current()->rowCount() < kFilterDelayPlaylistSizeThreshold || ui_->filter->text().isEmpty()) { + UpdateFilter(); + } + else { + filter_timer_->start(); + } + +} + +void PlaylistContainer::UpdateFilter() { + + manager_->current()->proxy()->setFilterFixedString(ui_->filter->text()); + ui_->playlist->JumpToCurrentlyPlayingTrack(); + + UpdateNoMatchesLabel(); + +} + +void PlaylistContainer::UpdateNoMatchesLabel() { + + Playlist *playlist = manager_->current(); + const bool has_rows = playlist->rowCount() != 0; + const bool has_results = playlist->proxy()->rowCount() != 0; + + QString text; + if (has_rows && !has_results) { + if (ui_->filter->text().trimmed().compare("the answer to life the universe and everything", Qt::CaseInsensitive) == 0) { + text = "42"; + } + else { + text = tr("No matches found. Clear the search box to show the whole playlist again."); + } + } + + if (!text.isEmpty()) { + no_matches_label_->setText(text); + RepositionNoMatchesLabel(true); + no_matches_label_->show(); + } + else { + no_matches_label_->hide(); + } + +} + +void PlaylistContainer::resizeEvent(QResizeEvent *e) { + QWidget::resizeEvent(e); + RepositionNoMatchesLabel(); +} + +void PlaylistContainer::FocusOnFilter(QKeyEvent *event) { + + ui_->filter->setFocus(); + + switch (event->key()) { + case Qt::Key_Backspace: + break; + + case Qt::Key_Escape: + ui_->filter->clear(); + break; + + default: + ui_->filter->setText(ui_->filter->text() + event->text()); + break; + } +} + +void PlaylistContainer::RepositionNoMatchesLabel(bool force) { + + if (!force && !no_matches_label_->isVisible()) return; + + const int kBorder = 10; + + QPoint pos = ui_->playlist->viewport()->mapTo(ui_->playlist, QPoint(kBorder, kBorder)); + QSize size = ui_->playlist->viewport()->size(); + size.setWidth(size.width() - kBorder * 2); + size.setHeight(size.height() - kBorder * 2); + + no_matches_label_->move(pos); + no_matches_label_->resize(size); + +} + +void PlaylistContainer::SelectionChanged() { + manager_->SelectionChanged(view()->selectionModel()->selection()); +} + +bool PlaylistContainer::eventFilter(QObject *objectWatched, QEvent *event) { + + if (objectWatched == ui_->filter) { + if (event->type() == QEvent::KeyPress) { + QKeyEvent *e = static_cast(event); + switch (e->key()) { + //case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + case Qt::Key_Return: + case Qt::Key_Enter: + view()->setFocus(Qt::OtherFocusReason); + QApplication::sendEvent(ui_->playlist, event); + return true; + case Qt::Key_Escape: + ui_->filter->clear(); + return true; + default: + break; + } + } + } + return QWidget::eventFilter(objectWatched, event); + +} diff --git a/src/playlist/playlistcontainer.h b/src/playlist/playlistcontainer.h new file mode 100644 index 00000000..7f98d55c --- /dev/null +++ b/src/playlist/playlistcontainer.h @@ -0,0 +1,125 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTCONTAINER_H +#define PLAYLISTCONTAINER_H + +#include "config.h" + +#include +#include + +class Ui_PlaylistContainer; + +class LineEditInterface; +class Playlist; +class PlaylistManager; +class PlaylistView; + +class QTimeLine; +class QTimer; +class QLabel; + +class PlaylistContainer : public QWidget { + Q_OBJECT + + public: + PlaylistContainer(QWidget *parent = nullptr); + ~PlaylistContainer(); + + static const char *kSettingsGroup; + + void SetActions(QAction *new_playlist, QAction *load_playlist, QAction *save_playlist, QAction *clear_playlist, QAction *next_playlist, QAction *previous_playlist); + void SetManager(PlaylistManager *manager); + + PlaylistView *view() const; + + bool eventFilter(QObject *objectWatched, QEvent *event); + +signals: + void TabChanged(int id); + void Rename(int id, const QString &new_name); + + void UndoRedoActionsChanged(QAction *undo, QAction *redo); + void ViewSelectionModelChanged(); + + protected: + // QWidget + void resizeEvent(QResizeEvent*); + + private slots: + void NewPlaylist(); + void LoadPlaylist(); + void SavePlaylist() { SavePlaylist(-1); } + void SavePlaylist(int id); + void ClearPlaylist(); + void GoToNextPlaylistTab(); + void GoToPreviousPlaylistTab(); + + void SetViewModel(Playlist *playlist); + void PlaylistAdded(int id, const QString &name, bool favorite); + void PlaylistClosed(int id); + void PlaylistRenamed(int id, const QString &new_name); + + void Started(); + + void ActivePlaying(); + void ActivePaused(); + void ActiveStopped(); + + void Save(); + + void SetTabBarVisible(bool visible); + void SetTabBarHeight(int height); + + void SelectionChanged(); + void MaybeUpdateFilter(); + void UpdateFilter(); + void FocusOnFilter(QKeyEvent *event); + + void UpdateNoMatchesLabel(); + + private: + void UpdateActiveIcon(const QIcon &icon); + void RepositionNoMatchesLabel(bool force = false); + + private: + static const int kFilterDelayMs; + static const int kFilterDelayPlaylistSizeThreshold; + + Ui_PlaylistContainer *ui_; + + PlaylistManager *manager_; + QAction *undo_; + QAction *redo_; + Playlist *playlist_; + + QSettings settings_; + bool starting_up_; + + bool tab_bar_visible_; + QTimeLine *tab_bar_animation_; + + QLabel *no_matches_label_; + + QTimer *filter_timer_; +}; + +#endif // PLAYLISTCONTAINER_H diff --git a/src/playlist/playlistcontainer.ui b/src/playlist/playlistcontainer.ui new file mode 100644 index 00000000..154e078d --- /dev/null +++ b/src/playlist/playlistcontainer.ui @@ -0,0 +1,189 @@ + + + PlaylistContainer + + + + 0 + 0 + 987 + 707 + + + + Form + + + #toolbar { + border-color: palette(dark); + border-style: solid; + border-width: 0px 1px 0px 1px; +} + + + + 0 + + + 0 + + + + + + + + + 0 + + + 0 + + + + + + 16 + 16 + + + + true + + + + + + + + 16 + 16 + + + + true + + + + + + + + 16 + 16 + + + + true + + + + + + + + 16 + 16 + + + + true + + + + + + + + 16 + 16 + + + + true + + + + + + + + 16 + 16 + + + + true + + + + + + + Qt::Vertical + + + + + + + + + + + + + true + + + QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + true + + + QAbstractItemView::DragDrop + + + QAbstractItemView::ExtendedSelection + + + false + + + true + + + false + + + true + + + true + + + + + + + + QSearchField + QWidget +
3rdparty/qocoa/qsearchfield.h
+
+ + PlaylistView + QTreeView +
playlist/playlistview.h
+
+ + PlaylistTabBar + QWidget +
playlist/playlisttabbar.h
+ 1 +
+
+ + +
diff --git a/src/playlist/playlistdelegates.cpp b/src/playlist/playlistdelegates.cpp new file mode 100644 index 00000000..fde2cf1e --- /dev/null +++ b/src/playlist/playlistdelegates.cpp @@ -0,0 +1,475 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "playlistdelegates.h" + +#include "queue.h" +#include "core/logging.h" +#include "core/player.h" +#include "core/utilities.h" +#include "core/iconloader.h" +#include "collection/collectionbackend.h" +#include "widgets/trackslider.h" + +#ifdef Q_OS_DARWIN +#include "core/mac_utilities.h" +#endif // Q_OS_DARWIN + +const int QueuedItemDelegate::kQueueBoxBorder = 1; +const int QueuedItemDelegate::kQueueBoxCornerRadius = 3; +const int QueuedItemDelegate::kQueueBoxLength = 30; +const QRgb QueuedItemDelegate::kQueueBoxGradientColor1 = qRgb(102, 150, 227); +const QRgb QueuedItemDelegate::kQueueBoxGradientColor2 = qRgb(77, 121, 200); +const int QueuedItemDelegate::kQueueOpacitySteps = 10; +const float QueuedItemDelegate::kQueueOpacityLowerBound = 0.4; + +const int PlaylistDelegateBase::kMinHeight = 19; + +QueuedItemDelegate::QueuedItemDelegate(QObject *parent, int indicator_column) + : QStyledItemDelegate(parent), indicator_column_(indicator_column) {} + +void QueuedItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + + QStyledItemDelegate::paint(painter, option, index); + + if (index.column() == indicator_column_) { + bool ok = false; + const int queue_pos = index.data(Playlist::Role_QueuePosition).toInt(&ok); + if (ok && queue_pos != -1) { + float opacity = kQueueOpacitySteps - qMin(kQueueOpacitySteps, queue_pos); + opacity /= kQueueOpacitySteps; + opacity *= 1.0 - kQueueOpacityLowerBound; + opacity += kQueueOpacityLowerBound; + painter->setOpacity(opacity); + + DrawBox(painter, option.rect, option.font, QString::number(queue_pos + 1), kQueueBoxLength); + + painter->setOpacity(1.0); + } + } + +} + +void QueuedItemDelegate::DrawBox(QPainter *painter, const QRect &line_rect, const QFont &font, const QString &text, int width) const { + + QFont smaller = font; + smaller.setPointSize(smaller.pointSize() - 1); + smaller.setBold(true); + + if (width == -1) width = QFontMetrics(font).width(text + " "); + + QRect rect(line_rect); + rect.setLeft(rect.right() - width - kQueueBoxBorder); + rect.setWidth(width); + rect.setTop(rect.top() + kQueueBoxBorder); + rect.setBottom(rect.bottom() - kQueueBoxBorder - 1); + + QRect text_rect(rect); + text_rect.setBottom(text_rect.bottom() + 1); + + QLinearGradient gradient(rect.topLeft(), rect.bottomLeft()); + gradient.setColorAt(0.0, kQueueBoxGradientColor1); + gradient.setColorAt(1.0, kQueueBoxGradientColor2); + + // Turn on antialiasing + painter->setRenderHint(QPainter::Antialiasing); + + // Draw the box + painter->translate(0.5, 0.5); + painter->setPen(QPen(Qt::white, 1)); + painter->setBrush(gradient); + painter->drawRoundedRect(rect, kQueueBoxCornerRadius, kQueueBoxCornerRadius); + + // Draw the text + painter->setFont(smaller); + painter->drawText(rect, Qt::AlignCenter, text); + painter->translate(-0.5, -0.5); + +} + +int QueuedItemDelegate::queue_indicator_size(const QModelIndex &index) const { + + if (index.column() == indicator_column_) { + const int queue_pos = index.data(Playlist::Role_QueuePosition).toInt(); + if (queue_pos != -1) { + return kQueueBoxLength + kQueueBoxBorder * 2; + } + } + return 0; + +} + + +PlaylistDelegateBase::PlaylistDelegateBase(QObject *parent, const QString &suffix) + : QueuedItemDelegate(parent), view_(qobject_cast(parent)), suffix_(suffix) +{ +} + +QString PlaylistDelegateBase::displayText(const QVariant &value, const QLocale&) const { + + QString text; + + switch (static_cast(value.type())) { + case QMetaType::Int: { + int v = value.toInt(); + if (v > 0) text = QString::number(v); + break; + } + + case QMetaType::Float: + case QMetaType::Double: { + double v = value.toDouble(); + if (v > 0) text = QString::number(v); + break; + } + + default: + text = value.toString(); + break; + } + + if (!text.isNull() && !suffix_.isNull()) text += " " + suffix_; + return text; + +} + +QSize PlaylistDelegateBase::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { + + QSize size = QueuedItemDelegate::sizeHint(option, index); + if (size.height() < kMinHeight) size.setHeight(kMinHeight); + return size; + +} + +void PlaylistDelegateBase::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + + QueuedItemDelegate::paint(painter, Adjusted(option, index), index); + + // Stop after indicator + if (index.column() == Playlist::Column_Title) { + if (index.data(Playlist::Role_StopAfter).toBool()) { + QRect rect(option.rect); + rect.setRight(rect.right() - queue_indicator_size(index)); + + DrawBox(painter, rect, option.font, tr("stop")); + } + } + +} + +QStyleOptionViewItemV4 PlaylistDelegateBase::Adjusted(const QStyleOptionViewItem &option, const QModelIndex &index) const { + + if (!view_) return option; + + QPoint top_left(-view_->horizontalScrollBar()->value(), -view_->verticalScrollBar()->value()); + + if (view_->header()->logicalIndexAt(top_left) != index.column()) + return option; + + QStyleOptionViewItemV4 ret(option); + + if (index.data(Playlist::Role_IsCurrent).toBool()) { + // Move the text in a bit on the first column for the song that's currently + // playing + ret.rect.setLeft(ret.rect.left() + 20); + } + + return ret; + +} + +bool PlaylistDelegateBase::helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) { + + // This function is copied from QAbstractItemDelegate, and changed to show + // displayText() in the tooltip, rather than the index's naked + // Qt::ToolTipRole text. + + Q_UNUSED(option); + + if (!event || !view) return false; + + QHelpEvent *he = static_cast(event); + QString text = displayText(index.data(), QLocale::system()); + + // Special case: we want newlines in the comment tooltip + if (index.column() == Playlist::Column_Comment) { + text = index.data(Qt::ToolTipRole).toString().toHtmlEscaped(); + text.replace("\\r\\n", "
"); + text.replace("\\n", "
"); + text.replace("\r\n", "
"); + text.replace("\n", "
"); + } + + if (text.isEmpty() || !he) return false; + + switch (event->type()) { + case QEvent::ToolTip: { + QRect displayed_text; + QSize real_text; + bool is_elided = false; + + real_text = sizeHint(option, index); + displayed_text = view->visualRect(index); + is_elided = displayed_text.width() < real_text.width(); + if (is_elided) { + QToolTip::showText(he->globalPos(), text, view); + } else { // in case that another text was previously displayed + QToolTip::hideText(); + } + return true; + } + + case QEvent::QueryWhatsThis: + return true; + + case QEvent::WhatsThis: + QWhatsThis::showText(he->globalPos(), text, view); + return true; + + default: + break; + } + return false; + +} + + +QString LengthItemDelegate::displayText(const QVariant &value, const QLocale&) const { + + bool ok = false; + qint64 nanoseconds = value.toLongLong(&ok); + + if (ok && nanoseconds > 0) return Utilities::PrettyTimeNanosec(nanoseconds); + return QString::null; + +} + + +QString SizeItemDelegate::displayText(const QVariant &value, const QLocale&) const { + + bool ok = false; + int bytes = value.toInt(&ok); + + if (ok) return Utilities::PrettySize(bytes); + return QString(); + +} + +QString DateItemDelegate::displayText(const QVariant &value, const QLocale &locale) const { + + bool ok = false; + int time = value.toInt(&ok); + + if (!ok || time == -1) + return QString::null; + + return QDateTime::fromTime_t(time).toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat)); + +} + +QString LastPlayedItemDelegate::displayText(const QVariant &value, const QLocale &locale) const { + + bool ok = false; + const int time = value.toInt(&ok); + + if (!ok || time == -1) + return tr("Never"); + + return Utilities::Ago(time, locale); + +} + +QString FileTypeItemDelegate::displayText(const QVariant &value, const QLocale &locale) const { + + bool ok = false; + Song::FileType type = Song::FileType(value.toInt(&ok)); + + if (!ok) return tr("Unknown"); + + return Song::TextForFiletype(type); + +} + +QString SamplerateBitdepthItemDelegate::displayText(const QVariant &value, const QLocale &locale) const { + + return value.toString(); + +} + +QWidget *TextItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { + + return new QLineEdit(parent); +} + +TagCompletionModel::TagCompletionModel(CollectionBackend *backend, Playlist::Column column) : QStringListModel() { + + QString col = database_column(column); + if (!col.isEmpty()) { + setStringList(backend->GetAll(col)); + } + +} + +QString TagCompletionModel::database_column(Playlist::Column column) { + + switch (column) { + case Playlist::Column_Artist: return "artist"; + case Playlist::Column_Album: return "album"; + case Playlist::Column_AlbumArtist: return "albumartist"; + case Playlist::Column_Composer: return "composer"; + case Playlist::Column_Performer: return "performer"; + case Playlist::Column_Grouping: return "grouping"; + case Playlist::Column_Genre: return "genre"; + default: + qLog(Warning) << "Unknown column" << column; + return QString(); + } + +} + +static TagCompletionModel *InitCompletionModel(CollectionBackend *backend, Playlist::Column column) { + + return new TagCompletionModel(backend, column); + +} + +TagCompleter::TagCompleter(CollectionBackend *backend, Playlist::Column column, QLineEdit *editor) : QCompleter(editor), editor_(editor) { + + QFuture future = QtConcurrent::run(&InitCompletionModel, backend, column); + NewClosure(future, this, SLOT(ModelReady(QFuture)), future); + +} + +void TagCompleter::ModelReady(QFuture future) { + + TagCompletionModel *model = future.result(); + setModel(model); + setCaseSensitivity(Qt::CaseInsensitive); + editor_->setCompleter(this); +} + +QWidget *TagCompletionItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex&) const { + + QLineEdit *editor = new QLineEdit(parent); + new TagCompleter(backend_, column_, editor); + + return editor; +} + +QString NativeSeparatorsDelegate::displayText(const QVariant &value, const QLocale&) const { + + const QString string_value = value.toString(); + + QUrl url; + if (value.type() == QVariant::Url) { + url = value.toUrl(); + } + else if (string_value.contains("://")) { + url = QUrl::fromEncoded(string_value.toLatin1()); + } + else { + return QDir::toNativeSeparators(string_value); + } + + if (url.scheme() == "file") { + return QDir::toNativeSeparators(url.toLocalFile()); + } + return string_value; + +} + +SongSourceDelegate::SongSourceDelegate(QObject *parent, Player *player) : PlaylistDelegateBase(parent), player_(player) {} + +QString SongSourceDelegate::displayText(const QVariant &value, const QLocale&) const { + return QString(); +} + +QPixmap SongSourceDelegate::LookupPixmap(const QUrl &url, const QSize &size) const { + + QPixmap pixmap; + if (cache_.find(url.scheme(), &pixmap)) { + return pixmap; + } + + QIcon icon; + const UrlHandler *handler = player_->HandlerForUrl(url); + if (handler) { + icon = handler->icon(); + } + else { + if (url.scheme() == "file") { + icon = IconLoader::Load("folder-sound"); + } + else if (url.scheme() == "cdda") { + icon = IconLoader::Load("cd"); + } + else { + icon = IconLoader::Load("folder-sound"); + } + } + pixmap = icon.pixmap(size.height()); + cache_.insert(url.scheme(), pixmap); + + return pixmap; + +} + +void SongSourceDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + + // Draw the background + PlaylistDelegateBase::paint(painter, option, index); + + QStyleOptionViewItem option_copy(option); + initStyleOption(&option_copy, index); + + // Find the pixmap to use for this URL + const QUrl &url = index.data().toUrl(); + QPixmap pixmap = LookupPixmap(url, option_copy.decorationSize); + + float device_pixel_ratio = 1.0f; +#ifdef Q_OS_DARWIN + QWidget *parent_widget = reinterpret_cast(parent()); + device_pixel_ratio = mac::GetDevicePixelRatio(parent_widget); +#endif + + // Draw the pixmap in the middle of the rectangle + QRect draw_rect(QPoint(0, 0), option_copy.decorationSize / device_pixel_ratio); + draw_rect.moveCenter(option_copy.rect.center()); + + painter->drawPixmap(draw_rect, pixmap); + +} + diff --git a/src/playlist/playlistdelegates.h b/src/playlist/playlistdelegates.h new file mode 100644 index 00000000..34f68660 --- /dev/null +++ b/src/playlist/playlistdelegates.h @@ -0,0 +1,172 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTDELEGATES_H +#define PLAYLISTDELEGATES_H + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "playlist.h" +#include "collection/collection.h" +#include "widgets/ratingwidget.h" + +class Player; + +class QueuedItemDelegate : public QStyledItemDelegate { +public: + QueuedItemDelegate(QObject *parent, int indicator_column = Playlist::Column_Title); + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + void DrawBox(QPainter *painter, const QRect &line_rect, const QFont &font, const QString &text, int width = -1) const; + + int queue_indicator_size(const QModelIndex &index) const; + +private: + static const int kQueueBoxBorder; + static const int kQueueBoxCornerRadius; + static const int kQueueBoxLength; + static const QRgb kQueueBoxGradientColor1; + static const QRgb kQueueBoxGradientColor2; + static const int kQueueOpacitySteps; + static const float kQueueOpacityLowerBound; + + int indicator_column_; +}; + +class PlaylistDelegateBase : public QueuedItemDelegate { + Q_OBJECT + public: + PlaylistDelegateBase(QObject *parent, const QString &suffix = QString()); + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + QString displayText(const QVariant &value, const QLocale &locale) const; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + + QStyleOptionViewItemV4 Adjusted(const QStyleOptionViewItem &option, const QModelIndex &index) const; + + static const int kMinHeight; + + public slots: + bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index); + + protected: + QTreeView *view_; + QString suffix_; +}; + +class LengthItemDelegate : public PlaylistDelegateBase { + public: + LengthItemDelegate(QObject *parent) : PlaylistDelegateBase(parent) {} + QString displayText(const QVariant &value, const QLocale &locale) const; +}; + +class SizeItemDelegate : public PlaylistDelegateBase { + public: + SizeItemDelegate(QObject *parent) : PlaylistDelegateBase(parent) {} + QString displayText(const QVariant &value, const QLocale &locale) const; +}; + +class DateItemDelegate : public PlaylistDelegateBase { + public: + DateItemDelegate(QObject *parent) : PlaylistDelegateBase(parent) {} + QString displayText(const QVariant &value, const QLocale &locale) const; +}; + +class LastPlayedItemDelegate : public PlaylistDelegateBase { +public: + LastPlayedItemDelegate(QObject *parent) : PlaylistDelegateBase(parent) {} + QString displayText(const QVariant &value, const QLocale &locale) const; +}; + +class FileTypeItemDelegate : public PlaylistDelegateBase { + public: + FileTypeItemDelegate(QObject *parent) : PlaylistDelegateBase(parent) {} + QString displayText(const QVariant &value, const QLocale &locale) const; +}; + +class SamplerateBitdepthItemDelegate : public PlaylistDelegateBase { + public: + SamplerateBitdepthItemDelegate(QObject *parent) : PlaylistDelegateBase(parent) {} + QString displayText(const QVariant &value, const QLocale &locale) const; +}; + +class TextItemDelegate : public PlaylistDelegateBase { + public: + TextItemDelegate(QObject *parent) : PlaylistDelegateBase(parent) {} + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; +}; + +class TagCompletionModel : public QStringListModel { +public: + TagCompletionModel(CollectionBackend *backend, Playlist::Column column); + +private: + static QString database_column(Playlist::Column column); +}; + +class TagCompleter : public QCompleter { + Q_OBJECT + +public: + TagCompleter(CollectionBackend *backend, Playlist::Column column, QLineEdit *editor); + + private slots: + void ModelReady(QFuture future); + +private: + QLineEdit *editor_; +}; + +class TagCompletionItemDelegate : public PlaylistDelegateBase { + public: + TagCompletionItemDelegate(QObject *parent, CollectionBackend *backend, Playlist::Column column) : PlaylistDelegateBase(parent), backend_(backend), column_(column) {}; + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + private: + CollectionBackend *backend_; + Playlist::Column column_; +}; + +class NativeSeparatorsDelegate : public PlaylistDelegateBase { + public: + NativeSeparatorsDelegate(QObject *parent) : PlaylistDelegateBase(parent) {} + QString displayText(const QVariant &value, const QLocale &locale) const; +}; + +class SongSourceDelegate : public PlaylistDelegateBase { + public: + SongSourceDelegate(QObject *parent, Player *player); + QString displayText(const QVariant &value, const QLocale &locale) const; + void paint(QPainter *paint, const QStyleOptionViewItem &option, const QModelIndex &index) const; + + private: + QPixmap LookupPixmap(const QUrl &url, const QSize &size) const; + + Player *player_; + mutable QPixmapCache cache_; +}; + +#endif // PLAYLISTDELEGATES_H diff --git a/src/playlist/playlistfilter.cpp b/src/playlist/playlistfilter.cpp new file mode 100644 index 00000000..3f5b7296 --- /dev/null +++ b/src/playlist/playlistfilter.cpp @@ -0,0 +1,85 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "playlistfilter.h" +#include "playlistfilterparser.h" + +#include + +PlaylistFilter::PlaylistFilter(QObject *parent) + : QSortFilterProxyModel(parent), + filter_tree_(new NopFilter), + query_hash_(0) + +{ + setDynamicSortFilter(true); + + column_names_["title"] = Playlist::Column_Title; + column_names_["name"] = Playlist::Column_Title; + column_names_["artist"] = Playlist::Column_Artist; + column_names_["album"] = Playlist::Column_Album; + column_names_["albumartist"] = Playlist::Column_AlbumArtist; + column_names_["composer"] = Playlist::Column_Composer; + column_names_["performer"] = Playlist::Column_Performer; + column_names_["grouping"] = Playlist::Column_Grouping; + column_names_["length"] = Playlist::Column_Length; + column_names_["track"] = Playlist::Column_Track; + column_names_["disc"] = Playlist::Column_Disc; + column_names_["year"] = Playlist::Column_Year; + column_names_["originalyear"] = Playlist::Column_OriginalYear; + column_names_["genre"] = Playlist::Column_Genre; + column_names_["comment"] = Playlist::Column_Comment; + column_names_["bitrate"] = Playlist::Column_Bitrate; + column_names_["filename"] = Playlist::Column_Filename; + + numerical_columns_ << Playlist::Column_Length + << Playlist::Column_Track + << Playlist::Column_Disc + << Playlist::Column_Year + << Playlist::Column_Bitrate; +} + +PlaylistFilter::~PlaylistFilter() { +} + +void PlaylistFilter::sort(int column, Qt::SortOrder order) { + // Pass this through to the Playlist, it does sorting itself + sourceModel()->sort(column, order); +} + +bool PlaylistFilter::filterAcceptsRow(int row, const QModelIndex &parent) const { + + QString filter = filterRegExp().pattern(); + + uint hash = qHash(filter); + if (hash != query_hash_) { + // Parse the query + FilterParser p(filter, column_names_, numerical_columns_); + filter_tree_.reset(p.parse()); + + query_hash_ = hash; + } + + // Test the row + return filter_tree_->accept(row, parent, sourceModel()); + +} diff --git a/src/playlist/playlistfilter.h b/src/playlist/playlistfilter.h new file mode 100644 index 00000000..7428bea2 --- /dev/null +++ b/src/playlist/playlistfilter.h @@ -0,0 +1,58 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTFILTER_H +#define PLAYLISTFILTER_H + +#include "config.h" + +#include +#include + +#include "playlist.h" + +#include + +class FilterTree; + +class PlaylistFilter : public QSortFilterProxyModel { + Q_OBJECT + + public: + PlaylistFilter(QObject *parent = nullptr); + ~PlaylistFilter(); + + // QAbstractItemModel + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + // QSortFilterProxyModel + // public so Playlist::NextVirtualIndex and friends can get at it + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; + +private: + // Mutable because they're modified from filterAcceptsRow() const + mutable QScopedPointer filter_tree_; + mutable uint query_hash_; + + QMap column_names_; + QSet numerical_columns_; +}; + +#endif // PLAYLISTFILTER_H diff --git a/src/playlist/playlistfilterparser.cpp b/src/playlist/playlistfilterparser.cpp new file mode 100644 index 00000000..6d4a3e12 --- /dev/null +++ b/src/playlist/playlistfilterparser.cpp @@ -0,0 +1,535 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "playlistfilterparser.h" +#include "playlist.h" +#include "core/logging.h" + +#include + +class SearchTermComparator { + public: + virtual ~SearchTermComparator() {} + virtual bool Matches(const QString &element) const = 0; +}; + +// "compares" by checking if the field contains the search term +class DefaultComparator : public SearchTermComparator { + public: + explicit DefaultComparator(const QString &value) : search_term_(value) {} + virtual bool Matches(const QString &element) const { + return element.contains(search_term_); + } + private: + QString search_term_; +}; + +class EqComparator : public SearchTermComparator { + public: + explicit EqComparator(const QString &value) : search_term_(value) {} + virtual bool Matches(const QString &element) const { + return search_term_ == element; + } + private: + QString search_term_; +}; + +class NeComparator : public SearchTermComparator { + public: + explicit NeComparator(const QString &value) : search_term_(value) {} + virtual bool Matches(const QString &element) const { + return search_term_ != element; + } + private: + QString search_term_; +}; + +class LexicalGtComparator : public SearchTermComparator { + public: + explicit LexicalGtComparator(const QString &value) : search_term_(value) {} + virtual bool Matches(const QString &element) const { + return element > search_term_; + } + private: + QString search_term_; +}; + +class LexicalGeComparator : public SearchTermComparator { + public: + explicit LexicalGeComparator(const QString &value) : search_term_(value) {} + virtual bool Matches(const QString &element) const { + return element >= search_term_; + } + private: + QString search_term_; +}; + +class LexicalLtComparator : public SearchTermComparator { + public: + explicit LexicalLtComparator(const QString &value) : search_term_(value) {} + virtual bool Matches(const QString &element) const { + return element < search_term_; + } + private: + QString search_term_; +}; + +class LexicalLeComparator : public SearchTermComparator { + public: + explicit LexicalLeComparator(const QString &value) : search_term_(value) {} + virtual bool Matches(const QString &element) const { + return element <= search_term_; + } + private: + QString search_term_; +}; + +class GtComparator : public SearchTermComparator { + public: + explicit GtComparator(int value) : search_term_(value) {} + virtual bool Matches(const QString &element) const { + return element.toInt() > search_term_; + } + private: + int search_term_; +}; + +class GeComparator : public SearchTermComparator { + public: + explicit GeComparator(int value) : search_term_(value) {} + virtual bool Matches(const QString &element) const { + return element.toInt() >= search_term_; + } + private: + int search_term_; +}; + +class LtComparator : public SearchTermComparator { + public: + explicit LtComparator(int value) : search_term_(value) {} + virtual bool Matches(const QString &element) const { + return element.toInt() < search_term_; + } + private: + int search_term_; +}; + +class LeComparator : public SearchTermComparator { + public: + explicit LeComparator(int value) : search_term_(value) {} + virtual bool Matches(const QString &element) const { + return element.toInt() <= search_term_; + } + private: + int search_term_; +}; + +// The length field of the playlist (entries) contains a +// song's running time in nano seconds. However, We don't +// really care about nano seconds, just seconds. Thus, with +// this decorator we drop the last 9 digits, if that many +// are present. +class DropTailComparatorDecorator : public SearchTermComparator { + public: + explicit DropTailComparatorDecorator(SearchTermComparator *cmp) : cmp_(cmp) {} + + virtual bool Matches(const QString &element) const { + if (element.length() > 9) + return cmp_->Matches(element.left(element.length() - 9)); + else + return cmp_->Matches(element); + } + private: + QScopedPointer cmp_; +}; + +class RatingComparatorDecorator : public SearchTermComparator { + public: + explicit RatingComparatorDecorator(SearchTermComparator *cmp) : cmp_(cmp) {} + virtual bool Matches(const QString &element) const { + return cmp_->Matches( + QString::number(static_cast(element.toDouble() * 10.0 + 0.5))); + } + private: + QScopedPointer cmp_; +}; + +// filter that applies a SearchTermComparator to all fields of a playlist entry +class FilterTerm : public FilterTree { + public: + explicit FilterTerm(SearchTermComparator *comparator, const QList &columns) : cmp_(comparator), columns_(columns) {} + + virtual bool accept(int row, const QModelIndex &parent, + const QAbstractItemModel *const model) const { + for (int i : columns_) { + QModelIndex idx(model->index(row, i, parent)); + if (cmp_->Matches(idx.data().toString().toLower())) return true; + } + return false; + } + virtual FilterType type() { return Term; } + private: + QScopedPointer cmp_; + QList columns_; +}; + +// filter that applies a SearchTermComparator to one specific field of a playlist entry +class FilterColumnTerm : public FilterTree { + public: + FilterColumnTerm(int column, SearchTermComparator *comparator) : col(column), cmp_(comparator) {} + + virtual bool accept(int row, const QModelIndex &parent, const QAbstractItemModel *const model) const { + QModelIndex idx(model->index(row, col, parent)); + return cmp_->Matches(idx.data().toString().toLower()); + } + virtual FilterType type() { return Column; } + private: + int col; + QScopedPointer cmp_; +}; + +class NotFilter : public FilterTree { + public: + explicit NotFilter(const FilterTree *inv) : child_(inv) {} + + virtual bool accept(int row, const QModelIndex &parent, const QAbstractItemModel *const model) const { + return !child_->accept(row, parent, model); + } + virtual FilterType type() { return Not; } + private: + QScopedPointer child_; +}; + +class OrFilter : public FilterTree { + public: + ~OrFilter() { qDeleteAll(children_); } + virtual void add(FilterTree *child) { children_.append(child); } + virtual bool accept(int row, const QModelIndex &parent, const QAbstractItemModel *const model) const { + for (FilterTree *child : children_) { + if (child->accept(row, parent, model)) return true; + } + return false; + } + FilterType type() { return Or; } + private: + QList children_; +}; + +class AndFilter : public FilterTree { + public: + virtual ~AndFilter() { qDeleteAll(children_); } + virtual void add(FilterTree *child) { children_.append(child); } + virtual bool accept(int row, const QModelIndex &parent, const QAbstractItemModel *const model) const { + for (FilterTree *child : children_) { + if (!child->accept(row, parent, model)) return false; + } + return true; + } + FilterType type() { return And; } + private: + QList children_; +}; + +FilterParser::FilterParser(const QString &filter, const QMap &columns, const QSet &numerical_cols) : filterstring_(filter), columns_(columns), numerical_columns_(numerical_cols) {} + +FilterTree *FilterParser::parse() { + iter_ = filterstring_.constBegin(); + end_ = filterstring_.constEnd(); + return parseOrGroup(); +} + +void FilterParser::advance() { + while (iter_ != end_ && iter_->isSpace()) { + ++iter_; + } +} + +FilterTree *FilterParser::parseOrGroup() { + advance(); + if (iter_ == end_) return new NopFilter; + + OrFilter *group = new OrFilter; + group->add(parseAndGroup()); + advance(); + while (checkOr()) { + group->add(parseAndGroup()); + advance(); + } + return group; +} + +FilterTree *FilterParser::parseAndGroup() { + advance(); + if (iter_ == end_) return new NopFilter; + + AndFilter *group = new AndFilter(); + do { + group->add(parseSearchExpression()); + advance(); + if (iter_ != end_ && *iter_ == QChar(')')) break; + if (checkOr(false)) { + break; + } + checkAnd(); // if there's no 'AND', we'll add the term anyway... + } while (iter_ != end_); + return group; +} + +bool FilterParser::checkAnd() { + if (iter_ != end_) { + if (*iter_ == QChar('A')) { + buf_ += *iter_; + iter_++; + if (iter_ != end_ && *iter_ == QChar('N')) { + buf_ += *iter_; + iter_++; + if (iter_ != end_ && *iter_ == QChar('D')) { + buf_ += *iter_; + iter_++; + if (iter_ != end_ && (iter_->isSpace() || *iter_ == QChar('-') || *iter_ == '(')) { + advance(); + buf_.clear(); + return true; + } + } + } + } + } + return false; +} + +bool FilterParser::checkOr(bool step_over) { + if (!buf_.isEmpty()) { + if (buf_ == "OR") { + if (step_over) { + buf_.clear(); + advance(); + } + return true; + } + } + else { + if (iter_ != end_) { + if (*iter_ == 'O') { + buf_ += *iter_; + iter_++; + if (iter_ != end_ && *iter_ == 'R') { + buf_ += *iter_; + iter_++; + if (iter_ != end_ && (iter_->isSpace() || *iter_ == '-' || *iter_ == '(')) { + if (step_over) { + buf_.clear(); + advance(); + } + return true; + } + } + } + } + } + return false; +} + +FilterTree *FilterParser::parseSearchExpression() { + advance(); + if (iter_ == end_) return new NopFilter; + if (*iter_ == '(') { + iter_++; + advance(); + FilterTree *tree = parseOrGroup(); + advance(); + if (iter_ != end_) { + if (*iter_ == ')') { + ++iter_; + } + } + return tree; + } + else if (*iter_ == '-') { + ++iter_; + FilterTree *tree = parseSearchExpression(); + if (tree->type() != FilterTree::Nop) return new NotFilter(tree); + return tree; + } else { + return parseSearchTerm(); + } +} + +FilterTree *FilterParser::parseSearchTerm() { + QString col; + QString search; + QString prefix; + bool inQuotes = false; + for (; iter_ != end_; ++iter_) { + if (inQuotes) { + if (*iter_ == '"') + inQuotes = false; + else + buf_ += *iter_; + } + else { + if (*iter_ == '"') { + inQuotes = true; + } + else if (col.isEmpty() && *iter_ == ':') { + col = buf_.toLower(); + buf_.clear(); + prefix.clear(); // prefix isn't allowed here - let's ignore it + } + else if (iter_->isSpace() || *iter_ == '(' || *iter_ == ')' || + *iter_ == '-') { + break; + } else if (buf_.isEmpty()) { + // we don't know whether there is a column part in this search term + // thus we assume the latter and just try and read a prefix + if (prefix.isEmpty() && (*iter_ == '>' || *iter_ == '<' || *iter_ == '=' || *iter_ == '!')) { + prefix += *iter_; + } + else if (prefix != "=" && *iter_ == '=') { + prefix += *iter_; + } + else { + buf_ += *iter_; + } + } + else { + buf_ += *iter_; + } + } + } + + search = buf_.toLower(); + buf_.clear(); + + return createSearchTermTreeNode(col, prefix, search); +} + +FilterTree *FilterParser::createSearchTermTreeNode( + const QString &col, const QString &prefix, const QString &search) const { + if (search.isEmpty() && prefix != "=") { + return new NopFilter; + } + // here comes a mess :/ + // well, not that much of a mess, but so many options -_- + SearchTermComparator *cmp = nullptr; + if (prefix == "!=" || prefix == "<>") { + cmp = new NeComparator(search); + } + else if (!col.isEmpty() && columns_.contains(col) && numerical_columns_.contains(columns_[col])) { + // the length column contains the time in seconds (nano seconds, actually - + // the "nano" part is handled by the DropTailComparatorDecorator, though). + int search_value; + if (columns_[col] == Playlist::Column_Length) { + search_value = parseTime(search); + } + //else if (columns_[col] == Playlist::Column_Rating) { + //search_value = static_cast(search.toDouble() * 2.0 + 0.5); + //} + else { + search_value = search.toInt(); + } + // alright, back to deciding which comparator we'll use + if (prefix == ">") { + cmp = new GtComparator(search_value); + } + else if (prefix == ">=") { + cmp = new GeComparator(search_value); + } + else if (prefix == "<") { + cmp = new LtComparator(search_value); + } + else if (prefix == "<=") { + cmp = new LeComparator(search_value); + } + else { + // convert back because for time/rating + cmp = new EqComparator(QString::number(search_value)); + } + } + else { + if (prefix == "=") { + cmp = new EqComparator(search); + } + else if (prefix == ">") { + cmp = new LexicalGtComparator(search); + } + else if (prefix == ">=") { + cmp = new LexicalGeComparator(search); + } + else if (prefix == "<") { + cmp = new LexicalLtComparator(search); + } + else if (prefix == "<=") { + cmp = new LexicalLeComparator(search); + } + else { + cmp = new DefaultComparator(search); + } + } + if (columns_.contains(col)) { + if (columns_[col] == Playlist::Column_Length) { + cmp = new DropTailComparatorDecorator(cmp); + } + return new FilterColumnTerm(columns_[col], cmp); + } + else { + return new FilterTerm(cmp, columns_.values()); + } +} + +// Try and parse the string as '[[h:]m:]s' (ignoring all spaces), +// and return the number of seconds if it parses correctly. +// If not, the original string is returned. +// The 'h', 'm' and 's' components can have any length (including 0). +// +// A few examples: +// "::" is parsed to "0" +// "1::" is parsed to "3600" +// "3:45" is parsed to "225" +// "1:165" is parsed to "225" +// "225" is parsed to "225" (srsly! ^.^) +// "2:3:4:5" is parsed to "2:3:4:5" +// "25m" is parsed to "25m" +int FilterParser::parseTime(const QString &time_str) const { + + int seconds = 0; + int accum = 0; + int colon_count = 0; + for (const QChar &c : time_str) { + if (c.isDigit()) { + accum = accum * 10 + c.digitValue(); + } + else if (c == ':') { + seconds = seconds * 60 + accum; + accum = 0; + ++colon_count; + if (colon_count > 2) { + return 0; + } + } + else if (!c.isSpace()) { + return 0; + } + } + seconds = seconds * 60 + accum; + return seconds; +} diff --git a/src/playlist/playlistfilterparser.h b/src/playlist/playlistfilterparser.h new file mode 100644 index 00000000..f7103d07 --- /dev/null +++ b/src/playlist/playlistfilterparser.h @@ -0,0 +1,103 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTFILTERPARSER_H +#define PLAYLISTFILTERPARSER_H + +#include "config.h" + +#include +#include +#include +#include + +class QAbstractItemModel; + +// structure for filter parse tree +class FilterTree { + public: + virtual ~FilterTree() {} + virtual bool accept(int row, const QModelIndex &parent, const QAbstractItemModel *const model) const = 0; + enum FilterType { + Nop = 0, + Or, + And, + Not, + Column, + Term + }; + virtual FilterType type() = 0; +}; + +// trivial filter that accepts *anything* +class NopFilter : public FilterTree { + public: + virtual bool accept(int row, const QModelIndex &parent, const QAbstractItemModel *const model) const { return true; } + virtual FilterType type() { return Nop; } +}; + + +// A utility class to parse search filter strings into a decision tree +// that can decide whether a playlist entry matches the filter. +// +// Here's a grammar describing the filters we expect: +//  expr ::= or-group +// or-group ::= and-group ('OR' and-group)* +// and-group ::= sexpr ('AND' sexpr)* +// sexpr ::= sterm | '-' sexpr | '(' or-group ')' +// sterm ::= col ':' sstring | sstring +// sstring ::= prefix? string +// string ::= [^:-()" ]+ | '"' [^"]+ '"' +// prefix ::= '=' | '<' | '>' | '<=' | '>=' +// col ::= "title" | "artist" | ... +class FilterParser { + public: + FilterParser( + const QString &filter, + const QMap &columns, + const QSet &numerical_cols); + + FilterTree *parse(); + + private: + void advance(); + FilterTree *parseOrGroup(); + FilterTree *parseAndGroup(); + // check if iter is at the start of 'AND' + // if so, step over it and return true + // it not, return false and leave iter where it was + bool checkAnd(); + // check if iter is at the start of 'OR' + bool checkOr(bool step_over = true); + FilterTree *parseSearchExpression(); + FilterTree *parseSearchTerm(); + + FilterTree *createSearchTermTreeNode(const QString &col, const QString &prefix, const QString &search) const; + int parseTime(const QString &time_str) const; + + QString::const_iterator iter_; + QString::const_iterator end_; + QString buf_; + const QString filterstring_; + const QMap columns_; + const QSet numerical_columns_; +}; + +#endif // PLAYLISTFILTERPARSER_H diff --git a/src/playlist/playlistheader.cpp b/src/playlist/playlistheader.cpp new file mode 100644 index 00000000..1311f929 --- /dev/null +++ b/src/playlist/playlistheader.cpp @@ -0,0 +1,133 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "playlistheader.h" +#include "playlistview.h" + +#include +#include +#include +#include + +PlaylistHeader::PlaylistHeader(Qt::Orientation orientation, PlaylistView *view, QWidget *parent) + : StretchHeaderView(orientation, parent), + view_(view), + menu_(new QMenu(this)), + show_mapper_(new QSignalMapper(this)) +{ + + hide_action_ = menu_->addAction(tr("&Hide..."), this, SLOT(HideCurrent())); + stretch_action_ = menu_->addAction(tr("&Stretch columns to fit window"), this, SLOT(ToggleStretchEnabled())); + menu_->addSeparator(); + + QMenu *align_menu = new QMenu(tr("&Align text"), this); + QActionGroup *align_group = new QActionGroup(this); + align_left_action_ = new QAction(tr("&Left"), align_group); + align_center_action_ = new QAction(tr("&Center"), align_group); + align_right_action_ = new QAction(tr("&Right"), align_group); + + align_left_action_->setCheckable(true); + align_center_action_->setCheckable(true); + align_right_action_->setCheckable(true); + align_menu->addActions(align_group->actions()); + + connect(align_group, SIGNAL(triggered(QAction*)), SLOT(SetColumnAlignment(QAction*))); + + menu_->addMenu(align_menu); + menu_->addSeparator(); + + stretch_action_->setCheckable(true); + stretch_action_->setChecked(is_stretch_enabled()); + + connect(show_mapper_, SIGNAL(mapped(int)), SLOT(ToggleVisible(int))); + connect(this, SIGNAL(StretchEnabledChanged(bool)), stretch_action_, SLOT(setChecked(bool))); + +} + +void PlaylistHeader::contextMenuEvent(QContextMenuEvent *e) { + + menu_section_ = logicalIndexAt(e->pos()); + + if (menu_section_ == -1 || (menu_section_ == logicalIndex(0) && logicalIndex(1) == -1)) + hide_action_->setVisible(false); + else { + hide_action_->setVisible(true); + + QString title(model()->headerData(menu_section_, Qt::Horizontal).toString()); + hide_action_->setText(tr("&Hide %1").arg(title)); + + Qt::Alignment alignment = view_->column_alignment(menu_section_); + if (alignment & Qt::AlignLeft) align_left_action_->setChecked(true); + else if (alignment & Qt::AlignHCenter) align_center_action_->setChecked(true); + else if (alignment & Qt::AlignRight) align_right_action_->setChecked(true); + } + + qDeleteAll(show_actions_); + show_actions_.clear(); + for (int i = 0 ; i < count() ; ++i) { + AddColumnAction(i); + } + + menu_->popup(e->globalPos()); + +} + +void PlaylistHeader::AddColumnAction(int index) { + + QString title(model()->headerData(index, Qt::Horizontal).toString()); + + QAction *action = menu_->addAction(title, show_mapper_, SLOT(map())); + action->setCheckable(true); + action->setChecked(!isSectionHidden(index)); + show_actions_ << action; + + show_mapper_->setMapping(action, index); + +} + +void PlaylistHeader::HideCurrent() { + if (menu_section_ == -1) return; + + SetSectionHidden(menu_section_, true); +} + +void PlaylistHeader::SetColumnAlignment(QAction *action) { + + Qt::Alignment alignment = Qt::AlignVCenter; + + if (action == align_left_action_) alignment |= Qt::AlignLeft; + if (action == align_center_action_) alignment |= Qt::AlignHCenter; + if (action == align_right_action_) alignment |= Qt::AlignRight; + + view_->SetColumnAlignment(menu_section_, alignment); + +} + +void PlaylistHeader::ToggleVisible(int section) { + SetSectionHidden(section, !isSectionHidden(section)); + emit SectionVisibilityChanged(section, !isSectionHidden(section)); +} + +void PlaylistHeader::enterEvent(QEvent*) { + emit MouseEntered(); +} + diff --git a/src/playlist/playlistheader.h b/src/playlist/playlistheader.h new file mode 100644 index 00000000..09f2a3de --- /dev/null +++ b/src/playlist/playlistheader.h @@ -0,0 +1,71 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTHEADER_H +#define PLAYLISTHEADER_H + +#include "config.h" + +#include "widgets/stretchheaderview.h" + +class PlaylistView; + +class QMenu; +class QSignalMapper; + +class PlaylistHeader : public StretchHeaderView { + Q_OBJECT + + public: + PlaylistHeader(Qt::Orientation orientation, PlaylistView *view, QWidget *parent = nullptr); + + // QWidget + void contextMenuEvent(QContextMenuEvent *e); + void enterEvent(QEvent *); + + signals: + void SectionVisibilityChanged(int logical, bool visible); + void MouseEntered(); + + private slots: + void HideCurrent(); + void ToggleVisible(int section); + void SetColumnAlignment(QAction *action); + + private: + void AddColumnAction(int index); + + private: + PlaylistView *view_; + + int menu_section_; + QMenu *menu_; + QAction *hide_action_; + QAction *stretch_action_; + QAction *align_left_action_; + QAction *align_center_action_; + QAction *align_right_action_; + QList show_actions_; + + QSignalMapper *show_mapper_; +}; + +#endif // PLAYLISTHEADER_H + diff --git a/src/playlist/playlistitem.cpp b/src/playlist/playlistitem.cpp new file mode 100644 index 00000000..760336c8 --- /dev/null +++ b/src/playlist/playlistitem.cpp @@ -0,0 +1,116 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "playlistitem.h" +#include "songplaylistitem.h" +#include "core/logging.h" +#include "core/song.h" +#include "collection/collection.h" +#include "collection/collectionplaylistitem.h" + +#include +#include +#include + + +PlaylistItem::~PlaylistItem() { +} + +PlaylistItem* PlaylistItem::NewFromType(const QString &type) { + + if (type == "Collection") return new CollectionPlaylistItem(type); + else if (type == "File") return new SongPlaylistItem(type); + + qLog(Warning) << "Invalid PlaylistItem type:" << type; + + return nullptr; + +} + +PlaylistItem* PlaylistItem::NewFromSongsTable(const QString &table, const Song &song) { + + if (table == Collection::kSongsTable) + return new CollectionPlaylistItem(song); + + qLog(Warning) << "Invalid PlaylistItem songs table:" << table; + return nullptr; + +} + +void PlaylistItem::BindToQuery(QSqlQuery* query) const { + + query->bindValue(":type", type()); + query->bindValue(":collection_id", DatabaseValue(Column_CollectionId)); + + DatabaseSongMetadata().BindToQuery(query); + +} + +void PlaylistItem::SetTemporaryMetadata(const Song &metadata) { + temp_metadata_ = metadata; +} + +void PlaylistItem::ClearTemporaryMetadata() { + temp_metadata_ = Song(); +} + +static void ReloadPlaylistItem(PlaylistItemPtr item) { + item->Reload(); +} + +QFuture PlaylistItem::BackgroundReload() { + return QtConcurrent::run(ReloadPlaylistItem, shared_from_this()); +} + +void PlaylistItem::SetBackgroundColor(short priority, const QColor &color) { + background_colors_[priority] = color; +} +bool PlaylistItem::HasBackgroundColor(short priority) const { + return background_colors_.contains(priority); +} +void PlaylistItem::RemoveBackgroundColor(short priority) { + background_colors_.remove(priority); +} +QColor PlaylistItem::GetCurrentBackgroundColor() const { + return background_colors_.isEmpty() ? QColor() : background_colors_[background_colors_.keys().last()]; +} +bool PlaylistItem::HasCurrentBackgroundColor() const { + return !background_colors_.isEmpty(); +} + +void PlaylistItem::SetForegroundColor(short priority, const QColor &color) { + foreground_colors_[priority] = color; +} +bool PlaylistItem::HasForegroundColor(short priority) const { + return foreground_colors_.contains(priority); +} +void PlaylistItem::RemoveForegroundColor(short priority) { + foreground_colors_.remove(priority); +} +QColor PlaylistItem::GetCurrentForegroundColor() const { + return foreground_colors_.isEmpty() ? QColor() : foreground_colors_[foreground_colors_.keys().last()]; +} +bool PlaylistItem::HasCurrentForegroundColor() const { + return !foreground_colors_.isEmpty(); +} +void PlaylistItem::SetShouldSkip(bool val) { should_skip_ = val; } +bool PlaylistItem::GetShouldSkip() const { return should_skip_; } diff --git a/src/playlist/playlistitem.h b/src/playlist/playlistitem.h new file mode 100644 index 00000000..a9bd29f3 --- /dev/null +++ b/src/playlist/playlistitem.h @@ -0,0 +1,123 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTITEM_H +#define PLAYLISTITEM_H + +#include "config.h" + +#include + +#include +#include +#include +#include +#include + +#include "core/song.h" + +class QAction; +class SqlRow; + +class PlaylistItem : public std::enable_shared_from_this { + public: + PlaylistItem(const QString &type) : should_skip_(false), type_(type) {} + virtual ~PlaylistItem(); + + static PlaylistItem* NewFromType(const QString &type); + static PlaylistItem* NewFromSongsTable(const QString &table, const Song &song); + + enum Option { + Default = 0x00, + + // Disables the "pause" action. + PauseDisabled = 0x01, + + // Disables the seek slider. + SeekDisabled = 0x04, + }; + Q_DECLARE_FLAGS(Options, Option); + + virtual QString type() const { return type_; } + + virtual Options options() const { return Default; } + + virtual QList actions() { return QList(); } + + virtual bool InitFromQuery(const SqlRow &query) = 0; + void BindToQuery(QSqlQuery* query) const; + virtual void Reload() {} + QFuture BackgroundReload(); + + virtual Song Metadata() const = 0; + virtual QUrl Url() const = 0; + + void SetTemporaryMetadata(const Song &metadata); + void ClearTemporaryMetadata(); + bool HasTemporaryMetadata() const { return temp_metadata_.is_valid(); } + + // Background colors. + void SetBackgroundColor(short priority, const QColor &color); + bool HasBackgroundColor(short priority) const; + void RemoveBackgroundColor(short priority); + QColor GetCurrentBackgroundColor() const; + bool HasCurrentBackgroundColor() const; + + // Foreground colors. + void SetForegroundColor(short priority, const QColor &color); + bool HasForegroundColor(short priority) const; + void RemoveForegroundColor(short priority); + QColor GetCurrentForegroundColor() const; + bool HasCurrentForegroundColor() const; + + // Convenience function to find out whether this item is from the local + // collection, as opposed to a device, a file on disk, or a stream. + // Remember that even if this returns true, the collection item might be + // invalid so you might want to check that its id is not equal to -1 + // before actually using it. + virtual bool IsLocalCollectionItem() const { return false; } + void SetShouldSkip(bool val); + bool GetShouldSkip() const; + + protected: + bool should_skip_; + + enum DatabaseColumn { Column_CollectionId, Column_InternetService, }; + + virtual QVariant DatabaseValue(DatabaseColumn) const { + return QVariant(QVariant::String); + } + virtual Song DatabaseSongMetadata() const { return Song(); } + + QString type_; + + Song temp_metadata_; + + QMap background_colors_; + QMap foreground_colors_; +}; +typedef std::shared_ptr PlaylistItemPtr; +typedef QList PlaylistItemList; + +Q_DECLARE_METATYPE(PlaylistItemPtr) +Q_DECLARE_METATYPE(QList) +Q_DECLARE_OPERATORS_FOR_FLAGS(PlaylistItem::Options) + +#endif // PLAYLISTITEM_H diff --git a/src/playlist/playlistitemmimedata.h b/src/playlist/playlistitemmimedata.h new file mode 100644 index 00000000..2103e338 --- /dev/null +++ b/src/playlist/playlistitemmimedata.h @@ -0,0 +1,39 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTITEMMIMEDATA_H +#define PLAYLISTITEMMIMEDATA_H + +#include "config.h" + +#include "playlistitem.h" +#include "core/mimedata.h" + +class PlaylistItemMimeData : public MimeData { + Q_OBJECT + +public: + PlaylistItemMimeData(const PlaylistItemPtr &item) : items_(PlaylistItemList() << item) {} + PlaylistItemMimeData(const PlaylistItemList &items) : items_(items) {} + + PlaylistItemList items_; +}; + +#endif // PLAYLISTITEMMIMEDATA_H diff --git a/src/playlist/playlistlistcontainer.cpp b/src/playlist/playlistlistcontainer.cpp new file mode 100644 index 00000000..6f0d26d1 --- /dev/null +++ b/src/playlist/playlistlistcontainer.cpp @@ -0,0 +1,423 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "playlist.h" +#include "playlistlistcontainer.h" +#include "playlistlistmodel.h" +#include "playlistmanager.h" +#include "ui_playlistlistcontainer.h" +#include "core/application.h" +#include "core/logging.h" +#include "core/player.h" +#include "core/iconloader.h" + +class PlaylistListSortFilterModel : public QSortFilterProxyModel { +public: + explicit PlaylistListSortFilterModel(QObject *parent) + : QSortFilterProxyModel(parent) { + } + + bool lessThan(const QModelIndex &left, const QModelIndex &right) const { + // Compare the display text first. + const int ret = left.data().toString().localeAwareCompare(right.data().toString()); + if (ret < 0) return true; + if (ret > 0) return false; + + // Now use the source model row order to ensure we always get a + // deterministic sorting even when two items are named the same. + return left.row() < right.row(); + } +}; + + +PlaylistListContainer::PlaylistListContainer(QWidget *parent) + : QWidget(parent), + app_(nullptr), + ui_(new Ui_PlaylistListContainer), + menu_(nullptr), + action_new_folder_(new QAction(this)), + action_remove_(new QAction(this)), + action_save_playlist_(new QAction(this)), + model_(new PlaylistListModel(this)), + proxy_(new PlaylistListSortFilterModel(this)), + loaded_icons_(false), + active_playlist_id_(-1) +{ + + ui_->setupUi(this); + ui_->tree->setAttribute(Qt::WA_MacShowFocusRect, false); + + action_new_folder_->setText(tr("New folder")); + action_remove_->setText(tr("Delete")); + action_save_playlist_->setText(tr("Save playlist", "Save playlist menu action.")); + + ui_->new_folder->setDefaultAction(action_new_folder_); + ui_->remove->setDefaultAction(action_remove_); + ui_->save_playlist->setDefaultAction(action_save_playlist_); + + connect(action_new_folder_, SIGNAL(triggered()), SLOT(NewFolderClicked())); + connect(action_remove_, SIGNAL(triggered()), SLOT(DeleteClicked())); + connect(action_save_playlist_, SIGNAL(triggered()), SLOT(SavePlaylist())); + connect(model_, SIGNAL(PlaylistPathChanged(int, QString)), SLOT(PlaylistPathChanged(int, QString))); + + proxy_->setSourceModel(model_); + proxy_->setDynamicSortFilter(true); + proxy_->sort(0); + ui_->tree->setModel(proxy_); + + connect(ui_->tree, SIGNAL(doubleClicked(QModelIndex)), SLOT(ItemDoubleClicked(QModelIndex))); + + model_->invisibleRootItem()->setData(PlaylistListModel::Type_Folder, PlaylistListModel::Role_Type); + +} + +PlaylistListContainer::~PlaylistListContainer() { delete ui_; } + +void PlaylistListContainer::showEvent(QShowEvent *e) { + + // Loading icons is expensive so only do it when the view is first opened + if (loaded_icons_) { + QWidget::showEvent(e); + return; + } + loaded_icons_ = true; + + action_new_folder_->setIcon(IconLoader::Load("folder-new")); + action_remove_->setIcon(IconLoader::Load("edit-delete")); + action_save_playlist_->setIcon(IconLoader::Load("document-save")); + + model_->SetIcons(IconLoader::Load("view-media-playlist"), IconLoader::Load("folder")); + + // Apply these icons to items that have already been created. + RecursivelySetIcons(model_->invisibleRootItem()); + + QWidget::showEvent(e); + +} + +void PlaylistListContainer::RecursivelySetIcons(QStandardItem *parent) const { + + for (int i = 0; i < parent->rowCount(); ++i) { + QStandardItem *child = parent->child(i); + switch (child->data(PlaylistListModel::Role_Type).toInt()) { + case PlaylistListModel::Type_Folder: + child->setIcon(model_->folder_icon()); + RecursivelySetIcons(child); + break; + + case PlaylistListModel::Type_Playlist: + child->setIcon(model_->playlist_icon()); + break; + } + } + +} + +void PlaylistListContainer::SetApplication(Application *app) { + + app_ = app; + PlaylistManager *manager = app_->playlist_manager(); + Player *player = app_->player(); + + connect(manager, SIGNAL(PlaylistAdded(int, QString, bool)), SLOT(AddPlaylist(int, QString, bool))); + connect(manager, SIGNAL(PlaylistFavorited(int, bool)), SLOT(PlaylistFavoriteStateChanged(int, bool))); + connect(manager, SIGNAL(PlaylistRenamed(int, QString)), SLOT(PlaylistRenamed(int, QString))); + connect(manager, SIGNAL(CurrentChanged(Playlist*)), SLOT(CurrentChanged(Playlist*))); + connect(manager, SIGNAL(ActiveChanged(Playlist*)), SLOT(ActiveChanged(Playlist*))); + + connect(model_, SIGNAL(PlaylistRenamed(int,QString)), manager, SLOT(Rename(int,QString))); + + connect(player, SIGNAL(Paused()), SLOT(ActivePaused())); + connect(player, SIGNAL(Playing()), SLOT(ActivePlaying())); + connect(player, SIGNAL(Stopped()), SLOT(ActiveStopped())); + + // Get all playlists, even ones that are hidden in the UI. + for (const PlaylistBackend::Playlist &p : app->playlist_backend()->GetAllFavoritePlaylists()) { + QStandardItem *playlist_item = model_->NewPlaylist(p.name, p.id); + QStandardItem *parent_folder = model_->FolderByPath(p.ui_path); + parent_folder->appendRow(playlist_item); + } + +} + +void PlaylistListContainer::NewFolderClicked() { + + QString name = QInputDialog::getText(this, tr("New folder"), tr("Enter the name of the folder")); + if (name.isEmpty()) { + return; + } + + name.replace("/", " "); + + model_->invisibleRootItem()->appendRow(model_->NewFolder(name)); + +} + +void PlaylistListContainer::AddPlaylist(int id, const QString &name, bool favorite) { + + if (!favorite) { + return; + } + + if (model_->PlaylistById(id)) { + // We know about this playlist already - it was probably one of the open + // ones that was loaded on startup. + return; + } + + const QString &ui_path = app_->playlist_manager()->playlist(id)->ui_path(); + + QStandardItem *playlist_item = model_->NewPlaylist(name, id); + QStandardItem *parent_folder = model_->FolderByPath(ui_path); + parent_folder->appendRow(playlist_item); + +} + +void PlaylistListContainer::PlaylistRenamed(int id, const QString &new_name) { + + QStandardItem *item = model_->PlaylistById(id); + if (!item) { + return; + } + + item->setText(new_name); + +} + +void PlaylistListContainer::RemovePlaylist(int id) { + + QStandardItem *item = model_->PlaylistById(id); + if (item) { + QStandardItem *parent = item->parent(); + if (!parent) { + parent = model_->invisibleRootItem(); + } + parent->removeRow(item->row()); + } + +} + +void PlaylistListContainer::SavePlaylist() { + + const QModelIndex ¤t_index = proxy_->mapToSource(ui_->tree->currentIndex()); + + // Is it a playlist? + if (current_index.data(PlaylistListModel::Role_Type).toInt() == PlaylistListModel::Type_Playlist) { + const int playlist_id = current_index.data(PlaylistListModel::Role_PlaylistId).toInt(); + QStandardItem *item = model_->PlaylistById(playlist_id); + QString playlist_name = item ? item->text() : tr("Playlist"); + app_->playlist_manager()->SaveWithUI(playlist_id, playlist_name); + } + +} + +void PlaylistListContainer::PlaylistFavoriteStateChanged(int id, bool favorite) { + + if (favorite) { + const QString &name = app_->playlist_manager()->GetPlaylistName(id); + AddPlaylist(id, name, favorite); + } + else { + RemovePlaylist(id); + } + +} + +void PlaylistListContainer::ActiveChanged(Playlist *new_playlist) { + + const int new_id = new_playlist->id(); + + if (new_id != active_playlist_id_) { + UpdateActiveIcon(active_playlist_id_, QIcon()); + } + + active_playlist_id_ = new_id; + +} + +void PlaylistListContainer::CurrentChanged(Playlist *new_playlist) { + + if (!new_playlist) { + return; + } + + // Focus this playlist in the tree + QStandardItem *item = model_->PlaylistById(new_playlist->id()); + if (!item) { + return; + } + + QModelIndex index = proxy_->mapFromSource(item->index()); + ui_->tree->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); + ui_->tree->scrollTo(index); + +} + +void PlaylistListContainer::PlaylistPathChanged(int id, const QString &new_path) { + + // Update the path in the database + app_->playlist_backend()->SetPlaylistUiPath(id, new_path); + Playlist *playlist = app_->playlist_manager()->playlist(id); + // Check the playlist exists (if it's not opened it's not in the manager) + if (playlist) { + playlist->set_ui_path(new_path); + } + +} + +void PlaylistListContainer::ItemDoubleClicked(const QModelIndex &proxy_index) { + + const QModelIndex &index = proxy_->mapToSource(proxy_index); + + // Is it a playlist? + if (index.data(PlaylistListModel::Role_Type).toInt() == PlaylistListModel::Type_Playlist) { + app_->playlist_manager()->SetCurrentOrOpen(index.data(PlaylistListModel::Role_PlaylistId).toInt()); + } + +} + +void PlaylistListContainer::DeleteClicked() { + + QSet ids; + QList folders_to_delete; + + for (const QModelIndex &proxy_index : ui_->tree->selectionModel()->selectedRows(0)) { + const QModelIndex &index = proxy_->mapToSource(proxy_index); + + // Is it a playlist? + switch (index.data(PlaylistListModel::Role_Type).toInt()) { + case PlaylistListModel::Type_Playlist: + ids << index.data(PlaylistListModel::Role_PlaylistId).toInt(); + break; + + case PlaylistListModel::Type_Folder: + // Find all the playlists inside. + RecursivelyFindPlaylists(index, &ids); + folders_to_delete << index; + break; + } + } + + // Make sure the user really wants to unfavorite all these playlists. + if (ids.count() > 1) { + const int button = QMessageBox::question(this, tr("Remove playlists"), tr("You are about to remove %1 playlists from your favorites, are you sure?").arg(ids.count()), QMessageBox::Yes, QMessageBox::Cancel); + + if (button != QMessageBox::Yes) { + return; + } + } + + // Unfavorite the playlists + for (int id : ids) { + app_->playlist_manager()->Favorite(id, false); + } + + // Delete the top-level folders. + for (const QPersistentModelIndex &index : folders_to_delete) { + if (index.isValid()) { + model_->removeRow(index.row(), index.parent()); + } + } + +} + +void PlaylistListContainer::RecursivelyFindPlaylists(const QModelIndex &parent, QSet *ids) const { + + switch (parent.data(PlaylistListModel::Role_Type).toInt()) { + case PlaylistListModel::Type_Playlist: + ids->insert(parent.data(PlaylistListModel::Role_PlaylistId).toInt()); + break; + + case PlaylistListModel::Type_Folder: + for (int i = 0; i < parent.model()->rowCount(parent); ++i) { + RecursivelyFindPlaylists(parent.child(i, 0), ids); + } + break; + } + +} + +void PlaylistListContainer::contextMenuEvent(QContextMenuEvent *e) { + + if (!menu_) { + menu_ = new QMenu(this); + menu_->addAction(action_new_folder_); + menu_->addAction(action_remove_); + menu_->addSeparator(); + menu_->addAction(action_save_playlist_); + } + menu_->popup(e->globalPos()); + +} + +void PlaylistListContainer::ActivePlaying() { + + if (padded_play_icon_.isNull()) { + QPixmap pixmap(":pictures/tiny-play.png"); + QPixmap new_pixmap(QSize(pixmap.height(), pixmap.height())); + new_pixmap.fill(Qt::transparent); + + QPainter p(&new_pixmap); + p.drawPixmap((new_pixmap.width() - pixmap.width()) / 2, 0, pixmap.width(), pixmap.height(), pixmap); + p.end(); + + padded_play_icon_.addPixmap(new_pixmap); + } + UpdateActiveIcon(active_playlist_id_, padded_play_icon_); + +} + +void PlaylistListContainer::ActivePaused() { + UpdateActiveIcon(active_playlist_id_, QIcon(":pictures/tiny-pause.png")); +} + +void PlaylistListContainer::ActiveStopped() { + UpdateActiveIcon(active_playlist_id_, QIcon()); +} + +void PlaylistListContainer::UpdateActiveIcon(int id, const QIcon &icon) { + + if (id == -1) { + return; + } + + QStandardItem *item = model_->PlaylistById(id); + if (!item) { + return; + } + + if (icon.isNull()) { + item->setIcon(model_->playlist_icon()); + } + else { + item->setIcon(icon); + } + +} diff --git a/src/playlist/playlistlistcontainer.h b/src/playlist/playlistlistcontainer.h new file mode 100644 index 00000000..890c0773 --- /dev/null +++ b/src/playlist/playlistlistcontainer.h @@ -0,0 +1,102 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTLISTCONTAINER_H +#define PLAYLISTLISTCONTAINER_H + +#include "config.h" + +#include "playlistbackend.h" + +#include + +class QMenu; +class QSortFilterProxyModel; +class QStandardItemModel; + +class Application; +class Playlist; +class PlaylistListModel; +class Ui_PlaylistListContainer; + +class PlaylistListContainer : public QWidget { + Q_OBJECT + + public: + PlaylistListContainer(QWidget *parent = nullptr); + ~PlaylistListContainer(); + + void SetApplication(Application *app); + +protected: + void showEvent(QShowEvent *e); + void contextMenuEvent(QContextMenuEvent *e); + +private slots: + // From the UI + void NewFolderClicked(); + void DeleteClicked(); + void ItemDoubleClicked(const QModelIndex &index); + + // From the model + void PlaylistPathChanged(int id, const QString &new_path); + + // From the PlaylistManager + void PlaylistRenamed(int id, const QString &new_name); + // Add playlist if favorite == true + void AddPlaylist(int id, const QString &name, bool favorite); + void RemovePlaylist(int id); + void SavePlaylist(); + void PlaylistFavoriteStateChanged(int id, bool favorite); + void CurrentChanged(Playlist *new_playlist); + void ActiveChanged(Playlist *new_playlist); + + // From the Player + void ActivePlaying(); + void ActivePaused(); + void ActiveStopped(); + +private: + QStandardItem *ItemForPlaylist(const QString &name, int id); + QStandardItem *ItemForFolder(const QString &name) const; + void RecursivelySetIcons(QStandardItem *parent) const; + + void RecursivelyFindPlaylists(const QModelIndex &parent, QSet *ids) const; + + void UpdateActiveIcon(int id, const QIcon &icon); + + Application *app_; + Ui_PlaylistListContainer *ui_; + QMenu *menu_; + + QAction *action_new_folder_; + QAction *action_remove_; + QAction *action_save_playlist_; + + PlaylistListModel *model_; + QSortFilterProxyModel *proxy_; + + bool loaded_icons_; + QIcon padded_play_icon_; + + int active_playlist_id_; +}; + +#endif // PLAYLISTLISTCONTAINER_H diff --git a/src/playlist/playlistlistcontainer.ui b/src/playlist/playlistlistcontainer.ui new file mode 100644 index 00000000..9f9cafb9 --- /dev/null +++ b/src/playlist/playlistlistcontainer.ui @@ -0,0 +1,142 @@ + + + PlaylistListContainer + + + + 0 + 0 + 160 + 503 + + + + + 0 + 0 + + + + Form + + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + + + New folder + + + + + + + Delete + + + + + + + Qt::Vertical + + + + + + + + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + 70 + 20 + + + + + + + + + + + + 0 + 0 + + + + true + + + true + + + QAbstractItemView::InternalMove + + + Qt::MoveAction + + + QAbstractItemView::ExtendedSelection + + + + 16 + 16 + + + + false + + + + + + + + AutoExpandingTreeView + QTreeView +
widgets/autoexpandingtreeview.h
+
+ + PlaylistListView + AutoExpandingTreeView +
playlist/playlistlistview.h
+ 1 +
+
+ + +
diff --git a/src/playlist/playlistlistmodel.cpp b/src/playlist/playlistlistmodel.cpp new file mode 100644 index 00000000..fab85f10 --- /dev/null +++ b/src/playlist/playlistlistmodel.cpp @@ -0,0 +1,238 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "playlistlistmodel.h" +#include "core/logging.h" + +#include + +PlaylistListModel::PlaylistListModel(QObject *parent) : QStandardItemModel(parent), dropping_rows_(false) { + + connect(this, SIGNAL(dataChanged(QModelIndex, QModelIndex)), SLOT(RowsChanged(QModelIndex, QModelIndex))); + connect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), SLOT(RowsAboutToBeRemoved(QModelIndex, int, int))); + connect(this, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(RowsInserted(QModelIndex, int, int))); + +} + +void PlaylistListModel::SetIcons(const QIcon &playlist_icon, const QIcon &folder_icon) { + playlist_icon_ = playlist_icon; + folder_icon_ = folder_icon; +} + +bool PlaylistListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { + + dropping_rows_ = true; + bool ret = QStandardItemModel::dropMimeData(data, action, row, column, parent); + dropping_rows_ = false; + + return ret; +} + +QString PlaylistListModel::ItemPath(const QStandardItem *item) const { + + QStringList components; + + const QStandardItem *current = item; + while (current) { + if (current->data(Role_Type).toInt() == Type_Folder) { + components.insert(0, current->data(Qt::DisplayRole).toString()); + } + current = current->parent(); + } + + return components.join("/"); + +} + +void PlaylistListModel::RowsChanged(const QModelIndex &begin, const QModelIndex &end) { + AddRowMappings(begin, end); +} + +void PlaylistListModel::RowsInserted(const QModelIndex &parent, int start, int end) { + + // RowsChanged will take care of these when dropping. + if (!dropping_rows_) { + AddRowMappings(index(start, 0, parent), index(end, 0, parent)); + } + +} + +void PlaylistListModel::AddRowMappings(const QModelIndex &begin, const QModelIndex &end) { + + const QString parent_path = ItemPath(itemFromIndex(begin)); + + for (int i = begin.row(); i <= end.row(); ++i) { + const QModelIndex index = begin.sibling(i, 0); + QStandardItem *item = itemFromIndex(index); + AddRowItem(item, parent_path); + } + +} + +void PlaylistListModel::AddRowItem(QStandardItem *item, const QString &parent_path) { + + switch (item->data(Role_Type).toInt()) { + case Type_Playlist: { + const int id = item->data(Role_PlaylistId).toInt(); + + playlists_by_id_[id] = item; + if (dropping_rows_) { + emit PlaylistPathChanged(id, parent_path); + } + + break; + } + + case Type_Folder: + for (int j = 0; j < item->rowCount(); ++j) { + QStandardItem *child_item = item->child(j); + AddRowItem(child_item, parent_path); + } + break; + } + +} + +void PlaylistListModel::RowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { + + for (int i = start; i <= end; ++i) { + const QModelIndex idx = index(i, 0, parent); + const QStandardItem *item = itemFromIndex(idx); + + switch (idx.data(Role_Type).toInt()) { + case Type_Playlist: { + const int id = idx.data(Role_PlaylistId).toInt(); + QMap::Iterator it = playlists_by_id_.find(id); + if (it != playlists_by_id_.end() && it.value() == item) { + playlists_by_id_.erase(it); + } + break; + } + + case Type_Folder: + break; + } + } + +} + +QStandardItem *PlaylistListModel::PlaylistById(int id) const { + return playlists_by_id_[id]; +} + +QStandardItem *PlaylistListModel::FolderByPath(const QString &path) { + + if (path.isEmpty()) { + return invisibleRootItem(); + } + + // Walk down from the root until we find the target folder. This is pretty + // inefficient but maintaining a path -> item map is difficult. + QStandardItem *parent = invisibleRootItem(); + + const QStringList parts = path.split('/', QString::SkipEmptyParts); + for (const QString &part : parts) { + QStandardItem *matching_child = nullptr; + + const int child_count = parent->rowCount(); + for (int i = 0; i < child_count; ++i) { + if (parent->child(i)->data(Qt::DisplayRole).toString() == part) { + matching_child = parent->child(i); + break; + } + } + + // Does this folder exist already? + if (matching_child) { + parent = matching_child; + } + else { + QStandardItem *child = NewFolder(part); + parent->appendRow(child); + parent = child; + } + } + + return parent; + +} + +QStandardItem *PlaylistListModel::NewFolder(const QString &name) const { + + QStandardItem *ret = new QStandardItem; + ret->setText(name); + ret->setData(PlaylistListModel::Type_Folder, PlaylistListModel::Role_Type); + ret->setIcon(folder_icon_); + ret->setFlags(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); + return ret; + +} + +QStandardItem *PlaylistListModel::NewPlaylist(const QString &name, int id) const { + + QStandardItem *ret = new QStandardItem; + ret->setText(name); + ret->setData(PlaylistListModel::Type_Playlist, PlaylistListModel::Role_Type); + ret->setData(id, PlaylistListModel::Role_PlaylistId); + ret->setIcon(playlist_icon_); + ret->setFlags(Qt::ItemIsDragEnabled | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); + return ret; + +} + +bool PlaylistListModel::setData(const QModelIndex &index, const QVariant &value, int role) { + + if (!QStandardItemModel::setData(index, value, role)) { + return false; + } + + switch (index.data(Role_Type).toInt()) { + case Type_Playlist: + emit PlaylistRenamed(index.data(Role_PlaylistId).toInt(), value.toString()); + break; + + case Type_Folder: + // Walk all the children and modify their paths. + UpdatePathsRecursive(index); + break; + } + + return true; + +} + +void PlaylistListModel::UpdatePathsRecursive(const QModelIndex &parent) { + + switch (parent.data(Role_Type).toInt()) { + case Type_Playlist: + emit PlaylistPathChanged(parent.data(Role_PlaylistId).toInt(), ItemPath(itemFromIndex(parent))); + break; + + case Type_Folder: + for (int i = 0; i < rowCount(parent); ++i) { + UpdatePathsRecursive(index(i, 0, parent)); + } + break; + } + +} + diff --git a/src/playlist/playlistlistmodel.h b/src/playlist/playlistlistmodel.h new file mode 100644 index 00000000..c1d12754 --- /dev/null +++ b/src/playlist/playlistlistmodel.h @@ -0,0 +1,98 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTLISTMODEL_H +#define PLAYLISTLISTMODEL_H + +#include "config.h" + +#include + +class PlaylistListModel : public QStandardItemModel { + Q_OBJECT + + public: + PlaylistListModel(QObject *parent = nullptr); + + enum Types { + Type_Folder, + Type_Playlist + }; + + enum Roles { + Role_Type = Qt::UserRole, + Role_PlaylistId + }; + + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + + // These icons will be used for newly created playlists and folders. + // The caller will need to set these icons on existing items if there are any. + void SetIcons(const QIcon &playlist_icon, const QIcon &folder_icon); + const QIcon &playlist_icon() const { return playlist_icon_; } + const QIcon &folder_icon() const { return folder_icon_; } + + // Walks from the given item to the root, returning the / separated path of + // all the parent folders. The path includes this item if it is a folder. + QString ItemPath(const QStandardItem *item) const; + + // Finds the playlist with the given ID, returns 0 if it doesn't exist. + QStandardItem *PlaylistById(int id) const; + + // Finds the folder with the given path, creating it (and its parents) if they + // do not exist. Returns invisibleRootItem() if path is empty. + QStandardItem *FolderByPath(const QString &path); + + // Returns a new folder item with the given name. The item isn't added to + // the model yet. + QStandardItem *NewFolder(const QString &name) const; + + // Returns a new playlist item with the given name and ID. The item isn't + // added to the model yet. + QStandardItem *NewPlaylist(const QString &name, int id) const; + + // QStandardItemModel + bool setData(const QModelIndex &index, const QVariant &value, int role); + +signals: + void PlaylistPathChanged(int id, const QString &new_path); + void PlaylistRenamed(int id, const QString &new_name); + + private slots: + void RowsChanged(const QModelIndex &begin, const QModelIndex &end); + void RowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + void RowsInserted(const QModelIndex &parent, int start, int end); + + private: + void AddRowMappings(const QModelIndex &begin, const QModelIndex &end); + void AddRowItem(QStandardItem *item, const QString &parent_path); + void UpdatePathsRecursive(const QModelIndex &parent); + + private: + bool dropping_rows_; + + QIcon playlist_icon_; + QIcon folder_icon_; + + QMap playlists_by_id_; + QMap folders_by_path_; +}; + +#endif // PLAYLISTLISTMODEL_H diff --git a/src/playlist/playlistlistview.cpp b/src/playlist/playlistlistview.cpp new file mode 100644 index 00000000..c18cbb9a --- /dev/null +++ b/src/playlist/playlistlistview.cpp @@ -0,0 +1,51 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2013, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "playlistlistview.h" + +#include + +PlaylistListView::PlaylistListView(QWidget *parent) + : AutoExpandingTreeView(parent) {} + +void PlaylistListView::paintEvent(QPaintEvent *event) { + + if (model()->rowCount() <= 0) { + QPainter p(viewport()); + QRect rect(viewport()->rect()); + + p.setPen(palette().color(QPalette::Disabled, QPalette::Text)); + + QFont bold_font; + bold_font.setBold(true); + p.setFont(bold_font); + + p.drawText(rect, Qt::AlignHCenter | Qt::TextWordWrap, + tr("\n\n" + "You can favorite playlists by clicking the star icon next " + "to a playlist name\n\n" + "Favorited playlists will be saved here")); + } + else { + AutoExpandingTreeView::paintEvent(event); + } +} diff --git a/src/playlist/playlistlistview.h b/src/playlist/playlistlistview.h new file mode 100644 index 00000000..8d7ac9fa --- /dev/null +++ b/src/playlist/playlistlistview.h @@ -0,0 +1,35 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2013, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "widgets/autoexpandingtreeview.h" + +class PlaylistListView : public AutoExpandingTreeView { + Q_OBJECT + + public: + PlaylistListView(QWidget *parent = nullptr); + ~PlaylistListView() {} + + protected: + // QWidget + void paintEvent(QPaintEvent* event); +}; diff --git a/src/playlist/playlistmanager.cpp b/src/playlist/playlistmanager.cpp new file mode 100644 index 00000000..b7545dd3 --- /dev/null +++ b/src/playlist/playlistmanager.cpp @@ -0,0 +1,568 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "playlistmanager.h" + +#include "playlistbackend.h" +#include "playlistcontainer.h" +#include "playlistmanager.h" +#include "playlistsaveoptionsdialog.h" +#include "playlistview.h" +#include "core/application.h" +#include "core/logging.h" +#include "core/player.h" +#include "core/songloader.h" +#include "core/utilities.h" +#include "collection/collectionbackend.h" +#include "collection/collectionplaylistitem.h" +#include "playlistparsers/playlistparser.h" + +PlaylistManager::PlaylistManager(Application *app, QObject *parent) + : PlaylistManagerInterface(app, parent), + app_(app), + playlist_backend_(nullptr), + collection_backend_(nullptr), + sequence_(nullptr), + parser_(nullptr), + playlist_container_(nullptr), + current_(-1), + active_(-1) +{ + connect(app_->player(), SIGNAL(Paused()), SLOT(SetActivePaused())); + connect(app_->player(), SIGNAL(Playing()), SLOT(SetActivePlaying())); + connect(app_->player(), SIGNAL(Stopped()), SLOT(SetActiveStopped())); +} + +PlaylistManager::~PlaylistManager() { + for (const Data &data : playlists_.values()) { + delete data.p; + } +} + +void PlaylistManager::Init(CollectionBackend *collection_backend, PlaylistBackend *playlist_backend, PlaylistSequence *sequence, PlaylistContainer *playlist_container) { + + collection_backend_ = collection_backend; + playlist_backend_ = playlist_backend; + sequence_ = sequence; + parser_ = new PlaylistParser(collection_backend, this); + playlist_container_ = playlist_container; + + connect(collection_backend_, SIGNAL(SongsDiscovered(SongList)), SLOT(SongsDiscovered(SongList))); + + for (const PlaylistBackend::Playlist &p : playlist_backend->GetAllOpenPlaylists()) { + AddPlaylist(p.id, p.name, p.special_type, p.ui_path, p.favorite); + } + + // If no playlist exists then make a new one + if (playlists_.isEmpty()) New(tr("Playlist")); + + emit PlaylistManagerInitialized(); + +} + +QList PlaylistManager::GetAllPlaylists() const { + + QList result; + + for (const Data &data : playlists_.values()) { + result.append(data.p); + } + + return result; + +} + +QItemSelection PlaylistManager::selection(int id) const { + QMap::const_iterator it = playlists_.find(id); + return it->selection; +} + +Playlist *PlaylistManager::AddPlaylist(int id, const QString &name, const QString &special_type, const QString &ui_path, bool favorite) { + + Playlist *ret = new Playlist(playlist_backend_, app_->task_manager(), collection_backend_, id, special_type, favorite); + ret->set_sequence(sequence_); + ret->set_ui_path(ui_path); + + connect(ret, SIGNAL(CurrentSongChanged(Song)), SIGNAL(CurrentSongChanged(Song))); + connect(ret, SIGNAL(PlaylistChanged()), SLOT(OneOfPlaylistsChanged())); + connect(ret, SIGNAL(PlaylistChanged()), SLOT(UpdateSummaryText())); + connect(ret, SIGNAL(EditingFinished(QModelIndex)), SIGNAL(EditingFinished(QModelIndex))); + connect(ret, SIGNAL(Error(QString)), SIGNAL(Error(QString))); + connect(ret, SIGNAL(PlayRequested(QModelIndex)), SIGNAL(PlayRequested(QModelIndex))); + connect(playlist_container_->view(), SIGNAL(ColumnAlignmentChanged(ColumnAlignmentMap)), ret, SLOT(SetColumnAlignment(ColumnAlignmentMap))); + + playlists_[id] = Data(ret, name); + + emit PlaylistAdded(id, name, favorite); + + if (current_ == -1) { + SetCurrentPlaylist(id); + } + if (active_ == -1) { + SetActivePlaylist(id); + } + + return ret; + +} + +void PlaylistManager::New(const QString &name, const SongList &songs, const QString &special_type) { + + if (name.isNull()) return; + + int id = playlist_backend_->CreatePlaylist(name, special_type); + + if (id == -1) qFatal("Couldn't create playlist"); + + Playlist *playlist = AddPlaylist(id, name, special_type, QString(), false); + playlist->InsertSongsOrCollectionItems(songs); + + SetCurrentPlaylist(id); + + // If the name is just "Playlist", append the id + if (name == tr("Playlist")) { + Rename(id, QString("%1 %2").arg(name).arg(id)); + } + +} + +void PlaylistManager::Load(const QString &filename) { + + QFileInfo info(filename); + + int id = playlist_backend_->CreatePlaylist(info.baseName(), QString()); + + if (id == -1) { + emit Error(tr("Couldn't create playlist")); + return; + } + + Playlist *playlist = AddPlaylist(id, info.baseName(), QString(), QString(), false); + + QList urls; + playlist->InsertUrls(urls << QUrl::fromLocalFile(filename)); + +} + +void PlaylistManager::Save(int id, const QString &filename, Playlist::Path path_type) { + + if (playlists_.contains(id)) { + parser_->Save(playlist(id)->GetAllSongs(), filename, path_type); + } + else { + // Playlist is not in the playlist manager: probably save action was + // triggered + // from the left side bar and the playlist isn't loaded. + QFuture> future = QtConcurrent::run(playlist_backend_, &PlaylistBackend::GetPlaylistSongs, id); + + NewClosure(future, this, SLOT(ItemsLoadedForSavePlaylist(QFuture, QString, Playlist::Path)), future, filename, path_type); + } + +} + +void PlaylistManager::ItemsLoadedForSavePlaylist(QFuture future, const QString &filename, Playlist::Path path_type) { + + parser_->Save(future.result(), filename, path_type); + +} + +void PlaylistManager::SaveWithUI(int id, const QString &playlist_name) { + + QSettings settings; + settings.beginGroup(Playlist::kSettingsGroup); + QString filename = settings.value("last_save_playlist").toString(); + QString extension = settings.value("last_save_extension", parser()->default_extension()).toString(); + QString filter = settings.value("last_save_filter", parser()->default_filter()).toString(); + + QString suggested_filename = playlist_name; + suggested_filename.replace(QRegExp("\\W"), ""); + + qLog(Debug) << "Using extension:" << extension; + + // We want to use the playlist tab name (with disallowed characters removed) + // as a default filename, but in the same directory as the last saved file. + + // Strip off filename components until we find something that's a folder + forever { + QFileInfo fileinfo(filename); + if (filename.isEmpty() || fileinfo.isDir()) break; + + filename = filename.section('/', 0, -2); + } + + // Use the home directory as a fallback in case the path is empty. + if (filename.isEmpty()) filename = QDir::homePath(); + + // Add the suggested filename + filename += "/" + suggested_filename + "." + extension; + qLog(Debug) << "Suggested filename:" << filename; + + filename = QFileDialog::getSaveFileName(nullptr, tr("Save playlist", "Title of the playlist save dialog."), filename, parser()->filters(), &filter); + + if (filename.isNull()) { + return; + } + + // Check if the file extension is valid. Fallback to the default if not. + QFileInfo info(filename); + ParserBase *parser = parser_->ParserForExtension(info.suffix()); + if (!parser) { + qLog(Warning) << "Unknown file extension:" << info.suffix(); + filename = info.absolutePath() + "/" + info.fileName() + "." + parser_->default_extension(); + info.setFile(filename); + filter = info.suffix(); + } + + int p = settings.value(Playlist::kPathType, Playlist::Path_Automatic).toInt(); + Playlist::Path path = static_cast(p); + if (path == Playlist::Path_Ask_User) { + PlaylistSaveOptionsDialog optionsDialog(nullptr); + optionsDialog.setModal(true); + if (optionsDialog.exec() != QDialog::Accepted) { + return; + } + path = optionsDialog.path_type(); + } + + settings.setValue("last_save_playlist", filename); + settings.setValue("last_save_filter", filter); + settings.setValue("last_save_extension", info.suffix()); + + Save(id == -1 ? current_id() : id, filename, path); + +} + +void PlaylistManager::Rename(int id, const QString &new_name) { + + Q_ASSERT(playlists_.contains(id)); + + playlist_backend_->RenamePlaylist(id, new_name); + playlists_[id].name = new_name; + + emit PlaylistRenamed(id, new_name); + +} + +void PlaylistManager::Favorite(int id, bool favorite) { + + if (playlists_.contains(id)) { + // If playlists_ contains this playlist, its means it's opened: star or + // unstar it. + playlist_backend_->FavoritePlaylist(id, favorite); + playlists_[id].p->set_favorite(favorite); + } + else { + Q_ASSERT(!favorite); + // Otherwise it means user wants to remove this playlist from the left + // panel, + // while it's not visible in the playlist tabbar either, because it has been + // closed: delete it. + playlist_backend_->RemovePlaylist(id); + } + emit PlaylistFavorited(id, favorite); + +} + +bool PlaylistManager::Close(int id) { + + // Won't allow removing the last playlist + if (playlists_.count() <= 1 || !playlists_.contains(id)) return false; + + int next_id = -1; + for (int possible_next_id : playlists_.keys()) { + if (possible_next_id != id) { + next_id = possible_next_id; + break; + } + } + if (next_id == -1) return false; + + if (id == active_) SetActivePlaylist(next_id); + if (id == current_) SetCurrentPlaylist(next_id); + + Data data = playlists_.take(id); + emit PlaylistClosed(id); + + if (!data.p->is_favorite()) { + playlist_backend_->RemovePlaylist(id); + emit PlaylistDeleted(id); + } + delete data.p; + + return true; + +} + +void PlaylistManager::Delete(int id) { + + if (!Close(id)) { + return; + } + + playlist_backend_->RemovePlaylist(id); + emit PlaylistDeleted(id); + +} + +void PlaylistManager::OneOfPlaylistsChanged() { + emit PlaylistChanged(qobject_cast(sender())); +} + +void PlaylistManager::SetCurrentPlaylist(int id) { + + Q_ASSERT(playlists_.contains(id)); + current_ = id; + emit CurrentChanged(current()); + UpdateSummaryText(); + +} + +void PlaylistManager::SetActivePlaylist(int id) { + + Q_ASSERT(playlists_.contains(id)); + + // Kinda a hack: unset the current item from the old active playlist before + // setting the new one + if (active_ != -1 && active_ != id) active()->set_current_row(-1); + + active_ = id; + emit ActiveChanged(active()); + +} + +void PlaylistManager::SetActiveToCurrent() { + + // Check if we need to update the active playlist. + // By calling SetActiveToCurrent, the playlist manager emits the signal + // "ActiveChanged". This signal causes the network remote module to + // send all playlists to the clients, even no change happend. + if (current_id() != active_id()) { + SetActivePlaylist(current_id()); + } + +} + +void PlaylistManager::ClearCurrent() { + current()->Clear(); +} + +void PlaylistManager::ShuffleCurrent() { + current()->Shuffle(); +} + +void PlaylistManager::RemoveDuplicatesCurrent() { + current()->RemoveDuplicateSongs(); +} + +void PlaylistManager::RemoveUnavailableCurrent() { + current()->RemoveUnavailableSongs(); +} + +void PlaylistManager::SetActivePlaying() { active()->Playing(); } + +void PlaylistManager::SetActivePaused() { active()->Paused(); } + +void PlaylistManager::SetActiveStopped() { active()->Stopped(); } + +void PlaylistManager::ChangePlaylistOrder(const QList &ids) { + playlist_backend_->SetPlaylistOrder(ids); +} + +void PlaylistManager::UpdateSummaryText() { + + int tracks = current()->rowCount(); + quint64 nanoseconds = 0; + int selected = 0; + + // Get the length of the selected tracks + for (const QItemSelectionRange &range : playlists_[current_id()].selection) { + if (!range.isValid()) continue; + + selected += range.bottom() - range.top() + 1; + for (int i = range.top() ; i <= range.bottom() ; ++i) { + qint64 length = range.model()->index(i, Playlist::Column_Length).data().toLongLong(); + if (length > 0) + nanoseconds += length; + } + } + + QString summary; + if (selected > 1) { + summary += tr("%1 selected of").arg(selected) + " "; + } else { + nanoseconds = current()->GetTotalLength(); + } + + // TODO: Make the plurals translatable + summary += tracks == 1 ? tr("1 track") : tr("%1 tracks").arg(tracks); + + if (nanoseconds) + summary += " - [ " + Utilities::WordyTimeNanosec(nanoseconds) + " ]"; + + emit SummaryTextChanged(summary); + +} + +void PlaylistManager::SelectionChanged(const QItemSelection &selection) { + playlists_[current_id()].selection = selection; + UpdateSummaryText(); +} + +void PlaylistManager::SongsDiscovered(const SongList &songs) { + + // Some songs might've changed in the collection, let's update any playlist + // items we have that match those songs + + for (const Song &song : songs) { + for (const Data &data : playlists_) { + PlaylistItemList items = data.p->collection_items_by_id(song.id()); + for (PlaylistItemPtr item : items) { + if (item->Metadata().directory_id() != song.directory_id()) continue; + static_cast(item.get())->SetMetadata(song); + data.p->ItemChanged(item); + } + } + } + +} + +// When Player has processed the new song chosen by the user... +void PlaylistManager::SongChangeRequestProcessed(const QUrl &url, bool valid) { + + for (Playlist *playlist : GetAllPlaylists()) { + if (playlist->ApplyValidityOnCurrentSong(url, valid)) { + return; + } + } + +} + +void PlaylistManager::InsertUrls(int id, const QList &urls, int pos, bool play_now, bool enqueue) { + + Q_ASSERT(playlists_.contains(id)); + + playlists_[id].p->InsertUrls(urls, pos, play_now, enqueue); + +} + +void PlaylistManager::InsertSongs(int id, const SongList &songs, int pos, bool play_now, bool enqueue) { + + Q_ASSERT(playlists_.contains(id)); + + playlists_[id].p->InsertSongs(songs, pos, play_now, enqueue); + +} + +void PlaylistManager::RemoveItemsWithoutUndo(int id, const QList &indices) { + + Q_ASSERT(playlists_.contains(id)); + + playlists_[id].p->RemoveItemsWithoutUndo(indices); + +} + +void PlaylistManager::RemoveCurrentSong() { + active()->removeRows(active()->current_index().row(), 1); +} + +void PlaylistManager::InvalidateDeletedSongs() { + for (Playlist *playlist : GetAllPlaylists()) { + playlist->InvalidateDeletedSongs(); + } +} + +void PlaylistManager::RemoveDeletedSongs() { + + for (Playlist *playlist : GetAllPlaylists()) { + playlist->RemoveDeletedSongs(); + } + +} + +QString PlaylistManager::GetNameForNewPlaylist(const SongList &songs) { + + if (songs.isEmpty()) { + return tr("Playlist"); + } + + QSet artists; + QSet albums; + + for (const Song &song : songs) { + artists << (song.artist().isEmpty() ? tr("Unknown") : song.artist()); + albums << (song.album().isEmpty() ? tr("Unknown") : song.album()); + + if (artists.size() > 1) { + break; + } + } + + bool various_artists = artists.size() > 1; + + QString result; + if (various_artists) { + result = tr("Various artists"); + } + else { + result = artists.values().first(); + } + + if (!various_artists && albums.size() == 1) { + result += " - " + albums.toList().first(); + } + + return result; + +} + +void PlaylistManager::Open(int id) { + + if (playlists_.contains(id)) { + return; + } + + const PlaylistBackend::Playlist &p = playlist_backend_->GetPlaylist(id); + if (p.id != id) { + return; + } + + AddPlaylist(p.id, p.name, p.special_type, p.ui_path, p.favorite); + +} + +void PlaylistManager::SetCurrentOrOpen(int id) { + + Open(id); + SetCurrentPlaylist(id); + +} + +bool PlaylistManager::IsPlaylistOpen(int id) { + return playlists_.contains(id); +} diff --git a/src/playlist/playlistmanager.h b/src/playlist/playlistmanager.h new file mode 100644 index 00000000..a8c94073 --- /dev/null +++ b/src/playlist/playlistmanager.h @@ -0,0 +1,243 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTMANAGER_H +#define PLAYLISTMANAGER_H + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "core/song.h" +#include "playlist.h" + +class Application; +class CollectionBackend; +class PlaylistBackend; +class PlaylistContainer; +class PlaylistParser; +class PlaylistSequence; +class TaskManager; + +class QModelIndex; +class QUrl; + +class PlaylistManagerInterface : public QObject { + Q_OBJECT + +public: + PlaylistManagerInterface(Application *app, QObject *parent) : QObject(parent) {} + + virtual int current_id() const = 0; + virtual int active_id() const = 0; + + virtual Playlist *playlist(int id) const = 0; + virtual Playlist *current() const = 0; + virtual Playlist *active() const = 0; + + // Returns the collection of playlists managed by this PlaylistManager. + virtual QList GetAllPlaylists() const = 0; + // Grays out and reloads all deleted songs in all playlists. + virtual void InvalidateDeletedSongs() = 0; + // Removes all deleted songs from all playlists. + virtual void RemoveDeletedSongs() = 0; + + virtual QItemSelection selection(int id) const = 0; + virtual QItemSelection current_selection() const = 0; + virtual QItemSelection active_selection() const = 0; + + virtual QString GetPlaylistName(int index) const = 0; + + virtual CollectionBackend *collection_backend() const = 0; + virtual PlaylistBackend *playlist_backend() const = 0; + virtual PlaylistSequence *sequence() const = 0; + virtual PlaylistParser *parser() const = 0; + virtual PlaylistContainer *playlist_container() const = 0; + +public slots: + virtual void New(const QString& name, const SongList& songs = SongList(), const QString& special_type = QString()) = 0; + virtual void Load(const QString& filename) = 0; + virtual void Save(int id, const QString& filename, Playlist::Path path_type) = 0; + virtual void Rename(int id, const QString& new_name) = 0; + virtual void Delete(int id) = 0; + virtual bool Close(int id) = 0; + virtual void Open(int id) = 0; + virtual void ChangePlaylistOrder(const QList& ids) = 0; + + virtual void SongChangeRequestProcessed(const QUrl& url, bool valid) = 0; + + virtual void SetCurrentPlaylist(int id) = 0; + virtual void SetActivePlaylist(int id) = 0; + virtual void SetActiveToCurrent() = 0; + + virtual void SelectionChanged(const QItemSelection& selection) = 0; + + // Convenience slots that defer to either current() or active() + virtual void ClearCurrent() = 0; + virtual void ShuffleCurrent() = 0; + virtual void RemoveDuplicatesCurrent() = 0; + virtual void RemoveUnavailableCurrent() = 0; + virtual void SetActivePlaying() = 0; + virtual void SetActivePaused() = 0; + virtual void SetActiveStopped() = 0; + +signals: + void PlaylistManagerInitialized(); + + void PlaylistAdded(int id, const QString& name, bool favorite); + void PlaylistDeleted(int id); + void PlaylistClosed(int id); + void PlaylistRenamed(int id, const QString& new_name); + void PlaylistFavorited(int id, bool favorite); + void CurrentChanged(Playlist *new_playlist); + void ActiveChanged(Playlist *new_playlist); + + void Error(const QString& message); + void SummaryTextChanged(const QString& summary); + + // Forwarded from individual playlists + void CurrentSongChanged(const Song& song); + + // Signals that one of manager's playlists has changed (new items, new + // ordering etc.) - the argument shows which. + void PlaylistChanged(Playlist *playlist); + void EditingFinished(const QModelIndex& index); + void PlayRequested(const QModelIndex& index); +}; + +class PlaylistManager : public PlaylistManagerInterface { + Q_OBJECT + + public: + PlaylistManager(Application *app, QObject *parent = nullptr); + ~PlaylistManager(); + + int current_id() const { return current_; } + int active_id() const { return active_; } + + Playlist *playlist(int id) const { return playlists_[id].p; } + Playlist *current() const { return playlist(current_id()); } + Playlist *active() const { return playlist(active_id()); } + + // Returns the collection of playlists managed by this PlaylistManager. + QList GetAllPlaylists() const; + // Grays out and reloads all deleted songs in all playlists. + void InvalidateDeletedSongs(); + // Removes all deleted songs from all playlists. + void RemoveDeletedSongs(); + // Returns true if the playlist is open + bool IsPlaylistOpen(int id); + + // Returns a pretty automatic name for playlist created from the given list of songs. + static QString GetNameForNewPlaylist(const SongList& songs); + + QItemSelection selection(int id) const; + QItemSelection current_selection() const { return selection(current_id()); } + QItemSelection active_selection() const { return selection(active_id()); } + + QString GetPlaylistName(int index) const { return playlists_[index].name; } + bool IsPlaylistFavorite(int index) const { return playlists_[index].p->is_favorite(); } + + void Init(CollectionBackend *collection_backend, PlaylistBackend *playlist_backend, PlaylistSequence *sequence, PlaylistContainer *playlist_container); + + CollectionBackend *collection_backend() const { return collection_backend_; } + PlaylistBackend *playlist_backend() const { return playlist_backend_; } + PlaylistSequence *sequence() const { return sequence_; } + PlaylistParser *parser() const { return parser_; } + PlaylistContainer *playlist_container() const { return playlist_container_; } + +public slots: + void New(const QString& name, const SongList& songs = SongList(), const QString& special_type = QString()); + void Load(const QString& filename); + void Save(int id, const QString& filename, Playlist::Path path_type); + // Display a file dialog to let user choose a file before saving the file + void SaveWithUI(int id, const QString& playlist_name); + void Rename(int id, const QString& new_name); + void Favorite(int id, bool favorite); + void Delete(int id); + bool Close(int id); + void Open(int id); + void ChangePlaylistOrder(const QList& ids); + + void SetCurrentPlaylist(int id); + void SetActivePlaylist(int id); + void SetActiveToCurrent(); + + void SelectionChanged(const QItemSelection& selection); + + // Makes a playlist current if it's open already, or opens it and makes it current if it is hidden. + void SetCurrentOrOpen(int id); + + // Convenience slots that defer to either current() or active() + void ClearCurrent(); + void ShuffleCurrent(); + void RemoveDuplicatesCurrent(); + void RemoveUnavailableCurrent(); + //void SetActiveStreamMetadata(const QUrl& url, const Song& song); + + void SongChangeRequestProcessed(const QUrl& url, bool valid); + + void InsertUrls(int id, const QList& urls, int pos = -1, bool play_now = false, bool enqueue = false); + void InsertSongs(int id, const SongList& songs, int pos = -1, bool play_now = false, bool enqueue = false); + // Removes items with given indices from the playlist. This operation is not undoable. + void RemoveItemsWithoutUndo(int id, const QList& indices); + // Remove the current playing song + void RemoveCurrentSong(); + + private slots: + void SetActivePlaying(); + void SetActivePaused(); + void SetActiveStopped(); + + void OneOfPlaylistsChanged(); + void UpdateSummaryText(); + void SongsDiscovered(const SongList& songs); + void ItemsLoadedForSavePlaylist(QFuture future, const QString& filename, Playlist::Path path_type); + + private: + Playlist *AddPlaylist(int id, const QString& name, const QString& special_type, const QString& ui_path, bool favorite); + +private: + struct Data { + Data(Playlist *_p = nullptr, const QString& _name = QString()) : p(_p), name(_name) {} + Playlist *p; + QString name; + QItemSelection selection; + }; + + Application *app_; + PlaylistBackend *playlist_backend_; + CollectionBackend *collection_backend_; + PlaylistSequence *sequence_; + PlaylistParser *parser_; + PlaylistContainer *playlist_container_; + + // key = id + QMap playlists_; + + int current_; + int active_; +}; + +#endif // PLAYLISTMANAGER_H diff --git a/src/playlist/playlistsaveoptionsdialog.cpp b/src/playlist/playlistsaveoptionsdialog.cpp new file mode 100644 index 00000000..967da87b --- /dev/null +++ b/src/playlist/playlistsaveoptionsdialog.cpp @@ -0,0 +1,58 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2014, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "playlistsaveoptionsdialog.h" + +#include "ui_playlistsaveoptionsdialog.h" +#include "playlistparsers/parserbase.h" + +#include + +const char *PlaylistSaveOptionsDialog::kSettingsGroup = "PlaylistSaveOptionsDialog"; + +PlaylistSaveOptionsDialog::PlaylistSaveOptionsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::PlaylistSaveOptionsDialog) { + + ui->setupUi(this); + + ui->filePaths->addItem(tr("Automatic"), Playlist::Path_Automatic); + ui->filePaths->addItem(tr("Relative"), Playlist::Path_Relative); + ui->filePaths->addItem(tr("Absolute"), Playlist::Path_Absolute); +} + +PlaylistSaveOptionsDialog::~PlaylistSaveOptionsDialog() { delete ui; } + +void PlaylistSaveOptionsDialog::accept() { + + if (ui->remember_user_choice->isChecked()) { + QSettings s; + s.beginGroup(Playlist::kSettingsGroup); + s.setValue(Playlist::kPathType, ui->filePaths->itemData(ui->filePaths->currentIndex()).toInt()); + } + + QDialog::accept(); + +} + +Playlist::Path PlaylistSaveOptionsDialog::path_type() const { + return static_cast(ui->filePaths->itemData(ui->filePaths->currentIndex()).toInt()); +} + diff --git a/src/playlist/playlistsaveoptionsdialog.h b/src/playlist/playlistsaveoptionsdialog.h new file mode 100644 index 00000000..8ec33011 --- /dev/null +++ b/src/playlist/playlistsaveoptionsdialog.h @@ -0,0 +1,50 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2014, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTSAVEOPTIONSDIALOG_H +#define PLAYLISTSAVEOPTIONSDIALOG_H + +#include "config.h" + +#include + +#include "playlist.h" + +namespace Ui { +class PlaylistSaveOptionsDialog; +} + +class PlaylistSaveOptionsDialog : public QDialog { + Q_OBJECT + + public: + explicit PlaylistSaveOptionsDialog(QWidget *parent = 0); + ~PlaylistSaveOptionsDialog(); + + void accept(); + Playlist::Path path_type() const; + + private: + static const char *kSettingsGroup; + + Ui::PlaylistSaveOptionsDialog* ui; +}; + +#endif // PLAYLISTSAVEOPTIONSDIALOG_H diff --git a/src/playlist/playlistsaveoptionsdialog.ui b/src/playlist/playlistsaveoptionsdialog.ui new file mode 100644 index 00000000..c2e004b4 --- /dev/null +++ b/src/playlist/playlistsaveoptionsdialog.ui @@ -0,0 +1,108 @@ + + + PlaylistSaveOptionsDialog + + + + 0 + 0 + 348 + 116 + + + + Playlist options + + + + + + + + File paths + + + + + + + + + + + + 0 + + + + + This can be changed later through the preferences + + + Remember my choice + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + PlaylistSaveOptionsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + PlaylistSaveOptionsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/playlist/playlistsequence.cpp b/src/playlist/playlistsequence.cpp new file mode 100644 index 00000000..300ba401 --- /dev/null +++ b/src/playlist/playlistsequence.cpp @@ -0,0 +1,254 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "playlistsequence.h" +#include "ui_playlistsequence.h" +#include "core/iconloader.h" + +#include +#include +#include +#include +#include + +const char *PlaylistSequence::kSettingsGroup = "PlaylistSequence"; + +PlaylistSequence::PlaylistSequence(QWidget *parent, SettingsProvider *settings) + : QWidget(parent), + ui_(new Ui_PlaylistSequence), + settings_(settings ? settings : new DefaultSettingsProvider), + repeat_menu_(new QMenu(this)), + shuffle_menu_(new QMenu(this)), + loading_(false), + repeat_mode_(Repeat_Off), + shuffle_mode_(Shuffle_Off), + dynamic_(false) +{ + + ui_->setupUi(this); + + // Icons + ui_->repeat->setIcon(AddDesaturatedIcon(IconLoader::Load("media-playlist-repeat"))); + ui_->shuffle->setIcon(AddDesaturatedIcon(IconLoader::Load("media-playlist-shuffle"))); + + // Remove arrow indicators + ui_->repeat->setStyleSheet("QToolButton::menu-indicator { image: none; }"); + ui_->shuffle->setStyleSheet("QToolButton::menu-indicator { image: none; }"); + + settings_->set_group(kSettingsGroup); + + QActionGroup *repeat_group = new QActionGroup(this); + repeat_group->addAction(ui_->action_repeat_off); + repeat_group->addAction(ui_->action_repeat_track); + repeat_group->addAction(ui_->action_repeat_album); + repeat_group->addAction(ui_->action_repeat_playlist); + repeat_group->addAction(ui_->action_repeat_onebyone); + repeat_group->addAction(ui_->action_repeat_intro); + repeat_menu_->addActions(repeat_group->actions()); + ui_->repeat->setMenu(repeat_menu_); + + QActionGroup *shuffle_group = new QActionGroup(this); + shuffle_group->addAction(ui_->action_shuffle_off); + shuffle_group->addAction(ui_->action_shuffle_all); + shuffle_group->addAction(ui_->action_shuffle_inside_album); + shuffle_group->addAction(ui_->action_shuffle_albums); + shuffle_menu_->addActions(shuffle_group->actions()); + ui_->shuffle->setMenu(shuffle_menu_); + + connect(repeat_group, SIGNAL(triggered(QAction*)), SLOT(RepeatActionTriggered(QAction*))); + connect(shuffle_group, SIGNAL(triggered(QAction*)), SLOT(ShuffleActionTriggered(QAction*))); + + Load(); + +} + +PlaylistSequence::~PlaylistSequence() { + delete ui_; +} + +void PlaylistSequence::Load() { + + loading_ = true; // Stops these setter functions calling Save() + SetShuffleMode(ShuffleMode(settings_->value("shuffle_mode", Shuffle_Off).toInt())); + SetRepeatMode(RepeatMode(settings_->value("repeat_mode", Repeat_Off).toInt())); + loading_ = false; + +} + +void PlaylistSequence::Save() { + + if (loading_) return; + + settings_->setValue("shuffle_mode", shuffle_mode_); + settings_->setValue("repeat_mode", repeat_mode_); + +} + +QIcon PlaylistSequence::AddDesaturatedIcon(const QIcon &icon) { + + QIcon ret; + for (const QSize &size : icon.availableSizes()) { + QPixmap on(icon.pixmap(size)); + QPixmap off(DesaturatedPixmap(on)); + + ret.addPixmap(off, QIcon::Normal, QIcon::Off); + ret.addPixmap(on, QIcon::Normal, QIcon::On); + } + return ret; + +} + +QPixmap PlaylistSequence::DesaturatedPixmap(const QPixmap &pixmap) { + + QPixmap ret(pixmap.size()); + ret.fill(Qt::transparent); + + QPainter p(&ret); + p.setOpacity(0.5); + p.drawPixmap(0, 0, pixmap); + p.end(); + + return ret; + +} + +void PlaylistSequence::RepeatActionTriggered(QAction *action) { + + RepeatMode mode = Repeat_Off; + if (action == ui_->action_repeat_track) mode = Repeat_Track; + if (action == ui_->action_repeat_album) mode = Repeat_Album; + if (action == ui_->action_repeat_playlist) mode = Repeat_Playlist; + if (action == ui_->action_repeat_onebyone) mode = Repeat_OneByOne; + if (action == ui_->action_repeat_intro) mode = Repeat_Intro; + + SetRepeatMode(mode); + +} + +void PlaylistSequence::ShuffleActionTriggered(QAction *action) { + + ShuffleMode mode = Shuffle_Off; + if (action == ui_->action_shuffle_all) mode = Shuffle_All; + if (action == ui_->action_shuffle_inside_album) mode = Shuffle_InsideAlbum; + if (action == ui_->action_shuffle_albums) mode = Shuffle_Albums; + + SetShuffleMode(mode); + +} + +void PlaylistSequence::SetRepeatMode(RepeatMode mode) { + + ui_->repeat->setChecked(mode != Repeat_Off); + + switch(mode) { + case Repeat_Off: ui_->action_repeat_off->setChecked(true); break; + case Repeat_Track: ui_->action_repeat_track->setChecked(true); break; + case Repeat_Album: ui_->action_repeat_album->setChecked(true); break; + case Repeat_Playlist: ui_->action_repeat_playlist->setChecked(true); break; + case Repeat_OneByOne: ui_->action_repeat_onebyone->setChecked(true); break; + case Repeat_Intro: ui_->action_repeat_intro->setChecked(true); break; + + } + + if (mode != repeat_mode_) { + repeat_mode_ = mode; + emit RepeatModeChanged(mode); + } + + Save(); + +} + +void PlaylistSequence::SetShuffleMode(ShuffleMode mode) { + + ui_->shuffle->setChecked(mode != Shuffle_Off); + + switch (mode) { + case Shuffle_Off: ui_->action_shuffle_off->setChecked(true); break; + case Shuffle_All: ui_->action_shuffle_all->setChecked(true); break; + case Shuffle_InsideAlbum: ui_->action_shuffle_inside_album->setChecked(true); break; + case Shuffle_Albums: ui_->action_shuffle_albums->setChecked(true); break; + } + + + if (mode != shuffle_mode_) { + shuffle_mode_ = mode; + emit ShuffleModeChanged(mode); + } + + Save(); + +} + +void PlaylistSequence::SetUsingDynamicPlaylist(bool dynamic) { + + dynamic_ = dynamic; + const QString not_available(tr("Not available while using a dynamic playlist")); + + setEnabled(!dynamic); + ui_->shuffle->setToolTip(dynamic ? not_available : tr("Shuffle")); + ui_->repeat->setToolTip(dynamic ? not_available : tr("Repeat")); + +} + +PlaylistSequence::ShuffleMode PlaylistSequence::shuffle_mode() const { + return dynamic_ ? Shuffle_Off : shuffle_mode_; +} + +PlaylistSequence::RepeatMode PlaylistSequence::repeat_mode() const { + return dynamic_ ? Repeat_Off : repeat_mode_; +} + +//called from global shortcut +void PlaylistSequence::CycleShuffleMode() { + + ShuffleMode mode = Shuffle_Off; + //we cycle through the shuffle modes + switch (shuffle_mode()) { + case Shuffle_Off: mode = Shuffle_All; break; + case Shuffle_All: mode = Shuffle_InsideAlbum; break; + case Shuffle_InsideAlbum: mode = Shuffle_Albums; break; + case Shuffle_Albums: break; + } + + SetShuffleMode(mode); + +} + +//called from global shortcut +void PlaylistSequence::CycleRepeatMode() { + + RepeatMode mode = Repeat_Off; + //we cycle through the repeat modes + switch (repeat_mode()) { + case Repeat_Off: mode = Repeat_Track; break; + case Repeat_Track: mode = Repeat_Album; break; + case Repeat_Album: mode = Repeat_Playlist; break; + case Repeat_Playlist: mode = Repeat_OneByOne; break; + case Repeat_OneByOne: mode = Repeat_Intro; break; + case Repeat_Intro: + break; + } + + SetRepeatMode(mode); + +} diff --git a/src/playlist/playlistsequence.h b/src/playlist/playlistsequence.h new file mode 100644 index 00000000..ac18a066 --- /dev/null +++ b/src/playlist/playlistsequence.h @@ -0,0 +1,100 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTSEQUENCE_H +#define PLAYLISTSEQUENCE_H + +#include "config.h" + +#include + +#include + +#include "core/settingsprovider.h" + +class QMenu; + +class Ui_PlaylistSequence; + +class PlaylistSequence : public QWidget { + Q_OBJECT + + public: + PlaylistSequence(QWidget *parent = nullptr, SettingsProvider *settings = 0); + ~PlaylistSequence(); + + enum RepeatMode { + Repeat_Off = 0, + Repeat_Track = 1, + Repeat_Album = 2, + Repeat_Playlist = 3, + Repeat_OneByOne = 4, + Repeat_Intro = 5, + }; + enum ShuffleMode { + Shuffle_Off = 0, + Shuffle_All = 1, + Shuffle_InsideAlbum = 2, + Shuffle_Albums = 3, + }; + + static const char *kSettingsGroup; + + RepeatMode repeat_mode() const; + ShuffleMode shuffle_mode() const; + + QMenu *repeat_menu() const { return repeat_menu_; } + QMenu *shuffle_menu() const { return shuffle_menu_; } + + public slots: + void SetRepeatMode(PlaylistSequence::RepeatMode mode); + void SetShuffleMode(PlaylistSequence::ShuffleMode mode); + void CycleShuffleMode(); + void CycleRepeatMode(); + void SetUsingDynamicPlaylist(bool dynamic); + + signals: + void RepeatModeChanged(PlaylistSequence::RepeatMode mode); + void ShuffleModeChanged(PlaylistSequence::ShuffleMode mode); + + private slots: + void RepeatActionTriggered(QAction *); + void ShuffleActionTriggered(QAction *); + + private: + void Load(); + void Save(); + static QIcon AddDesaturatedIcon(const QIcon& icon); + static QPixmap DesaturatedPixmap(const QPixmap& pixmap); + + private: + Ui_PlaylistSequence *ui_; + std::unique_ptr settings_; + + QMenu *repeat_menu_; + QMenu *shuffle_menu_; + + bool loading_; + RepeatMode repeat_mode_; + ShuffleMode shuffle_mode_; + bool dynamic_; +}; + +#endif // PLAYLISTSEQUENCE_H diff --git a/src/playlist/playlistsequence.ui b/src/playlist/playlistsequence.ui new file mode 100644 index 00000000..2b66de3c --- /dev/null +++ b/src/playlist/playlistsequence.ui @@ -0,0 +1,155 @@ + + + PlaylistSequence + + + + 0 + 0 + 80 + 37 + + + + QToolButton, QToolButton:hover, QToolButton:pressed { + border: 0px; + background: transparent; +} + + + + + 0 + + + 0 + + + + + Repeat + + + + 16 + 16 + + + + true + + + QToolButton::InstantPopup + + + + + + + Shuffle + + + + 16 + 16 + + + + true + + + QToolButton::InstantPopup + + + + + + + true + + + true + + + Don't repeat + + + + + true + + + Repeat track + + + + + true + + + Repeat album + + + + + true + + + Repeat playlist + + + + + true + + + Stop after each track + + + + + true + + + Intro tracks + + + + + true + + + true + + + Don't shuffle + + + + + true + + + Shuffle tracks in this album + + + + + true + + + Shuffle all + + + + + true + + + Shuffle albums + + + + + + diff --git a/src/playlist/playlisttabbar.cpp b/src/playlist/playlisttabbar.cpp new file mode 100644 index 00000000..8988ff67 --- /dev/null +++ b/src/playlist/playlisttabbar.cpp @@ -0,0 +1,432 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "playlist.h" +#include "playlistmanager.h" +#include "playlisttabbar.h" +#include "playlistview.h" +#include "songmimedata.h" +#include "core/logging.h" +#include "core/iconloader.h" +#include "widgets/renametablineedit.h" +#include "widgets/favoritewidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const char *PlaylistTabBar::kSettingsGroup = "PlaylistTabBar"; + +PlaylistTabBar::PlaylistTabBar(QWidget *parent) + : QTabBar(parent), + manager_(nullptr), + menu_(new QMenu(this)), + menu_index_(-1), + suppress_current_changed_(false), + initialized_(false), + rename_editor_(new RenameTabLineEdit(this)) { + + setAcceptDrops(true); + setElideMode(Qt::ElideRight); + setUsesScrollButtons(true); + setTabsClosable(true); + + close_ = menu_->addAction(IconLoader::Load("list-remove"), tr("Close playlist"), this, SLOT(Close())); + rename_ = menu_->addAction(IconLoader::Load("edit-rename"), tr("Rename playlist..."), this, SLOT(Rename())); + save_ = menu_->addAction(IconLoader::Load("document-save"), tr("Save playlist..."), this, SLOT(Save())); + menu_->addSeparator(); + + rename_editor_->setVisible(false); + connect(rename_editor_, SIGNAL(editingFinished()), SLOT(RenameInline())); + connect(rename_editor_, SIGNAL(EditingCanceled()), SLOT(HideEditor())); + + connect(this, SIGNAL(currentChanged(int)), SLOT(CurrentIndexChanged(int))); + connect(this, SIGNAL(tabMoved(int, int)), SLOT(TabMoved())); + // We can't just emit Close signal, we need to extract the playlist id first + connect(this, SIGNAL(tabCloseRequested(int)), SLOT(CloseFromTabIndex(int))); + +} + +void PlaylistTabBar::SetActions(QAction *new_playlist, QAction *load_playlist) { + + menu_->insertAction(0, new_playlist); + menu_->insertAction(0, load_playlist); + + new_ = new_playlist; + +} + +void PlaylistTabBar::SetManager(PlaylistManager *manager) { + + manager_ = manager; + connect(manager_, SIGNAL(PlaylistFavorited(int, bool)), SLOT(PlaylistFavoritedSlot(int, bool))); + connect(manager_, SIGNAL(PlaylistManagerInitialized()), this, SLOT(PlaylistManagerInitialized())); + +} + +void PlaylistTabBar::PlaylistManagerInitialized() { + + // Signal that we are done loading and thus further changes should be + // committed to the db. + initialized_ = true; + disconnect(manager_, SIGNAL(PlaylistManagerInitialized()), this, SLOT(PlaylistManagerInitialized())); + +} + +void PlaylistTabBar::contextMenuEvent(QContextMenuEvent *e) { + + //we need to finish the renaming action before showing context menu + if (rename_editor_->isVisible()) { + //discard any change + HideEditor(); + } + + menu_index_ = tabAt(e->pos()); + rename_->setEnabled(menu_index_ != -1); + close_->setEnabled(menu_index_ != -1 && count() > 1); + save_->setEnabled(menu_index_ != -1); + + menu_->popup(e->globalPos()); + +} + +void PlaylistTabBar::mouseReleaseEvent(QMouseEvent *e) { + + if (e->button() == Qt::MidButton) { + // Update menu index + menu_index_ = tabAt(e->pos()); + Close(); + } + + QTabBar::mouseReleaseEvent(e); + +} + +void PlaylistTabBar::mouseDoubleClickEvent(QMouseEvent *e) { + + int index = tabAt(e->pos()); + + // discard a double click with the middle button + if (e->button() != Qt::MidButton) { + if (index == -1) { + new_->activate(QAction::Trigger); + } + else { + //update current tab + menu_index_ = index; + + //set position + rename_editor_->setGeometry(tabRect(index)); + rename_editor_->setText(tabText(index)); + rename_editor_->setVisible(true); + rename_editor_->setFocus(); + } + } + + QTabBar::mouseDoubleClickEvent(e); + +} + +void PlaylistTabBar::Rename() { + + if (menu_index_ == -1) return; + + QString name = tabText(menu_index_); + name = QInputDialog::getText(this, tr("Rename playlist"), tr("Enter a new name for this playlist"), QLineEdit::Normal, name); + + if (name.isNull()) return; + + emit Rename(tabData(menu_index_).toInt(), name); + +} + +void PlaylistTabBar::RenameInline() { + emit Rename(tabData(menu_index_).toInt(), rename_editor_->text()); + HideEditor(); +} + +void PlaylistTabBar::HideEditor() { + + //editingFinished() will be called twice due to Qt bug #40, so we reuse the same instance, don't delete it + rename_editor_->setVisible(false); + + // hack to give back focus to playlist view + manager_->SetCurrentPlaylist(manager_->current()->id()); + +} + +void PlaylistTabBar::Close() { + + if (menu_index_ == -1) return; + + const int playlist_id = tabData(menu_index_).toInt(); + + QSettings s; + s.beginGroup(kSettingsGroup); + + const bool ask_for_delete = s.value("warn_close_playlist", true).toBool(); + + if (ask_for_delete && !manager_->IsPlaylistFavorite(playlist_id) && !manager_->playlist(playlist_id)->GetAllSongs().empty()) { + QMessageBox confirmation_box; + confirmation_box.setWindowIcon(QIcon(":/icons/64x64/strawberry.png")); + confirmation_box.setWindowTitle(tr("Remove playlist")); + confirmation_box.setIcon(QMessageBox::Question); + confirmation_box.setText( + tr("You are about to remove a playlist which is not part of your " + "favorite playlists: " + "the playlist will be deleted (this action cannot be undone). \n" + "Are you sure you want to continue?")); + confirmation_box.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); + + QCheckBox dont_prompt_again(tr("Warn me when closing a playlist tab"), &confirmation_box); + dont_prompt_again.setChecked(ask_for_delete); + dont_prompt_again.blockSignals(true); + dont_prompt_again.setToolTip(tr("This option can be changed in the \"Behavior\" preferences")); + + QGridLayout *grid = qobject_cast(confirmation_box.layout()); + QDialogButtonBox *buttons = confirmation_box.findChild(); + if (grid && buttons) { + const int index = grid->indexOf(buttons); + int row, column, row_span, column_span = 0; + grid->getItemPosition(index, &row, &column, &row_span, &column_span); + QLayoutItem *buttonsItem = grid->takeAt(index); + grid->addWidget(&dont_prompt_again, row, column, row_span, column_span, Qt::AlignLeft | Qt::AlignTop); + grid->addItem(buttonsItem, ++row, column, row_span, column_span); + } + else { + confirmation_box.addButton(&dont_prompt_again, QMessageBox::ActionRole); + } + + if (confirmation_box.exec() != QMessageBox::Yes) { + return; + } + + // If user changed the pref, save the new one + if (dont_prompt_again.isChecked() != ask_for_delete) { + s.setValue("warn_close_playlist", dont_prompt_again.isChecked()); + } + } + + // Close the playlist. If the playlist is not a favorite playlist, it will be + // deleted, as it will not be visible after being closed. Otherwise, the tab + // is closed but the playlist still exists and can be resurrected from the + // "Playlists" tab. + emit Close(playlist_id); + + // Select the nearest tab. + if (menu_index_ > 1) { + setCurrentIndex(menu_index_ - 1); + } + + // Update playlist tab order/visibility + TabMoved(); + +} + +void PlaylistTabBar::CloseFromTabIndex(int index) { + // Update the global index + menu_index_ = index; + Close(); +} + +void PlaylistTabBar::Save() { + if (menu_index_ == -1) return; + + emit Save(tabData(menu_index_).toInt()); +} + +int PlaylistTabBar::current_id() const { + if (currentIndex() == -1) return -1; + return tabData(currentIndex()).toInt(); +} + +int PlaylistTabBar::index_of(int id) const { + + for (int i = 0; i < count(); ++i) { + if (tabData(i).toInt() == id) { + return i; + } + } + return -1; + +} + +void PlaylistTabBar::set_current_id(int id) { setCurrentIndex(index_of(id)); } + +int PlaylistTabBar::id_of(int index) const { + + if (index < 0 || index >= count()) { + qLog(Warning) << "Playlist tab index requested is out of bounds!"; + return 0; + } + return tabData(index).toInt(); + +} + +void PlaylistTabBar::set_icon_by_id(int id, const QIcon &icon) { + setTabIcon(index_of(id), icon); +} + +void PlaylistTabBar::RemoveTab(int id) { + removeTab(index_of(id)); +} + +void PlaylistTabBar::set_text_by_id(int id, const QString &text) { + setTabText(index_of(id), text); + setTabToolTip(index_of(id), text); +} + +void PlaylistTabBar::CurrentIndexChanged(int index) { + if (!suppress_current_changed_) emit CurrentIdChanged(tabData(index).toInt()); +} + +void PlaylistTabBar::InsertTab(int id, int index, const QString &text, + bool favorite) { + suppress_current_changed_ = true; + insertTab(index, text); + setTabData(index, id); + setTabToolTip(index, text); + FavoriteWidget *widget = new FavoriteWidget(id, favorite); + widget->setToolTip( + tr("Click here to favorite this playlist so it will be saved and remain accessible" + "through the \"Playlists\" panel on the left side bar")); + connect(widget, SIGNAL(FavoriteStateChanged(int, bool)), SIGNAL(PlaylistFavorited(int, bool))); + setTabButton(index, QTabBar::LeftSide, widget); + suppress_current_changed_ = false; + + // If we are still starting up, we don't need to do this, as the + // tab ordering after startup will be the same as was already in the db. + if (initialized_) { + if (currentIndex() == index) emit CurrentIdChanged(id); + + // Update playlist tab order/visibility + TabMoved(); + } +} + +void PlaylistTabBar::TabMoved() { + QList ids; + for (int i = 0; i < count(); ++i) { + ids << tabData(i).toInt(); + } + emit PlaylistOrderChanged(ids); +} + +void PlaylistTabBar::dragEnterEvent(QDragEnterEvent *e) { + if (e->mimeData()->hasUrls() || e->mimeData()->hasFormat(Playlist::kRowsMimetype) || qobject_cast(e->mimeData())) { + e->acceptProposedAction(); + } +} + +void PlaylistTabBar::dragMoveEvent(QDragMoveEvent *e) { + + drag_hover_tab_ = tabAt(e->pos()); + + if (drag_hover_tab_ != -1) { + e->setDropAction(Qt::CopyAction); + e->accept(tabRect(drag_hover_tab_)); + + if (!drag_hover_timer_.isActive()) + drag_hover_timer_.start(kDragHoverTimeout, this); + } + else { + drag_hover_timer_.stop(); + } +} + +void PlaylistTabBar::dragLeaveEvent(QDragLeaveEvent*) { + drag_hover_timer_.stop(); +} + +void PlaylistTabBar::timerEvent(QTimerEvent *e) { + QTabBar::timerEvent(e); + + if (e->timerId() == drag_hover_timer_.timerId()) { + drag_hover_timer_.stop(); + if (drag_hover_tab_ != -1) setCurrentIndex(drag_hover_tab_); + } +} + +void PlaylistTabBar::dropEvent(QDropEvent *e) { + + if (drag_hover_tab_ == -1) { + const MimeData *mime_data = qobject_cast(e->mimeData()); + if(mime_data && !mime_data->name_for_new_playlist_.isEmpty()) { + manager_->New(mime_data->name_for_new_playlist_); + } + else { + manager_->New(tr("Playlist")); + } + setCurrentIndex(count() - 1); + } + else { + setCurrentIndex(drag_hover_tab_); + } + + manager_->current()->dropMimeData(e->mimeData(), e->proposedAction(), -1, 0, QModelIndex()); + +} + +bool PlaylistTabBar::event(QEvent *e) { + + switch (e->type()) { + case QEvent::ToolTip: { + QHelpEvent *he = static_cast(e); + + QRect displayed_tab; + QSize real_tab; + bool is_elided = false; + + real_tab = tabSizeHint(tabAt(he->pos())); + displayed_tab = tabRect(tabAt(he->pos())); + // Check whether the tab is elided or not + is_elided = displayed_tab.width() < real_tab.width(); + if (!is_elided) { + // If it's not elided, don't show the tooltip + QToolTip::hideText(); + } + else { + QToolTip::showText(he->globalPos(), tabToolTip(tabAt(he->pos()))); + } + return true; + } + default: + return QTabBar::event(e); + } + +} + +void PlaylistTabBar::PlaylistFavoritedSlot(int id, bool favorite) { + + const int index = index_of(id); + FavoriteWidget *favorite_widget = qobject_cast(tabButton(index, QTabBar::LeftSide)); + if (favorite_widget) { + favorite_widget->SetFavorite(favorite); + } + +} diff --git a/src/playlist/playlisttabbar.h b/src/playlist/playlisttabbar.h new file mode 100644 index 00000000..eac204d4 --- /dev/null +++ b/src/playlist/playlisttabbar.h @@ -0,0 +1,113 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTTABBAR_H +#define PLAYLISTTABBAR_H + +#include "config.h" + +#include +#include +#include + +class PlaylistManager; +class RenameTabLineEdit; + +class QMenu; + +class PlaylistTabBar : public QTabBar { + Q_OBJECT + + public: + PlaylistTabBar(QWidget *parent = nullptr); + + static const int kDragHoverTimeout = 500; + static const char *kSettingsGroup; + + void SetActions(QAction *new_playlist, QAction *load_playlist); + void SetManager(PlaylistManager *manager); + + // We use IDs to refer to tabs so the tabs can be moved around (and their indexes change). + int index_of(int id) const; + int current_id() const; + int id_of(int index) const; + + // Utility functions that use IDs rather than indexes + void set_current_id(int id); + void set_icon_by_id(int id, const QIcon &icon); + void set_text_by_id(int id, const QString &text); + + void RemoveTab(int id); + void InsertTab(int id, int index, const QString &text, bool favorite); + +signals: + void CurrentIdChanged(int id); + void Rename(int id, const QString &name); + void Close(int id); + void Save(int id); + void PlaylistOrderChanged(const QList &ids); + void PlaylistFavorited(int id, bool favorite); + +protected: + void contextMenuEvent(QContextMenuEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + void mouseDoubleClickEvent(QMouseEvent *e); + void dragEnterEvent(QDragEnterEvent *e); + void dragMoveEvent(QDragMoveEvent *e); + void dragLeaveEvent(QDragLeaveEvent *e); + void dropEvent(QDropEvent *e); + void timerEvent(QTimerEvent *e); + bool event(QEvent *e); + +private slots: + void CurrentIndexChanged(int index); + void Rename(); + void RenameInline(); + void HideEditor(); + void Close(); + void CloseFromTabIndex(int index); + // Used when playlist's favorite flag isn't changed from the favorite widget (e.g. from the playlistlistcontainer): will update the favorite widget + void PlaylistFavoritedSlot(int id, bool favorite); + // Used to signal that the playlist manager is done starting up + void PlaylistManagerInitialized(); + void TabMoved(); + void Save(); + +private: + PlaylistManager *manager_; + + QMenu *menu_; + int menu_index_; + QAction *new_; + QAction *rename_; + QAction *close_; + QAction *save_; + + QBasicTimer drag_hover_timer_; + int drag_hover_tab_; + + bool suppress_current_changed_; + bool initialized_; + + // Editor for inline renaming + RenameTabLineEdit *rename_editor_; +}; + +#endif // PLAYLISTTABBAR_H diff --git a/src/playlist/playlistundocommands.cpp b/src/playlist/playlistundocommands.cpp new file mode 100644 index 00000000..2adb72d4 --- /dev/null +++ b/src/playlist/playlistundocommands.cpp @@ -0,0 +1,128 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "playlistundocommands.h" +#include "playlist.h" + +namespace PlaylistUndoCommands { + +Base::Base(Playlist* playlist) : QUndoCommand(0), playlist_(playlist) {} + + +InsertItems::InsertItems(Playlist *playlist, const PlaylistItemList &items, int pos, bool enqueue) + : Base(playlist), + items_(items), + pos_(pos), + enqueue_(enqueue) +{ + setText(tr("add %n songs", "", items_.count())); +} + +void InsertItems::redo() { + playlist_->InsertItemsWithoutUndo(items_, pos_, enqueue_); +} + +void InsertItems::undo() { + const int start = pos_ == -1 ? playlist_->rowCount() - items_.count() : pos_; + playlist_->RemoveItemsWithoutUndo(start, items_.count()); +} + +bool InsertItems::UpdateItem(const PlaylistItemPtr &updated_item) { + for (int i = 0; i < items_.size(); i++) { + PlaylistItemPtr item = items_[i]; + if (item->Metadata().url() == updated_item->Metadata().url()) { + items_[i] = updated_item; + return true; + } + } + return false; +} + + +RemoveItems::RemoveItems(Playlist *playlist, int pos, int count) : Base(playlist) { + setText(tr("remove %n songs", "", count)); + + ranges_ << Range(pos, count); +} + +void RemoveItems::redo() { + for (int i = 0; i < ranges_.count(); ++i) + ranges_[i].items_ = + playlist_->RemoveItemsWithoutUndo(ranges_[i].pos_, ranges_[i].count_); +} + +void RemoveItems::undo() { + for (int i = ranges_.count() - 1; i >= 0; --i) + playlist_->InsertItemsWithoutUndo(ranges_[i].items_, ranges_[i].pos_); +} + +bool RemoveItems::mergeWith(const QUndoCommand *other) { + const RemoveItems* remove_command = static_cast(other); + ranges_.append(remove_command->ranges_); + + int sum = 0; + for (const Range &range : ranges_) sum += range.count_; + setText(tr("remove %n songs", "", sum)); + + return true; +} + + +MoveItems::MoveItems(Playlist *playlist, const QList &source_rows, int pos) + : Base(playlist), + source_rows_(source_rows), + pos_(pos) +{ + setText(tr("move %n songs", "", source_rows.count())); +} + +void MoveItems::redo() { + playlist_->MoveItemsWithoutUndo(source_rows_, pos_); +} + +void MoveItems::undo() { + playlist_->MoveItemsWithoutUndo(pos_, source_rows_); +} + +ReOrderItems::ReOrderItems(Playlist* playlist, const PlaylistItemList &new_items) + : Base(playlist), old_items_(playlist->items_), new_items_(new_items) {} + +void ReOrderItems::undo() { playlist_->ReOrderWithoutUndo(old_items_); } + +void ReOrderItems::redo() { playlist_->ReOrderWithoutUndo(new_items_); } + +SortItems::SortItems(Playlist* playlist, int column, Qt::SortOrder order, const PlaylistItemList &new_items) + : ReOrderItems(playlist, new_items), + column_(column), + order_(order) +{ + setText(tr("sort songs")); +} + + +ShuffleItems::ShuffleItems(Playlist* playlist, const PlaylistItemList &new_items) + : ReOrderItems(playlist, new_items) +{ + setText(tr("shuffle songs")); +} + +} // namespace diff --git a/src/playlist/playlistundocommands.h b/src/playlist/playlistundocommands.h new file mode 100644 index 00000000..1d6f43cc --- /dev/null +++ b/src/playlist/playlistundocommands.h @@ -0,0 +1,126 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTUNDOCOMMANDS_H +#define PLAYLISTUNDOCOMMANDS_H + +#include "config.h" + +#include +#include + +#include "playlistitem.h" + +class Playlist; + +namespace PlaylistUndoCommands { + enum Types { + Type_RemoveItems = 0, + }; + + class Base : public QUndoCommand { + Q_DECLARE_TR_FUNCTIONS(PlaylistUndoCommands); + + public: + Base(Playlist *playlist); + + protected: + Playlist *playlist_; + }; + + class InsertItems : public Base { + public: + InsertItems(Playlist *playlist, const PlaylistItemList &items, int pos, bool enqueue = false); + + void undo(); + void redo(); + // When load is async, items have already been pushed, so we need to update them. + // This function try to find the equivalent item, and replace it with the + // new (completely loaded) one. + // return true if the was found (and updated), false otherwise + bool UpdateItem(const PlaylistItemPtr &updated_item); + + private: + PlaylistItemList items_; + int pos_; + bool enqueue_; + }; + + class RemoveItems : public Base { + public: + RemoveItems(Playlist *playlist, int pos, int count); + + int id() const { return Type_RemoveItems; } + + void undo(); + void redo(); + bool mergeWith(const QUndoCommand *other); + + private: + struct Range { + Range(int pos, int count) : pos_(pos), count_(count) {} + int pos_; + int count_; + PlaylistItemList items_; + }; + + QList ranges_; + }; + + class MoveItems : public Base { + public: + MoveItems(Playlist *playlist, const QList &source_rows, int pos); + + void undo(); + void redo(); + + private: + QList source_rows_; + int pos_; + }; + + class ReOrderItems : public Base { + public: + ReOrderItems(Playlist *playlist, const PlaylistItemList &new_items); + + void undo(); + void redo(); + + private: + PlaylistItemList old_items_; + PlaylistItemList new_items_; + }; + + class SortItems : public ReOrderItems { + public: + SortItems(Playlist *playlist, int column, Qt::SortOrder order, const PlaylistItemList &new_items); + + private: + int column_; + Qt::SortOrder order_; + }; + + class ShuffleItems : public ReOrderItems { + public: + ShuffleItems(Playlist *playlist, const PlaylistItemList &new_items); + }; +} //namespace + +#endif // PLAYLISTUNDOCOMMANDS_H diff --git a/src/playlist/playlistview.cpp b/src/playlist/playlistview.cpp new file mode 100644 index 00000000..45d12017 --- /dev/null +++ b/src/playlist/playlistview.cpp @@ -0,0 +1,1223 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "playlist.h" +#include "playlistview.h" +#include "playlistdelegates.h" +#include "playlistheader.h" +#include "core/application.h" +#include "core/logging.h" +#include "core/player.h" +#include "core/iconloader.h" +#include "core/qt_blurimage.h" +#include "covermanager/currentartloader.h" +#include "settings/playbacksettingspage.h" +#include "settings/playlistsettingspage.h" +#include "settings/appearancesettingspage.h" + +const int PlaylistView::kStateVersion = 6; +const int PlaylistView::kGlowIntensitySteps = 24; +const int PlaylistView::kAutoscrollGraceTimeout = 30; // seconds +const int PlaylistView::kDropIndicatorWidth = 2; +const int PlaylistView::kDropIndicatorGradientWidth = 5; +const char *PlaylistView::kSettingBackgroundImageType = "playlistview_background_type"; +const char *PlaylistView::kSettingBackgroundImageFilename = "playlistview_background_image_file"; + +const int PlaylistView::kDefaultBlurRadius = 0; +const int PlaylistView::kDefaultOpacityLevel = 40; + +PlaylistProxyStyle::PlaylistProxyStyle(QStyle *base) + : QProxyStyle(base), common_style_(new QCommonStyle) {} + +void PlaylistProxyStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { + + if (element == CE_Header) { + const QStyleOptionHeader *header_option = qstyleoption_cast(option); + const QRect &rect = header_option->rect; + const QString &text = header_option->text; + const QFontMetrics &font_metrics = header_option->fontMetrics; + + // spaces added to make transition less abrupt + if (rect.width() < font_metrics.width(text + " ")) { + const Playlist::Column column = static_cast(header_option->section); + QStyleOptionHeader new_option(*header_option); + new_option.text = Playlist::abbreviated_column_name(column); + QProxyStyle::drawControl(element, &new_option, painter, widget); + return; + } + } + + if (element == CE_ItemViewItem) + common_style_->drawControl(element, option, painter, widget); + else + QProxyStyle::drawControl(element, option, painter, widget); + +} + +void PlaylistProxyStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { + + if (element == QStyle::PE_PanelItemViewRow || element == QStyle::PE_PanelItemViewItem) + common_style_->drawPrimitive(element, option, painter, widget); + else + QProxyStyle::drawPrimitive(element, option, painter, widget); + +} + + +PlaylistView::PlaylistView(QWidget *parent) + : QTreeView(parent), + app_(nullptr), + style_(new PlaylistProxyStyle(style())), + playlist_(nullptr), + header_(new PlaylistHeader(Qt::Horizontal, this, this)), + setting_initial_header_layout_(false), + upgrading_from_qheaderview_(false), + read_only_settings_(true), + upgrading_from_version_(-1), + background_image_type_(Invalid), + blur_radius_(kDefaultBlurRadius), + opacity_level_(kDefaultOpacityLevel), + previous_background_image_opacity_(0.0), + fade_animation_(new QTimeLine(1000, this)), + last_height_(-1), + last_width_(-1), + force_background_redraw_(false), + glow_enabled_(true), + currently_glowing_(false), + glow_intensity_step_(0), + inhibit_autoscroll_timer_(new QTimer(this)), + inhibit_autoscroll_(false), + currently_autoscrolling_(false), + row_height_(-1), + currenttrack_play_(":/pictures/currenttrack_play.png"), + currenttrack_pause_(":/pictures/currenttrack_pause.png"), + cached_current_row_row_(-1), + drop_indicator_row_(-1), + drag_over_(false) +{ + + //qLog(Debug) << __PRETTY_FUNCTION__; + + setHeader(header_); + header_->setSectionsMovable(true); + setStyle(style_); + setMouseTracking(true); + + + connect(header_, SIGNAL(sectionResized(int,int,int)), SLOT(SaveGeometry())); + connect(header_, SIGNAL(sectionMoved(int,int,int)), SLOT(SaveGeometry())); + connect(header_, SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), SLOT(SaveGeometry())); + connect(header_, SIGNAL(SectionVisibilityChanged(int,bool)), SLOT(SaveGeometry())); + connect(header_, SIGNAL(sectionResized(int,int,int)), SLOT(InvalidateCachedCurrentPixmap())); + connect(header_, SIGNAL(sectionMoved(int,int,int)), SLOT(InvalidateCachedCurrentPixmap())); + connect(header_, SIGNAL(SectionVisibilityChanged(int,bool)), SLOT(InvalidateCachedCurrentPixmap())); + connect(header_, SIGNAL(StretchEnabledChanged(bool)), SLOT(SaveSettings())); + connect(header_, SIGNAL(StretchEnabledChanged(bool)), SLOT(StretchChanged(bool))); + + inhibit_autoscroll_timer_->setInterval(kAutoscrollGraceTimeout * 1000); + inhibit_autoscroll_timer_->setSingleShot(true); + connect(inhibit_autoscroll_timer_, SIGNAL(timeout()), SLOT(InhibitAutoscrollTimeout())); + + horizontalScrollBar()->installEventFilter(this); + verticalScrollBar()->installEventFilter(this); + + setAlternatingRowColors(true); + + setAttribute(Qt::WA_MacShowFocusRect, false); + +#ifdef Q_OS_DARWIN + setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); +#endif + // For fading + connect(fade_animation_, SIGNAL(valueChanged(qreal)), SLOT(FadePreviousBackgroundImage(qreal))); + fade_animation_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0 + +} + +void PlaylistView::SetApplication(Application *app) { + + Q_ASSERT(app); + app_ = app; + connect(app_->current_art_loader(), SIGNAL(ArtLoaded(const Song&, const QString&, const QImage&)), SLOT(CurrentSongChanged(const Song&, const QString&, const QImage&))); + connect(app_->player(), SIGNAL(Paused()), SLOT(StopGlowing())); + connect(app_->player(), SIGNAL(Playing()), SLOT(StartGlowing())); + connect(app_->player(), SIGNAL(Stopped()), SLOT(StopGlowing())); + connect(app_->player(), SIGNAL(Stopped()), SLOT(PlayerStopped())); + +} + +void PlaylistView::SetItemDelegates(CollectionBackend *backend) { + + setItemDelegate(new PlaylistDelegateBase(this)); + + setItemDelegateForColumn(Playlist::Column_Title, new TextItemDelegate(this)); + setItemDelegateForColumn(Playlist::Column_Album, new TagCompletionItemDelegate(this, backend, Playlist::Column_Album)); + setItemDelegateForColumn(Playlist::Column_Artist, new TagCompletionItemDelegate(this, backend, Playlist::Column_Artist)); + setItemDelegateForColumn(Playlist::Column_AlbumArtist, new TagCompletionItemDelegate(this, backend, Playlist::Column_AlbumArtist)); + setItemDelegateForColumn(Playlist::Column_Genre, new TagCompletionItemDelegate(this, backend, Playlist::Column_Genre)); + setItemDelegateForColumn(Playlist::Column_Composer, new TagCompletionItemDelegate(this, backend, Playlist::Column_Composer)); + setItemDelegateForColumn(Playlist::Column_Performer, new TagCompletionItemDelegate(this, backend, Playlist::Column_Performer)); + setItemDelegateForColumn(Playlist::Column_Grouping, new TagCompletionItemDelegate(this, backend, Playlist::Column_Grouping)); + setItemDelegateForColumn(Playlist::Column_Length, new LengthItemDelegate(this)); + setItemDelegateForColumn(Playlist::Column_Filesize, new SizeItemDelegate(this)); + setItemDelegateForColumn(Playlist::Column_Filetype, new FileTypeItemDelegate(this)); + setItemDelegateForColumn(Playlist::Column_DateCreated, new DateItemDelegate(this)); + setItemDelegateForColumn(Playlist::Column_DateModified, new DateItemDelegate(this)); + + setItemDelegateForColumn(Playlist::Column_Samplerate, new PlaylistDelegateBase(this, ("Hz"))); + setItemDelegateForColumn(Playlist::Column_Bitdepth, new PlaylistDelegateBase(this, ("Bit"))); + setItemDelegateForColumn(Playlist::Column_Bitrate, new PlaylistDelegateBase(this, tr("kbps"))); + + setItemDelegateForColumn(Playlist::Column_SamplerateBitdepth, new SamplerateBitdepthItemDelegate(this)); + + setItemDelegateForColumn(Playlist::Column_Filename, new NativeSeparatorsDelegate(this)); + setItemDelegateForColumn(Playlist::Column_LastPlayed, new LastPlayedItemDelegate(this)); + +#if 0 + if (app_ && app_->player()) { + setItemDelegateForColumn(Playlist::Column_Source, new SongSourceDelegate(this, app_->player())); + } + else { + header_->HideSection(Playlist::Column_Source); + } +#endif + +} + +void PlaylistView::SetPlaylist(Playlist *playlist) { + + if (playlist_) { + disconnect(playlist_, SIGNAL(CurrentSongChanged(Song)), this, SLOT(MaybeAutoscroll())); + disconnect(playlist_, SIGNAL(destroyed()), this, SLOT(PlaylistDestroyed())); + disconnect(playlist_, SIGNAL(QueueChanged()), this, SLOT(update())); + } + + playlist_ = playlist; + LoadGeometry(); + ReloadSettings(); + setFocus(); + read_only_settings_ = false; + JumpToLastPlayedTrack(); + + connect(playlist_, SIGNAL(RestoreFinished()), SLOT(JumpToLastPlayedTrack())); + connect(playlist_, SIGNAL(CurrentSongChanged(Song)), SLOT(MaybeAutoscroll())); + connect(playlist_, SIGNAL(destroyed()), SLOT(PlaylistDestroyed())); + connect(playlist_, SIGNAL(QueueChanged()), SLOT(update())); + +} + +void PlaylistView::setModel(QAbstractItemModel *m) { + + if (model()) { + disconnect(model(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(InvalidateCachedCurrentPixmap())); + //disconnect(model(), SIGNAL(layoutAboutToBeChanged()), this, SLOT(RatingHoverOut())); + // When changing the model, always invalidate the current pixmap. + // If a remote client uses "stop after", without invaliding the stop + // mark would not appear. + InvalidateCachedCurrentPixmap(); + } + + QTreeView::setModel(m); + + connect(model(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(InvalidateCachedCurrentPixmap())); + +} + +void PlaylistView::LoadGeometry() { + + QSettings settings; + settings.beginGroup(Playlist::kSettingsGroup); + + QByteArray state(settings.value("state").toByteArray()); + if (!header_->RestoreState(state)) { + // Maybe we're upgrading from a version that persisted the state with QHeaderView. + if (!header_->restoreState(state)) { + header_->HideSection(Playlist::Column_Disc); + header_->HideSection(Playlist::Column_Year); + header_->HideSection(Playlist::Column_OriginalYear); + header_->HideSection(Playlist::Column_Genre); + header_->HideSection(Playlist::Column_Filename); + header_->HideSection(Playlist::Column_Filesize); + header_->HideSection(Playlist::Column_DateCreated); + header_->HideSection(Playlist::Column_DateModified); + header_->HideSection(Playlist::Column_AlbumArtist); + header_->HideSection(Playlist::Column_Composer); + header_->HideSection(Playlist::Column_Performer); + header_->HideSection(Playlist::Column_Grouping); + header_->HideSection(Playlist::Column_PlayCount); + header_->HideSection(Playlist::Column_SkipCount); + header_->HideSection(Playlist::Column_LastPlayed); + header_->HideSection(Playlist::Column_Comment); + header_->HideSection(Playlist::Column_BaseFilename); + + header_->HideSection(Playlist::Column_Samplerate); + header_->HideSection(Playlist::Column_Bitdepth); + + header_->moveSection(header_->visualIndex(Playlist::Column_Track), 0); + setting_initial_header_layout_ = true; + } + else { + upgrading_from_qheaderview_ = true; + } + } + + // Make sure at least one column is visible + bool all_hidden = true; + for (int i = 0; i < header_->count(); ++i) { + if (!header_->isSectionHidden(i) && header_->sectionSize(i) > 0) { + all_hidden = false; + break; + } + } + if (all_hidden) { + header_->ShowSection(Playlist::Column_Title); + } + +} + +void PlaylistView::SaveGeometry() { + + if (read_only_settings_) return; + + QSettings settings; + settings.beginGroup(Playlist::kSettingsGroup); + settings.setValue("state", header_->SaveState()); + +} + +void PlaylistView::ReloadBarPixmaps() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + currenttrack_bar_left_ = LoadBarPixmap(":/pictures/currenttrack_bar_left.png"); + currenttrack_bar_mid_ = LoadBarPixmap(":/pictures/currenttrack_bar_mid.png"); + currenttrack_bar_right_ = LoadBarPixmap(":/pictures/currenttrack_bar_right.png"); + +} + +QList PlaylistView::LoadBarPixmap(const QString &filename) { + + QImage image(filename); + image = image.scaledToHeight(row_height_, Qt::SmoothTransformation); + + // Colour the bar with the palette colour + QPainter p(&image); + p.setCompositionMode(QPainter::CompositionMode_SourceAtop); + p.setOpacity(0.7); + p.fillRect(image.rect(), QApplication::palette().color(QPalette::Highlight)); + p.end(); + + // Animation steps + QList ret; + for (int i = 0; i < kGlowIntensitySteps; ++i) { + QImage step(image.copy()); + p.begin(&step); + p.setCompositionMode(QPainter::CompositionMode_SourceAtop); + p.setOpacity(0.4 - 0.6 * sin(float(i) / kGlowIntensitySteps * (M_PI / 2))); + p.fillRect(step.rect(), Qt::white); + p.end(); + ret << QPixmap::fromImage(step); + } + + return ret; + +} + +void PlaylistView::drawTree(QPainter *painter, const QRegion ®ion) const { + + const_cast(this)->current_paint_region_ = region; + QTreeView::drawTree(painter, region); + const_cast(this)->current_paint_region_ = QRegion(); + +} + +void PlaylistView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + + QStyleOptionViewItemV4 opt(option); + + bool is_current = index.data(Playlist::Role_IsCurrent).toBool(); + bool is_paused = index.data(Playlist::Role_IsPaused).toBool(); + + if (is_current) { + const_cast(this)->last_current_item_ = index; + const_cast(this)->last_glow_rect_ = opt.rect; + + int step = glow_intensity_step_; + if (step >= kGlowIntensitySteps) + step = 2 * (kGlowIntensitySteps - 1) - step + 1; + + int row_height = opt.rect.height(); + if (row_height != row_height_) { + // Recreate the pixmaps if the height changed since last time + const_cast(this)->row_height_ = row_height; + const_cast(this)->ReloadBarPixmaps(); + } + + QRect middle(opt.rect); + middle.setLeft(middle.left() + currenttrack_bar_left_[0].width()); + middle.setRight(middle.right() - currenttrack_bar_right_[0].width()); + + // Selection + if (selectionModel()->isSelected(index)) + painter->fillRect(opt.rect, opt.palette.color(QPalette::Highlight)); + + // Draw the bar + painter->drawPixmap(opt.rect.topLeft(), currenttrack_bar_left_[step]); + painter->drawPixmap(opt.rect.topRight() - currenttrack_bar_right_[0].rect().topRight(), currenttrack_bar_right_[step]); + painter->drawPixmap(middle, currenttrack_bar_mid_[step]); + + // Draw the play icon + QPoint play_pos(currenttrack_bar_left_[0].width() / 3 * 2, (row_height - currenttrack_play_.height()) / 2); + painter->drawPixmap(opt.rect.topLeft() + play_pos, is_paused ? currenttrack_pause_ : currenttrack_play_); + + // Set the font + opt.palette.setColor(QPalette::Inactive, QPalette::HighlightedText, QApplication::palette().color(QPalette::Active, QPalette::HighlightedText)); + opt.palette.setColor(QPalette::Text, QApplication::palette().color(QPalette::HighlightedText)); + opt.palette.setColor(QPalette::Highlight, Qt::transparent); + opt.palette.setColor(QPalette::AlternateBase, Qt::transparent); + opt.decorationSize = QSize(20, 20); + + // Draw the actual row data on top. We cache this, because it's fairly + // expensive (1-2ms), and we do it many times per second. + const bool cache_dirty = cached_current_row_rect_ != opt.rect || cached_current_row_row_ != index.row() || cached_current_row_.isNull(); + + // We can't update the cache if we're not drawing the entire region, + // QTreeView clips its drawing to only the columns in the region, so it + // wouldn't update the whole pixmap properly. + const bool whole_region = current_paint_region_.boundingRect().width() == viewport()->width(); + + if (!cache_dirty) { + painter->drawPixmap(opt.rect, cached_current_row_); + } + else { + if (whole_region) { + const_cast(this)->UpdateCachedCurrentRowPixmap(opt, index); + painter->drawPixmap(opt.rect, cached_current_row_); + } + else { + QTreeView::drawRow(painter, opt, index); + } + } + } + else { + QTreeView::drawRow(painter, opt, index); + } + +} + +void PlaylistView::UpdateCachedCurrentRowPixmap(QStyleOptionViewItemV4 option, const QModelIndex &index) { + + cached_current_row_rect_ = option.rect; + cached_current_row_row_ = index.row(); + + option.rect.moveTo(0, 0); + cached_current_row_ = QPixmap(option.rect.size()); + cached_current_row_.fill(Qt::transparent); + + QPainter p(&cached_current_row_); + QTreeView::drawRow(&p, option, index); + +} + +void PlaylistView::InvalidateCachedCurrentPixmap() { + cached_current_row_ = QPixmap(); +} + +void PlaylistView::timerEvent(QTimerEvent *event) { + QTreeView::timerEvent(event); + if (event->timerId() == glow_timer_.timerId()) GlowIntensityChanged(); +} + +void PlaylistView::GlowIntensityChanged() { + glow_intensity_step_ = (glow_intensity_step_ + 1) % (kGlowIntensitySteps * 2); + + viewport()->update(last_glow_rect_); +} + +void PlaylistView::StopGlowing() { + currently_glowing_ = false; + glow_timer_.stop(); + glow_intensity_step_ = kGlowIntensitySteps; +} + +void PlaylistView::StartGlowing() { + currently_glowing_ = true; + if (isVisible() && glow_enabled_) + glow_timer_.start(1500 / kGlowIntensitySteps, this); +} + +void PlaylistView::hideEvent(QHideEvent *) { glow_timer_.stop(); } + +void PlaylistView::showEvent(QShowEvent *) { + if (currently_glowing_ && glow_enabled_) + glow_timer_.start(1500 / kGlowIntensitySteps, this); + + MaybeAutoscroll(); +} + +bool CompareSelectionRanges(const QItemSelectionRange &a, const QItemSelectionRange &b) { + return b.bottom() < a.bottom(); +} + +void PlaylistView::keyPressEvent(QKeyEvent *event) { + + if (!model() || state() == QAbstractItemView::EditingState) { + QTreeView::keyPressEvent(event); + } + else if (event == QKeySequence::Delete) { + RemoveSelected(false); + event->accept(); +#ifdef Q_OS_DARWIN + } + else if (event->key() == Qt::Key_Backspace) { + RemoveSelected(false); + event->accept(); +#endif + } + else if (event == QKeySequence::Copy) { + CopyCurrentSongToClipboard(); + } + else if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) { + if (currentIndex().isValid()) emit PlayItem(currentIndex()); + event->accept(); + } + else if (event->modifiers() != Qt::ControlModifier && event->key() == Qt::Key_Space) { + emit PlayPause(); + event->accept(); + } + else if (event->key() == Qt::Key_Up) { + app_->player()->SeekTo(0); + event->accept(); + } + else if (event->key() == Qt::Key_Left) { + emit SeekBackward(); + event->accept(); + } + else if (event->key() == Qt::Key_Right) { + emit SeekForward(); + event->accept(); + } + else if (event->modifiers() == Qt::NoModifier && ((event->key() >= Qt::Key_Exclam && event->key() <= Qt::Key_Z) || event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Escape)) { + emit FocusOnFilterSignal(event); + event->accept(); + } + else { + QTreeView::keyPressEvent(event); + } + +} + +void PlaylistView::contextMenuEvent(QContextMenuEvent *e) { + emit RightClicked(e->globalPos(), indexAt(e->pos())); + e->accept(); +} + +void PlaylistView::RemoveSelected(bool deleting_from_disk) { + + int rows_removed = 0; + QItemSelection selection(selectionModel()->selection()); + + if (selection.isEmpty()) { + return; + } + + // Store the last selected row, which is the last in the list + int last_row = selection.last().top(); + + // Sort the selection so we remove the items at the *bottom* first, ensuring + // we don't have to mess around with changing row numbers + qSort(selection.begin(), selection.end(), CompareSelectionRanges); + + for (const QItemSelectionRange &range : selection) { + if (range.top() < last_row) rows_removed += range.height(); + + if (!deleting_from_disk) { + model()->removeRows(range.top(), range.height(), range.topLeft()); + } else { + model()->removeRows(range.top(), range.height(), QModelIndex()); + } + } + + int new_row = last_row - rows_removed; + // Index of the first column for the row to select + QModelIndex new_index = model()->index(new_row, 0); + + // Select the new current item, we want always the item after the last selected + if (new_index.isValid()) { + // Workaround to update keyboard selected row, if it's not the first row + // (this also triggers selection) + if (new_row != 0) + keyPressEvent(new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier)); + // Update visual selection with the entire row + selectionModel()->select(new_index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + } + else { + // We're removing the last item, select the new last row + selectionModel()->select(model()->index(model()->rowCount() - 1, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + } + +} + +QList PlaylistView::GetEditableColumns() { + + QList columns; + QHeaderView *h = header(); + for (int col = 0; col < h->count(); col++) { + if (h->isSectionHidden(col)) continue; + QModelIndex index = model()->index(0, col); + if (index.flags() & Qt::ItemIsEditable) columns << h->visualIndex(col); + } + qSort(columns); + return columns; + +} + +QModelIndex PlaylistView::NextEditableIndex(const QModelIndex ¤t) { + + QList columns = GetEditableColumns(); + QHeaderView *h = header(); + int index = columns.indexOf(h->visualIndex(current.column())); + + if (index + 1 >= columns.size()) + return model()->index(current.row() + 1, h->logicalIndex(columns.first())); + + return model()->index(current.row(), h->logicalIndex(columns[index + 1])); + +} + +QModelIndex PlaylistView::PrevEditableIndex(const QModelIndex ¤t) { + + QList columns = GetEditableColumns(); + QHeaderView *h = header(); + int index = columns.indexOf(h->visualIndex(current.column())); + + if (index - 1 < 0) + return model()->index(current.row() - 1, h->logicalIndex(columns.last())); + + return model()->index(current.row(), h->logicalIndex(columns[index - 1])); + +} + +void PlaylistView::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint) { + + if (hint == QAbstractItemDelegate::NoHint) { + QTreeView::closeEditor(editor, QAbstractItemDelegate::SubmitModelCache); + } + else if (hint == QAbstractItemDelegate::EditNextItem || hint == QAbstractItemDelegate::EditPreviousItem) { + + QModelIndex index; + if (hint == QAbstractItemDelegate::EditNextItem) + index = NextEditableIndex(currentIndex()); + else + index = PrevEditableIndex(currentIndex()); + + if (!index.isValid()) { + QTreeView::closeEditor(editor, QAbstractItemDelegate::SubmitModelCache); + } + else { + QTreeView::closeEditor(editor, QAbstractItemDelegate::NoHint); + setCurrentIndex(index); + edit(index); + } + } + else { + QTreeView::closeEditor(editor, hint); + } + +} + +void PlaylistView::mouseMoveEvent(QMouseEvent *event) { + + if (!drag_over_) { + QTreeView::mouseMoveEvent(event); + } + +} + +void PlaylistView::leaveEvent(QEvent *e) { + + QTreeView::leaveEvent(e); + +} + +void PlaylistView::mousePressEvent(QMouseEvent *event) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (editTriggers() & QAbstractItemView::NoEditTriggers) { + QTreeView::mousePressEvent(event); + return; + } + + QTreeView::mousePressEvent(event); + + inhibit_autoscroll_ = true; + inhibit_autoscroll_timer_->start(); + +} + +void PlaylistView::scrollContentsBy(int dx, int dy) { + + if (dx) { + InvalidateCachedCurrentPixmap(); + } + cached_tree_ = QPixmap(); + + QTreeView::scrollContentsBy(dx, dy); + + if (!currently_autoscrolling_) { + // We only want to do this if the scroll was initiated by the user + inhibit_autoscroll_ = true; + inhibit_autoscroll_timer_->start(); + } + +} + +void PlaylistView::InhibitAutoscrollTimeout() { + // For 30 seconds after the user clicks on or scrolls the playlist we promise + // not to automatically scroll the view to keep up with a track change. + inhibit_autoscroll_ = false; +} + +void PlaylistView::MaybeAutoscroll() { + if (!inhibit_autoscroll_) JumpToCurrentlyPlayingTrack(); +} + +void PlaylistView::JumpToCurrentlyPlayingTrack() { + + Q_ASSERT(playlist_); + + // Usage of the "Jump to the currently playing track" action shall enable autoscroll + inhibit_autoscroll_ = false; + + if (playlist_->current_row() == -1) return; + + QModelIndex current = playlist_->proxy()->mapFromSource(playlist_->index(playlist_->current_row(), 0)); + if (!current.isValid()) return; + + currently_autoscrolling_ = true; + + // Scroll to the item + scrollTo(current, QAbstractItemView::PositionAtCenter); + + currently_autoscrolling_ = false; + +} + +void PlaylistView::JumpToLastPlayedTrack() { + + Q_ASSERT(playlist_); + + if (playlist_->last_played_row() == -1) return; + + QModelIndex last_played = playlist_->proxy()->mapFromSource(playlist_->index(playlist_->last_played_row(), 0)); + if (!last_played.isValid()) return; + + // Select last played song + last_current_item_ = last_played; + setCurrentIndex(last_current_item_); + + currently_autoscrolling_ = true; + + // Scroll to the item + scrollTo(last_played, QAbstractItemView::PositionAtCenter); + + currently_autoscrolling_ = false; + +} + +void PlaylistView::paintEvent(QPaintEvent *event) { + + // Reimplemented to draw the background image. + // Reimplemented also to draw the drop indicator + // When the user is dragging some stuff over the playlist paintEvent gets + // called for the entire viewport every time the user moves the mouse. + // The drawTree is kinda expensive, so we cache the result and draw from the + // cache while the user is dragging. The cached pixmap gets invalidated in + // dragLeaveEvent, dropEvent and scrollContentsBy. + + // Draw background + if (background_image_type_ == Custom || background_image_type_ == Album) { + if (!background_image_.isNull() || !previous_background_image_.isNull()) { + QPainter background_painter(viewport()); + + // Check if we should recompute the background image + if (height() != last_height_ || width() != last_width_ || force_background_redraw_) { + + if (background_image_.isNull()) { + cached_scaled_background_image_ = QPixmap(); + } + else { + cached_scaled_background_image_ = QPixmap::fromImage(background_image_.scaled(width(), height(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); + } + + last_height_ = height(); + last_width_ = width(); + force_background_redraw_ = false; + } + + // Actually draw the background image + if (!cached_scaled_background_image_.isNull()) { + // Set opactiy only if needed, as this deactivate hardware acceleration + if (!qFuzzyCompare(previous_background_image_opacity_, qreal(0.0))) { + background_painter.setOpacity(1.0 - previous_background_image_opacity_); + } + background_painter.drawPixmap((width() - cached_scaled_background_image_.width()) / 2, (height() - cached_scaled_background_image_.height()) / 2, cached_scaled_background_image_); + } + // Draw the previous background image if we're fading + if (!previous_background_image_.isNull()) { + background_painter.setOpacity(previous_background_image_opacity_); + background_painter.drawPixmap((width() - previous_background_image_.width()) / 2, (height() - previous_background_image_.height()) / 2, previous_background_image_); + } + } + } + + QPainter p(viewport()); + + if (drop_indicator_row_ != -1) { + if (cached_tree_.isNull()) { + cached_tree_ = QPixmap(size()); + cached_tree_.fill(Qt::transparent); + + QPainter cache_painter(&cached_tree_); + drawTree(&cache_painter, event->region()); + } + + p.drawPixmap(0, 0, cached_tree_); + } + else { + drawTree(&p, event->region()); + return; + } + + const int first_column = header_->logicalIndex(0); + + // Find the y position of the drop indicator + QModelIndex drop_index = model()->index(drop_indicator_row_, first_column); + int drop_pos = -1; + switch (dropIndicatorPosition()) { + case QAbstractItemView::OnItem: + return; // Don't draw anything + + case QAbstractItemView::AboveItem: + drop_pos = visualRect(drop_index).top(); + break; + + case QAbstractItemView::BelowItem: + drop_pos = visualRect(drop_index).bottom() + 1; + break; + + case QAbstractItemView::OnViewport: + if (model()->rowCount() == 0) + drop_pos = 1; + else + drop_pos = 1 + visualRect(model()->index(model()->rowCount() - 1, first_column)).bottom(); + break; + } + + // Draw a nice gradient first + QColor line_color(QApplication::palette().color(QPalette::Highlight)); + QColor shadow_color(line_color.lighter(140)); + QColor shadow_fadeout_color(shadow_color); + shadow_color.setAlpha(255); + shadow_fadeout_color.setAlpha(0); + + QLinearGradient gradient(QPoint(0, drop_pos - kDropIndicatorGradientWidth), QPoint(0, drop_pos + kDropIndicatorGradientWidth)); + gradient.setColorAt(0.0, shadow_fadeout_color); + gradient.setColorAt(0.5, shadow_color); + gradient.setColorAt(1.0, shadow_fadeout_color); + QPen gradient_pen(QBrush(gradient), kDropIndicatorGradientWidth * 2); + p.setPen(gradient_pen); + p.drawLine(QPoint(0, drop_pos), QPoint(width(), drop_pos)); + + // Now draw the line on top + QPen line_pen(line_color, kDropIndicatorWidth); + p.setPen(line_pen); + p.drawLine(QPoint(0, drop_pos), QPoint(width(), drop_pos)); + +} + +void PlaylistView::dragMoveEvent(QDragMoveEvent *event) { + + QTreeView::dragMoveEvent(event); + + QModelIndex index(indexAt(event->pos())); + drop_indicator_row_ = index.isValid() ? index.row() : 0; + +} + +void PlaylistView::dragEnterEvent(QDragEnterEvent *event) { + QTreeView::dragEnterEvent(event); + cached_tree_ = QPixmap(); + drag_over_ = true; +} + +void PlaylistView::dragLeaveEvent(QDragLeaveEvent *event) { + QTreeView::dragLeaveEvent(event); + cached_tree_ = QPixmap(); + drag_over_ = false; + drop_indicator_row_ = -1; +} + +void PlaylistView::dropEvent(QDropEvent *event) { + QTreeView::dropEvent(event); + cached_tree_ = QPixmap(); + drop_indicator_row_ = -1; + drag_over_ = false; +} + +void PlaylistView::PlaylistDestroyed() { + playlist_ = nullptr; + // We'll get a SetPlaylist() soon +} + +void PlaylistView::ReloadSettings() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QSettings s; + + s.beginGroup(PlaybackSettingsPage::kSettingsGroup); + glow_enabled_ = s.value("glow_effect", true).toBool(); + s.endGroup(); + + if (setting_initial_header_layout_ || upgrading_from_qheaderview_) { + s.beginGroup(Playlist::kSettingsGroup); + header_->SetStretchEnabled(s.value("stretch", true).toBool()); + s.endGroup(); + upgrading_from_qheaderview_ = false; + } + + if (currently_glowing_ && glow_enabled_ && isVisible()) StartGlowing(); + if (!glow_enabled_) StopGlowing(); + + if (setting_initial_header_layout_) { + + header_->SetColumnWidth(Playlist::Column_Track, 0.02); + header_->SetColumnWidth(Playlist::Column_Title, 0.16); + header_->SetColumnWidth(Playlist::Column_Artist, 0.10); + header_->SetColumnWidth(Playlist::Column_Album, 0.10); + header_->SetColumnWidth(Playlist::Column_Length, 0.03); + header_->SetColumnWidth(Playlist::Column_Bitrate, 0.07); + header_->SetColumnWidth(Playlist::Column_SamplerateBitdepth, 0.07); + header_->SetColumnWidth(Playlist::Column_Filetype, 0.06); + + setting_initial_header_layout_ = false; + } + + s.beginGroup(Playlist::kSettingsGroup); + column_alignment_ = s.value("column_alignments").value(); + s.endGroup(); + if (column_alignment_.isEmpty()) { + column_alignment_ = DefaultColumnAlignment(); + } + + emit ColumnAlignmentChanged(column_alignment_); + + // Background: + s.beginGroup(AppearanceSettingsPage::kSettingsGroup); + QVariant q_playlistview_background_type = s.value(kSettingBackgroundImageType); + s.endGroup(); + BackgroundImageType background_type(Default); + // bg_enabled should also be checked for backward compatibility (in releases <= 1.0, there was just a boolean to activate/deactivate the background) + s.beginGroup(Playlist::kSettingsGroup); + QVariant bg_enabled = s.value("bg_enabled"); + s.endGroup(); + if (q_playlistview_background_type.isValid()) { + background_type = static_cast(q_playlistview_background_type.toInt()); + } + else if (bg_enabled.isValid()) { + if (bg_enabled.toBool()) { + background_type = Default; + } + else { + background_type = None; + } + } + else { + background_type = Default; + } + s.beginGroup(AppearanceSettingsPage::kSettingsGroup); + QString background_image_filename = s.value(kSettingBackgroundImageFilename).toString(); + int blur_radius = s.value("blur_radius", kDefaultBlurRadius).toInt(); + int opacity_level = s.value("opacity_level", kDefaultOpacityLevel).toInt(); + s.endGroup(); + // Check if background properties have changed. + // We change properties only if they have actually changed, to avoid to call + // set_background_image when it is not needed, as this will cause the fading + // animation to start again. This also avoid to do useless + // "force_background_redraw". + + if (background_image_filename != background_image_filename_ || background_type != background_image_type_ || blur_radius_ != blur_radius || opacity_level_ != opacity_level) { + // Store background properties + background_image_type_ = background_type; + background_image_filename_ = background_image_filename; + blur_radius_ = blur_radius; + opacity_level_ = opacity_level; + if (background_image_type_ == Custom) { + set_background_image(QImage(background_image_filename)); + } + else if (background_image_type_ == Album) { + set_background_image(current_song_cover_art_); + } + else { + // User changed background image type to something that will not be + // painted through paintEvent: reset all background images. + // This avoid to use old (deprecated) images for fading when selecting + // Album or Custom background image type later. + //set_background_image(QImage(":/pictures/playlistbg.png")); + set_background_image(QImage()); + cached_scaled_background_image_ = QPixmap(); + previous_background_image_ = QPixmap(); + } + setProperty("default_background_enabled", background_image_type_ == Default); + emit BackgroundPropertyChanged(); + force_background_redraw_ = true; + } + + s.beginGroup(PlaylistSettingsPage::kSettingsGroup); + if(!s.value("editmetadatainline", false).toBool()) + setEditTriggers(editTriggers() & ~QAbstractItemView::SelectedClicked); + else + setEditTriggers(editTriggers() | QAbstractItemView::SelectedClicked); + + s.endGroup(); + +} + +void PlaylistView::SaveSettings() { + + if (read_only_settings_) return; + + QSettings s; + + s.beginGroup(PlaybackSettingsPage::kSettingsGroup); + s.setValue("glow_effect", glow_enabled_); + s.endGroup(); + + s.beginGroup(AppearanceSettingsPage::kSettingsGroup); + s.setValue(kSettingBackgroundImageType, background_image_type_); + s.endGroup(); + + s.beginGroup(Playlist::kSettingsGroup); + s.setValue("column_alignments", QVariant::fromValue(column_alignment_)); + s.endGroup(); + +} + +void PlaylistView::StretchChanged(bool stretch) { + setHorizontalScrollBarPolicy(stretch ? Qt::ScrollBarAlwaysOff : Qt::ScrollBarAsNeeded); + SaveGeometry(); +} + +bool PlaylistView::eventFilter(QObject *object, QEvent *event) { + + if (event->type() == QEvent::Enter && (object == horizontalScrollBar() || object == verticalScrollBar())) { + //RatingHoverOut(); + return false; + } + return QObject::eventFilter(object, event); + +} + +void PlaylistView::rowsInserted(const QModelIndex &parent, int start, int end) { + + const bool at_end = end == model()->rowCount(parent) - 1; + + QTreeView::rowsInserted(parent, start, end); + + if (at_end) { + // If the rows were inserted at the end of the playlist then let's scroll + // the view so the user can see. + scrollTo(model()->index(start, 0, parent), QAbstractItemView::PositionAtTop); + } + +} + +ColumnAlignmentMap PlaylistView::DefaultColumnAlignment() { + + ColumnAlignmentMap ret; + + ret[Playlist::Column_Length] = + ret[Playlist::Column_Track] = + ret[Playlist::Column_Disc] = + ret[Playlist::Column_Year] = + ret[Playlist::Column_Bitrate] = + ret[Playlist::Column_Samplerate] = + ret[Playlist::Column_Bitdepth] = + ret[Playlist::Column_SamplerateBitdepth] = + ret[Playlist::Column_Filesize] = + ret[Playlist::Column_PlayCount] = + ret[Playlist::Column_SkipCount] = + ret[Playlist::Column_OriginalYear] = + (Qt::AlignRight | Qt::AlignVCenter); + + return ret; + +} + +void PlaylistView::SetColumnAlignment(int section, Qt::Alignment alignment) { + + if (section < 0) return; + + column_alignment_[section] = alignment; + emit ColumnAlignmentChanged(column_alignment_); + SaveSettings(); + +} + +Qt::Alignment PlaylistView::column_alignment(int section) const { + return column_alignment_.value(section, Qt::AlignLeft | Qt::AlignVCenter); +} + +void PlaylistView::CopyCurrentSongToClipboard() const { + + // Get the display text of all visible columns. + QStringList columns; + + for (int i = 0; i < header()->count(); ++i) { + if (header()->isSectionHidden(i)) { + continue; + } + + const QVariant data = model()->data(currentIndex().sibling(currentIndex().row(), i)); + if (data.type() == QVariant::String) { + columns << data.toString(); + } + } + + // Get the song's URL + const QUrl url = model()->data(currentIndex().sibling(currentIndex().row(), Playlist::Column_Filename)).toUrl(); + + QMimeData *mime_data = new QMimeData; + mime_data->setUrls(QList() << url); + mime_data->setText(columns.join(" - ")); + + QApplication::clipboard()->setMimeData(mime_data); + +} + +void PlaylistView::CurrentSongChanged(const Song &song, const QString &uri, const QImage &song_art) { + + if (current_song_cover_art_ == song_art) return; + + current_song_cover_art_ = song_art; + if (background_image_type_ == Album) { + if (song.art_automatic().isEmpty() && song.art_manual().isEmpty()) { + set_background_image(QImage()); + } + else { + set_background_image(current_song_cover_art_); + } + force_background_redraw_ = true; + update(); + } + +} + +void PlaylistView::set_background_image(const QImage &image) { + + // Save previous image, for fading + previous_background_image_ = cached_scaled_background_image_; + + if (image.isNull() || image.format() == QImage::Format_ARGB32) { + background_image_ = image; + } + else { + background_image_ = image.convertToFormat(QImage::Format_ARGB32); + } + + if (!background_image_.isNull()) { + // Apply opacity filter + uchar *bits = background_image_.bits(); + for (int i = 0 ; i < background_image_.height() * background_image_.bytesPerLine() ; i += 4) { + bits[i + 3] = (opacity_level_ / 100.0) * 255; + } + + if (blur_radius_ != 0) { + QImage blurred(background_image_.size(), QImage::Format_ARGB32_Premultiplied); + blurred.fill(Qt::transparent); + QPainter blur_painter(&blurred); + qt_blurImage(&blur_painter, background_image_, blur_radius_, true, false); + blur_painter.end(); + + background_image_ = blurred; + } + } + + if (isVisible()) { + previous_background_image_opacity_ = 1.0; + fade_animation_->start(); + } + +} + +void PlaylistView::FadePreviousBackgroundImage(qreal value) { + + previous_background_image_opacity_ = value; + if (qFuzzyCompare(previous_background_image_opacity_, qreal(0.0))) { + previous_background_image_ = QPixmap(); + previous_background_image_opacity_ = 0.0; + } + + update(); + +} + +void PlaylistView::PlayerStopped() { + CurrentSongChanged(Song(), QString(), QImage()); +} + +void PlaylistView::focusInEvent(QFocusEvent *event) { + + QTreeView::focusInEvent(event); + + if (event->reason() == Qt::TabFocusReason || + event->reason() == Qt::BacktabFocusReason) { + // If there's a current item but no selection it probably means the list was + // filtered, and the selected item does not match the filter. If there's + // only 1 item in the view it is now impossible to select that item without + // using the mouse. + const QModelIndex ¤t = selectionModel()->currentIndex(); + if (current.isValid() && selectionModel()->selectedIndexes().isEmpty()) { + QItemSelection new_selection(current.sibling(current.row(), 0), current.sibling(current.row(), current.model()->columnCount(current.parent()) - 1)); + selectionModel()->select(new_selection, QItemSelectionModel::Select); + } + } + +} diff --git a/src/playlist/playlistview.h b/src/playlist/playlistview.h new file mode 100644 index 00000000..cbfca5fd --- /dev/null +++ b/src/playlist/playlistview.h @@ -0,0 +1,244 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTVIEW_H +#define PLAYLISTVIEW_H + +#include "config.h" + +#include + +#include +#include +#include + +#include "playlist.h" + +class QCommonStyle; + +class Application; +class CollectionBackend; +class PlaylistHeader; +class QTimeLine; + + +// This proxy style works around a bug/feature introduced in Qt 4.7's QGtkStyle +// that uses Gtk to paint row backgrounds, ignoring any custom brush or palette +// the caller set in the QStyleOption. That breaks our currently playing track +// animation, which relies on the background painted by Qt to be transparent. +// This proxy style uses QCommonStyle to paint the affected elements. +// This class is used by the global search view as well. +class PlaylistProxyStyle : public QProxyStyle { +public: + PlaylistProxyStyle(QStyle *base); + void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const; + + private: + std::unique_ptr common_style_; +}; + +class PlaylistView : public QTreeView { + Q_OBJECT + public: + enum BackgroundImageType { + Invalid, + Default, + None, + Custom, + Album + }; + + PlaylistView(QWidget *parent = nullptr); + + static const int kStateVersion; + // Constants for settings: are persistent, values should not be changed + static const char *kSettingBackgroundImageType; + static const char *kSettingBackgroundImageFilename; + + static const int kDefaultBlurRadius; + static const int kDefaultOpacityLevel; + + static ColumnAlignmentMap DefaultColumnAlignment(); + + void SetApplication(Application *app); + void SetItemDelegates(CollectionBackend *backend); + void SetPlaylist(Playlist *playlist); + void RemoveSelected(bool deleting_from_disk); + + void SetReadOnlySettings(bool read_only) { read_only_settings_ = read_only; } + + Playlist *playlist() const { return playlist_; } + BackgroundImageType background_image_type() const { return background_image_type_; } + Qt::Alignment column_alignment(int section) const; + + // QTreeView + void drawTree(QPainter *painter, const QRegion ®ion) const; + void drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + void setModel(QAbstractItemModel *model); + + public slots: + void ReloadSettings(); + void StopGlowing(); + void StartGlowing(); + void JumpToCurrentlyPlayingTrack(); + void JumpToLastPlayedTrack(); + void closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint); + void SetColumnAlignment(int section, Qt::Alignment alignment); + + void CopyCurrentSongToClipboard() const; + void CurrentSongChanged(const Song &new_song, const QString &uri, const QImage &cover_art); + void PlayerStopped(); + + signals: + void PlayItem(const QModelIndex &index); + void PlayPause(); + void RightClicked(const QPoint &global_pos, const QModelIndex &index); + void SeekForward(); + void SeekBackward(); + void FocusOnFilterSignal(QKeyEvent *event); + void BackgroundPropertyChanged(); + void ColumnAlignmentChanged(const ColumnAlignmentMap &alignment); + + protected: + // QWidget + void keyPressEvent(QKeyEvent *event); + void contextMenuEvent(QContextMenuEvent *e); + void hideEvent(QHideEvent *event); + void showEvent(QShowEvent *event); + void timerEvent(QTimerEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + void leaveEvent(QEvent*); + void paintEvent(QPaintEvent *event); + void dragMoveEvent(QDragMoveEvent *event); + void dragEnterEvent(QDragEnterEvent *event); + void dragLeaveEvent(QDragLeaveEvent *event); + void dropEvent(QDropEvent *event); + //void resizeEvent(QResizeEvent *event); + bool eventFilter(QObject *object, QEvent *event); + void focusInEvent(QFocusEvent *event); + + // QAbstractScrollArea + void scrollContentsBy(int dx, int dy); + + // QAbstractItemView + void rowsInserted(const QModelIndex &parent, int start, int end); + + private slots: + void LoadGeometry(); + void SaveGeometry(); + void GlowIntensityChanged(); + void InhibitAutoscrollTimeout(); + void MaybeAutoscroll(); + void InvalidateCachedCurrentPixmap(); + void PlaylistDestroyed(); + + void SaveSettings(); + void StretchChanged(bool stretch); + + void FadePreviousBackgroundImage(qreal value); + + private: + void ReloadBarPixmaps(); + QList LoadBarPixmap(const QString &filename); + void UpdateCachedCurrentRowPixmap(QStyleOptionViewItemV4 option, const QModelIndex &index); + + void set_background_image_type(BackgroundImageType bg) { + background_image_type_ = bg; + emit BackgroundPropertyChanged(); + } + // Save image as the background_image_ after applying some modifications + // (opacity, ...). + // Should be used instead of modifying background_image_ directly + void set_background_image(const QImage &image); + + private: + static const int kGlowIntensitySteps; + static const int kAutoscrollGraceTimeout; + static const int kDropIndicatorWidth; + static const int kDropIndicatorGradientWidth; + + QList GetEditableColumns(); + QModelIndex NextEditableIndex(const QModelIndex ¤t); + QModelIndex PrevEditableIndex(const QModelIndex ¤t); + + Application *app_; + PlaylistProxyStyle *style_; + Playlist *playlist_; + PlaylistHeader *header_; + bool setting_initial_header_layout_; + bool upgrading_from_qheaderview_; + bool read_only_settings_; + int upgrading_from_version_; + + BackgroundImageType background_image_type_; + // Stores the background image to be displayed. As we want this image to be + // particular (in terms of format, opacity), you should probably use + // set_background_image_type instead of modifying background_image_ directly + QImage background_image_; + int blur_radius_; + int opacity_level_; + // Used if background image is a filemane + QString background_image_filename_; + QImage current_song_cover_art_; + QPixmap cached_scaled_background_image_; + + // For fading when image change + QPixmap previous_background_image_; + qreal previous_background_image_opacity_; + QTimeLine *fade_animation_; + + // To know if we should redraw the background or not + int last_height_; + int last_width_; + bool force_background_redraw_; + + bool glow_enabled_; + bool currently_glowing_; + QBasicTimer glow_timer_; + int glow_intensity_step_; + QModelIndex last_current_item_; + QRect last_glow_rect_; + + QTimer *inhibit_autoscroll_timer_; + bool inhibit_autoscroll_; + bool currently_autoscrolling_; + + int row_height_; // Used to invalidate the currenttrack_bar pixmaps + QList currenttrack_bar_left_; + QList currenttrack_bar_mid_; + QList currenttrack_bar_right_; + QPixmap currenttrack_play_; + QPixmap currenttrack_pause_; + + QRegion current_paint_region_; + QPixmap cached_current_row_; + QRect cached_current_row_rect_; + int cached_current_row_row_; + + QPixmap cached_tree_; + int drop_indicator_row_; + bool drag_over_; + + ColumnAlignmentMap column_alignment_; +}; + +#endif // PLAYLISTVIEW_H diff --git a/src/playlist/queue.cpp b/src/playlist/queue.cpp new file mode 100644 index 00000000..bbaec44c --- /dev/null +++ b/src/playlist/queue.cpp @@ -0,0 +1,364 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "queue.h" + +#include +#include +#include + +const char *Queue::kRowsMimetype = "application/x-strawberry-queue-rows"; + +Queue::Queue(QObject *parent) : QAbstractProxyModel(parent) {} + +QModelIndex Queue::mapFromSource(const QModelIndex &source_index) const { + + if (!source_index.isValid()) return QModelIndex(); + + const int source_row = source_index.row(); + for (int i = 0; i < source_indexes_.count(); ++i) { + if (source_indexes_[i].row() == source_row) + return index(i, source_index.column()); + } + return QModelIndex(); + +} + +bool Queue::ContainsSourceRow(int source_row) const { + + for (int i = 0; i < source_indexes_.count(); ++i) { + if (source_indexes_[i].row() == source_row) return true; + } + return false; + +} + +QModelIndex Queue::mapToSource(const QModelIndex &proxy_index) const { + + if (!proxy_index.isValid()) return QModelIndex(); + + return source_indexes_[proxy_index.row()]; + +} + +void Queue::setSourceModel(QAbstractItemModel *source_model) { + + if (sourceModel()) { + disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(SourceDataChanged(QModelIndex,QModelIndex))); + disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(SourceLayoutChanged())); + disconnect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(SourceLayoutChanged())); + } + + QAbstractProxyModel::setSourceModel(source_model); + + connect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(SourceDataChanged(QModelIndex,QModelIndex))); + connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(SourceLayoutChanged())); + connect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(SourceLayoutChanged())); + +} + +void Queue::SourceDataChanged(const QModelIndex &top_left, const QModelIndex &bottom_right) { + + for (int row = top_left.row(); row <= bottom_right.row(); ++row) { + QModelIndex proxy_index = mapFromSource(sourceModel()->index(row, 0)); + if (!proxy_index.isValid()) continue; + + emit dataChanged(proxy_index, proxy_index); + } + +} + +void Queue::SourceLayoutChanged() { + + for (int i = 0; i < source_indexes_.count(); ++i) { + if (!source_indexes_[i].isValid()) { + beginRemoveRows(QModelIndex(), i, i); + source_indexes_.removeAt(i); + endRemoveRows(); + + --i; + } + } + +} + +QModelIndex Queue::index(int row, int column, const QModelIndex &parent) const { + return createIndex(row, column); +} + +QModelIndex Queue::parent(const QModelIndex &child) const { + return QModelIndex(); +} + +int Queue::rowCount(const QModelIndex &parent) const { + if (parent.isValid()) return 0; + return source_indexes_.count(); +} + +int Queue::columnCount(const QModelIndex&) const { return 1; } + +QVariant Queue::data(const QModelIndex &proxy_index, int role) const { + + QModelIndex source_index = source_indexes_[proxy_index.row()]; + + switch (role) { + case Playlist::Role_QueuePosition: + return proxy_index.row(); + + case Qt::DisplayRole: { + const QString artist = source_index.sibling(source_index.row(), Playlist::Column_Artist).data().toString(); + const QString title = source_index.sibling(source_index.row(), Playlist::Column_Title).data().toString(); + + if (artist.isEmpty()) return title; + return QString(artist + " - " + title); + } + + default: + return QVariant(); + } + +} + +void Queue::ToggleTracks(const QModelIndexList &source_indexes) { + + for (const QModelIndex &source_index : source_indexes) { + QModelIndex proxy_index = mapFromSource(source_index); + if (proxy_index.isValid()) { + // Dequeue the track + const int row = proxy_index.row(); + beginRemoveRows(QModelIndex(), row, row); + source_indexes_.removeAt(row); + endRemoveRows(); + } + else { + // Enqueue the track + const int row = source_indexes_.count(); + beginInsertRows(QModelIndex(), row, row); + source_indexes_ << QPersistentModelIndex(source_index); + endInsertRows(); + } + } + +} + +int Queue::PositionOf(const QModelIndex &source_index) const { + return mapFromSource(source_index).row(); +} + +bool Queue::is_empty() const { + return source_indexes_.isEmpty(); +} + +void Queue::Clear() { + if (source_indexes_.isEmpty()) return; + + beginRemoveRows(QModelIndex(), 0, source_indexes_.count() - 1); + source_indexes_.clear(); + endRemoveRows(); +} + +void Queue::Move(const QList &proxy_rows, int pos) { + + layoutAboutToBeChanged(); + QList moved_items; + + // Take the items out of the list first, keeping track of whether the + // insertion point changes + int offset = 0; + for (int row : proxy_rows) { + moved_items << source_indexes_.takeAt(row - offset); + if (pos != -1 && pos >= row) pos--; + offset++; + } + + // Put the items back in + const int start = pos == -1 ? source_indexes_.count() : pos; + for (int i = start; i < start + moved_items.count(); ++i) { + source_indexes_.insert(i, moved_items[i - start]); + } + + // Update persistent indexes + for (const QModelIndex &pidx : persistentIndexList()) { + const int dest_offset = proxy_rows.indexOf(pidx.row()); + if (dest_offset != -1) { + // This index was moved + changePersistentIndex(pidx, index(start + dest_offset, pidx.column(), QModelIndex())); + } + else { + int d = 0; + for (int row : proxy_rows) { + if (pidx.row() > row) d--; + } + if (pidx.row() + d >= start) d += proxy_rows.count(); + + changePersistentIndex(pidx, index(pidx.row() + d, pidx.column(), QModelIndex())); + } + } + + layoutChanged(); + +} + +void Queue::MoveUp(int row) { + Move(QList() << row, row - 1); +} + +void Queue::MoveDown(int row) { + Move(QList() << row, row + 2); +} + +QStringList Queue::mimeTypes() const { + return QStringList() << kRowsMimetype << Playlist::kRowsMimetype; +} + +Qt::DropActions Queue::supportedDropActions() const { + return Qt::MoveAction | Qt::CopyAction | Qt::LinkAction; +} + +QMimeData *Queue::mimeData(const QModelIndexList &indexes) const { + + QMimeData *data = new QMimeData; + + QList rows; + for (const QModelIndex &index : indexes) { + if (index.column() != 0) continue; + + rows << index.row(); + } + + QBuffer buf; + buf.open(QIODevice::WriteOnly); + QDataStream stream(&buf); + stream << rows; + buf.close(); + + data->setData(kRowsMimetype, buf.data()); + + return data; + +} + +bool Queue::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int, const QModelIndex&) { + + if (action == Qt::IgnoreAction) + return false; + + if (data->hasFormat(kRowsMimetype)) { + // Dragged from the queue + + QList proxy_rows; + QDataStream stream(data->data(kRowsMimetype)); + stream >> proxy_rows; + qStableSort(proxy_rows); // Make sure we take them in order + + Move(proxy_rows, row); + } + else if (data->hasFormat(Playlist::kRowsMimetype)) { + // Dragged from the playlist + + Playlist *playlist = nullptr; + QList source_rows; + QDataStream stream(data->data(Playlist::kRowsMimetype)); + stream.readRawData(reinterpret_cast(&playlist), sizeof(playlist)); + stream >> source_rows; + + QModelIndexList source_indexes; + for (int source_row : source_rows) { + const QModelIndex source_index = sourceModel()->index(source_row, 0); + const QModelIndex proxy_index = mapFromSource(source_index); + if (proxy_index.isValid()) { + // This row was already in the queue, so no need to add it again + continue; + } + + source_indexes << source_index; + } + + if (!source_indexes.isEmpty()) { + const int insert_point = row == -1 ? source_indexes_.count() : row; + beginInsertRows(QModelIndex(), insert_point, insert_point + source_indexes.count() - 1); + for (int i = 0 ; i < source_indexes.count() ; ++i) { + source_indexes_.insert(insert_point + i, source_indexes[i]); + } + endInsertRows(); + } + } + + return true; + +} + +Qt::ItemFlags Queue::flags(const QModelIndex &index) const { + + Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; + + if (index.isValid()) + flags |= Qt::ItemIsDragEnabled; + else + flags |= Qt::ItemIsDropEnabled; + + return flags; + +} + +int Queue::PeekNext() const { + if (source_indexes_.isEmpty()) return -1; + return source_indexes_.first().row(); +} + +int Queue::TakeNext() { + + if (source_indexes_.isEmpty()) return -1; + + beginRemoveRows(QModelIndex(), 0, 0); + int ret = source_indexes_.takeFirst().row(); + endRemoveRows(); + + return ret; + +} + +QVariant Queue::headerData(int section, Qt::Orientation orientation, int role) const { + return QVariant(); +} + +void Queue::Remove(QList &proxy_rows) { + + // order the rows + qStableSort(proxy_rows); + + // reflects immediately changes in the playlist + layoutAboutToBeChanged(); + + int removed_rows = 0; + for (int row : proxy_rows) { + // after the first row, the row number needs to be updated + const int real_row = row - removed_rows; + beginRemoveRows(QModelIndex(), real_row, real_row); + source_indexes_.removeAt(real_row); + endRemoveRows(); + removed_rows++; + } + + layoutChanged(); + +} diff --git a/src/playlist/queue.h b/src/playlist/queue.h new file mode 100644 index 00000000..d07c9d38 --- /dev/null +++ b/src/playlist/queue.h @@ -0,0 +1,79 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef QUEUE_H +#define QUEUE_H + +#include "config.h" + +#include "playlist.h" + +#include + +class Queue : public QAbstractProxyModel { + Q_OBJECT + + public: + Queue(QObject *parent = nullptr); + + static const char *kRowsMimetype; + + // Query the queue + bool is_empty() const; + int PositionOf(const QModelIndex &source_index) const; + bool ContainsSourceRow(int source_row) const; + int PeekNext() const; + + // Modify the queue + int TakeNext(); + void ToggleTracks(const QModelIndexList &source_indexes); + void Clear(); + void Move(const QList &proxy_rows, int pos); + void MoveUp(int row); + void MoveDown(int row); + void Remove(QList &proxy_rows); + + // QAbstractProxyModel + void setSourceModel(QAbstractItemModel *source_model); + QModelIndex mapFromSource(const QModelIndex &source_index) const; + QModelIndex mapToSource(const QModelIndex &proxy_index) const; + + // QAbstractItemModel + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &child) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &proxy_index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QStringList mimeTypes() const; + Qt::DropActions supportedDropActions() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + Qt::ItemFlags flags(const QModelIndex &index) const; + +private slots: + void SourceDataChanged(const QModelIndex &top_left, const QModelIndex &bottom_right); + void SourceLayoutChanged(); + +private: + QList source_indexes_; +}; + +#endif // QUEUE_H diff --git a/src/playlist/queuemanager.cpp b/src/playlist/queuemanager.cpp new file mode 100644 index 00000000..e3a50178 --- /dev/null +++ b/src/playlist/queuemanager.cpp @@ -0,0 +1,163 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "playlist.h" +#include "playlistdelegates.h" +#include "playlistmanager.h" +#include "queue.h" +#include "queuemanager.h" +#include "ui_queuemanager.h" +#include "core/iconloader.h" + +#include +#include + +QueueManager::QueueManager(QWidget *parent) + : QDialog(parent), + ui_(new Ui_QueueManager), + playlists_(nullptr), + current_playlist_(nullptr) { + ui_->setupUi(this); + ui_->list->setItemDelegate(new QueuedItemDelegate(this, 0)); + + // Set icons on buttons + ui_->move_down->setIcon(IconLoader::Load("go-down")); + ui_->move_up->setIcon(IconLoader::Load("go-up")); + ui_->remove->setIcon(IconLoader::Load("edit-delete")); + ui_->clear->setIcon(IconLoader::Load("edit-clear-list")); + + // Set a standard shortcut + ui_->remove->setShortcut(QKeySequence::Delete); + + // Button connections + connect(ui_->move_down, SIGNAL(clicked()), SLOT(MoveDown())); + connect(ui_->move_up, SIGNAL(clicked()), SLOT(MoveUp())); + connect(ui_->remove, SIGNAL(clicked()), SLOT(Remove())); + connect(ui_->clear, SIGNAL(clicked()), SLOT(Clear())); + + QShortcut *close = new QShortcut(QKeySequence::Close, this); + connect(close, SIGNAL(activated()), SLOT(close())); + +} + +QueueManager::~QueueManager() { + delete ui_; +} + +void QueueManager::SetPlaylistManager(PlaylistManager *manager) { + + playlists_ = manager; + + connect(playlists_, SIGNAL(CurrentChanged(Playlist*)), SLOT(CurrentPlaylistChanged(Playlist*))); + CurrentPlaylistChanged(playlists_->current()); + +} + +void QueueManager::CurrentPlaylistChanged(Playlist *playlist) { + + if (current_playlist_) { + disconnect(current_playlist_->queue(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(UpdateButtonState())); + disconnect(current_playlist_->queue(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(UpdateButtonState())); + disconnect(current_playlist_->queue(), SIGNAL(layoutChanged()), this, SLOT(UpdateButtonState())); + disconnect(current_playlist_, SIGNAL(destroyed()), this, SLOT(PlaylistDestroyed())); + } + + current_playlist_ = playlist; + + connect(current_playlist_->queue(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(UpdateButtonState())); + connect(current_playlist_->queue(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(UpdateButtonState())); + connect(current_playlist_->queue(), SIGNAL(layoutChanged()), this, SLOT(UpdateButtonState())); + connect(current_playlist_, SIGNAL(destroyed()), this, SLOT(PlaylistDestroyed())); + + ui_->list->setModel(current_playlist_->queue()); + + connect(ui_->list->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), SLOT(UpdateButtonState())); + +} + +void QueueManager::MoveUp() { + + QModelIndexList indexes = ui_->list->selectionModel()->selectedRows(); + qStableSort(indexes); + + if (indexes.isEmpty() || indexes.first().row() == 0) return; + + for (const QModelIndex &index : indexes) { + current_playlist_->queue()->MoveUp(index.row()); + } + +} + +void QueueManager::MoveDown() { + + QModelIndexList indexes = ui_->list->selectionModel()->selectedRows(); + qStableSort(indexes); + + if (indexes.isEmpty() || indexes.last().row() == current_playlist_->queue()->rowCount()-1) + return; + + for (int i = indexes.count() - 1; i >= 0; --i) { + current_playlist_->queue()->MoveDown(indexes[i].row()); + } + +} + +void QueueManager::Clear() { + current_playlist_->queue()->Clear(); +} + +void QueueManager::Remove() { + + // collect the rows to be removed + QList row_list; + for (const QModelIndex &index : ui_->list->selectionModel()->selectedRows()) { + if (index.isValid()) row_list << index.row(); + } + + current_playlist_->queue()->Remove(row_list); + +} + +void QueueManager::UpdateButtonState() { + + const QModelIndex current = ui_->list->selectionModel()->currentIndex(); + + if (current.isValid()) { + ui_->move_up->setEnabled(current.row() != 0); + ui_->move_down->setEnabled(current.row() != current_playlist_->queue()->rowCount()-1); + ui_->remove->setEnabled(true); + } + else { + ui_->move_up->setEnabled(false); + ui_->move_down->setEnabled(false); + ui_->remove->setEnabled(false); + } + + ui_->clear->setEnabled(!current_playlist_->queue()->is_empty()); + +} + +void QueueManager::PlaylistDestroyed() { + current_playlist_ = nullptr; + // We'll get another CurrentPlaylistChanged() soon +} + diff --git a/src/playlist/queuemanager.h b/src/playlist/queuemanager.h new file mode 100644 index 00000000..e34ff3db --- /dev/null +++ b/src/playlist/queuemanager.h @@ -0,0 +1,61 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef QUEUEMANAGER_H +#define QUEUEMANAGER_H + +#include "config.h" + +#include + +class Playlist; +class PlaylistManager; +class Ui_QueueManager; + +class QModelIndex; + +class QueueManager : public QDialog { + Q_OBJECT + + public: + QueueManager(QWidget *parent = nullptr); + ~QueueManager(); + + void SetPlaylistManager(PlaylistManager *manager); + +private slots: + void CurrentPlaylistChanged(Playlist *playlist); + void PlaylistDestroyed(); + void UpdateButtonState(); + + void MoveUp(); + void MoveDown(); + void Remove(); + void Clear(); + +private: + Ui_QueueManager *ui_; + + PlaylistManager *playlists_; + Playlist *current_playlist_; +}; + +#endif // QUEUEMANAGER_H + diff --git a/src/playlist/queuemanager.ui b/src/playlist/queuemanager.ui new file mode 100644 index 00000000..d6d88b7f --- /dev/null +++ b/src/playlist/queuemanager.ui @@ -0,0 +1,195 @@ + + + QueueManager + + + + 0 + 0 + 582 + 363 + + + + Queue Manager + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + + + + + + true + + + true + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + false + + + false + + + + + + + + + false + + + Move up + + + + 16 + 16 + + + + Ctrl+Up + + + + + + + false + + + Move down + + + + 16 + 16 + + + + Ctrl+Down + + + + + + + false + + + Remove + + + + + + + + + + false + + + Clear + + + + 16 + 16 + + + + Ctrl+K + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + + + buttonBox + accepted() + QueueManager + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + QueueManager + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/playlist/songloaderinserter.cpp b/src/playlist/songloaderinserter.cpp new file mode 100644 index 00000000..82ea59fe --- /dev/null +++ b/src/playlist/songloaderinserter.cpp @@ -0,0 +1,176 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include "playlist.h" +#include "songloaderinserter.h" +#include "core/logging.h" +#include "core/songloader.h" +#include "core/taskmanager.h" + +SongLoaderInserter::SongLoaderInserter(TaskManager *task_manager, CollectionBackendInterface *collection, const Player *player) + : task_manager_(task_manager), + destination_(nullptr), + row_(-1), + play_now_(true), + enqueue_(false), + collection_(collection), + player_(player) {} + +SongLoaderInserter::~SongLoaderInserter() { qDeleteAll(pending_); } + +void SongLoaderInserter::Load(Playlist *destination, int row, bool play_now, bool enqueue, const QList &urls) { + + destination_ = destination; + row_ = row; + play_now_ = play_now; + enqueue_ = enqueue; + + connect(destination, SIGNAL(destroyed()), SLOT(DestinationDestroyed())); + connect(this, SIGNAL(PreloadFinished()), SLOT(InsertSongs())); + connect(this, SIGNAL(EffectiveLoadFinished(const SongList&)), destination, SLOT(UpdateItems(const SongList&))); + + for (const QUrl& url : urls) { + SongLoader *loader = new SongLoader(collection_, player_, this); + + SongLoader::Result ret = loader->Load(url); + + if (ret == SongLoader::BlockingLoadRequired) { + pending_.append(loader); + continue; + } + + if (ret == SongLoader::Success) + songs_ << loader->songs(); + else + emit Error(tr("Error loading %1").arg(url.toString())); + delete loader; + } + + if (pending_.isEmpty()) { + InsertSongs(); + deleteLater(); + } + else { + QtConcurrent::run(this, &SongLoaderInserter::AsyncLoad); + } +} + +// Load audio CD tracks: +// First, we add tracks (without metadata) into the playlist +// In the meantime, MusicBrainz will be queried to get songs' metadata. +// AudioCDTagsLoaded will be called next, and playlist's items will be updated. +void SongLoaderInserter::LoadAudioCD(Playlist *destination, int row, bool play_now, bool enqueue) { + + destination_ = destination; + row_ = row; + play_now_ = play_now; + enqueue_ = enqueue; + + SongLoader *loader = new SongLoader(collection_, player_, this); + NewClosure(loader, SIGNAL(AudioCDTracksLoaded()), this, SLOT(AudioCDTracksLoaded(SongLoader*)), loader); + connect(loader, SIGNAL(LoadAudioCDFinished(bool)), SLOT(AudioCDTagsLoaded(bool))); + qLog(Info) << "Loading audio CD..."; + SongLoader::Result ret = loader->LoadAudioCD(); + if (ret == SongLoader::Error) { + emit Error(tr("Error while loading audio CD")); + delete loader; + } + // Songs will be loaded later: see AudioCDTracksLoaded and AudioCDTagsLoaded slots + +} + +void SongLoaderInserter::DestinationDestroyed() { destination_ = nullptr; } + +void SongLoaderInserter::AudioCDTracksLoaded(SongLoader *loader) { + songs_ = loader->songs(); + InsertSongs(); +} + +void SongLoaderInserter::AudioCDTagsLoaded(bool success) { + + SongLoader *loader = qobject_cast(sender()); + if (!loader || !destination_) return; + + if (success) + destination_->UpdateItems(loader->songs()); + else + qLog(Error) << "Error while getting audio CD metadata from MusicBrainz"; + + deleteLater(); + +} + +void SongLoaderInserter::InsertSongs() { + // Insert songs (that haven't been completely loaded) to allow user to see + // and play them while not loaded completely + if (destination_) { + destination_->InsertSongsOrCollectionItems(songs_, row_, play_now_, enqueue_); + } +} + +void SongLoaderInserter::AsyncLoad() { + + // First, quick load raw songs. + int async_progress = 0; + int async_load_id = task_manager_->StartTask(tr("Loading tracks")); + task_manager_->SetTaskProgress(async_load_id, async_progress, + pending_.count()); + for (int i = 0; i < pending_.count(); ++i) { + SongLoader *loader = pending_[i]; + loader->LoadFilenamesBlocking(); + task_manager_->SetTaskProgress(async_load_id, ++async_progress); + if (i == 0) { + // Load everything from the first song. It'll start playing as soon as + // we emit PreloadFinished, so it needs to have the duration set to show + // properly in the UI. + loader->LoadMetadataBlocking(); + } + songs_ << loader->songs(); + } + task_manager_->SetTaskFinished(async_load_id); + emit PreloadFinished(); + + // Songs are inserted in playlist, now load them completely. + async_progress = 0; + async_load_id = task_manager_->StartTask(tr("Loading tracks info")); + task_manager_->SetTaskProgress(async_load_id, async_progress, songs_.count()); + SongList songs; + for (int i = 0; i < pending_.count(); ++i) { + SongLoader *loader = pending_[i]; + if (i != 0) { + // We already did this earlier for the first song. + loader->LoadMetadataBlocking(); + } + songs << loader->songs(); + task_manager_->SetTaskProgress(async_load_id, songs.count()); + } + task_manager_->SetTaskFinished(async_load_id); + + // Replace the partially-loaded items by the new ones, fully loaded. + emit EffectiveLoadFinished(songs); + + deleteLater(); + +} + diff --git a/src/playlist/songloaderinserter.h b/src/playlist/songloaderinserter.h new file mode 100644 index 00000000..ad2be028 --- /dev/null +++ b/src/playlist/songloaderinserter.h @@ -0,0 +1,78 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef SONGLOADERINSERTER_H +#define SONGLOADERINSERTER_H + +#include "config.h" + +#include +#include +#include + +#include "core/song.h" + +class CollectionBackendInterface; +class Player; +class Playlist; +class SongLoader; +class TaskManager; + +class QModelIndex; + +class SongLoaderInserter : public QObject { + Q_OBJECT + public: + SongLoaderInserter(TaskManager *task_manager, CollectionBackendInterface *collection, const Player *player); + ~SongLoaderInserter(); + + void Load(Playlist *destination, int row, bool play_now, bool enqueue, const QList &urls); + void LoadAudioCD(Playlist *destination, int row, bool play_now, bool enqueue); + +signals: + void Error(const QString &message); + void PreloadFinished(); + void EffectiveLoadFinished(const SongList &songs); + + private slots: + void DestinationDestroyed(); + void AudioCDTracksLoaded(SongLoader *loader); + void AudioCDTagsLoaded(bool success); + void InsertSongs(); + + private: + void AsyncLoad(); + + private: + TaskManager *task_manager_; + + Playlist *destination_; + int row_; + bool play_now_; + bool enqueue_; + + SongList songs_; + + QList pending_; + CollectionBackendInterface *collection_; + const Player *player_; +}; + +#endif // SONGLOADERINSERTER_H diff --git a/src/playlist/songmimedata.h b/src/playlist/songmimedata.h new file mode 100644 index 00000000..ff4841aa --- /dev/null +++ b/src/playlist/songmimedata.h @@ -0,0 +1,44 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef SONGMIMEDATA_H +#define SONGMIMEDATA_H + +#include "config.h" + +#include + +#include "core/mimedata.h" +#include "core/song.h" + +class CollectionBackendInterface; + +class SongMimeData : public MimeData { + Q_OBJECT + + public: + SongMimeData() : backend(nullptr) {} + + CollectionBackendInterface *backend; + SongList songs; +}; + +#endif // SONGMIMEDATA_H + diff --git a/src/playlist/songplaylistitem.cpp b/src/playlist/songplaylistitem.cpp new file mode 100644 index 00000000..4bccebb0 --- /dev/null +++ b/src/playlist/songplaylistitem.cpp @@ -0,0 +1,56 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "playlistbackend.h" +#include "songplaylistitem.h" +#include "core/tagreaderclient.h" + +#include "collection/sqlrow.h" + +#include +#include +#include + +SongPlaylistItem::SongPlaylistItem(const QString &type) : PlaylistItem(type) {} + +SongPlaylistItem::SongPlaylistItem(const Song &song) + : PlaylistItem("File"), song_(song) {} + +bool SongPlaylistItem::InitFromQuery(const SqlRow &query) { + + song_.InitFromQuery(query, false, (Song::kColumns.count() + 1) * 3); + + return true; +} + +QUrl SongPlaylistItem::Url() const { return song_.url(); } + +void SongPlaylistItem::Reload() { + if (song_.url().scheme() != "file") return; + + TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_); +} + +Song SongPlaylistItem::Metadata() const { + if (HasTemporaryMetadata()) return temp_metadata_; + return song_; +} diff --git a/src/playlist/songplaylistitem.h b/src/playlist/songplaylistitem.h new file mode 100644 index 00000000..f7250811 --- /dev/null +++ b/src/playlist/songplaylistitem.h @@ -0,0 +1,51 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef SONGPLAYLISTITEM_H +#define SONGPLAYLISTITEM_H + +#include "config.h" + +#include "playlistitem.h" +#include "core/song.h" + +class SongPlaylistItem : public PlaylistItem { + public: + SongPlaylistItem(const QString& type); + SongPlaylistItem(const Song& song); + + // Restores a stream- or file-related playlist item using query row. + // If it's a file related playlist item, this will restore it's CUE + // attributes (if any) but won't parse the CUE! + bool InitFromQuery(const SqlRow& query); + void Reload(); + + Song Metadata() const; + + QUrl Url() const; + + protected: + Song DatabaseSongMetadata() const { return song_; } + + private: + Song song_; +}; + +#endif // SONGPLAYLISTITEM_H diff --git a/src/playlistparsers/asxiniparser.cpp b/src/playlistparsers/asxiniparser.cpp new file mode 100644 index 00000000..145a76d8 --- /dev/null +++ b/src/playlistparsers/asxiniparser.cpp @@ -0,0 +1,69 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "asxiniparser.h" +#include "core/logging.h" + +#include +#include + +AsxIniParser::AsxIniParser(CollectionBackendInterface *collection, QObject *parent) + : ParserBase(collection, parent) {} + +bool AsxIniParser::TryMagic(const QByteArray &data) const { + return data.toLower().contains("[reference]"); +} + +SongList AsxIniParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir) const { + + SongList ret; + + while (!device->atEnd()) { + QString line = QString::fromUtf8(device->readLine()).trimmed(); + int equals = line.indexOf('='); + QString key = line.left(equals).toLower(); + QString value = line.mid(equals + 1); + + if (key.startsWith("ref")) { + Song song = LoadSong(value, 0, dir); + if (song.is_valid()) { + ret << song; + } + } + } + + return ret; + +} + +void AsxIniParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, Playlist::Path path_type) const { + + QTextStream s(device); + s << "[Reference]" << endl; + + int n = 1; + for (const Song &song : songs) { + s << "Ref" << n << "=" << URLOrFilename(song.url(), dir, path_type) << endl; + ++n; + } + +} diff --git a/src/playlistparsers/asxiniparser.h b/src/playlistparsers/asxiniparser.h new file mode 100644 index 00000000..29a3e3e1 --- /dev/null +++ b/src/playlistparsers/asxiniparser.h @@ -0,0 +1,43 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ASXINIPARSER_H +#define ASXINIPARSER_H + +#include "config.h" + +#include "parserbase.h" + +class AsxIniParser : public ParserBase { + Q_OBJECT + + public: + AsxIniParser(CollectionBackendInterface *collection, QObject *parent = nullptr); + + QString name() const { return "ASX/INI"; } + QStringList file_extensions() const { return QStringList() << "asxini"; } + + bool TryMagic(const QByteArray &data) const; + + SongList Load(QIODevice *device, const QString &playlist_path = "", const QDir &dir = QDir()) const; + void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), Playlist::Path path_type = Playlist::Path_Automatic) const; +}; + +#endif // ASXINIPARSER_H diff --git a/src/playlistparsers/asxparser.cpp b/src/playlistparsers/asxparser.cpp new file mode 100644 index 00000000..6fc405f9 --- /dev/null +++ b/src/playlistparsers/asxparser.cpp @@ -0,0 +1,155 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "asxparser.h" +#include "core/utilities.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +ASXParser::ASXParser(CollectionBackendInterface *collection, QObject *parent) + : XMLParser(collection, parent) {} + +SongList ASXParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir) const { + + // We have to load everything first so we can munge the "XML". + QByteArray data = device->readAll(); + + // (thanks Amarok...) + // ASX looks a lot like xml, but doesn't require tags to be case sensitive, + // meaning we have to accept things like: ... + // We use a dirty way to achieve this: we make all tags lower case + QRegExp ex("(<[/]?[^>]*[A-Z]+[^>]*>)"); + ex.setCaseSensitivity(Qt::CaseInsensitive); + int index = 0; + while ((index = ex.indexIn(data, index)) != -1) { + data.replace(ex.cap(1).toLocal8Bit(), ex.cap(1).toLower().toLocal8Bit()); + index += ex.matchedLength(); + } + + // Some playlists have unescaped & characters in URLs :( + ex.setPattern("(href\\s*=\\s*\")([^\"]+)\""); + index = 0; + while ((index = ex.indexIn(data, index)) != -1) { + QString url = ex.cap(2); + url.replace(QRegExp("&(?!amp;|quot;|apos;|lt;|gt;)"), "&"); + + QByteArray replacement = QString(ex.cap(1) + url + "\"").toLocal8Bit(); + data.replace(ex.cap(0).toLocal8Bit(), replacement); + index += replacement.length(); + } + + QBuffer buffer(&data); + buffer.open(QIODevice::ReadOnly); + + SongList ret; + + QXmlStreamReader reader(&buffer); + if (!Utilities::ParseUntilElement(&reader, "asx")) { + return ret; + } + + while (!reader.atEnd() && Utilities::ParseUntilElement(&reader, "entry")) { + Song song = ParseTrack(&reader, dir); + if (song.is_valid()) { + ret << song; + } + } + return ret; + +} + +Song ASXParser::ParseTrack(QXmlStreamReader *reader, const QDir &dir) const { + + QString title, artist, album, ref; + + while (!reader->atEnd()) { + QXmlStreamReader::TokenType type = reader->readNext(); + + switch (type) { + case QXmlStreamReader::StartElement: { + QStringRef name = reader->name(); + if (name == "ref") { + ref = reader->attributes().value("href").toString(); + } else if (name == "title") { + title = reader->readElementText(); + } else if (name == "author") { + artist = reader->readElementText(); + } + break; + } + case QXmlStreamReader::EndElement: { + if (reader->name() == "entry") { + goto return_song; + } + break; + } + default: + break; + } + } + +return_song: + Song song = LoadSong(ref, 0, dir); + + // Override metadata with what was in the playlist + song.set_title(title); + song.set_artist(artist); + song.set_album(album); + return song; + +} + +void ASXParser::Save(const SongList &songs, QIODevice *device, const QDir&, Playlist::Path path_type) const { + + QXmlStreamWriter writer(device); + writer.setAutoFormatting(true); + writer.setAutoFormattingIndent(2); + writer.writeStartDocument(); + { + StreamElement asx("asx", &writer); + writer.writeAttribute("version", "3.0"); + for (const Song &song : songs) { + StreamElement entry("entry", &writer); + writer.writeTextElement("title", song.title()); + { + StreamElement ref("ref", &writer); + writer.writeAttribute("href", song.url().toString()); + } + if (!song.artist().isEmpty()) { + writer.writeTextElement("author", song.artist()); + } + } + } + writer.writeEndDocument(); + +} + +bool ASXParser::TryMagic(const QByteArray &data) const { + return data.toLower().contains(" + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ASXPARSER_H +#define ASXPARSER_H + +#include "config.h" + +#include "xmlparser.h" + +class ASXParser : public XMLParser { + Q_OBJECT + + public: + ASXParser(CollectionBackendInterface *collection, QObject *parent = nullptr); + + QString name() const { return "ASX"; } + QStringList file_extensions() const { return QStringList() << "asx"; } + + bool TryMagic(const QByteArray &data) const; + + SongList Load(QIODevice *device, const QString &playlist_path = "", const QDir &dir = QDir()) const; + void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), Playlist::Path path_type = Playlist::Path_Automatic) const; + + private: + Song ParseTrack(QXmlStreamReader *reader, const QDir &dir) const; +}; + +#endif diff --git a/src/playlistparsers/cueparser.cpp b/src/playlistparsers/cueparser.cpp new file mode 100644 index 00000000..83ffdd20 --- /dev/null +++ b/src/playlistparsers/cueparser.cpp @@ -0,0 +1,374 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "cueparser.h" +#include "core/logging.h" +#include "core/timeconstants.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +const char *CueParser::kFileLineRegExp = "(\\S+)\\s+(?:\"([^\"]+)\"|(\\S+))\\s*(?:\"([^\"]+)\"|(\\S+))?"; +const char *CueParser::kIndexRegExp = "(\\d{2,3}):(\\d{2}):(\\d{2})"; + +const char *CueParser::kPerformer = "performer"; +const char *CueParser::kTitle = "title"; +const char *CueParser::kSongWriter = "songwriter"; +const char *CueParser::kFile = "file"; +const char *CueParser::kTrack = "track"; +const char *CueParser::kIndex = "index"; +const char *CueParser::kAudioTrackType = "audio"; +const char *CueParser::kRem = "rem"; +const char *CueParser::kGenre = "genre"; +const char *CueParser::kDate = "date"; +const char *CueParser::kDisc = "discnumber"; + +CueParser::CueParser(CollectionBackendInterface *collection, QObject *parent) + : ParserBase(collection, parent) {} + +SongList CueParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir) const { + SongList ret; + + QTextStream text_stream(device); + text_stream.setCodec(QTextCodec::codecForUtfText(device->peek(1024), QTextCodec::codecForName("UTF-8"))); + + QString dir_path = dir.absolutePath(); + // read the first line already + QString line = text_stream.readLine(); + + QList entries; + int files = 0; + + // -- whole file + while (!text_stream.atEnd()) { + + QString album_artist; + QString album; + QString album_composer; + QString file; + QString file_type; + QString genre; + QString date; + QString disc; + + // -- FILE section + do { + QStringList splitted = SplitCueLine(line); + + // uninteresting or incorrect line + if (splitted.size() < 2) { + continue; + } + + QString line_name = splitted[0].toLower(); + QString line_value = splitted[1]; + + // PERFORMER + if (line_name == kPerformer) { + + album_artist = line_value; + + // TITLE + } + else if (line_name == kTitle) { + + album = line_value; + + // SONGWRITER + } + else if (line_name == kSongWriter) { + + album_composer = line_value; + + // FILE + } + else if (line_name == kFile) { + + file = QDir::isAbsolutePath(line_value) ? line_value : dir.absoluteFilePath(line_value); + + if (splitted.size() > 2) { + file_type = splitted[2]; + } + + // REM + } + else if (line_name == kRem) { + if (splitted.size() < 3) { + break; + } + + // REM GENRE + if (line_value.toLower() == kGenre) { + genre = splitted[2]; + + // REM DATE + } else if (line_value.toLower() == kDate) { + date = splitted[2]; + + // REM DISC + } else if (line_value.toLower() == kDisc) { + disc = splitted[2]; + } + + // end of the header -> go into the track mode + } else if (line_name == kTrack) { + + files++; + break; + } + + // just ignore the rest of possible field types for now... + } while (!(line = text_stream.readLine()).isNull()); + + if(line.isNull()) { + qLog(Warning) << "the .cue file from " << dir_path << " defines no tracks!"; + return ret; + } + + // if this is a data file, all of it's tracks will be ignored + bool valid_file = file_type.compare("BINARY", Qt::CaseInsensitive) && file_type.compare("MOTOROLA", Qt::CaseInsensitive); + + QString track_type; + QString index; + QString artist; + QString composer; + QString title; + + // TRACK section + do { + QStringList splitted = SplitCueLine(line); + + // uninteresting or incorrect line + if (splitted.size() < 2) { + continue; + } + + QString line_name = splitted[0].toLower(); + QString line_value = splitted[1]; + QString line_additional = splitted.size() > 2 ? splitted[2].toLower() : ""; + + if (line_name == kTrack) { + + // 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(valid_file && !index.isEmpty() && (track_type.isEmpty() || track_type == kAudioTrackType)) { + entries.append(CueEntry(file, index, title, artist, album_artist, album, composer, album_composer, genre, date, disc)); + } + + // clear the state + track_type = index = artist = title = ""; + + if (!line_additional.isEmpty()) { + track_type = line_additional; + } + + } + else if (line_name == kIndex) { + + // we need the index's position field + if (!line_additional.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 (line_value == "01" || index.isEmpty()) { + + index = line_additional; + } + } + } + else if (line_name == kPerformer) { + artist = line_value; + } + else if (line_name == kTitle) { + title = line_value; + } + else if (line_name == kSongWriter) { + composer = line_value; + // end of track's for the current file -> parse next one + } + else if (line_name == kFile) { + + break; + } + + // just ignore the rest of possible field types for now... + } while (!(line = text_stream.readLine()).isNull()); + + // we didn't add the last song yet... + if(valid_file && !index.isEmpty() && (track_type.isEmpty() || track_type == kAudioTrackType)) { + entries.append(CueEntry(file, index, title, artist, album_artist, album, composer, album_composer, genre, date, disc)); + } + } + + QDateTime cue_mtime = QFileInfo(playlist_path).lastModified(); + + // finalize parsing songs + for (int i = 0; i < entries.length(); i++) { + CueEntry entry = entries.at(i); + + Song song = LoadSong(entry.file, IndexToMarker(entry.index), dir); + + // cue song has mtime equal to qMax(media_file_mtime, cue_sheet_mtime) + if (cue_mtime.isValid()) { + song.set_mtime(qMax(cue_mtime.toTime_t(), song.mtime())); + } + song.set_cue_path(playlist_path); + + // overwrite the stuff, we may have read from the file or collection, using + // the current .cue metadata + + // set track number only in single-file mode + if (files == 1) { + song.set_track(i + 1); + } + + // 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; + } + } + + ret << song; + } + + return ret; +} + +// This and the kFileLineRegExp 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. +QStringList CueParser::SplitCueLine(const QString &line) const { + + QRegExp line_regexp(kFileLineRegExp); + if (!line_regexp.exactMatch(line.trimmed())) { + return QStringList(); + } + + // let's remove the empty entries while we're at it + return line_regexp.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. +bool CueParser::UpdateSong(const CueEntry &entry, const QString &next_index, Song *song) const { + + qint64 beginning = IndexToMarker(entry.index); + qint64 end = IndexToMarker(next_index); + + // incorrect indices (we won't be able to calculate beginning or end) + if (beginning == -1 || end == -1) { + return false; + } + + // believe the CUE: Init() forces validity + song->Init(entry.title, entry.PrettyArtist(), entry.album, beginning, end); + song->set_albumartist(entry.album_artist); + song->set_composer(entry.PrettyComposer()); + song->set_genre(entry.genre); + song->set_year(entry.date.toInt()); + song->set_disc(entry.disc.toInt()); + + 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. +bool CueParser::UpdateLastSong(const CueEntry &entry, Song *song) const { + + qint64 beginning = IndexToMarker(entry.index); + + // incorrect index (we won't be able to calculate beginning) + if (beginning == -1) { + return false; + } + + // believe the CUE and force validity (like UpdateSong() does) + song->set_valid(true); + + song->set_title(entry.title); + song->set_artist(entry.PrettyArtist()); + song->set_album(entry.album); + song->set_albumartist(entry.album_artist); + song->set_genre(entry.genre); + song->set_year(entry.date.toInt()); + song->set_composer(entry.PrettyComposer()); + song->set_disc(entry.disc.toInt()); + + // we don't do anything with the end here because it's already set to + // the end of the media file (if it exists) + song->set_beginning_nanosec(beginning); + + return true; + +} + +qint64 CueParser::IndexToMarker(const QString &index) const { + + QRegExp index_regexp(kIndexRegExp); + if (!index_regexp.exactMatch(index)) { + return -1; + } + + QStringList splitted = index_regexp.capturedTexts().mid(1, -1); + qlonglong frames = splitted.at(0).toLongLong() * 60 * 75 + splitted.at(1).toLongLong() * 75 + splitted.at(2).toLongLong(); + return (frames * kNsecPerSec) / 75; + +} + +void CueParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, Playlist::Path path_type) const { + // TODO +} + +// Looks for a track starting with one of the .cue's keywords. +bool CueParser::TryMagic(const QByteArray &data) const { + + QStringList splitted = QString::fromUtf8(data.constData()).split('\n'); + + for (int i = 0; i < splitted.length(); i++) { + QString line = splitted.at(i).trimmed(); + if (line.startsWith(kPerformer, Qt::CaseInsensitive) || + line.startsWith(kTitle, Qt::CaseInsensitive) || + line.startsWith(kFile, Qt::CaseInsensitive) || + line.startsWith(kTrack, Qt::CaseInsensitive)) { + return true; + } + } + + return false; + +} diff --git a/src/playlistparsers/cueparser.h b/src/playlistparsers/cueparser.h new file mode 100644 index 00000000..1c3f6d69 --- /dev/null +++ b/src/playlistparsers/cueparser.h @@ -0,0 +1,107 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef CUEPARSER_H +#define CUEPARSER_H + +#include "config.h" + +#include "parserbase.h" + +#include + +// 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. +class CueParser : public ParserBase { + Q_OBJECT + + public: + static const char *kFileLineRegExp; + static const char *kIndexRegExp; + + static const char *kPerformer; + static const char *kTitle; + static const char *kSongWriter; + static const char *kFile; + static const char *kTrack; + static const char *kIndex; + static const char *kAudioTrackType; + static const char *kRem; + static const char *kGenre; + static const char *kDate; + static const char *kDisc; + + CueParser(CollectionBackendInterface *library, QObject *parent = nullptr); + + QString name() const { return "CUE"; } + QStringList file_extensions() const { return QStringList() << "cue"; } + QString mime_type() const { return "application/x-cue"; } + + bool TryMagic(const QByteArray &data) const; + + SongList Load(QIODevice *device, const QString &playlist_path = "", const QDir &dir = QDir()) const; + void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), Playlist::Path path_type = Playlist::Path_Automatic) const; + + private: + // A single TRACK entry in .cue file. + struct CueEntry { + QString file; + + QString index; + + QString title; + QString artist; + QString album_artist; + QString album; + + QString composer; + QString album_composer; + + QString genre; + QString date; + QString disc; + + QString PrettyArtist() const { return artist.isEmpty() ? album_artist : artist; } + QString PrettyComposer() const { return composer.isEmpty() ? album_composer : composer; } + + CueEntry(QString &file, QString &index, QString &title, QString &artist, QString &album_artist, QString &album, QString &composer, QString &album_composer, QString &genre, QString &date, QString &disc) { + this->file = file; + this->index = index; + this->title = title; + this->artist = artist; + this->album_artist = album_artist; + this->album = album; + this->composer = composer; + this->album_composer = album_composer; + this->genre = genre; + this->date = date; + this->disc = disc; + } + }; + + bool UpdateSong(const CueEntry &entry, const QString &next_index, Song *song) const; + bool UpdateLastSong(const CueEntry &entry, Song *song) const; + + QStringList SplitCueLine(const QString &line) const; + qint64 IndexToMarker(const QString &index) const; +}; + +#endif // CUEPARSER_H diff --git a/src/playlistparsers/m3uparser.cpp b/src/playlistparsers/m3uparser.cpp new file mode 100644 index 00000000..26dc9547 --- /dev/null +++ b/src/playlistparsers/m3uparser.cpp @@ -0,0 +1,141 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "m3uparser.h" +#include "core/logging.h" +#include "core/timeconstants.h" + +#include "playlist/playlist.h" + +#include +#include + +M3UParser::M3UParser(CollectionBackendInterface *collection, QObject *parent) + : ParserBase(collection, parent) {} + +SongList M3UParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir) const { + + SongList ret; + + M3UType type = STANDARD; + Metadata current_metadata; + + QString data = QString::fromUtf8(device->readAll()); + data.replace('\r', '\n'); + data.replace("\n\n", "\n"); + QByteArray bytes = data.toUtf8(); + QBuffer buffer(&bytes); + buffer.open(QIODevice::ReadOnly); + + QString line = QString::fromUtf8(buffer.readLine()).trimmed(); + if (line.startsWith("#EXTM3U")) { + // This is in extended M3U format. + type = EXTENDED; + line = QString::fromUtf8(buffer.readLine()).trimmed(); + } + + forever { + if (line.startsWith('#')) { + // Extended info or comment. + if (type == EXTENDED && line.startsWith("#EXT")) { + if (!ParseMetadata(line, ¤t_metadata)) { + qLog(Warning) << "Failed to parse metadata: " << line; + } + } + } + else if (!line.isEmpty()) { + Song song = LoadSong(line, 0, dir); + if (!current_metadata.title.isEmpty()) { + song.set_title(current_metadata.title); + } + if (!current_metadata.artist.isEmpty()) { + song.set_artist(current_metadata.artist); + } + if (current_metadata.length > 0) { + song.set_length_nanosec(current_metadata.length); + } + ret << song; + + current_metadata = Metadata(); + } + if (buffer.atEnd()) { + break; + } + line = QString::fromUtf8(buffer.readLine()).trimmed(); + } + + return ret; + +} + +bool M3UParser::ParseMetadata(const QString &line, M3UParser::Metadata *metadata) const { + + // Extended info, eg. + // #EXTINF:123,Sample Artist - Sample title + QString info = line.section(':', 1); + QString l = info.section(',', 0, 0); + bool ok = false; + int length = l.toInt(&ok); + if (!ok) { + return false; + } + metadata->length = length * kNsecPerSec; + + QString track_info = info.section(',', 1); + QStringList list = track_info.split(" - "); + if (list.size() <= 1) { + metadata->title = track_info; + return true; + } + metadata->artist = list[0].trimmed(); + metadata->title = list[1].trimmed(); + return true; +} + +void M3UParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, Playlist::Path path_type) const { + + device->write("#EXTM3U\n"); + + QSettings s; + s.beginGroup(Playlist::kSettingsGroup); + bool writeMetadata = s.value(Playlist::kWriteMetadata, true).toBool(); + s.endGroup(); + + for (const Song &song : songs) { + if (song.url().isEmpty()) { + continue; + } + if (writeMetadata) { + QString meta = QString("#EXTINF:%1,%2 - %3\n") + .arg(song.length_nanosec() / kNsecPerSec) + .arg(song.artist()) + .arg(song.title()); + device->write(meta.toUtf8()); + } + device->write(URLOrFilename(song.url(), dir, path_type).toUtf8()); + device->write("\n"); + } +} + +bool M3UParser::TryMagic(const QByteArray &data) const { + return data.contains("#EXTM3U") || data.contains("#EXTINF"); +} diff --git a/src/playlistparsers/m3uparser.h b/src/playlistparsers/m3uparser.h new file mode 100644 index 00000000..73aa1f77 --- /dev/null +++ b/src/playlistparsers/m3uparser.h @@ -0,0 +1,72 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef M3UPARSER_H +#define M3UPARSER_H + +#include "config.h" + +#include + +#include "gtest/gtest_prod.h" + +#include "parserbase.h" + +class M3UParser : public ParserBase { + Q_OBJECT + + public: + M3UParser(CollectionBackendInterface *collection, QObject *parent = nullptr); + + QString name() const { return "M3U"; } + QStringList file_extensions() const { return QStringList() << "m3u" << "m3u8"; } + QString mime_type() const { return "text/uri-list"; } + + bool TryMagic(const QByteArray &data) const; + + SongList Load(QIODevice *device, const QString &playlist_path = "", const QDir &dir = QDir()) const; + void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), Playlist::Path path_type = Playlist::Path_Automatic) const; + + private: + enum M3UType { + STANDARD = 0, + EXTENDED, // Includes extended info (track, artist, etc.) + LINK, // Points to a directory. + }; + + struct Metadata { + Metadata() : length(-1) {} + QString artist; + QString title; + qint64 length; + }; + + bool ParseMetadata(const QString &line, Metadata *metadata) const; + + FRIEND_TEST(M3UParserTest, ParsesMetadata); + FRIEND_TEST(M3UParserTest, ParsesTrackLocation); + FRIEND_TEST(M3UParserTest, ParsesTrackLocationRelative); + FRIEND_TEST(M3UParserTest, ParsesTrackLocationHttp); +#ifdef Q_OS_WIN32 + FRIEND_TEST(M3UParserTest, ParsesTrackLocationAbsoluteWindows); +#endif // Q_OS_WIN32 +}; + +#endif // M3UPARSER_H diff --git a/src/playlistparsers/parserbase.cpp b/src/playlistparsers/parserbase.cpp new file mode 100644 index 00000000..32409925 --- /dev/null +++ b/src/playlistparsers/parserbase.cpp @@ -0,0 +1,112 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "parserbase.h" +#include "core/tagreaderclient.h" +#include "collection/collectionbackend.h" +#include "collection/collectionquery.h" +#include "collection/sqlrow.h" +#include "playlist/playlist.h" + +#include + +ParserBase::ParserBase(CollectionBackendInterface *collection, QObject *parent) + : QObject(parent), collection_(collection) {} + +void ParserBase::LoadSong(const QString &filename_or_url, qint64 beginning, const QDir &dir, Song *song) const { + + if (filename_or_url.isEmpty()) { + return; + } + + QString filename = filename_or_url; + + if (filename_or_url.contains(QRegExp("^[a-z]{2,}:"))) { + QUrl url(filename_or_url); + if (url.scheme() == "file") { + filename = url.toLocalFile(); + } + //else { + // song->set_url(QUrl::fromUserInput(filename_or_url)); + // song->set_filetype(Song::Type_Stream); + // song->set_valid(true); + // return; + // } + } + + // Strawberry always wants / separators internally. Using + // QDir::fromNativeSeparators() only works on the same platform the playlist + // was created on/for, using replace() lets playlists work on any platform. + filename = filename.replace('\\', '/'); + + // Make the path absolute + if (!QDir::isAbsolutePath(filename)) { + filename = dir.absoluteFilePath(filename); + } + + // Use the canonical path + if (QFile::exists(filename)) { + filename = QFileInfo(filename).canonicalFilePath(); + } + + const QUrl url = QUrl::fromLocalFile(filename); + + // Search in the collection + Song collection_song; + if (collection_) { + collection_song = collection_->GetSongByUrl(url, beginning); + } + + // If it was found in the collection then use it, otherwise load metadata from + // disk. + if (collection_song.is_valid()) { + *song = collection_song; + } + else { + TagReaderClient::Instance()->ReadFileBlocking(filename, song); + } + +} + +Song ParserBase::LoadSong(const QString &filename_or_url, qint64 beginning, const QDir &dir) const { + + Song song; + LoadSong(filename_or_url, beginning, dir, &song); + return song; + +} + +QString ParserBase::URLOrFilename(const QUrl &url, const QDir &dir, Playlist::Path path_type) const { + + if (url.scheme() != "file") return url.toString(); + + const QString filename = url.toLocalFile(); + + if (path_type != Playlist::Path_Absolute && QDir::isAbsolutePath(filename)) { + const QString relative = dir.relativeFilePath(filename); + + if (!relative.startsWith("../") || path_type == Playlist::Path_Relative) + return relative; + } + return filename; + +} diff --git a/src/playlistparsers/parserbase.h b/src/playlistparsers/parserbase.h new file mode 100644 index 00000000..d50c7f42 --- /dev/null +++ b/src/playlistparsers/parserbase.h @@ -0,0 +1,76 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PARSERBASE_H +#define PARSERBASE_H + +#include "config.h" + +#include +#include + +#include "core/song.h" +#include "playlist/playlist.h" + +class CollectionBackendInterface; + +class ParserBase : public QObject { + Q_OBJECT + + public: + ParserBase(CollectionBackendInterface *collection, QObject *parent = nullptr); + + virtual QString name() const = 0; + virtual QStringList file_extensions() const = 0; + virtual QString mime_type() const { return QString(); } + + virtual bool TryMagic(const QByteArray &data) const = 0; + + // Loads all songs from playlist found at path 'playlist_path' in directory 'dir'. + // The 'device' argument is an opened and ready to read from represantation of + // this playlist. + // This method might not return all of the songs found in the playlist. Any playlist + // parser may decide to leave out some entries if it finds them incomplete or invalid. + // This means that the final resulting SongList should be considered valid (at least + // from the parser's point of view). + virtual SongList Load(QIODevice *device, const QString &playlist_path = "", const QDir &dir = QDir()) const = 0; + virtual void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), Playlist::Path path_type = Playlist::Path_Automatic) const = 0; + +protected: + // Loads a song. If filename_or_url is a URL (with a scheme other than + // "file") then it is set on the song and the song marked as a stream. + // If it is a filename or a file:// URL then it is made absolute and canonical + // and set as a file:// url on the song. Also sets the song's metadata by + // searching in the Collection, or loading from the file as a fallback. + // This function should always be used when loading a playlist. + Song LoadSong(const QString &filename_or_url, qint64 beginning, const QDir &dir) const; + void LoadSong(const QString &filename_or_url, qint64 beginning, const QDir &dir, Song *song) const; + + // If the URL is a file:// URL then returns its path, absolute or relative to + // the directory depending on the path_type option. + // Otherwise returns the URL as is. + // This function should always be used when saving a playlist. + QString URLOrFilename(const QUrl &url, const QDir &dir, Playlist::Path path_type) const; + +private: + CollectionBackendInterface *collection_; +}; + +#endif // PARSERBASE_H diff --git a/src/playlistparsers/playlistparser.cpp b/src/playlistparsers/playlistparser.cpp new file mode 100644 index 00000000..eb886193 --- /dev/null +++ b/src/playlistparsers/playlistparser.cpp @@ -0,0 +1,190 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "asxparser.h" +#include "asxiniparser.h" +#include "cueparser.h" +#include "m3uparser.h" +#include "playlistparser.h" +#include "plsparser.h" +#include "wplparser.h" +#include "xspfparser.h" +#include "core/logging.h" + +#include + +const int PlaylistParser::kMagicSize = 512; + +PlaylistParser::PlaylistParser(CollectionBackendInterface *collection, QObject *parent) + : QObject(parent) +{ + + default_parser_ = new XSPFParser(collection, this); + parsers_ << new M3UParser(collection, this); + parsers_ << default_parser_; + parsers_ << new PLSParser(collection, this); + parsers_ << new ASXParser(collection, this); + parsers_ << new AsxIniParser(collection, this); + parsers_ << new CueParser(collection, this); + parsers_ << new WplParser(collection, this); + +} + +QStringList PlaylistParser::file_extensions() const { + + QStringList ret; + + for (ParserBase *parser : parsers_) { + ret << parser->file_extensions(); + } + + qStableSort(ret); + return ret; + +} + +QStringList PlaylistParser::mime_types() const { + + QStringList ret; + + for (ParserBase *parser : parsers_) { + if (!parser->mime_type().isEmpty()) ret << parser->mime_type(); + } + + qStableSort(ret); + return ret; + +} + +QString PlaylistParser::filters() const { + + QStringList filters; + QStringList all_extensions; + for (ParserBase *parser : parsers_) { + filters << FilterForParser(parser, &all_extensions); + } + + filters.prepend(tr("All playlists (%1)").arg(all_extensions.join(" "))); + + return filters.join(";;"); + +} + +QString PlaylistParser::FilterForParser(const ParserBase *parser, QStringList *all_extensions) const { + + QStringList extensions; + for (const QString &extension : parser->file_extensions()) + extensions << "*." + extension; + + if (all_extensions) *all_extensions << extensions; + + return tr("%1 playlists (%2)").arg(parser->name(), extensions.join(" ")); + +} + +QString PlaylistParser::default_extension() const { + return default_parser_->file_extensions()[0]; +} + +QString PlaylistParser::default_filter() const { + return FilterForParser(default_parser_); +} + +ParserBase *PlaylistParser::ParserForExtension(const QString &suffix) const { + + for (ParserBase *p : parsers_) { + if (p->file_extensions().contains(suffix)) return p; + } + return nullptr; + +} + +ParserBase *PlaylistParser::ParserForMimeType(const QString &mime_type) const { + + for (ParserBase *p : parsers_) { + if (!p->mime_type().isEmpty() && + (QString::compare(p->mime_type(), mime_type, Qt::CaseInsensitive) == 0)) + return p; + } + return nullptr; + +} + +ParserBase *PlaylistParser::ParserForMagic(const QByteArray &data, const QString &mime_type) const { + + for (ParserBase *p : parsers_) { + if ((!mime_type.isEmpty() && mime_type == p->mime_type()) || p->TryMagic(data)) + return p; + } + return nullptr; + +} + +SongList PlaylistParser::LoadFromFile(const QString &filename) const { + + QFileInfo info(filename); + + // Find a parser that supports this file extension + ParserBase *parser = ParserForExtension(info.suffix()); + if (!parser) { + qLog(Warning) << "Unknown filetype:" << filename; + return SongList(); + } + + // Open the file + QFile file(filename); + file.open(QIODevice::ReadOnly); + + return parser->Load(&file, filename, info.absolutePath()); + +} + +SongList PlaylistParser::LoadFromDevice(QIODevice *device, const QString &path_hint, const QDir &dir_hint) const { + + // Find a parser that supports this data + ParserBase *parser = ParserForMagic(device->peek(kMagicSize)); + if (!parser) { + return SongList(); + } + + return parser->Load(device, path_hint, dir_hint); + +} + +void PlaylistParser::Save(const SongList &songs, const QString &filename, Playlist::Path path_type) const { + + QFileInfo info(filename); + + // Find a parser that supports this file extension + ParserBase *parser = ParserForExtension(info.suffix()); + if (!parser) { + qLog(Warning) << "Unknown filetype:" << filename; + return; + } + + // Open the file + QFile file(filename); + file.open(QIODevice::WriteOnly); + + return parser->Save(songs, &file, info.absolutePath(), path_type); + +} diff --git a/src/playlistparsers/playlistparser.h b/src/playlistparsers/playlistparser.h new file mode 100644 index 00000000..dd706ee2 --- /dev/null +++ b/src/playlistparsers/playlistparser.h @@ -0,0 +1,67 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTPARSER_H +#define PLAYLISTPARSER_H + +#include "config.h" + +#include +#include + +#include "core/song.h" +#include "playlist/playlist.h" + +class ParserBase; +class CollectionBackendInterface; + +class PlaylistParser : public QObject { + Q_OBJECT + + public: + PlaylistParser(CollectionBackendInterface *collection, QObject *parent = nullptr); + + static const int kMagicSize; + + QStringList file_extensions() const; + QString filters() const; + + QStringList mime_types() const; + + QString default_extension() const; + QString default_filter() const; + + ParserBase *ParserForMagic(const QByteArray &data, const QString &mime_type = QString()) const; + ParserBase *ParserForExtension(const QString &suffix) const; + ParserBase *ParserForMimeType(const QString &mime) const; + + SongList LoadFromFile(const QString &filename) const; + SongList LoadFromDevice(QIODevice *device, const QString &path_hint = QString(), const QDir &dir_hint = QDir()) const; + void Save(const SongList &songs, const QString &filename, Playlist::Path) const; + +private: + QString FilterForParser(const ParserBase *parser, QStringList *all_extensions = nullptr) const; + +private: + QList parsers_; + ParserBase *default_parser_; +}; + +#endif // PLAYLISTPARSER_H diff --git a/src/playlistparsers/plsparser.cpp b/src/playlistparsers/plsparser.cpp new file mode 100644 index 00000000..59a9274f --- /dev/null +++ b/src/playlistparsers/plsparser.cpp @@ -0,0 +1,91 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "plsparser.h" +#include "core/logging.h" +#include "core/timeconstants.h" + +#include +#include + +PLSParser::PLSParser(CollectionBackendInterface *collection, QObject *parent) + : ParserBase(collection, parent) {} + +SongList PLSParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir) const { + + QMap songs; + QRegExp n_re("\\d+$"); + + while (!device->atEnd()) { + QString line = QString::fromUtf8(device->readLine()).trimmed(); + int equals = line.indexOf('='); + QString key = line.left(equals).toLower(); + QString value = line.mid(equals + 1); + + n_re.indexIn(key); + int n = n_re.cap(0).toInt(); + + if (key.startsWith("file")) { + Song song = LoadSong(value, 0, dir); + + // Use the title and length we've already loaded if any + if (!songs[n].title().isEmpty()) song.set_title(songs[n].title()); + if (songs[n].length_nanosec() != -1) + song.set_length_nanosec(songs[n].length_nanosec()); + + songs[n] = song; + } + else if (key.startsWith("title")) { + songs[n].set_title(value); + } + else if (key.startsWith("length")) { + qint64 seconds = value.toLongLong(); + if (seconds > 0) { + songs[n].set_length_nanosec(seconds * kNsecPerSec); + } + } + } + + return songs.values(); + +} + +void PLSParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, Playlist::Path path_type) const { + + QTextStream s(device); + s << "[playlist]" << endl; + s << "Version=2" << endl; + s << "NumberOfEntries=" << songs.count() << endl; + + int n = 1; + for (const Song &song : songs) { + s << "File" << n << "=" << URLOrFilename(song.url(), dir, path_type) << endl; + s << "Title" << n << "=" << song.title() << endl; + s << "Length" << n << "=" << song.length_nanosec() / kNsecPerSec << endl; + ++n; + } + +} + +bool PLSParser::TryMagic(const QByteArray &data) const { + return data.toLower().contains("[playlist]"); +} diff --git a/src/playlistparsers/plsparser.h b/src/playlistparsers/plsparser.h new file mode 100644 index 00000000..51970ff9 --- /dev/null +++ b/src/playlistparsers/plsparser.h @@ -0,0 +1,44 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLSPARSER_H +#define PLSPARSER_H + +#include "config.h" + +#include "parserbase.h" + +class PLSParser : public ParserBase { + Q_OBJECT + + public: + PLSParser(CollectionBackendInterface *collection, QObject *parent = nullptr); + + QString name() const { return "PLS"; } + QStringList file_extensions() const { return QStringList() << "pls"; } + QString mime_type() const { return "audio/x-scpls"; } + + bool TryMagic(const QByteArray &data) const; + + SongList Load(QIODevice *device, const QString &playlist_path = "", const QDir &dir = QDir()) const; + void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), Playlist::Path path_type = Playlist::Path_Automatic) const; +}; + +#endif // PLSPARSER_H diff --git a/src/playlistparsers/wplparser.cpp b/src/playlistparsers/wplparser.cpp new file mode 100644 index 00000000..1a58bdb7 --- /dev/null +++ b/src/playlistparsers/wplparser.cpp @@ -0,0 +1,120 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2013, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "wplparser.h" +#include "core/utilities.h" +#include "version.h" + +#include + +WplParser::WplParser(CollectionBackendInterface *collection, QObject *parent) + : XMLParser(collection, parent) {} + +bool WplParser::TryMagic(const QByteArray &data) const { + return data.contains(""); +} + +SongList WplParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir) const { + + SongList ret; + + QXmlStreamReader reader(device); + if (!Utilities::ParseUntilElement(&reader, "smil") || !Utilities::ParseUntilElement(&reader, "body")) { + return ret; + } + + while (!reader.atEnd() && Utilities::ParseUntilElement(&reader, "seq")) { + ParseSeq(dir, &reader, &ret); + } + return ret; + +} + +void WplParser::ParseSeq(const QDir &dir, QXmlStreamReader *reader, SongList *songs) const { + + while (!reader->atEnd()) { + QXmlStreamReader::TokenType type = reader->readNext(); + switch (type) { + case QXmlStreamReader::StartElement: { + QStringRef name = reader->name(); + if (name == "media") { + QStringRef src = reader->attributes().value("src"); + if (!src.isEmpty()) { + Song song = LoadSong(src.toString(), 0, dir); + if (song.is_valid()) { + songs->append(song); + } + } + } else { + Utilities::ConsumeCurrentElement(reader); + } + break; + } + case QXmlStreamReader::EndElement: { + if (reader->name() == "seq") { + return; + } + break; + } + default: + break; + } + } + +} + +void WplParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, Playlist::Path path_type) const { + + QXmlStreamWriter writer(device); + writer.setAutoFormatting(true); + writer.setAutoFormattingIndent(2); + writer.writeProcessingInstruction("wpl", "version=\"1.0\""); + + StreamElement smil("smil", &writer); + + { + StreamElement head("head", &writer); + WriteMeta("Generator", "Strawberry -- " STRAWBERRY_VERSION_DISPLAY, &writer); + WriteMeta("ItemCount", QString::number(songs.count()), &writer); + } + + { + StreamElement body("body", &writer); + { + StreamElement seq("seq", &writer); + for (const Song &song : songs) { + writer.writeStartElement("media"); + writer.writeAttribute("src", URLOrFilename(song.url(), dir, path_type)); + writer.writeEndElement(); + } + } + } +} + +void WplParser::WriteMeta(const QString &name, const QString &content, QXmlStreamWriter *writer) const { + + writer->writeStartElement("meta"); + writer->writeAttribute("name", name); + writer->writeAttribute("content", content); + writer->writeEndElement(); + +} diff --git a/src/playlistparsers/wplparser.h b/src/playlistparsers/wplparser.h new file mode 100644 index 00000000..02daf832 --- /dev/null +++ b/src/playlistparsers/wplparser.h @@ -0,0 +1,46 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2013, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef WPLPARSER_H +#define WPLPARSER_H + +#include "config.h" + +#include "xmlparser.h" + +class WplParser : public XMLParser { + public: + WplParser(CollectionBackendInterface *collection, QObject *parent = nullptr); + + QString name() const { return "WPL"; } + QStringList file_extensions() const { return QStringList() << "wpl"; } + QString mime_type() const { return "application/vnd.ms-wpl"; } + + bool TryMagic(const QByteArray &data) const; + + SongList Load(QIODevice *device, const QString &playlist_path, const QDir &dir) const; + void Save(const SongList &songs, QIODevice *device, const QDir &dir, Playlist::Path path_type = Playlist::Path_Automatic) const; + +private: + void ParseSeq(const QDir &dir, QXmlStreamReader *reader, SongList *songs) const; + void WriteMeta(const QString &name, const QString &content, QXmlStreamWriter *writer) const; +}; + +#endif // WPLPARSER_H diff --git a/src/playlistparsers/xmlparser.cpp b/src/playlistparsers/xmlparser.cpp new file mode 100644 index 00000000..5c0348d6 --- /dev/null +++ b/src/playlistparsers/xmlparser.cpp @@ -0,0 +1,33 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "xmlparser.h" + +#include +#include +#include +#include +#include +#include + +XMLParser::XMLParser(CollectionBackendInterface *collection, QObject *parent) + : ParserBase(collection, parent) {} diff --git a/src/playlistparsers/xmlparser.h b/src/playlistparsers/xmlparser.h new file mode 100644 index 00000000..ffe38fad --- /dev/null +++ b/src/playlistparsers/xmlparser.h @@ -0,0 +1,52 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef XMLPARSER_H +#define XMLPARSER_H + +#include "config.h" + +#include "parserbase.h" + +#include +#include + +class QDomDocument; +class QDomNode; + +class XMLParser : public ParserBase { + protected: + XMLParser(CollectionBackendInterface *collection, QObject *parent); + + class StreamElement { + public: + StreamElement(const QString& name, QXmlStreamWriter *stream) : stream_(stream) { + stream->writeStartElement(name); + } + + ~StreamElement() { stream_->writeEndElement(); } + + private: + QXmlStreamWriter *stream_; + Q_DISABLE_COPY(StreamElement); + }; +}; + +#endif diff --git a/src/playlistparsers/xspfparser.cpp b/src/playlistparsers/xspfparser.cpp new file mode 100644 index 00000000..971b2cbd --- /dev/null +++ b/src/playlistparsers/xspfparser.cpp @@ -0,0 +1,198 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "xspfparser.h" +#include "core/timeconstants.h" +#include "core/utilities.h" + +#include "playlist/playlist.h" + +#include +#include +#include +#include +#include +#include + +XSPFParser::XSPFParser(CollectionBackendInterface *collection, QObject *parent) + : XMLParser(collection, parent) {} + +SongList XSPFParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir) const { + SongList ret; + + QXmlStreamReader reader(device); + if (!Utilities::ParseUntilElement(&reader, "playlist") || !Utilities::ParseUntilElement(&reader, "trackList")) { + return ret; + } + + while (!reader.atEnd() && Utilities::ParseUntilElement(&reader, "track")) { + Song song = ParseTrack(&reader, dir); + if (song.is_valid()) { + ret << song; + } + } + return ret; + +} + +Song XSPFParser::ParseTrack(QXmlStreamReader *reader, const QDir &dir) const { + + QString title, artist, album, location; + qint64 nanosec = -1; + int track_num = -1; + + while (!reader->atEnd()) { + QXmlStreamReader::TokenType type = reader->readNext(); + switch (type) { + case QXmlStreamReader::StartElement: { + QStringRef name = reader->name(); + if (name == "location") { + location = reader->readElementText(); + } + else if (name == "title") { + title = reader->readElementText(); + } + else if (name == "creator") { + artist = reader->readElementText(); + } + else if (name == "album") { + album = reader->readElementText(); + } + else if (name == "duration") { // in milliseconds. + const QString duration = reader->readElementText(); + bool ok = false; + nanosec = duration.toInt(&ok) * kNsecPerMsec; + if (!ok) { + nanosec = -1; + } + } + else if (name == "trackNum") { + const QString track_num_str = reader->readElementText(); + bool ok = false; + track_num = track_num_str.toInt(&ok); + if (!ok || track_num < 1) { + track_num = -1; + } + } + else if (name == "image") { + // TODO: Fetch album covers. + } + else if (name == "info") { + // TODO: Do something with extra info? + } + break; + } + case QXmlStreamReader::EndElement: { + if (reader->name() == "track") { + goto return_song; + } + } + default: + break; + } + } + +return_song: + Song song = LoadSong(location, 0, dir); + + // Override metadata with what was in the playlist + song.set_title(title); + song.set_artist(artist); + song.set_album(album); + song.set_length_nanosec(nanosec); + song.set_track(track_num); + return song; + +} + +void XSPFParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, Playlist::Path path_type) const { + + QFileInfo file; + QXmlStreamWriter writer(device); + writer.setAutoFormatting(true); + writer.setAutoFormattingIndent(2); + writer.writeStartDocument(); + StreamElement playlist("playlist", &writer); + writer.writeAttribute("version", "1"); + writer.writeDefaultNamespace("http://xspf.org/ns/0/"); + + QSettings s; + s.beginGroup(Playlist::kSettingsGroup); + bool writeMetadata = s.value(Playlist::kWriteMetadata, true).toBool(); + s.endGroup(); + + StreamElement tracklist("trackList", &writer); + for (const Song &song : songs) { + QString filename_or_url = URLOrFilename(song.url(), dir, path_type).toUtf8(); + + StreamElement track("track", &writer); + writer.writeTextElement("location", filename_or_url); + + if (writeMetadata) { + writer.writeTextElement("title", song.title()); + if (!song.artist().isEmpty()) { + writer.writeTextElement("creator", song.artist()); + } + if (!song.album().isEmpty()) { + writer.writeTextElement("album", song.album()); + } + if (song.length_nanosec() != -1) { + writer.writeTextElement( + "duration", QString::number(song.length_nanosec() / kNsecPerMsec)); + } + if (song.track() > 0) { + writer.writeTextElement("trackNum", QString::number(song.track())); + } + + QString art = song.art_manual().isEmpty() ? song.art_automatic() + : song.art_manual(); + // Ignore images that are in our resource bundle. + if (!art.startsWith(":") && !art.isEmpty()) { + QString art_filename; + if (!art.contains("://")) { + art_filename = art; + } else if (QUrl(art).scheme() == "file") { + art_filename = QUrl(art).toLocalFile(); + } + + if (!art_filename.isEmpty() && !(art_filename == "(embedded)")) { + // Make this filename relative to the directory we're saving the + // playlist. + QUrl url = QUrl(art_filename); + url.setScheme("file"); // Need to explicitly set this. + art_filename = URLOrFilename(url, dir, path_type).toUtf8(); + } else { + // Just use whatever URL was in the Song. + art_filename = art; + } + + writer.writeTextElement("image", art_filename); + } + } + } + writer.writeEndDocument(); + +} + +bool XSPFParser::TryMagic(const QByteArray &data) const { + return data.contains(" + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef XSPFPARSER_H +#define XSPFPARSER_H + +#include "config.h" + +#include "xmlparser.h" + +#include + +class QDomDocument; +class QDomNode; + +class XSPFParser : public XMLParser { + Q_OBJECT + + public: + XSPFParser(CollectionBackendInterface *collection, QObject *parent = nullptr); + + QString name() const { return "XSPF"; } + QStringList file_extensions() const { return QStringList() << "xspf"; } + + bool TryMagic(const QByteArray &data) const; + + SongList Load(QIODevice *device, const QString &playlist_path = "", const QDir &dir = QDir()) const; + void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), Playlist::Path path_type = Playlist::Path_Automatic) const; + + private: + Song ParseTrack(QXmlStreamReader *reader, const QDir &dir) const; +}; + +#endif diff --git a/src/settings/appearancesettingspage.cpp b/src/settings/appearancesettingspage.cpp new file mode 100644 index 00000000..2e59f57b --- /dev/null +++ b/src/settings/appearancesettingspage.cpp @@ -0,0 +1,255 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "appearancesettingspage.h" +#include "ui_appearancesettingspage.h" + +#include "settingsdialog.h" + +#include "core/application.h" +#include "core/mainwindow.h" +#include "core/appearance.h" +#include "core/logging.h" +#include "core/iconloader.h" +#include "playlist/playlistview.h" +#include "covermanager/albumcoverchoicecontroller.h" + +const char *AppearanceSettingsPage::kSettingsGroup = "Appearance"; + +AppearanceSettingsPage::AppearanceSettingsPage(SettingsDialog *dialog) + : SettingsPage(dialog), + ui_(new Ui_AppearanceSettingsPage), + original_use_a_custom_color_set_(false), + playlist_view_background_image_type_(PlaylistView::Default) { + + ui_->setupUi(this); + setWindowIcon(IconLoader::Load("view-media-visualization")); + + connect(ui_->blur_slider, SIGNAL(valueChanged(int)), SLOT(BlurLevelChanged(int))); + connect(ui_->opacity_slider, SIGNAL(valueChanged(int)), SLOT(OpacityLevelChanged(int))); + + Load(); + + connect(ui_->select_foreground_color, SIGNAL(pressed()), SLOT(SelectForegroundColor())); + connect(ui_->select_background_color, SIGNAL(pressed()), SLOT(SelectBackgroundColor())); + connect(ui_->use_a_custom_color_set, SIGNAL(toggled(bool)), SLOT(UseCustomColorSetOptionChanged(bool))); + + connect(ui_->select_background_image_filename_button, SIGNAL(pressed()), SLOT(SelectBackgroundImage())); + connect(ui_->use_custom_background_image, SIGNAL(toggled(bool)), ui_->background_image_filename, SLOT(setEnabled(bool))); + connect(ui_->use_custom_background_image, SIGNAL(toggled(bool)), ui_->select_background_image_filename_button, SLOT(setEnabled(bool))); + + connect(ui_->use_custom_background_image, SIGNAL(toggled(bool)), ui_->blur_slider, SLOT(setEnabled(bool))); + connect(ui_->use_album_cover_background, SIGNAL(toggled(bool)), ui_->blur_slider, SLOT(setEnabled(bool))); + + connect(ui_->use_default_background, SIGNAL(toggled(bool)), SLOT(DisableBlurAndOpacitySliders(bool))); + connect(ui_->use_no_background, SIGNAL(toggled(bool)), SLOT(DisableBlurAndOpacitySliders(bool))); + +} + +AppearanceSettingsPage::~AppearanceSettingsPage() { + delete ui_; +} + +void AppearanceSettingsPage::Load() { + + QSettings s; + s.beginGroup(kSettingsGroup); + + QPalette p = QApplication::palette(); + + // Keep in mind originals colors, in case the user clicks on Cancel, to be able to restore colors + original_use_a_custom_color_set_ = s.value(Appearance::kUseCustomColorSet, false).toBool(); + + original_foreground_color_ = s.value(Appearance::kForegroundColor, p.color(QPalette::WindowText)).value(); + current_foreground_color_ = original_foreground_color_; + original_background_color_ = s.value(Appearance::kBackgroundColor, p.color(QPalette::Window)).value(); + current_background_color_ = original_background_color_; + + InitColorSelectorsColors(); + + s.endGroup(); + + // Playlist settings + s.beginGroup(kSettingsGroup); + playlist_view_background_image_type_ = static_cast(s.value(PlaylistView::kSettingBackgroundImageType).toInt()); + playlist_view_background_image_filename_ = s.value(PlaylistView::kSettingBackgroundImageFilename).toString(); + + ui_->use_system_color_set->setChecked(!original_use_a_custom_color_set_); + ui_->use_a_custom_color_set->setChecked(original_use_a_custom_color_set_); + + switch (playlist_view_background_image_type_) { + case PlaylistView::None: + ui_->use_no_background->setChecked(true); + DisableBlurAndOpacitySliders(true); + break; + case PlaylistView::Album: + ui_->use_album_cover_background->setChecked(true); + break; + case PlaylistView::Custom: + ui_->use_custom_background_image->setChecked(true); + break; + case PlaylistView::Default: + default: + ui_->use_default_background->setChecked(true); + DisableBlurAndOpacitySliders(true); + } + ui_->background_image_filename->setText(playlist_view_background_image_filename_); + ui_->blur_slider->setValue(s.value("blur_radius", PlaylistView::kDefaultBlurRadius).toInt()); + ui_->opacity_slider->setValue(s.value("opacity_level", PlaylistView::kDefaultOpacityLevel).toInt()); + + s.endGroup(); + +} + +void AppearanceSettingsPage::Save() { + + QSettings s; + + s.beginGroup(kSettingsGroup); + bool use_a_custom_color_set = ui_->use_a_custom_color_set->isChecked(); + s.setValue(Appearance::kUseCustomColorSet, use_a_custom_color_set); + if (use_a_custom_color_set) { + s.setValue(Appearance::kBackgroundColor, current_background_color_); + s.setValue(Appearance::kForegroundColor, current_foreground_color_); + } + else { + dialog()->appearance()->ResetToSystemDefaultTheme(); + } + + playlist_view_background_image_filename_ = ui_->background_image_filename->text(); + if (ui_->use_no_background->isChecked()) { + playlist_view_background_image_type_ = PlaylistView::None; + } + else if (ui_->use_album_cover_background->isChecked()) { + playlist_view_background_image_type_ = PlaylistView::Album; + } + else if (ui_->use_default_background->isChecked()) { + playlist_view_background_image_type_ = PlaylistView::Default; + } + else if (ui_->use_custom_background_image->isChecked()) { + playlist_view_background_image_type_ = PlaylistView::Custom; + s.setValue(PlaylistView::kSettingBackgroundImageFilename, playlist_view_background_image_filename_); + } + s.setValue(PlaylistView::kSettingBackgroundImageType, playlist_view_background_image_type_); + s.setValue("blur_radius", ui_->blur_slider->value()); + s.setValue("opacity_level", ui_->opacity_slider->value()); + + s.endGroup(); + +} + +void AppearanceSettingsPage::Cancel() { + + if (original_use_a_custom_color_set_) { + dialog()->appearance()->ChangeForegroundColor(original_foreground_color_); + dialog()->appearance()->ChangeBackgroundColor(original_background_color_); + } + else { + dialog()->appearance()->ResetToSystemDefaultTheme(); + } + +} + +void AppearanceSettingsPage::SelectForegroundColor() { + + QColor color_selected = QColorDialog::getColor(current_foreground_color_); + if (!color_selected.isValid()) return; + + current_foreground_color_ = color_selected; + dialog()->appearance()->ChangeForegroundColor(color_selected); + + UpdateColorSelectorColor(ui_->select_foreground_color, color_selected); + +} + +void AppearanceSettingsPage::SelectBackgroundColor() { + + QColor color_selected = QColorDialog::getColor(current_background_color_); + if (!color_selected.isValid()) return; + + current_background_color_ = color_selected; + dialog()->appearance()->ChangeBackgroundColor(color_selected); + + UpdateColorSelectorColor(ui_->select_background_color, color_selected); + +} + +void AppearanceSettingsPage::UseCustomColorSetOptionChanged(bool checked) { + if (checked) { + dialog()->appearance()->ChangeForegroundColor(current_foreground_color_); + dialog()->appearance()->ChangeBackgroundColor(current_background_color_); + } + else { + dialog()->appearance()->ResetToSystemDefaultTheme(); + } +} + +void AppearanceSettingsPage::InitColorSelectorsColors() { + UpdateColorSelectorColor(ui_->select_foreground_color, current_foreground_color_); + UpdateColorSelectorColor(ui_->select_background_color, current_background_color_); +} + +void AppearanceSettingsPage::UpdateColorSelectorColor(QWidget *color_selector, const QColor &color) { + + QString css = QString("background-color: rgb(%1, %2, %3); color: rgb(255, 255, 255)").arg(color.red()).arg(color.green()).arg(color.blue()); + color_selector->setStyleSheet(css); + +} + +void AppearanceSettingsPage::SelectBackgroundImage() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QString selected_filename = QFileDialog::getOpenFileName(this, tr("Select background image"), playlist_view_background_image_filename_, tr(AlbumCoverChoiceController::kLoadImageFileFilter) + ";;" + tr(AlbumCoverChoiceController::kAllFilesFilter)); + if (selected_filename.isEmpty()) return; + playlist_view_background_image_filename_ = selected_filename; + ui_->background_image_filename->setText(playlist_view_background_image_filename_); + +} + +void AppearanceSettingsPage::BlurLevelChanged(int value) { + ui_->background_blur_radius_label->setText(QString("%1px").arg(value)); +} + +void AppearanceSettingsPage::OpacityLevelChanged(int percent) { + ui_->background_opacity_label->setText(QString("%1\%").arg(percent)); +} + +void AppearanceSettingsPage::DisableBlurAndOpacitySliders(bool checked) { + + // Blur slider + ui_->blur_slider->setDisabled(checked); + ui_->background_blur_radius_label->setDisabled(checked); + ui_->select_background_blur_label->setDisabled(checked); + + // Opacity slider + ui_->opacity_slider->setDisabled(checked); + ui_->background_opacity_label->setDisabled(checked); + ui_->select_opacity_level_label->setDisabled(checked); + +} diff --git a/src/settings/appearancesettingspage.h b/src/settings/appearancesettingspage.h new file mode 100644 index 00000000..0fc4784b --- /dev/null +++ b/src/settings/appearancesettingspage.h @@ -0,0 +1,73 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2012, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef APPEARANCESETTINGSPAGE_H +#define APPEARANCESETTINGSPAGE_H + +#include "config.h" + +#include "settingspage.h" + +#include "playlist/playlistview.h" + +class QWidget; + +class Ui_AppearanceSettingsPage; + +class AppearanceSettingsPage : public SettingsPage { + Q_OBJECT + +public: + AppearanceSettingsPage(SettingsDialog *dialog); + ~AppearanceSettingsPage(); + static const char *kSettingsGroup; + + void Load(); + void Save(); + void Cancel(); + +private slots: + void SelectForegroundColor(); + void SelectBackgroundColor(); + void UseCustomColorSetOptionChanged(bool); + void SelectBackgroundImage(); + void BlurLevelChanged(int); + void OpacityLevelChanged(int); + void DisableBlurAndOpacitySliders(bool); + +private: + + // Set the widget's background to new_color + void UpdateColorSelectorColor(QWidget *color_selector, const QColor &new_color); + // Init (or refresh) the colorSelectors colors + void InitColorSelectorsColors(); + + Ui_AppearanceSettingsPage *ui_; + bool original_use_a_custom_color_set_; + QColor original_foreground_color_; + QColor original_background_color_; + QColor current_foreground_color_; + QColor current_background_color_; + PlaylistView::BackgroundImageType playlist_view_background_image_type_; + QString playlist_view_background_image_filename_; + +}; + +#endif // APPEARANCESETTINGSPAGE_H diff --git a/src/settings/appearancesettingspage.ui b/src/settings/appearancesettingspage.ui new file mode 100644 index 00000000..1a36db62 --- /dev/null +++ b/src/settings/appearancesettingspage.ui @@ -0,0 +1,307 @@ + + + AppearanceSettingsPage + + + + 0 + 0 + 596 + 566 + + + + Appearance + + + + + + Colors + + + + + + Use the system default color set + + + + + + + Use a custom color set + + + + + + + + + false + + + Select foreground color: + + + + + + + false + + + + + + + + + + + + + + false + + + Select background color: + + + + + + + false + + + + + + + + + + + + + + + Background image + + + + + + Default background image + + + + + + + The album cover of the currently playing song + + + Album cover + + + + + + + No background image + + + + + + + + + Custom image: + + + + + + + false + + + + + + + false + + + Browse... + + + + + + + + + + + true + + + Blur amount + + + + + + + 0 + + + 10 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 1 + + + + + + + true + + + 0px + + + + + + + Opacity + + + + + + + 100 + + + 10 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 10 + + + + + + + 40% + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + use_a_custom_color_set + toggled(bool) + select_background_color + setEnabled(bool) + + + 301 + 72 + + + 440 + 139 + + + + + use_a_custom_color_set + toggled(bool) + select_background_color_label + setEnabled(bool) + + + 301 + 72 + + + 162 + 139 + + + + + use_a_custom_color_set + toggled(bool) + select_foreground_color + setEnabled(bool) + + + 301 + 72 + + + 440 + 104 + + + + + use_a_custom_color_set + toggled(bool) + select_foreground_color_label + setEnabled(bool) + + + 301 + 72 + + + 162 + 104 + + + + + diff --git a/src/settings/backendsettingspage.cpp b/src/settings/backendsettingspage.cpp new file mode 100644 index 00000000..75110383 --- /dev/null +++ b/src/settings/backendsettingspage.cpp @@ -0,0 +1,550 @@ +/* + * Strawberry Music Player + * Copyright 2013, Jonas Kvinge + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "backendsettingspage.h" +#include "ui_backendsettingspage.h" + +#include +#include +#include + +#include "settingsdialog.h" +#include "core/application.h" +#include "core/player.h" +#include "core/logging.h" +#include "core/utilities.h" +#include "core/iconloader.h" +#include "engine/enginetype.h" +#include "engine/enginebase.h" +#include "engine/gstengine.h" +#include "engine/xineengine.h" +#include "engine/devicefinder.h" + +const char *BackendSettingsPage::kSettingsGroup = "Backend"; +const char *BackendSettingsPage::EngineText_Xine = "Xine"; +const char *BackendSettingsPage::EngineText_GStreamer = "GStreamer"; +const char *BackendSettingsPage::EngineText_Phonon = "Phonon"; +const char *BackendSettingsPage::EngineText_VLC = "VLC"; + +BackendSettingsPage::BackendSettingsPage(SettingsDialog *dialog) : SettingsPage(dialog), ui_(new Ui_BackendSettingsPage) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + ui_->setupUi(this); + setWindowIcon(IconLoader::Load("soundcard")); + + connect(ui_->combobox_engine, SIGNAL(currentIndexChanged(int)), SLOT(EngineChanged(int))); + connect(ui_->combobox_output, SIGNAL(currentIndexChanged(int)), SLOT(OutputChanged(int))); + connect(ui_->combobox_device, SIGNAL(currentIndexChanged(int)), SLOT(DeviceSelectionChanged(int))); + connect(ui_->lineedit_device, SIGNAL(textChanged(const QString &)), SLOT(DeviceStringChanged())); + + connect(ui_->slider_bufferminfill, SIGNAL(valueChanged(int)), SLOT(BufferMinFillChanged(int))); + ui_->label_bufferminfillvalue->setMinimumWidth(QFontMetrics(ui_->label_bufferminfillvalue->font()).width("WW%")); + + connect(ui_->stickslider_replaygainpreamp, SIGNAL(valueChanged(int)), SLOT(RgPreampChanged(int))); + ui_->label_replaygainpreamp->setMinimumWidth(QFontMetrics(ui_->label_replaygainpreamp->font()).width("-WW.W dB")); + RgPreampChanged(ui_->stickslider_replaygainpreamp->value()); + + s_.beginGroup(BackendSettingsPage::kSettingsGroup); + +} + +BackendSettingsPage::~BackendSettingsPage() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + //dialog()->app()->player()->CreateEngine(engineloaded_); + //dialog()->app()->player()->ReloadSettings(); + //dialog()->app()->player()->Init(); + + s_.endGroup(); + + delete ui_; + +} + +void BackendSettingsPage::Load() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + configloaded_ = false; + engineloaded_ = Engine::None; + + Engine::EngineType enginetype = Engine::EngineTypeFromName(s_.value("engine", EngineText_Xine).toString()); + + ui_->combobox_engine->clear(); +#ifdef HAVE_XINE + ui_->combobox_engine->addItem(IconLoader::Load("xine"), EngineText_Xine, Engine::Xine); +#endif +#ifdef HAVE_GSTREAMER + ui_->combobox_engine->addItem(IconLoader::Load("gstreamer"), EngineText_GStreamer, Engine::GStreamer); +#endif +#ifdef HAVE_PHONON + ui_->combobox_engine->addItem(IconLoader::Load("speaker"), EngineText_Phonon, Engine::Phonon); +#endif +#ifdef HAVE_VLC + ui_->combobox_engine->addItem(IconLoader::Load("vlc"), EngineText_VLC, Engine::VLC); +#endif + + configloaded_ = true; + + ui_->combobox_engine->setCurrentIndex(ui_->combobox_engine->findData(enginetype)); + if (enginetype != engineloaded_) Load_Engine(enginetype); + + ui_->spinbox_bufferduration->setValue(s_.value("bufferduration", 4000).toInt()); + ui_->checkbox_monoplayback->setChecked(s_.value("monoplayback", false).toBool()); + ui_->slider_bufferminfill->setValue(s_.value("bufferminfill", 33).toInt()); + + ui_->checkbox_replaygain->setChecked(s_.value("rgenabled", false).toBool()); + ui_->combobox_replaygainmode->setCurrentIndex(s_.value("rgmode", 0).toInt()); + ui_->stickslider_replaygainpreamp->setValue(s_.value("rgpreamp", 0.0).toDouble() * 10 + 150); + ui_->checkbox_replaygaincompression->setChecked(s_.value("rgcompression", true).toBool()); + +} + +void BackendSettingsPage::Load_Engine(Engine::EngineType enginetype) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + output_ = s_.value("output", "").toString(); + device_ = s_.value("device", "").toString(); + + ui_->combobox_output->clear(); + ui_->combobox_device->clear(); + + ui_->combobox_output->setEnabled(false); + ui_->combobox_device->setEnabled(false); + + ui_->lineedit_device->setEnabled(false); + ui_->lineedit_device->setText(""); + + // If a engine is loaded (!= Engine::None) AND engine has been switched reset output and device. + if ((engineloaded_ != Engine::None) && (engineloaded_ != enginetype)) { + output_ = ""; + device_ = ""; + s_.setValue("output", ""); + s_.setValue("device", ""); + } + + if (dialog()->app()->player()->engine()->type() != enginetype) { + dialog()->app()->player()->CreateEngine(enginetype); + dialog()->app()->player()->ReloadSettings(); + dialog()->app()->player()->Init(); + } + + switch(enginetype) { +#ifdef HAVE_XINE + case Engine::Xine: + Xine_Load(); + break; +#endif +#ifdef HAVE_GSTREAMER + case Engine::GStreamer: + Gst_Load(); + break; +#endif +#ifdef HAVE_PHONON + case Engine::Phonon: + Phonon_Load(); + break; +#endif +#ifdef HAVE_VLC + case Engine::VLC: + VLC_Load(); + break; +#endif + default: + QMessageBox messageBox; + QString msg = QString("Missing engine %1!").arg(Engine::EngineNameFromType(enginetype)); + messageBox.critical(nullptr, "Error", msg); + messageBox.setFixedSize(500, 200); + return; + } + +} + +void BackendSettingsPage::Load_Device(QString output) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + ui_->combobox_device->setEnabled(false); + ui_->combobox_device->clear(); + ui_->lineedit_device->setEnabled(false); + ui_->lineedit_device->setText(""); + + ui_->combobox_device->addItem(IconLoader::Load("soundcard"), "Automatically select", ""); + ui_->combobox_device->addItem(IconLoader::Load("soundcard"), "Custom", ""); + int i = 0; + for (DeviceFinder *finder : dialog()->app()->enginedevice()->device_finders_) { + if (finder->output() != output) continue; + for (const DeviceFinder::Device &device : finder->ListDevices()) { + i++; + ui_->combobox_device->addItem(IconLoader::Load(device.iconname), device.description, device.string); + } + } + if (i > 0) { + ui_->combobox_device->setEnabled(true); + ui_->lineedit_device->setEnabled(true); + ui_->lineedit_device->setText(device_); + + } + +} + +#ifdef HAVE_GSTREAMER +void BackendSettingsPage::Gst_Load() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (output_ == "") output_ = GstEngine::kAutoSink; + + if (dialog()->app()->player()->engine()->type() != Engine::GStreamer) { + QMessageBox messageBox; + messageBox.critical(nullptr, "Error", "GStramer not initialized! Please restart."); + messageBox.setFixedSize(500, 200); + return; + } + GstEngine *gstengine = qobject_cast(dialog()->app()->player()->engine()); + + ui_->combobox_output->clear(); + int i = 0; + for (const EngineBase::OutputDetails &output : gstengine->GetOutputsList()) { + i++; + ui_->combobox_output->addItem(IconLoader::Load(output.iconname), output.description, QVariant::fromValue(output)); + //qLog(Debug) << output.description; + } + if (i > 0) ui_->combobox_output->setEnabled(true); + + for (int i = 0; i < ui_->combobox_output->count(); ++i) { + EngineBase::OutputDetails details = ui_->combobox_output->itemData(i).value(); + if (details.name == output_) { + ui_->combobox_output->setCurrentIndex(i); + break; + } + } + + engineloaded_=Engine::GStreamer; + +} +#endif + +#ifdef HAVE_XINE +void BackendSettingsPage::Xine_Load() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (output_ == "") output_ = "auto"; + + if (dialog()->app()->player()->engine()->type() != Engine::Xine) { + QMessageBox messageBox; + messageBox.critical(nullptr, "Error", "Xine not initialized! Please restart."); + messageBox.setFixedSize(500, 200); + return; + } + XineEngine *xineengine = qobject_cast(dialog()->app()->player()->engine()); + + ui_->combobox_output->clear(); + int i = 0; + for (const EngineBase::OutputDetails &output : xineengine->GetOutputsList()) { + i++; + ui_->combobox_output->addItem(IconLoader::Load(output.iconname), output.description, QVariant::fromValue(output)); + //qLog(Debug) << output.description; + } + if (i > 0) ui_->combobox_output->setEnabled(true); + + for (int i = 0; i < ui_->combobox_output->count(); ++i) { + EngineBase::OutputDetails details = ui_->combobox_output->itemData(i).value(); + if (details.name == output_) { + ui_->combobox_output->setCurrentIndex(i); + break; + } + } + + engineloaded_=Engine::Xine; + +} +#endif + +#ifdef HAVE_PHONON +void BackendSettingsPage::Phonon_Load() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + ui_->combobox_output->clear(); + ui_->combobox_device->clear(); + ui_->lineedit_device->setText(""); + + engineloaded_=Engine::Phonon; + +} +#endif + +#ifdef HAVE_VLC +void BackendSettingsPage::VLC_Load() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + ui_->combobox_output->clear(); + ui_->combobox_device->clear(); + ui_->lineedit_device->setText(""); + + engineloaded_=Engine::VLC; + +} +#endif + +void BackendSettingsPage::Save() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + s_.setValue("engine", ui_->combobox_engine->itemText(ui_->combobox_engine->currentIndex()).toLower()); + + QVariant myVariant = ui_->combobox_engine->itemData(ui_->combobox_engine->currentIndex()); + Engine::EngineType enginetype = myVariant.value(); + + switch(enginetype) { +#ifdef HAVE_XINE + case Engine::Xine: + Xine_Save(); + break; +#endif +#ifdef HAVE_GSTREAMER + case Engine::GStreamer: + Gst_Save(); + break; +#endif +#ifdef HAVE_PHONON + case Engine::Phonon: + Phonon_Save(); + break; +#endif +#ifdef HAVE_VLC + case Engine::VLC: + VLC_Save(); + break; +#endif + default: + break; + } + + s_.setValue("device", ui_->lineedit_device->text()); + s_.setValue("bufferduration", ui_->spinbox_bufferduration->value()); + s_.setValue("monoplayback", ui_->checkbox_monoplayback->isChecked()); + s_.setValue("bufferminfill", ui_->slider_bufferminfill->value()); + s_.setValue("rgenabled", ui_->checkbox_replaygain->isChecked()); + s_.setValue("rgmode", ui_->combobox_replaygainmode->currentIndex()); + s_.setValue("rgpreamp", float(ui_->stickslider_replaygainpreamp->value()) / 10 - 15); + s_.setValue("rgcompression", ui_->checkbox_replaygaincompression->isChecked()); + +} + +#ifdef HAVE_XINE +void BackendSettingsPage::Xine_Save() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + XineEngine *xineengine = qobject_cast(dialog()->app()->player()->engine()); + + EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value(); + s_.setValue("output", output.name); + + for (EngineBase::OutputDetails &output : xineengine->GetOutputsList()) { + if (xineengine->ALSADeviceSupport(output.name)) output.device_property_value = QVariant(ui_->lineedit_device->text()); + else output.device_property_value = QVariant(ui_->combobox_device->itemData(ui_->combobox_device->currentIndex())); + } + +} +#endif + +#ifdef HAVE_GSTREAMER +void BackendSettingsPage::Gst_Save() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + GstEngine *gstengine = qobject_cast(dialog()->app()->player()->engine()); + + EngineBase::OutputDetails output = ui_->combobox_output->itemData(ui_->combobox_output->currentIndex()).value(); + s_.setValue("output", output.name); + + for (EngineBase::OutputDetails &output : gstengine->GetOutputsList()) { + if (GstEngine::ALSADeviceSupport(output.name)) output.device_property_value = QVariant(ui_->lineedit_device->text()); + else output.device_property_value = QVariant(ui_->combobox_device->itemData(ui_->combobox_device->currentIndex())); + } + +} +#endif + +#ifdef HAVE_PHONON +void BackendSettingsPage::Phonon_Save() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + +} +#endif + +#ifdef HAVE_VLC +void BackendSettingsPage::VLC_Save() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + +} +#endif + +void BackendSettingsPage::EngineChanged(int index) { + + if (configloaded_ == false) return; + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QVariant myVariant = ui_->combobox_engine->itemData(index); + Engine::EngineType enginetype = myVariant.value(); + + Load_Engine(enginetype); + +} + +void BackendSettingsPage::OutputChanged(int index) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QVariant myVariant = ui_->combobox_engine->itemData(ui_->combobox_engine->currentIndex()); + Engine::EngineType enginetype = myVariant.value(); + OutputChanged(index, enginetype); + +} + +void BackendSettingsPage::OutputChanged(int index, Engine::EngineType enginetype) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + switch(enginetype) { + case Engine::Xine: +#ifdef HAVE_XINE + Xine_OutputChanged(index); + break; +#endif + case Engine::GStreamer: +#ifdef HAVE_GSTREAMER + Gst_OutputChanged(index); + break; +#endif + case Engine::Phonon: +#ifdef HAVE_PHONON + Phonon_OutputChanged(index); + break; +#endif + case Engine::VLC: +#ifdef HAVE_VLC + VLC_OutputChanged(index); + break; +#endif + default: + break; + } + +} + +#ifdef HAVE_XINE +void BackendSettingsPage::Xine_OutputChanged(int index) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + EngineBase::OutputDetails details = ui_->combobox_output->itemData(index).value(); + QString name = details.name; + if (XineEngine::ALSADeviceSupport(name)) Load_Device("alsa"); + else Load_Device(name); +} +#endif + +#ifdef HAVE_GSTREAMER +void BackendSettingsPage::Gst_OutputChanged(int index) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + EngineBase::OutputDetails details = ui_->combobox_output->itemData(index).value(); + QString name = details.name; + if (GstEngine::ALSADeviceSupport(name)) Load_Device("alsa"); + else Load_Device(name); + +} +#endif + +#ifdef HAVE_PHONON +void BackendSettingsPage::Phonon_OutputChanged(int index) { + //qLog(Debug) << __PRETTY_FUNCTION__; + Load_Device(""); +} +#endif + +#ifdef HAVE_VLC +void BackendSettingsPage::VLC_OutputChanged(int index) { + //qLog(Debug) << __PRETTY_FUNCTION__; + Load_Device(""); +} +#endif + +void BackendSettingsPage::DeviceSelectionChanged(int index) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + if (index == 1) return; + + QString string = ui_->combobox_device->itemData(index).value(); + + ui_->lineedit_device->setText(string); + +} + +void BackendSettingsPage::DeviceStringChanged() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QString string = ui_->lineedit_device->text(); + + for (int i = 0; i < ui_->combobox_device->count(); ++i) { + QString s = ui_->combobox_device->itemData(i).value(); + if (s == string ) { + ui_->combobox_device->setCurrentIndex(i); + return; + } + } + + ui_->combobox_device->setCurrentIndex(1); + +} + +void BackendSettingsPage::RgPreampChanged(int value) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + float db = float(value) / 10 - 15; + QString db_str; + db_str.sprintf("%+.1f dB", db); + ui_->label_replaygainpreamp->setText(db_str); + +} + +void BackendSettingsPage::BufferMinFillChanged(int value) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + ui_->label_bufferminfillvalue->setText(QString::number(value) + "%"); +} diff --git a/src/settings/backendsettingspage.h b/src/settings/backendsettingspage.h new file mode 100644 index 00000000..9dc3bf97 --- /dev/null +++ b/src/settings/backendsettingspage.h @@ -0,0 +1,102 @@ +/* + * Strawberry Music Player + * Copyright 2013, Jonas Kvinge + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef BACKENDSETTINGSPAGE_H +#define BACKENDSETTINGSPAGE_H + +#include "config.h" + +#include +#include + +#include "backendsettingspage.h" + +#include "settingspage.h" + +#include "engine/engine_fwd.h" +#include "engine/enginetype.h" + +class Ui_BackendSettingsPage; + +class BackendSettingsPage : public SettingsPage { + Q_OBJECT + +public: + BackendSettingsPage(SettingsDialog *dialog); + ~BackendSettingsPage(); + + static const char *kSettingsGroup; + static const char *EngineText_Xine; + static const char *EngineText_GStreamer; + static const char *EngineText_Phonon; + static const char *EngineText_VLC; + + void Load(); + void Save(); + + private slots: + void EngineChanged(int index); + void OutputChanged(int index); + void DeviceSelectionChanged(int index); + void DeviceStringChanged(); + void RgPreampChanged(int value); + void BufferMinFillChanged(int value); + +private: + Ui_BackendSettingsPage *ui_; + + void EngineChanged(Engine::EngineType enginetype); + void OutputChanged(int index, Engine::EngineType enginetype); + + void Load_Engine(Engine::EngineType enginetype); + void Load_Device(QString output); + +#ifdef HAVE_XINE + void Xine_Load(); + void Xine_Save(); + void Xine_OutputChanged(int index); +#endif + +#ifdef HAVE_GSTREAMER + void Gst_Load(); + void Gst_Save(); + void Gst_OutputChanged(int index); +#endif + +#ifdef HAVE_PHONON + void Phonon_Load(); + void Phonon_Save(); + void Phonon_OutputChanged(int index); +#endif + +#ifdef HAVE_VLC + void VLC_Load(); + void VLC_Save(); + void VLC_OutputChanged(int index); +#endif + + bool configloaded_; + Engine::EngineType engineloaded_; + QString output_; + QString device_; + QSettings s_; + +}; + +#endif // BACKENDSETTINGSPAGE_H diff --git a/src/settings/backendsettingspage.ui b/src/settings/backendsettingspage.ui new file mode 100644 index 00000000..5007f555 --- /dev/null +++ b/src/settings/backendsettingspage.ui @@ -0,0 +1,299 @@ + + + BackendSettingsPage + + + + 0 + 0 + 596 + 638 + + + + Backend + + + + + + Audio output + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Engine + + + + + + + + + + true + + + Output + + + + + + + false + + + + + + + true + + + Device + + + + + + + + + false + + + + + + + false + + + + 80 + 16777215 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + + + + + + + + + + Buffer duration + + + + + + + ms + + + 60000 + + + 100 + + + + + + + Minimum buffer fill + + + + + + + + + + + + + + + + 1 + + + 50 + + + 33 + + + Qt::Horizontal + + + 1 + + + + + + + + + Changing mono playback preference will be effective for the next playing songs + + + Mono playback + + + + + + + + + + Replay Gain + + + + + + Use Replay Gain metadata if it is available + + + + + + + false + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Replay Gain mode + + + + + + + + Radio (equal loudness for all tracks) + + + + + Album (ideal loudness for all tracks) + + + + + + + + Pre-amp + + + + + + + + + + + + 300 + + + 150 + + + Qt::Horizontal + + + 150 + + + + + + + + + Apply compression to prevent clipping + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 114 + + + + + + + + + StickySlider + QSlider +
widgets/stickyslider.h
+
+ + LineEdit + QWidget +
widgets/lineedit.h
+
+
+ + +
diff --git a/src/settings/behavioursettingspage.cpp b/src/settings/behavioursettingspage.cpp new file mode 100644 index 00000000..180bb0ba --- /dev/null +++ b/src/settings/behavioursettingspage.cpp @@ -0,0 +1,108 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include "behavioursettingspage.h" + +#include "core/iconloader.h" +#include "core/mainwindow.h" +#include "ui_behavioursettingspage.h" + +const char *BehaviourSettingsPage::kSettingsGroup = "Behaviour"; + +namespace { + + bool LocaleAwareCompare(const QString& a, const QString& b) { + return a.localeAwareCompare(b) < 0; + } +} // namespace + +BehaviourSettingsPage::BehaviourSettingsPage(SettingsDialog *dialog) : SettingsPage(dialog), ui_(new Ui_BehaviourSettingsPage) { + + ui_->setupUi(this); + setWindowIcon(IconLoader::Load("strawberry")); + + connect(ui_->checkbox_showtrayicon, SIGNAL(toggled(bool)), SLOT(ShowTrayIconToggled(bool))); + +#ifdef Q_OS_DARWIN + ui_->checkbox_showtrayicon->setEnabled(false); + ui_->startup_group->setEnabled(false); +#endif + +} + +BehaviourSettingsPage::~BehaviourSettingsPage() { + delete ui_; +} + +void BehaviourSettingsPage::Load() { + + QSettings s; + + s.beginGroup(kSettingsGroup); + ui_->checkbox_showtrayicon->setChecked(s.value("showtrayicon", true).toBool()); + ui_->checkbox_scrolltrayicon->setChecked(s.value("scrolltrayicon", ui_->checkbox_showtrayicon->isChecked()).toBool()); + ui_->checkbox_keeprunning->setChecked(s.value("keeprunning", false).toBool()); + + MainWindow::StartupBehaviour behaviour = MainWindow::StartupBehaviour(s.value("startupbehaviour", MainWindow::Startup_Remember).toInt()); + switch (behaviour) { + case MainWindow::Startup_AlwaysHide: ui_->radiobutton_alwayshide->setChecked(true); break; + case MainWindow::Startup_AlwaysShow: ui_->radiobutton_alwaysshow->setChecked(true); break; + case MainWindow::Startup_Remember: ui_->radiobutton_remember->setChecked(true); break; + } + + ui_->checkbox_resumeplayback->setChecked(s.value("resumeplayback", false).toBool()); + ui_->spinbox_seekstepsec->setValue(s.value("seek_step_sec", 10).toInt()); + + s.endGroup(); + +} + +void BehaviourSettingsPage::Save() { + + QSettings s; + + MainWindow::StartupBehaviour behaviour = MainWindow::Startup_Remember; + if (ui_->radiobutton_alwayshide->isChecked()) behaviour = MainWindow::Startup_AlwaysHide; + if (ui_->radiobutton_alwaysshow->isChecked()) behaviour = MainWindow::Startup_AlwaysShow; + if (ui_->radiobutton_remember->isChecked()) behaviour = MainWindow::Startup_Remember; + + s.beginGroup(kSettingsGroup); + s.setValue("showtrayicon", ui_->checkbox_showtrayicon->isChecked()); + s.setValue("scrolltrayicon", ui_->checkbox_scrolltrayicon->isChecked()); + s.setValue("keeprunning", ui_->checkbox_keeprunning->isChecked()); + s.setValue("startupbehaviour", int(behaviour)); + s.setValue("resumeplayback", ui_->checkbox_resumeplayback->isChecked()); + s.endGroup(); + +} + +void BehaviourSettingsPage::ShowTrayIconToggled(bool on) { + + ui_->radiobutton_alwayshide->setEnabled(on); + if (!on && ui_->radiobutton_alwayshide->isChecked()) ui_->radiobutton_remember->setChecked(true); + ui_->checkbox_keeprunning->setEnabled(on); + ui_->checkbox_keeprunning->setChecked(on); + ui_->checkbox_scrolltrayicon->setEnabled(on); + +} diff --git a/src/settings/behavioursettingspage.h b/src/settings/behavioursettingspage.h new file mode 100644 index 00000000..9cd1032f --- /dev/null +++ b/src/settings/behavioursettingspage.h @@ -0,0 +1,51 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef BEHAVIOURSETTINGSPAGE_H +#define BEHAVIOURSETTINGSPAGE_H + +#include "config.h" + +#include "settingspage.h" + +#include + +class Ui_BehaviourSettingsPage; + +class BehaviourSettingsPage : public SettingsPage { + Q_OBJECT + +public: + BehaviourSettingsPage(SettingsDialog *dialog); + ~BehaviourSettingsPage(); + static const char *kSettingsGroup; + + void Load(); + void Save(); + +private slots: + void ShowTrayIconToggled(bool on); + +private: + Ui_BehaviourSettingsPage *ui_; + +}; + +#endif // BEHAVIOURSETTINGSPAGE_H diff --git a/src/settings/behavioursettingspage.ui b/src/settings/behavioursettingspage.ui new file mode 100644 index 00000000..3b70018a --- /dev/null +++ b/src/settings/behavioursettingspage.ui @@ -0,0 +1,178 @@ + + + BehaviourSettingsPage + + + + 0 + 0 + 516 + 851 + + + + Behavior + + + + + + Show system tray icon + + + true + + + + + + + Scroll over icon to change track + + + false + + + + + + + Keep running in the background when the window is closed + + + false + + + + + + + Qt::Horizontal + + + + + + + When Strawberry starts + + + + + + Always show the main window + + + + + + + Always hide the main window + + + + + + + Remember from last time + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + Resume playback on start + + + false + + + + + + + + + + Seeking using a keyboard shortcut or mouse wheel + + + + + + Time step + + + + + + + s + + + + + + 1 + + + 20 + + + 10 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + + 20 + 5 + + + + + + + + + diff --git a/src/settings/collectionsettingspage.cpp b/src/settings/collectionsettingspage.cpp new file mode 100644 index 00000000..188ee78b --- /dev/null +++ b/src/settings/collectionsettingspage.cpp @@ -0,0 +1,131 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "collectionsettingspage.h" +#include "ui_collectionsettingspage.h" + +#include "settings/settingsdialog.h" +#include "core/application.h" +#include "core/utilities.h" +#include "core/iconloader.h" +#include "playlist/playlistdelegates.h" + +#include "collection/collectionbackend.h" +#include "collection/collectiondirectorymodel.h" +#include "collection/collectionmodel.h" +#include "collection/collectionview.h" +#include "collection/collectionwatcher.h" + +const char *CollectionSettingsPage::kSettingsGroup = "Collection"; + +CollectionSettingsPage::CollectionSettingsPage(SettingsDialog* dialog) + : SettingsPage(dialog), + ui_(new Ui_CollectionSettingsPage), + initialised_model_(false) { + ui_->setupUi(this); + ui_->list->setItemDelegate(new NativeSeparatorsDelegate(this)); + + // Icons + setWindowIcon(IconLoader::Load("vinyl")); + ui_->add->setIcon(IconLoader::Load("document-open-folder")); + + connect(ui_->add, SIGNAL(clicked()), SLOT(Add())); + connect(ui_->remove, SIGNAL(clicked()), SLOT(Remove())); +} + +CollectionSettingsPage::~CollectionSettingsPage() { delete ui_; } + +void CollectionSettingsPage::Add() { + + QSettings settings; + settings.beginGroup(kSettingsGroup); + + QString path(settings.value("last_path", Utilities::GetConfigPath(Utilities::Path_DefaultMusicCollection)).toString()); + path = QFileDialog::getExistingDirectory(this, tr("Add directory..."), path); + + if (!path.isNull()) { + dialog()->collection_directory_model()->AddDirectory(path); + } + + settings.setValue("last_path", path); +} + +void CollectionSettingsPage::Remove() { + dialog()->collection_directory_model()->RemoveDirectory(ui_->list->currentIndex()); +} + +void CollectionSettingsPage::CurrentRowChanged(const QModelIndex& index) { + ui_->remove->setEnabled(index.isValid()); +} + +void CollectionSettingsPage::Save() { + + QSettings s; + + s.beginGroup(kSettingsGroup); + s.setValue("auto_open", ui_->auto_open->isChecked()); + s.setValue("pretty_covers", ui_->pretty_covers->isChecked()); + s.setValue("show_dividers", ui_->show_dividers->isChecked()); + s.setValue("startup_scan", ui_->startup_scan->isChecked()); + s.setValue("monitor", ui_->monitor->isChecked()); + + QString filter_text = ui_->cover_art_patterns->text(); + QStringList filters = filter_text.split(',', QString::SkipEmptyParts); + s.setValue("cover_art_patterns", filters); + + s.endGroup(); + +} + +void CollectionSettingsPage::Load() { + + if (!initialised_model_) { + if (ui_->list->selectionModel()) { + disconnect(ui_->list->selectionModel(), SIGNAL(currentRowChanged(QModelIndex, QModelIndex)), this, SLOT(CurrentRowChanged(QModelIndex))); + } + + ui_->list->setModel(dialog()->collection_directory_model()); + initialised_model_ = true; + + connect(ui_->list->selectionModel(), SIGNAL(currentRowChanged(QModelIndex, QModelIndex)), SLOT(CurrentRowChanged(QModelIndex))); + } + + QSettings s; + + s.beginGroup(kSettingsGroup); + ui_->auto_open->setChecked(s.value("auto_open", true).toBool()); + ui_->pretty_covers->setChecked(s.value("pretty_covers", true).toBool()); + ui_->show_dividers->setChecked(s.value("show_dividers", true).toBool()); + ui_->startup_scan->setChecked(s.value("startup_scan", true).toBool()); + ui_->monitor->setChecked(s.value("monitor", true).toBool()); + + QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList(); + ui_->cover_art_patterns->setText(filters.join(",")); + + s.endGroup(); +} diff --git a/src/settings/collectionsettingspage.h b/src/settings/collectionsettingspage.h new file mode 100644 index 00000000..a5fa7221 --- /dev/null +++ b/src/settings/collectionsettingspage.h @@ -0,0 +1,56 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef LIBRARYSETTINGSPAGE_H +#define LIBRARYSETTINGSPAGE_H + +#include "config.h" + +#include "settingspage.h" + +class Ui_CollectionSettingsPage; +class CollectionDirectoryModel; + +class QModelIndex; + +class CollectionSettingsPage : public SettingsPage { + Q_OBJECT + +public: + CollectionSettingsPage(SettingsDialog *dialog); + ~CollectionSettingsPage(); + + static const char *kSettingsGroup; + + void Load(); + void Save(); + +private slots: + void Add(); + void Remove(); + + void CurrentRowChanged(const QModelIndex &index); + +private: + Ui_CollectionSettingsPage *ui_; + bool initialised_model_; +}; + +#endif // LIBRARYSETTINGSPAGE_H diff --git a/src/settings/collectionsettingspage.ui b/src/settings/collectionsettingspage.ui new file mode 100644 index 00000000..74f2c39f --- /dev/null +++ b/src/settings/collectionsettingspage.ui @@ -0,0 +1,152 @@ + + + CollectionSettingsPage + + + + 0 + 0 + 509 + 452 + + + + Collection + + + + + + These folders will be scanned for music to make up your collection + + + + + + + + + + 16 + 16 + + + + true + + + + + + + + + Add new folder... + + + false + + + + + + + Remove folder + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Automatic updating + + + + + + Update the collection when Strawberry starts + + + + + + + Monitor the collection for changes + + + + + + + Preferred album art filenames (comma separated) + + + + + + + When looking for album art Strawberry will first look for picture files that contain one of these words. +If there are no matches then it will use the largest image in the directory. + + + + + + + + + + Display options + + + + + + Automatically open single categories in the collection tree + + + + + + + Show album cover art in collection + + + + + + + Show dividers + + + + + + + + + + list + add + remove + + + + diff --git a/src/settings/networkproxysettingspage.cpp b/src/settings/networkproxysettingspage.cpp new file mode 100644 index 00000000..f27686c3 --- /dev/null +++ b/src/settings/networkproxysettingspage.cpp @@ -0,0 +1,94 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "networkproxysettingspage.h" +#include "ui_networkproxysettingspage.h" + +#include "core/iconloader.h" +#include "core/networkproxyfactory.h" + +#include + +const char *NetworkProxySettingsPage::kSettingsGroup = "NetworkProxy"; + +NetworkProxySettingsPage::NetworkProxySettingsPage(SettingsDialog* dialog) + : SettingsPage(dialog), ui_(new Ui_NetworkProxySettingsPage) { + + ui_->setupUi(this); + setWindowIcon(IconLoader::Load("applications-internet")); +} + +NetworkProxySettingsPage::~NetworkProxySettingsPage() { delete ui_; } + +void NetworkProxySettingsPage::Load() { + + QSettings s; + + s.beginGroup(NetworkProxyFactory::kSettingsGroup); + NetworkProxyFactory::Mode mode = NetworkProxyFactory::Mode(s.value("mode", NetworkProxyFactory::Mode_System).toInt()); + switch (mode) { + case NetworkProxyFactory::Mode_Manual: + ui_->proxy_manual->setChecked(true); + break; + + case NetworkProxyFactory::Mode_Direct: + ui_->proxy_direct->setChecked(true); + break; + + case NetworkProxyFactory::Mode_System: + default: + ui_->proxy_system->setChecked(true); + break; + } + + ui_->proxy_type->setCurrentIndex(s.value("type", QNetworkProxy::HttpProxy).toInt() == QNetworkProxy::HttpProxy ? 0 : 1); + ui_->proxy_hostname->setText(s.value("hostname").toString()); + ui_->proxy_port->setValue(s.value("port").toInt()); + ui_->proxy_auth->setChecked(s.value("use_authentication", false).toBool()); + ui_->proxy_username->setText(s.value("username").toString()); + ui_->proxy_password->setText(s.value("password").toString()); + s.endGroup(); + +} + +void NetworkProxySettingsPage::Save() { + + QSettings s; + + NetworkProxyFactory::Mode mode = NetworkProxyFactory::Mode_System; + if (ui_->proxy_direct->isChecked()) mode = NetworkProxyFactory::Mode_Direct; + else if (ui_->proxy_system->isChecked()) mode = NetworkProxyFactory::Mode_System; + else if (ui_->proxy_manual->isChecked()) mode = NetworkProxyFactory::Mode_Manual; + + s.beginGroup(NetworkProxyFactory::kSettingsGroup); + s.setValue("mode", mode); + s.setValue("type", ui_->proxy_type->currentIndex() == 0 ? QNetworkProxy::HttpProxy : QNetworkProxy::Socks5Proxy); + s.setValue("hostname", ui_->proxy_hostname->text()); + s.setValue("port", ui_->proxy_port->value()); + s.setValue("use_authentication", ui_->proxy_auth->isChecked()); + s.setValue("username", ui_->proxy_username->text()); + s.setValue("password", ui_->proxy_password->text()); + s.endGroup(); + + NetworkProxyFactory::Instance()->ReloadSettings(); + +} diff --git a/src/settings/networkproxysettingspage.h b/src/settings/networkproxysettingspage.h new file mode 100644 index 00000000..75579ef9 --- /dev/null +++ b/src/settings/networkproxysettingspage.h @@ -0,0 +1,45 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef NETWORKPROXYSETTINGSPAGE_H +#define NETWORKPROXYSETTINGSPAGE_H + +#include "config.h" + +#include "settingspage.h" + +class Ui_NetworkProxySettingsPage; + +class NetworkProxySettingsPage : public SettingsPage { + Q_OBJECT + +public: + NetworkProxySettingsPage(SettingsDialog* dialog); + ~NetworkProxySettingsPage(); + static const char *kSettingsGroup; + + void Load(); + void Save(); + +private: + Ui_NetworkProxySettingsPage* ui_; +}; + +#endif // NETWORKPROXYSETTINGSPAGE_H diff --git a/src/settings/networkproxysettingspage.ui b/src/settings/networkproxysettingspage.ui new file mode 100644 index 00000000..62cf0fe1 --- /dev/null +++ b/src/settings/networkproxysettingspage.ui @@ -0,0 +1,167 @@ + + + NetworkProxySettingsPage + + + + 0 + 0 + 400 + 300 + + + + Network Proxy + + + + + + Use the system proxy settings + + + true + + + + + + + Direct internet connection + + + + + + + Manual proxy configuration + + + + + + + false + + + + 24 + + + + + + + + HTTP proxy + + + + + SOCKS proxy + + + + + + + + + + + Port + + + + + + + 65535 + + + 8080 + + + + + + + + + Use authentication + + + true + + + false + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Username + + + + + + + + + + Password + + + + + + + QLineEdit::Password + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 36 + + + + + + + + + + proxy_manual + toggled(bool) + proxy_manual_container + setEnabled(bool) + + + 39 + 76 + + + 29 + 99 + + + + + diff --git a/src/settings/notificationssettingspage.cpp b/src/settings/notificationssettingspage.cpp new file mode 100644 index 00000000..77fe8784 --- /dev/null +++ b/src/settings/notificationssettingspage.cpp @@ -0,0 +1,325 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "notificationssettingspage.h" +#include "ui_notificationssettingspage.h" + +#include "settingsdialog.h" +#include "core/iconloader.h" +#include "widgets/osdpretty.h" + +#include +#include +#include +#include + +const char *NotificationsSettingsPage::kSettingsGroup = "Notifications"; + +NotificationsSettingsPage::NotificationsSettingsPage(SettingsDialog* dialog) + : SettingsPage(dialog), ui_(new Ui_NotificationsSettingsPage), pretty_popup_(new OSDPretty(OSDPretty::Mode_Draggable)) { + ui_->setupUi(this); + setWindowIcon(IconLoader::Load("help-hint")); + + pretty_popup_->SetMessage(tr("OSD Preview"), tr("Drag to reposition"), QImage(":pictures/nocover.png")); + + ui_->notifications_bg_preset->setItemData(0, QColor(OSDPretty::kPresetBlue), Qt::DecorationRole); + ui_->notifications_bg_preset->setItemData(1, QColor(OSDPretty::kPresetOrange), Qt::DecorationRole); + + // Create and populate the helper menus + QMenu* menu = new QMenu(this); + menu->addAction(ui_->action_artist); + menu->addAction(ui_->action_album); + menu->addAction(ui_->action_title); + menu->addAction(ui_->action_albumartist); + menu->addAction(ui_->action_year); + menu->addAction(ui_->action_composer); + menu->addAction(ui_->action_performer); + menu->addAction(ui_->action_grouping); + menu->addAction(ui_->action_length); + menu->addAction(ui_->action_disc); + menu->addAction(ui_->action_track); + menu->addAction(ui_->action_genre); + menu->addAction(ui_->action_playcount); + menu->addAction(ui_->action_skipcount); + menu->addAction(ui_->action_filename); + menu->addAction(ui_->action_rating); + menu->addAction(ui_->action_score); + menu->addSeparator(); + menu->addAction(ui_->action_newline); + ui_->notifications_exp_chooser1->setMenu(menu); + ui_->notifications_exp_chooser2->setMenu(menu); + ui_->notifications_exp_chooser1->setPopupMode(QToolButton::InstantPopup); + ui_->notifications_exp_chooser2->setPopupMode(QToolButton::InstantPopup); + // We need this because by default menus don't show tooltips + connect(menu, SIGNAL(hovered(QAction*)), SLOT(ShowMenuTooltip(QAction*))); + + connect(ui_->notifications_none, SIGNAL(toggled(bool)), SLOT(NotificationTypeChanged())); + connect(ui_->notifications_native, SIGNAL(toggled(bool)), SLOT(NotificationTypeChanged())); + connect(ui_->notifications_tray, SIGNAL(toggled(bool)), SLOT(NotificationTypeChanged())); + connect(ui_->notifications_pretty, SIGNAL(toggled(bool)), SLOT(NotificationTypeChanged())); + connect(ui_->notifications_opacity, SIGNAL(valueChanged(int)), SLOT(PrettyOpacityChanged(int))); + connect(ui_->notifications_bg_preset, SIGNAL(activated(int)), SLOT(PrettyColorPresetChanged(int))); + connect(ui_->notifications_fg_choose, SIGNAL(clicked()), SLOT(ChooseFgColor())); + connect(ui_->notifications_font_choose, SIGNAL(clicked()), SLOT(ChooseFont())); + connect(ui_->notifications_exp_chooser1, SIGNAL(triggered(QAction*)), SLOT(InsertVariableFirstLine(QAction*))); + connect(ui_->notifications_exp_chooser2, SIGNAL(triggered(QAction*)), SLOT(InsertVariableSecondLine(QAction*))); + connect(ui_->notifications_disable_duration, SIGNAL(toggled(bool)), ui_->notifications_duration, SLOT(setDisabled(bool))); + + if (!OSD::SupportsNativeNotifications()) + ui_->notifications_native->setEnabled(false); + if (!OSD::SupportsTrayPopups()) ui_->notifications_tray->setEnabled(false); + + connect(ui_->notifications_pretty, SIGNAL(toggled(bool)), SLOT(UpdatePopupVisible())); + + connect(ui_->notifications_custom_text_enabled, SIGNAL(toggled(bool)), SLOT(NotificationCustomTextChanged(bool))); + connect(ui_->notifications_preview, SIGNAL(clicked()), SLOT(PrepareNotificationPreview())); + + // Icons + ui_->notifications_exp_chooser1->setIcon(IconLoader::Load("list-add")); + ui_->notifications_exp_chooser2->setIcon(IconLoader::Load("list-add")); + +} + +NotificationsSettingsPage::~NotificationsSettingsPage() { + delete pretty_popup_; + delete ui_; +} + +void NotificationsSettingsPage::showEvent(QShowEvent*) { + UpdatePopupVisible(); +} + +void NotificationsSettingsPage::hideEvent(QHideEvent*) { + UpdatePopupVisible(); +} + +void NotificationsSettingsPage::Load() { + + QSettings s; + + s.beginGroup(OSD::kSettingsGroup); + OSD::Behaviour osd_behaviour = OSD::Behaviour(s.value("Behaviour", OSD::Native).toInt()); + switch (osd_behaviour) { + case OSD::Native: + if (OSD::SupportsNativeNotifications()) { + ui_->notifications_native->setChecked(true); + break; + } + // Fallthrough + + case OSD::Pretty: + ui_->notifications_pretty->setChecked(true); + break; + + case OSD::TrayPopup: + if (OSD::SupportsTrayPopups()) { + ui_->notifications_tray->setChecked(true); + break; + } + // Fallthrough + + case OSD::Disabled: + default: + ui_->notifications_none->setChecked(true); + break; + } + ui_->notifications_duration->setValue(s.value("Timeout", 5000).toInt() / 1000); + ui_->notifications_volume->setChecked( s.value("ShowOnVolumeChange", false).toBool()); + ui_->notifications_play_mode->setChecked( s.value("ShowOnPlayModeChange", true).toBool()); + ui_->notifications_pause->setChecked(s.value("ShowOnPausePlayback", true).toBool()); + ui_->notifications_art->setChecked(s.value("ShowArt", true).toBool()); + ui_->notifications_custom_text_enabled->setChecked(s.value("CustomTextEnabled", false).toBool()); + ui_->notifications_custom_text1->setText(s.value("CustomText1").toString()); + ui_->notifications_custom_text2->setText(s.value("CustomText2").toString()); + s.endGroup(); + +#ifdef Q_OS_DARWIN + ui_->notifications_options->setEnabled(ui_->notifications_pretty->isChecked()); +#endif + + // Pretty OSD + pretty_popup_->ReloadSettings(); + ui_->notifications_opacity->setValue(pretty_popup_->background_opacity() * 100); + + QRgb color = pretty_popup_->background_color(); + if (color == OSDPretty::kPresetBlue) + ui_->notifications_bg_preset->setCurrentIndex(0); + else if (color == OSDPretty::kPresetOrange) + ui_->notifications_bg_preset->setCurrentIndex(1); + else + ui_->notifications_bg_preset->setCurrentIndex(2); + ui_->notifications_bg_preset->setItemData(2, QColor(color), Qt::DecorationRole); + ui_->notifications_disable_duration->setChecked(pretty_popup_->disable_duration()); + UpdatePopupVisible(); +} + +void NotificationsSettingsPage::Save() { + + QSettings s; + + OSD::Behaviour osd_behaviour = OSD::Disabled; + if (ui_->notifications_none->isChecked()) osd_behaviour = OSD::Disabled; + else if (ui_->notifications_native->isChecked()) osd_behaviour = OSD::Native; + else if (ui_->notifications_tray->isChecked()) osd_behaviour = OSD::TrayPopup; + else if (ui_->notifications_pretty->isChecked()) osd_behaviour = OSD::Pretty; + + s.beginGroup(OSD::kSettingsGroup); + s.setValue("Behaviour", int(osd_behaviour)); + s.setValue("Timeout", ui_->notifications_duration->value() * 1000); + s.setValue("ShowOnVolumeChange", ui_->notifications_volume->isChecked()); + s.setValue("ShowOnPlayModeChange", ui_->notifications_play_mode->isChecked()); + s.setValue("ShowOnPausePlayback", ui_->notifications_pause->isChecked()); + s.setValue("ShowArt", ui_->notifications_art->isChecked()); + s.setValue("CustomTextEnabled", ui_->notifications_custom_text_enabled->isChecked()); + s.setValue("CustomText1", ui_->notifications_custom_text1->text()); + s.setValue("CustomText2", ui_->notifications_custom_text2->text()); + s.endGroup(); + + s.beginGroup(OSDPretty::kSettingsGroup); + s.setValue("foreground_color", pretty_popup_->foreground_color()); + s.setValue("background_color", pretty_popup_->background_color()); + s.setValue("background_opacity", pretty_popup_->background_opacity()); + s.setValue("popup_display", pretty_popup_->popup_display()); + s.setValue("popup_pos", pretty_popup_->popup_pos()); + s.setValue("font", pretty_popup_->font().toString()); + s.setValue("disable_duration", ui_->notifications_disable_duration->isChecked()); + s.endGroup(); +} + +void NotificationsSettingsPage::PrettyOpacityChanged(int value) { + pretty_popup_->set_background_opacity(qreal(value) / 100.0); +} + +void NotificationsSettingsPage::UpdatePopupVisible() { + pretty_popup_->setVisible(isVisible() && ui_->notifications_pretty->isChecked()); +} + +void NotificationsSettingsPage::PrettyColorPresetChanged(int index) { + + if (dialog()->is_loading_settings()) return; + + switch (index) { + case 0: + pretty_popup_->set_background_color(OSDPretty::kPresetBlue); + break; + + case 1: + pretty_popup_->set_background_color(OSDPretty::kPresetOrange); + break; + + case 2: + default: + ChooseBgColor(); + break; + } + +} + +void NotificationsSettingsPage::ChooseBgColor() { + + QColor color = QColorDialog::getColor(pretty_popup_->background_color(), this); + if (!color.isValid()) + return; + + pretty_popup_->set_background_color(color.rgb()); + ui_->notifications_bg_preset->setItemData(2, color, Qt::DecorationRole); + +} + +void NotificationsSettingsPage::ChooseFgColor() { + + QColor color = QColorDialog::getColor(pretty_popup_->foreground_color(), this); + if (!color.isValid()) + return; + + pretty_popup_->set_foreground_color(color.rgb()); + +} + +void NotificationsSettingsPage::ChooseFont() { + + bool ok; + QFont font = QFontDialog::getFont(&ok, pretty_popup_->font(), this); + if (ok) pretty_popup_->set_font(font); + +} + +void NotificationsSettingsPage::NotificationCustomTextChanged(bool enabled) { + + ui_->notifications_custom_text1->setEnabled(enabled); + ui_->notifications_custom_text2->setEnabled(enabled); + ui_->notifications_exp_chooser1->setEnabled(enabled); + ui_->notifications_exp_chooser2->setEnabled(enabled); + ui_->notifications_preview->setEnabled(enabled); + ui_->label_19->setEnabled(enabled); + ui_->label_20->setEnabled(enabled); + +} + +void NotificationsSettingsPage::PrepareNotificationPreview() { + + OSD::Behaviour notificationType = OSD::Disabled; + if (ui_->notifications_native->isChecked()) { + notificationType = OSD::Native; + } + else if (ui_->notifications_pretty->isChecked()) { + notificationType = OSD::Pretty; + } + else if (ui_->notifications_tray->isChecked()) { + notificationType = OSD::TrayPopup; + } + + // If user changes timeout or other options, that won't be reflected in the preview + emit NotificationPreview(notificationType, ui_->notifications_custom_text1->text(), ui_->notifications_custom_text2->text()); + +} + +void NotificationsSettingsPage::InsertVariableFirstLine(QAction* action) { + // We use action name, therefore those shouldn't be translatable + ui_->notifications_custom_text1->insert(action->text()); +} + +void NotificationsSettingsPage::InsertVariableSecondLine(QAction* action) { + // We use action name, therefore those shouldn't be translatable + ui_->notifications_custom_text2->insert(action->text()); +} + +void NotificationsSettingsPage::ShowMenuTooltip(QAction* action) { + QToolTip::showText(QCursor::pos(), action->toolTip()); +} + +void NotificationsSettingsPage::NotificationTypeChanged() { + + bool enabled = !ui_->notifications_none->isChecked(); + bool pretty = ui_->notifications_pretty->isChecked(); + + ui_->notifications_general->setEnabled(enabled); + ui_->notifications_pretty_group->setEnabled(pretty); + ui_->notifications_custom_text_group->setEnabled(enabled); + +#ifdef Q_OS_DARWIN + ui_->notifications_options->setEnabled(pretty); +#endif + ui_->notifications_duration->setEnabled(!pretty || (pretty && !ui_->notifications_disable_duration->isChecked())); + ui_->notifications_disable_duration->setEnabled(pretty); + +} diff --git a/src/settings/notificationssettingspage.h b/src/settings/notificationssettingspage.h new file mode 100644 index 00000000..8f046d0a --- /dev/null +++ b/src/settings/notificationssettingspage.h @@ -0,0 +1,66 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef NOTIFICATIONSSETTINGSPAGE_H +#define NOTIFICATIONSSETTINGSPAGE_H + +#include "config.h" + +#include "settingspage.h" + +class Ui_NotificationsSettingsPage; + +class NotificationsSettingsPage : public SettingsPage { + Q_OBJECT + +public: + NotificationsSettingsPage(SettingsDialog *dialog); + ~NotificationsSettingsPage(); + static const char *kSettingsGroup; + + void Load(); + void Save(); + +protected: + void hideEvent(QHideEvent*); + void showEvent(QShowEvent*); + +private slots: + void NotificationTypeChanged(); + void NotificationCustomTextChanged(bool enabled); + void PrepareNotificationPreview(); + void InsertVariableFirstLine(QAction *action); + void InsertVariableSecondLine(QAction *action); + void ShowMenuTooltip(QAction *action); + + void PrettyOpacityChanged(int value); + void PrettyColorPresetChanged(int index); + void ChooseBgColor(); + void ChooseFgColor(); + void ChooseFont(); + + void UpdatePopupVisible(); + +private: + Ui_NotificationsSettingsPage *ui_; + OSDPretty *pretty_popup_; +}; + +#endif // NOTIFICATIONSSETTINGSPAGE_H diff --git a/src/settings/notificationssettingspage.ui b/src/settings/notificationssettingspage.ui new file mode 100644 index 00000000..2ab566fb --- /dev/null +++ b/src/settings/notificationssettingspage.ui @@ -0,0 +1,487 @@ + + + NotificationsSettingsPage + + + + 0 + 0 + 526 + 642 + + + + Notifications + + + + + + Strawberry can show a message when the track changes. + + + + + + + Notification type + + + + + + Disabled + + + + + + + Show a native desktop notification + + + + + + + Show a pretty OSD + + + + + + + Show a popup from the system tray + + + + + + + + + + General settings + + + + + + + 0 + + + + + Popup duration + + + + + + + seconds + + + 1 + + + 20 + + + 5 + + + + + + + Disable duration + + + + + + + + + + Show a notification when I change the volume + + + + + + + Show a notification when I change the repeat/shuffle mode + + + + + + + Show a notification when I pause playback + + + + + + + Include album art in the notification + + + + + + + + + + Custom message settings + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + + 0 + + + + + Use a custom message for notifications + + + + + + + false + + + + 0 + 0 + + + + Preview + + + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + + 0 + + + 0 + + + + + false + + + + + + + false + + + + + + + + + + Summary + + + + + + + Body + + + + + + + false + + + + + + + false + + + + + + + + + + + + + + + + Pretty OSD options + + + + + + Background opacity + + + + + + + Qt::Horizontal + + + + + + + Background color + + + + + + + + Basic Blue + + + + + Strawberry Orange + + + + + Custom... + + + + + + + + Text options + + + + + + + Choose color... + + + + + + + Choose font... + + + + + + + + + + Qt::Vertical + + + + 20 + 32 + + + + + + + + %artist% + + + Add song artist tag + + + + + %album% + + + Add song album tag + + + + + %title% + + + Add song title tag + + + + + %albumartist% + + + Add song albumartist tag + + + + + %year% + + + Add song year tag + + + + + %composer% + + + Add song composer tag + + + + + %performer% + + + Add song performer tag + + + + + %grouping% + + + Add song grouping tag + + + + + %disc% + + + Add song disc tag + + + + + %track% + + + Add song track tag + + + + + %genre% + + + Add song genre tag + + + + + %length% + + + Add song length tag + + + + + %playcount% + + + Add song play count + + + + + %skipcount% + + + Add song skip count + + + + + %rating% + + + Add song rating + + + + + %score% + + + Add song auto score + + + + + %newline% + + + Add a new line if supported by the notification type + + + + + %filename% + + + Add song filename + + + + + + diff --git a/src/settings/playbacksettingspage.cpp b/src/settings/playbacksettingspage.cpp new file mode 100644 index 00000000..6c599a0c --- /dev/null +++ b/src/settings/playbacksettingspage.cpp @@ -0,0 +1,86 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "playbacksettingspage.h" +#include "ui_playbacksettingspage.h" + +#include "core/iconloader.h" +#include "settingsdialog.h" +#include "playlist/playlist.h" + +const char *PlaybackSettingsPage::kSettingsGroup = "Playback"; + +PlaybackSettingsPage::PlaybackSettingsPage(SettingsDialog *dialog) : SettingsPage(dialog), ui_(new Ui_PlaybackSettingsPage) { + + ui_->setupUi(this); + setWindowIcon(IconLoader::Load("media-play")); + + connect(ui_->fading_cross, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged())); + connect(ui_->fading_out, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged())); + connect(ui_->fading_auto, SIGNAL(toggled(bool)), SLOT(FadingOptionsChanged())); + +} + +PlaybackSettingsPage::~PlaybackSettingsPage() { + + delete ui_; + +} + +void PlaybackSettingsPage::Load() { + + QSettings s; + + s.beginGroup(kSettingsGroup); + ui_->current_glow->setChecked(s.value("glow_effect", true).toBool()); + ui_->fading_out->setChecked(s.value("FadeoutEnabled", false).toBool()); + ui_->fading_cross->setChecked(s.value("CrossfadeEnabled", false).toBool()); + ui_->fading_auto->setChecked(s.value("AutoCrossfadeEnabled", false).toBool()); + ui_->fading_duration->setValue(s.value("FadeoutDuration", 2000).toInt()); + ui_->fading_samealbum->setChecked(s.value("NoCrossfadeSameAlbum", true).toBool()); + ui_->fadeout_pause->setChecked(s.value("FadeoutPauseEnabled", false).toBool()); + ui_->fading_pause_duration->setValue(s.value("FadeoutPauseDuration", 250).toInt()); + s.endGroup(); + +} + +void PlaybackSettingsPage::Save() { + + QSettings s; + + s.beginGroup(kSettingsGroup); + s.setValue("glow_effect", ui_->current_glow->isChecked()); + s.setValue("FadeoutEnabled", ui_->fading_out->isChecked()); + s.setValue("FadeoutDuration", ui_->fading_duration->value()); + s.setValue("CrossfadeEnabled", ui_->fading_cross->isChecked()); + s.setValue("AutoCrossfadeEnabled", ui_->fading_auto->isChecked()); + s.setValue("NoCrossfadeSameAlbum", ui_->fading_samealbum->isChecked()); + s.setValue("FadeoutPauseEnabled", ui_->fadeout_pause->isChecked()); + s.setValue("FadeoutPauseDuration", ui_->fading_pause_duration->value()); + s.endGroup(); +} + +void PlaybackSettingsPage::FadingOptionsChanged() { + + ui_->fading_options->setEnabled(ui_->fading_out->isChecked() || ui_->fading_cross->isChecked() || ui_->fading_auto->isChecked()); + +} diff --git a/src/settings/playbacksettingspage.h b/src/settings/playbacksettingspage.h new file mode 100644 index 00000000..d869bd11 --- /dev/null +++ b/src/settings/playbacksettingspage.h @@ -0,0 +1,48 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYBACKSETTINGSPAGE_H +#define PLAYBACKSETTINGSPAGE_H + +#include "config.h" + +#include "settingspage.h" + +class Ui_PlaybackSettingsPage; + +class PlaybackSettingsPage : public SettingsPage { + Q_OBJECT + +public: + PlaybackSettingsPage(SettingsDialog* dialog); + ~PlaybackSettingsPage(); + static const char *kSettingsGroup; + + void Load(); + void Save(); + + private slots: + void FadingOptionsChanged(); + +private: + Ui_PlaybackSettingsPage* ui_; +}; + +#endif // PLAYBACKSETTINGSPAGE_H diff --git a/src/settings/playbacksettingspage.ui b/src/settings/playbacksettingspage.ui new file mode 100644 index 00000000..e83b4a92 --- /dev/null +++ b/src/settings/playbacksettingspage.ui @@ -0,0 +1,231 @@ + + + PlaybackSettingsPage + + + + 0 + 0 + 596 + 638 + + + + Playback + + + + + + Show a glowing animation on the current track + + + true + + + + + + + Fading + + + + + + Fade out when stopping a track + + + true + + + + + + + Cross-fade when changing tracks manually + + + true + + + + + + + Cross-fade when changing tracks automatically + + + + + + + false + + + Except between tracks on the same album or in the same CUE sheet + + + + + + + + 0 + + + + + Fading duration + + + 22 + + + + + + + ms + + + 10000 + + + 1000 + + + 2000 + + + + + + + Qt::Horizontal + + + + 257 + 20 + + + + + + + + + + + Fade out on pause / fade in on resume + + + + + + + + + Fading duration + + + 22 + + + + + + + ms + + + 10000 + + + 50 + + + 250 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 114 + + + + + + + + + StickySlider + QSlider +
widgets/stickyslider.h
+
+
+ + + + replaygain + toggled(bool) + widget + setEnabled(bool) + + + 89 + 259 + + + 143 + 285 + + + + + fading_auto + toggled(bool) + fading_samealbum + setEnabled(bool) + + + 109 + 133 + + + 113 + 153 + + + + +
diff --git a/src/settings/playlistsettingspage.cpp b/src/settings/playlistsettingspage.cpp new file mode 100644 index 00000000..b278c399 --- /dev/null +++ b/src/settings/playlistsettingspage.cpp @@ -0,0 +1,133 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include "playlistsettingspage.h" + +#include "core/mainwindow.h" +#include "core/iconloader.h" +#include "ui_playlistsettingspage.h" +#include "playlist/playlist.h" +#include "playlist/playlisttabbar.h" +#include "settings/playlistsettingspage.h" + +const char *PlaylistSettingsPage::kSettingsGroup = "Playlist"; + +namespace { + + bool LocaleAwareCompare(const QString &a, const QString &b) { + return a.localeAwareCompare(b) < 0; + } +} // namespace + +PlaylistSettingsPage::PlaylistSettingsPage(SettingsDialog* dialog) : SettingsPage(dialog), ui_(new Ui_PlaylistSettingsPage) { + + ui_->setupUi(this); + setWindowIcon(IconLoader::Load("document-new")); + + ui_->combobox_doubleclickaddmode->setItemData(0, MainWindow::AddBehaviour_Append); + ui_->combobox_doubleclickaddmode->setItemData(1, MainWindow::AddBehaviour_Load); + ui_->combobox_doubleclickaddmode->setItemData(2, MainWindow::AddBehaviour_OpenInNew); + ui_->combobox_doubleclickaddmode->setItemData(3, MainWindow::AddBehaviour_Enqueue); + + ui_->combobox_doubleclickplaymode->setItemData(0, MainWindow::PlayBehaviour_Never); + ui_->combobox_doubleclickplaymode->setItemData(1, MainWindow::PlayBehaviour_IfStopped); + ui_->combobox_doubleclickplaymode->setItemData(2, MainWindow::PlayBehaviour_Always); + + ui_->combobox_menuplaymode->setItemData(0, MainWindow::PlayBehaviour_Never); + ui_->combobox_menuplaymode->setItemData(1, MainWindow::PlayBehaviour_IfStopped); + ui_->combobox_menuplaymode->setItemData(2, MainWindow::PlayBehaviour_Always); + +} + +PlaylistSettingsPage::~PlaylistSettingsPage() { + delete ui_; +} + +void PlaylistSettingsPage::Load() { + + QSettings s; + + s.beginGroup(PlaylistSettingsPage::kSettingsGroup); + ui_->combobox_doubleclickaddmode->setCurrentIndex(ui_->combobox_doubleclickaddmode->findData(s.value("doubleclick_addmode", MainWindow::AddBehaviour_Append).toInt())); + ui_->combobox_doubleclickplaymode->setCurrentIndex(ui_->combobox_doubleclickplaymode->findData(s.value("doubleclick_playmode", MainWindow::PlayBehaviour_Never).toInt())); + ui_->combobox_menuplaymode->setCurrentIndex(ui_->combobox_menuplaymode->findData(s.value("menu_playmode", MainWindow::PlayBehaviour_Never).toInt())); + ui_->checkbox_greyoutdeleted->setChecked(s.value("greyoutdeleted", false).toBool()); + + Playlist::Path path = Playlist::Path(s.value(Playlist::kPathType, Playlist::Path_Automatic).toInt()); + switch (path) { + case Playlist::Path_Automatic: + ui_->radiobutton_automaticpath->setChecked(true); + break; + case Playlist::Path_Absolute: + ui_->radiobutton_absolutepath->setChecked(true); + break; + case Playlist::Path_Relative: + ui_->radiobutton_relativepath->setChecked(true); + break; + case Playlist::Path_Ask_User: + ui_->radiobutton_askpath->setChecked(true); + } + + ui_->checkbox_warncloseplaylist->setChecked(s.value("warn_close_playlist", true).toBool()); + ui_->checkbox_editmetadatainline->setChecked(s.value("editmetadatainline", false).toBool()); + ui_->checkbox_writemetadata->setChecked(s.value(Playlist::kWriteMetadata, false).toBool()); + + s.endGroup(); + +} + +void PlaylistSettingsPage::Save() { + + QSettings s; + + MainWindow::AddBehaviour doubleclick_addmode = MainWindow::AddBehaviour(ui_->combobox_doubleclickaddmode->itemData(ui_->combobox_doubleclickaddmode->currentIndex()).toInt()); + MainWindow::PlayBehaviour doubleclick_playmode = MainWindow::PlayBehaviour(ui_->combobox_doubleclickplaymode->itemData(ui_->combobox_doubleclickplaymode->currentIndex()).toInt()); + MainWindow::PlayBehaviour menu_playmode = MainWindow::PlayBehaviour(ui_->combobox_menuplaymode->itemData(ui_->combobox_menuplaymode->currentIndex()).toInt()); + + Playlist::Path path = Playlist::Path_Automatic; + if (ui_->radiobutton_automaticpath->isChecked()) { + path = Playlist::Path_Automatic; + } + else if (ui_->radiobutton_absolutepath->isChecked()) { + path = Playlist::Path_Absolute; + } + else if (ui_->radiobutton_relativepath->isChecked()) { + path = Playlist::Path_Relative; + } + else if (ui_->radiobutton_askpath->isChecked()) { + path = Playlist::Path_Ask_User; + } + + s.beginGroup(PlaylistSettingsPage::kSettingsGroup); + s.setValue("doubleclick_addmode", doubleclick_addmode); + s.setValue("doubleclick_playmode", doubleclick_playmode); + s.setValue("menu_playmode", menu_playmode); + s.setValue("greyoutdeleted", ui_->checkbox_greyoutdeleted->isChecked()); + s.setValue(Playlist::kPathType, static_cast(path)); + s.setValue("warn_close_playlist", ui_->checkbox_warncloseplaylist->isChecked()); + s.setValue("editmetadatainline", ui_->checkbox_editmetadatainline->isChecked()); + s.setValue(Playlist::kWriteMetadata, ui_->checkbox_writemetadata->isChecked()); + s.endGroup(); + +} diff --git a/src/settings/playlistsettingspage.h b/src/settings/playlistsettingspage.h new file mode 100644 index 00000000..e39f846c --- /dev/null +++ b/src/settings/playlistsettingspage.h @@ -0,0 +1,50 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef PLAYLISTSETTINGSPAGE_H +#define PLAYLISTSETTINGSPAGE_H + +#include "config.h" + +#include "settingspage.h" + +#include + +class Ui_PlaylistSettingsPage; + +class PlaylistSettingsPage : public SettingsPage { + Q_OBJECT + +public: + PlaylistSettingsPage(SettingsDialog* dialog); + ~PlaylistSettingsPage(); + static const char *kSettingsGroup; + + void Load(); + void Save(); + +private slots: + +private: + Ui_PlaylistSettingsPage* ui_; + +}; + +#endif // PLAYLISTSETTINGSPAGE_H diff --git a/src/settings/playlistsettingspage.ui b/src/settings/playlistsettingspage.ui new file mode 100644 index 00000000..5be24dbc --- /dev/null +++ b/src/settings/playlistsettingspage.ui @@ -0,0 +1,274 @@ + + + PlaylistSettingsPage + + + + 0 + 0 + 516 + 851 + + + + Playlist + + + + + + Warn me when closing a playlist tab + + + + + + + Grey out non existent songs in my playlists + + + + + + + Using the menu to add a song will... + + + + + + 0 + + + + Never start playing + + + + + Play if there is nothing already playing + + + + + Always start playing + + + + + + + + + + + Pressing "Previous" in player will... + + + + + + 0 + + + + Jump to previous song right away + + + + + Restart song, then jump to previous if pressed again + + + + + + + + + + + Double clicking a song will... + + + + + + 0 + + + + Append to the playlist + + + + + Replace the playlist + + + + + Open in new playlist + + + + + Add to the queue + + + + + + + + 1 + + + + Never start playing + + + + + Play if there is nothing already playing + + + + + Always start playing + + + + + + + + + + + Double clicking a song in the playlist will... + + + + + + 0 + + + + Change the currently playing song + + + + + Add to the queue + + + + + + + + + + + When saving a playlist, file paths should be + + + + + + Automatic + + + true + + + + + + + Absolute + + + + + + + Relative + + + + + + + Ask when saving + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + + + Metadata + + + + + + If activated, clicking a selected song in the playlist view will let you edit the tag value directly + + + Enable song metadata inline edition with click + + + + + + + Write metadata when saving playlists + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 5 + + + + + + + + + diff --git a/src/settings/settingsdialog.cpp b/src/settings/settingsdialog.cpp new file mode 100644 index 00000000..dcb78e64 --- /dev/null +++ b/src/settings/settingsdialog.cpp @@ -0,0 +1,267 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "settingsdialog.h" + +#include "behavioursettingspage.h" +#include "collectionsettingspage.h" +#include "backendsettingspage.h" +#include "playbacksettingspage.h" +#include "playlistsettingspage.h" +#include "shortcutssettingspage.h" +#include "transcodersettingspage.h" +#include "appearancesettingspage.h" +#include "networkproxysettingspage.h" +#include "notificationssettingspage.h" + +#include "core/application.h" +#include "core/mainwindow.h" +#include "core/player.h" +#include "core/logging.h" +#include "core/networkproxyfactory.h" +#include "core/player.h" +#include "core/iconloader.h" +#include "engine/enginebase.h" +#include "engine/gstengine.h" +#include "playlist/playlistview.h" +#include "widgets/groupediconview.h" +#include "widgets/osdpretty.h" + +#include "ui_settingsdialog.h" + + +SettingsItemDelegate::SettingsItemDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ +} + +QSize SettingsItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { + + const bool is_separator = index.data(SettingsDialog::Role_IsSeparator).toBool(); + QSize ret = QStyledItemDelegate::sizeHint(option, index); + + if (is_separator) { + ret.setHeight(ret.height() * 2); + } + + return ret; + +} + +void SettingsItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + + const bool is_separator = index.data(SettingsDialog::Role_IsSeparator).toBool(); + + if (is_separator) { + GroupedIconView::DrawHeader(painter, option.rect, option.font, option.palette, index.data().toString()); + } + else { + QStyledItemDelegate::paint(painter, option, index); + } + +} + + +SettingsDialog::SettingsDialog(Application *app, QWidget *parent) + : QDialog(parent), + app_(app), + //player_(app_->player()), + model_(app_->collection_model()->directory_model()), + //gst_engine_(qobject_cast(app_->player()->engine())), + //engine_(app_->player()->engine()), + appearance_(app_->appearance()), + ui_(new Ui_SettingsDialog), + //mui_(parent), + loading_settings_(false) { + + ui_->setupUi(this); + ui_->list->setItemDelegate(new SettingsItemDelegate(this)); + + QTreeWidgetItem *general = AddCategory(tr("General")); + + AddPage(Page_Behaviour, new BehaviourSettingsPage(this), general); + AddPage(Page_Collection, new CollectionSettingsPage(this), general); + AddPage(Page_Backend, new BackendSettingsPage(this), general); + AddPage(Page_Playback, new PlaybackSettingsPage(this), general); + AddPage(Page_Playlist, new PlaylistSettingsPage(this), general); + AddPage(Page_Proxy, new NetworkProxySettingsPage(this), general); +#ifdef HAVE_GSTREAMER + AddPage(Page_Transcoding, new TranscoderSettingsPage(this), general); +#endif + + // User interface + QTreeWidgetItem *iface = AddCategory(tr("User interface")); + AddPage(Page_GlobalShortcuts, new GlobalShortcutsSettingsPage(this), iface); + AddPage(Page_Appearance, new AppearanceSettingsPage(this), iface); + AddPage(Page_Notifications, new NotificationsSettingsPage(this), iface); + + // List box + connect(ui_->list, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), SLOT(CurrentItemChanged(QTreeWidgetItem*))); + ui_->list->setCurrentItem(pages_[Page_Behaviour].item_); + + // Make sure the list is big enough to show all the items + ui_->list->setMinimumWidth(static_cast(ui_->list)->sizeHintForColumn(0)); + + ui_->buttonBox->button(QDialogButtonBox::Cancel)->setShortcut(QKeySequence::Close); + +} + +SettingsDialog::~SettingsDialog() { + delete ui_; +} + +QTreeWidgetItem *SettingsDialog::AddCategory(const QString &name) { + + QTreeWidgetItem *item = new QTreeWidgetItem; + item->setText(0, name); + item->setData(0, Role_IsSeparator, true); + item->setFlags(Qt::ItemIsEnabled); + + ui_->list->invisibleRootItem()->addChild(item); + item->setExpanded(true); + + return item; + +} + +void SettingsDialog::AddPage(Page id, SettingsPage *page, QTreeWidgetItem *parent) { + + if (!parent) parent = ui_->list->invisibleRootItem(); + + // Connect page's signals to the settings dialog's signals + connect(page, SIGNAL(NotificationPreview(OSD::Behaviour,QString,QString)), SIGNAL(NotificationPreview(OSD::Behaviour,QString,QString))); + + // Create the list item + QTreeWidgetItem *item = new QTreeWidgetItem; + item->setText(0, page->windowTitle()); + item->setIcon(0, page->windowIcon()); + item->setData(0, Role_IsSeparator, false); + + if (!page->IsEnabled()) { + item->setFlags(Qt::NoItemFlags); + } + + parent->addChild(item); + + // Create a scroll area containing the page + QScrollArea *area = new QScrollArea; + area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + area->setWidget(page); + area->setWidgetResizable(true); + area->setFrameShape(QFrame::NoFrame); + area->setMinimumWidth(page->layout()->minimumSize().width()); + + // Add the page to the stack + ui_->stacked_widget->addWidget(area); + + // Remember where the page is + PageData data; + data.item_ = item; + data.scroll_area_ = area; + data.page_ = page; + pages_[id] = data; + +} + +void SettingsDialog::Save() { + for (const PageData &data : pages_.values()) { + data.page_->Save(); + } +} + +void SettingsDialog::accept() { + Save(); + QDialog::accept(); +} + +void SettingsDialog::reject() { + + // Notify each page that user clicks on Cancel + for (const PageData &data : pages_.values()) { + data.page_->Cancel(); + } + + QDialog::reject(); +} + +void SettingsDialog::DialogButtonClicked(QAbstractButton *button) { + + // While we only connect Apply at the moment, this might change in the future + if (ui_->buttonBox->button(QDialogButtonBox::Apply) == button) { + Save(); + } +} + +void SettingsDialog::showEvent(QShowEvent *e) { + + // Load settings + loading_settings_ = true; + for (const PageData &data : pages_.values()) { + data.page_->Load(); + } + loading_settings_ = false; + + // Resize the dialog if it's too big + const QSize available = QApplication::desktop()->availableGeometry(this).size(); + if (available.height() < height()) { + resize(width(), sizeHint().height()); + } + + QDialog::showEvent(e); + +} + +void SettingsDialog::OpenAtPage(Page page) { + + if (!pages_.contains(page)) { + return; + } + + ui_->list->setCurrentItem(pages_[page].item_); + show(); + +} + +void SettingsDialog::CurrentItemChanged(QTreeWidgetItem *item) { + + if (!(item->flags() & Qt::ItemIsSelectable)) { + return; + } + + // Set the title + ui_->title->setText("" + item->text(0) + ""); + + // Display the right page + for (const PageData &data : pages_.values()) { + if (data.item_ == item) { + ui_->stacked_widget->setCurrentWidget(data.scroll_area_); + break; + } + } + +} diff --git a/src/settings/settingsdialog.h b/src/settings/settingsdialog.h new file mode 100644 index 00000000..15cebbde --- /dev/null +++ b/src/settings/settingsdialog.h @@ -0,0 +1,135 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef SETTINGSDIALOG_H +#define SETTINGSDIALOG_H + +#include "config.h" + +#include +#include + +#include "widgets/osd.h" + +class QAbstractButton; +class QScrollArea; +class QTreeWidgetItem; + +class Application; +class Player; +class Appearance; +class GlobalShortcuts; +class CollectionDirectoryModel; +class SettingsPage; +class Ui_MainWindow; +class Ui_SettingsDialog; + +class GstEngine; + + +class SettingsItemDelegate : public QStyledItemDelegate { +public: + SettingsItemDelegate(QObject *parent); + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; +}; + + +class SettingsDialog : public QDialog { + Q_OBJECT + +public: + SettingsDialog(Application *app, QWidget *parent = nullptr); + ~SettingsDialog(); + + enum Page { + Page_Behaviour, + Page_Collection, + Page_Backend, + Page_Playback, + Page_Playlist, + Page_GlobalShortcuts, + Page_Appearance, + Page_Notifications, + Page_Proxy, + Page_Transcoding, + }; + + enum Role { + Role_IsSeparator = Qt::UserRole + }; + + void SetGlobalShortcutManager(GlobalShortcuts *manager) { manager_ = manager; } + + bool is_loading_settings() const { return loading_settings_; } + + Application *app() const { return app_; } + //Player *player() const { return player_; } + CollectionDirectoryModel *collection_directory_model() const { return model_; } + GlobalShortcuts *global_shortcuts_manager() const { return manager_; } + //const EngineBase *engine() const { return engine_; } + //const GstEngine *gst_engine() const { return gst_engine_; } + Appearance *appearance() const { return appearance_; } + + void OpenAtPage(Page page); + + // QDialog + void accept(); + void reject(); + + // QWidget + void showEvent(QShowEvent *e); + +signals: + void NotificationPreview(OSD::Behaviour, QString, QString); + +private slots: + void CurrentItemChanged(QTreeWidgetItem *item); + void DialogButtonClicked(QAbstractButton *button); + +private: + struct PageData { + QTreeWidgetItem *item_; + QScrollArea *scroll_area_; + SettingsPage *page_; + }; + + QTreeWidgetItem *AddCategory(const QString &name); + void AddPage(Page id, SettingsPage *page, QTreeWidgetItem *parent = nullptr); + + void Save(); + +private: + Application *app_; + //Player *player_; + CollectionDirectoryModel *model_; + GlobalShortcuts *manager_; + //const EngineBase *engine_; + Appearance *appearance_; + //const GstEngine *gst_engine_; + + Ui_SettingsDialog *ui_; + //Ui_MainWindow *mui_; + bool loading_settings_; + + QMap pages_; +}; + +#endif // SETTINGSDIALOG_H diff --git a/src/settings/settingsdialog.ui b/src/settings/settingsdialog.ui new file mode 100644 index 00000000..48c623a7 --- /dev/null +++ b/src/settings/settingsdialog.ui @@ -0,0 +1,151 @@ + + + SettingsDialog + + + + 0 + 0 + 827 + 768 + + + + Settings + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + + + + + 220 + 16777215 + + + + Qt::ScrollBarAlwaysOff + + + + 32 + 32 + + + + false + + + false + + + false + + + false + + + + 1 + + + + + + + + + + + + + Qt::Horizontal + + + + + + + -1 + + + + + + + Qt::Horizontal + + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + list + buttonBox + + + + + + + buttonBox + accepted() + SettingsDialog + accept() + + + 816 + 757 + + + 521 + 545 + + + + + buttonBox + rejected() + SettingsDialog + reject() + + + 816 + 757 + + + 322 + 549 + + + + + buttonBox + clicked(QAbstractButton*) + SettingsDialog + DialogButtonClicked(QAbstractButton*) + + + 20 + 20 + + + 20 + 20 + + + + + diff --git a/src/settings/settingspage.cpp b/src/settings/settingspage.cpp new file mode 100644 index 00000000..a2507484 --- /dev/null +++ b/src/settings/settingspage.cpp @@ -0,0 +1,26 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "settingsdialog.h" +#include "settingspage.h" + +SettingsPage::SettingsPage(SettingsDialog *dialog) : QWidget(dialog), dialog_(dialog) { } diff --git a/src/settings/settingspage.h b/src/settings/settingspage.h new file mode 100644 index 00000000..0bf377b8 --- /dev/null +++ b/src/settings/settingspage.h @@ -0,0 +1,57 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef SETTINGSPAGE_H +#define SETTINGSPAGE_H + +#include "config.h" + +#include + +#include "widgets/osd.h" + +class SettingsDialog; + +class SettingsPage : public QWidget { + Q_OBJECT + +public: + SettingsPage(SettingsDialog* dialog); + + // Return false to grey out the page's item in the list. + virtual bool IsEnabled() const { return true; } + + // Load is called when the dialog is shown, Save when the user clicks OK, and + // Cancel when the user clicks on Cancel + virtual void Load() = 0; + virtual void Save() = 0; + virtual void Cancel() {} + + // The dialog that this page belongs to. + SettingsDialog *dialog() const { return dialog_; } + +signals: + void NotificationPreview(OSD::Behaviour, QString, QString); + +private: + SettingsDialog *dialog_; +}; + +#endif // SETTINGSPAGE_H diff --git a/src/settings/shortcutssettingspage.cpp b/src/settings/shortcutssettingspage.cpp new file mode 100644 index 00000000..84ffc929 --- /dev/null +++ b/src/settings/shortcutssettingspage.cpp @@ -0,0 +1,201 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "shortcutssettingspage.h" +#include "ui_shortcutssettingspage.h" + +#include "globalshortcuts/globalshortcuts.h" +#include "globalshortcuts/globalshortcutgrabber.h" +#include "core/logging.h" +#include "core/utilities.h" +#include "core/iconloader.h" +#include "settings/settingsdialog.h" + +const char *GlobalShortcutsSettingsPage::kSettingsGroup = "GlobalShortcuts"; + +GlobalShortcutsSettingsPage::GlobalShortcutsSettingsPage(SettingsDialog *dialog) + : SettingsPage(dialog), + ui_(new Ui_GlobalShortcutsSettingsPage), + initialised_(false), + grabber_(new GlobalShortcutGrabber) { + ui_->setupUi(this); + ui_->shortcut_options->setEnabled(false); + ui_->list->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + setWindowIcon(IconLoader::Load("keyboard")); + + settings_.beginGroup(kSettingsGroup); + + connect(ui_->list, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), SLOT(ItemClicked(QTreeWidgetItem*))); + connect(ui_->radio_none, SIGNAL(clicked()), SLOT(NoneClicked())); + connect(ui_->radio_default, SIGNAL(clicked()), SLOT(DefaultClicked())); + connect(ui_->radio_custom, SIGNAL(clicked()), SLOT(ChangeClicked())); + connect(ui_->change, SIGNAL(clicked()), SLOT(ChangeClicked())); + connect(ui_->gnome_open, SIGNAL(clicked()), SLOT(OpenGnomeKeybindingProperties())); + +} + +GlobalShortcutsSettingsPage::~GlobalShortcutsSettingsPage() { delete ui_; } + +bool GlobalShortcutsSettingsPage::IsEnabled() const { + +#ifdef Q_OS_MAC + qLog(Debug) << Utilities::GetMacVersion(); + if (Utilities::GetMacVersion() < 6) { // Leopard and earlier. + return false; + } +#endif + + return true; + +} + +void GlobalShortcutsSettingsPage::Load() { + + GlobalShortcuts *manager = dialog()->global_shortcuts_manager(); + + if (!initialised_) { + initialised_ = true; + + connect(ui_->mac_open, SIGNAL(clicked()), manager, SLOT(ShowMacAccessibilityDialog())); + + if (!manager->IsGsdAvailable()) { + ui_->gnome_container->hide(); + } + + for (const GlobalShortcuts::Shortcut &s : manager->shortcuts().values()) { + Shortcut shortcut; + shortcut.s = s; + shortcut.key = s.action->shortcut(); + shortcut.item = new QTreeWidgetItem(ui_->list, QStringList() << s.action->text() << s.action->shortcut().toString(QKeySequence::NativeText)); + shortcut.item->setData(0, Qt::UserRole, s.id); + shortcuts_[s.id] = shortcut; + } + + ui_->list->sortItems(0, Qt::AscendingOrder); + ItemClicked(ui_->list->topLevelItem(0)); + } + + for (const Shortcut &s : shortcuts_.values()) { + SetShortcut(s.s.id, s.s.action->shortcut()); + } + + bool use_gnome = settings_.value("use_gnome", true).toBool(); + if (ui_->gnome_container->isVisibleTo(this)) { + ui_->gnome_checkbox->setChecked(use_gnome); + } + + ui_->mac_container->setVisible(!manager->IsMacAccessibilityEnabled()); +#ifdef Q_OS_DARWIN + qint32 mac_version = Utilities::GetMacVersion(); + ui_->mac_label->setVisible(mac_version < 9); + ui_->mac_label_mavericks->setVisible(mac_version >= 9); +#endif // Q_OS_DARWIN + +} + +void GlobalShortcutsSettingsPage::SetShortcut(const QString &id, const QKeySequence &key) { + + Shortcut &shortcut = shortcuts_[id]; + + shortcut.key = key; + shortcut.item->setText(1, key.toString(QKeySequence::NativeText)); + +} + +void GlobalShortcutsSettingsPage::Save() { + + for (const Shortcut &s : shortcuts_.values()) { + s.s.action->setShortcut(s.key); + s.s.shortcut->setKey(s.key); + settings_.setValue(s.s.id, s.key.toString()); + } + + settings_.setValue("use_gnome", ui_->gnome_checkbox->isChecked()); + + dialog()->global_shortcuts_manager()->ReloadSettings(); + +} + +void GlobalShortcutsSettingsPage::ItemClicked(QTreeWidgetItem *item) { + + current_id_ = item->data(0, Qt::UserRole).toString(); + Shortcut &shortcut = shortcuts_[current_id_]; + + // Enable options + ui_->shortcut_options->setEnabled(true); + ui_->shortcut_options->setTitle(tr("Shortcut for %1").arg(shortcut.s.action->text())); + + if (shortcut.key == shortcut.s.default_key) + ui_->radio_default->setChecked(true); + else if (shortcut.key.isEmpty()) + ui_->radio_none->setChecked(true); + else + ui_->radio_custom->setChecked(true); + +} + +void GlobalShortcutsSettingsPage::NoneClicked() { + SetShortcut(current_id_, QKeySequence()); +} + +void GlobalShortcutsSettingsPage::DefaultClicked() { + SetShortcut(current_id_, shortcuts_[current_id_].s.default_key); +} + +void GlobalShortcutsSettingsPage::ChangeClicked() { + + GlobalShortcuts *manager = dialog()->global_shortcuts_manager(); + manager->Unregister(); + QKeySequence key = grabber_->GetKey(shortcuts_[current_id_].s.action->text()); + manager->Register(); + + if (key.isEmpty()) return; + + // Check if this key sequence is used by any other actions + for (const QString &id : shortcuts_.keys()) { + if (shortcuts_[id].key == key) SetShortcut(id, QKeySequence()); + } + + ui_->radio_custom->setChecked(true); + SetShortcut(current_id_, key); + +} + +void GlobalShortcutsSettingsPage::OpenGnomeKeybindingProperties() { + + if (!QProcess::startDetached("gnome-keybinding-properties")) { + if (!QProcess::startDetached("gnome-control-center", QStringList() << "keyboard")) { + QMessageBox::warning(this, "Error", + tr("The \"%1\" command could not be started.") + .arg("gnome-keybinding-properties")); + } + } + +} diff --git a/src/settings/shortcutssettingspage.h b/src/settings/shortcutssettingspage.h new file mode 100644 index 00000000..df386aad --- /dev/null +++ b/src/settings/shortcutssettingspage.h @@ -0,0 +1,81 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef GLOBALSHORTCUTSSETTINGSPAGE_H +#define GLOBALSHORTCUTSSETTINGSPAGE_H + +#include "config.h" + +#include + +#include +#include + +#include "globalshortcuts/globalshortcuts.h" +#include "settings/settingspage.h" + +class QTreeWidgetItem; + +class Ui_GlobalShortcutsSettingsPage; +class GlobalShortcutGrabber; + +class GlobalShortcutsSettingsPage : public SettingsPage { + Q_OBJECT + +public: + GlobalShortcutsSettingsPage(SettingsDialog *dialog); + ~GlobalShortcutsSettingsPage(); + static const char *kSettingsGroup; + + bool IsEnabled() const; + + void Load(); + void Save(); + +private slots: + void ItemClicked(QTreeWidgetItem *); + void NoneClicked(); + void DefaultClicked(); + void ChangeClicked(); + + void OpenGnomeKeybindingProperties(); + +private: + struct Shortcut { + GlobalShortcuts::Shortcut s; + QKeySequence key; + QTreeWidgetItem *item; + }; + + void SetShortcut(const QString &id, const QKeySequence &key); + +private: + Ui_GlobalShortcutsSettingsPage *ui_; + + bool initialised_; + std::unique_ptr grabber_; + + QSettings settings_; + QMap shortcuts_; + + QString current_id_; +}; + +#endif // GLOBALSHORTCUTSSETTINGSPAGE_H diff --git a/src/settings/shortcutssettingspage.ui b/src/settings/shortcutssettingspage.ui new file mode 100644 index 00000000..7567e89a --- /dev/null +++ b/src/settings/shortcutssettingspage.ui @@ -0,0 +1,232 @@ + + + GlobalShortcutsSettingsPage + + + + 0 + 0 + 507 + 393 + + + + Shortcuts + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + + + + + 0 + + + + + + 0 + 0 + + + + Use Gnome's shortcut keys + + + + + + + Open... + + + + + + + + + + + 0 + + + 0 + + + + + + 0 + 0 + + + + You need to launch System Preferences and turn on "<span style=" font-style:italic;">Enable access for assistive devices</span>" to use global shortcuts in Strawberry. + + + true + + + + + + + + 0 + 0 + + + + You need to launch System Preferences and allow Strawberry to "<span style="font-style:italic">control your computer</span>" to use global shortcuts in Strawberry. + + + true + + + + + + + Open... + + + + + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + false + + + true + + + + Action + + + + + Shortcut + + + + + + + + true + + + Shortcut for %1 + + + + + + true + + + &None + + + true + + + + + + + De&fault + + + + + + + &Custom + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Change shortcut... + + + + + + + + + + list + radio_none + radio_default + radio_custom + change + + + + + + + gnome_checkbox + toggled(bool) + list + setDisabled(bool) + + + 63 + 25 + + + 82 + 63 + + + + + gnome_checkbox + toggled(bool) + shortcut_options + setDisabled(bool) + + + 244 + 26 + + + 122 + 298 + + + + + diff --git a/src/settings/transcodersettingspage.cpp b/src/settings/transcodersettingspage.cpp new file mode 100644 index 00000000..89c456ca --- /dev/null +++ b/src/settings/transcodersettingspage.cpp @@ -0,0 +1,56 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "transcodersettingspage.h" +#include "ui_transcodersettingspage.h" + +#include "core/iconloader.h" + +TranscoderSettingsPage::TranscoderSettingsPage(SettingsDialog* dialog) + : SettingsPage(dialog), ui_(new Ui_TranscoderSettingsPage) { + ui_->setupUi(this); + setWindowIcon(IconLoader::Load("tools-wizard")); +} + +TranscoderSettingsPage::~TranscoderSettingsPage() { + delete ui_; +} + +void TranscoderSettingsPage::Load() { + ui_->transcoding_aac->Load(); + ui_->transcoding_flac->Load(); + ui_->transcoding_mp3->Load(); + ui_->transcoding_speex->Load(); + ui_->transcoding_vorbis->Load(); + ui_->transcoding_wma->Load(); + ui_->transcoding_opus->Load(); +} + +void TranscoderSettingsPage::Save() { + ui_->transcoding_aac->Save(); + ui_->transcoding_flac->Save(); + ui_->transcoding_mp3->Save(); + ui_->transcoding_speex->Save(); + ui_->transcoding_vorbis->Save(); + ui_->transcoding_wma->Save(); + ui_->transcoding_opus->Save(); +} diff --git a/src/settings/transcodersettingspage.h b/src/settings/transcodersettingspage.h new file mode 100644 index 00000000..1f13fec3 --- /dev/null +++ b/src/settings/transcodersettingspage.h @@ -0,0 +1,46 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TRANSCODERSETTINGSPAGE_H +#define TRANSCODERSETTINGSPAGE_H + +#include "config.h" + +#include "settingspage.h" + +class Ui_TranscoderSettingsPage; + +class TranscoderSettingsPage : public SettingsPage { + Q_OBJECT + +public: + TranscoderSettingsPage(SettingsDialog* dialog); + ~TranscoderSettingsPage(); + + static const char *kSettingsGroup; + + void Load(); + void Save(); + +private: + Ui_TranscoderSettingsPage* ui_; +}; + +#endif // TRANSCODERSETTINGSPAGE_H diff --git a/src/settings/transcodersettingspage.ui b/src/settings/transcodersettingspage.ui new file mode 100644 index 00000000..5b7e0621 --- /dev/null +++ b/src/settings/transcodersettingspage.ui @@ -0,0 +1,194 @@ + + + TranscoderSettingsPage + + + + 0 + 0 + 400 + 300 + + + + Transcoding + + + + + + These settings are used in the "Transcode Music" dialog, and when converting music before copying it to a device. + + + true + + + + + + + 0 + + + + MP3 + + + + 0 + + + 0 + + + + + + + + + Vorbis + + + + 0 + + + 0 + + + + + + + + + FLAC + + + + 0 + + + 0 + + + + + + + + + Speex + + + + 0 + + + 0 + + + + + + + + + AAC + + + + 0 + + + 0 + + + + + + + + + WMA + + + + 0 + + + 0 + + + + + + + + + Opus + + + + 0 + + + 0 + + + + + + + + + + + + + TranscoderOptionsMP3 + QWidget +
transcoder/transcoderoptionsmp3.h
+ 1 +
+ + TranscoderOptionsVorbis + QWidget +
transcoder/transcoderoptionsvorbis.h
+ 1 +
+ + TranscoderOptionsSpeex + QWidget +
transcoder/transcoderoptionsspeex.h
+ 1 +
+ + TranscoderOptionsAAC + QWidget +
transcoder/transcoderoptionsaac.h
+ 1 +
+ + TranscoderOptionsFlac + QWidget +
transcoder/transcoderoptionsflac.h
+ 1 +
+ + TranscoderOptionsWma + QWidget +
transcoder/transcoderoptionswma.h
+ 1 +
+ + TranscoderOptionsOpus + QWidget +
transcoder/transcoderoptionsopus.h
+ 1 +
+
+ + +
diff --git a/src/transcoder/transcodedialog.cpp b/src/transcoder/transcodedialog.cpp new file mode 100644 index 00000000..287a21f4 --- /dev/null +++ b/src/transcoder/transcodedialog.cpp @@ -0,0 +1,356 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "transcodedialog.h" +#include "transcoder.h" +#include "transcoderoptionsdialog.h" +#include "ui_transcodedialog.h" +#include "ui_transcodelogdialog.h" +#include "core/iconloader.h" +#include "core/mainwindow.h" +#include "widgets/fileview.h" + +#include +#include +#include +#include +#include + +// winspool.h defines this :( +#ifdef AddJob +#undef AddJob +#endif + +const char *TranscodeDialog::kSettingsGroup = "Transcoder"; +const int TranscodeDialog::kProgressInterval = 500; +const int TranscodeDialog::kMaxDestinationItems = 10; + +static bool ComparePresetsByName(const TranscoderPreset &left, const TranscoderPreset &right) { + return left.name_ < right.name_; +} + +TranscodeDialog::TranscodeDialog(QWidget *parent) + : QDialog(parent), + ui_(new Ui_TranscodeDialog), + log_ui_(new Ui_TranscodeLogDialog), + log_dialog_(new QDialog(this)), + transcoder_(new Transcoder(this)), + queued_(0), + finished_success_(0), + finished_failed_(0) { + + ui_->setupUi(this); + ui_->files->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + + log_ui_->setupUi(log_dialog_); + QPushButton *clear_button = log_ui_->buttonBox->addButton(tr("Clear"), QDialogButtonBox::ResetRole); + connect(clear_button, SIGNAL(clicked()), log_ui_->log, SLOT(clear())); + + // Get presets + QList presets = Transcoder::GetAllPresets(); + qSort(presets.begin(), presets.end(), ComparePresetsByName); + for (const TranscoderPreset &preset : presets) { + ui_->format->addItem(QString("%1 (.%2)").arg(preset.name_, preset.extension_), QVariant::fromValue(preset)); + } + + // Load settings + QSettings s; + s.beginGroup(kSettingsGroup); + last_add_dir_ = s.value("last_add_dir", QDir::homePath()).toString(); + last_import_dir_ = s.value("last_import_dir", QDir::homePath()).toString(); + + QString last_output_format = s.value("last_output_format", "audio/x-vorbis").toString(); + for (int i = 0; i < ui_->format->count(); ++i) { + if (last_output_format == + ui_->format->itemData(i).value().codec_mimetype_) { + ui_->format->setCurrentIndex(i); + break; + } + } + + // Add a start button + start_button_ = ui_->button_box->addButton(tr("Start transcoding"), QDialogButtonBox::ActionRole); + cancel_button_ = ui_->button_box->button(QDialogButtonBox::Cancel); + close_button_ = ui_->button_box->button(QDialogButtonBox::Close); + + close_button_->setShortcut(QKeySequence::Close); + + // Hide elements + cancel_button_->hide(); + ui_->progress_group->hide(); + + // Connect stuff + connect(ui_->add, SIGNAL(clicked()), SLOT(Add())); + connect(ui_->import, SIGNAL(clicked()), SLOT(Import())); + connect(ui_->remove, SIGNAL(clicked()), SLOT(Remove())); + connect(start_button_, SIGNAL(clicked()), SLOT(Start())); + connect(cancel_button_, SIGNAL(clicked()), SLOT(Cancel())); + connect(close_button_, SIGNAL(clicked()), SLOT(hide())); + connect(ui_->details, SIGNAL(clicked()), log_dialog_, SLOT(show())); + connect(ui_->options, SIGNAL(clicked()), SLOT(Options())); + connect(ui_->select, SIGNAL(clicked()), SLOT(AddDestination())); + + connect(transcoder_, SIGNAL(JobComplete(QString, QString, bool)), SLOT(JobComplete(QString, QString, bool))); + connect(transcoder_, SIGNAL(LogLine(QString)), SLOT(LogLine(QString))); + connect(transcoder_, SIGNAL(AllJobsComplete()), SLOT(AllJobsComplete())); + +} + +TranscodeDialog::~TranscodeDialog() { + delete log_ui_; + delete ui_; +} + +void TranscodeDialog::SetWorking(bool working) { + + start_button_->setVisible(!working); + cancel_button_->setVisible(working); + close_button_->setVisible(!working); + ui_->input_group->setEnabled(!working); + ui_->output_group->setEnabled(!working); + ui_->progress_group->setVisible(true); + + if (working) + progress_timer_.start(kProgressInterval, this); + else + progress_timer_.stop(); + +} + +void TranscodeDialog::Start() { + + SetWorking(true); + + QAbstractItemModel *file_model = ui_->files->model(); + TranscoderPreset preset = ui_->format->itemData(ui_->format->currentIndex()).value(); + + // Add jobs to the transcoder + for (int i = 0; i < file_model->rowCount(); ++i) { + QString filename = file_model->index(i, 0).data(Qt::UserRole).toString(); + QString outfilename = GetOutputFileName(filename, preset); + transcoder_->AddJob(filename, preset, outfilename); + } + + // Set up the progressbar + ui_->progress_bar->setValue(0); + ui_->progress_bar->setMaximum(file_model->rowCount() * 100); + + // Reset the UI + queued_ = file_model->rowCount(); + finished_success_ = 0; + finished_failed_ = 0; + UpdateStatusText(); + + // Start transcoding + transcoder_->Start(); + + // Save the last output format + QSettings s; + s.beginGroup(kSettingsGroup); + s.setValue("last_output_format", preset.codec_mimetype_); + +} + +void TranscodeDialog::Cancel() { + + transcoder_->Cancel(); + SetWorking(false); + +} + +void TranscodeDialog::JobComplete(const QString &input, const QString &output, bool success) { + + (*(success ? &finished_success_ : &finished_failed_))++; + queued_--; + + UpdateStatusText(); + UpdateProgress(); + +} + +void TranscodeDialog::UpdateProgress() { + + int progress = (finished_success_ + finished_failed_) * 100; + + QMap current_jobs = transcoder_->GetProgress(); + for (float value : current_jobs.values()) { + progress += qBound(0, int(value * 100), 99); + } + + ui_->progress_bar->setValue(progress); + +} + +void TranscodeDialog::UpdateStatusText() { + + QStringList sections; + + if (queued_) { + sections << "" + tr("%n remaining", "", queued_) + ""; + } + + if (finished_success_) { + sections << "" + tr("%n finished", "", finished_success_) + ""; + } + + if (finished_failed_) { + sections << "" + tr("%n failed", "", finished_failed_) + ""; + } + + ui_->progress_text->setText(sections.join(", ")); + +} + +void TranscodeDialog::AllJobsComplete() { + SetWorking(false); +} + +void TranscodeDialog::Add() { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + QStringList filenames = QFileDialog::getOpenFileNames( + this, tr("Add files to transcode"), last_add_dir_, + QString("%1 (%2);;%3").arg(tr("Music"), FileView::kFileFilter, tr(MainWindow::kAllFilesFilterSpec))); + + if (filenames.isEmpty()) return; + + SetFilenames(filenames); + + last_add_dir_ = filenames[0]; + QSettings s; + s.beginGroup(kSettingsGroup); + s.setValue("last_add_dir", last_add_dir_); + +} + +void TranscodeDialog::Import() { + + QString path = QFileDialog::getExistingDirectory(this, tr("Open a directory to import music from"), last_import_dir_, QFileDialog::ShowDirsOnly); + + if (path.isEmpty()) return; + + QStringList filenames; + QStringList audioTypes = QString(FileView::kFileFilter).split(" ", QString::SkipEmptyParts); + QDirIterator files(path, audioTypes, QDir::Files | QDir::Readable, QDirIterator::Subdirectories); + + while (files.hasNext()) { + filenames << files.next(); + } + + SetFilenames(filenames); + + last_import_dir_ = path; + QSettings settings; + settings.beginGroup(kSettingsGroup); + settings.setValue("last_import_dir", last_import_dir_); + +} + +void TranscodeDialog::SetFilenames(const QStringList &filenames) { + + for (const QString &filename : filenames) { + QString name = filename.section('/', -1, -1); + QString path = filename.section('/', 0, -2); + + QTreeWidgetItem *item = new QTreeWidgetItem(ui_->files, QStringList() << name << path); + item->setData(0, Qt::UserRole, filename); + } + +} + +void TranscodeDialog::Remove() { qDeleteAll(ui_->files->selectedItems()); } + +void TranscodeDialog::LogLine(const QString &message) { + + QString date(QDateTime::currentDateTime().toString(Qt::TextDate)); + log_ui_->log->appendPlainText(QString("%1: %2").arg(date, message)); + +} + +void TranscodeDialog::timerEvent(QTimerEvent *e) { + + QDialog::timerEvent(e); + + if (e->timerId() == progress_timer_.timerId()) { + UpdateProgress(); + } + +} + +void TranscodeDialog::Options() { + + TranscoderPreset preset = ui_->format->itemData(ui_->format->currentIndex()).value(); + + TranscoderOptionsDialog dialog(preset.type_, this); + if (dialog.is_valid()) { + dialog.exec(); + } + +} + +// Adds a folder to the destination box. +void TranscodeDialog::AddDestination() { + + int index = ui_->destination->currentIndex(); + QString initial_dir = (!ui_->destination->itemData(index).isNull() ? ui_->destination->itemData(index).toString() : QDir::homePath()); + QString dir = QFileDialog::getExistingDirectory(this, tr("Add folder"), initial_dir); + + if (!dir.isEmpty()) { + // Keep only a finite number of items in the box. + while (ui_->destination->count() >= kMaxDestinationItems) { + ui_->destination->removeItem(1); // The oldest folder item. + } + + QIcon icon = IconLoader::Load("folder"); + QVariant data = QVariant::fromValue(dir); + // Do not insert duplicates. + int duplicate_index = ui_->destination->findData(data); + if (duplicate_index == -1) { + ui_->destination->addItem(icon, dir, data); + ui_->destination->setCurrentIndex(ui_->destination->count() - 1); + } + else { + ui_->destination->setCurrentIndex(duplicate_index); + } + } + +} + +// Returns the rightmost non-empty part of 'path'. +QString TranscodeDialog::TrimPath(const QString &path) const { + return path.section('/', -1, -1, QString::SectionSkipEmpty); +} + +QString TranscodeDialog::GetOutputFileName(const QString &input, const TranscoderPreset &preset) const { + + QString path = ui_->destination->itemData(ui_->destination->currentIndex()).toString(); + if (path.isEmpty()) { + // Keep the original path. + return input.section('.', 0, -2) + '.' + preset.extension_; + } + else { + QString file_name = TrimPath(input); + file_name = file_name.section('.', 0, -2); + return path + '/' + file_name + '.' + preset.extension_; + } +} diff --git a/src/transcoder/transcodedialog.h b/src/transcoder/transcodedialog.h new file mode 100644 index 00000000..a835ecec --- /dev/null +++ b/src/transcoder/transcodedialog.h @@ -0,0 +1,91 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TRANSCODEDIALOG_H +#define TRANSCODEDIALOG_H + +#include "config.h" + +#include +#include +#include + +class Transcoder; +class Ui_TranscodeDialog; +class Ui_TranscodeLogDialog; + +struct TranscoderPreset; + +class TranscodeDialog : public QDialog { + Q_OBJECT + + public: + TranscodeDialog(QWidget *parent = nullptr); + ~TranscodeDialog(); + + static const char *kSettingsGroup; + static const int kProgressInterval; + static const int kMaxDestinationItems; + + void SetFilenames(const QStringList &filenames); + + protected: + void timerEvent(QTimerEvent *e); + + private slots: + void Add(); + void Import(); + void Remove(); + void Start(); + void Cancel(); + void JobComplete(const QString &input, const QString &output, bool success); + void LogLine(const QString &message); + void AllJobsComplete(); + void Options(); + void AddDestination(); + + private: + void SetWorking(bool working); + void UpdateStatusText(); + void UpdateProgress(); + QString TrimPath(const QString &path) const; + QString GetOutputFileName(const QString &input, const TranscoderPreset &preset) const; + + private: + Ui_TranscodeDialog *ui_; + Ui_TranscodeLogDialog *log_ui_; + QDialog *log_dialog_; + + QBasicTimer progress_timer_; + + QPushButton *start_button_; + QPushButton *cancel_button_; + QPushButton *close_button_; + + QString last_add_dir_; + QString last_import_dir_; + + Transcoder *transcoder_; + int queued_; + int finished_success_; + int finished_failed_; +}; + +#endif // TRANSCODEDIALOG_H diff --git a/src/transcoder/transcodedialog.ui b/src/transcoder/transcodedialog.ui new file mode 100644 index 00000000..70bdbb0d --- /dev/null +++ b/src/transcoder/transcodedialog.ui @@ -0,0 +1,225 @@ + + + TranscodeDialog + + + + 0 + 0 + 499 + 448 + + + + Transcode Music + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + + + + Files to transcode + + + + + + true + + + QAbstractItemView::ExtendedSelection + + + false + + + true + + + false + + + true + + + false + + + + Filename + + + + + Directory + + + + + + + + + + Add... + + + + + + + Remove + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Add all tracks from a directory and all its subdirectories + + + Import... + + + + + + + + + + + + Output options + + + + + + Audio format + + + + + + + + 0 + 0 + + + + + + + + Options... + + + + + + + Destination + + + + + + + true + + + + 0 + 0 + + + + + Alongside the originals + + + + + + + + Select... + + + + + + + + + + Progress + + + + + + + + + 0 + 0 + + + + Qt::RichText + + + + + + + Details... + + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Close + + + + + + + files + add + remove + format + button_box + + + + diff --git a/src/transcoder/transcodelogdialog.ui b/src/transcoder/transcodelogdialog.ui new file mode 100644 index 00000000..b9c3315c --- /dev/null +++ b/src/transcoder/transcodelogdialog.ui @@ -0,0 +1,58 @@ + + + TranscodeLogDialog + + + + 0 + 0 + 676 + 358 + + + + Transcoder Log + + + + :/icons/64x64/strawberry.png:/icons/64x64/strawberry.png + + + + + + true + + + + + + + QDialogButtonBox::Close + + + + + + + + + + + buttonBox + rejected() + TranscodeLogDialog + hide() + + + 599 + 331 + + + 582 + 355 + + + + + diff --git a/src/transcoder/transcoder.cpp b/src/transcoder/transcoder.cpp new file mode 100644 index 00000000..5eb8ccbe --- /dev/null +++ b/src/transcoder/transcoder.cpp @@ -0,0 +1,587 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "transcoder.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "core/logging.h" +#include "core/signalchecker.h" +#include "core/utilities.h" + +using std::shared_ptr; + +int Transcoder::JobFinishedEvent::sEventType = -1; + + +TranscoderPreset::TranscoderPreset(Song::FileType type, const QString &name, const QString &extension, const QString &codec_mimetype, const QString &muxer_mimetype) + : type_(type), + name_(name), + extension_(extension), + codec_mimetype_(codec_mimetype), + muxer_mimetype_(muxer_mimetype) {} + + +GstElement *Transcoder::CreateElement(const QString &factory_name, GstElement *bin, const QString &name) { + + GstElement *ret = gst_element_factory_make(factory_name.toLatin1().constData(), name.isNull() ? factory_name.toLatin1().constData() : name.toLatin1().constData()); + + if (ret && bin) gst_bin_add(GST_BIN(bin), ret); + + if (!ret) { + emit LogLine(tr("Could not create the GStreamer element \"%1\" - make sure you have all the required GStreamer plugins installed").arg(factory_name)); + } + else { + SetElementProperties(factory_name, G_OBJECT(ret)); + } + + return ret; + +} + +struct SuitableElement { + + SuitableElement(const QString &name = QString(), int rank = 0) : name_(name), rank_(rank) {} + + bool operator<(const SuitableElement &other) const { + return rank_ < other.rank_; + } + + QString name_; + int rank_; + +}; + +GstElement *Transcoder::CreateElementForMimeType(const QString &element_type, const QString &mime_type, GstElement *bin) { + + if (mime_type.isEmpty()) return nullptr; + + // HACK: Force ffmux_mp4 because it doesn't set any useful src caps + if (mime_type == "audio/mp4") { + LogLine(QString("Using '%1' (rank %2)").arg("ffmux_mp4").arg(-1)); + return CreateElement("ffmux_mp4", bin); + } + + // Keep track of all the suitable elements we find and figure out which + // is the best at the end. + QList suitable_elements_; + + // The caps we're trying to find + GstCaps *target_caps = gst_caps_from_string(mime_type.toUtf8().constData()); + + GstRegistry *registry = gst_registry_get(); + GList *const features = gst_registry_get_feature_list(registry, GST_TYPE_ELEMENT_FACTORY); + + for (GList *p = features; p; p = g_list_next(p)) { + GstElementFactory *factory = GST_ELEMENT_FACTORY(p->data); + + // Is this the right type of plugin? + if (QString(gst_element_factory_get_klass(factory)).contains(element_type)) { + const GList *const templates = gst_element_factory_get_static_pad_templates(factory); + for (const GList *p = templates; p; p = g_list_next(p)) { + // Only interested in source pads + GstStaticPadTemplate *pad_template = reinterpret_cast(p->data); + if (pad_template->direction != GST_PAD_SRC) continue; + + // Does this pad support the mime type we want? + GstCaps *caps = gst_static_pad_template_get_caps(pad_template); + GstCaps *intersection = gst_caps_intersect(caps, target_caps); + + if (intersection) { + if (!gst_caps_is_empty(intersection)) { + int rank = gst_plugin_feature_get_rank(GST_PLUGIN_FEATURE(factory)); + QString name = GST_OBJECT_NAME(factory); + + if (name.startsWith("ffmux") || name.startsWith("ffenc")) + rank = -1; // ffmpeg usually sucks + + suitable_elements_ << SuitableElement(name, rank); + } + gst_caps_unref(intersection); + } + } + } + } + + gst_plugin_feature_list_free(features); + gst_caps_unref(target_caps); + + if (suitable_elements_.isEmpty()) return nullptr; + + // Sort by rank + qSort(suitable_elements_); + const SuitableElement &best = suitable_elements_.last(); + + LogLine(QString("Using '%1' (rank %2)").arg(best.name_).arg(best.rank_)); + + if (best.name_ == "lamemp3enc") { + // Special case: we need to add xingmux and id3v2mux to the pipeline when + // using lamemp3enc because it doesn't write the VBR or ID3v2 headers + // itself. + + LogLine("Adding xingmux and id3v2mux to the pipeline"); + + // Create the bin + GstElement *mp3bin = gst_bin_new("mp3bin"); + gst_bin_add(GST_BIN(bin), mp3bin); + + // Create the elements + GstElement *lame = CreateElement("lamemp3enc", mp3bin); + GstElement *xing = CreateElement("xingmux", mp3bin); + GstElement *id3v2 = CreateElement("id3v2mux", mp3bin); + + if (!lame || !xing || !id3v2) { + return nullptr; + } + + // Link the elements together + gst_element_link_many(lame, xing, id3v2, nullptr); + + // Link the bin's ghost pads to the elements on each end + GstPad *pad = gst_element_get_static_pad(lame, "sink"); + gst_element_add_pad(mp3bin, gst_ghost_pad_new("sink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + pad = gst_element_get_static_pad(id3v2, "src"); + gst_element_add_pad(mp3bin, gst_ghost_pad_new("src", pad)); + gst_object_unref(GST_OBJECT(pad)); + + return mp3bin; + } + else { + return CreateElement(best.name_, bin); + } +} + +Transcoder::JobFinishedEvent::JobFinishedEvent(JobState *state, bool success) + : QEvent(QEvent::Type(sEventType)), state_(state), success_(success) {} + +void Transcoder::JobState::PostFinished(bool success) { + + if (success) { + emit parent_->LogLine(tr("Successfully written %1").arg(QDir::toNativeSeparators(job_.output))); + } + + QCoreApplication::postEvent(parent_, new Transcoder::JobFinishedEvent(this, success)); + +} + +Transcoder::Transcoder(QObject *parent, const QString &settings_postfix) + : QObject(parent), max_threads_(QThread::idealThreadCount()), settings_postfix_(settings_postfix) { + + if (JobFinishedEvent::sEventType == -1) + JobFinishedEvent::sEventType = QEvent::registerEventType(); + + // Initialise some settings for the lamemp3enc element. + QSettings s; + s.beginGroup("Transcoder/lamemp3enc" + settings_postfix_); + + if (s.value("target").isNull()) { + s.setValue("target", 1); // 1 == bitrate + } + if (s.value("cbr").isNull()) { + s.setValue("cbr", true); + } + +} + +QList Transcoder::GetAllPresets() { + + QList ret; + ret << PresetForFileType(Song::Type_Flac); + ret << PresetForFileType(Song::Type_Mp4); + ret << PresetForFileType(Song::Type_Mpeg); + ret << PresetForFileType(Song::Type_OggVorbis); + ret << PresetForFileType(Song::Type_OggFlac); + ret << PresetForFileType(Song::Type_OggSpeex); + ret << PresetForFileType(Song::Type_Asf); + ret << PresetForFileType(Song::Type_Wav); + ret << PresetForFileType(Song::Type_OggOpus); + return ret; + +} + +TranscoderPreset Transcoder::PresetForFileType(Song::FileType type) { + + switch (type) { + case Song::Type_Flac: + return TranscoderPreset(type, tr("Flac"), "flac", "audio/x-flac"); + case Song::Type_Mp4: + return TranscoderPreset(type, tr("M4A AAC"), "mp4", "audio/mpeg, mpegversion=(int)4", "audio/mp4"); + case Song::Type_Mpeg: + return TranscoderPreset(type, tr("MP3"), "mp3", "audio/mpeg, mpegversion=(int)1, layer=(int)3"); + case Song::Type_OggVorbis: + return TranscoderPreset(type, tr("Ogg Vorbis"), "ogg", "audio/x-vorbis", "application/ogg"); + case Song::Type_OggFlac: + return TranscoderPreset(type, tr("Ogg Flac"), "ogg", "audio/x-flac", "application/ogg"); + case Song::Type_OggSpeex: + return TranscoderPreset(type, tr("Ogg Speex"), "spx", "audio/x-speex", "application/ogg"); + case Song::Type_OggOpus: + return TranscoderPreset(type, tr("Ogg Opus"), "opus", "audio/x-opus", "application/ogg"); + case Song::Type_Asf: + return TranscoderPreset(type, tr("Windows Media audio"), "wma", "audio/x-wma", "video/x-ms-asf"); + case Song::Type_Wav: + return TranscoderPreset(type, tr("Wav"), "wav", QString(), "audio/x-wav"); + default: + qLog(Warning) << "Unsupported format in PresetForFileType:" << type; + return TranscoderPreset(); + } + +} + +Song::FileType Transcoder::PickBestFormat(QList supported) { + + if (supported.isEmpty()) return Song::Type_Unknown; + + QList best_formats; + best_formats << Song::Type_Mpeg; + best_formats << Song::Type_OggVorbis; + best_formats << Song::Type_Asf; + + for (Song::FileType type : best_formats) { + if (supported.isEmpty() || supported.contains(type)) return type; + } + + return supported[0]; + +} + +void Transcoder::AddJob(const QString &input, const TranscoderPreset &preset, const QString &output) { + + Job job; + job.input = input; + job.preset = preset; + + // Use the supplied filename if there was one, otherwise take the file + // extension off the input filename and append the correct one. + if (!output.isEmpty()) + job.output = output; + else + job.output = input.section('.', 0, -2) + '.' + preset.extension_; + + // Never overwrite existing files + if (QFile::exists(job.output)) { + for (int i = 0;; ++i) { + QString new_filename = QString("%1.%2.%3").arg(job.output.section('.', 0, -2)).arg(i).arg( preset.extension_); + if (!QFile::exists(new_filename)) { + job.output = new_filename; + break; + } + } + } + + queued_jobs_ << job; + +} + +void Transcoder::AddTemporaryJob(const QString &input, const TranscoderPreset &preset) { + + Job job; + job.input = input; + job.output = Utilities::GetTemporaryFileName(); + job.preset = preset; + + queued_jobs_ << job; + +} + +void Transcoder::Start() { + + emit LogLine(tr("Transcoding %1 files using %2 threads").arg(queued_jobs_.count()).arg(max_threads())); + + forever { + StartJobStatus status = MaybeStartNextJob(); + if (status == AllThreadsBusy || status == NoMoreJobs) break; + } + +} + +Transcoder::StartJobStatus Transcoder::MaybeStartNextJob() { + + if (current_jobs_.count() >= max_threads()) return AllThreadsBusy; + if (queued_jobs_.isEmpty()) { + if (current_jobs_.isEmpty()) { + emit AllJobsComplete(); + } + + return NoMoreJobs; + } + + Job job = queued_jobs_.takeFirst(); + if (StartJob(job)) { + return StartedSuccessfully; + } + + emit JobComplete(job.input, job.output, false); + return FailedToStart; + +} + +void Transcoder::NewPadCallback(GstElement*, GstPad *pad, gpointer data) { + + JobState *state = reinterpret_cast(data); + GstPad *const audiopad = gst_element_get_static_pad(state->convert_element_, "sink"); + + if (GST_PAD_IS_LINKED(audiopad)) { + qLog(Debug) << "audiopad is already linked, unlinking old pad"; + gst_pad_unlink(audiopad, GST_PAD_PEER(audiopad)); + } + + gst_pad_link(pad, audiopad); + gst_object_unref(audiopad); + +} + +GstBusSyncReply Transcoder::BusCallbackSync(GstBus*, GstMessage *msg, gpointer data) { + + JobState *state = reinterpret_cast(data); + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: + state->PostFinished(true); + break; + + case GST_MESSAGE_ERROR: + state->ReportError(msg); + state->PostFinished(false); + break; + + default: + break; + } + return GST_BUS_PASS; + +} + +void Transcoder::JobState::ReportError(GstMessage *msg) { + + GError *error; + gchar *debugs; + + gst_message_parse_error(msg, &error, &debugs); + QString message = QString::fromLocal8Bit(error->message); + + g_error_free(error); + free(debugs); + + emit parent_->LogLine(tr("Error processing %1: %2").arg(QDir::toNativeSeparators(job_.input), message)); + +} + +bool Transcoder::StartJob(const Job &job) { + + shared_ptr state(new JobState(job, this)); + + emit LogLine(tr("Starting %1").arg(QDir::toNativeSeparators(job.input))); + + // Create the pipeline. + // This should be a scoped_ptr, but scoped_ptr doesn't support custom + // destructors. + state->pipeline_ = gst_pipeline_new("pipeline"); + if (!state->pipeline_) return false; + + // Create all the elements + GstElement *src = CreateElement("filesrc", state->pipeline_); + GstElement *decode = CreateElement("decodebin", state->pipeline_); + GstElement *convert = CreateElement("audioconvert", state->pipeline_); + GstElement *resample = CreateElement("audioresample", state->pipeline_); + GstElement *codec = CreateElementForMimeType("Codec/Encoder/Audio", job.preset.codec_mimetype_, state->pipeline_); + GstElement *muxer = CreateElementForMimeType("Codec/Muxer", job.preset.muxer_mimetype_, state->pipeline_); + GstElement *sink = CreateElement("filesink", state->pipeline_); + + if (!src || !decode || !convert || !sink) return false; + + if (!codec && !job.preset.codec_mimetype_.isEmpty()) { + LogLine(tr("Couldn't find an encoder for %1, check you have the correct GStreamer plugins installed").arg(job.preset.codec_mimetype_)); + return false; + } + + if (!muxer && !job.preset.muxer_mimetype_.isEmpty()) { + LogLine(tr("Couldn't find a muxer for %1, check you have the correct GStreamer plugins installed").arg(job.preset.muxer_mimetype_)); + return false; + } + + // Join them together + gst_element_link(src, decode); + if (codec && muxer) gst_element_link_many(convert, resample, codec, muxer, sink, nullptr); + else if (codec) gst_element_link_many(convert, resample, codec, sink, nullptr); + else if (muxer) gst_element_link_many(convert, resample, muxer, sink, nullptr); + + // Set properties + g_object_set(src, "location", job.input.toUtf8().constData(), nullptr); + g_object_set(sink, "location", job.output.toUtf8().constData(), nullptr); + + // Set callbacks + state->convert_element_ = convert; + + CHECKED_GCONNECT(decode, "pad-added", &NewPadCallback, state.get()); + gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(state->pipeline_)), BusCallbackSync, state.get(), nullptr); + + // Start the pipeline + gst_element_set_state(state->pipeline_, GST_STATE_PLAYING); + + // GStreamer now transcodes in another thread, so we can return now and do + // something else. Keep the JobState object around. It'll post an event + // to our event loop when it finishes. + current_jobs_ << state; + + return true; + +} + +Transcoder::JobState::~JobState() { + + if (pipeline_) { + gst_element_set_state(pipeline_, GST_STATE_NULL); + gst_object_unref(pipeline_); + } + +} + +bool Transcoder::event(QEvent *e) { + + if (e->type() == JobFinishedEvent::sEventType) { + JobFinishedEvent *finished_event = static_cast(e); + + // Find this job in the list + JobStateList::iterator it = current_jobs_.begin(); + while (it != current_jobs_.end()) { + if (it->get() == finished_event->state_) break; + ++it; + } + if (it == current_jobs_.end()) { + // Couldn't find it, maybe GStreamer gave us an event after we'd destroyed + // the pipeline? + return true; + } + + QString input = (*it)->job_.input; + QString output = (*it)->job_.output; + + // Remove event handlers from the gstreamer pipeline so they don't get + // called after the pipeline is shutting down + gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(finished_event->state_->pipeline_)), nullptr, nullptr, nullptr); + + // Remove it from the list - this will also destroy the GStreamer pipeline + current_jobs_.erase(it); + + // Emit the finished signal + emit JobComplete(input, output, finished_event->success_); + + // Start some more jobs + MaybeStartNextJob(); + + return true; + } + + return QObject::event(e); + +} + +void Transcoder::Cancel() { + + // Remove all pending jobs + queued_jobs_.clear(); + + // Stop the running ones + JobStateList::iterator it = current_jobs_.begin(); + while (it != current_jobs_.end()) { + shared_ptr state(*it); + + // Remove event handlers from the gstreamer pipeline so they don't get + // called after the pipeline is shutting down + gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(state->pipeline_)), nullptr, nullptr, nullptr); + + // Stop the pipeline + if (gst_element_set_state(state->pipeline_, GST_STATE_NULL) == GST_STATE_CHANGE_ASYNC) { + // Wait for it to finish stopping... + gst_element_get_state(state->pipeline_, nullptr, nullptr, GST_CLOCK_TIME_NONE); + } + + // Remove the job, this destroys the GStreamer pipeline too + it = current_jobs_.erase(it); + } + +} + +QMap Transcoder::GetProgress() const { + + QMap ret; + + for (const auto &state : current_jobs_) { + if (!state->pipeline_) continue; + + gint64 position = 0; + gint64 duration = 0; + + gst_element_query_position(state->pipeline_, GST_FORMAT_TIME, &position); + gst_element_query_duration(state->pipeline_, GST_FORMAT_TIME, &duration); + + ret[state->job_.input] = float(position) / duration; + } + + return ret; + +} + +void Transcoder::SetElementProperties(const QString &name, GObject *object) { + + QSettings s; + s.beginGroup("Transcoder/" + name + settings_postfix_); + + guint properties_count = 0; + GParamSpec **properties = g_object_class_list_properties( G_OBJECT_GET_CLASS(object), &properties_count); + + for (int i = 0; i < properties_count; ++i) { + GParamSpec *property = properties[i]; + + const QVariant value = s.value(property->name); + if (value.isNull()) continue; + + LogLine(QString("Setting %1 property: %2 = %3").arg(name, property->name, value.toString())); + + switch (property->value_type) { + case G_TYPE_DOUBLE: + g_object_set(object, property->name, value.toDouble(), nullptr); + break; + case G_TYPE_FLOAT: + g_object_set(object, property->name, value.toFloat(), nullptr); + break; + case G_TYPE_BOOLEAN: + g_object_set(object, property->name, value.toInt(), nullptr); + break; + case G_TYPE_INT: + default: + g_object_set(object, property->name, value.toInt(), nullptr); + break; + } + } + + g_free(properties); + +} diff --git a/src/transcoder/transcoder.h b/src/transcoder/transcoder.h new file mode 100644 index 00000000..72e71b65 --- /dev/null +++ b/src/transcoder/transcoder.h @@ -0,0 +1,144 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TRANSCODER_H +#define TRANSCODER_H + +#include "config.h" + +#include + +#include + +#include +#include +#include +#include + +#include "core/song.h" + +struct TranscoderPreset { + TranscoderPreset() : type_(Song::Type_Unknown) {} + TranscoderPreset(Song::FileType type, const QString &name, const QString &extension, const QString &codec_mimetype, const QString &muxer_mimetype_ = QString()); + + Song::FileType type_; + QString name_; + QString extension_; + QString codec_mimetype_; + QString muxer_mimetype_; +}; +Q_DECLARE_METATYPE(TranscoderPreset); + +class Transcoder : public QObject { + Q_OBJECT + + public: + Transcoder(QObject *parent = nullptr, const QString &settings_postfix = ""); + + static TranscoderPreset PresetForFileType(Song::FileType type); + static QList GetAllPresets(); + static Song::FileType PickBestFormat(QList supported); + + int max_threads() const { return max_threads_; } + void set_max_threads(int count) { max_threads_ = count; } + + void AddJob(const QString &input, const TranscoderPreset &preset, const QString &output = QString()); + void AddTemporaryJob(const QString &input, const TranscoderPreset &preset); + + QMap GetProgress() const; + int QueuedJobsCount() const { return queued_jobs_.count(); } + + public slots: + void Start(); + void Cancel(); + +signals: + void JobComplete(const QString &input, const QString &output, bool success); + void LogLine(const QString &message); + void AllJobsComplete(); + + protected: + bool event(QEvent *e); + + private: + // The description of a file to transcode - lives in the main thread. + struct Job { + QString input; + QString output; + TranscoderPreset preset; + }; + + // State held by a job and shared across gstreamer callbacks - lives in the + // job's thread. + struct JobState { + JobState(const Job &job, Transcoder *parent) + : job_(job), + parent_(parent), + pipeline_(nullptr), + convert_element_(nullptr) {} + ~JobState(); + + void PostFinished(bool success); + void ReportError(GstMessage *msg); + + Job job_; + Transcoder *parent_; + GstElement *pipeline_; + GstElement *convert_element_; + }; + + // Event passed from a GStreamer callback to the Transcoder when a job + // finishes. + struct JobFinishedEvent : public QEvent { + JobFinishedEvent(JobState *state, bool success); + + static int sEventType; + + JobState *state_; + bool success_; + }; + + enum StartJobStatus { + StartedSuccessfully, + FailedToStart, + NoMoreJobs, + AllThreadsBusy, + }; + + StartJobStatus MaybeStartNextJob(); + bool StartJob(const Job &job); + + GstElement *CreateElement(const QString &factory_name, GstElement *bin = nullptr, const QString &name = QString()); + GstElement *CreateElementForMimeType(const QString &element_type, const QString &mime_type, GstElement *bin = nullptr); + void SetElementProperties(const QString &name, GObject *element); + + static void NewPadCallback(GstElement*, GstPad *pad, gpointer data); + static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage *msg, gpointer data); + + private: + typedef QList> JobStateList; + + int max_threads_; + QList queued_jobs_; + JobStateList current_jobs_; + QString settings_postfix_; +}; + +#endif // TRANSCODER_H diff --git a/src/transcoder/transcoderoptionsaac.cpp b/src/transcoder/transcoderoptionsaac.cpp new file mode 100644 index 00000000..7ffc64b9 --- /dev/null +++ b/src/transcoder/transcoderoptionsaac.cpp @@ -0,0 +1,58 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "transcoderoptionsaac.h" +#include "ui_transcoderoptionsaac.h" + +#include + +const char *TranscoderOptionsAAC::kSettingsGroup = "Transcoder/faac"; + +TranscoderOptionsAAC::TranscoderOptionsAAC(QWidget* parent) : TranscoderOptionsInterface(parent), ui_(new Ui_TranscoderOptionsAAC) { + ui_->setupUi(this); +} + +TranscoderOptionsAAC::~TranscoderOptionsAAC() { + delete ui_; +} + +void TranscoderOptionsAAC::Load() { + QSettings s; + s.beginGroup(kSettingsGroup + settings_postfix_); + + ui_->bitrate_slider->setValue(s.value("bitrate", 128000).toInt() / 1000); + ui_->profile->setCurrentIndex(s.value("profile", 2).toInt() - 1); + ui_->tns->setChecked(s.value("tns", false).toBool()); + ui_->midside->setChecked(s.value("midside", true).toBool()); + ui_->shortctl->setCurrentIndex(s.value("shortctl", 0).toInt()); +} + +void TranscoderOptionsAAC::Save() { + QSettings s; + s.beginGroup(kSettingsGroup + settings_postfix_); + + s.setValue("bitrate", ui_->bitrate_slider->value() * 1000); + s.setValue("profile", ui_->profile->currentIndex() + 1); + s.setValue("tns", ui_->tns->isChecked()); + s.setValue("midside", ui_->midside->isChecked()); + s.setValue("shortctl", ui_->shortctl->currentIndex()); +} diff --git a/src/transcoder/transcoderoptionsaac.h b/src/transcoder/transcoderoptionsaac.h new file mode 100644 index 00000000..bf55fa08 --- /dev/null +++ b/src/transcoder/transcoderoptionsaac.h @@ -0,0 +1,44 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TRANSCODEROPTIONSAAC_H +#define TRANSCODEROPTIONSAAC_H + +#include "config.h" + +#include "transcoderoptionsinterface.h" + +class Ui_TranscoderOptionsAAC; + +class TranscoderOptionsAAC : public TranscoderOptionsInterface { + public: + TranscoderOptionsAAC(QWidget *parent = nullptr); + ~TranscoderOptionsAAC(); + + void Load(); + void Save(); + +private: + static const char *kSettingsGroup; + + Ui_TranscoderOptionsAAC *ui_; +}; + +#endif // TRANSCODEROPTIONSAAC_H diff --git a/src/transcoder/transcoderoptionsaac.ui b/src/transcoder/transcoderoptionsaac.ui new file mode 100644 index 00000000..9463d276 --- /dev/null +++ b/src/transcoder/transcoderoptionsaac.ui @@ -0,0 +1,185 @@ + + + TranscoderOptionsAAC + + + + 0 + 0 + 480 + 344 + + + + Form + + + + + + Bitrate + + + + + + + + + 8 + + + 320 + + + 128 + + + Qt::Horizontal + + + + + + + kbps + + + 8 + + + 320 + + + 8 + + + 128 + + + + + + + + + Profile + + + + + + + 1 + + + + Main profile (MAIN) + + + + + Low complexity profile (LC) + + + + + Scalable sampling rate profile (SSR) + + + + + Long term prediction profile (LTP) + + + + + + + + Use temporal noise shaping + + + + + + + Allow mid/side encoding + + + true + + + + + + + Block type + + + + + + + + Normal block type + + + + + No short blocks + + + + + No long blocks + + + + + + + + bitrate_slider + bitrate_spinbox + profile + shortctl + tns + midside + + + + + bitrate_slider + valueChanged(int) + bitrate_spinbox + setValue(int) + + + 170 + 29 + + + 445 + 24 + + + + + bitrate_spinbox + valueChanged(int) + bitrate_slider + setValue(int) + + + 407 + 18 + + + 191 + 29 + + + + + diff --git a/src/transcoder/transcoderoptionsdialog.cpp b/src/transcoder/transcoderoptionsdialog.cpp new file mode 100644 index 00000000..e60ac649 --- /dev/null +++ b/src/transcoder/transcoderoptionsdialog.cpp @@ -0,0 +1,81 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "transcoderoptionsaac.h" +#include "transcoderoptionsdialog.h" +#include "transcoderoptionsflac.h" +#include "transcoderoptionsmp3.h" +#include "transcoderoptionsspeex.h" +#include "transcoderoptionsvorbis.h" +#include "transcoderoptionsopus.h" +#include "transcoderoptionswma.h" +#include "ui_transcoderoptionsdialog.h" + +TranscoderOptionsDialog::TranscoderOptionsDialog(Song::FileType type, QWidget *parent) + : QDialog(parent), ui_(new Ui_TranscoderOptionsDialog), options_(nullptr) { + + ui_->setupUi(this); + + switch (type) { + case Song::Type_Flac: + case Song::Type_OggFlac: options_ = new TranscoderOptionsFlac(this); break; + case Song::Type_Mp4: options_ = new TranscoderOptionsAAC(this); break; + case Song::Type_Mpeg: options_ = new TranscoderOptionsMP3(this); break; + case Song::Type_OggVorbis: options_ = new TranscoderOptionsVorbis(this); break; + case Song::Type_OggOpus: options_ = new TranscoderOptionsOpus(this); break; + case Song::Type_OggSpeex: options_ = new TranscoderOptionsSpeex(this); break; + case Song::Type_Asf: options_ = new TranscoderOptionsWma(this); break; + default: + break; + } + + if (options_) { + setWindowTitle(windowTitle() + " - " + Song::TextForFiletype(type)); + options_->layout()->setContentsMargins(0, 0, 0, 0); + ui_->verticalLayout->insertWidget(0, options_); + resize(width(), minimumHeight()); + } + +} + +TranscoderOptionsDialog::~TranscoderOptionsDialog() { + delete ui_; +} + +void TranscoderOptionsDialog::showEvent(QShowEvent *e) { + if (options_) { + options_->Load(); + } +} + +void TranscoderOptionsDialog::accept() { + if (options_) { + options_->Save(); + } + QDialog::accept(); +} + +void TranscoderOptionsDialog::set_settings_postfix(const QString &settings_postfix) { + if (options_) { + options_->settings_postfix_ = settings_postfix; + } +} diff --git a/src/transcoder/transcoderoptionsdialog.h b/src/transcoder/transcoderoptionsdialog.h new file mode 100644 index 00000000..0205dd45 --- /dev/null +++ b/src/transcoder/transcoderoptionsdialog.h @@ -0,0 +1,54 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TRANSCODEROPTIONSDIALOG_H +#define TRANSCODEROPTIONSDIALOG_H + +#include "config.h" + +#include + +#include "transcoderoptionsinterface.h" +#include "core/song.h" + +class Ui_TranscoderOptionsDialog; + +class TranscoderOptionsDialog : public QDialog { + Q_OBJECT + + public: + TranscoderOptionsDialog(Song::FileType type, QWidget *parent = nullptr); + ~TranscoderOptionsDialog(); + + bool is_valid() const { return options_; } + + void accept(); + + void set_settings_postfix(const QString &settings_postfix); + + protected: + void showEvent(QShowEvent *e); + +private: + Ui_TranscoderOptionsDialog *ui_; + TranscoderOptionsInterface *options_; +}; + +#endif // TRANSCODEROPTIONSDIALOG_H diff --git a/src/transcoder/transcoderoptionsdialog.ui b/src/transcoder/transcoderoptionsdialog.ui new file mode 100644 index 00000000..9bc4844a --- /dev/null +++ b/src/transcoder/transcoderoptionsdialog.ui @@ -0,0 +1,64 @@ + + + TranscoderOptionsDialog + + + + 0 + 0 + 400 + 300 + + + + Transcoding options + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + TranscoderOptionsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + TranscoderOptionsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/transcoder/transcoderoptionsflac.cpp b/src/transcoder/transcoderoptionsflac.cpp new file mode 100644 index 00000000..2dc06011 --- /dev/null +++ b/src/transcoder/transcoderoptionsflac.cpp @@ -0,0 +1,51 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "transcoderoptionsflac.h" +#include "ui_transcoderoptionsflac.h" + +#include + +const char *TranscoderOptionsFlac::kSettingsGroup = "Transcoder/flacenc"; + +TranscoderOptionsFlac::TranscoderOptionsFlac(QWidget *parent) + : TranscoderOptionsInterface(parent), ui_(new Ui_TranscoderOptionsFlac) { + ui_->setupUi(this); +} + +TranscoderOptionsFlac::~TranscoderOptionsFlac() { + delete ui_; +} + +void TranscoderOptionsFlac::Load() { + QSettings s; + s.beginGroup(kSettingsGroup + settings_postfix_); + + ui_->quality->setValue(s.value("quality", 5).toInt()); +} + +void TranscoderOptionsFlac::Save() { + QSettings s; + s.beginGroup(kSettingsGroup + settings_postfix_); + + s.setValue("quality", ui_->quality->value()); +} diff --git a/src/transcoder/transcoderoptionsflac.h b/src/transcoder/transcoderoptionsflac.h new file mode 100644 index 00000000..4ee9e200 --- /dev/null +++ b/src/transcoder/transcoderoptionsflac.h @@ -0,0 +1,44 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TRANSCODEROPTIONSFLAC_H +#define TRANSCODEROPTIONSFLAC_H + +#include "config.h" + +#include "transcoderoptionsinterface.h" + +class Ui_TranscoderOptionsFlac; + +class TranscoderOptionsFlac : public TranscoderOptionsInterface { + public: + TranscoderOptionsFlac(QWidget *parent = nullptr); + ~TranscoderOptionsFlac(); + + void Load(); + void Save(); + +private: + static const char *kSettingsGroup; + + Ui_TranscoderOptionsFlac *ui_; +}; + +#endif // TRANSCODEROPTIONSFLAC_H diff --git a/src/transcoder/transcoderoptionsflac.ui b/src/transcoder/transcoderoptionsflac.ui new file mode 100644 index 00000000..8375ff62 --- /dev/null +++ b/src/transcoder/transcoderoptionsflac.ui @@ -0,0 +1,62 @@ + + + TranscoderOptionsFlac + + + + 0 + 0 + 400 + 102 + + + + Form + + + + + + Quality + + + + + + + + + Fast + + + + + + + 9 + + + 5 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + + + + + Best + + + + + + + + + + diff --git a/src/transcoder/transcoderoptionsinterface.h b/src/transcoder/transcoderoptionsinterface.h new file mode 100644 index 00000000..21492612 --- /dev/null +++ b/src/transcoder/transcoderoptionsinterface.h @@ -0,0 +1,39 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TRANSCODEROPTIONSINTERFACE_H +#define TRANSCODEROPTIONSINTERFACE_H + +#include "config.h" + +#include + +class TranscoderOptionsInterface : public QWidget { +public: + TranscoderOptionsInterface(QWidget *parent) : QWidget(parent) {} + virtual ~TranscoderOptionsInterface() {} + + virtual void Load() = 0; + virtual void Save() = 0; + + QString settings_postfix_; +}; + +#endif // TRANSCODEROPTIONSINTERFACE_H diff --git a/src/transcoder/transcoderoptionsmp3.cpp b/src/transcoder/transcoderoptionsmp3.cpp new file mode 100644 index 00000000..56aaef44 --- /dev/null +++ b/src/transcoder/transcoderoptionsmp3.cpp @@ -0,0 +1,82 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "transcoderoptionsmp3.h" +#include "ui_transcoderoptionsmp3.h" + +#include + +const char* TranscoderOptionsMP3::kSettingsGroup = "Transcoder/lamemp3enc"; + +TranscoderOptionsMP3::TranscoderOptionsMP3(QWidget *parent) + : TranscoderOptionsInterface(parent), ui_(new Ui_TranscoderOptionsMP3) { + ui_->setupUi(this); + + connect(ui_->quality_slider, SIGNAL(valueChanged(int)), SLOT(QualitySliderChanged(int))); + connect(ui_->quality_spinbox, SIGNAL(valueChanged(double)), SLOT(QualitySpinboxChanged(double))); + +} + +TranscoderOptionsMP3::~TranscoderOptionsMP3() { + delete ui_; +} + +void TranscoderOptionsMP3::Load() { + + QSettings s; + s.beginGroup(kSettingsGroup + settings_postfix_); + + if (s.value("target", 1).toInt() == 0) { + ui_->target_quality->setChecked(true); + } else { + ui_->target_bitrate->setChecked(true); + } + + ui_->quality_spinbox->setValue(s.value("quality", 4.0).toFloat()); + ui_->bitrate_slider->setValue(s.value("bitrate", 128).toInt()); + ui_->cbr->setChecked(s.value("cbr", true).toBool()); + ui_->encoding_engine_quality->setCurrentIndex(s.value("encoding-engine-quality", 1).toInt()); + ui_->mono->setChecked(s.value("mono", false).toBool()); + +} + +void TranscoderOptionsMP3::Save() { + + QSettings s; + s.beginGroup(kSettingsGroup + settings_postfix_); + + s.setValue("target", ui_->target_quality->isChecked() ? 0 : 1); + s.setValue("quality", ui_->quality_spinbox->value()); + s.setValue("bitrate", ui_->bitrate_slider->value()); + s.setValue("cbr", ui_->cbr->isChecked()); + s.setValue("encoding-engine-quality", ui_->encoding_engine_quality->currentIndex()); + s.setValue("mono", ui_->mono->isChecked()); + +} + +void TranscoderOptionsMP3::QualitySliderChanged(int value) { + ui_->quality_spinbox->setValue(float(value) / 100); +} + +void TranscoderOptionsMP3::QualitySpinboxChanged(double value) { + ui_->quality_slider->setValue(value * 100); +} diff --git a/src/transcoder/transcoderoptionsmp3.h b/src/transcoder/transcoderoptionsmp3.h new file mode 100644 index 00000000..240bcaf3 --- /dev/null +++ b/src/transcoder/transcoderoptionsmp3.h @@ -0,0 +1,50 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TRANSCODEROPTIONSMP3_H +#define TRANSCODEROPTIONSMP3_H + +#include "config.h" + +#include "transcoderoptionsinterface.h" + +class Ui_TranscoderOptionsMP3; + +class TranscoderOptionsMP3 : public TranscoderOptionsInterface { + Q_OBJECT + + public: + TranscoderOptionsMP3(QWidget *parent = nullptr); + ~TranscoderOptionsMP3(); + + void Load(); + void Save(); + +private slots: + void QualitySliderChanged(int value); + void QualitySpinboxChanged(double value); + +private: + static const char* kSettingsGroup; + + Ui_TranscoderOptionsMP3 *ui_; +}; + +#endif // TRANSCODEROPTIONSMP3_H diff --git a/src/transcoder/transcoderoptionsmp3.ui b/src/transcoder/transcoderoptionsmp3.ui new file mode 100644 index 00000000..a6eb09b5 --- /dev/null +++ b/src/transcoder/transcoderoptionsmp3.ui @@ -0,0 +1,291 @@ + + + TranscoderOptionsMP3 + + + + 0 + 0 + 557 + 486 + + + + Form + + + + + + Optimize for quality + + + false + + + + + + + false + + + + 32 + + + 0 + + + 0 + + + 0 + + + + + Quality + + + + + + + + + 999 + + + 400 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 100 + + + + + + + 9.990000000000000 + + + 4.000000000000000 + + + + + + + + + + + + Optimize for bitrate + + + true + + + + + + + + 32 + + + 0 + + + 0 + + + 0 + + + + + Bitrate + + + + + + + + + 8 + + + 320 + + + 128 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 32 + + + + + + + kbps + + + 8 + + + 320 + + + 8 + + + 128 + + + + + + + + + Constant bitrate + + + + + + + + + + Encoding engine quality + + + + + + + 1 + + + + Fast + + + + + Standard + + + + + High + + + + + + + + Force mono encoding + + + + + + + target_quality + quality_slider + quality_spinbox + target_bitrate + bitrate_slider + bitrate_spinbox + cbr + encoding_engine_quality + mono + + + + + bitrate_slider + valueChanged(int) + bitrate_spinbox + setValue(int) + + + 191 + 109 + + + 492 + 115 + + + + + bitrate_spinbox + valueChanged(int) + bitrate_slider + setValue(int) + + + 485 + 115 + + + 182 + 113 + + + + + target_quality + toggled(bool) + widget + setEnabled(bool) + + + 65 + 23 + + + 20 + 49 + + + + + target_bitrate + toggled(bool) + widget_2 + setEnabled(bool) + + + 46 + 87 + + + 22 + 122 + + + + + diff --git a/src/transcoder/transcoderoptionsopus.cpp b/src/transcoder/transcoderoptionsopus.cpp new file mode 100644 index 00000000..860aa934 --- /dev/null +++ b/src/transcoder/transcoderoptionsopus.cpp @@ -0,0 +1,53 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2013, Martin Brodbeck + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "transcoderoptionsopus.h" +#include "ui_transcoderoptionsopus.h" + +#include + +// TODO: Add more options than only bitrate as soon as gst doesn't crash +// anymore while using the cbr parmameter (like cbr=false) + +const char* TranscoderOptionsOpus::kSettingsGroup = "Transcoder/opusenc"; + +TranscoderOptionsOpus::TranscoderOptionsOpus(QWidget *parent) : TranscoderOptionsInterface(parent), ui_(new Ui_TranscoderOptionsOpus) { + ui_->setupUi(this); +} + +TranscoderOptionsOpus::~TranscoderOptionsOpus() { + delete ui_; +} + +void TranscoderOptionsOpus::Load() { + QSettings s; + s.beginGroup(kSettingsGroup + settings_postfix_); + + ui_->bitrate_slider->setValue(s.value("bitrate", 128000).toInt() / 1000); +} + +void TranscoderOptionsOpus::Save() { + QSettings s; + s.beginGroup(kSettingsGroup + settings_postfix_); + + s.setValue("bitrate", ui_->bitrate_slider->value() * 1000); +} diff --git a/src/transcoder/transcoderoptionsopus.h b/src/transcoder/transcoderoptionsopus.h new file mode 100644 index 00000000..07e352e0 --- /dev/null +++ b/src/transcoder/transcoderoptionsopus.h @@ -0,0 +1,44 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2013, Martin Brodbeck + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TRANSCODEROPTIONSOPUS_H +#define TRANSCODEROPTIONSOPUS_H + +#include "config.h" + +#include "transcoderoptionsinterface.h" + +class Ui_TranscoderOptionsOpus; + +class TranscoderOptionsOpus : public TranscoderOptionsInterface { + public: + TranscoderOptionsOpus(QWidget *parent = nullptr); + ~TranscoderOptionsOpus(); + + void Load(); + void Save(); + +private: + static const char* kSettingsGroup; + + Ui_TranscoderOptionsOpus* ui_; +}; + +#endif // TRANSCODEROPTIONSOPUS_H diff --git a/src/transcoder/transcoderoptionsopus.ui b/src/transcoder/transcoderoptionsopus.ui new file mode 100644 index 00000000..dd6587ba --- /dev/null +++ b/src/transcoder/transcoderoptionsopus.ui @@ -0,0 +1,94 @@ + + + TranscoderOptionsOpus + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + Bitrate + + + + + + + + + 6 + + + 510 + + + 128 + + + Qt::Horizontal + + + + + + + kbps + + + 320 + + + 128 + + + + + + + + + + + bitrate_slider + valueChanged(int) + bitrate_spinbox + setValue(int) + + + 166 + 25 + + + 323 + 24 + + + + + bitrate_spinbox + valueChanged(int) + bitrate_slider + setValue(int) + + + 325 + 14 + + + 190 + 31 + + + + + diff --git a/src/transcoder/transcoderoptionsspeex.cpp b/src/transcoder/transcoderoptionsspeex.cpp new file mode 100644 index 00000000..5ade71be --- /dev/null +++ b/src/transcoder/transcoderoptionsspeex.cpp @@ -0,0 +1,71 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "transcoderoptionsspeex.h" +#include "ui_transcoderoptionsspeex.h" + +#include + +const char *TranscoderOptionsSpeex::kSettingsGroup = "Transcoder/speexenc"; + +TranscoderOptionsSpeex::TranscoderOptionsSpeex(QWidget *parent) + : TranscoderOptionsInterface(parent), ui_(new Ui_TranscoderOptionsSpeex) { + ui_->setupUi(this); +} + +TranscoderOptionsSpeex::~TranscoderOptionsSpeex() { + delete ui_; +} + +void TranscoderOptionsSpeex::Load() { + + QSettings s; + s.beginGroup(kSettingsGroup + settings_postfix_); + + ui_->quality_slider->setValue(s.value("quality", 8).toInt()); + ui_->bitrate_slider->setValue(s.value("bitrate", 0).toInt() / 1000); + ui_->mode->setCurrentIndex(s.value("mode", 0).toInt()); + ui_->vbr->setChecked(s.value("vbr", false).toBool()); + ui_->abr_slider->setValue(s.value("abr", 0).toInt() / 1000); + ui_->vad->setChecked(s.value("vad", false).toBool()); + ui_->dtx->setChecked(s.value("dtx", false).toBool()); + ui_->complexity->setValue(s.value("complexity", 3).toInt()); + ui_->nframes->setValue(s.value("nframes", 1).toInt()); + +} + +void TranscoderOptionsSpeex::Save() { + + QSettings s; + s.beginGroup(kSettingsGroup + settings_postfix_); + + s.setValue("quality", ui_->quality_slider->value()); + s.setValue("bitrate", ui_->bitrate_slider->value() * 1000); + s.setValue("mode", ui_->mode->currentIndex()); + s.setValue("vbr", ui_->vbr->isChecked()); + s.setValue("abr", ui_->abr_slider->value() * 1000); + s.setValue("vad", ui_->vad->isChecked()); + s.setValue("dtx", ui_->dtx->isChecked()); + s.setValue("complexity", ui_->complexity->value()); + s.setValue("nframes", ui_->nframes->value()); + +} diff --git a/src/transcoder/transcoderoptionsspeex.h b/src/transcoder/transcoderoptionsspeex.h new file mode 100644 index 00000000..d4611eb4 --- /dev/null +++ b/src/transcoder/transcoderoptionsspeex.h @@ -0,0 +1,44 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TRANSCODEROPTIONSSPEEX_H +#define TRANSCODEROPTIONSSPEEX_H + +#include "config.h" + +#include "transcoderoptionsinterface.h" + +class Ui_TranscoderOptionsSpeex; + +class TranscoderOptionsSpeex : public TranscoderOptionsInterface { + public: + TranscoderOptionsSpeex(QWidget *parent = nullptr); + ~TranscoderOptionsSpeex(); + + void Load(); + void Save(); + +private: + static const char *kSettingsGroup; + + Ui_TranscoderOptionsSpeex *ui_; +}; + +#endif // TRANSCODEROPTIONSSPEEX_H diff --git a/src/transcoder/transcoderoptionsspeex.ui b/src/transcoder/transcoderoptionsspeex.ui new file mode 100644 index 00000000..7aedd613 --- /dev/null +++ b/src/transcoder/transcoderoptionsspeex.ui @@ -0,0 +1,353 @@ + + + TranscoderOptionsSpeex + + + + 0 + 0 + 555 + 424 + + + + Form + + + + + + Quality + + + + + + + + + 10 + + + 8 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + + + + + 10 + + + 8 + + + + + + + + + Bitrate + + + + + + + + + 320 + + + 8 + + + 32 + + + Qt::Horizontal + + + + + + + + 100 + 0 + + + + automatic + + + kbps + + + 320 + + + 8 + + + + + + + + + Average bitrate + + + + + + + + + 320 + + + 8 + + + 32 + + + Qt::Horizontal + + + + + + + + 100 + 0 + + + + disabled + + + kbps + + + 320 + + + 8 + + + + + + + + + Encoding mode + + + + + + + + Auto + + + + + Ultra wide band (UWB) + + + + + Wide band (WB) + + + + + Narrow band (NB) + + + + + + + + Variable bit rate + + + + + + + Voice activity detection + + + + + + + Discontinuous transmission + + + + + + + Encoding complexity + + + + + + + 10000 + + + 3 + + + + + + + Frames per buffer + + + + + + + 10000 + + + 1 + + + + + + + quality_slider + quality_spinbox + bitrate_slider + bitrate_spinbox + abr_slider + abr_spinbox + mode + vbr + vad + dtx + complexity + nframes + + + + + quality_slider + valueChanged(int) + quality_spinbox + setValue(int) + + + 232 + 29 + + + 525 + 23 + + + + + quality_spinbox + valueChanged(int) + quality_slider + setValue(int) + + + 517 + 25 + + + 248 + 31 + + + + + bitrate_slider + valueChanged(int) + bitrate_spinbox + setValue(int) + + + 185 + 66 + + + 445 + 51 + + + + + bitrate_spinbox + valueChanged(int) + bitrate_slider + setValue(int) + + + 471 + 68 + + + 214 + 56 + + + + + abr_slider + valueChanged(int) + abr_spinbox + setValue(int) + + + 323 + 90 + + + 500 + 95 + + + + + abr_spinbox + valueChanged(int) + abr_slider + setValue(int) + + + 493 + 84 + + + 339 + 94 + + + + + diff --git a/src/transcoder/transcoderoptionsvorbis.cpp b/src/transcoder/transcoderoptionsvorbis.cpp new file mode 100644 index 00000000..542b3750 --- /dev/null +++ b/src/transcoder/transcoderoptionsvorbis.cpp @@ -0,0 +1,81 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "transcoderoptionsvorbis.h" +#include "ui_transcoderoptionsvorbis.h" + +#include + +const char* TranscoderOptionsVorbis::kSettingsGroup = "Transcoder/vorbisenc"; + +TranscoderOptionsVorbis::TranscoderOptionsVorbis(QWidget* parent) + : TranscoderOptionsInterface(parent), ui_(new Ui_TranscoderOptionsVorbis) { + ui_->setupUi(this); +} + +TranscoderOptionsVorbis::~TranscoderOptionsVorbis() { + delete ui_; +} + +void TranscoderOptionsVorbis::Load() { + + QSettings s; + s.beginGroup(kSettingsGroup + settings_postfix_); + +#define GET_BITRATE(variable, property) \ + int variable = s.value(property, -1).toInt(); \ + variable = variable == -1 ? 0 : variable / 1000 + + GET_BITRATE(bitrate, "bitrate"); + GET_BITRATE(min_bitrate, "min-bitrate"); + GET_BITRATE(max_bitrate, "max-bitrate"); +#undef GET_BITRATE + + ui_->quality_slider->setValue(s.value("quality", 0.3).toDouble() * 10); + ui_->managed->setChecked(s.value("managed", false).toBool()); + ui_->max_bitrate_slider->setValue(max_bitrate); + ui_->min_bitrate_slider->setValue(min_bitrate); + ui_->bitrate_slider->setValue(bitrate); + +} + +void TranscoderOptionsVorbis::Save() { + + QSettings s; + s.beginGroup(kSettingsGroup + settings_postfix_); + +#define GET_BITRATE(variable, ui_slider) \ + int variable = ui_slider->value(); \ + variable = variable == 0 ? -1 : variable * 1000 + + GET_BITRATE(bitrate, ui_->bitrate_slider); + GET_BITRATE(min_bitrate, ui_->min_bitrate_slider); + GET_BITRATE(max_bitrate, ui_->max_bitrate_slider); +#undef GET_BITRATE + + s.setValue("quality", double(ui_->quality_slider->value()) / 10); + s.setValue("managed", ui_->managed->isChecked()); + s.setValue("bitrate", bitrate); + s.setValue("min-bitrate", min_bitrate); + s.setValue("max-bitrate", max_bitrate); + +} diff --git a/src/transcoder/transcoderoptionsvorbis.h b/src/transcoder/transcoderoptionsvorbis.h new file mode 100644 index 00000000..5df6b801 --- /dev/null +++ b/src/transcoder/transcoderoptionsvorbis.h @@ -0,0 +1,44 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TRANSCODEROPTIONSVORBIS_H +#define TRANSCODEROPTIONSVORBIS_H + +#include "config.h" + +#include "transcoderoptionsinterface.h" + +class Ui_TranscoderOptionsVorbis; + +class TranscoderOptionsVorbis : public TranscoderOptionsInterface { + public: + TranscoderOptionsVorbis(QWidget* parent = nullptr); + ~TranscoderOptionsVorbis(); + + void Load(); + void Save(); + +private: + static const char *kSettingsGroup; + + Ui_TranscoderOptionsVorbis *ui_; +}; + +#endif // TRANSCODEROPTIONSVORBIS_H diff --git a/src/transcoder/transcoderoptionsvorbis.ui b/src/transcoder/transcoderoptionsvorbis.ui new file mode 100644 index 00000000..65f390ac --- /dev/null +++ b/src/transcoder/transcoderoptionsvorbis.ui @@ -0,0 +1,365 @@ + + + TranscoderOptionsVorbis + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + Quality + + + + + + + + + -1 + + + 10 + + + 3 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + + + + + -1 + + + 10 + + + 3 + + + + + + + + + Use bitrate management engine + + + + + + + false + + + + 32 + + + 0 + + + 0 + + + 0 + + + + + Target bitrate + + + + + + + + + 250 + + + 128 + + + Qt::Horizontal + + + + + + + kbps + + + 250 + + + 128 + + + + + + + + + Minimum bitrate + + + + + + + + + 250 + + + 0 + + + Qt::Horizontal + + + + + + + disabled + + + kbps + + + 250 + + + 0 + + + + + + + + + Maximum bitrate + + + + + + + + + 250 + + + 0 + + + Qt::Horizontal + + + + + + + disabled + + + kbps + + + 250 + + + 0 + + + + + + + + + + + + quality_slider + quality_spinbox + managed + bitrate_slider + bitrate_spinbox + min_bitrate_slider + min_bitrate_spinbox + max_bitrate_slider + max_bitrate_spinbox + + + + + quality_slider + valueChanged(int) + quality_spinbox + setValue(int) + + + 176 + 29 + + + 365 + 31 + + + + + quality_spinbox + valueChanged(int) + quality_slider + setValue(int) + + + 358 + 19 + + + 136 + 29 + + + + + bitrate_slider + valueChanged(int) + bitrate_spinbox + setValue(int) + + + 255 + 83 + + + 344 + 88 + + + + + bitrate_spinbox + valueChanged(int) + bitrate_slider + setValue(int) + + + 341 + 77 + + + 244 + 80 + + + + + min_bitrate_spinbox + valueChanged(int) + min_bitrate_slider + setValue(int) + + + 324 + 122 + + + 265 + 115 + + + + + min_bitrate_slider + valueChanged(int) + min_bitrate_spinbox + setValue(int) + + + 217 + 122 + + + 347 + 124 + + + + + max_bitrate_slider + valueChanged(int) + max_bitrate_spinbox + setValue(int) + + + 278 + 157 + + + 323 + 155 + + + + + max_bitrate_spinbox + valueChanged(int) + max_bitrate_slider + setValue(int) + + + 344 + 166 + + + 216 + 155 + + + + + managed + toggled(bool) + widget + setEnabled(bool) + + + 55 + 58 + + + 25 + 86 + + + + + diff --git a/src/transcoder/transcoderoptionswma.cpp b/src/transcoder/transcoderoptionswma.cpp new file mode 100644 index 00000000..ca6d8857 --- /dev/null +++ b/src/transcoder/transcoderoptionswma.cpp @@ -0,0 +1,55 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "transcoderoptionswma.h" +#include "ui_transcoderoptionswma.h" + +#include + +const char *TranscoderOptionsWma::kSettingsGroup = "Transcoder/ffenc_wmav2"; + +TranscoderOptionsWma::TranscoderOptionsWma(QWidget *parent) + : TranscoderOptionsInterface(parent), ui_(new Ui_TranscoderOptionsWma) { + ui_->setupUi(this); +} + +TranscoderOptionsWma::~TranscoderOptionsWma() { + delete ui_; +} + +void TranscoderOptionsWma::Load() { + + QSettings s; + s.beginGroup(kSettingsGroup + settings_postfix_); + + ui_->bitrate_slider->setValue(s.value("bitrate", 128000).toInt() / 1000); + +} + +void TranscoderOptionsWma::Save() { + + QSettings s; + s.beginGroup(kSettingsGroup + settings_postfix_); + + s.setValue("bitrate", ui_->bitrate_slider->value() * 1000); + +} diff --git a/src/transcoder/transcoderoptionswma.h b/src/transcoder/transcoderoptionswma.h new file mode 100644 index 00000000..0203b0b8 --- /dev/null +++ b/src/transcoder/transcoderoptionswma.h @@ -0,0 +1,44 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TRANSCODEROPTIONSWMA_H +#define TRANSCODEROPTIONSWMA_H + +#include "config.h" + +#include "transcoderoptionsinterface.h" + +class Ui_TranscoderOptionsWma; + +class TranscoderOptionsWma : public TranscoderOptionsInterface { + public: + TranscoderOptionsWma(QWidget *parent = nullptr); + ~TranscoderOptionsWma(); + + void Load(); + void Save(); + +private: + static const char *kSettingsGroup; + + Ui_TranscoderOptionsWma *ui_; +}; + +#endif // TRANSCODEROPTIONSWMA_H diff --git a/src/transcoder/transcoderoptionswma.ui b/src/transcoder/transcoderoptionswma.ui new file mode 100644 index 00000000..a6b47eb1 --- /dev/null +++ b/src/transcoder/transcoderoptionswma.ui @@ -0,0 +1,91 @@ + + + TranscoderOptionsWma + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + Bitrate + + + + + + + + + 320 + + + 128 + + + Qt::Horizontal + + + + + + + kbps + + + 320 + + + 128 + + + + + + + + + + + bitrate_slider + valueChanged(int) + bitrate_spinbox + setValue(int) + + + 166 + 25 + + + 323 + 24 + + + + + bitrate_spinbox + valueChanged(int) + bitrate_slider + setValue(int) + + + 325 + 14 + + + 190 + 31 + + + + + diff --git a/src/version.h.in b/src/version.h.in new file mode 100644 index 00000000..eb242e82 --- /dev/null +++ b/src/version.h.in @@ -0,0 +1,23 @@ +/* This file is part of Strawberry. + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#ifndef VERSION_H_IN +#define VERSION_H_IN + +#define STRAWBERRY_VERSION_DISPLAY "${STRAWBERRY_VERSION_DISPLAY}" +#define STRAWBERRY_VERSION_SPARKLE "${STRAWBERRY_VERSION_SPARKLE}" + +#endif // VERSION_H_IN diff --git a/src/widgets/autoexpandingtreeview.cpp b/src/widgets/autoexpandingtreeview.cpp new file mode 100644 index 00000000..61be6782 --- /dev/null +++ b/src/widgets/autoexpandingtreeview.cpp @@ -0,0 +1,179 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include + +#include "autoexpandingtreeview.h" +#include "core/mimedata.h" + +const int AutoExpandingTreeView::kRowsToShow = 50; + +AutoExpandingTreeView::AutoExpandingTreeView(QWidget *parent) + : QTreeView(parent), + auto_open_(true), + expand_on_reset_(true), + add_on_double_click_(true), + ignore_next_click_(false) +{ + setExpandsOnDoubleClick(false); + setAnimated(true); + + connect(this, SIGNAL(expanded(QModelIndex)), SLOT(ItemExpanded(QModelIndex))); + connect(this, SIGNAL(clicked(QModelIndex)), SLOT(ItemClicked(QModelIndex))); + connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(ItemDoubleClicked(QModelIndex))); +} + +void AutoExpandingTreeView::reset() { + QTreeView::reset(); + + // Expand nodes in the tree until we have about 50 rows visible in the view + if (auto_open_ && expand_on_reset_) { + RecursivelyExpand(rootIndex()); + } +} + +void AutoExpandingTreeView::RecursivelyExpand(const QModelIndex &index) { + int rows = model()->rowCount(index); + RecursivelyExpand(index, &rows); +} + +bool AutoExpandingTreeView::RecursivelyExpand(const QModelIndex &index, int *count) { + if (!CanRecursivelyExpand(index)) + return true; + + if (model()->canFetchMore(index)) + model()->fetchMore(index); + + int children = model()->rowCount(index); + if (*count + children > kRowsToShow) + return false; + + expand(index); + *count += children; + + for (int i = 0 ; i < children ; ++i) { + if (!RecursivelyExpand(model()->index(i, 0, index), count)) + return false; + } + + return true; +} + +void AutoExpandingTreeView::ItemExpanded(const QModelIndex &index) { + if (model()->rowCount(index) == 1 && auto_open_) + expand(model()->index(0, 0, index)); +} + +void AutoExpandingTreeView::ItemClicked(const QModelIndex &index) { + if (ignore_next_click_) { + ignore_next_click_ = false; + return; + } + + setExpanded(index, !isExpanded(index)); +} + +void AutoExpandingTreeView::ItemDoubleClicked(const QModelIndex &index) { + ignore_next_click_ = true; + + if (add_on_double_click_) { + QMimeData *data = model()->mimeData(QModelIndexList() << index); + if (MimeData *mime_data = qobject_cast(data)) { + mime_data->from_doubleclick_ = true; + } + emit AddToPlaylistSignal(data); + } +} + +void AutoExpandingTreeView::mousePressEvent(QMouseEvent *event) { + if (event->modifiers() != Qt::NoModifier) { + ignore_next_click_ = true; + } + + QTreeView::mousePressEvent(event); + + //enqueue to playlist with middleClick + if (event->button() == Qt::MidButton) { + QMimeData *data = model()->mimeData(selectedIndexes()); + if (MimeData *mime_data = qobject_cast(data)) { + mime_data->enqueue_now_ = true; + } + emit AddToPlaylistSignal(data); + } +} + +void AutoExpandingTreeView::mouseDoubleClickEvent(QMouseEvent *event) { + State p_state = state(); + QModelIndex index = indexAt(event->pos()); + + QTreeView::mouseDoubleClickEvent(event); + + // If the p_state was the "AnimatingState", then the base class's + // "mouseDoubleClickEvent" method just did nothing, hence the + // "doubleClicked" signal is not emitted. So let's do it ourselves. + if (index.isValid() && p_state == AnimatingState) { + emit doubleClicked(index); + } +} + +void AutoExpandingTreeView::keyPressEvent(QKeyEvent *e) { + QModelIndex index = currentIndex(); + + switch (e->key()) { + case Qt::Key_Enter: + case Qt::Key_Return: + if (currentIndex().isValid()) + emit doubleClicked(currentIndex()); + e->accept(); + break; + + case Qt::Key_Backspace: + case Qt::Key_Escape: + emit FocusOnFilterSignal(e); + e->accept(); + break; + + case Qt::Key_Left: + // Set focus on the root of the current branch + if (index.isValid() && index.parent() != rootIndex() && + (!isExpanded(index) || model()->rowCount(index) == 0)) { + setCurrentIndex(index.parent()); + setFocus(); + e->accept(); + } + break; + } + + QTreeView::keyPressEvent(e); +} + +void AutoExpandingTreeView::UpAndFocus() { + setCurrentIndex(moveCursor(QAbstractItemView::MoveUp, Qt::NoModifier)); + setFocus(); +} + +void AutoExpandingTreeView::DownAndFocus() { + setCurrentIndex(moveCursor(QAbstractItemView::MoveDown, Qt::NoModifier)); + setFocus(); +} + diff --git a/src/widgets/autoexpandingtreeview.h b/src/widgets/autoexpandingtreeview.h new file mode 100644 index 00000000..f2b3013c --- /dev/null +++ b/src/widgets/autoexpandingtreeview.h @@ -0,0 +1,77 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef AUTOEXPANDINGTREEVIEW_H +#define AUTOEXPANDINGTREEVIEW_H + +#include "config.h" + +#include + +class AutoExpandingTreeView : public QTreeView { + Q_OBJECT + + public: + AutoExpandingTreeView(QWidget *parent = nullptr); + + static const int kRowsToShow; + + void SetAutoOpen(bool v) { auto_open_ = v; } + void SetExpandOnReset(bool v) { expand_on_reset_ = v; } + void SetAddOnDoubleClick(bool v) { add_on_double_click_ = v; } + +public slots: + void RecursivelyExpand(const QModelIndex &index); + void UpAndFocus(); + void DownAndFocus(); + +signals: + void AddToPlaylistSignal(QMimeData *data); + void FocusOnFilterSignal(QKeyEvent *event); + +protected: + // QAbstractItemView + void reset(); + + // QWidget + void mousePressEvent(QMouseEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event); + void keyPressEvent(QKeyEvent *event); + + virtual bool CanRecursivelyExpand(const QModelIndex &index) const { return true; } + +private slots: + void ItemExpanded(const QModelIndex &index); + void ItemClicked(const QModelIndex &index); + void ItemDoubleClicked(const QModelIndex &index); + +private: + bool RecursivelyExpand(const QModelIndex &index, int *count); + +private: + bool auto_open_; + bool expand_on_reset_; + bool add_on_double_click_; + + bool ignore_next_click_; +}; + +#endif // AUTOEXPANDINGTREEVIEW_H + diff --git a/src/widgets/busyindicator.cpp b/src/widgets/busyindicator.cpp new file mode 100644 index 00000000..09586e0d --- /dev/null +++ b/src/widgets/busyindicator.cpp @@ -0,0 +1,83 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include + +#include "core/logging.h" + +#include "busyindicator.h" + +BusyIndicator::BusyIndicator(const QString &text, QWidget* parent) + : QWidget(parent) { + Init(text); +} + +BusyIndicator::BusyIndicator(QWidget* parent) + : QWidget(parent) { + Init(QString::null); +} + +void BusyIndicator::Init(const QString &text) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + + movie_ = new QMovie(":pictures/spinner.gif"), + label_ = new QLabel; + + QLabel *icon = new QLabel; + icon->setMovie(movie_); + icon->setMinimumSize(16, 16); + + label_->setWordWrap(true); + label_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + + QHBoxLayout *layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(icon); + layout->addSpacing(6); + layout->addWidget(label_); + + set_text(text); +} + +BusyIndicator::~BusyIndicator() { + delete movie_; +} + +void BusyIndicator::showEvent(QShowEvent *) { + movie_->start(); +} + +void BusyIndicator::hideEvent(QHideEvent *) { + movie_->stop(); +} + +void BusyIndicator::set_text(const QString &text) { + label_->setText(text); + label_->setVisible(!text.isEmpty()); +} + +QString BusyIndicator::text() const { + return label_->text(); +} + diff --git a/src/widgets/busyindicator.h b/src/widgets/busyindicator.h new file mode 100644 index 00000000..bdc39350 --- /dev/null +++ b/src/widgets/busyindicator.h @@ -0,0 +1,55 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef BUSYINDICATOR_H +#define BUSYINDICATOR_H + +#include "config.h" + +#include + +class QMovie; + +class BusyIndicator : public QWidget { + Q_OBJECT + Q_PROPERTY(QString text READ text WRITE set_text) + + public: + explicit BusyIndicator(const QString &text, QWidget *parent = nullptr); + explicit BusyIndicator(QWidget *parent = nullptr); + ~BusyIndicator(); + + QString text() const; + void set_text(const QString &text); + + protected: + void showEvent(QShowEvent *event); + void hideEvent(QHideEvent *event); + + private: + void Init(const QString &text); + + private: + QMovie *movie_; + QLabel *label_; +}; + +#endif // BUSYINDICATOR_H + diff --git a/src/widgets/clickablelabel.cpp b/src/widgets/clickablelabel.cpp new file mode 100644 index 00000000..33c53ce8 --- /dev/null +++ b/src/widgets/clickablelabel.cpp @@ -0,0 +1,30 @@ +/* This file is part of Strawberry. + Copyright 2010, Andrea Decorte + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#include "config.h" + +#include "clickablelabel.h" + +ClickableLabel::ClickableLabel(QWidget *parent) + : QLabel(parent) +{ +} + +void ClickableLabel::mousePressEvent(QMouseEvent *event) { + emit Clicked(); + QLabel::mousePressEvent(event); +} diff --git a/src/widgets/clickablelabel.h b/src/widgets/clickablelabel.h new file mode 100644 index 00000000..85ad70ba --- /dev/null +++ b/src/widgets/clickablelabel.h @@ -0,0 +1,38 @@ +/* This file is part of Strawberry. + Copyright 2010, Andrea Decorte + + Strawberry is free software: you can 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. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#ifndef CLICKABLELABEL_H +#define CLICKABLELABEL_H + +#include "config.h" + +#include + +class ClickableLabel : public QLabel { + Q_OBJECT + + public: + ClickableLabel(QWidget *parent = nullptr); + +signals: + void Clicked(); + +protected: + void mousePressEvent(QMouseEvent* event); +}; + +#endif // CLICKABLELABEL_H diff --git a/src/widgets/didyoumean.cpp b/src/widgets/didyoumean.cpp new file mode 100644 index 00000000..2229baf6 --- /dev/null +++ b/src/widgets/didyoumean.cpp @@ -0,0 +1,177 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "didyoumean.h" + +#include "core/logging.h" + +const int DidYouMean::kPadding = 3; + +DidYouMean::DidYouMean(QWidget *buddy, QWidget *parent) + : QWidget(parent, Qt::ToolTip), + buddy_(buddy), + close_(new QToolButton(this)), + normal_font_(font()), + correction_font_(font()), + press_enter_font_(font()) { + + // Close icon + close_->setToolTip(tr("Close")); + close_->setIcon(QIcon(":/qt-project.org/styles/macstyle/images/closedock-16.png")); + close_->setIconSize(QSize(16, 16)); + connect(close_, SIGNAL(clicked()), SLOT(hide())); + + // Cursors + setCursor(Qt::PointingHandCursor); + close_->setCursor(Qt::ArrowCursor); + + // Fonts + correction_font_.setBold(true); + press_enter_font_.setBold(true); + press_enter_font_.setPointSizeF(7.5); + + hide(); + buddy_->installEventFilter(this); + + // Texts + did_you_mean_ = tr("Did you mean") + ": "; + press_enter_ = "(" + tr("press enter") + ")"; + + // Texts' sizes + did_you_mean_size_ = QFontMetrics(normal_font_).width(did_you_mean_); + press_enter_size_ = QFontMetrics(press_enter_font_).width(press_enter_); + +} + +bool DidYouMean::eventFilter(QObject *object, QEvent *event) { + + if (object != buddy_) { + return QObject::eventFilter(object, event); + } + + switch (event->type()) { + case QEvent::Move: + case QEvent::Resize: + if (isVisible()) { + UpdateGeometry(); + } + break; + + case QEvent::KeyPress: + if (!isVisible()) { + break; + } + + switch (static_cast(event)->key()) { + case Qt::Key_Return: + case Qt::Key_Enter: + emit Accepted(correction_); + // fallthrough + case Qt::Key_Escape: + hide(); + return true; + + default: + break; + } + + break; + + case QEvent::FocusOut: + case QEvent::WindowDeactivate: + hide(); + break; + + default: + break; + } + + return QObject::eventFilter(object, event); +} + +void DidYouMean::showEvent(QShowEvent*) { + UpdateGeometry(); +} + +void DidYouMean::UpdateGeometry() { + const int text_height = fontMetrics().height(); + const int height = text_height + kPadding * 2; + + move(buddy_->mapToGlobal(buddy_->rect().bottomLeft())); + // Resize to len(text to display) + total number of padding added + + // size(close button), so the "Did you mean" widget is always fully displayed + + resize(QSize(did_you_mean_size_ + QFontMetrics(correction_font_).width(correction_ + " ") + press_enter_size_ + kPadding * 6 + close_->width(), height)); + + close_->move(kPadding, kPadding); + close_->resize(text_height, text_height); +} + +void DidYouMean::paintEvent(QPaintEvent*) { + QPainter p(this); + + // Draw the background + QColor bg(palette().color(QPalette::Inactive, QPalette::ToolTipBase)); + p.fillRect(0, 0, width()-1, height()-1, bg); + + // Border + p.setPen(Qt::black); + p.drawRect(0, 0, width()-1, height()-1); + + // Text rectangle + QRect text_rect(kPadding + close_->width() + kPadding, kPadding, rect().width() - kPadding, rect().height() - kPadding); + + // Text + p.setFont(normal_font_); + p.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, did_you_mean_); + text_rect.setLeft(text_rect.left() + p.fontMetrics().width(did_you_mean_)); + + p.setFont(correction_font_); + p.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, correction_); + text_rect.setLeft(text_rect.left() + p.fontMetrics().width(correction_ + " ")); + + p.setPen(palette().color(QPalette::Disabled, QPalette::Text)); + p.setFont(press_enter_font_); + p.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, press_enter_); +} + +void DidYouMean::SetCorrection(const QString &correction) { + correction_ = correction; + UpdateGeometry(); + update(); +} + +void DidYouMean::Show(const QString &correction) { + SetCorrection(correction); + show(); +} + +void DidYouMean::mouseReleaseEvent(QMouseEvent *e) { + emit Accepted(correction_); + hide(); +} + diff --git a/src/widgets/didyoumean.h b/src/widgets/didyoumean.h new file mode 100644 index 00000000..2ce8ccb0 --- /dev/null +++ b/src/widgets/didyoumean.h @@ -0,0 +1,73 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef DIDYOUMEAN_H +#define DIDYOUMEAN_H + +#include "config.h" + +#include + +class QToolButton; + +class DidYouMean : public QWidget { + Q_OBJECT + +public: + DidYouMean(QWidget *buddy, QWidget *parent); + + static const int kPadding; + +public slots: + void SetCorrection(const QString& correction); + void Show(const QString& correction); + +signals: + void Accepted(const QString& correction); + +protected: + void paintEvent(QPaintEvent*); + void showEvent(QShowEvent*); + void mouseReleaseEvent(QMouseEvent *e); + bool eventFilter(QObject *object, QEvent *event); + +private: + void UpdateGeometry(); + +private: + QWidget *buddy_; + QString correction_; + + QToolButton *close_; + + QFont normal_font_; + QFont correction_font_; + QFont press_enter_font_; + + QString did_you_mean_; + QString press_enter_; + + // Size of the text to display, according to QFonts above. + // Stored here to avoid to recompute them each time + int did_you_mean_size_; + int press_enter_size_; +}; + +#endif // DIDYOUMEAN_H diff --git a/src/widgets/elidedlabel.cpp b/src/widgets/elidedlabel.cpp new file mode 100644 index 00000000..db82de58 --- /dev/null +++ b/src/widgets/elidedlabel.cpp @@ -0,0 +1,38 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "elidedlabel.h" + +ElidedLabel::ElidedLabel(QWidget *parent) : QLabel(parent) {} + +void ElidedLabel::SetText(const QString& text) { + text_ = text; + UpdateText(); +} + +void ElidedLabel::resizeEvent(QResizeEvent *) { + UpdateText(); +} + +void ElidedLabel::UpdateText() { + setText(fontMetrics().elidedText(text_, Qt::ElideRight, width() - 5)); +} diff --git a/src/widgets/elidedlabel.h b/src/widgets/elidedlabel.h new file mode 100644 index 00000000..1a439e0b --- /dev/null +++ b/src/widgets/elidedlabel.h @@ -0,0 +1,47 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef ELIDEDLABEL_H +#define ELIDEDLABEL_H + +#include "config.h" + +#include + +class ElidedLabel : public QLabel { + Q_OBJECT + + public: + ElidedLabel(QWidget *parent = nullptr); + +public slots: + void SetText(const QString &text); + +protected: + void resizeEvent(QResizeEvent *e); + +private: + void UpdateText(); + +private: + QString text_; +}; + +#endif // ELIDEDLABEL_H diff --git a/src/widgets/fancytabwidget.cpp b/src/widgets/fancytabwidget.cpp new file mode 100644 index 00000000..74eef05a --- /dev/null +++ b/src/widgets/fancytabwidget.cpp @@ -0,0 +1,731 @@ +/************************************************************************** +** +** 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 "stylehelper.h" +#include "core/logging.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Core; +using namespace Internal; + +const int FancyTabBar::m_rounding = 22; +const int FancyTabBar::m_textPadding = 4; + +void FancyTabProxyStyle::drawControl(ControlElement element, const QStyleOption* option, QPainter* p, const QWidget* widget) const { + + const QStyleOptionTabV3* v_opt = qstyleoption_cast(option); + + if (element != CE_TabBarTab || !v_opt) { + QProxyStyle::drawControl(element, option, p, widget); + return; + } + + const QRect rect = v_opt->rect; + const bool selected = v_opt->state & State_Selected; + const bool vertical_tabs = v_opt->shape == QTabBar::RoundedWest; + const QString text = v_opt->text; + + if (selected) { + //background + p->save(); + QLinearGradient grad(rect.topLeft(), rect.topRight()); + grad.setColorAt(0, QColor(255, 255, 255, 140)); + grad.setColorAt(1, QColor(255, 255, 255, 210)); + p->fillRect(rect.adjusted(0, 0, 0, -1), grad); + p->restore(); + + //shadows + p->setPen(QColor(0, 0, 0, 110)); + p->drawLine(rect.topLeft() + QPoint(1,-1), rect.topRight() - QPoint(0,1)); + p->drawLine(rect.bottomLeft(), rect.bottomRight()); + p->setPen(QColor(0, 0, 0, 40)); + p->drawLine(rect.topLeft(), rect.bottomLeft()); + + //highlights + p->setPen(QColor(255, 255, 255, 50)); + p->drawLine(rect.topLeft() + QPoint(0, -2), rect.topRight() - QPoint(0,2)); + p->drawLine(rect.bottomLeft() + QPoint(0, 1), rect.bottomRight() + QPoint(0,1)); + p->setPen(QColor(255, 255, 255, 40)); + p->drawLine(rect.topLeft() + QPoint(0, 0), rect.topRight()); + p->drawLine(rect.topRight() + QPoint(0, 1), rect.bottomRight() - QPoint(0, 1)); + p->drawLine(rect.bottomLeft() + QPoint(0,-1), rect.bottomRight()-QPoint(0,1)); + } + + QTransform m; + if (vertical_tabs) { + 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 icon_rect(QPoint(8, 0), v_opt->iconSize); + QRect text_rect(icon_rect.topRight() + QPoint(4, 0), draw_rect.size()); + text_rect.setRight(draw_rect.width()); + icon_rect.translate(0, (draw_rect.height() - icon_rect.height()) / 2); + + QFont boldFont(p->font()); + boldFont.setPointSizeF(Utils::StyleHelper::sidebarFontSize()); + boldFont.setBold(true); + p->setFont(boldFont); + p->setPen(selected ? QColor(255, 255, 255, 160) : QColor(0, 0, 0, 110)); + int textFlags = Qt::AlignHCenter | Qt::AlignVCenter; + p->drawText(text_rect, textFlags, text); + p->setPen(selected ? QColor(60, 60, 60) : Utils::StyleHelper::panelTextColor()); +#ifndef Q_WS_MAC + if (widget) { + const QString fader_key = "tab_" + text + "_fader"; + const QString animation_key = "tab_" + text + "_animation"; + + const QString tab_hover = widget->property("tab_hover").toString(); + int fader = widget->property(fader_key.toUtf8().constData()).toInt(); + QPropertyAnimation* animation = widget->property(animation_key.toUtf8().constData()).value(); + + if (!animation) { + QWidget* mut_widget = const_cast(widget); + fader = 0; + mut_widget->setProperty(fader_key.toUtf8().constData(), fader); + animation = new QPropertyAnimation(mut_widget, fader_key.toUtf8(), mut_widget); + connect(animation, SIGNAL(valueChanged(QVariant)), mut_widget, SLOT(update())); + mut_widget->setProperty(animation_key.toUtf8().constData(), QVariant::fromValue(animation)); + } + + if (text == tab_hover) { + if (animation->state() != QAbstractAnimation::Running && fader != 40) { + animation->stop(); + animation->setDuration(80); + animation->setEndValue(40); + animation->start(); + } + } else { + if (animation->state() != QAbstractAnimation::Running && fader != 0) { + animation->stop(); + animation->setDuration(160); + animation->setEndValue(0); + animation->start(); + } + } + + if (!selected) { + p->save(); + QLinearGradient grad(draw_rect.topLeft(), vertical_tabs ? draw_rect.bottomLeft() : draw_rect.topRight()); + grad.setColorAt(0, Qt::transparent); + grad.setColorAt(0.5, QColor(255, 255, 255, fader)); + grad.setColorAt(1, Qt::transparent); + p->fillRect(draw_rect, grad); + p->setPen(QPen(grad, 1.0)); + p->drawLine(draw_rect.topLeft(), vertical_tabs ? draw_rect.bottomLeft() : draw_rect.topRight()); + p->drawLine(draw_rect.bottomRight(), vertical_tabs ? draw_rect.topRight() : draw_rect.bottomLeft()); + p->restore(); + } + } +#endif + + Utils::StyleHelper::drawIconWithShadow(v_opt->icon, icon_rect, p, QIcon::Normal); + + p->drawText(text_rect.translated(0, -1), textFlags, text); + + p->restore(); +} + +void FancyTabProxyStyle::polish(QWidget* widget) { + if (QString(widget->metaObject()->className()) == "QTabBar") { + 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) { + QTabBar* bar = qobject_cast(o); + if (bar && (e->type() == QEvent::MouseMove || e->type() == QEvent::Leave)) { + QMouseEvent* event = static_cast(e); + const QString old_hovered_tab = bar->property("tab_hover").toString(); + const QString hovered_tab = e->type() == QEvent::Leave ? QString() : bar->tabText(bar->tabAt(event->pos())); + bar->setProperty("tab_hover", hovered_tab); + + if (old_hovered_tab != hovered_tab) + bar->update(); + } + + return false; +} + +FancyTab::FancyTab(QWidget* tabbar) + : QWidget(tabbar), tabbar(tabbar), m_fader(0) +{ + animator.setPropertyName("fader"); + animator.setTargetObject(this); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); +} + +void FancyTab::fadeIn() +{ + animator.stop(); + animator.setDuration(80); + animator.setEndValue(40); + animator.start(); +} + +void FancyTab::fadeOut() +{ + animator.stop(); + animator.setDuration(160); + animator.setEndValue(0); + animator.start(); +} + +void FancyTab::setFader(float value) +{ + m_fader = value; + tabbar->update(); +} + +FancyTabBar::FancyTabBar(QWidget *parent) + : QWidget(parent) +{ + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + setStyle(new QCommonStyle); + setMinimumWidth(qMax(2 * m_rounding, 40)); + setAttribute(Qt::WA_Hover, true); + setFocusPolicy(Qt::NoFocus); + setMouseTracking(true); // Needed for hover events + m_triggerTimer.setSingleShot(true); + + QVBoxLayout* layout = new QVBoxLayout; + layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding)); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); + setLayout(layout); + + // We use a zerotimer to keep the sidebar responsive + connect(&m_triggerTimer, SIGNAL(timeout()), this, SLOT(emitCurrentIndex())); +} + +FancyTabBar::~FancyTabBar() +{ + delete style(); +} + +QSize FancyTab::sizeHint() const { + QFont boldFont(font()); + boldFont.setPointSizeF(Utils::StyleHelper::sidebarFontSize()); + boldFont.setBold(true); + QFontMetrics fm(boldFont); + int spacing = 8; + int width = 60 + spacing + 2; + int iconHeight = 32; + QSize ret(width, iconHeight + spacing + fm.height()); + return ret; +} + +QSize FancyTabBar::tabSizeHint(bool minimum) const +{ + QFont boldFont(font()); + boldFont.setPointSizeF(Utils::StyleHelper::sidebarFontSize()); + boldFont.setBold(true); + QFontMetrics fm(boldFont); + int spacing = 8; + int width = 60 + spacing + 2; + int iconHeight = minimum ? 0 : 32; + return QSize(width, iconHeight + spacing + fm.height()); +} + +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()); +} + +bool FancyTab::event(QEvent* event) +{ + if (event->type() == QEvent::ToolTip) { + QFontMetrics metrics (font()); + int text_width = metrics.width(text); + + if (text_width > sizeHint().width()) { + // The text is elided: show the tooltip + QHelpEvent* he = static_cast(event); + QToolTip::showText(he->globalPos(), text); + } else { + QToolTip::hideText(); + } + return true; + } + return QWidget::event(event); +} + +void FancyTab::enterEvent(QEvent*) +{ + fadeIn(); +} + +void FancyTab::leaveEvent(QEvent*) +{ + fadeOut(); +} + +QSize FancyTabBar::sizeHint() const +{ + QSize sh = tabSizeHint(); + return QSize(sh.width(), sh.height() * m_tabs.count()); +} + +QSize FancyTabBar::minimumSizeHint() const +{ + QSize sh = tabSizeHint(true); + return QSize(sh.width(), sh.height() * m_tabs.count()); +} + +QRect FancyTabBar::tabRect(int index) const +{ + return m_tabs[index]->geometry(); +} + +QString FancyTabBar::tabToolTip(int index) const { + return m_tabs[index]->toolTip(); +} + +void FancyTabBar::setTabToolTip(int index, const QString& toolTip) { + m_tabs[index]->setToolTip(toolTip); +} + +// This keeps the sidebar responsive since +// we get a repaint before loading the +// mode itself +void FancyTabBar::emitCurrentIndex() +{ + emit currentChanged(m_currentIndex); +} + +void FancyTabBar::mousePressEvent(QMouseEvent *e) +{ + e->accept(); + for (int index = 0; index < m_tabs.count(); ++index) { + if (tabRect(index).contains(e->pos())) { + m_currentIndex = index; + update(); + m_triggerTimer.start(0); + break; + } + } +} + +void FancyTabBar::addTab(const QIcon& icon, const QString& label) { + FancyTab *tab = new FancyTab(this); + tab->icon = icon; + tab->text = label; + tab->setToolTip(label); + m_tabs.append(tab); + qobject_cast(layout())->insertWidget(layout()->count()-1, 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 == m_currentIndex); + + if (selected) { + //background + painter->save(); + QLinearGradient grad(rect.topLeft(), rect.topRight()); + grad.setColorAt(0, QColor(255, 255, 255, 140)); + grad.setColorAt(1, QColor(255, 255, 255, 210)); + painter->fillRect(rect.adjusted(0, 0, 0, -1), grad); + painter->restore(); + + //shadows + painter->setPen(QColor(0, 0, 0, 110)); + painter->drawLine(rect.topLeft() + QPoint(1,-1), rect.topRight() - QPoint(0,1)); + painter->drawLine(rect.bottomLeft(), rect.bottomRight()); + painter->setPen(QColor(0, 0, 0, 40)); + painter->drawLine(rect.topLeft(), rect.bottomLeft()); + + //highlights + painter->setPen(QColor(255, 255, 255, 50)); + painter->drawLine(rect.topLeft() + QPoint(0, -2), rect.topRight() - QPoint(0,2)); + painter->drawLine(rect.bottomLeft() + QPoint(0, 1), rect.bottomRight() + QPoint(0,1)); + painter->setPen(QColor(255, 255, 255, 40)); + painter->drawLine(rect.topLeft() + QPoint(0, 0), rect.topRight()); + painter->drawLine(rect.topRight() + QPoint(0, 1), rect.bottomRight() - QPoint(0, 1)); + painter->drawLine(rect.bottomLeft() + QPoint(0,-1), rect.bottomRight()-QPoint(0,1)); + } + + QString tabText(painter->fontMetrics().elidedText(this->tabText(tabIndex), Qt::ElideRight, width())); + QRect tabTextRect(tabRect(tabIndex)); + QRect tabIconRect(tabTextRect); + tabIconRect.adjust(+4, +4, -4, -4); + tabTextRect.translate(0, -2); + QFont boldFont(painter->font()); + boldFont.setPointSizeF(Utils::StyleHelper::sidebarFontSize()); + boldFont.setBold(true); + painter->setFont(boldFont); + painter->setPen(selected ? QColor(255, 255, 255, 160) : QColor(0, 0, 0, 110)); + int textFlags = Qt::AlignCenter | Qt::AlignBottom; + painter->drawText(tabTextRect, textFlags, tabText); + painter->setPen(selected ? QColor(60, 60, 60) : Utils::StyleHelper::panelTextColor()); +#ifndef Q_WS_MAC + if (!selected) { + painter->save(); + int fader = int(m_tabs[tabIndex]->fader()); + QLinearGradient grad(rect.topLeft(), rect.topRight()); + grad.setColorAt(0, Qt::transparent); + grad.setColorAt(0.5, QColor(255, 255, 255, fader)); + grad.setColorAt(1, Qt::transparent); + painter->fillRect(rect, grad); + painter->setPen(QPen(grad, 1.0)); + painter->drawLine(rect.topLeft(), rect.topRight()); + painter->drawLine(rect.bottomLeft(), rect.bottomRight()); + painter->restore(); + } +#endif + + const int textHeight = painter->fontMetrics().height(); + tabIconRect.adjust(0, 4, 0, -textHeight); + Utils::StyleHelper::drawIconWithShadow(tabIcon(tabIndex), tabIconRect, painter, QIcon::Normal); + + painter->translate(0, -1); + painter->drawText(tabTextRect, textFlags, tabText); + painter->restore(); +} + +void FancyTabBar::setCurrentIndex(int index) { + m_currentIndex = index; + update(); + emit currentChanged(m_currentIndex); +} + +////// +// FancyColorButton +////// + +class FancyColorButton : public QWidget +{ +public: + FancyColorButton(QWidget *parent) + : m_parent(parent) + { + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + } + + void mousePressEvent(QMouseEvent *ev) + { + if (ev->modifiers() & Qt::ShiftModifier) + Utils::StyleHelper::setBaseColor(QColorDialog::getColor(Utils::StyleHelper::requestedBaseColor(), m_parent)); + } +private: + QWidget *m_parent; +}; + +////// +// FancyTabWidget +////// + +FancyTabWidget::FancyTabWidget(QWidget* parent) + : QWidget(parent), + mode_(Mode_None), + tab_bar_(nullptr), + stack_(new QStackedLayout), + side_widget_(new QWidget), + side_layout_(new QVBoxLayout), + top_layout_(new QVBoxLayout), + use_background_(false), + menu_(nullptr), + proxy_style_(new FancyTabProxyStyle) { + side_layout_->setSpacing(0); + side_layout_->setMargin(0); + side_layout_->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding)); + + side_widget_->setLayout(side_layout_); + side_widget_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + + top_layout_->setMargin(0); + top_layout_->setSpacing(0); + top_layout_->addLayout(stack_); + + QHBoxLayout* main_layout = new QHBoxLayout; + main_layout->setMargin(0); + main_layout->setSpacing(1); + main_layout->addWidget(side_widget_); + main_layout->addLayout(top_layout_); + setLayout(main_layout); +} + +void FancyTabWidget::AddTab(QWidget* tab, const QIcon& icon, const QString& label) { + stack_->addWidget(tab); + items_ << Item(icon, label); +} + +void FancyTabWidget::AddSpacer(int size) { + items_ << Item(size); +} + +void FancyTabWidget::SetBackgroundPixmap(const QPixmap& pixmap) { + background_pixmap_ = pixmap; + update(); +} + +void FancyTabWidget::paintEvent(QPaintEvent*) { + if (!use_background_) return; + + QPainter painter(this); + + QRect rect = side_widget_->rect().adjusted(0, 0, 1, 0); + rect = style()->visualRect(layoutDirection(), geometry(), rect); + Utils::StyleHelper::verticalGradient(&painter, rect, rect); + + if (!background_pixmap_.isNull()) { + QRect pixmap_rect(background_pixmap_.rect()); + pixmap_rect.moveTo(rect.topLeft()); + + while (pixmap_rect.top() < rect.bottom()) { + QRect source_rect(pixmap_rect.intersected(rect)); + source_rect.moveTo(0, 0); + painter.drawPixmap(pixmap_rect.topLeft(), background_pixmap_, source_rect); + pixmap_rect.moveTop(pixmap_rect.bottom() - 10); + } + } + + painter.setPen(Utils::StyleHelper::borderColor()); + painter.drawLine(rect.topRight(), rect.bottomRight()); + + QColor light = Utils::StyleHelper::sidebarHighlight(); + painter.setPen(light); + painter.drawLine(rect.bottomLeft(), rect.bottomRight()); +} + +int FancyTabWidget::current_index() const { + return stack_->currentIndex(); +} + +void FancyTabWidget::SetCurrentIndex(int index) { + if (FancyTabBar* bar = qobject_cast(tab_bar_)) { + bar->setCurrentIndex(index); + } else if (QTabBar* bar = qobject_cast(tab_bar_)) { + bar->setCurrentIndex(index); + } else { + stack_->setCurrentIndex(index); + } +} + +void FancyTabWidget::SetCurrentWidget(QWidget* widget) { + SetCurrentIndex(stack_->indexOf(widget)); +} + +void FancyTabWidget::ShowWidget(int index) { + stack_->setCurrentIndex(index); + emit CurrentChanged(index); +} + +void FancyTabWidget::AddBottomWidget(QWidget* widget) { + top_layout_->addWidget(widget); +} + +void FancyTabWidget::SetMode(Mode mode) { + // Remove previous tab bar + delete tab_bar_; + tab_bar_ = nullptr; + + use_background_ = false; + + // Create new tab bar + switch (mode) { + case Mode_None: + default: + qLog(Warning) << "Unknown fancy tab mode" << mode; + // fallthrough + + case Mode_LargeSidebar: { + FancyTabBar* bar = new FancyTabBar(this); + side_layout_->insertWidget(0, bar); + tab_bar_ = bar; + + for (const Item& item : items_) { + if (item.type_ == Item::Type_Spacer) + bar->addSpacer(item.spacer_size_); + else + bar->addTab(item.tab_icon_, item.tab_label_); + } + + bar->setCurrentIndex(stack_->currentIndex()); + connect(bar, SIGNAL(currentChanged(int)), SLOT(ShowWidget(int))); + + use_background_ = true; + + break; + } + + case Mode_Tabs: + MakeTabBar(QTabBar::RoundedNorth, true, false, false); + break; + + case Mode_IconOnlyTabs: + MakeTabBar(QTabBar::RoundedNorth, false, true, false); + break; + + case Mode_SmallSidebar: + MakeTabBar(QTabBar::RoundedWest, true, true, true); + use_background_ = true; + break; + + case Mode_PlainSidebar: + MakeTabBar(QTabBar::RoundedWest, true, true, false); + break; + } + + tab_bar_->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + + mode_ = mode; + emit ModeChanged(mode); + update(); +} + +void FancyTabWidget::contextMenuEvent(QContextMenuEvent* e) { + if (!menu_) { + menu_ = new QMenu(this); + + QSignalMapper* mapper = new QSignalMapper(this); + QActionGroup* group = new QActionGroup(this); + AddMenuItem(mapper, group, tr("Large sidebar"), Mode_LargeSidebar); + AddMenuItem(mapper, group, tr("Small sidebar"), Mode_SmallSidebar); + AddMenuItem(mapper, group, tr("Plain sidebar"), Mode_PlainSidebar); + AddMenuItem(mapper, group, tr("Tabs on top"), Mode_Tabs); + AddMenuItem(mapper, group, tr("Icons on top"), Mode_IconOnlyTabs); + menu_->addActions(group->actions()); + + connect(mapper, SIGNAL(mapped(int)), SLOT(SetMode(int))); + } + + menu_->popup(e->globalPos()); +} + +void FancyTabWidget::AddMenuItem(QSignalMapper* mapper, QActionGroup* group, + const QString& text, Mode mode) { + QAction* action = group->addAction(text); + action->setCheckable(true); + mapper->setMapping(action, mode); + connect(action, SIGNAL(triggered()), mapper, SLOT(map())); + + if (mode == mode_) action->setChecked(true); +} + +void FancyTabWidget::MakeTabBar(QTabBar::Shape shape, bool text, bool icons, + bool fancy) { + QTabBar* bar = new QTabBar(this); + bar->setShape(shape); + bar->setDocumentMode(true); + bar->setUsesScrollButtons(true); + bar->setElideMode(Qt::ElideRight); + + if (shape == QTabBar::RoundedWest) { + bar->setIconSize(QSize(22, 22)); + } + + if (fancy) { + bar->setStyle(proxy_style_.get()); + } + + if (shape == QTabBar::RoundedNorth) + top_layout_->insertWidget(0, bar); + else + side_layout_->insertWidget(0, bar); + + for (const Item& item : items_) { + if (item.type_ != Item::Type_Tab) continue; + + QString label = item.tab_label_; + if (shape == QTabBar::RoundedWest) { + label = QFontMetrics(font()).elidedText(label, Qt::ElideMiddle, 100); + } + + int tab_id = -1; + if (icons && text) + tab_id = bar->addTab(item.tab_icon_, label); + else if (icons) + tab_id = bar->addTab(item.tab_icon_, QString()); + else if (text) + tab_id = bar->addTab(label); + + bar->setTabToolTip(tab_id, item.tab_label_); + } + + bar->setCurrentIndex(stack_->currentIndex()); + connect(bar, SIGNAL(currentChanged(int)), SLOT(ShowWidget(int))); + tab_bar_ = bar; +} diff --git a/src/widgets/fancytabwidget.h b/src/widgets/fancytabwidget.h new file mode 100644 index 00000000..70d72be5 --- /dev/null +++ b/src/widgets/fancytabwidget.h @@ -0,0 +1,234 @@ +/************************************************************************** +** +** 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 QActionGroup; +class QMenu; +class QPainter; +class QSignalMapper; +class QStackedLayout; +class QStatusBar; +class QVBoxLayout; + +namespace Core { +namespace Internal { + +class FancyTabProxyStyle : public QProxyStyle { + Q_OBJECT + +public: + void drawControl(ControlElement element, const QStyleOption* option, + QPainter* painter, const QWidget* widget) const; + void polish(QWidget* widget); + void polish(QApplication* app); + void polish(QPalette& palette); + +protected: + bool eventFilter(QObject* o, QEvent* e); +}; + +class FancyTab : public QWidget { + Q_OBJECT + + Q_PROPERTY(float fader READ fader WRITE setFader) +public: + FancyTab(QWidget *tabbar); + float fader() { return m_fader; } + void setFader(float value); + + QSize sizeHint() const; + + void fadeIn(); + void fadeOut(); + + QIcon icon; + QString text; + +protected: + bool event(QEvent *); + void enterEvent(QEvent *); + void leaveEvent(QEvent *); + +private: + QPropertyAnimation animator; + QWidget *tabbar; + float m_fader; +}; + +class FancyTabBar : public QWidget +{ + Q_OBJECT + + public: + FancyTabBar(QWidget* parent = nullptr); + ~FancyTabBar(); + + void paintEvent(QPaintEvent *event); + void paintTab(QPainter *painter, int tabIndex) const; + void mousePressEvent(QMouseEvent *); + bool validIndex(int index) const { return index >= 0 && index < m_tabs.count(); } + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + void addTab(const QIcon &icon, const QString &label); + void addSpacer(int size = 40); + void removeTab(int index) { + FancyTab *tab = m_tabs.takeAt(index); + delete tab; + } + void setCurrentIndex(int index); + int currentIndex() const { return m_currentIndex; } + + void setTabToolTip(int index, const QString& toolTip); + QString tabToolTip(int index) const; + + QIcon tabIcon(int index) const {return m_tabs.at(index)->icon; } + QString tabText(int index) const { return m_tabs.at(index)->text; } + int count() const {return m_tabs.count(); } + QRect tabRect(int index) const; + +signals: + void currentChanged(int); + +public slots: + void emitCurrentIndex(); + +private: + static const int m_rounding; + static const int m_textPadding; + int m_currentIndex; + QList m_tabs; + QTimer m_triggerTimer; + QSize tabSizeHint(bool minimum = false) const; + +}; + +class FancyTabWidget : public QWidget { + Q_OBJECT + + public: + FancyTabWidget(QWidget* parent = nullptr); + + // Values are persisted - only add to the end + enum Mode { + Mode_None = 0, + + Mode_LargeSidebar = 1, + Mode_SmallSidebar = 2, + Mode_Tabs = 3, + Mode_IconOnlyTabs = 4, + Mode_PlainSidebar = 5, + }; + + struct Item { + Item(const QIcon& icon, const QString& label) + : type_(Type_Tab), tab_label_(label), tab_icon_(icon), spacer_size_(0) {} + Item(int size) : type_(Type_Spacer), spacer_size_(size) {} + + enum Type { + Type_Tab, + Type_Spacer, + }; + + Type type_; + QString tab_label_; + QIcon tab_icon_; + int spacer_size_; + }; + + void AddTab(QWidget *tab, const QIcon &icon, const QString &label); + void AddSpacer(int size = 40); + void SetBackgroundPixmap(const QPixmap& pixmap); + + void AddBottomWidget(QWidget* widget); + + int current_index() const; + Mode mode() const { return mode_; } + +public slots: + void SetCurrentIndex(int index); + void SetCurrentWidget(QWidget* widget); + void SetMode(Mode mode); + void SetMode(int mode) { SetMode(Mode(mode)); } + +signals: + void CurrentChanged(int index); + void ModeChanged(FancyTabWidget::Mode mode); + +protected: + void paintEvent(QPaintEvent *event); + void contextMenuEvent(QContextMenuEvent* e); + +private slots: + void ShowWidget(int index); + +private: + void MakeTabBar(QTabBar::Shape shape, bool text, bool icons, bool fancy); + void AddMenuItem(QSignalMapper* mapper, QActionGroup* group, + const QString& text, Mode mode); + + Mode mode_; + QList items_; + + QWidget* tab_bar_; + QStackedLayout* stack_; + QPixmap background_pixmap_; + QWidget* side_widget_; + QVBoxLayout* side_layout_; + QVBoxLayout* top_layout_; + + bool use_background_; + + QMenu* menu_; + + std::unique_ptr proxy_style_; +}; + +} // namespace Internal +} // namespace Core + +Q_DECLARE_METATYPE(QPropertyAnimation*); + +using Core::Internal::FancyTab; +using Core::Internal::FancyTabBar; +using Core::Internal::FancyTabWidget; + +#endif // FANCYTABWIDGET_H diff --git a/src/widgets/favoritewidget.cpp b/src/widgets/favoritewidget.cpp new file mode 100644 index 00000000..a535d7b5 --- /dev/null +++ b/src/widgets/favoritewidget.cpp @@ -0,0 +1,75 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2013, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "favoritewidget.h" + +#include +#include +#include +#include +#include + +#include "core/logging.h" + +const int FavoriteWidget::kStarSize = 15; + +FavoriteWidget::FavoriteWidget(int tab_index, bool favorite, QWidget *parent) + : QWidget(parent), + tab_index_(tab_index), + favorite_(favorite), + on_(":/icons/64x64/star.png"), + off_(":/icons/64x64/star-grey.png"), + rect_(0, 0, kStarSize, kStarSize) {} + +void FavoriteWidget::SetFavorite(bool favorite) { + + if (favorite_ != favorite) { + favorite_ = favorite; + update(); + emit FavoriteStateChanged(tab_index_, favorite_); + } + +} + +QSize FavoriteWidget::sizeHint() const { + const int frame_width = 1 + style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + return QSize(kStarSize + frame_width * 2, kStarSize + frame_width * 2); +} + +void FavoriteWidget::paintEvent(QPaintEvent *e) { + + QStylePainter p(this); + + if (favorite_) { + p.drawPixmap(rect_, on_); + } + else { + p.drawPixmap(rect_, off_); + } + +} + +void FavoriteWidget::mouseReleaseEvent(QMouseEvent *e) { + favorite_ = !favorite_; + update(); + emit FavoriteStateChanged(tab_index_, favorite_); +} diff --git a/src/widgets/favoritewidget.h b/src/widgets/favoritewidget.h new file mode 100644 index 00000000..f0599200 --- /dev/null +++ b/src/widgets/favoritewidget.h @@ -0,0 +1,56 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2013, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +class QPaintEvent; +class QMouseEvent; + +class FavoriteWidget : public QWidget { + Q_OBJECT + + public: + FavoriteWidget(int tab_id, bool favorite = false, QWidget *parent = nullptr); + + // Change the value if different from the current one and then update display + // and emit FavoriteStateChanged signal + void SetFavorite(bool favorite); + + QSize sizeHint() const; + +signals: + void FavoriteStateChanged(int, bool); + + protected: + void paintEvent(QPaintEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + + private: + static const int kStarSize; + + // The playlist's id this widget belongs to + int tab_index_; + bool favorite_; + QPixmap on_; + QPixmap off_; + QRect rect_; +}; diff --git a/src/widgets/fileview.cpp b/src/widgets/fileview.cpp new file mode 100644 index 00000000..3efed83d --- /dev/null +++ b/src/widgets/fileview.cpp @@ -0,0 +1,270 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "fileview.h" + +#include "ui_fileview.h" +#include "core/deletefiles.h" +#include "core/filesystemmusicstorage.h" +#include "core/mimedata.h" +#include "core/iconloader.h" +#include "core/mainwindow.h" // for filter information +#ifdef HAVE_GSTREAMER +#include "dialogs/organiseerrordialog.h" +#endif + +const char *FileView::kFileFilter = + "*.mp3 *.ogg *.flac *.mpc *.m4a *.aac *.wma " + "*.mp4 *.spx *.wav *.m3u *.m3u8 *.pls *.xspf " + "*.asx *.asxini *.cue *.ape *.wv *.mka *.opus " + "*.oga *.mka *.mp2"; + +FileView::FileView(QWidget *parent) + : QWidget(parent), + ui_(new Ui_FileView), + model_(nullptr), + undo_stack_(new QUndoStack(this)), + task_manager_(nullptr), + storage_(new FilesystemMusicStorage("/")) +{ + + ui_->setupUi(this); + + // Icons + ui_->back->setIcon(IconLoader::Load("go-previous")); + ui_->forward->setIcon(IconLoader::Load("go-next")); + ui_->home->setIcon(IconLoader::Load("go-home")); + ui_->up->setIcon(IconLoader::Load("go-up")); + + connect(ui_->back, SIGNAL(clicked()), undo_stack_, SLOT(undo())); + connect(ui_->forward, SIGNAL(clicked()), undo_stack_, SLOT(redo())); + connect(ui_->home, SIGNAL(clicked()), SLOT(FileHome())); + connect(ui_->up, SIGNAL(clicked()), SLOT(FileUp())); + connect(ui_->path, SIGNAL(textChanged(QString)), SLOT(ChangeFilePath(QString))); + + connect(undo_stack_, SIGNAL(canUndoChanged(bool)), ui_->back, SLOT(setEnabled(bool))); + connect(undo_stack_, SIGNAL(canRedoChanged(bool)), ui_->forward, SLOT(setEnabled(bool))); + + connect(ui_->list, SIGNAL(activated(QModelIndex)), SLOT(ItemActivated(QModelIndex))); + connect(ui_->list, SIGNAL(doubleClicked(QModelIndex)), SLOT(ItemDoubleClick(QModelIndex))); + connect(ui_->list, SIGNAL(AddToPlaylist(QMimeData*)), SIGNAL(AddToPlaylist(QMimeData*))); + connect(ui_->list, SIGNAL(CopyToCollection(QList)), SIGNAL(CopyToCollection(QList))); + connect(ui_->list, SIGNAL(MoveToCollection(QList)), SIGNAL(MoveToCollection(QList))); + connect(ui_->list, SIGNAL(CopyToDevice(QList)), SIGNAL(CopyToDevice(QList))); +#ifdef HAVE_GSTREAMER + connect(ui_->list, SIGNAL(Delete(QStringList)), SLOT(Delete(QStringList))); +#endif + connect(ui_->list, SIGNAL(EditTags(QList)), SIGNAL(EditTags(QList))); + + QString filter(FileView::kFileFilter); + filter_list_ << filter.split(" "); + +} + +FileView::~FileView() { + delete ui_; +} + +void FileView::SetPath(const QString &path) { + if (!model_) + lazy_set_path_ = path; + else + ChangeFilePathWithoutUndo(path); +} + +void FileView::SetTaskManager(TaskManager *task_manager) { + task_manager_ = task_manager; +} + +void FileView::FileUp() { + + QDir dir(model_->rootDirectory()); + dir.cdUp(); + + // Is this the same as going back? If so just go back, so we can keep the + // view scroll position. + if (undo_stack_->canUndo()) { + const UndoCommand *last_dir = static_cast(undo_stack_->command(undo_stack_->index()-1)); + if (last_dir->undo_path() == dir.path()) { + undo_stack_->undo(); + return; + } + } + + ChangeFilePath(dir.path()); + +} + +void FileView::FileHome() { + ChangeFilePath(QDir::homePath()); +} + +void FileView::ChangeFilePath(const QString &new_path_native) { + + QString new_path = QDir::fromNativeSeparators(new_path_native); + + QFileInfo info(new_path); + if (!info.exists() || !info.isDir()) + return; + + QString old_path(model_->rootPath()); + if (old_path == new_path) + return; + + undo_stack_->push(new UndoCommand(this, new_path)); + +} + +void FileView::ChangeFilePathWithoutUndo(const QString &new_path) { + + ui_->list->setRootIndex(model_->setRootPath(new_path)); + ui_->path->setText(QDir::toNativeSeparators(new_path)); + + QDir dir(new_path); + ui_->up->setEnabled(dir.cdUp()); + + emit PathChanged(new_path); + +} + +void FileView::ItemActivated(const QModelIndex &index) { + if (model_->isDir(index)) + ChangeFilePath(model_->filePath(index)); +} + +void FileView::ItemDoubleClick(const QModelIndex &index) { + + if (model_->isDir(index)) + return; + + QString file_path = model_->filePath(index); + + MimeData *data = new MimeData; + data->from_doubleclick_ = true; + data->setUrls(QList() << QUrl::fromLocalFile(file_path)); + data->name_for_new_playlist_ = file_path; + + emit AddToPlaylist(data); + +} + + +FileView::UndoCommand::UndoCommand(FileView *view, const QString &new_path) + : view_(view) { + + old_state_.path = view->model_->rootPath(); + old_state_.scroll_pos = view_->ui_->list->verticalScrollBar()->value(); + old_state_.index = view_->ui_->list->currentIndex(); + + new_state_.path = new_path; +} + +void FileView::UndoCommand::redo() { + + view_->ChangeFilePathWithoutUndo(new_state_.path); + if (new_state_.scroll_pos != -1) { + view_->ui_->list->setCurrentIndex(new_state_.index); + view_->ui_->list->verticalScrollBar()->setValue(new_state_.scroll_pos); + } + +} + +void FileView::UndoCommand::undo() { + + new_state_.scroll_pos = view_->ui_->list->verticalScrollBar()->value(); + new_state_.index = view_->ui_->list->currentIndex(); + + view_->ChangeFilePathWithoutUndo(old_state_.path); + view_->ui_->list->setCurrentIndex(old_state_.index); + view_->ui_->list->verticalScrollBar()->setValue(old_state_.scroll_pos); + +} + +#ifdef HAVE_GSTREAMER +void FileView::Delete(const QStringList &filenames) { + + if (filenames.isEmpty()) + return; + + if (QMessageBox::warning(this, tr("Delete files"), + tr("These files will be deleted from disk, are you sure you want to continue?"), + QMessageBox::Yes, QMessageBox::Cancel) != QMessageBox::Yes) + return; + + DeleteFiles *delete_files = new DeleteFiles(task_manager_, storage_); + connect(delete_files, SIGNAL(Finished(SongList)), SLOT(DeleteFinished(SongList))); + delete_files->Start(filenames); + +} + +void FileView::DeleteFinished(const SongList &songs_with_errors) { + + if (songs_with_errors.isEmpty()) return; + + OrganiseErrorDialog *dialog = new OrganiseErrorDialog(this); + dialog->Show(OrganiseErrorDialog::Type_Delete, songs_with_errors); + // It deletes itself when the user closes it + +} +#endif + +void FileView::showEvent(QShowEvent *e) { + + QWidget::showEvent(e); + + if (model_) return; + + model_ = new QFileSystemModel(this); + + model_->setNameFilters(filter_list_); + // if an item fails the filter, hide it + model_->setNameFilterDisables(false); + + ui_->list->setModel(model_); + ChangeFilePathWithoutUndo(QDir::homePath()); + + if (!lazy_set_path_.isEmpty()) ChangeFilePathWithoutUndo(lazy_set_path_); + +} + +void FileView::keyPressEvent(QKeyEvent *e) { + + switch (e->key()) { + case Qt::Key_Back: + case Qt::Key_Backspace: + ui_->up->click(); + break; + case Qt::Key_Enter: + case Qt::Key_Return: + ItemDoubleClick(ui_->list->currentIndex()); + break; + } + + QWidget::keyPressEvent(e); + +} + diff --git a/src/widgets/fileview.h b/src/widgets/fileview.h new file mode 100644 index 00000000..29a54502 --- /dev/null +++ b/src/widgets/fileview.h @@ -0,0 +1,119 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef FILEVIEW_H +#define FILEVIEW_H + +#include "config.h" + +#include + +#include +#include +#include +#include + +#include "core/song.h" + +class FilesystemMusicStorage; +class MusicStorage; +class TaskManager; +class Ui_FileView; + +class QFileSystemModel; +class QUndoStack; + +class FileView : public QWidget { + Q_OBJECT + + public: + FileView(QWidget *parent = nullptr); + ~FileView(); + + static const char *kFileFilter; + + void SetPath(const QString &path); + void SetTaskManager(TaskManager *task_manager); + + void showEvent(QShowEvent*); + void keyPressEvent(QKeyEvent *e); + + signals: + void PathChanged(const QString &path); + + void AddToPlaylist(QMimeData *data); + void CopyToCollection(const QList &urls); + void MoveToCollection(const QList &urls); + void CopyToDevice(const QList &urls); + void EditTags(const QList &urls); + + private slots: + void FileUp(); + void FileHome(); + void ChangeFilePath(const QString &new_path); + void ItemActivated(const QModelIndex &index); + void ItemDoubleClick(const QModelIndex &index); + +#ifdef HAVE_GSTREAMER + void Delete(const QStringList &filenames); + void DeleteFinished(const SongList &songs_with_errors); +#endif + + private: + void ChangeFilePathWithoutUndo(const QString &new_path); + + private: + class UndoCommand : public QUndoCommand { + public: + UndoCommand(FileView *view, const QString &new_path); + + QString undo_path() const { return old_state_.path; } + + void undo(); + void redo(); + + private: + struct State { + State() : scroll_pos(-1) {} + + QString path; + QModelIndex index; + int scroll_pos; + }; + + FileView *view_; + State old_state_; + State new_state_; + }; + + Ui_FileView *ui_; + + QFileSystemModel *model_; + QUndoStack *undo_stack_; + + TaskManager *task_manager_; + std::shared_ptr storage_; + + QString lazy_set_path_; + + QStringList filter_list_; +}; + +#endif // FILEVIEW_H diff --git a/src/widgets/fileview.ui b/src/widgets/fileview.ui new file mode 100644 index 00000000..b66c3eab --- /dev/null +++ b/src/widgets/fileview.ui @@ -0,0 +1,124 @@ + + + FileView + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 0 + + + 0 + + + + + 0 + + + + + false + + + + 16 + 16 + + + + true + + + + + + + false + + + + 16 + 16 + + + + true + + + + + + + + 16 + 16 + + + + true + + + + + + + + 16 + 16 + + + + true + + + + + + + + + + + + true + + + QAbstractItemView::DragOnly + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + + 16 + 16 + + + + + + + + + FileViewList + QListView +
widgets/fileviewlist.h
+
+
+ + +
diff --git a/src/widgets/fileviewlist.cpp b/src/widgets/fileviewlist.cpp new file mode 100644 index 00000000..27ade84d --- /dev/null +++ b/src/widgets/fileviewlist.cpp @@ -0,0 +1,186 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "fileviewlist.h" + +#include "core/mimedata.h" +#include "core/utilities.h" +#include "core/logging.h" +#include "core/iconloader.h" + +FileViewList::FileViewList(QWidget *parent) + : QListView(parent), + menu_(new QMenu(this)) +{ + + //qLog(Debug) << __PRETTY_FUNCTION__; + menu_->addAction(IconLoader::Load("media-play"), tr("Append to current playlist"), this, SLOT(AddToPlaylistSlot())); + menu_->addAction(IconLoader::Load("media-play"), tr("Replace current playlist"), this, SLOT(LoadSlot())); + menu_->addAction(IconLoader::Load("document-new"), tr("Open in new playlist"), this, SLOT(OpenInNewPlaylistSlot())); + menu_->addSeparator(); + menu_->addAction(IconLoader::Load("edit-copy"), tr("Copy to collection..."), this, SLOT(CopyToCollectionSlot())); + menu_->addAction(IconLoader::Load("go-jump"), tr("Move to collection..."), this, SLOT(MoveToCollectionSlot())); + menu_->addAction(IconLoader::Load("device"), tr("Copy to device..."), this, SLOT(CopyToDeviceSlot())); + menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete from disk..."), this, SLOT(DeleteSlot())); + + menu_->addSeparator(); + menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit track information..."), this, SLOT(EditTagsSlot())); + menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, SLOT(ShowInBrowser())); + + setAttribute(Qt::WA_MacShowFocusRect, false); + +} + +void FileViewList::contextMenuEvent(QContextMenuEvent *e) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + menu_selection_ = selectionModel()->selection(); + + menu_->popup(e->globalPos()); + e->accept(); + +} + +QList FileViewList::UrlListFromSelection() const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + QList urls; + for (const QModelIndex& index : menu_selection_.indexes()) { + if (index.column() == 0) + urls << QUrl::fromLocalFile(static_cast(model())->fileInfo(index).canonicalFilePath()); + } + return urls; + +} + +MimeData *FileViewList::MimeDataFromSelection() const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + MimeData *data = new MimeData; + data->setUrls(UrlListFromSelection()); + + QList filenames = FilenamesFromSelection(); + // if just one folder selected - use it's path as the new playlist's name + if (filenames.size() == 1 && QFileInfo(filenames.first()).isDir()) { + data->name_for_new_playlist_ = filenames.first(); + // otherwise, use the current root path + } + else { + data->name_for_new_playlist_ = static_cast(model())->rootPath(); + } + + return data; + +} + +QStringList FileViewList::FilenamesFromSelection() const { + + //qLog(Debug) << __PRETTY_FUNCTION__; + QStringList filenames; + for (const QModelIndex& index : menu_selection_.indexes()) { + if (index.column() == 0) + filenames << static_cast(model())->filePath(index); + } + return filenames; + +} + +void FileViewList::LoadSlot() { + //qLog(Debug) << __PRETTY_FUNCTION__; + MimeData *data = MimeDataFromSelection(); + data->clear_first_ = true; + emit AddToPlaylist(data); +} + +void FileViewList::AddToPlaylistSlot() { + //qLog(Debug) << __PRETTY_FUNCTION__; + emit AddToPlaylist(MimeDataFromSelection()); +} + +void FileViewList::OpenInNewPlaylistSlot() { + MimeData *data = MimeDataFromSelection(); + data->open_in_new_playlist_ = true; + emit AddToPlaylist(data); +} + +void FileViewList::CopyToCollectionSlot() { + emit CopyToCollection(UrlListFromSelection()); +} + +void FileViewList::MoveToCollectionSlot() { + emit MoveToCollection(UrlListFromSelection()); +} + +void FileViewList::CopyToDeviceSlot() { + //qLog(Debug) << __PRETTY_FUNCTION__; + emit CopyToDevice(UrlListFromSelection()); +} + +void FileViewList::DeleteSlot() { + //qLog(Debug) << __PRETTY_FUNCTION__; + emit Delete(FilenamesFromSelection()); +} + +void FileViewList::EditTagsSlot() { + //qLog(Debug) << __PRETTY_FUNCTION__; + emit EditTags(UrlListFromSelection()); +} + +void FileViewList::mousePressEvent(QMouseEvent *e) { + + //qLog(Debug) << __PRETTY_FUNCTION__; + switch (e->button()) { + case Qt::XButton1: + emit Back(); + break; + case Qt::XButton2: + emit Forward(); + break; + // enqueue to playlist with middleClick + case Qt::MidButton: { + QListView::mousePressEvent(e); + + // we need to update the menu selection + menu_selection_ = selectionModel()->selection(); + + MimeData *data = new MimeData; + data->setUrls(UrlListFromSelection()); + data->enqueue_now_ = true; + emit AddToPlaylist(data); + break; + } + default: + QListView::mousePressEvent(e); + break; + } + +} + +void FileViewList::ShowInBrowser() { + //qLog(Debug) << __PRETTY_FUNCTION__; + Utilities::OpenInFileBrowser(UrlListFromSelection()); +} diff --git a/src/widgets/fileviewlist.h b/src/widgets/fileviewlist.h new file mode 100644 index 00000000..b3b8f08e --- /dev/null +++ b/src/widgets/fileviewlist.h @@ -0,0 +1,73 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef FILEVIEWLIST_H +#define FILEVIEWLIST_H + +#include "config.h" + +#include +#include + +class MimeData; + +class FileViewList : public QListView { + Q_OBJECT + + public: + FileViewList(QWidget* parent = nullptr); + + void mousePressEvent(QMouseEvent *e); + + signals: + void AddToPlaylist(QMimeData *data); + void CopyToCollection(const QList& urls); + void MoveToCollection(const QList& urls); + void CopyToDevice(const QList& urls); + void Delete(const QStringList& filenames); + void EditTags(const QList& urls); + void Back(); + void Forward(); + + protected: + void contextMenuEvent(QContextMenuEvent *e); + + private slots: + void LoadSlot(); + void AddToPlaylistSlot(); + void OpenInNewPlaylistSlot(); + void CopyToCollectionSlot(); + void MoveToCollectionSlot(); + void CopyToDeviceSlot(); + void DeleteSlot(); + void EditTagsSlot(); + void ShowInBrowser(); + + QStringList FilenamesFromSelection() const; + QList UrlListFromSelection() const; + MimeData *MimeDataFromSelection() const; + + private: + QMenu *menu_; + QItemSelection menu_selection_; +}; + +#endif // FILEVIEWLIST_H + diff --git a/src/widgets/forcescrollperpixel.cpp b/src/widgets/forcescrollperpixel.cpp new file mode 100644 index 00000000..7f42b6c1 --- /dev/null +++ b/src/widgets/forcescrollperpixel.cpp @@ -0,0 +1,45 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include "forcescrollperpixel.h" + +#include +#include +#include + + +ForceScrollPerPixel::ForceScrollPerPixel(QAbstractItemView *item_view, QObject *parent) + : QObject(parent), item_view_(item_view) { + item_view_->installEventFilter(this); +} + +bool ForceScrollPerPixel::eventFilter(QObject *object, QEvent *event) { + if (object == item_view_ && + event->type() != QEvent::Destroy && + event->type() != QEvent::WinIdChange) { + //event->type() != QEvent::AccessibilityPrepare) + item_view_->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + item_view_->verticalScrollBar()->setSingleStep(20); + } + + return QObject::eventFilter(object, event); +} diff --git a/src/widgets/forcescrollperpixel.h b/src/widgets/forcescrollperpixel.h new file mode 100644 index 00000000..4329e34f --- /dev/null +++ b/src/widgets/forcescrollperpixel.h @@ -0,0 +1,43 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef FORCESCROLLPERPIXEL_H +#define FORCESCROLLPERPIXEL_H + +#include "config.h" + +#include + +class QAbstractItemView; + +// Some KDE styles override the ScrollMode property of QAbstractItemViews. +// This helper class forces the mode back to ScrollPerPixel. +class ForceScrollPerPixel : public QObject { + public: + ForceScrollPerPixel(QAbstractItemView *item_view, QObject *parent = nullptr); + +protected: + bool eventFilter(QObject *object, QEvent *event); + +private: + QAbstractItemView *item_view_; +}; + +#endif // FORCESCROLLPERPIXEL_H diff --git a/src/widgets/freespacebar.cpp b/src/widgets/freespacebar.cpp new file mode 100644 index 00000000..2f959a91 --- /dev/null +++ b/src/widgets/freespacebar.cpp @@ -0,0 +1,220 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can 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. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include + +#include "freespacebar.h" +#include "core/utilities.h" + +const int FreeSpaceBar::kBarHeight = 20; +const int FreeSpaceBar::kBarBorderRadius = 8; +const int FreeSpaceBar::kMarkerSpacing = 32; +const int FreeSpaceBar::kLabelBoxSize = 12; +const int FreeSpaceBar::kLabelBoxPadding = 4; +const int FreeSpaceBar::kLabelSpacing = 16; + +const QRgb FreeSpaceBar::kColorBg1 = qRgb(214, 207, 200); +const QRgb FreeSpaceBar::kColorBg2 = qRgb(234, 226, 218); +const QRgb FreeSpaceBar::kColorAdd1 = qRgb(136, 180, 229); +const QRgb FreeSpaceBar::kColorAdd2 = qRgb(72, 146, 229); +const QRgb FreeSpaceBar::kColorBar1 = qRgb(250, 148, 76); +const QRgb FreeSpaceBar::kColorBar2 = qRgb(214, 102, 24); +const QRgb FreeSpaceBar::kColorBorder = qRgb(174, 168, 162); + + +FreeSpaceBar::FreeSpaceBar(QWidget *parent) + : QWidget(parent), + free_(100), + additional_(0), + total_(100), + free_text_(tr("Available")), + additional_text_(tr("New songs")), + used_text_(tr("Used")) +{ + setMinimumHeight(sizeHint().height()); +} + +QSize FreeSpaceBar::sizeHint() const { + return QSize(150, kBarHeight + kLabelBoxPadding + fontMetrics().height()); +} + +void FreeSpaceBar::paintEvent(QPaintEvent *) { + + // Geometry + QRect bar_rect(rect()); + bar_rect.setHeight(kBarHeight); + + QRect reflection_rect(bar_rect); + reflection_rect.moveTop(reflection_rect.bottom()); + + QRect labels_rect(rect()); + labels_rect.setTop(labels_rect.top() + kBarHeight + kLabelBoxPadding); + + // Draw the reflection + // Create the reflected pixmap + QImage reflection(reflection_rect.size(), QImage::Format_ARGB32_Premultiplied); + reflection.fill(palette().color(QPalette::Background).rgba()); + QPainter p(&reflection); + + // Set up the transformation + QTransform transform; + transform.scale(1.0, -1.0); + transform.translate(0.0, -reflection.height()); + p.setTransform(transform); + + // Draw the bar + DrawBar(&p, QRect(QPoint(0, 0), reflection.size())); + + // Make it fade out towards the bottom + QLinearGradient fade_gradient(reflection.rect().topLeft(), reflection.rect().bottomLeft()); + fade_gradient.setColorAt(0.0, QColor(0, 0, 0, 0)); + fade_gradient.setColorAt(1.0, QColor(0, 0, 0, 128)); + + p.setCompositionMode(QPainter::CompositionMode_DestinationIn); + p.fillRect(reflection.rect(), fade_gradient); + + p.end(); + + // Draw on the widget + p.begin(this); + DrawBar(&p, bar_rect); + p.drawImage(reflection_rect, reflection); + DrawText(&p, labels_rect); + +} + +void FreeSpaceBar::DrawBar(QPainter* p, const QRect &r) { + + p->setRenderHint(QPainter::Antialiasing, true); + p->setRenderHint(QPainter::HighQualityAntialiasing, true); + + QRect bar_rect(r); + bar_rect.setWidth(float(bar_rect.width()) * (float(total_ - free_) / total_)); + + QLinearGradient background_gradient(r.topLeft(), r.bottomLeft()); + background_gradient.setColorAt(0, kColorBg1); + background_gradient.setColorAt(1, kColorBg2); + + QLinearGradient bar_gradient(bar_rect.topLeft(), bar_rect.bottomLeft()); + bar_gradient.setColorAt(0, kColorBar1); + bar_gradient.setColorAt(1, kColorBar2); + + // Draw the background + p->setPen(Qt::NoPen); + p->setBrush(background_gradient); + p->drawRoundedRect(r, kBarBorderRadius, kBarBorderRadius); + + // Create a path to use for clipping the bars + QPainterPath clip_path; + clip_path.addRoundedRect(r, kBarBorderRadius, kBarBorderRadius); + p->setClipPath(clip_path); + + // Draw any additional space + if (additional_) { + QRect additional_rect(bar_rect); + additional_rect.setLeft(bar_rect.right()); + additional_rect.setWidth(float(r.width()) * (float(qMin(free_, additional_)) / total_) + 1); + + QLinearGradient additional_gradient(additional_rect.topLeft(), additional_rect.bottomLeft()); + additional_gradient.setColorAt(0, kColorAdd1); + additional_gradient.setColorAt(1, kColorAdd2); + + p->fillRect(additional_rect, additional_gradient); + } + + // Draw the bar foreground + p->fillRect(bar_rect, bar_gradient); + + // Draw a border + p->setClipping(false); + p->setPen(kColorBorder); + p->setBrush(Qt::NoBrush); + p->drawRoundedRect(r, kBarBorderRadius, kBarBorderRadius); + + // Draw marker lines over the top every few pixels + p->setOpacity(0.35); + p->setRenderHint(QPainter::Antialiasing, false); + p->setPen(QPen(palette().color(QPalette::Light), 1.0)); + for (int x = r.left() + kMarkerSpacing ; x < r.right() ; x += kMarkerSpacing) { + p->drawLine(x, r.top() + 2, x, r.bottom() - 2); + } + + p->setOpacity(1.0); + +} + +void FreeSpaceBar::DrawText(QPainter* p, const QRect &r) { + + QFont small_font(font()); + small_font.setPointSize(small_font.pointSize() - 1); + small_font.setBold(true); + QFontMetrics small_metrics(small_font); + p->setFont(small_font); + + // Work out the geometry for the text + QList