tomahawk-player/data/images/inbox.svg000664 001750 001750 00000007055 12661705042 021017 0ustar00stefanstefan000000 000000 Slice 1 Created with Sketch (http://www.bohemiancoding.com/sketch) tomahawk-player/src/libtomahawk/widgets/PlaylistsModel.h000664 001750 001750 00000003056 12661705042 024653 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #pragma once #ifndef TOMAHAWK_PLAYLISTSMODEL_H #define TOMAHAWK_PLAYLISTSMODEL_H #include "DllMacro.h" #include "Typedefs.h" #include namespace Tomahawk { class PlaylistsModelPrivate; class DLLEXPORT PlaylistsModel : public QAbstractListModel { Q_OBJECT public: explicit PlaylistsModel( const QList& playlists, QObject* parent = 0 ); virtual ~PlaylistsModel(); virtual QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const; virtual int rowCount( const QModelIndex& parent = QModelIndex() ) const; protected: QScopedPointer d_ptr; void updateArtists(); private: Q_DECLARE_PRIVATE( PlaylistsModel ) }; } // namespace Tomahawk #endif // TOMAHAWK_PLAYLISTSMODEL_H tomahawk-player/thirdparty/libportfwd/third-party/miniupnpc-1.6/portlistingparse.c000664 001750 001750 00000006700 12661705042 031673 0ustar00stefanstefan000000 000000 /* $Id: portlistingparse.c,v 1.4 2011/03/18 11:02:17 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2011 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include "portlistingparse.h" #include "minixml.h" /* list of the elements */ static const struct { const portMappingElt code; const char * const str; } elements[] = { { PortMappingEntry, "PortMappingEntry"}, { NewRemoteHost, "NewRemoteHost"}, { NewExternalPort, "NewExternalPort"}, { NewProtocol, "NewProtocol"}, { NewInternalPort, "NewInternalPort"}, { NewInternalClient, "NewInternalClient"}, { NewEnabled, "NewEnabled"}, { NewDescription, "NewDescription"}, { NewLeaseTime, "NewLeaseTime"}, { PortMappingEltNone, NULL} }; /* Helper function */ static UNSIGNED_INTEGER atoui(const char * p, int l) { UNSIGNED_INTEGER r = 0; while(l > 0 && *p) { if(*p >= '0' && *p <= '9') r = r*10 + (*p - '0'); else break; p++; l--; } return r; } /* Start element handler */ static void startelt(void * d, const char * name, int l) { int i; struct PortMappingParserData * pdata = (struct PortMappingParserData *)d; pdata->curelt = PortMappingEltNone; for(i = 0; elements[i].str; i++) { if(memcmp(name, elements[i].str, l) == 0) { pdata->curelt = elements[i].code; break; } } if(pdata->curelt == PortMappingEntry) { struct PortMapping * pm; pm = calloc(1, sizeof(struct PortMapping)); LIST_INSERT_HEAD( &(pdata->head), pm, entries); } } /* End element handler */ static void endelt(void * d, const char * name, int l) { struct PortMappingParserData * pdata = (struct PortMappingParserData *)d; pdata->curelt = PortMappingEltNone; } /* Data handler */ static void data(void * d, const char * data, int l) { struct PortMapping * pm; struct PortMappingParserData * pdata = (struct PortMappingParserData *)d; pm = pdata->head.lh_first; if(!pm) return; if(l > 63) l = 63; switch(pdata->curelt) { case NewRemoteHost: memcpy(pm->remoteHost, data, l); pm->remoteHost[l] = '\0'; break; case NewExternalPort: pm->externalPort = (unsigned short)atoui(data, l); break; case NewProtocol: if(l > 3) l = 3; memcpy(pm->protocol, data, l); pm->protocol[l] = '\0'; break; case NewInternalPort: pm->internalPort = (unsigned short)atoui(data, l); break; case NewInternalClient: memcpy(pm->internalClient, data, l); pm->internalClient[l] = '\0'; break; case NewEnabled: pm->enabled = (unsigned char)atoui(data, l); break; case NewDescription: memcpy(pm->description, data, l); pm->description[l] = '\0'; break; case NewLeaseTime: pm->leaseTime = atoui(data, l); break; default: break; } } /* Parse the PortMappingList XML document for IGD version 2 */ void ParsePortListing(const char * buffer, int bufsize, struct PortMappingParserData * pdata) { struct xmlparser parser; memset(pdata, 0, sizeof(struct PortMappingParserData)); LIST_INIT(&(pdata->head)); /* init xmlparser */ parser.xmlstart = buffer; parser.xmlsize = bufsize; parser.data = pdata; parser.starteltfunc = startelt; parser.endeltfunc = endelt; parser.datafunc = data; parser.attfunc = 0; parsexml(&parser); } void FreePortListing(struct PortMappingParserData * pdata) { struct PortMapping * pm; while((pm = pdata->head.lh_first) != NULL) { LIST_REMOVE(pm, entries); free(pm); } } tomahawk-player/src/tomahawk/CMakeLists.unix.cmake000664 001750 001750 00000000450 12661705042 023356 0ustar00stefanstefan000000 000000 ADD_DEFINITIONS( -ggdb ) ADD_DEFINITIONS( -Wall ) ADD_DEFINITIONS( -g ) ADD_DEFINITIONS( -fno-operator-names ) ADD_DEFINITIONS( -fPIC ) IF( APPLE ) INCLUDE( "CMakeLists.osx.cmake" ) ENDIF( APPLE ) IF( UNIX AND NOT APPLE ) INCLUDE( "CMakeLists.linux.cmake" ) ENDIF( UNIX AND NOT APPLE ) tomahawk-player/src/tomahawk/CMakeLists.txt000664 001750 001750 00000015332 12661705042 022140 0ustar00stefanstefan000000 000000 PROJECT( tomahawk ) CMAKE_MINIMUM_REQUIRED( VERSION 2.8 ) include( AddAppIconMacro ) # SET( CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" ) # SET( CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" ) # SET( CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" ) IF( NOT CMAKE_BUILD_TYPE STREQUAL "Release" AND NOT CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") MESSAGE( "Building in debug mode, enabling all debug updates" ) ENDIF() # build plugins # use glob, but hardcoded list for now: #FILE( GLOB plugindirs "src/plugins/*" ) #FOREACH( moddir ${plugindirs} ) # MESSAGE( status "Building plugin: ${moddir}" ) # ADD_SUBDIRECTORY( ${moddir} ) #ENDFOREACH( moddir ) SET( tomahawkSources ${tomahawkSources} AclRegistryImpl.cpp ShortcutHandler.cpp UbuntuUnityHack.cpp TomahawkApp.cpp main.cpp ) IF( LIBLASTFM_FOUND ) SET(tomahawkSources ${tomahawkSources} Scrobbler.cpp ) ENDIF( LIBLASTFM_FOUND ) SET( tomahawkSourcesGui ${tomahawkSourcesGui} dialogs/DiagnosticsDialog.cpp dialogs/LoadPlaylistDialog.cpp dialogs/SettingsDialog.cpp sourcetree/SourcesModel.cpp sourcetree/SourcesProxyModel.cpp sourcetree/SourceTreeView.cpp sourcetree/SourceDelegate.cpp sourcetree/items/ScriptCollectionItem.cpp sourcetree/items/SourceTreeItem.cpp sourcetree/items/SourceItem.cpp sourcetree/items/PlaylistItems.cpp sourcetree/items/CategoryItems.cpp sourcetree/items/GenericPageItems.cpp sourcetree/items/LovedTracksItem.cpp sourcetree/items/TemporaryPageItem.cpp sourcetree/items/GroupItem.cpp sourcetree/items/CollectionItem.cpp sourcetree/items/HistoryItem.cpp sourcetree/items/InboxItem.cpp sourcetree/items/QueueItem.cpp TomahawkTrayIcon.cpp AudioControls.cpp TomahawkWindow.cpp widgets/ContainedMenuButton.cpp widgets/AccountListView.cpp widgets/AccountListWidget.cpp widgets/AccountModelFactoryProxy.cpp widgets/AccountWidget.cpp widgets/AccountsPopupWidget.cpp widgets/AccountsToolButton.cpp widgets/SlideSwitchButton.cpp widgets/SocialWidget.cpp widgets/SplashWidget.cpp ) SET( tomahawkUI ${tomahawkUI} dialogs/DiagnosticsDialog.ui dialogs/HostDialog.ui dialogs/LoadPlaylistDialog.ui dialogs/ProxyDialog.ui dialogs/Settings_Accounts.ui dialogs/Settings_Advanced.ui dialogs/Settings_Collection.ui TomahawkWindow.ui AudioControls.ui widgets/SocialWidget.ui ) INCLUDE_DIRECTORIES( . ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/../libtomahawk sourcetree ../libtomahawk mac ${THIRDPARTY_DIR}/libcrashreporter-qt/src/ ${THIRDPARTY_DIR}/kdsingleapplicationguard/ ${TAGLIB_INCLUDES} ${LIBATTICA_INCLUDE_DIR} ${ECHONEST_INCLUDE_DIR} ${LIBLASTFM_INCLUDE_DIRS} ${Boost_INCLUDE_DIR} ) SET( OS_SPECIFIC_LINK_LIBRARIES "" ) IF( WIN32 ) INCLUDE( "CMakeLists.win32.cmake" ) ENDIF( WIN32 ) IF( UNIX ) INCLUDE( "CMakeLists.unix.cmake" ) ENDIF( UNIX ) IF( APPLE ) SET( CMAKE_LINKER_FLAGS "-headerpad_max_install_names ${CMAKE_LINKER_FLAGS}" ) INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/thirdparty/SPMediaKeyTap ) SET( tomahawkSources ${tomahawkSources} mac/TomahawkApp_Mac.mm mac/MacShortcutHandler.cpp ) ENDIF( APPLE ) IF( QCA2_FOUND ) INCLUDE_DIRECTORIES( ${QCA2_INCLUDE_DIR} ) ENDIF( QCA2_FOUND ) INCLUDE(GNUInstallDirs) # currently only in libtomahawk, we might want to properly split what's in which config file # configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.h.in # ${CMAKE_CURRENT_BINARY_DIR}/config.h) # configure_file(${CMAKE_CURRENT_SOURCE_DIR}/TomahawkVersion.h.in # ${CMAKE_CURRENT_BINARY_DIR}/TomahawkVersion.h) # translations include( ${CMAKE_SOURCE_DIR}/lang/translations.cmake ) add_tomahawk_translations( ${TOMAHAWK_TRANSLATION_LANGUAGES} ) SET( final_src ${final_src} ${tomahawkMoc} ${tomahawkSources} ${trans_outfile}) IF( BUILD_GUI ) LIST(APPEND tomahawkSources ${tomahawkSourcesGui}) qt_wrap_ui( tomahawkUI_H ${tomahawkUI} ) ENDIF() tomahawk_add_app_icon( tomahawkSources Tomahawk "${CMAKE_SOURCE_DIR}/data/icons/tomahawk-icon-*.png" ) qt_add_resources( RC_SRCS "../../resources.qrc" ) SET( final_src ${final_src} ${tomahawkUI_H} ${tomahawkMoc} ${tomahawkSources} ${RC_SRCS} ) if(APPLE) set(TOMAHAWK_RUNTIME_OUTPUT_NAME "Tomahawk") else() set(TOMAHAWK_RUNTIME_OUTPUT_NAME "tomahawk") endif() ADD_EXECUTABLE( tomahawk_bin WIN32 MACOSX_BUNDLE ${final_src} ) SET_TARGET_PROPERTIES(tomahawk_bin PROPERTIES AUTOMOC TRUE MACOSX_BUNDLE_INFO_PLIST "${CMAKE_BINARY_DIR}/Info.plist" RUNTIME_OUTPUT_NAME ${TOMAHAWK_RUNTIME_OUTPUT_NAME} ) qt5_use_modules(tomahawk_bin Core Widgets Network Sql WebKitWidgets) if(WIN32) qt5_use_modules(tomahawk_bin WinExtras) endif() MESSAGE( STATUS "OS_SPECIFIC_LINK_LIBRARIES: ${OS_SPECIFIC_LINK_LIBRARIES}" ) IF( LIBLASTFM_FOUND ) LIST(APPEND LINK_LIBRARIES ${LINK_LIBRARIES} ${LIBLASTFM_LIBRARIES} ) ENDIF( LIBLASTFM_FOUND ) IF( QCA2_FOUND ) LIST(APPEND LINK_LIBRARIES ${LINK_LIBRARIES} ${QCA2_LIBRARIES} ) ENDIF( QCA2_FOUND ) IF( WITH_CRASHREPORTER ) LIST(APPEND LINK_LIBRARIES ${LINK_LIBRARIES} crashreporter-handler ) ENDIF() TARGET_LINK_LIBRARIES( tomahawk_bin kdsingleapplicationguard ${LINK_LIBRARIES} ${TOMAHAWK_WIDGETS_LIBRARIES} ${TOMAHAWK_PLAYDARAPI_LIBRARIES} ${TOMAHAWK_LIBRARIES} ${OS_SPECIFIC_LINK_LIBRARIES} ${QT_LIBRARIES} ${MAC_EXTRA_LIBS} ${ECHONEST_LIBRARIES} ${TAGLIB_LIBRARIES} ${Boost_LIBRARIES} ) IF( APPLE ) IF( HAVE_SPARKLE ) MESSAGE("Sparkle Found, installing framekwork in bundle") INSTALL(DIRECTORY "${SPARKLE}/Versions/Current/Resources" DESTINATION "${CMAKE_BINARY_DIR}/Tomahawk.app/Contents/Frameworks/Sparkle.framework") ENDIF( HAVE_SPARKLE ) ENDIF( APPLE ) INSTALL( TARGETS tomahawk_bin BUNDLE DESTINATION . RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) IF( UNIX AND NOT APPLE AND KDE4_INSTALLED AND LEGACY_KDE_INTEGRATION ) #install protocol file FILE( READ ${CMAKE_SOURCE_DIR}/admin/unix/tomahawk.protocol protocol ) STRING( REPLACE "/path/to/binary" # match this "${CMAKE_INSTALL_FULL_BINDIR}/tomahawk" # this is linux (kde) so pretty safe I think edited_protocol # save in this variable "${protocol}" # from the contents of this var ) FILE( WRITE ${CMAKE_BINARY_DIR}/tomahawk.protocol "${edited_protocol}" ) IF( ${SERVICES_INSTALL_DIR} ) SET( PROTOCOL_INSTALL_DIR ${SERVICES_INSTALL_DIR} ) ELSE() SET( PROTOCOL_INSTALL_DIR "${CMAKE_INSTALL_FULL_DATADIR}/kde4/services" ) ENDIF() INSTALL( FILES ${CMAKE_BINARY_DIR}/tomahawk.protocol DESTINATION ${PROTOCOL_INSTALL_DIR} ) ENDIF() #INCLUDE( "CPack.txt" ) tomahawk-player/CMakeModules/CMakeDateStamp.cmake000664 001750 001750 00000001176 12661705042 023075 0ustar00stefanstefan000000 000000 find_program(DATE_EXECUTABLE NAMES date) mark_as_advanced(DATE_EXECUTABLE) if(DATE_EXECUTABLE) execute_process( COMMAND ${DATE_EXECUTABLE} +%Y OUTPUT_VARIABLE CMAKE_DATESTAMP_YEAR OUTPUT_STRIP_TRAILING_WHITESPACE WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) execute_process( COMMAND ${DATE_EXECUTABLE} +%m OUTPUT_VARIABLE CMAKE_DATESTAMP_MONTH OUTPUT_STRIP_TRAILING_WHITESPACE WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) execute_process( COMMAND ${DATE_EXECUTABLE} +%d OUTPUT_VARIABLE CMAKE_DATESTAMP_DAY OUTPUT_STRIP_TRAILING_WHITESPACE WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) endif() tomahawk-player/data/images/add-contact.svg000664 001750 001750 00000007425 12661705042 022062 0ustar00stefanstefan000000 000000 Slice 1 Created with Sketch (http://www.bohemiancoding.com/sketch) tomahawk-player/lang/tomahawk_sq.ts000664 001750 001750 00000606672 12661705042 020622 0ustar00stefanstefan000000 000000 ACLJobDelegate Allow %1 to connect and stream from you? Lejo %1 në lidhuni dhe transmeto nga ju? Allow Streaming Lejo Transmetimin Deny Access Blloko Aksesin ACLJobItem Tomahawk needs you to decide whether %1 is allowed to connect. Tomahawk duhët të vendosë nëse %1 lejohet të lidhet. AccountFactoryWrapper Add Account Shto Llogari AccountFactoryWrapperDelegate Online në linjë Connecting... Lidhu... Offline Jo N'Linje AccountListWidget Connections Lidhje Connect &All Lidh &T'gjitha Disconnect &All Shkeput T'gjitha ActionCollection &Listen Along &Dëgjo Sëbashku Stop &Listening Along Ndalo &Dëgjo Sëbashku &Follow in Real-Time &Listen Privately &Dëgjo privatisht &Load Playlist &Ngarko Listën &Load Station &Rename Playlist &Riemëro Listën &Rename Station &Copy Playlist Link &Kopjo Linkun e Listës &Play &Luaj &Stop &Ndalo &Previous Track &Kënga Mëparshme &Next Track &Kënga Tjetër &Quit &Lëre U&pdate Collection Azhorno Koleksionin Fully &Rescan Collection Import Playlist... Show Offline Friends &Configure Tomahawk... &Konfiguro Tomahawk... Minimize Minimizo Zoom Zoom Enter Full Screen Futu Ekran të Plotë Hide Menu Bar Diagnostics... Diagnostikimi... About &Tomahawk... Rreth &Tomahawk... &Legal Information... Informacioni &Ligjorë &View Logfile Check For Updates... Kërko Për Azhornime... 0.8 0.8 Report a Bug Get Support Help Us Translate &Controls &Kontrollon &Settings &Cilësime &Help &Ndihmë What's New in ... &Window &Dritare Main Menu Menuja Kryesore AlbumInfoWidget Top Hits Album Details AlbumModel All albums from %1 Të gjithë albumet nga %1 All albums T'gjithë Albumet ArtistInfoWidget Top Albums Top Hits Hitet Krye Show More Biography Related Artists Artistë Lidhur Sorry, we could not find any albums for this artist! Na vjen keq, ne nuk mund të gjejm albumin për këtë artist! Sorry, we could not find any related artists! Na vjen keq, ne nuk mund të gjejm ndonjë artist në lidhje! Sorry, we could not find any top hits for this artist! Na vjen keq, ne nuk mund të gjejm hitet e parë për këtë artist! Music Muzikë Songs Këngë Albums Albume AudioControls Shuffle Përziej Repeat Përsërit Time Elapsed Time Remaining Playing from %1 Luan nga %1 AudioEngine Sorry, Tomahawk couldn't find the track '%1' by %2 Na vjen keq, Tomahawk nuk mund të gjejë këngën '%1' nga %2 Sorry, Tomahawk couldn't find the artist '%1' Na vjen keq, Tomahawk nuk mund të gjejë artistin '%1' Sorry, Tomahawk couldn't find the album '%1' by %2 Na vjen keq, Tomahawk nuk mund të gjejë albumin '%1' nga %2 Sorry, couldn't find any playable tracks CaptionLabel Close Mbyll CategoryAddItem Create new Playlist Krijo listë të re Create new Station Krijo Stacion Te Ri New Station Stacion i'ri %1 Station %1 Stacion CategoryItem Playlists Lista Stations Stacione ClearButton Clear Pastro CollectionItem Collection Koleksion CollectionViewPage Sorry, there are no albums in this collection! Artists Artistë Albums Albume Songs Këngë After you have scanned your music collection you will find your tracks right here. This collection is empty. Ky koleksion është bosh. Cloud collections aren't supported in the flat view yet. We will have them covered soon. Switch to another view to navigate them. ColumnItemDelegate Unknown Panjohur ColumnView Sorry, your filter '%1' did not match any results. ColumnViewPreviewWidget Composer: Duration: Kohëzgjatje: Bitrate: Year: Age: Mosha: %1 kbps %1 kbps ContextView Playlist Details This playlist is currently empty. This playlist is currently empty. Add some tracks to it and enjoy the music! DelegateConfigWrapper About Rreth Delete Account Fshi Llogari Config Error Konfigurim Gabimi About this Account Rreth kësaj Llogarie DiagnosticsDialog Tomahawk Diagnostics Diagnostikimi Tomahawk &Copy to Clipboard Open &Log-file EchonestSteerer Steer this station: Much less Shumë më pak Less Pak A bit less Pak më pak Keep at current Mbajë aktualen A bit more Pak më shumë More Më shumë Much more Shumë më shumë Tempo Ritëm Loudness Zhurmshëm Danceability Energy Energji Song Hotttnesss Artist Hotttnesss Artist Familiarity Njohja Artistit By Description Nga Përshkrimi Enter a description Shkruani një përshkrim Apply steering command Aplikoni komandën drejtuese Reset all steering commands Rivë gjitha komandat drejtuese FilterHeader Filter... Filter... GlobalActionManager Resolver installation from file %1 failed. Install plug-in <b>%1</b> %2<br/>by <b>%3</b><br/><br/>You are attempting to install a Tomahawk plug-in from an unknown source. Plug-ins from untrusted sources may put your data at risk.<br/>Do you want to install this plug-in? HatchetAccountConfig Connect to your Hatchet account <html><head/><body><p><a href="http://blog.hatchet.is">Learn More</a> and/or <a href="http://hatchet.is/register"><span style=" text-decoration: underline; color:#0000ff;">Create Account</span></a></p></body></html> Enter One-time Password (OTP) Username Hatchet username Password: Fjalëkalim: Hatchet password Login Hyrje HistoryWidget Recently Played Tracks Your recently played tracks %1's recently played tracks Sorry, we could not find any recent plays! HostDialog Host Settings Configure your external IP address or host name here. Make sure to manually forward the selected port to this host on your router. Static Host Name: Static Port: Automatically detect external IP address InboxItem Inbox Kutia InboxJobItem Sent %1 by %2 to %3. Dërguar %1 nga %2 tek %3. %1 sent you %2 by %3. %1 dërguar ty %2 nga %3. InboxPage Inbox Details Detajet Kutia Your friends have not shared any recommendations with you yet. Connect with them and share your musical gems! IndexingJobItem Indexing Music Library JSResolver Script Resolver Warning: API call %1 returned data synchronously. LastFmConfig Scrobble tracks to Last.fm Username: Përdoruesi: Password: Fjalëkalimi: Test Login Hyrje Testi Import Playback History Synchronize Loved Tracks Sinkronizo Këngët Dashurisë LatchedStatusItem %1 is listening along with you! %1 është duke dëgjuar së bashku me ju! LoadPlaylist Load Playlist Enter the URL of the hosted playlist (e.g. .xspf format) or click the button to select a local M3U of XSPF playlist to import. Playlist URL Enter URL... ... ... Automatically Update (upon changes to hosted playlist) To import a playlist from Spotify, Rdio, Beats, etc. - simply drag the link into Tomahawk. LoadPlaylistDialog Load Playlist Playlists (*.xspf *.m3u *.jspf) LovedTracksItem Top Loved Tracks Këngë Dashurie Hite Favorites Sorry, we could not find any of your Favorites! The most loved tracks from all your friends Këngët e dashurisë nga të gjithë shokët tuaj All of your loved tracks Të gjitha këngët e dashurise tuaja All of %1's loved tracks Të gjitha %1 këngët e dashurise MetadataEditor Tags Etiketa Title: Titull: Title... Titull... Artist: Artisti: Artist... Artisti... Album: Albumi: Album... Albumi... Track Number: Duration: Kohëzgjatje: 00.00 00.00 Year: Viti: Bitrate: File Dokumenti File Name: Emer Dokumenti: File Name... Emer Dokumenti... File Size... Masë Dokumentit File size... Masë Dokumenti... File Size: Masë Dokumenti: Back Mbrapa Forward Përpara Error Gabim Could not write tags to file: %1 Properties Cilësimet NetworkActivityWidget Trending Tracks Hot Playlists Trending Artists PlayableModel Artist Artisti Title Titull Composer Kompozer Album Albumi Track Kënga Duration Kohëzgjatje Bitrate Age Mosha Year Viti Size Masë Origin Origjina Accuracy Saktësi Perfect match Përshtatje e përkryer Very good match Përshtatje shumë e mirë Good match Përshtatje e mirë Vague match Përshtatje e paqartë Bad match Përshtatje e keqe Very bad match Përshtatje shumë e keqe Not available Jo në dispozicion Searching... Name Emri PlaylistItemDelegate played %1 by you Luajtur %1 nga ju played %1 by %2 Dëgjuar %1 nga %2 PlaylistModel A playlist you created %1. Një listë e krijuar nga ju %1. A playlist by %1, created %2. Një listë nga %1, krijuar %2. All tracks by %1 on album %2 Të gjithë këngët %1 në album %2 All tracks by %1 Të gjitha këngët nga %1 ProxyDialog Proxy Settings Cilësime Proxy Hostname of proxy server Host Port Port Proxy login User Përdorues Password Fjalëkalim Proxy password Fjalëkalim proxi No Proxy Hosts: (Overrides system proxy) localhost *.example.com (space separated) Use proxy for DNS lookups? QObject %n year(s) ago %n vit më parë%n vite më parë %n year(s) %n vit%n vite %n month(s) ago %n muaj më parë%n muaj më parë %n month(s) %n muaj%n muaj %n week(s) ago %n javë më parë%n javë më parë %n week(s) %n javë%n javë %n day(s) ago %n dit më parë%n ditë më parë %n day(s) %n dit%n ditë %n hour(s) ago %n orë më parë%n orë më parë %n hour(s) %n orë%n orë %1 minutes ago %1 minutë më parë %1 minutes %1 minutë just now tani Friend Finders Shok Kërkues Music Finders Kërkues Muzike Status Updaters Gjëndja Azhornimeve %1 Config Songs Beginning of a sentence summary No configured filters! and Inserted between items in a list of two , Inserted between items in a list , . Inserted when ending a sentence summary . , and Inserted between the last two items in a list of more than two , dhe and Inserted before the last item in a list dhe and Inserted before the sorting summary in a sentence summary dhe QSQLiteResult No query Parameter count mismatch QueueItem Queue QueueView Open Queue Hap Radhë Queue Details Queue The queue is currently empty. Drop something to enqueue it! ResolverConfigDelegate Not found: %1 Nuk u gjënd: %1 Failed to load: %1 Dështoi të ngarkohej: %1 ScannerStatusItem Scanning Collection ScriptCollectionHeader Reload Collection Ngarko Koleksionin ScriptEngine Resolver Error: %1:%2 %3 SSL Error You have asked Tomahawk to connect securely to <b>%1</b>, but we can't confirm that your connection is secure:<br><br><b>%2</b><br><br>Do you want to trust this connection? Trust certificate SearchLineEdit Search Kërko SearchWidget Search: %1 kërko: %1 Results for '%1' Rezultate për '%1' Songs Këngë Show More Artists Artistë Albums Albume Sorry, we could not find any artists! Sorry, we could not find any albums! Na vjen keq, ne nuk mund të gjejmë ndonjë album! Sorry, we could not find any songs! Na vjen keq, ne nuk mund të gjejmë ndonjë këngë! Servent Automatically detecting external IP failed: Could not parse JSON response. Automatically detecting external IP failed: %1 SettingsDialog Collection Koleksion Advanced Avancuar All T'gjithë Install Plug-In... Some changed settings will not take effect until Tomahawk is restarted Disa ndyshime cilësimesh nuk do të hyjnë në fuqi deri kur Tomahawk është rifilluar Configure the accounts and services used by Tomahawk to search and retrieve music, find your friends and update your status. Konfiguro llogaritë dhe shërbimet e përdorura nga TOMAHAWK për të kërkuar dhe të rifitoj muzikë, gjej shokët tuaj dhe përditëso statusin tuaj. Plug-Ins Manage how Tomahawk finds music on your computer. Menaxho se si Tomahawk gjen muzikë në kompjuterin tuaj. Configure Tomahawk's advanced settings, including network connectivity settings, browser interaction and more. Open Directory Hap Skedarë Install resolver from file Instalo zbërthyesit nga dokumenti Tomahawk Resolvers (*.axe *.js);;All files (*) Tomahawk Zgjidhës (*.axe *.js);;të gjithë dokumentet (*) Delete all Access Control entries? Do you really want to delete all Access Control entries? You will be asked for a decision again for each peer that you connect to. Information Informacion Settings_Accounts Filter by Capability: Settings_Advanced Remote Peer Connection Method Active (your host needs to be directly reachable) UPnP / Automatic Port Forwarding (recommended) Manual Port Forwarding Host Settings... SOCKS Proxy Use SOCKS Proxy Proxy Settings... Other Settings Cilësime të Tjera Allow web browsers to interact with Tomahawk (recommended) Allow other computers to interact with Tomahawk (not recommended yet) Send Tomahawk Crash Reports Show Notifications on song change Clear All Access Control Entries Settings_Collection Folders to scan for music: Due to the unique way Tomahawk works, your music files must at least have Artist & Title metadata/ID3 tags to be added to your Collection. + + - - The Echo Nest supports keeping track of your catalog metadata and using it to craft personalized radios. Enabling this option will allow you (and all your friends) to create automatic playlists and stations based on your personal taste profile. Upload Collection info to enable personalized "User Radio" Watch for changes (automatically update Collection) Time between scans (in seconds): SlideSwitchButton On Ndezur Off Fikur SocialWidget Facebook Facebook Twitter Twitter Tweet Tweet Listening to "%1" by %2. %3 Dëgjo në "%1" nga %2. %3 Listening to "%1" by %2 on "%3". %4 Dëgjo tek "%1" nga %2 në "%3". %4 %1 characters left %1 karaktere të mbetur SourceDelegate All available tracks T'gjithë këngët dispozicion Drop to send tracks Show Shfaq Hide Fsheh SourceInfoWidget Recent Albums Albume Fundit Latest Additions Shtimet Fundit Recently Played Tracks New Additions Shtimet Reja My recent activity Aktivitetet e fundit t'mia Recent activity from %1 SourceItem Latest Additions Shtimet e Fundit History SuperCollection KoleksionSuper Latest additions to your collection Futjet më të fundit për koleksionin tuaj Latest additions to %1's collection Futjet më të fundit për koleksionin %1 Sorry, we could not find any recent additions! Na vjen keq, ne nuk mund të gjejm ndonjë Shtim të fundit! SourceTreeView &Copy Link &Kopjo Linkun &Delete %1 &Fshi %1 Add to my Playlists Shto tek Lista ime Add to my Automatic Playlists Shto tek Lista ime Automatike Add to my Stations Shto tek Stacioni im &Export Playlist &Eksporto Listën playlist Lista automatic playlist listë automatike station stacion Would you like to delete the %1 <b>"%2"</b>? e.g. Would you like to delete the playlist named Foobar? Dëshironi të fshini %1 <b>"%2"</b>? Delete Fshi Save XSPF Ruaj XSPF Playlists (*.xspf) Lista (*.xspf) SourcesModel Group Grupe Source Collection Koleksion Playlist Lista Automatic Playlist Listë Automatike Station Stacion Cloud Collections Discover Zbulo Open Pages Your Music Muzika Juaj Friends Shokë SpotifyConfig Configure your Spotify account Konfiguro Llogarine tuaj Spotify Username or Facebook Email Përdorues ose Adresë Facebook Log In Hyrni Right click on any Tomahawk playlist to sync it to Spotify. Kliko në të djathtë në cilënbo listë të Tomahawk për sinkronizimin me Spotify. Select All Z'gjidh T'Gjithë Sync Starred tracks to Loved tracks High Quality Streams Use this to force Spotify to never announce listening data to Social Networks Always run in Private Mode Spotify playlists to keep in sync: Mbajë listën Spotify të sinkronizuar: Delete Tomahawk playlist when removing synchronization Fshij listën Tomahawk kur të hiqni sinkronizimin Username: Përdoruesi: Password: Pasword: SpotifyPlaylistUpdater Delete associated Spotify playlist? TemporaryPageItem Copy Artist Link Kopjo Linkun e Artistit Copy Album Link Kopjo Linkun e Albumit Copy Track Link Kopjo Linkun e Këngës Tomahawk::Accounts::AccountDelegate Add Account Shto Llogari Remove Hiq %1 downloads %1 shkarkime Online në linjë Connecting... Lidhu... Offline Jo N'Linje Tomahawk::Accounts::AccountModel Manual Install Required Instalo Manualisht Nevojshëm Unfortunately, automatic installation of this resolver is not available or disabled for your platform.<br /><br />Please use "Install from file" above, by fetching it from your distribution or compiling it yourself. Further instructions can be found here:<br /><br />http://www.tomahawk-player.org/resolvers/%1 Tomahawk::Accounts::GoogleWrapper Configure this Google Account Konfiguro këtë Llogari Google Google Address: Adresa Google: Enter your Google login to connect with your friends using Tomahawk! Shkruani llogarinë tuaj Google të për lidhur me miqtë tuaj duke përdorur TOMAHAWK! username@gmail.com username@gmail.com You may need to change your %1Google Account Settings%2 to login. %1 is <a href>, %2 is </a> Tomahawk::Accounts::GoogleWrapperFactory Login to directly connect to your Google Talk contacts that also use Tomahawk. Tomahawk::Accounts::GoogleWrapperSip Enter Google Address Shkruaj Adresen Google Add Friend Shto Shok Enter Google Address: Shkruaj Adresen Google: Tomahawk::Accounts::HatchetAccountConfig Logged in as: %1 Log out Log in Continue Vazhgo Tomahawk::Accounts::HatchetAccountFactory Connect to Hatchet to capture your playback data, sync your playlists to Android and more. Tomahawk::Accounts::LastFmAccountFactory Scrobble your tracks to last.fm, and find freely downloadable tracks to play Tomahawk::Accounts::LastFmConfig Testing... Testimi... Test Login Hyrje Testi Importing %1 e.g. Importing 2012/01/01 Importo %1 Importing History... Importo Historine... History Incomplete. Resume Text on a button that resumes import Playback History Imported Historia Importuar Ridëgjimit Failed Dështoi Success Sukses Could not contact server Nuk mund të kontaktoni server'in Synchronizing... Sinkronizimi... Synchronization Finished Sinkronizimi Mbaroi Tomahawk::Accounts::ResolverAccountFactory Resolver installation error: cannot open bundle. Resolver installation error: incomplete bundle. Resolver installation error: bad metadata in bundle. Resolver installation error: platform mismatch. Resolver installation error: Tomahawk %1 or newer is required. Tomahawk::Accounts::SpotifyAccount Sync with Spotify Synk me Spotify Re-enable syncing with Spotify Create local copy Krijo kopje vendore Subscribe to playlist changes Abonohu në listën e ndryshimeve Re-enable playlist subscription Stop subscribing to changes Ndalo abonimin tek ndryshimet Enable Spotify collaborations Mundëso Bashkëpunimin Spotify Disable Spotify collaborations Gjymto Bashkëpunimin Spotify Stop syncing with Spotify Ndalo sinkronizimin me Spotify Tomahawk::Accounts::SpotifyAccountConfig Logging in... Failed: %1 Dështoi: %1 Logged in as %1 Hyrë si %1 Log Out Dilni Log In Hyr Tomahawk::Accounts::SpotifyAccountFactory Play music from and sync your playlists with Spotify Premium Luaj muzikë nga lista juaj dhe sinkronizo me Spotify Premium Tomahawk::Accounts::TelepathyConfigStorage the KDE instant messaging framework KDE Instant Messaging Accounts Tomahawk::Accounts::XmppAccountFactory Login to connect to your Jabber/XMPP contacts that also use Tomahawk. Tomahawk::Accounts::XmppConfigWidget Account provided by %1. You forgot to enter your username! Keni harruar të fusni përdoruesin! Your Xmpp Id should look like an email address ID juaj Xmpp duhet të duket si adresë emaili Example: username@jabber.org Shembull: username@jabber.org Tomahawk::Accounts::ZeroconfAccount Local Network Tomahawk::Accounts::ZeroconfFactory Local Network Automatically connect to Tomahawk users on the same local network. Tomahawk::Collection Collection Koleksion Tomahawk::ContextMenu &Play &Luaj Add to &Queue Shto tek &Rradha Add to &Playlist Shto tek &Lista Send to &Friend Continue Playback after this &Track Stop Playback after this &Track &Love &Dashuri View Similar Tracks to "%1" &Go to "%1" &Shko tek "%1" Go to "%1" Shko tek "%1" &Copy Track Link &Kopjo Linkun e Këngës Mark as &Listened &Remove Items &Remove Item Copy Album &Link Kopjo &Linkun e Albumit Copy Artist &Link Kopjo &Linkun e Artistit Un-&Love Lart-&Dashuri Properties... Cilësimet... Tomahawk::DatabaseCommand_AllAlbums Unknown Panjohur Tomahawk::DropJobNotifier playlist listë artist artisti track kënga album albumi Fetching %1 from database Duke ngarkuar %1 nga baza e të dhënave Parsing %1 %2 Tomahawk::DynamicControlList Save Settings Tomahawk::DynamicModel Could not find a playable track. Please change the filters or try again. Nuk gjeti këngë për të luajtur. Ju lutëm ndryshoni filterat dhe provoni përsëri. Failed to generate preview with the desired filters Dështoi në gjenerimin e shikimit paraprak me filterat e dëshiruar Tomahawk::DynamicSetupWidget Type: Tipi: Generate Gjenero Tomahawk::DynamicView Add some filters above to seed this station! Shto disa filtra më lart për pasardhësit e këtij stacioni! Press Generate to get started! Shtyp gjenero për të filluar! Add some filters above, and press Generate to get started! Shto disa filtra më lart, dhe shtyp gjenero për të filluar! Tomahawk::DynamicWidget Station ran out of tracks! Try tweaking the filters for a new set of songs to play. Tomahawk::EchonestControl Similar To Limit To Artist name Emri artistit is është from user nga përdoruesi No users with Echo Nest Catalogs enabled. Try enabling option in Collection settings similar to ngjashme me Enter any combination of song name and artist here... Less Pak More Më shumë 0 BPM 0 BPM 500 BPM 500 BPM 0 secs 0 sek 3600 secs 3600 sek -100 dB -100 dB 100 dB 100 dB -180%1 -180%1 180%1 180%1 Major Madh Minor Mitur C C C Sharp C Sharp D D E Flat E Sheshtë E E F F F Sharp F Mprehtë G G A Flat A Sheshtë A A B Flat B Sheshtë B B Ascending Ngjitje Descending Zbritje Tempo Ritëm Duration Kohëzgjatje Loudness Zhurmshëm Artist Familiarity Njohja Artistit Artist Hotttnesss Song Hotttnesss Latitude Gjerësi Longitude Gjatësi Mode Mënyrë Key Çelës Energy Energji Danceability is not nuk është Studio Song type: The song was recorded in a studio. Live Song type: The song was a life performance. Direkt Acoustic Song type Akustike Electric Song type Elektrike Christmas Song type: A christmas song Krishtlindje At Least Të Pakten At Most only by ~%1 vetëm me ~%1 similar to ~%1 përbashket me ~%1 with genre ~%1 me lloj ~%1 from no one nga as një You Ju from my radio nga radio e mia from %1 radio nga %1 radio Variety Shumëllojshmëri Adventurousness Aventurist very low shumë e ulët low ulët moderate mesatar high lartë very high shumë e lartë with %1 %2 me %1 %2 about %1 BPM rreth %1 BPM about %n minute(s) long rreth %n minuta gjatëreth %n minuta gjatë about %1 dB rreth %1 dB at around %1%2 %3 përafërsisht %1 %2 %3 in %1 në %1 in a %1 key në një %1 çelës sorted in %1 %2 order renditur no %1 %2 rregull with a %1 mood me një %1 mënyrë in a %1 style në një %1 stil where genre is %1 ku zhanri është %1 where song type is %1 ku është tipi i këngës %1 where song type is not %1 ku nuk është tipi i këngës %1 Tomahawk::GroovesharkParser Error fetching Grooveshark information from the network! Tomahawk::InfoSystem::ChartsPlugin Artists Artistë Albums Albume Tracks Këngë Tomahawk::InfoSystem::FdoNotifyPlugin on 'on' is followed by an album name %1%4 %2%3. %1 is a title, %2 is an artist and %3 is replaced by either the previous message or nothing, %4 is the preposition used to link track and artist ('by' in english) %1%4 %2%3. by preposition to link track and artist nga The current track could not be resolved. Tomahawk will pick back up with the next resolvable track from this source. Tomahawk is stopped. %1 sent you %2%4 %3. %1 is a nickname, %2 is a title, %3 is an artist, %4 is the preposition used to link track and artist ('by' in english) %1 sent you "%2" by %3. %1 is a nickname, %2 is a title, %3 is an artist on "%1" %1 is an album name ndezur "%1" "%1" by %2%3. %1 is a title, %2 is an artist and %3 is replaced by either the previous message or nothing "%1" nga %2%3. Tomahawk - Now Playing Tomahawk::InfoSystem::LastFmInfoPlugin Top Tracks Këngët Hite Loved Tracks Këngë Dashurie Hyped Tracks Top Artists Artistë Lartë Hyped Artists Tomahawk::InfoSystem::NewReleasesPlugin Albums Albume Tomahawk::InfoSystem::SnoreNotifyPlugin Notify User Now Playing Unresolved track Playback Stopped You received a Song recommendation on 'on' is followed by an album name %1%4 %2%3. %1 is a title, %2 is an artist and %3 is replaced by either the previous message or nothing, %4 is the preposition used to link track and artist ('by' in english) by preposition to link track and artist nga on "%1" %1 is an album name në "%1" "%1" by %2%3. %1 is a title, %2 is an artist and %3 is replaced by either the previous message or nothing "%1" nga %2%3. %1 sent you %2%4 %3. %1 is a nickname, %2 is a title, %3 is an artist, %4 is the preposition used to link track and artist ('by' in english) %1 dërguar ju %2%4 %3. %1 sent you "%2" by %3. %1 is a nickname, %2 is a title, %3 is an artist %1 dërguar ju "%2" nga %3. Tomahawk::ItunesParser Error fetching iTunes information from the network! Tomahawk::JSPFLoader New Playlist Lista e're Failed to save tracks Dështoi në ruajtjen e këngëve Some tracks in the playlist do not contain an artist and a title. They will be ignored. Disa këngë në listë nuk përmbajnë artist apo titull. Do të injorohen. XSPF Error Gabim XSPF This is not a valid XSPF playlist. Kjo nuk është listë e vlefshme XSPF. Tomahawk::LatchManager &Catch Up &Kape &Listen Along &Dëgjo gjatë Tomahawk::LocalCollection Your Collection Tomahawk::RemoteCollection Collection of %1 Tomahawk::ScriptCollection %1 Collection Name of a collection based on a resolver, e.g. Subsonic Collection %1 Koleksion Tomahawk::ShortenedLinkParser Network error parsing shortened link! Gabim shkurtim lidhje analizë rrjeti! Tomahawk::Source Scanning (%L1 tracks) Skanim (%L1 Këgët) Checking Kontrollo Syncing Sinkronizim Importing Importo Saving (%1%) Ruaj (%1%) Online Në linjë Offline Jo N'Linje Tomahawk::SpotifyParser Error fetching Spotify information from the network! Tomahawk::Track and dhe You Ju you ju and dhe %n other(s) %n people loved this track sent you this track %1 Tomahawk::Widgets::ChartsPage Charts Tomahawk::Widgets::Dashboard Feed An overview of your friends' recent activity Tomahawk::Widgets::DashboardWidget Recently Played Tracks Feed Tomahawk::Widgets::NetworkActivity Trending What's hot amongst your friends Tomahawk::Widgets::NetworkActivityWidget Charts Last Week Loved Tracks Top Loved Recently Loved Sorry, we are still loading the charts. Sorry, we couldn't find any trending tracks. Last Month Muaji Kaluar Last Year Viti Kaluar Overall Tomahawk::Widgets::NewReleasesPage New Releases Tomahawk::Widgets::WhatsNew_0_8 What's new in 0.8? An overview of the changes and additions since 0.7. Tomahawk::XspfUpdater Automatically update from XSPF Azhornim automatikisht nga XSPF TomahawkApp You Ju Tomahawk is updating the database. Please wait, this may take a minute! Tomahawk Tomahawk Updating database Updating database %1 Automatically detecting external IP failed. TomahawkSettings Local Network TomahawkTrayIcon &Stop Playback after current Track Hide Tomahawk Window Fsheh Dritaren Tomahawk Show Tomahawk Window Shfaq Dritaren Tomahawk Currently not playing. Momentalisht nuk po dëgjohet. Play Luaj Pause Pushim &Love &Dashuri Un-&Love Lart-&Dashuri &Continue Playback after current Track TomahawkUtils Configure Accounts Invite Ftesë TomahawkWindow Tomahawk Tomahawk Back Mbrapa Go back one page Shko mbrapa një faqe Forward Përpara Go forward one page Shko përpara një faqe Hide Menu Bar Show Menu Bar &Main Menu &Menuja Kryesore Play Next Love Unlove Exit Full Screen Dil Ekran të Plotë Enter Full Screen Futu Ekran të Plotë This is not a valid XSPF playlist. Kjo nuk është listë e vlefshme XSPF. Some tracks in the playlist do not contain an artist and a title. They will be ignored. Disa këngë në listë nuk përmbajnë artist apo titull. Do të injorohen. Failed to load JSPF playlist Sorry, there is a problem accessing your audio device or the desired track, current track will be skipped. Make sure you have a suitable Phonon backend and required plugins installed. Sorry, there is a problem accessing your audio device or the desired track, current track will be skipped. Station Stacion Create New Station Krijo Stacion Te Ri Name: Emri: Playlist Lista Create New Playlist Pause Pushim Search &Play &Luaj %1 by %2 track, artist name %1 nga %2 %1 - %2 current track, some window title %1 - %2 <h2><b>Tomahawk %1<br/>(%2)</h2> <h2><b>Tomahawk %1<br/>(%2)</h2> <h2><b>Tomahawk %1</h2> <h2><b>Tomahawk %1</h2> Copyright 2010 - 2015 Thanks to: Faleminderit nga: About Tomahawk Rreth Tomahawk TrackDetailView Marked as Favorite Alternate Sources: Unknown Release-Date on %1 TrackInfoWidget Similar Tracks Këngë te Njëjta Sorry, but we could not find similar tracks for this song! Na vjen keq, por ne nuk mund të gjejm ushë të ngjashme për këtë këngë! Top Hits TrackView Sorry, your filter '%1' did not match any results. TransferStatusItem from streaming artist - track from friend nga to streaming artist - track to friend tek Type selector Artist Artist Description User Radio Song Këngë Genre Mood Style Adventurousness Variety Tempo Duration Loudness Danceability Energy Artist Familiarity Artist Hotttnesss Song Hotttnesss Longitude Latitude Mode Key Sorting Song Type ViewManager Inbox Listening suggestions from your friends WhatsNewWidget_0_8 WHAT'S NEW If you are reading this it likely means you have already noticed the biggest change since our last release - a shiny new interface and design. New views, fonts, icons, header images and animations at every turn. Below you can find an overview of the functional changes and additions since 0.7 too: Inbox <html><head/><body><p>Send tracks to your friends just by dragging it onto their avatar in the Tomahawk sidebar. Check out what your friends think you should listen to in your inbox.</p></body></html> Universal Link Support <html><head/><body><p>Love that your friends and influencers are posting music links but hate that they are links for music services you don't use? Just drag Rdio, Deezer, Beats or other music service URLs into your Tomahawk queue or playlists and have them automatically play from your preferred source.</p></body></html> Beats Music <html><head/><body><p>Beats Music (recently aquired by Apple) is now supported. This means that Beats Music subscribers can also benefit by resolving other music service links so they stream from their Beats Music account. Welcome aboard!</p></body></html> Google Music <html><head/><body><p>Google Music is another of our latest supported services - both for the music you've uploaded to Google Music as well as their full streaming catalog for Google Play Music All Access subscribers. Not only is all of your Google Play Music a potential source for streaming - your entire online collection is browseable too.</p></body></html> <html><head/><body><p>Tomahawk for Android is now in beta! The majority of the same resolvers are supported in the Android app - plus a couple of additional ones in Rdio &amp; Deezer. <a href="http://hatchet.is/register">Create a Hatchet account</a> to sync all of your playlists from your desktop to your mobile. Find current and future music influencers and follow them to discover and hear what they love. Just like on the desktop, Tomahawk on Android can open other music service links and play them from yours. Even when you are listening to other music apps, Tomahawk can capture all of that playback data and add it to your Hatchet profile.</p></body></html> Connectivity <html><head/><body><p>Tomahawk now supports IPv6 and multiple local IP addresses. This improves the discoverability and connection between Tomahawk users - particularly large local networks often found in work and university settings. The more friends, the more music, the more playlists and the more curation from those people whose musical tastes you value. Sit back and just Listen Along!</p></body></html> Android XMPPBot Terms for %1: Termat për %1: No terms found, sorry. Termat nuk u gjeden, na falni. Hotttness for %1: %2 Familiarity for %1: %2 Lyrics for "%1" by %2: %3 Teksti për "%1" nga %2: %3 XSPFLoader Failed to parse contents of XSPF playlist Some playlist entries were found without artist and track name, they will be omitted Failed to fetch the desired playlist from the network, or the desired file does not exist New Playlist Lista e're XmlConsole Xml stream console Filter Filter Save log Ruaj shënimet Disabled Gjymto By JID Nga JID By namespace uri By all attributes Nga të gjithë atributet Visible stanzas Information query Pyetje informacioni Message Mesazhi Presence Prani Custom Me porosi Close Mbyll Save XMPP log to file Ruaj logo XMPP në dokument OpenDocument Format (*.odf);;HTML file (*.html);;Plain text (*.txt) XmppConfigWidget Xmpp Configuration Konfigurimi Xmpp Configure Login Information Informacioni Hyres Configure this Jabber/XMPP account Enter your XMPP login to connect with your friends using Tomahawk! XMPP ID: e.g. user@jabber.org Password: Fjalëkalim: An account with this name already exists! Një llogari me këtë emer ekziston! Advanced Xmpp Settings Cilësime të Avancuara Xmpp Server: Server: Port: Porti: Lots of servers don't support this (e.g. GTalk, jabber.org) Shumë servera nuk e mbështesin këtë (sh.m. GTalk, jabber.org) Display currently playing track Enforce secure connection Detyro lidhje të sigurt XmppSipPlugin User Interaction Nërfaqja Përdoruesit Host is unknown Pritësi është i panjohur Item not found Artikull nuk u gjet Authorization Error Gabim Autorizimi Remote Stream Error Remote Connection failed Lidhja Distancë Dështoj Internal Server Error Gabim Internal Serveri System shutdown Fikja Sistemit Conflict Konflikt Unknown Panjohur Do you want to add <b>%1</b> to your friend list? Dëshironi to shtoni <b>%1</b> tek lista e shoqërisë? No Compression Support Enter Jabber ID Shkruaj Jabber ID No Encryption Support No Authorization Support No Supported Feature Add Friend Shto Shok Enter Xmpp ID: Fut Xmpp ID: Add Friend... Shto Shok... XML Console... I'm sorry -- I'm just an automatic presence used by Tomahawk Player (http://gettomahawk.com). If you are getting this message, the person you are trying to reach is probably not signed on, so please try again later! Authorize User Autorizo Përdoruesin ZeroconfConfig Local Network configuration Kopnfigurimi Rrjetit Vendorë This plugin will automatically find other users running Tomahawk on your local network Connect automatically when Tomahawk starts Lidhu automatikisht kur Tomahawk fillon tomahawk-player/src/tests/TestServent.h000664 001750 001750 00000011106 12661705042 021361 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2013, Dominik Schmidt * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef TOMAHAWK_TESTDATABASE_H #define TOMAHAWK_TESTDATABASE_H #include #include #include "network/Servent.h" #include "sip/SipInfo.h" class TestServent : public QObject { Q_OBJECT private: void saneHostAddress( const QString& address ) { // We do not use QHostAddress here as we use it inside our code. // (Do not use the same code to test and generate) // No loopback IPv4 addresses QVERIFY2( !address.startsWith( QLatin1String( "127.0.0." ) ), "Loopback IPv4 address detected" ); // No IPv6 localhost address QVERIFY2( address != "::1", "IPv6 localhost address detected" ); // No IPv4 localhost as IPv6 address QVERIFY2( address != "::7F00:1", "IPv4 localhost as IPv6 address detected" ); // No link-local IPv6 addresses QVERIFY2( !address.startsWith( QLatin1String( "fe80::" ) ), "Link-local IPv6 address detected" ); } void listenAllBasic( Servent** servent ) { // Instantiate a new instance for each test so we have a sane state. *servent = new Servent(); QVERIFY( *servent != NULL ); #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) QHostAddress anyAddress = QHostAddress::Any; #else QHostAddress anyAddress = QHostAddress::AnyIPv6; #endif // TODO: Use a random free port for tests // With (upnp == false) and (mode == // Tomahawk::Network::ExternalAddress::Upnp) we should not do // any external address detection. bool ok = (*servent)->startListening( anyAddress, false, 52222, Tomahawk::Network::ExternalAddress::Upnp, 52222); QVERIFY( ok ); } private slots: void testListenAll() { Servent* servent; listenAllBasic( &servent ); // Verify that computed external addresses are ok QList externalAddresses = servent->addresses(); foreach ( QHostAddress addr, externalAddresses ) { saneHostAddress( addr.toString() ); } // Verify that the local SipInfos contain valid addresses QList sipInfos = servent->getLocalSipInfos( uuid(), uuid() ); foreach ( SipInfo sipInfo, sipInfos ) { saneHostAddress( sipInfo.host() ); } delete servent; } void testWhitelist() { Servent* servent; listenAllBasic( &servent ); // Check for IPv4 localhost QVERIFY( servent->isIPWhitelisted( QHostAddress::LocalHost ) ); // Check for IPv6 localhost QVERIFY( servent->isIPWhitelisted( QHostAddress::LocalHostIPv6 ) ); // Verify that all interface addresses are whitelisted. foreach ( QHostAddress addr, QNetworkInterface::allAddresses() ) { QVERIFY( servent->isIPWhitelisted( addr ) ); if ( addr.protocol() == QAbstractSocket::IPv4Protocol ) { // Convert to IPv6 mapped address quint32 ipv4 = addr.toIPv4Address(); Q_IPV6ADDR ipv6; for (int i = 0; i < 16; ++i) { ipv6[i] = 0; } ipv6[10] = 0xff; ipv6[11] = 0xff; ipv6[12] = 0xff & (ipv4 >> 24); ipv6[13] = 0xff & (ipv4 >> 16); ipv6[14] = 0xff & (ipv4 >> 8); ipv6[15] = 0xff & ipv4; QHostAddress ipv6Addr( ipv6 ); QString error = QString( "%1 converted to IPv6 %2" ) .arg( addr.toString() ).arg( ipv6Addr.toString() ); QVERIFY2( servent->isIPWhitelisted( ipv6Addr ), error.toLatin1().constData() ); } } delete servent; } }; #endif // TOMAHAWK_TESTDATABASE_H tomahawk-player/src/libtomahawk/utils/M3uLoader.cpp000664 001750 001750 00000012425 12661705042 023526 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Hugo Lindström * Copyright 2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "M3uLoader.h" #include "utils/Logger.h" #include "utils/TomahawkUtils.h" #include "Query.h" #include "SourceList.h" #include "Playlist.h" #include "DropJob.h" #include "ViewManager.h" #include #include /* taglib */ #include #include using namespace Tomahawk; M3uLoader::M3uLoader( const QStringList& urls, bool createNewPlaylist, QObject* parent ) : QObject( parent ) , m_single( false ) , m_trackMode( true ) , m_createNewPlaylist( createNewPlaylist ) , m_urls( urls ) { } M3uLoader::M3uLoader( const QString& url, bool createNewPlaylist, QObject* parent ) : QObject( parent ) , m_single( false ) , m_trackMode( true ) , m_createNewPlaylist( createNewPlaylist ) , m_urls( url ) { } M3uLoader::~M3uLoader() { } void M3uLoader::parse() { foreach ( const QString& url, m_urls ) parseM3u( url ); } void M3uLoader::getTags( const QFileInfo& info ) { QByteArray fileName = QFile::encodeName( info.canonicalFilePath() ); const char *encodedName = fileName.constData(); TagLib::FileRef f( encodedName ); if( f.isNull() ) return; TagLib::Tag *tag = f.tag(); if( !tag ) return; QString artist = TStringToQString( tag->artist() ).trimmed(); QString album = TStringToQString( tag->album() ).trimmed(); QString track = TStringToQString( tag->title() ).trimmed(); if ( artist.isEmpty() || track.isEmpty() ) { qDebug() << "Error parsing" << info.fileName(); return; } else { qDebug() << Q_FUNC_INFO << artist << track << album; Tomahawk::query_ptr q = Tomahawk::Query::get( artist, track, album, uuid(), !m_createNewPlaylist ); if ( !q.isNull() ) { q->setResultHint( "file://" + info.absoluteFilePath() ); q->setSaveHTTPResultHint( true ); qDebug() << "Adding resulthint" << q->resultHint(); m_tracks << q; } } } void M3uLoader::parseLine( const QString& line, const QFile& file ) { QFileInfo tmpFile( QUrl::fromUserInput( QString( line.simplified() ) ).toLocalFile() ); if ( tmpFile.exists() ) { getTags( tmpFile ); } else { QUrl fileUrl = QUrl::fromUserInput( QString( QFileInfo( file ).canonicalPath() + "/" + line.simplified() ) ); QFileInfo tmpFile( fileUrl.toLocalFile() ); if ( tmpFile.exists() ) { getTags( tmpFile ); } } } void M3uLoader::parseM3u( const QString& fileLink ) { QFileInfo fileInfo( fileLink ); QFile file( QUrl::fromUserInput( fileLink ).toLocalFile() ); if ( !file.open( QIODevice::ReadOnly ) ) { tDebug() << "Error opening m3u:" << file.errorString(); return; } QTextStream stream( &file ); QString singleLine; while ( !stream.atEnd() ) { QString line = stream.readLine().trimmed(); /// Fallback solution for, (drums) itunes! singleLine.append( line ); /// If anyone wants to take on the regex for parsing EXT, go ahead /// But the notion that users does not tag by a common rule. that seems hard /// So ignore that for now if ( line.contains( "EXT" ) ) continue; parseLine( line, file ); } if ( m_tracks.isEmpty() ) { if ( !singleLine.isEmpty() ) { QStringList m3uList = singleLine.split( "\r" ); foreach( const QString& line, m3uList ) parseLine( line, file ); } if ( m_tracks.isEmpty() ) { tDebug() << "Could not parse M3U!"; return; } } if ( m_createNewPlaylist ) { m_title = QUrl::fromPercentEncoding( fileInfo.baseName().toUtf8() ); m_playlist = Playlist::create( SourceList::instance()->getLocal(), uuid(), m_title, m_info, m_creator, false, m_tracks ); connect( m_playlist.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), this, SLOT( playlistCreated() ) ); } else emit tracks( m_tracks ); m_tracks.clear(); } void M3uLoader::playlistCreated() { ViewManager::instance()->show( m_playlist ); deleteLater(); } tomahawk-player/data/images/trending.svg000664 001750 001750 00000002741 12661705042 021507 0ustar00stefanstefan000000 000000 Slice 1 Created with Sketch (http://www.bohemiancoding.com/sketch) tomahawk-player/src/libtomahawk/utils/ShortLinkHelper_p.h000664 001750 001750 00000002576 12661705042 025002 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright (C) 2011 Leo Franchi * Copyright (C) 2011, Jeff Mitchell * Copyright (C) 2011-2012, Christian Muehlhaeuser * Copyright (C) 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #pragma once #ifndef SHORTLINKHELPER_P_H #define SHORTLINKHELPER_P_H #include "ShortLinkHelper.h" namespace Tomahawk { namespace Utils { class ShortLinkHelperPrivate { public: ShortLinkHelperPrivate( ShortLinkHelper* q ) : q_ptr( q ) { } ShortLinkHelper* q_ptr; Q_DECLARE_PUBLIC( ShortLinkHelper ) private: QNetworkReply* reply; }; } } #endif // SHORTLINKHELPER_P_H tomahawk-player/src/libtomahawk/database/DatabaseCollection.cpp000664 001750 001750 00000016545 12661705042 026066 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2011, Leo Franchi * Copyright 2013, Teo Mrnjavac * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "DatabaseCollection.h" #include "database/Database.h" #include "database/DatabaseCommand_AllArtists.h" #include "database/DatabaseCommand_AllAlbums.h" #include "database/DatabaseCommand_AllTracks.h" #include "database/DatabaseCommand_AddFiles.h" #include "database/DatabaseCommand_DeleteFiles.h" #include "database/DatabaseCommand_LoadAllPlaylists.h" #include "database/DatabaseCommand_LoadAllAutoPlaylists.h" #include "database/DatabaseCommand_LoadAllStations.h" #include "utils/Logger.h" #include "PlaylistEntry.h" using namespace Tomahawk; DatabaseCollection::DatabaseCollection( const source_ptr& src, QObject* parent ) : Collection( src, QString( "dbcollection:%1" ).arg( src->nodeId() ), parent ) { } void DatabaseCollection::loadPlaylists() { DatabaseCommand_LoadAllPlaylists* cmd = new DatabaseCommand_LoadAllPlaylists( source() ); connect( cmd, SIGNAL( done( const QList& ) ), SLOT( setPlaylists( const QList& ) ) ); Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) ); } void DatabaseCollection::loadAutoPlaylists() { DatabaseCommand_LoadAllAutoPlaylists* cmd = new DatabaseCommand_LoadAllAutoPlaylists( source() ); connect( cmd, SIGNAL( autoPlaylistLoaded( Tomahawk::source_ptr, QVariantList ) ), SLOT( autoPlaylistCreated( const Tomahawk::source_ptr&, const QVariantList& ) ) ); Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) ); } void DatabaseCollection::loadStations() { DatabaseCommand_LoadAllStations* cmd = new DatabaseCommand_LoadAllStations( source() ); connect( cmd, SIGNAL( stationLoaded( Tomahawk::source_ptr, QVariantList ) ), SLOT( stationCreated( const Tomahawk::source_ptr&, const QVariantList& ) ) ); Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) ); } void DatabaseCollection::addTracks( const QList& newitems ) { qDebug() << Q_FUNC_INFO << newitems.length(); DatabaseCommand_AddFiles* cmd = new DatabaseCommand_AddFiles( newitems, source() ); Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) ); } void DatabaseCollection::removeTracks( const QDir& dir ) { qDebug() << Q_FUNC_INFO << dir; DatabaseCommand_DeleteFiles* cmd = new DatabaseCommand_DeleteFiles( dir, source() ); Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) ); } QList< Tomahawk::playlist_ptr > DatabaseCollection::playlists() { if ( Collection::playlists().isEmpty() ) { loadPlaylists(); } return Collection::playlists(); } QList< dynplaylist_ptr > DatabaseCollection::autoPlaylists() { if ( Collection::autoPlaylists().isEmpty() ) { loadAutoPlaylists(); } return Collection::autoPlaylists(); } QList< dynplaylist_ptr > DatabaseCollection::stations() { if ( Collection::stations().isEmpty() ) { loadStations(); } return Collection::stations(); } Tomahawk::ArtistsRequest* DatabaseCollection::requestArtists() { //FIXME: assuming there's only one dbcollection per source, and that this is the one Tomahawk::collection_ptr thisCollection = source()->dbCollection(); if ( thisCollection->name() != this->name() ) return 0; Tomahawk::ArtistsRequest* cmd = new DatabaseCommand_AllArtists( thisCollection ); return cmd; } Tomahawk::AlbumsRequest* DatabaseCollection::requestAlbums( const Tomahawk::artist_ptr& artist ) { //FIXME: assuming there's only one dbcollection per source, and that this is the one Tomahawk::collection_ptr thisCollection = source()->dbCollection(); if ( thisCollection->name() != this->name() ) return 0; Tomahawk::AlbumsRequest* cmd = new DatabaseCommand_AllAlbums( thisCollection, artist ); return cmd; } Tomahawk::TracksRequest* DatabaseCollection::requestTracks( const Tomahawk::album_ptr& album ) { //FIXME: assuming there's only one dbcollection per source, and that this is the one Tomahawk::collection_ptr thisCollection = source()->dbCollection(); if ( thisCollection->name() != this->name() ) return 0; DatabaseCommand_AllTracks* cmd = new DatabaseCommand_AllTracks( thisCollection ); if ( album ) { cmd->setAlbum( album->weakRef() ); cmd->setSortOrder( DatabaseCommand_AllTracks::AlbumPosition ); } return cmd; } int DatabaseCollection::trackCount() const { return source()->trackCount(); } void DatabaseCollection::autoPlaylistCreated( const source_ptr& source, const QVariantList& data ) { dynplaylist_ptr p( new DynamicPlaylist( source, //src data[0].toString(), //current rev data[1].toString(), //title data[2].toString(), //info data[3].toString(), //creator data[4].toUInt(), // createdOn data[5].toString(), // dynamic type static_cast(data[6].toInt()), // dynamic mode data[7].toBool(), //shared data[8].toInt(), //lastmod data[9].toString() ), &QObject::deleteLater ); //GUID p->setWeakSelf( p.toWeakRef() ); addAutoPlaylist( p ); } void DatabaseCollection::stationCreated( const source_ptr& source, const QVariantList& data ) { dynplaylist_ptr p( new DynamicPlaylist( source, //src data[0].toString(), //current rev data[1].toString(), //title data[2].toString(), //info data[3].toString(), //creator data[4].toUInt(), // createdOn data[5].toString(), // dynamic type static_cast(data[6].toInt()), // dynamic mode data[7].toBool(), //shared data[8].toInt(), //lastmod data[9].toString() ), &QObject::deleteLater ); //GUID p->setWeakSelf( p.toWeakRef() ); addStation( p ); } tomahawk-player/src/libtomahawk/network/RemoteCollection.h000664 001750 001750 00000002701 12661705042 025174 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef REMOTECOLLECTION_H #define REMOTECOLLECTION_H #include "Typedefs.h" #include "database/DatabaseCollection.h" class ControlConnection; namespace Tomahawk { class RemoteCollection : public DatabaseCollection { Q_OBJECT friend class ControlConnection; // for receiveTracks() public: explicit RemoteCollection( Tomahawk::source_ptr source, QObject* parent = 0 ); ~RemoteCollection() { qDebug() << Q_FUNC_INFO; } virtual QString prettyName() const; public slots: virtual void addTracks( const QList& newitems ); virtual void removeTracks( const QDir& dir ); }; } #endif // REMOTECOLLECTION_H tomahawk-player/data/images/repeat-all.svg000664 001750 001750 00000002641 12661705042 021722 0ustar00stefanstefan000000 000000 repeat-all Created with Sketch (http://www.bohemiancoding.com/sketch) tomahawk-player/src/libtomahawk/accounts/lastfm/LastFmInfoPlugin.h000664 001750 001750 00000006231 12661705042 026524 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2012, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef LASTFMPLUGIN_H #define LASTFMPLUGIN_H #include "infosystem/InfoSystem.h" #include "infosystem/InfoSystemWorker.h" #include "DllMacro.h" #include #include #include #include class QNetworkReply; namespace Tomahawk { namespace Accounts { class LastFmAccount; } namespace InfoSystem { class DLLEXPORT LastFmInfoPlugin : public InfoPlugin { Q_OBJECT public: LastFmInfoPlugin( Accounts::LastFmAccount* account ); virtual ~LastFmInfoPlugin(); const QString friendlyName() const { return "LastFM"; }; public slots: void settingsChanged(); void onAuthenticated(); void coverArtReturned(); void artistImagesReturned(); void similarArtistsReturned(); void topTracksReturned(); void artistInfoReturned(); void albumInfoReturned(); void chartReturned(); void similarTracksReturned(); protected slots: virtual void init(); virtual void getInfo( Tomahawk::InfoSystem::InfoRequestData requestData ); virtual void notInCacheSlot( Tomahawk::InfoSystem::InfoStringHash criteria, Tomahawk::InfoSystem::InfoRequestData requestData ); virtual void pushInfo( Tomahawk::InfoSystem::InfoPushData pushData ); private: void fetchSimilarArtists( Tomahawk::InfoSystem::InfoRequestData requestData ); void fetchTopTracks( Tomahawk::InfoSystem::InfoRequestData requestData ); void fetchArtistInfo( Tomahawk::InfoSystem::InfoRequestData requestData ); void fetchAlbumInfo( Tomahawk::InfoSystem::InfoRequestData requestData ); void fetchChart( Tomahawk::InfoSystem::InfoRequestData requestData ); void fetchChartCapabilities( Tomahawk::InfoSystem::InfoRequestData requestData ); void fetchSimilarTracks( Tomahawk::InfoSystem::InfoRequestData requestData ); void createScrobbler(); void nowPlaying( const QVariant& input ); void scrobble(); void sendLoveSong( const InfoType type, QVariant input ); void dataError( Tomahawk::InfoSystem::InfoRequestData requestData ); QPointer< Accounts::LastFmAccount > m_account; QList parseTrackList( QNetworkReply* reply ); lastfm::MutableTrack m_track; lastfm::Audioscrobbler* m_scrobbler; QString m_pw; QList< QUrl > m_badUrls; }; } } #endif // LASTFMPLUGIN_H tomahawk-player/thirdparty/qxt/qxtweb-standalone/web/qxtwebcontent.h000664 001750 001750 00000005500 12661705042 027273 0ustar00stefanstefan000000 000000 /**************************************************************************** ** 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 QXTWEBCONTENT_H #define QXTWEBCONTENT_H #include #include #include #include #include class QxtWebContentPrivate; class QXT_WEB_EXPORT QxtWebContent : public QxtFifo { Q_OBJECT public: QxtWebContent(int contentLength, const QByteArray& start, QObject *parent, QIODevice* sourceDevice); explicit QxtWebContent(const QByteArray& content, QObject* parent = 0); static QHash parseUrlEncodedQuery(const QString& data); bool wantAll() const; qint64 bytesNeeded() const; qint64 unreadBytes() const; void waitForAllContent(); public Q_SLOTS: void ignoreRemainingContent(); protected: virtual qint64 readData(char* data, qint64 maxSize); virtual qint64 writeData(const char* data, qint64 maxSize); private Q_SLOTS: void sourceDisconnect(); void errorReceived(QAbstractSocket::SocketError); private: QXT_DECLARE_PRIVATE(QxtWebContent) }; #endif // QXTWEBCONTENT_H tomahawk-player/thirdparty/libportfwd/third-party/miniupnpc-1.6/miniupnpcstrings.h000664 001750 001750 00000000745 12661705042 031706 0ustar00stefanstefan000000 000000 /* $Id: miniupnpcstrings.h.in,v 1.4 2011/01/04 11:41:53 nanard Exp $ */ /* Project: miniupnp * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * Author: Thomas Bernard * Copyright (c) 2005-2011 Thomas Bernard * This software is subjects to the conditions detailed * in the LICENCE file provided within this distribution */ #ifndef __MINIUPNPCSTRINGS_H__ #define __MINIUPNPCSTRINGS_H__ #define OS_STRING "Linux/3.0-ARCH" #define MINIUPNPC_VERSION_STRING "1.6" #endif tomahawk-player/src/libtomahawk/database/DatabaseCommand_ModifyInboxEntry.h000664 001750 001750 00000003020 12661705042 030327 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2013, Teo Mrnjavac * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef DATABASECOMMAND_MODIFYINBOXENTRY_H #define DATABASECOMMAND_MODIFYINBOXENTRY_H #include "DatabaseCommand.h" namespace Tomahawk { class DatabaseCommand_ModifyInboxEntry : public DatabaseCommand { Q_OBJECT public: explicit DatabaseCommand_ModifyInboxEntry( const Tomahawk::query_ptr& query, bool newValue, QObject *parent = 0 ); virtual void exec( DatabaseImpl* dbi ); virtual bool doesMutates() const { return true; } virtual bool groupable() const { return true; } virtual bool localOnly() const { return true; } virtual QString commandname() const { return "modifyinboxentry"; } signals: void done(); private: Tomahawk::query_ptr m_query; bool m_newValue; }; } #endif // DATABASECOMMAND_MODIFYINBOXENTRY_H tomahawk-player/lang/tomahawk_th.ts000664 001750 001750 00000600325 12661705042 020577 0ustar00stefanstefan000000 000000 ACLJobDelegate Allow %1 to connect and stream from you? อนุญาต %1 เพื่อ เชื่อมต่อและดึงกระแสจากคุณหรือไม่? Allow Streaming อนุญาตการดึงกระแส Deny Access ปฏิเสธการเข้าถึง ACLJobItem Tomahawk needs you to decide whether %1 is allowed to connect. Tomahawk ต้องการให้คุณตัดสินใจว่าจะอนุญาต %1 ให้เชื่อมต่อหรือไม่ AccountFactoryWrapper Add Account เพิ่มบัญชี AccountFactoryWrapperDelegate Online ออนไลน์ Connecting... กำลังเชื่อมต่อ... Offline ออฟไลน์ AccountListWidget Connections การเชื่อมต่อ Connect &All เชื่อมต่อ&ทั้งหมด Disconnect &All ไม่เชื่อมต่อ&ทั้งหมด ActionCollection &Listen Along ฟังร่วมกัน Stop &Listening Along หยุดการฟังร่วมกัน &Follow in Real-Time ติดตามในเวลาจริง &Listen Privately &ฟังแบบส่วนตัว &Load Playlist โห&ลดบัญชีการเล่น &Load Station โหลดสถานี &Rename Playlist เปลี่ยนชื่อบัญชีการเล่น &Rename Station เปลี่ยนชื่อสถานี &Copy Playlist Link คัดลอกลิงค์บัญชีการเล่น &Play เล่น &Stop หยุด &Previous Track แทร็คก่อนหน้า &Next Track แทร็คถัดไป &Quit ออก U&pdate Collection อัพเดตคอลเลกชั่น Fully &Rescan Collection สแกนคอลเลกชั่นอย่างสมบูรณ์ Import Playlist... นำเข้าบัญชีการเล่น... Show Offline Friends แสดงเพื่อนที่ออฟไลน์ &Configure Tomahawk... &ปรับแต่งค่า Tomahawk... Minimize ย่อลง Zoom ซูม Enter Full Screen เข้าสู่แบบเต็มจอ Hide Menu Bar ซ่อนแถบเมนู Diagnostics... การวินิจฉัย... About &Tomahawk... เกี่ยวกับ &Tomahawk... &Legal Information... &ข้อมูลด้านกฎหมาย... &View Logfile ดูไฟล์จดกิจกรรม Check For Updates... ตรวจการอัพเดต... 0.8 0.8 Report a Bug รายงายบัก Get Support รับการสนับสนุน Help Us Translate ช่วยเราแปล &Controls การควบคุม &Settings การตั้งค่า &Help ช่วยเหลือ What's New in ... สิ่งใหม่ใน... &Window หน้าต่าง Main Menu เมนูหลัก AlbumInfoWidget Top Hits ฮิตสุด Album Details รายละเอียดอัลบั้ม AlbumModel All albums from %1 ทุกอัลบั้มจาก %1 All albums อัลบั้มทั้งหมด ArtistInfoWidget Top Albums อัลบั้มเด่น Top Hits ฮิตสุด Show More แสดงเพิ่มเติม Biography ชีวประวัติ Related Artists ศิลปินที่เกี่ยวข้อง Sorry, we could not find any albums for this artist! Sorry, we could not find any related artists! Sorry, we could not find any top hits for this artist! Music ดนตรี Songs เพลง Albums อัลบั้ม AudioControls Shuffle เล่นสุ่ม Repeat เล่นซ้ำ Time Elapsed เวลาที่เล่น Time Remaining เวลาที่เหลือ Playing from %1 กำลังเล่นจาก %1 AudioEngine Sorry, Tomahawk couldn't find the track '%1' by %2 Sorry, Tomahawk couldn't find the artist '%1' Sorry, Tomahawk couldn't find the album '%1' by %2 Sorry, couldn't find any playable tracks CaptionLabel Close ปิด CategoryAddItem Create new Playlist สร้างบัญชีการเล่นใหม่ Create new Station สร้างสถานีใหม่ New Station สถานีใหม่ %1 Station สถานี %1 CategoryItem Playlists บัญชีการเล่น Stations สถานี ClearButton Clear ล้าง CollectionItem Collection ชุดสะสม CollectionViewPage Sorry, there are no albums in this collection! Artists Albums Songs After you have scanned your music collection you will find your tracks right here. This collection is empty. Cloud collections aren't supported in the flat view yet. We will have them covered soon. Switch to another view to navigate them. ColumnItemDelegate Unknown ไม่ทราบ ColumnView Sorry, your filter '%1' did not match any results. ColumnViewPreviewWidget Composer: ผู้แต่ง: Duration: ระยะเวลา: Bitrate: อัตราบิต: Year: ปี: Age: อายุ: %1 kbps %1 kbps ContextView Playlist Details รายละเอียดบัญชีการเล่น This playlist is currently empty. This playlist is currently empty. Add some tracks to it and enjoy the music! DelegateConfigWrapper About เกี่ยวกับ Delete Account ลบบัญชี Config Error About this Account เกี่ยวกับบัญชีนี้ DiagnosticsDialog Tomahawk Diagnostics &Copy to Clipboard คัดลอกสู่หน่วยความจำ Open &Log-file เปิดไฟล์จดกิจกรรม EchonestSteerer Steer this station: นำทางสถานีนี้: Much less น้อยกว่ามาก Less น้อยกว่า A bit less น้อยกว่าเล็กน้อย Keep at current คงไว้ที่ปัจจุบัน A bit more มากกว่าเล็กน้อย More มากกว่า Much more มากกว่ามาก Tempo จังหวะ Loudness ความดัง Danceability Energy พลังงาน Song Hotttnesss Artist Hotttnesss Artist Familiarity By Description Enter a description Apply steering command Reset all steering commands FilterHeader Filter... GlobalActionManager Resolver installation from file %1 failed. Install plug-in <b>%1</b> %2<br/>by <b>%3</b><br/><br/>You are attempting to install a Tomahawk plug-in from an unknown source. Plug-ins from untrusted sources may put your data at risk.<br/>Do you want to install this plug-in? HatchetAccountConfig Connect to your Hatchet account <html><head/><body><p><a href="http://blog.hatchet.is">Learn More</a> and/or <a href="http://hatchet.is/register"><span style=" text-decoration: underline; color:#0000ff;">Create Account</span></a></p></body></html> Enter One-time Password (OTP) Username Hatchet username Password: Hatchet password Login HistoryWidget Recently Played Tracks Your recently played tracks %1's recently played tracks Sorry, we could not find any recent plays! HostDialog Host Settings Configure your external IP address or host name here. Make sure to manually forward the selected port to this host on your router. Static Host Name: Static Port: Automatically detect external IP address InboxItem Inbox กล่องรับ InboxJobItem Sent %1 by %2 to %3. %1 sent you %2 by %3. InboxPage Inbox Details Your friends have not shared any recommendations with you yet. Connect with them and share your musical gems! IndexingJobItem Indexing Music Library JSResolver Script Resolver Warning: API call %1 returned data synchronously. LastFmConfig Scrobble tracks to Last.fm Username: Password: Test Login Import Playback History Synchronize Loved Tracks LatchedStatusItem %1 is listening along with you! LoadPlaylist Load Playlist Enter the URL of the hosted playlist (e.g. .xspf format) or click the button to select a local M3U of XSPF playlist to import. Playlist URL Enter URL... ... Automatically Update (upon changes to hosted playlist) To import a playlist from Spotify, Rdio, Beats, etc. - simply drag the link into Tomahawk. LoadPlaylistDialog Load Playlist Playlists (*.xspf *.m3u *.jspf) LovedTracksItem Top Loved Tracks Favorites Sorry, we could not find any of your Favorites! The most loved tracks from all your friends All of your loved tracks All of %1's loved tracks MetadataEditor Tags แท็ก Title: ชื่อเรียก: Title... ชื่อเรียก... Artist: ศิลปิน: Artist... ศิลปิน... Album: อัลบั้ม: Album... อัลบั้ม... Track Number: หมายเลขแทร็ค: Duration: ระยะเวลา: 00.00 00.00 Year: ปี: Bitrate: อัตราบิต: File ไฟล์ File Name: ชื่อไฟล์: File Name... ชื่อไฟล์... File Size... ขนาดไฟล์... File size... ขนาดไฟล์... File Size: ขนาดไฟล์: Back กลับ Forward เดินหน้า Error Could not write tags to file: %1 Properties คุณสมบัติ NetworkActivityWidget Trending Tracks Hot Playlists Trending Artists PlayableModel Artist Title Composer Album Track Duration Bitrate Age Year Size Origin Accuracy Perfect match Very good match Good match Vague match Bad match Very bad match Not available Searching... Name PlaylistItemDelegate played %1 by you played %1 by %2 PlaylistModel A playlist you created %1. A playlist by %1, created %2. All tracks by %1 on album %2 All tracks by %1 ProxyDialog Proxy Settings Hostname of proxy server Host Port Proxy login User Password Proxy password No Proxy Hosts: (Overrides system proxy) localhost *.example.com (space separated) Use proxy for DNS lookups? QObject %n year(s) ago %n year(s) %n month(s) ago %n month(s) %n week(s) ago %n week(s) %n day(s) ago %n day(s) %n hour(s) ago %n hour(s) %1 minutes ago %1 minutes just now Friend Finders Music Finders Status Updaters %1 Config Songs Beginning of a sentence summary No configured filters! and Inserted between items in a list of two , Inserted between items in a list . Inserted when ending a sentence summary , and Inserted between the last two items in a list of more than two and Inserted before the last item in a list and Inserted before the sorting summary in a sentence summary QSQLiteResult No query Parameter count mismatch QueueItem Queue QueueView Open Queue Queue Details Queue The queue is currently empty. Drop something to enqueue it! ResolverConfigDelegate Not found: %1 Failed to load: %1 ScannerStatusItem Scanning Collection ScriptCollectionHeader Reload Collection ScriptEngine Resolver Error: %1:%2 %3 SSL Error You have asked Tomahawk to connect securely to <b>%1</b>, but we can't confirm that your connection is secure:<br><br><b>%2</b><br><br>Do you want to trust this connection? Trust certificate SearchLineEdit Search SearchWidget Search: %1 Results for '%1' Songs Show More Artists Albums Sorry, we could not find any artists! Sorry, we could not find any albums! Sorry, we could not find any songs! Servent Automatically detecting external IP failed: Could not parse JSON response. Automatically detecting external IP failed: %1 SettingsDialog Collection Advanced All Install Plug-In... Some changed settings will not take effect until Tomahawk is restarted Configure the accounts and services used by Tomahawk to search and retrieve music, find your friends and update your status. Plug-Ins Manage how Tomahawk finds music on your computer. Configure Tomahawk's advanced settings, including network connectivity settings, browser interaction and more. Open Directory Install resolver from file Tomahawk Resolvers (*.axe *.js);;All files (*) Delete all Access Control entries? Do you really want to delete all Access Control entries? You will be asked for a decision again for each peer that you connect to. Information Settings_Accounts Filter by Capability: Settings_Advanced Remote Peer Connection Method Active (your host needs to be directly reachable) UPnP / Automatic Port Forwarding (recommended) Manual Port Forwarding Host Settings... SOCKS Proxy Use SOCKS Proxy Proxy Settings... Other Settings Allow web browsers to interact with Tomahawk (recommended) Allow other computers to interact with Tomahawk (not recommended yet) Send Tomahawk Crash Reports Show Notifications on song change Clear All Access Control Entries Settings_Collection Folders to scan for music: Due to the unique way Tomahawk works, your music files must at least have Artist & Title metadata/ID3 tags to be added to your Collection. + - The Echo Nest supports keeping track of your catalog metadata and using it to craft personalized radios. Enabling this option will allow you (and all your friends) to create automatic playlists and stations based on your personal taste profile. Upload Collection info to enable personalized "User Radio" Watch for changes (automatically update Collection) Time between scans (in seconds): SlideSwitchButton On Off SocialWidget Facebook Twitter Tweet Listening to "%1" by %2. %3 Listening to "%1" by %2 on "%3". %4 %1 characters left SourceDelegate All available tracks Drop to send tracks Show Hide SourceInfoWidget Recent Albums Latest Additions Recently Played Tracks New Additions My recent activity Recent activity from %1 SourceItem Latest Additions History SuperCollection Latest additions to your collection Latest additions to %1's collection Sorry, we could not find any recent additions! SourceTreeView &Copy Link &Delete %1 Add to my Playlists Add to my Automatic Playlists Add to my Stations &Export Playlist playlist automatic playlist station Would you like to delete the %1 <b>"%2"</b>? e.g. Would you like to delete the playlist named Foobar? Delete Save XSPF Playlists (*.xspf) SourcesModel Group Source Collection Playlist Automatic Playlist Station Cloud Collections Discover Open Pages Your Music Friends SpotifyConfig Configure your Spotify account Username or Facebook Email Log In Right click on any Tomahawk playlist to sync it to Spotify. Select All Sync Starred tracks to Loved tracks High Quality Streams Use this to force Spotify to never announce listening data to Social Networks Always run in Private Mode Spotify playlists to keep in sync: Delete Tomahawk playlist when removing synchronization Username: Password: SpotifyPlaylistUpdater Delete associated Spotify playlist? TemporaryPageItem Copy Artist Link Copy Album Link Copy Track Link Tomahawk::Accounts::AccountDelegate Add Account Remove %1 downloads Online Connecting... Offline Tomahawk::Accounts::AccountModel Manual Install Required Unfortunately, automatic installation of this resolver is not available or disabled for your platform.<br /><br />Please use "Install from file" above, by fetching it from your distribution or compiling it yourself. Further instructions can be found here:<br /><br />http://www.tomahawk-player.org/resolvers/%1 Tomahawk::Accounts::GoogleWrapper Configure this Google Account Google Address: Enter your Google login to connect with your friends using Tomahawk! username@gmail.com You may need to change your %1Google Account Settings%2 to login. %1 is <a href>, %2 is </a> Tomahawk::Accounts::GoogleWrapperFactory Login to directly connect to your Google Talk contacts that also use Tomahawk. Tomahawk::Accounts::GoogleWrapperSip Enter Google Address Add Friend Enter Google Address: Tomahawk::Accounts::HatchetAccountConfig Logged in as: %1 Log out Log in Continue Tomahawk::Accounts::HatchetAccountFactory Connect to Hatchet to capture your playback data, sync your playlists to Android and more. Tomahawk::Accounts::LastFmAccountFactory Scrobble your tracks to last.fm, and find freely downloadable tracks to play Tomahawk::Accounts::LastFmConfig Testing... Test Login Importing %1 e.g. Importing 2012/01/01 Importing History... History Incomplete. Resume Text on a button that resumes import Playback History Imported Failed Success Could not contact server Synchronizing... Synchronization Finished Tomahawk::Accounts::ResolverAccountFactory Resolver installation error: cannot open bundle. Resolver installation error: incomplete bundle. Resolver installation error: bad metadata in bundle. Resolver installation error: platform mismatch. Resolver installation error: Tomahawk %1 or newer is required. Tomahawk::Accounts::SpotifyAccount Sync with Spotify Re-enable syncing with Spotify Create local copy Subscribe to playlist changes Re-enable playlist subscription Stop subscribing to changes Enable Spotify collaborations Disable Spotify collaborations Stop syncing with Spotify Tomahawk::Accounts::SpotifyAccountConfig Logging in... Failed: %1 Logged in as %1 Log Out Log In Tomahawk::Accounts::SpotifyAccountFactory Play music from and sync your playlists with Spotify Premium Tomahawk::Accounts::TelepathyConfigStorage the KDE instant messaging framework KDE Instant Messaging Accounts Tomahawk::Accounts::XmppAccountFactory Login to connect to your Jabber/XMPP contacts that also use Tomahawk. Tomahawk::Accounts::XmppConfigWidget Account provided by %1. You forgot to enter your username! Your Xmpp Id should look like an email address Example: username@jabber.org Tomahawk::Accounts::ZeroconfAccount Local Network Tomahawk::Accounts::ZeroconfFactory Local Network Automatically connect to Tomahawk users on the same local network. Tomahawk::Collection Collection Tomahawk::ContextMenu &Play Add to &Queue Add to &Playlist Send to &Friend Continue Playback after this &Track Stop Playback after this &Track &Love View Similar Tracks to "%1" &Go to "%1" Go to "%1" &Copy Track Link Mark as &Listened &Remove Items &Remove Item Copy Album &Link Copy Artist &Link Un-&Love Properties... Tomahawk::DatabaseCommand_AllAlbums Unknown Tomahawk::DropJobNotifier playlist artist track album Fetching %1 from database Parsing %1 %2 Tomahawk::DynamicControlList Save Settings Tomahawk::DynamicModel Could not find a playable track. Please change the filters or try again. Failed to generate preview with the desired filters Tomahawk::DynamicSetupWidget Type: Generate Tomahawk::DynamicView Add some filters above to seed this station! Press Generate to get started! Add some filters above, and press Generate to get started! Tomahawk::DynamicWidget Station ran out of tracks! Try tweaking the filters for a new set of songs to play. Tomahawk::EchonestControl Similar To Limit To Artist name is from user No users with Echo Nest Catalogs enabled. Try enabling option in Collection settings similar to Enter any combination of song name and artist here... Less More 0 BPM 500 BPM 0 secs 3600 secs -100 dB 100 dB -180%1 180%1 Major Minor C C Sharp D E Flat E F F Sharp G A Flat A B Flat B Ascending Descending Tempo Duration Loudness Artist Familiarity Artist Hotttnesss Song Hotttnesss Latitude Longitude Mode Key Energy Danceability is not Studio Song type: The song was recorded in a studio. Live Song type: The song was a life performance. Acoustic Song type Electric Song type Christmas Song type: A christmas song At Least At Most only by ~%1 similar to ~%1 with genre ~%1 from no one You from my radio from %1 radio Variety Adventurousness very low low moderate high very high with %1 %2 about %1 BPM about %n minute(s) long about %1 dB at around %1%2 %3 in %1 in a %1 key sorted in %1 %2 order with a %1 mood in a %1 style where genre is %1 where song type is %1 where song type is not %1 Tomahawk::GroovesharkParser Error fetching Grooveshark information from the network! Tomahawk::InfoSystem::ChartsPlugin Artists Albums Tracks Tomahawk::InfoSystem::FdoNotifyPlugin on 'on' is followed by an album name %1%4 %2%3. %1 is a title, %2 is an artist and %3 is replaced by either the previous message or nothing, %4 is the preposition used to link track and artist ('by' in english) by preposition to link track and artist The current track could not be resolved. Tomahawk will pick back up with the next resolvable track from this source. Tomahawk is stopped. %1 sent you %2%4 %3. %1 is a nickname, %2 is a title, %3 is an artist, %4 is the preposition used to link track and artist ('by' in english) %1 sent you "%2" by %3. %1 is a nickname, %2 is a title, %3 is an artist on "%1" %1 is an album name "%1" by %2%3. %1 is a title, %2 is an artist and %3 is replaced by either the previous message or nothing Tomahawk - Now Playing Tomahawk::InfoSystem::LastFmInfoPlugin Top Tracks Loved Tracks Hyped Tracks Top Artists Hyped Artists Tomahawk::InfoSystem::NewReleasesPlugin Albums Tomahawk::InfoSystem::SnoreNotifyPlugin Notify User Now Playing Unresolved track Playback Stopped You received a Song recommendation on 'on' is followed by an album name %1%4 %2%3. %1 is a title, %2 is an artist and %3 is replaced by either the previous message or nothing, %4 is the preposition used to link track and artist ('by' in english) by preposition to link track and artist on "%1" %1 is an album name "%1" by %2%3. %1 is a title, %2 is an artist and %3 is replaced by either the previous message or nothing %1 sent you %2%4 %3. %1 is a nickname, %2 is a title, %3 is an artist, %4 is the preposition used to link track and artist ('by' in english) %1 sent you "%2" by %3. %1 is a nickname, %2 is a title, %3 is an artist Tomahawk::ItunesParser Error fetching iTunes information from the network! Tomahawk::JSPFLoader New Playlist Failed to save tracks Some tracks in the playlist do not contain an artist and a title. They will be ignored. XSPF Error This is not a valid XSPF playlist. Tomahawk::LatchManager &Catch Up &Listen Along Tomahawk::LocalCollection Your Collection Tomahawk::RemoteCollection Collection of %1 Tomahawk::ScriptCollection %1 Collection Name of a collection based on a resolver, e.g. Subsonic Collection Tomahawk::ShortenedLinkParser Network error parsing shortened link! Tomahawk::Source Scanning (%L1 tracks) Checking Syncing Importing Saving (%1%) Online Offline Tomahawk::SpotifyParser Error fetching Spotify information from the network! Tomahawk::Track and You you and %n other(s) %n people loved this track sent you this track %1 Tomahawk::Widgets::ChartsPage Charts Tomahawk::Widgets::Dashboard Feed An overview of your friends' recent activity Tomahawk::Widgets::DashboardWidget Recently Played Tracks Feed Tomahawk::Widgets::NetworkActivity Trending What's hot amongst your friends Tomahawk::Widgets::NetworkActivityWidget Charts Last Week Loved Tracks Top Loved Recently Loved Sorry, we are still loading the charts. Sorry, we couldn't find any trending tracks. Last Month Last Year Overall Tomahawk::Widgets::NewReleasesPage New Releases Tomahawk::Widgets::WhatsNew_0_8 What's new in 0.8? An overview of the changes and additions since 0.7. Tomahawk::XspfUpdater Automatically update from XSPF TomahawkApp You Tomahawk is updating the database. Please wait, this may take a minute! Tomahawk Updating database Updating database %1 Automatically detecting external IP failed. TomahawkSettings Local Network TomahawkTrayIcon &Stop Playback after current Track Hide Tomahawk Window Show Tomahawk Window Currently not playing. Play Pause &Love Un-&Love &Continue Playback after current Track TomahawkUtils Configure Accounts Invite TomahawkWindow Tomahawk Back Go back one page Forward Go forward one page Hide Menu Bar Show Menu Bar &Main Menu Play Next Love Unlove Exit Full Screen Enter Full Screen This is not a valid XSPF playlist. Some tracks in the playlist do not contain an artist and a title. They will be ignored. Failed to load JSPF playlist Sorry, there is a problem accessing your audio device or the desired track, current track will be skipped. Make sure you have a suitable Phonon backend and required plugins installed. Sorry, there is a problem accessing your audio device or the desired track, current track will be skipped. Station Create New Station Name: Playlist Create New Playlist Pause Search &Play %1 by %2 track, artist name %1 - %2 current track, some window title <h2><b>Tomahawk %1<br/>(%2)</h2> <h2><b>Tomahawk %1</h2> Copyright 2010 - 2015 Thanks to: About Tomahawk TrackDetailView Marked as Favorite Alternate Sources: Unknown Release-Date on %1 TrackInfoWidget Similar Tracks Sorry, but we could not find similar tracks for this song! Top Hits TrackView Sorry, your filter '%1' did not match any results. TransferStatusItem from streaming artist - track from friend to streaming artist - track to friend Type selector Artist Artist Description User Radio Song Genre Mood Style Adventurousness Variety Tempo Duration Loudness Danceability Energy Artist Familiarity Artist Hotttnesss Song Hotttnesss Longitude Latitude Mode Key Sorting Song Type ViewManager Inbox Listening suggestions from your friends WhatsNewWidget_0_8 WHAT'S NEW If you are reading this it likely means you have already noticed the biggest change since our last release - a shiny new interface and design. New views, fonts, icons, header images and animations at every turn. Below you can find an overview of the functional changes and additions since 0.7 too: Inbox <html><head/><body><p>Send tracks to your friends just by dragging it onto their avatar in the Tomahawk sidebar. Check out what your friends think you should listen to in your inbox.</p></body></html> Universal Link Support <html><head/><body><p>Love that your friends and influencers are posting music links but hate that they are links for music services you don't use? Just drag Rdio, Deezer, Beats or other music service URLs into your Tomahawk queue or playlists and have them automatically play from your preferred source.</p></body></html> Beats Music <html><head/><body><p>Beats Music (recently aquired by Apple) is now supported. This means that Beats Music subscribers can also benefit by resolving other music service links so they stream from their Beats Music account. Welcome aboard!</p></body></html> Google Music <html><head/><body><p>Google Music is another of our latest supported services - both for the music you've uploaded to Google Music as well as their full streaming catalog for Google Play Music All Access subscribers. Not only is all of your Google Play Music a potential source for streaming - your entire online collection is browseable too.</p></body></html> <html><head/><body><p>Tomahawk for Android is now in beta! The majority of the same resolvers are supported in the Android app - plus a couple of additional ones in Rdio &amp; Deezer. <a href="http://hatchet.is/register">Create a Hatchet account</a> to sync all of your playlists from your desktop to your mobile. Find current and future music influencers and follow them to discover and hear what they love. Just like on the desktop, Tomahawk on Android can open other music service links and play them from yours. Even when you are listening to other music apps, Tomahawk can capture all of that playback data and add it to your Hatchet profile.</p></body></html> Connectivity <html><head/><body><p>Tomahawk now supports IPv6 and multiple local IP addresses. This improves the discoverability and connection between Tomahawk users - particularly large local networks often found in work and university settings. The more friends, the more music, the more playlists and the more curation from those people whose musical tastes you value. Sit back and just Listen Along!</p></body></html> Android XMPPBot Terms for %1: No terms found, sorry. Hotttness for %1: %2 Familiarity for %1: %2 Lyrics for "%1" by %2: %3 XSPFLoader Failed to parse contents of XSPF playlist Some playlist entries were found without artist and track name, they will be omitted Failed to fetch the desired playlist from the network, or the desired file does not exist New Playlist XmlConsole Xml stream console Filter Save log Disabled By JID By namespace uri By all attributes Visible stanzas Information query Message Presence Custom Close Save XMPP log to file OpenDocument Format (*.odf);;HTML file (*.html);;Plain text (*.txt) XmppConfigWidget Xmpp Configuration Configure Login Information Configure this Jabber/XMPP account Enter your XMPP login to connect with your friends using Tomahawk! XMPP ID: e.g. user@jabber.org Password: An account with this name already exists! Advanced Xmpp Settings Server: Port: Lots of servers don't support this (e.g. GTalk, jabber.org) Display currently playing track Enforce secure connection XmppSipPlugin User Interaction Host is unknown Item not found Authorization Error Remote Stream Error Remote Connection failed Internal Server Error System shutdown Conflict Unknown Do you want to add <b>%1</b> to your friend list? No Compression Support Enter Jabber ID No Encryption Support No Authorization Support No Supported Feature Add Friend Enter Xmpp ID: Add Friend... XML Console... I'm sorry -- I'm just an automatic presence used by Tomahawk Player (http://gettomahawk.com). If you are getting this message, the person you are trying to reach is probably not signed on, so please try again later! Authorize User ZeroconfConfig Local Network configuration This plugin will automatically find other users running Tomahawk on your local network Connect automatically when Tomahawk starts tomahawk-player/src/libtomahawk/viewpages/TrackViewPage.ui000664 001750 001750 00000005424 12661705042 025115 0ustar00stefanstefan000000 000000 TrackInfoWidget 0 0 828 635 Form 0 0 0 0 QFrame::StyledPanel QFrame::Raised 4 32 4 32 8 Top Hits 0 0 0 0 190 GridView QListView
playlist/GridView.h
CaptionLabel QLabel
widgets/CaptionLabel.h
ContextView QWidget
playlist/ContextView.h
1
tomahawk-player/src/tests/tomahawk_add_test.cmake000664 001750 001750 00000001510 12661705042 023404 0ustar00stefanstefan000000 000000 macro(tomahawk_add_test test_class) include_directories(${QT_INCLUDES} "${PROJECT_SOURCE_DIR}/src" ${CMAKE_CURRENT_BINARY_DIR}) set(TOMAHAWK_TEST_CLASS ${test_class}) set(TOMAHAWK_TEST_TARGET ${TOMAHAWK_TEST_CLASS}Test) configure_file(main.cpp.in Test${TOMAHAWK_TEST_CLASS}.cpp) configure_file(Test${TOMAHAWK_TEST_CLASS}.h Test${TOMAHAWK_TEST_CLASS}.h) add_executable(${TOMAHAWK_TEST_CLASS}Test Test${TOMAHAWK_TEST_CLASS}.cpp) set_target_properties(${TOMAHAWK_TEST_TARGET} PROPERTIES AUTOMOC ON) target_link_libraries(${TOMAHAWK_TEST_TARGET} ${TOMAHAWK_LIBRARIES} ${QT_QTTEST_LIBRARY} ${QT_QTCORE_LIBRARY} ) add_test(NAME ${TOMAHAWK_TEST_TARGET} COMMAND ${TOMAHAWK_TEST_TARGET}) qt5_use_modules(${TOMAHAWK_TEST_TARGET} Core Network Widgets Sql Xml Test) endmacro() tomahawk-player/src/libtomahawk/widgets/PlayableCover.cpp000664 001750 001750 00000031255 12661705042 024773 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2011 - 2012, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "PlayableCover.h" #include "Artist.h" #include "Album.h" #include "ContextMenu.h" #include "ViewManager.h" #include "audio/AudioEngine.h" #include "widgets/ImageButton.h" #include "utils/TomahawkStyle.h" #include "utils/TomahawkUtilsGui.h" #include "utils/Logger.h" #include #include #include #include using namespace Tomahawk; PlayableCover::PlayableCover( QWidget* parent ) : QLabel( parent ) , m_showText( false ) , m_showControls( true ) , m_type( Track ) { setMouseTracking( true ); m_button = new ImageButton( this ); m_button->setPixmap( TomahawkUtils::defaultPixmap( TomahawkUtils::PlayButton, TomahawkUtils::Original, QSize( 48, 48 ) ) ); m_button->setPixmap( TomahawkUtils::defaultPixmap( TomahawkUtils::PlayButtonPressed, TomahawkUtils::Original, QSize( 48, 48 ) ), QIcon::Off, QIcon::Active ); m_button->setFixedSize( 48, 48 ); m_button->setContentsMargins( 0, 0, 0, 0 ); m_button->setFocusPolicy( Qt::NoFocus ); m_button->installEventFilter( this ); m_button->hide(); connect( m_button, SIGNAL( clicked( bool ) ), SLOT( onClicked() ) ); m_contextMenu = new ContextMenu( this ); m_contextMenu->setSupportedActions( ContextMenu::ActionQueue | ContextMenu::ActionCopyLink | ContextMenu::ActionStopAfter | ContextMenu::ActionLove | ContextMenu::ActionPage ); } PlayableCover::~PlayableCover() { } void PlayableCover::enterEvent( QEvent* event ) { QLabel::enterEvent( event ); if ( m_showControls ) m_button->show(); } void PlayableCover::leaveEvent( QEvent* event ) { QLabel::leaveEvent( event ); m_button->hide(); } void PlayableCover::resizeEvent( QResizeEvent* event ) { QLabel::resizeEvent( event ); m_button->move( contentsRect().center() - QPoint( 22, 23 ) ); } void PlayableCover::mousePressEvent( QMouseEvent* event ) { if ( event->button() == Qt::LeftButton ) m_dragStartPosition = event->pos(); } void PlayableCover::mouseMoveEvent( QMouseEvent* event ) { QLabel::mouseMoveEvent( event ); foreach ( const QRect& rect, m_itemRects ) { if ( rect.contains( event->pos() ) ) { if ( m_hoveredRect != rect ) { setCursor( Qt::PointingHandCursor ); m_hoveredRect = rect; repaint(); } return; } } if ( !m_hoveredRect.isNull() ) { setCursor( Qt::ArrowCursor ); m_hoveredRect = QRect(); repaint(); } if ( !( event->buttons() & Qt::LeftButton ) ) return; if ( ( event->pos() - m_dragStartPosition ).manhattanLength() < QApplication::startDragDistance() ) return; QByteArray resultData; QDataStream resultStream( &resultData, QIODevice::WriteOnly ); QMimeData* mimeData = new QMimeData(); QPixmap pixmap; const int pixHeight = TomahawkUtils::defaultFontHeight() * 3; const QSize pixSize = QSize( pixHeight, pixHeight ); switch ( m_type ) { case Artist: if ( m_artist ) { pixmap = m_artist->cover( pixSize, false ); resultStream << m_artist->name(); mimeData->setData( "application/tomahawk.metadata.artist", resultData ); } else { delete mimeData; return; } break; case Album: if ( m_album && !m_album->name().isEmpty() ) { pixmap = m_album->cover( pixSize, false ); resultStream << m_album->artist()->name(); resultStream << m_album->name(); mimeData->setData( "application/tomahawk.metadata.album", resultData ); } else { if ( m_artist ) { pixmap = m_artist->cover( pixSize, false ); resultStream << m_artist->name(); mimeData->setData( "application/tomahawk.metadata.artist", resultData ); } else { delete mimeData; return; } } break; case Track: if ( m_query ) { pixmap = m_query->track()->cover( pixSize, false ); resultStream << QString( "application/tomahawk.query.list" ) << qlonglong( &m_query ); mimeData->setData( "application/tomahawk.mixed", resultData ); } else { delete mimeData; return; } break; } QDrag* drag = new QDrag( this ); drag->setMimeData( mimeData ); drag->setPixmap( pixmap ); drag->setHotSpot( QPoint( -20, -20 ) ); drag->exec( Qt::CopyAction, Qt::CopyAction ); } void PlayableCover::mouseDoubleClickEvent( QMouseEvent* event ) { switch ( m_type ) { case Artist: if ( m_artist ) ViewManager::instance()->show( m_artist ); break; case Album: if ( m_album && !m_album->name().isEmpty() ) ViewManager::instance()->show( m_album ); else if ( m_artist ) ViewManager::instance()->show( m_artist ); break; case Track: if ( m_query ) ViewManager::instance()->show( m_query ); break; } } void PlayableCover::mouseReleaseEvent( QMouseEvent* event ) { QLabel::mouseReleaseEvent( event ); foreach ( const QRect& rect, m_itemRects ) { if ( rect.contains( event->pos() ) ) { mouseDoubleClickEvent( event ); return; } } } void PlayableCover::contextMenuEvent( QContextMenuEvent* event ) { m_contextMenu->clear(); switch ( m_type ) { case Artist: if ( m_artist ) m_contextMenu->setArtist( m_artist ); break; case Album: if ( m_album && !m_album->name().isEmpty() ) m_contextMenu->setAlbum( m_album ); else if ( m_artist ) m_contextMenu->setArtist( m_artist ); break; case Track: if ( m_query ) m_contextMenu->setQuery( m_query ); break; } m_contextMenu->exec( event->globalPos() ); } void PlayableCover::setPixmap( const QPixmap& pixmap ) { m_pixmap = pixmap; // TomahawkUtils::createRoundedImage( pixmap, size() ); repaint(); } void PlayableCover::paintEvent( QPaintEvent* event ) { Q_UNUSED( event ); QPainter painter( this ); painter.setRenderHint( QPainter::Antialiasing ); painter.drawPixmap( 0, 0, pixmap() ); if ( !m_showText ) return; QRect r = contentsRect().adjusted( margin(), margin(), -margin(), -margin() ); QPixmap buffer( r.size() ); buffer.fill( Qt::transparent ); QPainter bufpainter( &buffer ); bufpainter.setRenderHint( QPainter::Antialiasing ); QTextOption to; to.setWrapMode( QTextOption::NoWrap ); QColor c1; c1.setRgb( 0, 0, 0 ); c1.setAlphaF( 0.00 ); QColor c2; c2.setRgb( 0, 0, 0 ); c2.setAlphaF( 0.88 ); QString text; QFont font = QLabel::font(); font.setPointSize( TomahawkUtils::defaultFontSize() ); QFont boldFont = font; boldFont.setBold( true ); boldFont.setPointSize( TomahawkUtils::defaultFontSize() + 5 ); QString top, bottom; if ( m_artist ) { top = m_artist->name(); } else if ( m_album ) { top = m_album->name(); bottom = m_album->artist()->name(); } else if ( m_query ) { top = m_query->queryTrack()->track(); bottom = m_query->queryTrack()->artist(); } int bottomHeight = QFontMetrics( font ).boundingRect( bottom ).height(); int topHeight = QFontMetrics( boldFont ).boundingRect( top ).height(); int frameHeight = bottomHeight + topHeight + 4; QRect gradientRect = r.adjusted( 0, r.height() - frameHeight * 3, 0, 0 ); QLinearGradient gradient( QPointF( 0, 0 ), QPointF( 0, 1 ) ); gradient.setCoordinateMode( QGradient::ObjectBoundingMode ); gradient.setColorAt( 0.0, c1 ); gradient.setColorAt( 0.6, c2 ); gradient.setColorAt( 1.0, c2 ); bufpainter.save(); bufpainter.setPen( Qt::transparent ); bufpainter.setBrush( gradient ); bufpainter.drawRect( gradientRect ); bufpainter.restore(); bufpainter.setPen( Qt::white ); QRect textRect = r.adjusted( 8, r.height() - frameHeight - 16, -8, -16 ); bool oneLiner = false; if ( bottom.isEmpty() ) oneLiner = true; bufpainter.setFont( boldFont ); if ( oneLiner ) { bufpainter.save(); QFont f = bufpainter.font(); while ( f.pointSizeF() > 9 && bufpainter.fontMetrics().width( top ) > textRect.width() ) { f.setPointSizeF( f.pointSizeF() - 0.2 ); bufpainter.setFont( f ); } to.setAlignment( Qt::AlignHCenter | Qt::AlignVCenter ); text = bufpainter.fontMetrics().elidedText( top, Qt::ElideRight, textRect.width() - 3 ); bufpainter.drawText( textRect, text, to ); bufpainter.restore(); } else { to.setAlignment( Qt::AlignHCenter | Qt::AlignTop ); text = bufpainter.fontMetrics().elidedText( top, Qt::ElideRight, textRect.width() - 3 ); bufpainter.drawText( textRect, text, to ); bufpainter.setFont( font ); QRect r = textRect; r.setTop( r.bottom() - bufpainter.fontMetrics().height() ); r.adjust( 4, 0, -4, -1 ); text = bufpainter.fontMetrics().elidedText( bottom, Qt::ElideRight, textRect.width() - 16 ); int textWidth = bufpainter.fontMetrics().width( text ); r.adjust( ( r.width() - textWidth ) / 2 - 6, 0, - ( ( r.width() - textWidth ) / 2 - 6 ), 0 ); m_itemRects.clear(); m_itemRects << r; if ( m_hoveredRect == r ) { TomahawkUtils::drawQueryBackground( &bufpainter, r ); bufpainter.setPen( TomahawkStyle::SELECTION_FOREGROUND ); } to.setAlignment( Qt::AlignHCenter | Qt::AlignBottom ); bufpainter.drawText( textRect.adjusted( 5, -1, -5, -1 ), text, to ); } { QBrush brush( buffer ); QPen pen; pen.setColor( Qt::transparent ); pen.setJoinStyle( Qt::RoundJoin ); float frameWidthPct = 0.20; painter.setBrush( brush ); painter.setPen( pen ); painter.drawRoundedRect( r, frameWidthPct * 100.0, frameWidthPct * 100.0, Qt::RelativeSize ); } } void PlayableCover::onClicked() { switch ( m_type ) { case Artist: if ( m_artist ) AudioEngine::instance()->playItem( m_artist ); break; case Album: if ( m_album && !m_album->name().isEmpty() ) AudioEngine::instance()->playItem( m_album ); else if ( m_artist ) AudioEngine::instance()->playItem( m_artist ); break; case Track: if ( m_query ) AudioEngine::instance()->playItem( Tomahawk::playlistinterface_ptr(), m_query ); break; } } void PlayableCover::setArtist( const Tomahawk::artist_ptr& artist ) { m_type = Artist; m_artist = artist; repaint(); } void PlayableCover::setAlbum( const Tomahawk::album_ptr& album ) { m_type = Album; m_album = album; repaint(); } void PlayableCover::setQuery( const Tomahawk::query_ptr& query ) { m_query = query; if ( query ) { m_artist = query->track()->artistPtr(); m_album = query->track()->albumPtr(); } repaint(); } void PlayableCover::setShowText( bool b ) { m_showText = b; repaint(); } void PlayableCover::setShowControls( bool b ) { m_showControls = b; repaint(); } void PlayableCover::setType( DisplayType type ) { m_type = type; } tomahawk-player/src/tomahawk/sourcetree/items/SourceTreeItem.cpp000664 001750 001750 00000012405 12661705042 026302 0ustar00stefanstefan000000 000000 /* Copyright 2010-2011, Leo Franchi This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "SourceTreeItem.h" #include "audio/AudioEngine.h" #include "utils/Logger.h" // Forward Declarations breaking QSharedPointer #if QT_VERSION < QT_VERSION_CHECK( 5, 0, 0 ) #include "Query.h" #endif using namespace Tomahawk; SourceTreeItem::SourceTreeItem( SourcesModel* model, SourceTreeItem* parent, SourcesModel::RowType thisType, int peerSortValue, int index ) : QObject() , m_type( thisType ) , m_parent( parent ) , m_model( model ) , m_peerSortValue( peerSortValue ) , m_dropType( DropTypesNone ) { connect( AudioEngine::instance(), SIGNAL( started( Tomahawk::result_ptr ) ), SLOT( checkPlayingStatus() ) ); connect( this, SIGNAL( beginChildRowsAdded( int, int ) ), m_model, SLOT( onItemRowsAddedBegin( int, int ) ) ); connect( this, SIGNAL( beginChildRowsRemoved( int, int ) ), m_model, SLOT( onItemRowsRemovedBegin( int, int ) ) ); connect( this, SIGNAL( childRowsAdded() ), m_model, SLOT( onItemRowsAddedDone() ) ); connect( this, SIGNAL( childRowsRemoved() ), m_model, SLOT( onItemRowsRemovedDone() ) ); connect( this, SIGNAL( updated() ), m_model, SLOT( itemUpdated() ) ); connect( this, SIGNAL( selectRequest( SourceTreeItem* ) ), m_model, SLOT( itemSelectRequest( SourceTreeItem* ) ) ); connect( this, SIGNAL( expandRequest( SourceTreeItem* ) ), m_model, SLOT( itemExpandRequest( SourceTreeItem* ) ) ); connect( this, SIGNAL( toggleExpandRequest( SourceTreeItem* ) ), m_model, SLOT( itemToggleExpandRequest( SourceTreeItem* ) ) ); if ( !m_parent ) return; // caller must call begin/endInsertRows if ( index < 0 ) m_parent->appendChild( this ); else m_parent->insertChild( index, this ); } SourceTreeItem::~SourceTreeItem() { qDeleteAll( m_children ); } void SourceTreeItem::checkPlayingStatus() { if ( isBeingPlayed() ) emit updated(); } SourcesModel::RowType SourceTreeItem::type() const { return m_type; } SourceTreeItem* SourceTreeItem::parent() const { return m_parent; } SourcesModel* SourceTreeItem::model() const { return m_model; } QList< SourceTreeItem* > SourceTreeItem::children() const { return m_children; } void SourceTreeItem::appendChild( SourceTreeItem* item ) { m_children.append( item ); } void SourceTreeItem::insertChild( int index, SourceTreeItem* item ) { m_children.insert( index, item ); } void SourceTreeItem::removeChild( SourceTreeItem* item ) { m_children.removeAll( item ); } QString SourceTreeItem::text() const { return QString(); } QString SourceTreeItem::tooltip() const { return QString(); } Qt::ItemFlags SourceTreeItem::flags() const { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } QIcon SourceTreeItem::icon() const { return QIcon(); } bool SourceTreeItem::willAcceptDrag( const QMimeData* ) const { return false; } bool SourceTreeItem::dropMimeData( const QMimeData*, Qt::DropAction ) { return false; } bool SourceTreeItem::setData(const QVariant&, bool) { return false; } int SourceTreeItem::peerSortValue() const { return m_peerSortValue; } int SourceTreeItem::IDValue() const { return 0; } SourceTreeItem::DropTypes SourceTreeItem::supportedDropTypes( const QMimeData* mimeData ) const { Q_UNUSED( mimeData ); return DropTypesNone; } void SourceTreeItem::setDropType( SourceTreeItem::DropType type ) { m_dropType = type; } SourceTreeItem::DropType SourceTreeItem::dropType() const { return m_dropType; } bool SourceTreeItem::isBeingPlayed() const { return false; } QList< QAction* > SourceTreeItem::customActions() const { return QList< QAction* >(); } void SourceTreeItem::beginRowsAdded( int from, int to ) { emit beginChildRowsAdded( from, to ); } void SourceTreeItem::endRowsAdded() { emit childRowsAdded(); } void SourceTreeItem::beginRowsRemoved( int from, int to ) { emit beginChildRowsRemoved( from, to ); } void SourceTreeItem::endRowsRemoved() { emit childRowsRemoved(); } void SourceTreeItem::setRowType( SourcesModel::RowType t ) { m_type = t; } void SourceTreeItem::setParentItem( SourceTreeItem* item ) { m_parent = item; } void SourceTreeItem::removeFromList() { pageDestroyed(); } void SourceTreeItem::pageDestroyed() { model()->removeSourceItemLink( this ); int idx = parent()->children().indexOf( this ); if ( idx < 0 ) return; parent()->beginRowsRemoved( idx, idx ); parent()->removeChild( this ); parent()->endRowsRemoved(); emit removed(); deleteLater(); } tomahawk-player/src/libtomahawk/jobview/JobStatusView.cpp000664 001750 001750 00000014172 12661705042 025012 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2014, Christian Muehlhaeuser * Copyright 2011, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "JobStatusView.h" #include "Pipeline.h" #include "AclJobItem.h" #include "JobStatusModel.h" #include "JobStatusItem.h" #include "JobStatusDelegate.h" #include "utils/Logger.h" #include "Source.h" #include "IndexingJobItem.h" #include "PipelineStatusItem.h" #include "ScannerStatusItem.h" #include "TransferStatusItem.h" #include "LatchedStatusItem.h" #include #include #include #include using namespace Tomahawk; JobStatusView* JobStatusView::s_instance = 0; QList< QPointer< JobStatusItem > > s_jobItems; void JobStatusView::addJob( JobStatusItem* item ) { if ( s_instance == 0 || s_instance->model() == 0 ) { s_jobItems.append( QPointer( item ) ); } else { s_instance->model()->addJob( item ); } } void JobStatusView::addJob( const QPointer& item ) { if ( s_instance == 0 || s_instance->model() == 0 ) { s_jobItems.append( item ); } else { s_instance->model()->addJob( item.data() ); } } JobStatusView::JobStatusView( AnimatedSplitter* parent ) : AnimatedWidget( parent ) , m_model( 0 ) , m_parent( parent ) , m_cachedHeight( -1 ) { s_instance = this; setHiddenSize( QSize( 0, 0 ) ); setLayout( new QVBoxLayout() ); m_view = new QListView( this ); layout()->setMargin( 0 ); layout()->addWidget( m_view ); m_view->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); m_view->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); m_view->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Ignored ); m_view->setFrameShape( QFrame::NoFrame ); m_view->setAttribute( Qt::WA_MacShowFocusRect, 0 ); m_view->setUniformItemSizes( false ); new IndexStatusManager( this ); new PipelineStatusManager( this ); new ScannerStatusManager( this ); new TransferStatusManager( this ); new LatchedStatusManager( this ); setMouseTracking( true ); m_view->setMouseTracking( true ); } void JobStatusView::setModel( JobStatusSortModel* m ) { m_model = m; m_view->setModel( m ); m_view->setItemDelegate( new JobStatusDelegate( m_view ) ); connect( m_view->model(), SIGNAL( rowsInserted( QModelIndex, int, int ) ), this, SLOT( checkCount() ) ); connect( m_view->model(), SIGNAL( rowsRemoved( QModelIndex, int, int ) ), this, SLOT( checkCount() ) ); connect( m_view->model(), SIGNAL( modelReset() ), this, SLOT( checkCount() ) ); connect( m_view->model(), SIGNAL( customDelegateJobInserted( int, JobStatusItem* ) ), this, SLOT( customDelegateJobInserted( int, JobStatusItem* ) ) ); connect( m_view->model(), SIGNAL( customDelegateJobRemoved( int ) ), this, SLOT( customDelegateJobRemoved( int ) ) ); connect( m_view->model(), SIGNAL( refreshDelegates() ), this, SLOT( refreshDelegates() ) ); foreach ( const QPointer item, s_jobItems ) { if ( !item.isNull() ) { m_model->addJob( item.data() ); } } s_jobItems.clear(); } void JobStatusView::customDelegateJobInserted( int row, JobStatusItem* item ) { if ( !item ) return; item->createDelegate( m_view ); m_view->setItemDelegateForRow( row, item->customDelegate() ); ACLJobDelegate* delegate = qobject_cast< ACLJobDelegate* >( item->customDelegate() ); if ( delegate ) { connect( delegate, SIGNAL( update( const QModelIndex& ) ), m_view, SLOT( update( const QModelIndex & ) ) ); connect( delegate, SIGNAL( aclResult( Tomahawk::ACLStatus::Type ) ), item, SLOT( aclResult( Tomahawk::ACLStatus::Type ) ) ); delegate->emitSizeHintChanged( m_model->index( row, 0 ) ); } else tLog() << Q_FUNC_INFO << "delegate was not properly found!"; checkCount(); } void JobStatusView::customDelegateJobRemoved( int row ) { Q_UNUSED( row ); checkCount(); } void JobStatusView::refreshDelegates() { int count = m_model->rowCount(); for ( int i = 0; i < count; i++ ) { QModelIndex index = m_model->index( i, 0 ); QVariant itemVar = index.data( JobStatusModel::JobDataRole ); if ( !itemVar.canConvert< JobStatusItem* >() || !itemVar.value< JobStatusItem* >() ) { tLog() << Q_FUNC_INFO << "unable to fetch JobStatusItem* at row" << i; continue; } JobStatusItem* item = itemVar.value< JobStatusItem* >(); if ( item->hasCustomDelegate() ) m_view->setItemDelegateForRow( i, item->customDelegate() ); else m_view->setItemDelegateForRow( i, m_view->itemDelegate() ); } checkCount(); } void JobStatusView::checkCount() { m_cachedHeight = -1; if ( m_view->model()->rowCount() == 0 && !isHidden() ) emit hideWidget(); else emit sizeHintChanged( sizeHint() ); } QSize JobStatusView::sizeHint() const { if ( m_cachedHeight >= 0 ) return QSize( 0, m_cachedHeight ); unsigned int y = 0; y += m_view->contentsMargins().top() + m_view->contentsMargins().bottom(); if ( m_view->model()->rowCount() ) { for ( int i = 0; i < m_view->model()->rowCount(); i++ ) { y += m_view->sizeHintForRow( i ); } y += 2; // some padding } m_cachedHeight = y; return QSize( 0, y ); } tomahawk-player/src/viewpages/whatsnew_0_8/data/images/design-screenshot.png000664 001750 001750 00000445456 12661705042 030413 0ustar00stefanstefan000000 000000 PNG  IHDRH) pHYs  tIME jbKGDJIDATx}`TUm[f&wB ŵ.ꮟmoWwUt]aUuQ HN'dZ2}A_uqsS}-tZV.˲TJJRJ8d2D"݈XXE 04Ř{J/ӌ(/{++ !ә؍XX2 A z!Тp.OtX k՚8sggnN.1j*999 ǡ1w zÉ'#h>G*Q MCI)))n9n E"0, z(CS42$1% [:6 -hFCP]8;S{p-Lf?"F@Qt 葢! GF% nXN*SJh+ ++?9"u,>3̇& -3#`dXm~?`4t233@"xlT<ƆFm}Ar-DOsf z6>>>3= C!p8t: nlJ/O. pjjaV3 pK.kZ0O4y„>B",Hɂ H X i,K(ćjp|N02nxe[nQ7++?>:lnBqf: k{anL"233-K(h\;77 P*]nBۣ}?@DU۷o>cƌ1cj4b WYmdnT]kY]~"!c6am4.Wn7IyhBF"[3D !RW 4s ?>ciHfwb%Vb%VοD 2͵~8!H]ABpʣFDt@8,8E ' V5!!#-:@8J{ +Hć) PTشV \h4*U*}x ͝ uDW$L&Ba> cJ% >Q=W)c :<28`+i=ǽkR=_2"XcX%$_>U0J@~= 7& cxnЊf "VR#bKh8-pmNJ.?>>>55PXii }8G5`A_ (G %t/4KQ;OJL{>c"sG/ǣB&S.jjk^c%Vb?w SSC 6%":V&RjHl$BstFkTE;v{]]]=;::nA(F49?Hd^?"z`߁@@V"Ø].^op;;; Hƀapj=W \ -VC*8OnZ#j _\\°Vpt VD2f.TGQ0S8:xQO xcJNv7Pߨ@^L9}u٥JhXiWo7_^yH D+Kl]]AGym 6IH"L/.NtvCL@${3DN xeK"i1QLo|X19 ԫ;v, :]*0mLmu]v98T*Z}p 8-1P@*!2g d#E}3R(M L#\gboj>+Doa'OQ vEϞ>8ԪR6'=+]+ =5>1r.`ʯ<> ({3φ}a.5B${hf(l݃=XL-`"6A(9-u< >RLozس U:Ys,\&4q "2Imjl\~wm-L6QI}x G>y2¶7PထCMx ÀkP>08 hxV-GʣNM{yv'GlXGbSjYg86r%2,p\xS xy-aZt=JiR%ES|*9xJ Lj7VT1eOݿZO(a AzJsڣ;DF3>oኧf:7jM HΈX.!C($~ I$pi9c@Z*brA@ 'IIIeqv/ p:g>qwS%J d TN#{s!gw@Ǎ+VSu;8Na;mgV&:]aj}gGS?uO,(4'GC=7N¢Z&߶sgnvB-KVG]rE෮}yRJ\[}717ƿsJrv4 @Ϻx@*?%r%5ORm'7,4::ׁ;@I瑣VFgKfXi5hC>Q H+122 }\nz1!!IM+ ݻ̬,@ہѣ"k4{BLaT $pDąʓUׯ7aaq5qoX 0t@yai4 la}7XQfQH>Ja99&ӹTEִ/^B˷}zovOX6Y K>?튛^xQ%c'C8o?U=A\*_iG^>x ]wʀ5VbW>*!gwIC{mn!CїwAQ.%F=U琢KG$q04? OXTYyzo@aGYh^бS?e tpAvѢE\sMZZZ}}}AAa\32 87R4 ,\]Uv\sFŮ I H)Bft:ZT4Qgze"VFkOSSU eqQPTR:k0 9a5rc탇,T]A%AVx5ϠGc:Xu1 ' ɥfO41.WB6*j@0@u 'ANI= dH)5> *`T-3`F< BR8D FjB4ZmqqW^9yʔQGE pj+N8AnG|DRb%'eg666رf )cJqo&N"%B }L4huI(n>p͝憆z<ɹU&$3c`U#lNy <һV?>+-o,;!`2۱U;q =`>u\Ī$( #V`w(HnRiqq6S29]!`pR m4Dd+J8 ra7 :4>>@d"{_}U{{f3r4@3wZjvCj4/U@V-Ú.@ p:= I=!滑q!ATxX?8$pB ?.cIo&IBa]RfQC&y)miM3^m+w8-?:3wv9md#6˱ب\Zj8Lf`@lC|,G;IX*mƸ8=DEj5}#4ىK4 yyyYYYUU 4^^^677 z!9LpC| /v566\<Nl9",q%0<9.u2z`}|VIb4770`"2AB{2Ao<~vH~{99${*l,mTRL2b=RN* ]GܲlZmbDFD7vڝ=E?ժ;+8M>x34"zغ#H A` ;[8}0ebiN|,6tJ]R!%шL@Uo z= . f 8q℺:9م@ GⰮ EEEmmm&vN2TS+.ܿff&\ %  j'8Sؼ}D*vOAy\ӡ)D ,=Vb?[ܐM-*:bxjRLkTQQ\^K#Ir{ŜT1yE䏛;'iwx/| D{JiB]^)&z*>GEKedhy}㣝O+gNC322Z`8p ɦ𞧠R sr:(C.P:r(nܸqVXۡ7xMNNz=JO2رc99& 3 nS%ZbdA9E1Lؿ}B/>zhym]]錕_g mHM)e+Xd?GLr}&@L?ഐB8TkU/Rsi9ьZW臫*mOPjI:P^K9ꇂ~dsQ ۭT>puUWa3t8O*;3y{*h`"E?HZ QYD/[CZjQݐ24== $ v\P"2g81ܹcܹn xnw ϴRVv6ʯGq)NakBJVed%2*,!G[CMUVۚv3 =ۍe9UVqtښ*v9ZkѶRጞdUiLSp"-˳OROOI?sZv!ksc 2r'\ (t JL2~ZF K @\^C1آ&}=K(L1¤$R TIP^^n2 $KnݺuuuU HJJjni {wBW(PA#ueUUU8yb˖-- p?99%X x`dg؆8w<T?c9Ѹw {Jl0>~ʯ CGJSgBbEWۚL#Z?*K3񦣻*6-=Ho[ g9ﵷ68ڄTLA"d1TՁnXa__́s{Ѥ۴'w(ȡkc뛐G/3g%QN=B= І\@Eܽ=RF]C^ ÜYV :41)  ?ۭl6[,޳JeҤǏ?~DIbM(y0޸8Izd4E.W7N .|ֿd!Ń&Ǐ1bqB)) oDRgHR={{,*75o>V b/1b%).%Ew)Űp|u6?rU2ו'dfI"LQ(4|J 7zl.<2 u`iYݏS nA'ƈ9*pRvӛb9ZQ'g\tw4Ya;'c:|\JvD)F8vYm2!/g#;0wFPy SNBڑbEq.)AtJVm !ц*ɳ a_r,I=|ax]j4L. @0&y++Onڼé#Hhg;nu]c~[}=} T%;G{,؂t jhl[vFT(a p[jM Q1rHl2O٣;Gr55>V~N37C]vS SBDEV6JE_Qjwmg(]5~cq۴X]CK܉))hDܣF{R$g^*P8`ln> ~tQMoa78ojx<X!.J_Vn|ՇR=^޷N?ƚmػlɛJeC&lY P*LwG0qNu u{'"͉[s)v_sSHĥ0 -~EMbRT:,--6lԩ0`(+8h8C& h޺ukEEΝۗ-]L˛6mڔSGA׆á  FΓD%,cOwիW▖hpM@ 9t??;0Йsg& LPё! <3_}o1H#3yvp>o`J |dż}Dq *XbmޢK8bRs+=L@0u|I~쀻2&şy|ϗJl0?e,}ۣXÞ ^K㔿Wqޯ߷&SG n /^ 1 y(Y\rrrlv;0n=4ZmWW.%$&7D Tn%b[ R"34jkkAJ464 +0oa(Vc4C q%Cgd=Zx ol68`ڵkzTo E[A$iW␛+ gNN\.S*U njC(A2މN^)Ϊ[$a!N"5 YiI)ZM=ʸ y_}iFi598 )Q#}$yy -67IF R}cd*MVJ" gPlQY)W7'y[Yܻ-}aϛLrzF.wZ'R-,CsMU΀.%W"GG6@*:}ǖ7-D3-n]R-Qy-|1䷶i.`We j dO8FP)KhՁ.7>;mۚOu(զuj [k ɉԦy]b JzC/CEona8]*Y׶/ TL2zŝ+͟_yi]LaN=¼Ф?rǎ+[ G7{mMj1}弢l~CqmO|z2}f{t8ß_WU+^Ol=qT`<CǍ6Ȟ~_Rrun~9SGHBg=qG8xX8dy /=# #.-e}Í㇦}nF˺[/16?gꀆ`ڳLԌqT\KnZF^;Fw]ƚ?nf&A{S=>\YH2d2Gk-'UhR~*1Q-; kQ=Iӹske)(05*LD7bX|>wqWTFj3Zh*,vP-%Q_zCG!lh Dgp@v@X e"H!é5 &L}@OVd2k2"NpUL_[~JH_4Z{3Cda9T-80DzFX 10qlFQbzT- #ute,̅8(&X%*D;had( jjjrrrPT2"HFQP~mF"epVQX9#F~]]Сl7LD`5>?!!4ܶmȘLć$xl8q9l0H5w^}pϞ0x%6G iiK3)M1a;+ i9CKxoj2S&Iâ\W+I%d w0^Ҝ_%K]qّ/l'_'e>_?uuI81pت:Ɍ0-R0`Ȱ, Uٛ,ЬĘjDF kd_2h!c![<$u˒nPNm,W0r=ꦮ<>}߼_އ~I3Joq]I1Ą8QELҶ>{ 6 Ҥ .ێSf=UKzC#QxYnhIȘM-]_ܰ:ŋ]xiN\S;,{EܼށiVK3KJsb;Gw3팽>ep,Bbt4clY8ֹqvxSH'Q4)Ϡ]_?to%rU|F4a /꧗ZA0L&<녥R`l4l!Ba!DjZ@YRvx.1H4v= o\.xQ=y$ɡ1d[4G Bk/cu H4˱ix;p7KSS0$Dr.`pD];#'Ћd3 #` \KlWF]0KKǁ8!ؗ+8V[W17u% V}Ysuniiawvv>PS}99_2 -;v)2H (rtׇyx5BaM!lvL%ݫՓ+lFF84w]Az}ƕBd"0c'N{CLmHgU ;>3G}:L@?i[ߩjkβE.Q˜~=R>c^q/;8ܱpr.c ?]fSw[;O*z(p{C5VGg}ףLC)))8Tn3QX2;+0e2bώ&֒syC(|,$`w !F# F`lqILL"pv` C죜!8OH4iQUTXJmcBLY  ґ<| (̺k:lĹyn?4''gݰFd'!qwɓ'WX"݇#_wݵF#,`S$R<)dP$ >b"UG+*ΩӵJcپo> wVnxnFN:n@M?ē(ˑ58*]0!gGSɌn6}{=EGl^@  YnXD 322e%sUN r$t>x 4ۆ#N ';fb R@R$V|QQxH)@<, {yy93xl@o=ؑ\Di7v؆FҩTBST C&qp<8;0t\ >Z7m_D&C^K{mp"Ɨ Yncp'FÔS# p# ) WgwJ2ȝeqX3b+*_}O,.QiL%t%ɟ)Kg~J ,Uu &@v{ΚDjLB~ 8t:;::VE. gt`h +2t!;d^F+++eB0[vl6礯jdYσDɦ.ѕ7b$X@/ ;:2':/ zj5$%T9逶,⁥J8x~w„ ٰ!N#$]t8{fJIINJB HƱÇU륑!a!`0#"?d̘ .GW0z=$jܨ^уkOoᇮ -fNhc;lsy;ا;XuR'Ac++c{YIu?F #ت__0)PV@2K)rgv5VQu{%gA6E&xG ѣS'֣PT Hv= @ޭ pn (4 p/8N\ mq!ĵB@;'37NFk2?<pz…A_qXDL:|+KD*t}8x$DZ"[Ƽw}_Z'}OvlQ^4eK׬\gx:gs}ߔ8eg0_.W,,\M\Qx`~R8}_9:u#f[ֲFF"![3"'{t[/\s}MڲmS{/04=tQUgv{7L5ՕօkO}p6[\4zr޳n;.?xys獒UoTg[/T_ditXN?nY a9!gD5ut.K|2(L&b+MBDqJ!zPX/ARCӊǀla3.,A*CJ;&(.k2@w}>@In4@og$;`" .MeIH [ [ȠBB p=zt<H:$A)8qYDu/G;unq;v h;HEy儩f&unnaNg7yh#8_G8 tO?]D#. Ldd7`00QFeeewD{AIlRbbQaH tUs.Z~0Ѧ)X8b$rɑF?ξUTgٱ/{w~ uU}Q2XڰlѶf_nkᨙ7Jd-=2.]۟v׃huh;/Y Ҹ\u󖝼k|ƭwy/y7SX?L1$Uڼ@ؚO}B& &lRkUSCU^__6xEqGkH«8q𫯾9foծLB͹?=6ɮtK$6WZW~_ϏRB2q#;1gKyl7IB }Mawެ>K<Ͽ~`7^4fZf騑Y|wt﹤`-d)ϲ\ 6է0ɝ_{NlϽ>/؃wzc'ϸmsnytIQ j/?ߨdRR8A: &\ߧ{?{CJ$ϡ6I^&xR8,h?QIj(-_'_lj6pH&ptΨ3s _|a JxvFermYe?Iykc%CEPl9ۍ2O8P鴝BS{ ,iفyq D"| 8)QJc-LA )٥ıY.e4U@`q-V(DҼxb%dga%$ rnD>3(GLf2Y;v!#m[x#4Hd)9Ό9bKǎ-d 2:x:7)))&hiiIOO1G#GAw Ln,Qѐ@:pO6+CIU-&^07zJqOvPhEWŠ"wGUt߲& !:UEA(U(Ҕ^ETAHBzgfvC ~oܽef99vxy{^A:~{_iV\A.c|Vɮͷ@6nTh4kub3 ŧ{(APvj.UYdޢ/ۯVVj;bQ@߿qs*|>)l?".{$&L>@a.NwY|lACז{2IRV5 |^QxXwlDy1$evi$wv(`he̕[#;媊 iV"K0LCw9AV\v+ ٥Y\̎7[0's12-c 9s/^6m=oh2.rع%.}D Lˮ] /-l3W2n2YG9T; ^?o+-ܬj4crpV!- " \JT%يVRd)⨊+"CLY x~~|\ țr 'rf倀L20|}ڼT䤤$.*,H*y@3h{ߟkNWRRyf@t%fP0׆Z2\t8]&ɥK?Aw R%CIL= ]B{.sQIC1Fo|oZ4VɬvDROJj*hM`yJJR:,gª-GO³4%pr2h=TAR!2:} CD /)5o=zdzN~w[2Ft~rpP`fNZ Ͱ*Vk9E%{AyF8C2"@{;189(z!ZjjJA c`/,+h* I,jUd 6NRYa|apXElKϢa'5h*arpa)WtyY'2@qpv!,4~b*|Z%ONR[JBv Jf0~9Cܻ]v>Lr%-132Qf=HN 0i2 l?ʝ.Bj4]FG6N83\@@3Qjt0B<VT53)Wօi2sw+g;mZmqSJ}V(+^_ܽ*}yIyբ]ߍ-_i؂[s4 x/g?_}ƴo͙û$__ڴ }l?0unoݢ;Y ڠ}#9^L݁~`u'xK?zJS~ݸ6GWͿY|8E qGoI-NQſI$G|,Iv0"͋Nh"+Տ'Qf@=o@b5F*`U]יeąiP |xR=UL9W &EBqX(X#Ձg`-V+b=@hT*uVV8@04h4ӅDa۷_F޽y' _/..1 xa9KhhVXP=h9N@^g͚5{̽{w̠"D_XhUEha$i"W(NkUxÕp<3E $5v G7EWCuSv<.r12xKԸaS0Iz|ڈθ\=1ͮBzRO5j=/,26/PQ9!( z"k=<يzW@W$&%d8Ȏ"i5L~Adf@%ee#&4<>{k*-#[UK<ifJ)8셹h{'oC+ZroHgTR J*%5[%c2s,"\O:=q%o09|ڊhIYw_֪=;=}IMʒ fu<ʕ9Y :b̵*e̠~ᏡmAQNryI@@n.~F}%o_Xvtym/GY/pRU4Z&p ׺됦 7qE0&96؁FcwlUw$P`b:ZF?ɳUEP p|\-EAx*Zsݍ[D4K(eγ[Y2F%XFTaaa g$ #SxHP͜8qE:"eKRTqϼl](aAou_~<J4Zb[<[wc7o62P˩nBcyVUʝ/Y>y 8;׮kR-ZQ yEvtKx)_n,.zYN"ֶMd_Ok|rZQ:͛gho 8=((DÍ' :ug]p~ǎjF_#IZFgG%6(a+_(/]E.6@"ۭV]Pj Od+-= +3ըsXͿXϸ!Lbs.=M%E`38NyOϢtrN#+*(@ NQ/bFrO?+)(e,4xUE9*iBo)霜|}6@4!6vupL3oZGAj>g3?븮 Sx~^C8"c:x>Кqְ#P@?QxTҰaF* cT WupIUx47؁GX==M5k֬Q37<AgSEQ e "n=-Q&My ;5>,Ԥr2GSHQdSHdՉJҢA Op|K\Ť~q>Z*'$N@&TǮjo_qƬ<,%yJGrj kEW/GS ݲMGԐI]*(waoPxϿdd*{Ap_#EuIPO 8Hxʰ`5);oL@./,6dǫ cf̊r-ಜN,H\60̰;V*r>Tڍ'uvFK.hr`@92ظ@a]rUE瓔QԪ[ff&D٠l%> l_nݼfZ4j)8(|d"xp*`t(PA0J -7I, NH)f+6;+H!lD'}.=#\fJJe A:dI)fE~vڷyчF ՑUYˁkŕ#T-_3a$μ{sÃ>#AֈLKj{VN<‡Ve򨞥f+ u!ՙ~C"W"vwGYp?~ʤQC9{1 Έa}^;Gy8Aߙ{%g%|qB4]Fc5/L5R k j|.t8d!^n{Oxpki|߶k'͑us:qaƍЯ.p*Q\bO⳱Ǵ*)0lD:^n \gR8{#F1UDysNd3ga& cUz /عu"X1 }w@ZBn嗫E#i]~i<|UF^΅AJ[K }_SVPg d''R>a1wAgw:m)Wџμ%()! ^,B$I"7̃@[gp9U,Ѓ%0 s$*rBtN\7̔d b#HЈA>al7i~~:~s6>j4u zDF36|hF=(ܢX6K {7+CzL|}L' jBcnsoƻǖۈ9rӧouk^m<^gG4a1/]v>߯T?P)e;Q,?ȮF~R>}Ռ(CloX;x+^֪u(yڒbЌ72f۫ص0=f Lq LoŪ5o͙i[c;W١;gU:gZ n]JnA JNY9똏7xpqoP~ǾrT*_n?@YԉzNb}^z%|whnݽu∷ˤ2?U›]40m{gpd#W!8U)!uU#4Q'| `/OMx;u?'UXm?Z)Q|dN߷{>lttN8Aljt_ޛ%OG1zWt>J!.ś o w  -nR=? 4W˯ZA'frr!J$*FԐL%Rӆp+fgg׬Y)<]sz@UVbb" ʣ-T20 -aj\t WC\/_%Xa!!zP<<<_IEAKIIqE _? /౱/^$2 Tr}:]TT2$GTtM/?q8H@r5)琈։7E`=x7P#xgCa.eꠇlc]y}kw~;on9|dÑoK7[#Mܾb[3{cZrd{۬mu{˾ l&&\}sh;E^8S> LQrż=466}{P $.=r.ӣOv}cɟ?1Kn=+wZh{"i6Wyŵm}uF>R杗_81w~Uwk'y3&$%ou ԁ-^Xف+v9uz.zjJ;2X1G 'L:dҞ;);M' KRk"m1j_G!)qׇ6_ܽ]V04k)KVve՘Wj/{sQ,FOkN֋k2N뗌-^ۗW Mt@5*X9tW}sH]wPww7)g ;MׂÆQ*I (fuϹe|CCdr$=饩!Ȏ")Z)Dy'5lBU LF>KV:OTEI)@Ç a$R8gAƩ* r2őA==-D ?:33 d2zܹ{`%`6$ ~@ P#pbH,DWh 6HOO;|9v bC\T .+GCn(m}~LoW `YoAS-N}0deYg-DC+t4\`].{bedˆ޵^ D\_o^PI h< <*,R ]Gk;wo;} ];왳;,X[jd Novm,"/B$yCZENJe;~j^Mw;Py%2P0v%`@9}`) r-v޵(dPZKazT۵rj-DhU4/ӑdF3lqAI dyAS-~l`@Kq<ɸK E"ش4KfF롶$81=Gl1GJL`HiFl?mRNHaaaN ]T@O̖T^Ž'a% :NLhyv33KVЫlwd#+K-woS2W/E՚߮>:|t]Ɓe4:3IF ܿ;!uK|w(޽Sle3um׭͹Q3Y,?\-\+Ҳ˷x但%wp(@ݺѠ xmGO2}ai%!AneYr9ɰc*SKdm "-?pV8OhҦkf7zA"7reWju޲ Ou18LjSvo֫@ miU lJdfϲ|&kaeVK 4$gf`l_ LoԷFZFZ)QDFIjPp OK[_Uƚy}j*ձ]5eKkǖIqc;KѣF> wJ?\_v/䅯YկNV7]*w!zyAߴmڠ|N` xJ-3{ש>ܴ5CS E O=+_97PVPAgdsuv.z5vD(:|鉛k֬1zo\*AwsayxB6qݏC9bJix+#xqo:*3,rhE+ DF=m۾^*WzBuXKvVD%+`! ¼dvè-)HPw8T8Yw)V!jfxh.+CBXYN/5`jT~Ms N5vި*C$6+EPʊ͆7M}3"ѪK-0 bB@ XF ImEz6#ZzN9lQ--cѠ$+Y Ad9IJe&@Ii{<JJh*2 f,) ಂNQ!G!e7Eݜcwkѕ6(jaS\-rt V,mɕj}F9iQ&YQjȃ JaAt(kҴ4)ۺAuP(5ꈋw*n<3Kti!w^Ak܁hٲ[6mor}ta[n܅m–s=M֌b2Jr\E0C}nv"xEÞ ){bd"~޹qd~J(raJȭTb+J:uꄆS8 tsrx F#zJ.^,YD'$,l֭ST)5J3 Җ@)9N!Jf@JŻ솮u^Q~EŐUS\~rL6--Z4Oa*9| =)9ZddQavZvbbbSv툏`qF5K);H/,:W%Iznl!##1xrgˣjBT.(2L83LyX^+Xc oLE\92kՀ6;( eǫ Ტ~OB>E [EIZ&`"X+Ç$~QFB" :A?II&tJ* u'\7srTPu(oB.~%덼@ jT-.ηX-EE`L ~:)3$| `?IM{箯/iX!#P:G*S&H8D7d3$˒вL@ ݼ#)jRmxT͛7;b0tlu#:.-4B5?~O"Xs,CbN;Y.t'ێ] fvagSOW+,Qt;&EJ XtKGJ4MV>i[wh\C(_n5Kexzz:'=P?((~cHe%$&{&FY cB.Co9$?i@ .$V_ڳ{cQO盗󂿿|fC/jUC@]vf= .F@A\&I$e,ƶiӆ/& \e\K(X\%'\_lJj=6MyseGNo[Nj pP+Tr{˸Ǐ66Ăa)gerzP^t 'N}+FPQCHb1"MVZdgVr0APԔhD5v=!!G$|`W &XӼ )1aF/_&Jyk؎2ԍ/ 4=77%$-%Z:wjժAlge8 j>3;j1 u+aoWnN (/yy $)T+* 2s_,vͺ=N3rFN>fв̎8J/+Ш:4+;5U8\Y(`ƫɥr_ɵaaar{"DA hF}}+gJJJ7IT8IOwiAJrd@Ctz;zƷsv˶m[ D˗&bbj,nSV.39Vqh ԽgyGP6$/-(frD~.jLR8ZAlMA&.'vXe|TD􏀻R>4Q<" Z٢1z?CZv_-x[4sm˴ug*utJ^~~oFFki2.%*b{zIQl.NczPJdh£Lba$Z]>2ƈ`=bl^_EcjN6ZP9[W̚_,AgOmz݅GL\\b~TL%̲==4]ћ N%mS;s?~;clotWH07 w{iv_#6%Nk/},Y}iׯj3UT><[[SV҆~m+{{GF.yAo_)edAvVl//k 'q2ʐ{#'<9Gabӵʖ8z^wNɟև֙;u-e꜄k&Gιz~/k^^O\h<'4),AX}Gf[x DŽHe|#{ZiKW8IM_Cĸ wRQ5CV]]OZ={i6.Ŋ5yzoEPG}wͷ̛S】 {ԩs_]={n\#=KwV?J0E'4)ZXYB,]Pto=s;~Z¤w&JTYW|}T:wo_﷌| 3>=HS;ʹV K^P?<}/ڿ`/fVDmfa4ibus6Ր5nOہ+|0rcIreH\sy&v˙ߒGIxT;sl˦? 5_\{ϱ bYS~ɷϱJҶ!2K >Y?n_tO{oMaM_N`:GP(NHjs>m~s.wUd *N׌*NIw^:qPmd]a7gg|y_]Ĵ2u8+S0W2"՗5B]5(IWȏĂUǸ~UUv(d݌LR (4L E)00@B vq%g iq%9߸a#GX lYYI)hfDHLNNZNNN!\&9 H5hĘb҉6!㔣kG:'/4*U FCU6^p;UL:hU_Dyשg4S獺B VW@ΙKN:1f,Ce^|{ UZ#2Sk2\yEQ)Nnةd$oWf]&SJO(=|N}wlޢoBP)O]ٰL|J)JTS-V'T(`xY{S>qv\e@f, U$aË3ËT\ɔX %g-j?];AO_,_|*<J>yk:لk}UضkEY`R@#UR.~j%IU׊{5p:LuCPFPVOT>A:# B29pl4.Vld qö?f?̖E?y69vyiЁZ]I֑E[a6nP<*K ުJ>gG:9~Μ9sͪ~ )]:C_AQ2n HZ;4TA6:G<EԩS$L\:jEh2LM}AhSTrˢ+9% PtvoNwnwh.L'YS%16Ef֭_ڽK0tRIT D4^`d@+@gBR e%AYՠeU%HO29KɘFZY+(Λ>k k Kk6qĞ/vKn'[rr@)*%.ZKr ll/WO-:^SO!lޱqWnd|fvov?R҈%b2l*UB&N|P`P:u@yZqqQ``ٵkW_mppUZpi47j߾=O<);Ii%l޼yZzw`$A2zړXΟ{:+QtdgN#|7rKס?QSi*T{i꧋=Zlyng>E-݌5o_s/lu7 ƽ9qKJMy+lk|;o3ڙ !)ӆ/lݡ!5[hg;{%9I gdr@JxTFuF-Of]#ȍE,yjs'?gho, Wg?^XѨF$CΑ\P#]jӯnA*yؗrnڳ͍[&FdZvC?D׬bhC/ -Q"g"|j4Y1+1dA|S`{!H)ɀcEREx @V(NUi~I|'AXt :u͐Aq 3&/n鴺hOffʗ@"I,6!آ'۶?nw {gt5<`DPG[ΌZ.ZHUEÐm-n q?#^ՊCK0?3ȢD KΠUQr2,f˽w)'Y#:xs+UH0(ːBNϏ{z>Sn5sIM+ N5PCx1gݣ?j}H8by͓,9AxZFFs1;an|%jv`6?6UOwˮZ&k[ GT;>Q߷nFb$E5B:0< Dmtώ~g HI=LEY7CїSv[ ~"N/qȟ`!w<4(d) I}"igDyG%1O$JJ b.8 f*k5SADuIMMͅJK#'uQ8 4¹`:Pra0 @8{05D?qEh%yp>k=ߎ |tȉi]>;ҽ ңkrF*W|VqH5iҤG΍C+"S͛3yPxsۃP*eOEY:Y QN]~vɥ;7ZfffNN D?rǻJZs m6+|%@oϔB:2εӧK{tſ*Р uErТl(& Fˋׯ[,ܜLh6Y-)ATh;u6l xP9)gh2n\2=?Q)fW,^qTkPLC[kꈚujkwb^6笥+ި/?>o@Ż9eՋFE,[FR5Y1gDÎԨtW#%lX(?|UAUk.;&xCLwzԊ]6Gda=ЪNiZz6}Y;gwo7ih/Cu~nPV/`1>^RCen9b?suGXVf; 6iaa֯[mi_\m毷?iv2V.ɻ)J'ܭ΢o2娙>ؼY㐨(_*z%cgdXLy8OUb󉱯"ENPZ<쉗LN1vg*&oQu|RmYdgV')'v2N IVZ f4@??@V-,))-,,[LLL&M5uqqIAA>WiiiZEFO0N~~q8 H1wrr)(}WߟJBwY]oTmƚ ߜLN.yNRk3L(?$"ZVU3,8+1EE]`֞;{PB7E, (6@TT+b v)*"H !l;?M|>f3;sw9]"Sr߶W-9y5`۱ee L1TI/RrhӮ^~mQ112ݧV=$~.3gxތlc7*roXS;q'Zw=prtjmm5gqzYv>dlݿdp:+{ìQsZ6ofTQpCmC'LvatpةZN]|GOh=; -&EGm85?iEj$onO%2"BG\ˉJ`A}u8]K\4':K'?p/slI|Njg%l[wxGN543 m_DG &3٭\}bNox~Cm)L r4*لn +m*}p¹ˍJKJ#q- Hj ^;>_:>;&mWuGxXd.DOi4_%*QCW1{,p*li) dX-K-}htwR^)ז)sEiJ%^βUoII`HIڵVXU"XBۊu};e@\qԇf ѽՖN1Z4K9J ]!z3wȾaIat^x|:bxb-{! kԅ'IJnEUh^;ybltzx Ͻoοot5 KTLZTD8NJuz Sz]FsͨN[ =&M6y9@1ȄĎ{' $ǯ<}9+vVu1дG_I^*Աe$0Ɲxş0KH(53뚂xnEzt=3؛#뿺eLjOxR-q9(N"/u~-8M@ G(Ya$pPTGqܨ3c0 l!0UCQ9&MՒ,CY CpYRD!;v%؃ǒyE)(,z 4( B8oނ?$0&akkkJKuz=ޡCb1d8~zaaaĐ l8`DDNegJdG%''lZr$ns2*a{}xEyEVUVo-)uk~%ǎL}oWmo`N貖VVUΫ[.>_R]e*)#­No͹?КC K6)|U}g--]OWWJʫjXűXA~]d;[a] u*9zZW__T-̚<{/i߉ nr K-fF|䗞Z۞-^ /68m 5ٿPfXE{ilͅEzH^Gi48w` VgnW.u]5!Ct)^9{ۡC~cEc==u%T|0`3ʹX'鬜a~is:]vzϞzO -/JLMeiVYPU^QcVݒ#mDgz QWH^+N՗eܲk;j~ZS_`ѓȔ{]]UU>ݭݸ7;TNEuEJ|?렳= J- .Dt)KӭeVI䂽=[Єsf\z/.(Ts pǵC^'PiL0Lzy{-س˗L~~:lF~R.4,uۃ;cBVFm^ 6<<".Qx! =^t9G9q8;VH}ll Fegde[B32w;N !meA*ŁU35؁ J`Áպh l}^_nNvAA!..!==[nQQQ&fCBB~ueK*B=d.,! 1YD,X |? edSBe{sꞢL}>@M0$K224,ߚk(ƯY{dA}fIesh9W/ϼ غi2{h(6Ei&M. rˠ lkwCzu uS, VD77O`o+}jۊ߼̰1$p2{no-}0̩~A,LBHy{ՆFˢ0zʬ[&^3l@ ,-{}V@BcS]<\씫2#Fk ESW}fL`l&\D8ȝejH6H!Թ(eŠ ~āOMCCC]Pa^j(ȼXXXH`8ۼ[i5y&+yrPkW['N9u*;;jbi0sjnn7YYMU~ڵZmew 9Ǐe[ v P? AvDSM;sύNqk*\il6缮&4}Dg| {k FrgN;;_W 3?|M3 4,sw~cϖ6tcxl9SDkknOPo {cRfM& "A `ELT Kd/LBDF92)XHPISSSq$[׮uv nNl vpl #G|𰼼ܬ,Re#G4` Z/ C*48l[ !Dn}Xk(_d[ާ&k:pf$[U]]Ueg} y-lٻ$֐5ՠ DڗBAD {GYT_[ \%(% c>BFԙ V6O.5:$֯|$ojG߿_5H }XeeGs^(Fu \@d |&S]zWte(zs g99M-I[mpAS䢉~p0;d傧}qH|~"Ix݃Zy]]LMba.>M~+F>}***,++w|:4,o`ؔ1of6s˾]?qK{tc;>>Й#(,{ާ_1~;Y,\a|ʐ5u+BGaV/1J-m]OMV ? +C-Y wmuGV>}Z8S>52,Sӆ>Mw紼@x> ޱJTʠWv}w(lWyxo̞o0.ꋏ}SxL&+[]QR{O6XxŚswֲi*A)M&s4 6A7Q}$F'ZD3μZt'1Akl3*&`$$a\<-X*Т2>h;*HV7x< 3M> AWUUYс&S泒, @丸BXaJ(>>>{ׯ5-n<òaa(jd4^")@_nɞa}o[V '_i1R,yiWNj̲oG;GD*2/;afi'l- Y))8Wϯ5\ 8^U^S_xz&~C缦v=2gDKC3FI5'O2 ,a]rz%Ke_|\>,QK9n˦wȳ/oܯ?_VDvB+?x{[g)ŏkg\7SlV).yċ?|2/=w%ZAlYYg9Gq7Mѹmq܏mYo=) ~WKO O9'Rꝶ?[y}onoVyhL찎υ8 CriB{`?/LGr`Ԏa?vv-Bc|7iFB>~z? …u.UKyv*ٲՌWgy_/ooȖ6#wo\!̛/[_{fʓ3k¨OVԡ̢Ҭ}W}}Ȱ!4ێ5J]J[fʞ7X']v4\Vf唖e:Zh/YuGxñ!yEqj())8*PZVॽ"jVpWmnت* %}{' K*>LNQ٩2Qׯ0VL٫r#:&.*à^/BP]9-y:Uk$8y[[VYboYƘyb%'K aI?x`A% ű\k t木swnGvݴq϶lǼg^uEvQV.p1gVogu9|7>b.J!463:!nM'dmʬJwAUyO߲gYF}:23c1S DOXz0&='Yܛ\acer*xTbZ4Ka%HmXN2 Eo?P4sE7^=pI=rhή*kC٩ w_{71%^W]),Qg+\u#ZDqb2Y-*&-9U3O?,\Z0<^nrk'EUTq%e_f$/|Ft}{&eŮ^ڼrՁ#|~T^UWvrߺG4a"`-qF}Bb/ihH܂66=~4ZiwL"Opg4yiCo&vB~}o6pdEuAM,ყ<О`ء[DɊLY@.)]ii ][)E#B YY Tj0ˆO3ݻ{[ }>UI 1dj@堷~=^׾}{\6l#߷XI3< FQ**}n/Wسn0$pCJԤĄ=zOYtLxddbbbTTTrrrLldܻgO%K.?uӊځ!,*vn݇[.-/-yU:1ʺzuAzр1S kY{W=95ۋdx?1> 7=~ I5E i #YDi2&*1]^e:GZ)I2QK*|,,x!:5)@7tX<$*YUhu,{ 2iI1yy=g ^񼉓#XK|TaT~^7E%}*Ov! ȽP:vN+B%δ>">TtW<"OLIqֹyl Z^FAҡ1b%_LsV.e€l3jmHr̥(:N[k,dž·oKNѲUB8$ 0p'>9>jqwNx(ivץۑhK]ErԔrx(Djt<72u z۸͋C`@cy$$*cg e;>bL'uԮϨ`gTY૎o&JSx7~Rn\4'3-ed'J+.Sဧ>\ 'p:HW `0E::FǾ6꿡аa_CGNM*"F5Zj p@uxऄeeмު~NJUq~h~&0M2p##4#q4T-$dM!<}h4vuyݣe5M sj`1Hxbr{0f) 7uu n0jd &pX˅ܚF#e:P!{;txQUXk&G wqq>%6ɞp%Q|w^=}|n(+'Ź^u Anܙ .r$1WQa {a$9Apd}ǎN d -`ib/cPP6CcZ@{Q(U;LRH[;}@$PQH6`p|8MG$+0o""1--)7a(&&$WWW ; 27NaVCOmp\ mvيe+?{7KW|j7EQ]bLL;嶻m`]Lnq8j|Vo=~Zr!L0]'wyj-me=޹s"e>%4%F XW g H] 111G]T-F p :Hj4$?07nIM,n"₝_Ә’ Kp&S:#^ G$Ngee%~f8Nh)>]kэ$8\̴QOE"$B+ ChhHhhp2Q^dQ o~׮> *\%\rZ{ltDuMp.qCα[tn]BDWUrR'm|JP]ݟ5BǧuqucY2W_]sṛ,u>眻V{w23=w$|h 3S vqm[#@"_1cd7MT5؝GNGHĵp D0*ÐT AN[ "0蠆u8*Kr HvTw<h5zVl,hۆ6LdfSiOI0p>gYz=t=-=SJC^sDkTֶ?Zy~N=y'e{=5H5 1D7N}"^.),21c@3qA^m!gy GrF˄_q"K$-~=N@ErpR)&JX8p u;q -#&nݺ*ӊcT)G5vڤ)&u5dAdL@ 3jm V=l:.c_ 1,@ѩ"%%fYR2&B`-PHLL Zzkl?~?XB"̔++53 .4ĚuKaե]DMG/ѫRPGD( S.ʰ%uFrA{W]>GA2O(fO)><pڪ3Vp>zTJ!3LΎi.c7)9\@w[yCkd'|Ј}қQœ;z~YV*8FћBC:LO56A%{N:ꚧЙϝ.Yԟ-Vll,e lVDL6Q3aO(O$e+ՠD7ҲDZcǰLgH39(RA͘pi(m > ?>CnFzԸ,f_["&傓b J"f-m@dW]{XUUUU]]ݵkWp7?E NO7mMnn.\ Ïr=+u GDxnޗ?4Qt&uwyXxC@5Sy#&jp~|cdќJc3$ ctFx޸zML(G(C "FXOBG,Րތt~?$)TmK?ȘŪ5|Tze,x3n#ʒAɀa^?|fy2I aHãD=^?@ [=#g7a4^]dH7Umgϧi{WqIeP7X[;#d"b:tO15F:w{2FNL1,6[X\DKTBh\jtn;߄;GMj'%tD@y-UGÑ1G%\ґ3'ˈO\)=tiZRII1|îx@T8Ny#D**P`/**<72#3jM@â,>04!@qUb ɘG!11n'biM QB2xQ,6Pt()q!r췯_ӗ^dgDTغg_M;ie~jMg o>qKsć^9k|,y=EU0'xi߽O^=ưs&|SU}NQҔxC21)8?!6"; C[닎1ǵ@HUՍŎIK'$'{ttƑ:Wq{<OG ,ycO J -]]Pcc<?@~@xGxx+-xGBqЕYDEf4fs 'X[Ȱ+~q QxE[l3_9cTs8 \ qg8 Ȁ*(6C'z/fI#ɒrՓ8Z`3n!k&O}=W';g?5}I7+2齯tcmEï{xo~ɤ'>0Q ?t_re2ÞUhSB^;\?.uLGth޴Jun.OvSb՚Kd_X5?jfԨ{y?ƺywN8w/g79S$vnބe}zC#\V8kOxG V-X9mp`͢Y~ǤW__2zD^Wn;w@aqWr'X)jeJa,nZsö́)w7[[34Sܖ"ZiKߘ8+fGײ`cOipBbB 0/*kayTݰ3g SUG6V\WWx I*؃SWPWxjN uɜ},< _7?gxIw?DFF FB]h7 w^/!ܛjE^^2T8]՘-fWX\."~yjC^Ջw*0HWJ$ƃMB~ٽwkzTm}2aBa fd(Q )+c(p;գ*i,:ߛp (Hzzu : #"̡o0h"A{V2ڏ}Q/yC<@+ 3!M]]#b"#ycT #(`| afPӞV`U{KwiCּQɒʧӄItP ENQ^ yQ5 ˍMxΏuz z/0kӖ^pO>Br_d7Y8z篸]`j3IŨcF-Y#:4 ow/C-\9Ѹ ˘7M44Lq4ɠa||扻\7'G}Mɇ|㍼ݷ:؅p;_Uq&N),̪hr({'孇czqF. @Ӏn1D͑Rt%*::.6.1!)&6Ƃ^z28i5\WtltTTtUUnhhxii(V jiSɑJQYuZtORZC uNJ.V:#ٟ9n^x٤opf]=>l XN>8|)'1fFqޠ>@YC,!u n6, Yyp&$%Ԩux"lu %HɨU^Ѡ +Ѡ}'͝`W|֔l<\fs>]/2c%urޢ(N[*x8 ֻ ֯i=N DP + nNoa1AkF&CGGtZ І:`ѩu^\Ba<߅ׇD0}ߥ1,<ւL[QVe̺B\E Ld DsdBtqc;[%c]!t7lXxOm)UT!ΈnӤHّtQzpd߉ /G1 ِ'xZfUj%TQ0"[0Np08 >}b?l&i?}Ъ4H'F,:4ad-^||<9s ,Xij F#E5+WmVm0D(6j@:HN6[xУћVX5uon$^;ճ? 8T}eiCUm}nQqh HShTHljX|;J"\Z:sBE 8s) b$M>@s<$KSEt&)1MdLyZI pcJJJ=wخH2re=TR8 ΖOb 8H(E`Z^ ^tV~>[ϋʑ0#!fôݺP$C|kjHLCI #Z^.DG6C'$$GŞ..;g W}}_6dP]8s×W>$"-BX`9g,x1!,p٫78?3B8L}F#w:PTgrq2-}>LL#yk8 J8,EJU  ڄ!>l!Lb%D K(YD)DS,_Qqq1"g~&v!@ĘP@teH3)H'QÇKJJa%|V 7zIO\tj5Z/Gxnڶ<`˭'{pD޿?xnسhu;UI(SexDFȄ!OJgҾq~G|Aa( Y$vI ~c_-xDEP2a.`,Uj0(8(φBV!rB7#3MCRnn޽{233kkk2%&&ȭV+)2DPD*m;EԾuvb޺VŰi%KNt@jp4ƙs2I<#'O#np"wwGEEl*$5@O4פI4QůQ> w >0 ~N'88X++T%+JUf:3Qnt=QU/t\hh(8gϰX{}>Ąp:ID'i@1eP^FS\QHaGw'κle/ػ߼/X$2neȨv=C,t]U5 / ;d7I׮kzkUk({o}VL_R{QQ_5a?YoI[,!!!UUu~˸LdlB]F 5*`8qV ϊbDDQcbh؀^#7? hExh.VP R4gCqUfV;*ːDms%%% |^/TiO50?[OLKKIĄ  45$I&sJʞgsbw;/xNס׉uO >$%(wSq>ܜ"I# l3/=w*jvQ(+:n}vxC@W8 XT W+zxVo)F%\/8TQ'%P׭I /ZGFFC^S>kOXrtVECAZcmk_]OmU'd:5- j^^%7o%AU`v!`[P5tn}?NB~kj d+<">S`HRP?/%HXiβu_uߵiZgyޝ?}iۭ8$&hb̨>u_,ɷq+2\sem:s"Cu~`NgNS+O嶷yR:I3җ~yϻBF1ýY5 GX%aʻftI7Ø+IIޯ?t5NkOg(Ӗ־GUW{*2$ \?O,e-9ݎb2irz&6gBzw"L:uƚߞD1Ef ~ENX̉q]ww%[!1>{{q5Ԗ(9;?XqѨsV8gd'p/F>q3㖽QǞ ;W+vW{z£NIYMpci|ϖo?QU^mޚ_Pfij8<}`/Ѵ>Hޞ5uw{ \8hgV~ЦK/5⇧&lm˒a~IZos$OXd-+·PB%^4,'Z#qOL9rdiq1;"Z#)2α `8* րD`1baA;[AΉ0GGtֽlM7z<}t)k@-IG [`wUB83XK۶ߥZsq .e܂'|N h6ˍ('H$ eb I.Mz"&Cb\h7FG~Nhc^~n;.JQvZE\9! g)G:+ڨ(\ΰJ8|P!q+hN N-A"D[[]&1ȏс . ۶mkκ3Reqdyg=Ei%HAX qNeL&5II!5KFjjɓ'I E t 0Y< TTI9Aop%1t~*+M﵀u5ںw%8:eMlHeZ1'䐅K0Lɯփ/q۶mOmP4q7J\OY԰)T:DK\&qz;uθs'biAzB;.H"^``&zN .îs\HUk%.9-yͥؤ ͨ}KL[&u4Q,Q [=,)"Ìw^Q5'sڶIƵܔj_`Wc/0(kXyNC2=P3k0NKyQ?-j%߽{.]Nd {oWq[;h(,) Rx.3 lSD<,RjOm,쪲8ix4}n&u]cjVR!4JT0h4"V륱4e@⎪ꈈp5v༣IqlSHYH# w-:6.,4rgfdZ M`E+4x+Jqm#!CT\$wa;˴m/wLHWQgnp&aYf) IQV+ʙ>oSDqO<HN$A7"4@ {ĮݶY >-6۔AYԩUGa5,v>X,! VW[*JCBBwFQ j3-%X*FAC~7bV5f HlDžɧ 6RDH.eݘ&im[|>9Yx./.;_"o;]wNb?Lv'<#?@ʻSWI;$H<@D<#a;Q1J2bD ,3rޫꙝ%rXǙU{?rȹ7ֿ <>Ϗʫ/]ZgN<`矾ixKpv *)|g}+^y9yWAIJ6&}9[ w%e?߻۱& ϻ/?^OBO~Us3W{f9fB^qɗ?{Ƕ)N"S & 3: ~X}an*= PDb7vx~쥪j}M/uyHCZt%IpWn5f PIGq|+7}y?{wB[?>}:` M=N`BIg7;:;_:8k&“OrIgihYVTƍy#+~LT]3\4hnDl[Oc؅8*n+ɤkQ_9ߎ*ʚU, 76oY8"ŧ_{ǭ~wÍgU|%wN:!]Ziz_ݷtιߗ80_ǿzӉo8zaw/lʷ|/Avym*R[<^ˏ[mK}里4'Oi?{'ꋿFTƳ(F`^gQ+۶M3d밦v&uƽI 4cT ٪QlVz>A[o7?O3As` xpѨÿ{=!i]4Kt7$yN-u/= S;i03_HX%'1r˫orlt'$#n??oEgymӨK_eiӬ~ggE\sS;f;#]}׮rmxg}p۵˻(w ݵU?0? )L# rힳfQ2q\Ԯ q\I]=z?h sh5k^tʂR^ h,vMȻ1l&0T*p895]w׼GydU&i1Y?mkc[ydjr ĽiOx/Rdv0nV{jOX,f$称OQ}Ƿ~y>wִ$ STVq'+g=8|S߆ }]N5ɗ.| 3N-dOh_ F4oZM+)pTN ;FOӱ4x ]o^w:v@+^zYEd   IW{1,H>CӝN: xEdI[o&UQOx;8d#z޵2 4a1?=LwNen_PŵP{}*ԅf #RڞZ?AQiB[ŧ2! >XUMcZptP'\_W-<+?8xL 8=Bj|q^'8>?rA?9iTN~aCp`oN=%J[jϺ^7 uw,໿[7$yÁ{%|yh;+VvCD%Jvɉw"y) ( hWyb@/zz''/l9wTNrB!?( UW^uzvӝ$Px&}^v%=|hr~{V^===}_K;Q:c^ =&qixg;ɴXx;S*ǪcS ,y9ZP2%**Ey*ɆLVJ2y!M럒NzFǶvs 7>EOAȉm`q2Xd\.m޼V,ީzRQ4YHNN{Ѳ4V-OWwPʔIDӌtȂ=%DY ۝lxϰ@:NeyZʹ6LR$ g%N/${=ίCPtm F2բmT^$@H?h'˖e%W(>媢cQ.JH1pa.f5uXԽLդi>Hh>=:lklٌDT긏0L[R&lI/^LոiÛ`6l`cé%Qf);=ݽLr>};[@~L]If{=Mf_i4QΜ'BIFge2yyfD4QfK`hʧw,BT?.Y󰅭}1gJRv:zقM5I)h?ILG;%&Aɼtzђ%K|۶ukܛؑ%;PZ!i a'j>1> BPry l˄=a,nFLʊOMOI4^?XIIz /MscRz0c@9Ο|? $5d2yV(:4tJcjٿ1bDǙT2m>RqVLEtܣq: c&jy*9C=ɷa"a'mڡ(=Eb&aGuYzgox? ^qul,ک*cY sKU+WUN:SHL%tμa畢SJ?_Cmw H:SdO=6ip5O,IKT!{&/T\I㓬 Y&cW46뉭ix smYLNo|s:ٴcfn|s/#/s͟k4s=W=0c_j:r>n lʱm떾ƫ~p;ΞLwdkZn%MkҖ<«`rg.#̟wޭZsc^w;?kccDs^(IbJ)ÿg޸~Ww/ԇ:KtG?|楲-O/S~nf6woF8@*vo^?:wxɫ֝£//K&ig}g$u澘[>o5\q]a}0KJz HgSF7ګБ:kMVݏWwqqto8(gmXco8߿H+'1\)G@{)oV;^~ӽg$爹/ݣ44̀s\^ҹ+Z!1(և aܙ2YL&/h1w_w"/ CP\GȸX(s3Tmt̉'ϴmwnb²< >)wMt2q2_}Սg$ IӇ^9~u_>匋̋yUrc(lNd#@;wӻ6 S3mCSy {,Jr,$bq8z|pȢaf1"5a)"-a@{BG οȸcAq,e8ޔ3jA;` PB8Yqȁ/9 8fӝ6۶{0: -jNe\JU\ j(D^dA~u:ǒr_':NuzY3FmzV Ec!A-Fy21C#U5k@ ujjz]mg|QA-%QQ }a֢ Ȓ,[|N hL-[ w}'|eYgfa\#Q3N"V/3<?rG+04x=P)͵lܲU|a5{ӷclpF(^p\4gi6#EU%Y&Otc&KƷdR/hLK?vp:c&iA<3[^mR׭Rxp2ϡ@8׋P8jr+K@ ȶ,h2P?A.S?Z|^3Qs2<'I9A8!be1c, #ʜV,BBm3 cՙ6۶۬nwꠍEIY\RaY+/rR*(NGten{G2-NMVǷf',M͖rhtڬԿBߐ,XT ~qFKլNOi5ۍiw& C%\bihH4)$ \j$u <8~Zw{ w =ggjsW `2W*-l:CV0r0$^/rXxѾ"ɺ͢]اq , ('/ p,#``Ah]`nqD`g۾Lij1U<0RrDrr_.}}jvNMn }ɲ!SD ,fYfuEU]F / f Ok9ǁ燘+Pqt+=%> qSXwî۱m+r]-c|ˆvuö́(Q~kVG,-_*EU+އРY0D3[6Nu–TJn^rN3g\hq)ܐ d 2!2s󖯆g\Hl|Z~ͷY)UDhmvJ,enθry]k_sվ?l(> `lKîaZtK_W%jku"!RlRV$@ {aVX}~h&Խw[B]^A0w[:RZ( 憀 JQ$أ [ew'C93h}ٙUgX6^DEeR,+%8.,o_w׿wD7n~ڏթ-^mP&Ŭҧߒ6VguhathZ9]Ey P.lȩJ16^);A r3+Vuz-03UY3. :5LXD]fh@&дx1%K4M#|@q1 :b^f6@aNЭX Rm յ(eY;96935 \IDKg94M T :=d0\㶮}BzE&Fw}*id21IH.4{Tגr=2|PZiF٨Vg:zǶN18%xY!,Щ,<'@YZ)\I Z@o C<*8N(pt0,:YNC|n>7dž *̈JAU^(| vΠv7 \] e9VmvfrsTAjEE+ȰžB,,1(12lYlR<[p#^zwA}uq;8`9\K-w='_\p; ;n?W|#?n->}+Sz=ܳ8ߖ?ǿ{Nf?1|O?8/׷6'sn!ŕƍRus>2*$ ݇~QdoxwX㎖8Yo44576Gs.T]/HһcЬnw0b8Uɲ2]){unT 1+RؗX@ʱAЏl |ɘ~v! 4 аwtq;WH>M $, '_s%bp`Sna9E׷$%`&"&p`?aCfk~* {8^xvAXu'f蘂c$)(Jo ٓL~mԛiuZScy۷ &ѱm%ϱ1E؈ \U^",8&VY 1]f&zUѬC-9iFdl:\N^ }Z(V.  ڞfA ˚ JVxG8;F?75јٮ74(f˜kA3R)_dY?h>V[O ďw{ǾmȿX$/,~p;'RZN8~vy?^zYKDD%DE" w -bf4U* -)g X$LdԪyz z5}AA XD!%4F D4cy.VӅ)\M/"? hΈX-!nb# ϨWJfyr\J 1pE4m[& o2*4 eUE6B"aы BDZ2@ _%ivZ)h7SS׬Ih6@Mb`XDXJ߀V iEPF3|IRM"-Iϧ) B;8ugg[Ս 4`73Qr=WY*J σ+Xe&_Pѥ{@O2s٩oXl`WAO߉w7\wuan׽bĮ0xƝ#!ez7>%'qI$ӗʑO^[N&!s݋gYo:zO=mQͯoWx̷yzzBn@?` nǾ FE=3@Qe9a>biNq Smaٖ@ra6MlI"kyFb^붚@]&z.@*P~45P1)I|0\[+ ұ|^B?E'~{:'`aEC%t$~R$$ $⣕/sE b$ Au 8`@\Sѥ14QV-_luo2u|B|Q[b5m0bkERjP@\`:),yd9R._$V+ZU$b^C^o{@NZO^2@[+w[/ ۮNOW3juzfƲMaq(Vs1]EkAA\,El0MʗyQD (4Aqpyn=!tXn^k5kz^1ӶD{α=VX8ri@Ѵ\J"Gƀ7HQ+`9|FC tnL?sA~/İƄxLsl@wL2]Q4,1⒏e\&w?Rb f^KGOVt5:0p@ (0q P }tIŴ=^_6<"']xLGFl4mwZq2H. :2@W'liP*'gxZnAA$w)@udlmiu`#mXDL#jㄶcD9j V4ow$gJAZlZkԪJ$3E,-P (v _\ݞh͵Z= ƾω| D% $qOu}XaB%4~$3I?j̤貴OCc}.ubFW2fٚmEP"`]I"UYnoHSFne^1L$# dgZݜrzճ;^m ?W B|J묤% .^0֘#e bl~!Ea4n'_/kuq=j33}zވ <`>p kX@h-'wlӷRTUƁʲ/Ӎ\׶jaa^6ڜ!ˏ|WRB[<@I9"(x~,~o96t_Q8#v0|DIhw2*W,y!^ $Y%kR@Vd`ڈP O4(*^ N8h@x"s7^VDz<<b*2(/x˶A)x+ԞI:>7̓7mYVMON\tec>X[MlvB[( hvu?PAϗd+Z1h$)+==eC07 j*)3uEanW ```1<Җ @l}/c+d5ȌXTZ1|䶭,h pE!M6MHpJ%C e__qR?%)0` a!"X^wi9IB8az-p$ Ri2Gh֧&mHb,aKp:ݮlnwmZ:6C,4U.pDԊaݪq,S~KJ, @+CDi.Rb'  IP ]N6aalծ!Uwr3"q$yϏ  ,Qd?9E\LXE%U) u!hc׷kVUN?t `XQXj$|EA{&=RF `*) =v5O+Q\JbC!^-sNHd)qy0ވSJP*/#^UOyLA*Gv=$uaddH=R(tPʰ,K K"p`sJ!bCKAQ=L\lxqlǛkt&fm9cHcT<0qU3zp)| a 9'-)i0%ر``&Vw ?|"cFE`X/W. Ar̖c$nLRz%aapIA Ow4mXSSfeF{&ODM,sn^ / h95hetH!,]u夒"9E)hJ$Fؠ&N`Q[9[oZ}jflsC(I'H`C NWdÕ$pCAW(вp~zV5hs I&_%Ux@My |N `T(!O$C 0P.kԷO:nZXp5[U+Fw չ)wZvO ǣrriXk5t~fD/q .rx;; pU+9E1bIJ",_/'IϷ-]k8z% Gv;zM,1ߞ @}c,])ʢ+ "d^QH[TEg3:Mk̟$ в( đĒi̽ӅByNrbI9#U ;pv,_XӰu;4=Clųi)iN3aEUF%ܳ.2ӛ6.L6[~*ѕb$!,u@ZeVLc/"+4m0؃acć^(+ZdJ c>}yimTڜZi5^JfJO5`gR{BQS Ea|Y5Ԝ\L2yfv7AgH(r! vbfP#I"̀mtn -h9ED BX,gy=i ?MUT0)Thpˆ c1"?',>ICIr?m c5,QCDӅb { *rzel~x+|D"@hPťC*s8x*kVئiiu;ݲs,'+ ,2, ˂zp Oխ7|EIӴޯ0QTx}ۨ[o Tn75H#B՛zS M4ulU`v|Oq +W)|tVqpB}=0lmtx>0c> ̅?c i'5j>J%v'}AP4lȮ(`#^K}KLX (+nVg9]`~ZAIcwl^Z`'F'* /!U*kza5{x1cA&|6|XYͶ[m Lc9f^g$gDqޱ5Gs<,yϥ r2 tV}%&p(,B܏* vKC$JP IT|˶=ľ~, XjӮ`8EBEU9^]hDq8+( s ""1+(jXba}vd=?e2yA{T(M!퀥Qp/_JVcdFz22JFơSb<3CnnI(XnZXJ7{i b@0'RJ^H =|1ROSt %zLSt{e CeL9@ Ȍ$Ke52u4|uy!rۼ"eR,42$!aCQj__B,zc8[|9IT plSfz$kLKRd3 ĭ/(9x"{"f"7넡]]oJUv&aCaB۾e+͉(`a3`r"`k謠Z BIBN"c`V@D)rMˏs|% T.cG'bmu86ь9Qɠ+4숀"ZR=t:A vOCcI$iK8%oeN,u%dYKT%tUiH;i͊]`;vUzB *XQAEEQu}q:PaB !!rwD?;-${kkQt4,=S奒u؊kS>4Q +)8г5udh;W9eP4-t<9pWhUYə#]RByʹTUE#o,NѼ(adrcsNa*㖪5Ωeh!imc٦ܡi;obɽƂzwꆢ#{ !q<[e^ Jj Y!.% Ĭ V pe+ 84QJ_kU)/#Koɥ)xXQ\X9(K*2/ؙ R:MJVrɋݴrEd7V_+t 슦aʒ]KR$ʹ ;n1x"BP"ӚF3OV?ط y;?{ϻ㯉' ,wc{tyEbKrAsϩǮ}# /<Rpq#T$r0nO ,/~7:-/իWC/wӳ>/?^!%Ɨࡉ,(35;6:ɥbM/_̾rΚ +> ͉'jVGjO}>TMdoHUЎ"& ~hF8UC/n.%"1!FU D*Dʔ3ij7ਕngPe m'Ny%k?veWsLTy{>Ý H_m+ϔ7_otg]W}Mѵ;/||ֻ_݋^{p6˷>f-eR@l:nSu`Ewpyۡ}gP4mӰUKAS$~Sbӯk]*ɢ 9ٰ, 5ǣ%E%t^ܿ1=Ng`Y%b hGӳN!Lx!k|:jpD9mte$QƔ]CVz- r°dh חT]NIYߎ<(h*吭 v:y8M1LOm >b3Ky,1ŕT jVFD5M 1ZywY {ۙ;:wuωO?ztZټktY$ٷU,yDqAc3>J<ﱁE 1 (N\A gP=S)faVhYT`'Ӆ"[^9h,s1h4H$aKLɖ bc B]*N?)b ); nG\fg+mIjV<\dAU^7 ; j]JBV=HpǪF#_~Gތߏ;rnzW}ӋLdP3hу?xk菻r|)~w3;_s~9˹ >9,=\o<|w&s8_q}j]qe KLb3;3D7D^uWh:)\-Sq*V~Ǿ׉3@ 𒥅rYVjMEm}&X2Lќ/#jGl%j6=Wob.Y4>9e"!C'8`Y,j !&nRDEix`+(#VذVWU b{fD/[rN*Imۖx" jIsCUߢ=\(W5݀k1*z\JCN#<Էb5]R-~HpZ (A),P#T{]nީ+Jw2L{ޫ_U} Ys\qOg1Nم⨏Xy/[tQ9t{X$y~G$j:m̡:m. fݻrdUcSK)jڔV]eC3K$AVB_oauȦd%u,~]Y9= sNe> %NJҐrR%qvb'q8R&O9 P ;4Pon{9~$KR/:߾uXO:E/u^{ͽ=Dᩞ]k07or1̆?,&) G=xU gĻ-x{_-~SK}є@~/!=wW=6X8]K038zaV@$dh9: KJHN74LݹsXŔ˖bsf\o0Mh1uI'- ; 8$9oF0T"ߓ`k{R8^tlKr匏hW%`zCFC}JT,+\9ky͆d_a@EUb}C\hMEY޶鑞Dt3;_mgۚl9/hLhr췉|'󶷜)(W~|_]DuΙ0%ډ$^*U#$D ֲ92=I;KWy^yD"NMMy(Rg^Qͬ$^4(Hylg.U4T$WRy( z hőe> ia!'HD7r%4n'.xIXc*k9TI&Qӯ5 |Кi m>8*z>e߸ eg|gi[۾^|;J"sg7_٢.7L|_}s~Eǝ5/.;Odg\%q|cgދbD%\x݇?z:?z^Bo]}de^/Ͽ|,jo~t!LP5H6,9vc^xE2d)<%L*EzzBwd#.1[&u[5M+#"4>ժmTlʥem&[Ļr=^Sj`*-$i ZV52)ό b Wyb|@t$bicSD̷niТAM#kX 1rYBk,AV3㔪5P ڭJpzjP \/uaHTY4U1E/O/BϿ""ݰq2%*ҙsƝM1XӶeIMlz4f I8O MvvEy&!h*K)Z} EKJ 6tD&BآRRT7LIP](~e;oG;zʾetfg( Ĝ裻zz&&~|ۆON. F y)i\)tBEd@]d+#Q೎D/(N8i)Fre@F@*a2tٰbNx3:MLT5E{2Xm+^j0q]O8nƒh b- ا)3R{mD wf}|ypl8nX0R;{jT>;7Du"ظ"zlR5[L}qvr  F D"OW$!~t40Fj x bP*d|vS} 9j2~5A)T)i+ yU.?R D(nVknǎ\gsJ7&c:эjtt?1ջpX%gyd)$n9BG&^CB7]03I^O\ ݮGf(G/>N)rL *&@jfat:4mD`c8U%W.B bcCډm`B,HG\%PR K"&KB+$ &:m-AHNԲ*quQ|/&t#&?[W;ڿt{ Q2?izK.]>  pr&y( `4t]0}t)s,,B,HPeiK>gN$t$TUdC)9BnMG LџFP@KQ&ۣg>ߢgNO%OALkÒ}mjiKk{:`](Z:o7oY5{";3ckWN0Ck#vYJ$vD_r8LUm*ԷA+ŤzLS~pc>cģS;txۨ25T@ Z%1!Y>6XkB_6-Oa /^|EE˄H3>A4#WJ^$Ȓ)`K:D3" Aj0{gfy|r_P.% {Ɲ+~7[~qaN|KMS6œqб**)P6UH1cUVP0HH&.ڳwy~tSBA@ij6 MF({+d/ ՉRR8YeMª>ƼI'C,y$G*񰲑`H#2mbg]yݧut‰򳟬=r1}Ӳl {~G_Rb9᰺"Y$aZNh~V`)"sPX2LQV}BɂN0ZF`vnzd'FpQ tT$mu  EF浼@P+/lhncض,Hbv`p6TP0Lc!+zJR4azD&s!;1 pE#鶢C+XF$D;!SD&X$SEI$zgy.M황˚}dvco?*GYC(@8e]R<ؔ\$}P]C$tQegtB 6XTY+jɀ&%U wŌFDH8zaqhOcd9vV^v K х7n>/ǒ/?y>D_F 9+k RF ,Di3$-K14zӠ%٢-(B,Eo zz8l3Fs.V{ʖ:26I_!X`DIe7 5rL` :$`~Bi$QR|Q) :(t8 ;zqyB ]jCl+!fp DƾKQnaoeբrA9EFӎV'rFR-gg''4Ky-CLy<9:wnءG>X(`]!a*1ڂR~jsY{ E~o߲ź :n0<՚nLi2!-NFDɐ3OȖdβb((t R(0E  gKDfDDqL&GU++avKhDVsċIq">NVN8ȑlb+ňw璘C@)9 "VtmC[4P;#jo{'Z _K.>:5S{t K/޺b;@ a{k&@03}U*O;P| &u@ 0dD-c 0J T+TϣO&GK?t˙;qii=P_ctS6ezq"Hc+׉$MG Nw#c3-Cld}=}{FƟ;~/hƟw3 )Z̄ l ozKÃt,[R'˰:-UOX?n`~m{y/ynY .Z(qxZ6Xh<==ٕ 騦-nES`5UknJAv %+3Fi1$3С`aq fZOi*+'lux´8H`p"%eaOoqhI:4طdԘ[153H qB;NΉN̵PbwS¼txBAxz 7h˹5v9]Y R=y]EFt#~r5&m&E7R+$؞<,?BÞ;2+8$2 o)CGh TԠ'$+s" {R]|N@ebeźm#~uMđӡ.2-*!:LcrֿgeB6 ϸW~2ó?wsk|5'R-@M|iqߣtn< ,+?x߹/z߿&#]8wz|{祗k߷vଚċD *6ήG/8 (̎V(U0@K B*&.;L7]B ې*!p9kF+F1aI;Fk:tV]ˋȪQ(R&G!Qa ں:[yx(TpaB CA2L."vS LMU`jkR6+SI}ϥ9K/亾.>mjDJEIЭЗ:A03DΪ(+ B.#/+LAADdpN hCCjuƢU2\Q-@D{IRa6bCey$5O,i? PZ0-cWH_L h9\sxN[žfM/uwk:/n|p6=l%RCp%L(ޱN§xN{ t7/2~~/ފ:2+oq'_wկ}Ya,Q:/׺Ids~+/G>o=|Ʒ޻o|kN?,$YaⲚ~hY[D~,g&@  j`$Dp(va8n:nqߣC)*G"ģHre"ܵ MiSAvWIDQƉeRJY8Q)Z V! V@tIHվZ8;͠A$54ZC-^Ve稷ałmYA&0 R;FkuGxGE0[-xCDYr| dlrQC[{0,K+-OM|fN<49*ArݞWpzmҟ\Xۦz?{)7N ?uٿzz]/;OC:w7?n Y]lå}ǣW3Te^sܽLC)I%PϰD[W ^NSLG14}-;ϔE0[ 8wcko=J)AڅSNԸ%([$a_DѕI? I$3 p=dim> %xo4bIPYq"83F`.2i\ {ƳVvoe+90 @ʗK2z@ 3c6(Wa 5~^ F m!LcQO8yj,(1(`Է 62uFh}J߂_|q'6;ko\C6V|d瞴^}u{OC/=7BǾtYWkڇVn2 щcSG/6憢q׽E~8<$۪W].OWW?Vwٷv;3,xE^v.I[- D{cEtA]D'cM;NW]T0=\UW4fFLzQxH}=X125S)V`B20RE5 lH0lAiLLp Q(Y9QUAHV<&{:h#:fW+-=ݓ=V.t1,ѲǗ쒦jϠ^&5g7:{-lm |bm|-{0%R|o_c9fͲ墱U7LV-p;]zwcNE+R+qzQ_xtϮ }ُx|f=幖)K<:{B^KM?^_uk! q_g y P~N\|Ee?(B0gϤK^y;6ž##2뀚Hv Nz/ɢ(۲$iJLpH-ٺ9IVUdYT t:! R)H UI (ɼ8^fjYbk.Q(reeaJvI~-ݲk"bB,Ƕ>rImwjŋfm̰MEպd݂Ho"MSwu]ϴ]ԝ(&daؼrz]9T)Z m;ut lT`T/~xd0${^7r'ejL6?lE)%]fZCR_ -}֢ӽk2& d3>`Esh FWXy3U4 {jds&~Vϡrֺ.S3e2. 'مJGdjaOS *YU(mLmkysJXV^q\o|?% d{>?>8nF譧>yK̟r~߰˷|O鵫hK^ZM-K3szB5ˆS"' 2A SJ<\E@`εڝz!2 ]v@:|&BT(!Hr$sqC}Ev4U"*@ S!YKj }瀞eY&8~Q}.bu6*VfUk=a8K(EiMq W-Ag֪B5+A7J44%]M:(b֑ѹϗmW /zhqw]w55 eX'ZbDI-ۆycJPKXy˱;[yQ*Ac=)j]"4 1To=)nGF4l45B:P,2OgBIa@X,4~L#հBqr_}y>+n]gzʝ7ޠ GF6Qf,E>xХ\~˟q4O^ɠˋ2T8jE1J %RYAm4%sLSd%x`Lg.]|^2,Kr"K$rpAo¶pffxQ&ˊgm7tg&Jg2^g牦A#;=zlш.Ɇ!d8+mˏϳVǥ3]ot^Q8E%%t<8N0@M9Ƴl6ϳƸjKo롛oo\ɣ5/9x*)G-w:9(M2Q6Vզ J6NAʀ1St]$b*KH0,N̆3ۘ޶eݓޡeGG'di&lY giLԴ 2weX_]=<4<[^ʇfRE-f,7oDR*Uc- K`YXt&Q"(֐bNsAh td*;3[lkɢ1|?O~3 v񈊚_oIȟgJLSqE:h4]}l4U T"iBFq hU쩘}^b35( #e|dDiu]:,' ܒm֘3z+`Y w2>"ww>vF%>fx(6GQdGvjK vemi1%˭RZ)hk&Ro^8W* ~NgfҹUA2"*WNdT.b~Z9׮9_߽p c"ēk!)IuMp~%sAgN;O&(m'Ll+6zI"E"اaS6 #+RG @#]W F(RhR'=%glK`& ƍq䇩7=_PO{U-*74M0ʄ*tHVKيb o D^.G&eRQ YsWT:[Y! Bw24B$=SvEa@OQِt>;7Pm;lA9""H\~<#~tŕ;6n1ϔq >oxY&$EYdt̲+D mv+-nΊZe<|`ɔk+7AB=(h2+(%I7۰&y3 0Ge} npI;5FkY"%_T-TJE/ խb9I3p2s$廒*9*!iښtp¦3GjUˮJ.=īl]Lӎ1#i9#?J(t]o\smzʔCVy`vh&/|*2$d1w,+yR 3NUzz^欄5Ά2͘n!=oI sE6 y5rb%}gV)@2`7f헱>L1cmy\Bmv|$qXo4=׉}[",??t7><zA~EdGf>eX4AL'd0eV;F!Y–3DA JrzrFS[n|ۓY$qRٲ2E=Kb}X!f=`(7@cS3{G SꓷwxSnؚőGX&It S㹞O(Q| r IY*&kiZ .0XYٺE%U]"mϜ.ʁ@P%ȩkٞkrRhWZqbZ{ǐu3Y*-oG!SB-fFM?5hϻf0| L= $!L?O-2!sUV[K(lwǧfyJb .XW53_Ƈm>6\2rIwaEDDR&w T y'>5S!h:,(dC#P\Q%YkeNǜ&t5"Pvf*y9a dS5-W8v8P,\`лrYtܫh?2tZƒņryG8F>00L Z!yL:`48ʀqLL7o9'.+Y%L͕ݹYQz&ZbE[OJe-tY0]'~鵽Dh8AQQkB;8E{熾ZIBGnS/]<6ܶa{ KVXeQ$#Tn"׶8JU7$BS J Ŋw%UaYrEO,}.Q%|On=!*Zʡ'Ԝڅ_} ty6N,כt^#[3!tAnlBg TDA"^1>N9m-5U14%dS!F$Q$GDaI\X90NxLZD4WToǑHbߚ$ Dk!+&z%794^"Ո!pEiJQQE2v[WNd<>g]Rܖ)&h1JHz-GauD;?9mns3fej?!.s(mϳ }gǍ=ޢfMXE|_o.0JJyǝrꚎtUѺ$B; D!ET9;p=IHrGy8la2͍T:VR&.cK0=\tQO?On1?Efݏ[|b*s"<mq3s]Ɛ !dj:}\~mz}5Y/4,} ]q6Se9L3]hWY2քFK<}NuYFm\(\lp&FH2`L[l&L&H((fn6 bSA*qzrزjb+iΉJ#L*R4>(gR\W1Ev&(s%]Q'ȋRx`8ߥ՛_,a.L6@J%}%F*# Vs<{tnyUya U,rq,\8x؊arE:#<FB~u!Qᴔ2&C>2C+> k/ϡQԌB+{5S n& Pcӊv-0Opɷ OC}mW>"=-E |?ߕ4߱9iI\?]O}G}J>=eo=uP5oWܵ;{OT%4bhUKx^Onzgo|c_ɱ/xN_ە ]x?;o'-ޏ'; SWx|t`?)v|pq`Pdq 8Ů![F5HD%:sܐ>1k6z×a ٨KtSd%42)Tb]65Mn6JN1@b ]:AqNfw|TIIdn 4 %χ&BN!PN%[Pܚjǔ.Ŗ.794BdWprjű@4yK]o @uV7҉,,*6*[~1]!\`G =zK")V̘<0't[MY`Ol&a+جk1'/\YmI⠬'bFBzL3gw`.$0'3 KkN{Jt'.s7G К%}Ӫ͎~{9,-e:򎫯Xsګq_vc6%!posa#/-C3N_xW=}U{_e}&$FQ @QmnZg[nWZU(N( Ls9ߞ}I Tەr g|yO-`mO|~y/gIW`cg;X?0=9΍>T&}8ďٳg@L:/WcxphkadͰxeC*MBQa<9'I"MB@5ɩWoؒ7T#=cjҚn>8D{IJ4)i\Y!p\fꚮBm,)enxnQUS /! RxҲ-tW<{{ƯzooĠtL{? M{Eㆢ+^~^~3[hn;8>>̳;.> b81vǟիfOHҮt ^;O=)1˳qSPU9%5<@ ;o3,i{m[ʯRM ,{xO|ҹGMpmnܛs _=)"]m~y=*@4r: 1y&Rx_ M2(#Jj>UtF9atu6UeuA3"rʩ9dDU_ņʸa:mE i Ers7=_||ª!b\XTkTvleHX,>xTaѲ5=$di42r xm/Q ;=2 IV+UUDr="8F>'I]S'9D帟^QF6u҄5[[oݪrn2OhBpFT#bI+[LipcZV5V-8 5YRx:,"C\>Eyx,"pzAd8N$2j( Y%V^ %5gzoL Þj'phM{?VٛTT~ic{]Yӷoa2+SBrؙWR5_\} r#޷f7z\pG'[ҧv i)rp}ِ$>{w.6AqE‡ȏ-Sxaܙלi'o>sW_ {?[%y&7\_>Y憡󟗸o˖4#L$ ?d{צvIG.ƳϼP=uFi* 9!2"FkV<񴓫j볙욍nĂ7,BfK{aۉJP S[/@k§QY7ufrʸR,N.Zj0lT1q b?#B;XqB:1qĥ -YMNjhڨa1,ۿyTC}+hDP4c2GjoD3#ЅNF!&6vjռ{Tl4 d  "/K،F#$ş%tG1c~g@`j!V[RCY˰,4Ʀ#CK^Bf]I27(タŠљVw1 t_~Aw3=ӼGo/-v{Ow?VQ˘ۍ+<&Zbyʳ\mkǤcۼǞޛ-CD}w0.ƫko_MY+t*.'*=PT zv!)::o;檊?buKGNypn?녣.v-/_3;k4Ot<8- ڷޡk}A=BlZz>IC.rR[gy|aWu`O@ׇ^(X}D",K,"Ta#ݹp3f5ɿ.`1M ;'px+Fw:|Ё,hJt: \pN* ݝن긜L!2o f347MV$.UjDt1lS}mWgϧg<ɀã|DZ@Ns$(G8J{ũTQH$N> IT= 1Y5MB,8=G(8հyPFؒjdA]"藕gAl&OioVG"_~uDCBaöM=i2EQC_%Fo&N\sܠ~xCɰ6nLPn!7!&!F.a@vRcMSf$ըܼ'(l6^Up)wZi$7Ws‰G5{J1Ŷ~u\pQ*0cm^_dLا/Θ<ȳ/.)ό7Uոs3a]۲ڰJ2ULM0ny^Dm71LCBr=rTpR^x@s!OFqB8E 5Q lne"zFD6 RƓ>n v \oHvi|4v!=r{<^w8Ax#(Jh+"C/S)\b AF"YC|mۈvΣj'+dTUDjFvY8 'H #/f]M<"4(ٻ,n辷t'd[7c-I +"U!2ml1lYSO; '3=/FnOgKϙd2xDa\01=EJ1W3'J^C'?tQl*UYҳ/ϟW'eZǣlwg}1trDC89>RQ)"tu"6!HZ*+)dBUI 45D:3]Pt^\;e-]90 E,bNVZ @fx9+اK:%?_Y^v7?/*r.Z>&QQbRt{n!BrH#b$JGZhUoT9$DT<80@&`I߱b烻yŝbmLP),@~Ieqr 'ݲ(aIqu6 /T:#K|go']._DtG%cW$E $El( Dn逳$9 ^lGsocu!kx'O<崓O?9Ͻd͌'(BIKqQϓ$)}]ܲO,1wfUґ"ddVѴFj]V22cI#6e8kd$U6)3`Zv~2dcr$Tx)p ^8hgzzHDpŚO-v7IWlMSqxs#׷dl38tTDz `6KFTTբe[EKjv,r5( 6e8o@LhXu"IYNXj?I;Ry%磕nF?~sbbHf蚞N!O0 vX H䬅LT@p깶g6bYuAY.{G!BY2̺|Zjdz4K cOQD 6H)Tm S!zsϛ1؇}4fX2}:BL LLCa-AKh?>"끉 0 [SqSgL/qJ5C7= ^mhdXA-Г!uAl##xo_ZH3&XK] eDUI'̈́jXH#PDQ?/uz =)ʺ{s_~Q4c{#÷vf; ,oi%COP0UCձ'_6Κ9^XB/o? `<ЌgYa{rA 8^ Q+"Cuz1 l>CF Rj>2-0j XNH#eYm OF~P;:mm)K%C@h$+dICAU)",|cA¶,g"Yf9t?ts/g5Lkʄ&=eECۿJQBlJ,ֱy3Yμ\/7]2dHIm@?,f\C8IHꕈRYU1!QUe%E5r}Z7T5]ؼ_nW1؆(N_ǔu_ޫo7[ŪfϜSݴ%}5eIV"uA*?bbd].b9 \Ak{A ,mYⶎu+WVmFlmh"I[A3Pde.'Q|{DDfu,XЀ"*C^m;8[fPIXH#2;lƺkl۞|ȑLp%5ve-&5yx<*FWQPյnw.Fݰ5'| w_1kE/[֯:CZ'gE-J2V#D|4fz{ln%Қu1q TX.)AOhQ]--]Դ"ڶZwoaCcڼ ci|3$5Ck4R@l!zf '. K 輰*q]uˑ+2nِE(!wfq(Xr#H :cЌHehsL\†-6Z`ULD+ֵv5VzSV X: V E3ò;UfsprLwd b}]!a%`'P] KA9'=wOuOKa_]bm 4 9 P`;zcݝrpA# nMug~#Yy汰™orrb7?\5Ȕ,ĈW*fCyn[WTau,\tAmM$I.>s .g{rz4-7I[0|`vhgzJM]졚6}99`8\S S./c&/pH!ә-pBt }??(ZHJx!HM5Evm9H-`Q84 4CqyDh SÈ"Pgۤ:LL=W-] Ǣ#F\Ə*Q>qA(p{!N%'"]> vrTlȻǥ0dpsueJ"#*KW5# &t綍6?5|ie睶z}]o^WR5tL 8)x #sФQܴ^[,ݧWn93gۖҿxO_ #`g\ρ1/͆j00S,"Ͼ}i[2v#ξNh]yQMpb*L /n/}߰\N1-w&kLQ~hOD: Y"df3SHĞN>+&T_Ȅn:>Gu/+ E|U9s)EPy!\4"zۧ *- !>LX2 R_$˚L$`m̘&ʕ%dC-_Iͥ]Pcy2e)@,62qmMc-8k*w(/}m]e%~:iܝ?M&Y{]k%B;* }Gt]7]25:xR}mͰںl.q%ɓnjih_x^p{]d&T603tlDԍ[xz-`ՆϞPJ+/9qxmeD`sL ]ṔƷ="M)B1EleܝI T?mf[waˀcImɾ}@Åܳvtp<_USSBoǰj"pcq V9bSͤe0'*h TW53p4D=.+q% {|iklx E%A^d`Y|3yIr->a/˄"KmJE/˕ Q?bdQ纃d]`{ז +WakȢj"ʪH6_"n(@ţ2Oe1=3|w8Q"?\MeRZM:KoyfEwɔMTVyˆa2o҉,o;r-w=~fNw4}ShӲ}=O?JӁӨVE2:x)?`ֳ"H E9de߼gaL]JzĔ[I0&RSdbVyIUO_tFôhHH7;ءN)\ȁ+'0pW* uGʿiBBEtˑk^@9c:#Kذbqr̔Bip~$O,3(zNiCٽoQZ@imw1;<+Y@uu*J$beBr K8QP=N1a pF /GĔD1!ROFgKz$^0,U4`2%I|89% $r" "Qy6D$B]=Wq-2-Ckˆo;=.VB|s=O&+JڶTw\,K-FK3,sǑzXNGf/윭6N0>r)ˁ܄g"X bZjpnuyXT' 9t~oag}A4sMar}]B4Yi/{V%kꓩ@ϘfiXr%3pM{^)M9*ؠ\+N?],.YTb+ɣ;18%ڊRejͦvv9x٬&T7ldaC%a8_83̱f|3"|H,Bn1۲S|oؗn>~791wO^yW:;V}k*4Sˏ;n.vakahQ^y>sh+p" IR4:lnEY\UvPGB I%"J%ªNH?4KJH9d5τZ< 22<x=Cy 5)@A[\e}4|b @t]dxa$#cV(4~.໋?Z?O.9+w̹]k(r. =kwp?7G_rɉp\.@e`kC+!J";hEfa ;ݾoS{H1:6E׷QOF 0B 2J$;PA.@{ eR` ߣ2QPH <4xEq.sbעiz^D1b})^ 9da\tLax?ۑh El*,]'pkS>Qa95,sC9{u u$ʲPR5+qJذjնm\M˦E3.ݶհږFґ}m #G՛1&k[cS:sw^z}!ωN~- ҴSh;kIU2Tr?`(?}ƒw^ѭ>_UV]cF5}HbME&QD6]TLp$]nUm}),:. xfٟf}1'8ic@JND4Ŋ촓.:T:taÛVYwu˝"\wӝ ?4׻dOJmCf}H4> l7Ou"^|6Ge]}-(z4kF ܚ=ac(t-kBnfh5(kn8@-vK|rc@U!Dy x#A8CD-h @핗b*D#xS{, y!{B,K]+ ЅQ/c'bx}NAibmtF=zI1=бZ.r#6y%ez)3h} i4/J;E~*dY gx\? k$AKsI!ӅRfLL JԴ` |di_,2gEkUKW&kitY_~} 9jxB{3c[e[[${M%ʵ\Ԡ#'O sЕ-TVuvnHHjGo2w7寜w-zcIS#=~ڍ xIJTw)>ZQeB[/i˺v?,0^ٗ}"0w_愸0Ԉc ^pѷFO?&u?o~>1t/.K#}y!=ϝgsXW-SG6F0ʊ/^% mzׯb' rakX3̶֨S8vn %x2ulH83<>,`=ZHѨ8J#ns ="ʋux#sXO4 CЛ y;CH'0rQ{L<,y oDϕc& Rxz&.K_lTxqp=բþcFɊJ"VՠRT q4x^5# |Ge5!QbJPgJ΢"vU7u ?1xyB O ^1&B5'*]n fH4(J,O?7,^0K. m;̳_tb=t5qRܶy=iδ#蠡"* ѦݙDf&,V'**e7-+YtH:_g e mNb}0ʷ/pT&a(svgf".xb@ DzmdqsxahӬ3p@"|8ISmyߦ^Or%<,e;KPw৻ :)㮫O~+B$y/%}CXcJ ïZ63ZX(DO0 Qy?F'o:c96H>&|ḰM.;^s}&%= M^C}mےo8Uo28@lܿ=BA5VmX!Y+b|uHTME9Yt, A `pĿx-rkR_G(GK6$D qqX!0`B4I.'ʮm#8i5f1'QI䘭HnN)^+ 2UUӚV{e sM3=ղdD'q_:;s|{IQF޴b5%ߥb|(U̕d2;N\5_-Z6t?XfVa}эU5M+?+{s{ c,Yq3))OV79d^\`A457SnMCV+QX]+3A--W5˔$&m3fPE]koﭬl}q֯cuҋёcibYN"a&\k~w6[mEg}?=kΨѠeǝq}~(3uc]"@-eaͣ߉wc: Cym}|_S.) P3 bƋ Ci``lr}v\{2K= I8D ƉBA#eI v.Niy'i 'ȶnsQjoE~ Xg=toQҪ}" VE,0Py:$:/7l(B_*ۂ.wm%jX?R95˴twue""lR⤨p!E3|(-;:x\n}8ϩS?u]kxIMgo<FV_MuHs_F\A\s!ۓY1iwެ3WU~g[@h~eg9z}#\jb 3Kw5$?0 m ˳J 9B$LW&2U5I $T q٪Dc vM8Q8t3c#r,C(@ty|/%˔BQ*(d- /:h@,3>8(/KZN86 '0yVhmEA>GbNjoDmï 6 2G f{2"W~p wec0%GaLRX3tOv$'_vn:eptБʗ lWz񢅐МmoED{CҶӋj|nxki*S=(q{my[!L4RF᧎љO>zO_t896f K'?I >3!w(AAӏ  qC}`:PwYX(eR}28l (z<aRDr8绶EI ^SUǃ%"<6$+ .QJEU" ӄ0]bmePCfyqc6| 4x܋$@*E `}|:J`?AU,OdArr%[58Lb䔆f5tj;'X%Ӵ{Bc0k8#Clnm^ )jeUء9I+T Fd)KؖQ}t!כخ%8MKm.dBC3;N,š0avǣK+5_^ i祄 ECyO؇e v)U:`p1P <[,AV@F(Ӵ-k3Pg~Bd݇!GuKW0E@2O89ȼJE80<T\L \*%]†Ċ҈$ǚB0'⒘ Mo1]@R?%|ѐJvP.mYA`l=dD1THuY 4d8/S8Nӭlh%Ha)17 ;7[v[fcesͣm% l))kn8;F5.U+ݯ{JN-[k U _ƺ c uop"eY4LIE8˲$IR }KrWupΊ.l׺E,!~y!pk7"&G˄*~ E(,eQ"L 徦"SeDky 6&IlR`@'4%IEXCa(;!/F,@v >ߵYްSe@rPD-:IN"]3<`%b|tH󇕃@RVȃ Y)عKkxBXܹ .\"F.X:ێ(+<])3 ay59>i=ekkmL/\VŽ&m󨽇vngGr 2zhR=GX9)- Χ a@};@4l8CHƚz>zψQ‰p77_9o 1 _Ј#l ݞLXMia,Cij 0!p Q0E/&36g(O<4 7BaǸs`ȳ+X< tCa ͘p Y1rJP86@6S*©14HOǺd8ALDccٶKE N1GÝ'>z_v55>?92,*q۠~_c[R}} 뺑+iÈDմazcγs N33 jiXiۚ{SXT[SYQYWEJ~Z`%åa.GPLiH/BӔ11Qx%R]/6cFޖ`KfoB̾7nEKtzxuV2m8A# (zvrbUtrK3~aZ8BO0Vm9 v M=pie2֮Yڕv_˧ ~hϸ]5s@((6F/ BA3MKgyF 2*/m{PTY mH ('cDcR(~2kgH 5]谅JXJ'!C 2h66)L(Bu&g D4#M@ 7^ E#glLGGEx "T(cH΀)#Q7>Z>Snp/[{B3@%6Obm[ 'ySjU;LP3jSѰ>JM(U{侹Ͽɵ %W(yӱ纾8ξTJr('Tz6$d3ACe* :Z nnT[©p !j] ex{t!S8`ѣ'z:-n`q1x^3)#<~A]W.y8w7̻82; _j&0t*m῱@XCw>ط#FNm8iWl|k=*(‘2~ b zs{;NYAv_`{ {% $&LP-) ,' dDyWcesp|*3ă@PTd6t>\4*"r@L~!Km?<Yݴ3o}7{)[vyys#7stO8Y bxvҫ,S|+rV"r&OnoBw[ti ^|s um{y('yLa8s,_ =BdK4TSe Tf ElSe^Qx޵4h 'Y)QRAp˴HDYX\:\ $:~z“=tIp @E8Ǒ<)(m'%`UV +oG #g#3H#K$AR"]nQjk++G3</I)ww4y+wTWez[bJv#G+^*څmZ1|Gc3=f'hqvn9S,0Q&!(pX,'ST݄O.;gA̰--+Tg:#Sqw/9o;abt#y7SNgy^zo}7g3>9/m9>}邟]|1ډW'~uVM=kO1exL8|OukѤ,/i/Ph̃nBH9Vh|fÆ!=jGL>!;Bp6 P2Pn>q+zcsDqXvml |Hz*2X(LvΒ&bΈ*>NB@8(& {HBta,Sh%0tt |YԯƑQg,IbDQlѸLӥmݑi7mY;q̸u7Q>*>Z\`*scatkfQ(9.2<.Փ ]3E4S65 㩪 q܈΍O{79uʮKK 71ΦxDN5n#{,g]}9ǚE^g#7۰F41_٧}!/^~sS{|z#}tI}{i} vr-TٻJ=ʂ I\l#|'PߐEjJbB:9AwRFv aAP!~D:$/sB9wѪ]B;ԄGOsJAbsoB;+ZN@D3BD`Viϳ4@$|=I1  iʖd貖0j]<|oޙ8ӏQWźNzK^o/zϧy4n`: V1Ey|"_ TG+n鴲I!F Mu5}_x'5Pz BHM섦dK놊DLjQ"ݙ|PZ`ˮrC߳^Ť:3Z5m$ETu0l/E:I]nkF2 ]>~ߗz3ղ M=/1qpmKw%9Gj4ŹwG5{=;{ڮ'N4)n_<_DoOϱLs׏hѴ[o@JU<:Դ`ǰ=)7ee]!CSm U1}:yy: ^>z@bI v@@Cz]__]_؜(cx]r-d k;D41FL&J%9, etBYa4ӆcKS&r"TpD23}1SҜ\=w1@޽`V>'K8?%ҧai`;>XDo¹I׍cЬnx%p5Z1B'e &?gȬŮk s[{hKnNu7;̄Ad EdT@PFEDDE ?2#Hb椓Nwss5#Z^Su=o;\k/iأ{ϗ#_w\ /p{/c{79_?|ńշlKeN v_doyf{֎څ}bT\v*, e2%gX*qLh)$#pFxa)$;5uWF'e89O/eF<#}ЃSd?HY>&{2e89dP>{<U*aq' KRdօJb cVPjT,Cذa㧗=sCW{aHyjޝ7qt~uk|:蘊{ıchAMӵz77u atWAZ];ٴ%cԪ:@r+÷o3ܭv̀`'i6IfsvZ>BADŽ\Etu_ZOw|sprn]tsdg v%a<թڥEf` FQFuyo#,'tU M6X:*S3<_hp0}(`&5 -u87GջJ-LNo΀&9xSehˌ'\ #W+W m'2)8i!]qXˊZ:uP\2391vnGTCN35U*yirm7~A$茩\Ml|CBEaf毝eHK@H6Rzʷ:aO"e)`)"!߆$eߏYC_JD4pk 4۰-c8IFobn҆LZy 쌢I?,LZX&-OF-nr-IԾo^*2L-[a wgBk&Šs ķSnp{9 pB =gľ:bQX[L}u 1d0.N)#1ò+>̩>ʬ>YeU࿁%bx2UUtx 0N8 hiyr}b&P5̉kgޱmUoq:X؈>|XyS'߽sӦY0|;EpJcѻ?vR)'d`FXmfLM7#Ff{b ^1'juU14 L^s=zxn%lHB~ro&_c]g ?@RV{|lVHnG*$%dBid<Ï<:NxpE;8:vbDa:$,&H) ]\I;ɛj*4*+ބ=iDeuk>S4DֻLE;@I#"5_~WM.E&EK*SnB$n$jGc|x~%F@Fc6/b4(9% XQY:|xv2VאhXmfEIdBoMH+"U$ ;~0v䇀Na(.-3${r#qkhPH @_/YE KJԢ̫bOXg>H ο=&Cz;H KA`76v'1LFHjuz,)dSƘx`;~TAX4cfŻr:̐+- --O‰ThyϷVϕii>Uu&Qr,*2}zyo¸kEAq)88jNJ 8Auy[HH OV'Ew%"ekBbd =FEfu-\4Df[%'4$/1ɘF2+dd+:ay=?}8z"kx1 M2x>:cK*#A@S'vۄ/6F~ iؚPVExHM̺yPPUzʞ WfseIM\c0&A{PEyak`2EǠ eC-\Ȇᒬ_wgrb?]tR!FwbnuȬD*][kQ*YM9@ۭe_ lT/ܽGU|72i-mɺ(RCcȏb&,(6/!57VH`i)+r0hAr3XCd61V~gJNec֥h,I E _Xq%\I@!8 ma'#Ằ{,PxMǎBfPgQ4!zyFohφ -M-UkIްqw>sۓ|^yr@.4!Sce n؃7Ϟ]sENJfÌ+%U lCHBvtr ZÙ^l6W֚՟;.˟~}׮5Ouj'}qCk]uOwGUdƤ8V` 0uQWeA8,V_! yvRp'?"}q'+*??xp!tY>kkŎmÿ,և_9{/?ص2=|CTBb& UFlafNF\2^JQJ&~9͜9gBCbK/y7~˙|YicѶr{'nx<簦 o>U n}͎SL&|=aK~AR{~ܬ  iʊAcebgH ±r`S&M0VkZo4s7Ym3?wRU/}hQ8~>zdډܵ'ʻ\>mMt 5S&Zv]B&\I$FnHOi"#9ndܡ,"LoXgNP ^&R{鋞uW8!J|_,a6?}`M߿oռ |%a8$Eb2ۙ1̠k)vX ~ydB_@$BhZh/Z^ ,jJyA0FshN9g{ڽ?h5 qKR5HI 4~8LK>);o?C\O,> >Ç^Un~BՒl/2t%lY|4VN/,\繶c;YRȔ@]'>F͗,es?o}V@dvW#GCo~{?ѿ~ϟ|N=m^/|w?7|&7_ҹlRx~Lr,It!(rxR('b:'"шǑ &MP:|?I`TdFZ78EfSH}bryӟ]|9^A܏~7gK={3%u::M]+ &<n"_VBb 6ӑL) AB@_=<?'O,/>|GW^]Ÿ3¿[~g3_iJ{?}睿_l;?*r?~e'a# %w0Ot(2bR9S$`I4ӘA4f۫tq:u\֋fppư՝| >щd`rլTLzQp)a}cN|S}_~%W׿/ݽ>$TuPBjZXL,z\)7[S+5A`Zf\r:*MfAaLIRf~zQa;n&!'~~kO௾B-T޶u 6nhwWK<|mKsD7ɗTqISQD4IbQS,m3C$2^n@Xv)+ DYxV&KBMK H7W_VS?FQ׷uGCӁG_Gpԙro~y B !'Rx_e'F9QG9AܓpcJS]@%ȟ3Gwr% ;w~mwK|Ejʝwz*Xժ]U^c5Z|ݯ9T}ui"x\E0J4+kꦍ3UKo6hUvS7n?sLrTQ"H6nEedc |g_'kwlg=<1QEW7 >z-7yjiƭOzLogbk|c8!Nlf mRDl*J^!%"]jLlhu8¬C8WXE$A, %(~9oo>o|<6,_om;6\ ku_hq4;%][n_M~Xj*xpx9 +˼_K T?=rr#HɌ|2`hpizF5SX?5A!OQiM]i2a< V`PqiXP" 2]d9Le"g4@rB2vWn[Nmw;]#N>6 aTh޳6]+lIYOL3t]JcOJ?^3 BzmauM]Ubw+ [kMFT d!k&Tx<>|pӓDtjZJKcdJ4=A$$RT%C.%m @|fDL0QYmv6 "o$#. 86OY]'?v\,w=g 2OɱTI<112a/E^\i<{ SrY]er#E_e!=9vLVۈ-M 1IQQ#μ mސ <+Κ҈wl-1b/%MxN^ꗶa_0UsFJ#2 A`Q5n3H]JR2L!GOI#I|PmIJ0l.Xq>8%#ՀAdha)(unS+}'qeiUe9Y|\.&3ߘ4Ĺ/l_vh+C`hM6TSK`3kn@tNeJ=s)#PSű r06C ix0礎SAOZn4cQ-˜]&9lrfy}FIDnFHH|$3X%b|G}T G 8Qج#Y7)%:k;!2HIYJ8b(d&T )8%ƢQ@+رL? ʉ qkK(`M@ @4HP#&-?M$Y&fIza1x҆nEO:L"b2`J2ft:0-'I gwq)UUm|4'?ʳu4l" 0e$4鏥t(=Y/ tXZP)DF#nQ7 0.|a^ vt|MqB |t_QY ؞%p\} ɐTMg+领@ q11ux>P;TƼM  i"-\ZǟaTYI#4wvQdekΤMg5(ɸ =P P&U^ehn!5Iי|+eeI*@I$B'7VJ'JO)6lA;5) h*2 4I&UrL̑ױoˁ[Ti_xM;yظORB'9CwCҎq̼N$JϤ4LJaI#+ajzLʐ0d+,:b=-Ο@Y6n2DPL1(n%eŗNEWsw29ݳ2m\EpǤq;"?PMMɲdR,+ƙIrFPE-b EN3fOV'-&D bg:ECd4Oۄ]wBK8iid_B :'M8&!R<1(I#>+ms8Ss<+q $] 88)II"߂#T#qۃGO-}Sv~ъ1~&~ԤEmVt`n n^1*;*_$Y=*{]JuHRV3t;,EY!KǎVf(˱*3=ZH w1ُ|"f)jB/":bh3eD'TIQh,$lN͛9"984|fFruU4i$K*p{@u'LpR M`9RU؟T0n02tH~iX讇mMU:kN-Ƕl61>!:%$>,_]]Uk33`G9'F(XWd豂P:q j:}Ӳ,T]+Y'ʕ E$Y>^ 'i$<4i {$%\:|WT*A|fj s FYtg|HQV(g`RTM=0'<p@qR2WtO-"Z*\DW ﺪ:#cΑMXD-/CXm;gL7J#&tM9YEi!Ryڒl.%y3}.$urҗ+qr&|3Aٲ>FyU)Rʨai%c7D/4lhIna:{Lt1ltIqB'q-a"DJ<>ڟ{I TI/ʊݙxwq`tlAr#-G}"%}슞(7`]r#%yhI#"pk=~= I%-i*XێWͰp$*b˗DXcۇT'%2}c$O.MU?XʆIq=묭|rvfݶI`*X7b%UH;`P5M8]xy{zz_8n{N\J4V+__z/{3? l  aFZ%dM?e<]}wnPkyU dFR>0%=an u4MSʁnCGeퟂv.v\o|{H#+^'\`9˵J;JknhN©& υd,6u ק.&B݉;qwaS&1@YcUYSi,`,ϧ4Ows A(5+ fOlH)gDe$ӏٱՎ5z#>o>xWMS^> ϯCOO_m GR q *Bd p˅\A]}ƌkLE,LFKȏ;iO Xy+{,};/}G-m^r%TNS]?kOd}?; v_ve]|9fǖ&691n{|zL5B;Fp9s:ɵ#MZ ҳ^L,=vp޽'Ӕ[_Q_:^>=5U.YVkj|TݲiC>QWuޜAkUkFJ9) 6*n16{ s_/5661!$r=8vS{/K.$c+ѧ$R"}{|f;0",\i  Z ƶa <V'"2LH:GT2}\ nQ/hԫRpWY,\2}9&hʀ  +]#KH\L"9xv_`<ɆY&` LZg@B9啼񞍑la4[z/{ѡp_r!G_{y=~% V-^UٻZ[Mc۲?㪗Ig~}mL[W˪L^װO}+_yKzUqFRi  !IfUy{KAN u)(/[ڏ;n7|s.}+_zE>137b}Ln1L5XzVة<D\sxa 9C"ځ8H.$OJ;cc`\% s9ر3w&ޡ|{m#f4jRԙzQ{gv<vj[o{/{i6Wkcc!u aZvSV.:^7q=K xA in`tu:㶓'o߶uMkF/T60I0C[.>y7ZƉkBn]oz_g$ymoyv;_?>|ۿ~co|wN[5w4Wn+݇-d OwBEWDta"?ec1V}hu '- Db@z,;~صW9_Lf߳Mw3/ 3:uձ)Nl0RBTD,Z8=X^62_*{1bAe։ޠs1hYkk-['&r>cZ䩅Hp"Ro5W=>15l}уnw|O%CGY8=uf hٮKdOM&]Y]7m^Wkڷn'sy}U!Xf63`o S{ٰP? ;_*[}cKsuRxݒ- j*ZRmԄ)qky w)ۑAk6/pjX>:wIIye0.%N٣X7VrC0E' s$0<&xrM@< +hx;y Y#\zԤh%{IX1w9p58_߬y[[;k]&tyO?|iipWQjl=iS}obȒRTw>^:L66H +aƏ{y+vjVvI)YbSNo֭[n{9;vH:zkz\577ZZUT\um7my{~꧞q##'àQ#AX n}>v:v_ȒOtZ9yǏDpd -IVrd N IdNfp 񱥥LCq2oȓYVy{DJ !' !kXKJshRB%°Md QRt#L.?jZ2KVR.U&/S"`l0짫|OKe t垷%Q!z|H1G2ioIP=W>s4ܝcJv0B|cKG=hs]{o"[QǮUSܺwc-?y4YGn[ RaEeti@9\_:v~hGici猇lhRЩR(QRH]"NG}8i{bA3Z"1{DJ8i$7܂g=|%wWg {qv ,eu=IyO eݽyK9{ϱIE=l=bifIЋD8G"FHrwJKUͅVt͛u9FK5) M;sdTL]#ɉ SfgQ2Fj>].Xe8睄atvmV5>>kr߮z+kLXs $,cU)TZ ۱y6oL+4cc}Ϲ{u}LŔDNPҰe0)q2L j'Bq{l-wHleÑaq=pIbT"3zaQF 󪦬,eSaBG$hI [ķ2hjeTV[QF)Eue.Idt %K<ߵ9(A#&>! XќJo&mv)D ʛzbyb$QW#ځ}/N%]Hn 9?eyz[<ח0+7~'Y[tp"@dAdb%IՒylbRNkoHJe$KcE Q23 ЭMKss%jQ nJ6w'uKi~_TSM3r{9βqȐ࿭v[4MvmZ]Ym6>r%>]eS>wG>61s_72Fc~C:1&A B«*!9Dx ZRT34VSM`}VMSww^86nFzG;o/ѰCD?P9OA%kުV]MF^ sYfsq ˡTAr M1y.Lf#k:b${nTGFqak^$H2jzD=cͿLQB5lYDjIeҥbh.3oBa+ OrGߞ?k:I/|Aa/DS5:n! yr jɅUba@HI#Ws1 V$xLyPc(]jOI& h4Gr"6:"*^S8'V z;ntOA4)Ru&HLƥK8>&& EqgǏ9jJp[c\y$R9>=Z3 6jsu&U:d zf  CO*ӳv&wr 'qzBK):J ư7kXj-s1D6|+t0prTrvgzlB7K_Vr'+uF =66y @M=}}O1,?rX+ih!:'/NZٲm;H *^֭[8f̐9 r\h \Pj`Ær)2L+1][S0$Dc$/ 1Ma:d6䉆I"꼑"Nc g$(.d" W~ĉŸ!HJC z&̤ $4[`>Y_f 3Ey $ HѤ:p!8X= `9irX?UbI̤ԅٝn+S0Ƙ9֚ 9I;c?{S}&ZpϹӵZ޸qé:vLO ;15}[]{7m;@+>r`o x۶:2&3pr\>)YeuDFr*ulWͱ=4;;dz$ޢSSSccvzayӦ7/}]wC,\ uOt)Aʉe $":W~8J!dJPa4dsB ] 485Cl!6PU84M |2}(mnt5&2Qꕘ"K~TGFm[&iR=H|E0"qo3YHKD"Ni VYDaדAH 92-(:]w8ipf,,:IGEѤ !OdFQA&Z"^QE"C KO8299i{⦙Xz>Ѩ$Y%K͵^k4Ə:edRTpM`iiLc++\pjRbjw%*kO~m&h Vn..-=)PZkbbѨ }Gwd1ŗH i-<&$F 3IidƸ/6^÷@ҡM]*?QE*?TsD 619oٶsvX㰜 _,YؙT) sd!01*|GbQDUY `&L̖:=\cNE|hZ !_LV4.M ^`-^,ilqCֆ Q#i.8pYLe();%`]7TEfD AǦaS*(~5fHQ[NRR!SoMXC4>.ϳЍ_1FcHVJK$s"YC7S|Y[ ײs13-7˵NqA *#I ';E{`yFY>(x d-[RCS +]wqI8(]]] X40vZU*3$uŕ~gd)@owMMNT+*kZ-DF. ~@xY5m`9_Y^ٰaіl?mm!$*Kjk5+Fח0x>]Y^b O)̊@ǏOOM Vi%h2zNmPi}Oyr, d/Q'F!寔D6=H5xd'͈ 瘓5, V*G֖=V/iCzv&eNA^LTϼPJ"t&L:Z@O4duJKsR#FIUY&7qu۠lI{R;ic iC|b ۉaQ Pq,f^K+H`:skGcV7Oh!,氇@cC @J;>V[.` +ԧ-N1:6w-T*&e#o1k }S'E傒l5͙x+a8hj5e]:]{cT\rRr5QKϟKʩ sf m. ԲU h1It:9Hx!0W|$uXIQCf$t||Nh,9 V)j4Id¹!x H@.4ēKVWSI,_jN .갚F$y}qfkQ3O$ Ef;]e;*q3.-,Zaؠ!H09Vήh$Cx,RM B,I(ix3z}^ +6ir$Xךz#F8b4uuu @r[1@5@8kCI, _Vm&06,"3f, njdmfDHV[vce?oUk052r_Z,4(7 w.%X-50F +w 37w`eհlȞ_n9̉aY>k%LM---7'A&x8B痍9pɱ#<hR{+7ibtҘ B1fҎy-+dh,)Sl\0V(D4V~ҨLfVJ'JZo`K{a;JnۣP0,8sp=:O&*\ye]5V"*ǡ,%RZl^J}t`Z ^U!  q]" hr~0=4;4 l@0|r"DY^a*_ dEbEv?\ ƃ)?XD!x֡"dG@4#RF>:x52cS{<0+54#:s p1|[b)TAGTY Tv1`2*[&3 2(cT6T3!X)1?pO6{r q莉O7lQ!ԭNAD?Fӭ#?nчŮ(،Ș7y*C2xJmꈧp|v>$my2%EAǠ Bscun4D2aA2QZ<u4ў̃6ؒkg2[=vN4K>[rԬj0t?ס(b5O2(&[ CFo380MW=4/I+""eXUKӾ:PGU~=lOp~ Zyut#¶$j3`6N^'Gv XBG۴5n$,&xPĆ07VYJTB?pJj 0nRI-+~Л6Gj,*7!o͌WY&6`4^W^ -R*(mL08.t'v(_ /s52D*""""?%."""""]DDDDDwCֱIENDB`tomahawk-player/src/libtomahawk/mac/000775 001750 001750 00000000000 12661705042 020623 5ustar00stefanstefan000000 000000 tomahawk-player/src/libtomahawk/widgets/BackgroundWidget.h000664 001750 001750 00000002770 12661705042 025133 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2014, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef BACKGROUNDWIDGET_H #define BACKGROUNDWIDGET_H #include #include "DllMacro.h" class QPaintEvent; class DLLEXPORT BackgroundWidget : public QWidget { Q_OBJECT public: explicit BackgroundWidget( QWidget* parent = 0 ); virtual ~BackgroundWidget(); public slots: virtual void setBackground( const QPixmap& p, bool blurred = true, bool blackWhite = false ); virtual void setBackgroundColor( const QColor& c ); protected: virtual void resizeEvent( QResizeEvent* event ); virtual void paintEvent( QPaintEvent* event ); private: QColor m_backgroundColor; QPixmap m_background; QPixmap m_backgroundSlice; bool m_blurred; }; #endif // BACKGROUNDWIDGET_H tomahawk-player/src/tomahawk/sourcetree/000775 001750 001750 00000000000 12661705042 021554 5ustar00stefanstefan000000 000000 tomahawk-player/src/libtomahawk/database/DatabaseCommand_LoadTrackAttributes.cpp000664 001750 001750 00000003172 12661705042 031334 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2013, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "DatabaseCommand_LoadTrackAttributes.h" #include #include "collection/Collection.h" #include "database/Database.h" #include "network/Servent.h" #include "utils/Logger.h" #include "DatabaseImpl.h" #include "PlaylistEntry.h" #include "Result.h" #include "TrackData.h" using namespace Tomahawk; void DatabaseCommand_LoadTrackAttributes::exec( DatabaseImpl* dbi ) { Q_ASSERT( m_track ); if ( m_track->trackId() == 0 ) return; TomahawkSqlQuery query = dbi->newquery(); query.prepare( "SELECT k, v FROM track_attributes WHERE id = ?" ); query.bindValue( 0, m_track->trackId() ); query.exec(); QVariantMap attr; while ( query.next() ) { attr[ query.value( 0 ).toString() ] = query.value( 1 ).toString(); } m_track->setAttributes( attr ); emit done(); } tomahawk-player/data/images/arrow-down-double.svg000664 001750 001750 00000005674 12661705042 023254 0ustar00stefanstefan000000 000000 Slice 1 Created with Sketch (http://www.bohemiancoding.com/sketch) tomahawk-player/thirdparty/qt-certificate-addon/tests/auto/auto.pro000664 001750 001750 00000000163 12661705042 026757 0ustar00stefanstefan000000 000000 TEMPLATE = subdirs SUBDIRS += keybuilder \ certificaterequest \ certificaterequestbuilder tomahawk-player/src/tomahawk/ShortcutHandler.cpp000664 001750 001750 00000001753 12661705042 023217 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "ShortcutHandler.h" using namespace Tomahawk; ShortcutHandler::ShortcutHandler( QObject *parent ) : QObject( parent ) { } ShortcutHandler::~ShortcutHandler() { } tomahawk-player/src/libtomahawk/database/DatabaseCommand_AllAlbums.cpp000664 001750 001750 00000013105 12661705042 027272 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "DatabaseCommand_AllAlbums.h" #include "utils/TomahawkUtils.h" #include "utils/Logger.h" #include "Artist.h" #include "DatabaseImpl.h" #include "PlaylistEntry.h" #include "Source.h" #include #include namespace Tomahawk { DatabaseCommand_AllAlbums::DatabaseCommand_AllAlbums( const Tomahawk::collection_ptr& collection, const Tomahawk::artist_ptr& artist, QObject* parent ) : DatabaseCommand( parent ) , m_collection( collection ) , m_artist( artist ) , m_amount( 0 ) , m_sortOrder( DatabaseCommand_AllAlbums::None ) , m_sortDescending( false ) { } DatabaseCommand_AllAlbums::~DatabaseCommand_AllAlbums() { } void DatabaseCommand_AllAlbums::setArtist( const Tomahawk::artist_ptr& artist ) { m_artist = artist; } void DatabaseCommand_AllAlbums::execForArtist( DatabaseImpl* dbi ) { TomahawkSqlQuery query = dbi->newquery(); QList al; QString orderToken, sourceToken, filterToken, timeToken, tables; switch ( m_sortOrder ) { case 0: break; case ModificationTime: orderToken = "file.mtime"; timeToken = QString( "AND file.mtime <= %1" ).arg( QDateTime::currentDateTimeUtc().toTime_t() ); } if ( !m_collection.isNull() ) sourceToken = QString( "AND file.source %1" ).arg( m_collection->source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( m_collection->source()->id() ) ); if ( !m_filter.isEmpty() ) { QString filtersql; QStringList sl = m_filter.split( " ", QString::SkipEmptyParts ); foreach( QString s, sl ) { filtersql += QString( " AND ( artist.name LIKE '%%1%' OR album.name LIKE '%%1%' OR track.name LIKE '%%1%' )" ).arg( TomahawkSqlQuery::escape( s ) ); } filterToken = QString( "AND artist.id = file_join.artist AND file_join.track = track.id %1" ).arg( filtersql ); tables = "file, file_join, artist, track"; } else tables = "file, file_join"; QString sql = QString( "SELECT DISTINCT album.id, album.name " "FROM %1 " "LEFT OUTER JOIN album ON file_join.album = album.id " "WHERE file.id = file_join.file " "AND file_join.artist = %2 " "%3 %4 %5 %6 %7 %8" ).arg( tables ) .arg( m_artist->id() ) .arg( sourceToken ) .arg( timeToken ) .arg( filterToken ) .arg( m_sortOrder > 0 ? QString( "ORDER BY %1" ).arg( orderToken ) : QString() ) .arg( m_sortDescending ? "DESC" : QString() ) .arg( m_amount > 0 ? QString( "LIMIT 0, %1" ).arg( m_amount ) : QString() ); query.prepare( sql ); query.exec(); while( query.next() ) { unsigned int albumId = query.value( 0 ).toUInt(); QString albumName = query.value( 1 ).toString(); if ( query.value( 0 ).isNull() ) { albumName = tr( "Unknown" ); } Tomahawk::album_ptr album = Tomahawk::Album::get( albumId, albumName, m_artist ); al << album; } emit albums( al, data() ); emit albums( al ); emit done(); } void DatabaseCommand_AllAlbums::execForCollection( DatabaseImpl* dbi ) { TomahawkSqlQuery query = dbi->newquery(); QList al; QString orderToken, sourceToken; switch ( m_sortOrder ) { case 0: break; case ModificationTime: orderToken = "file.mtime"; } if ( !m_collection.isNull() ) sourceToken = QString( "AND file.source %1 " ).arg( m_collection->source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( m_collection->source()->id() ) ); QString sql = QString( "SELECT DISTINCT album.id, album.name, album.artist, artist.name " "FROM file_join, file, album " "LEFT OUTER JOIN artist ON album.artist = artist.id " "WHERE file.id = file_join.file " "AND file_join.album = album.id " "%1 " "%2 %3 %4" ).arg( sourceToken ) .arg( m_sortOrder > 0 ? QString( "ORDER BY %1" ).arg( orderToken ) : QString() ) .arg( m_sortDescending ? "DESC" : QString() ) .arg( m_amount > 0 ? QString( "LIMIT 0, %1" ).arg( m_amount ) : QString() ); query.prepare( sql ); query.exec(); while( query.next() ) { Tomahawk::artist_ptr artist = Tomahawk::Artist::get( query.value( 2 ).toUInt(), query.value( 3 ).toString() ); Tomahawk::album_ptr album = Tomahawk::Album::get( query.value( 0 ).toUInt(), query.value( 1 ).toString(), artist ); al << album; } emit albums( al, data() ); emit albums( al ); emit done(); } void DatabaseCommand_AllAlbums::exec( DatabaseImpl* dbi ) { if ( !m_artist.isNull() ) { execForArtist( dbi ); } else { execForCollection( dbi ); } } } tomahawk-player/data/images/tweet.svg000664 001750 001750 00000006621 12661705042 021026 0ustar00stefanstefan000000 000000 Slice 1 Created with Sketch (http://www.bohemiancoding.com/sketch) tomahawk-player/src/libtomahawk/Track.cpp000664 001750 001750 00000046524 12661705042 021646 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2013, Christian Muehlhaeuser * Copyright 2013, Teo Mrnjavac * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "Track_p.h" #include "audio/AudioEngine.h" #include "collection/Collection.h" #include "database/Database.h" #include "database/DatabaseImpl.h" #include "database/DatabaseCommand_LogPlayback.h" #include "database/DatabaseCommand_ModifyInboxEntry.h" #include "resolvers/Resolver.h" #include "utils/Logger.h" #include "Album.h" #include "Pipeline.h" #include "PlaylistEntry.h" #include "SourceList.h" #include #include #include using namespace Tomahawk; QHash< QString, track_wptr > Track::s_tracksByName = QHash< QString, track_wptr >(); static QMutex s_nameCacheMutex; inline QString cacheKey( const QString& artist, const QString& track, const QString& album, const QString& albumArtist, int duration, const QString& composer, unsigned int albumpos, unsigned int discnumber ) { const QString durationStr = QString::number( duration ); const QString albumposStr = QString::number( albumpos ); const QString discnumberStr = QString::number( discnumber ); QString str; // Preallocate space so that we will only call malloc once. // With Qt5 we can possibly revert back to just "+" these strings. // The "+" implementation in Qt4 differs slighty depending on compile // options which could drastically reduce the performance. str.reserve( artist.size() + track.size() + album.size() + albumArtist.size() + composer.size() + durationStr.size() + albumposStr.size() + discnumberStr.size() ); str += artist; str += track; str += album; str += albumArtist; str += composer; str += durationStr; str += albumposStr; str += discnumberStr; return str; } track_ptr Track::get( const QString& artist, const QString& track, const QString& album, const QString& albumArtist, int duration, const QString& composer, unsigned int albumpos, unsigned int discnumber ) { if ( artist.trimmed().isEmpty() || track.trimmed().isEmpty() ) { tDebug() << "Artist:" << artist << "Track:" << track; Q_ASSERT_X( false , Q_FUNC_INFO, "Given artist or track is empty" ); return track_ptr(); } QMutexLocker lock( &s_nameCacheMutex ); const QString key = cacheKey( artist, track, album, albumArtist, duration, composer, albumpos, discnumber ); if ( s_tracksByName.contains( key ) ) { track_wptr track = s_tracksByName.value( key ); if ( track ) return track.toStrongRef(); } track_ptr t = track_ptr( new Track( artist, track, album, albumArtist, duration, composer, albumpos, discnumber ), &Track::deleteLater ); t->setWeakRef( t.toWeakRef() ); s_tracksByName.insert( key, t ); return t; } track_ptr Track::get( unsigned int id, const QString& artist, const QString& track, const QString& album, const QString& albumArtist, int duration, const QString& composer, unsigned int albumpos, unsigned int discnumber ) { QMutexLocker lock( &s_nameCacheMutex ); const QString key = cacheKey( artist, track, album, albumArtist, duration, composer, albumpos, discnumber ); if ( s_tracksByName.contains( key ) ) { track_wptr track = s_tracksByName.value( key ); if ( track ) return track; } track_ptr t = track_ptr( new Track( id, artist, track, album, albumArtist, duration, composer, albumpos, discnumber ), &Track::deleteLater ); t->setWeakRef( t.toWeakRef() ); s_tracksByName.insert( key, t ); return t; } Track::Track( unsigned int id, const QString& artist, const QString& track, const QString& album, const QString& albumArtist, int duration, const QString& composer, unsigned int albumpos, unsigned int discnumber ) : d_ptr( new TrackPrivate( this, album, albumArtist, duration, composer, albumpos, discnumber ) ) { Q_D( Track ); d->trackData = TrackData::get( id, artist, track ); init(); } Track::Track( const QString& artist, const QString& track, const QString& album, const QString& albumArtist, int duration, const QString& composer, unsigned int albumpos, unsigned int discnumber ) : d_ptr( new TrackPrivate( this, album, albumArtist, duration, composer, albumpos, discnumber ) ) { Q_D( Track ); d->trackData = TrackData::get( 0, artist, track ); init(); } Track::~Track() { tDebug( LOGVERBOSE ) << Q_FUNC_INFO << toString(); } void Track::setArtist( const QString& artist ) { Q_D( Track ); d->artistPtr = artist_ptr(); d->trackData = TrackData::get( 0, artist, track() ); init(); emit updated(); } void Track::setAlbum( const QString& album ) { Q_D( Track ); d->albumPtr = album_ptr(); d->album = album; updateSortNames(); emit updated(); } void Track::setTrack( const QString& track ) { Q_D( Track ); d->trackData = TrackData::get( 0, artist(), track ); init(); emit updated(); } void Track::setAlbumPos( unsigned int albumpos ) { Q_D( Track ); d->albumpos = albumpos; emit updated(); } void Track::setAttributes( const QVariantMap& map ) { Q_D( Track ); d->trackData->setAttributes( map ); emit attributesLoaded(); } void Track::init() { Q_D( Track ); updateSortNames(); #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) QObject::connect( d->trackData.data(), &TrackData::attributesLoaded, this, &Track::attributesLoaded ); QObject::connect( d->trackData.data(), &TrackData::socialActionsLoaded, this, &Track::socialActionsLoaded ); QObject::connect( d->trackData.data(), &TrackData::statsLoaded, this, &Track::statsLoaded ); QObject::connect( d->trackData.data(), &TrackData::similarTracksLoaded, this, &Track::similarTracksLoaded ); QObject::connect( d->trackData.data(), &TrackData::lyricsLoaded, this, &Track::lyricsLoaded ); #else connect( d->trackData.data(), SIGNAL( attributesLoaded() ), SIGNAL( attributesLoaded() ) ); connect( d->trackData.data(), SIGNAL( socialActionsLoaded() ), SIGNAL( socialActionsLoaded() ) ); connect( d->trackData.data(), SIGNAL( statsLoaded() ), SIGNAL( statsLoaded() ) ); connect( d->trackData.data(), SIGNAL( similarTracksLoaded() ), SIGNAL( similarTracksLoaded() ) ); connect( d->trackData.data(), SIGNAL( lyricsLoaded() ), SIGNAL( lyricsLoaded() ) ); #endif } void Track::deleteLater() { Q_D( Track ); QMutexLocker lock( &s_nameCacheMutex ); const QString key = cacheKey( artist(), track(), d->album, d->albumArtist, d->duration, d->composer, d->albumpos, d->discnumber ); if ( s_tracksByName.contains( key ) ) { s_tracksByName.remove( key ); } QObject::deleteLater(); } unsigned int Track::trackId() const { Q_D( const Track ); return d->trackData->trackId(); } QWeakPointer Track::weakRef() { Q_D( Track ); return d->ownRef; } void Track::setWeakRef( QWeakPointer weakRef ) { Q_D( Track ); d->ownRef = weakRef; } void Track::startPlaying() { DatabaseCommand_LogPlayback* cmd = new DatabaseCommand_LogPlayback( weakRef().toStrongRef(), DatabaseCommand_LogPlayback::Started ); Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) ); markAsListened(); } void Track::finishPlaying( int timeElapsed ) { DatabaseCommand_LogPlayback* cmd = new DatabaseCommand_LogPlayback( weakRef().toStrongRef(), DatabaseCommand_LogPlayback::Finished, timeElapsed ); Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) ); } void Track::markAsListened() { Q_D( Track ); if ( !isListened() ) { DatabaseCommand_ModifyInboxEntry* cmd = new DatabaseCommand_ModifyInboxEntry( toQuery(), false ); Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) ); // The dbcmd does this in the DB, but let's update the TrackData ASAP QList< Tomahawk::SocialAction > actions = allSocialActions(); for ( QList< Tomahawk::SocialAction >::iterator it = actions.begin(); it != actions.end(); ++it ) { if ( it->action == "Inbox" ) { it->value = false; //listened! } } d->trackData->blockSignals( true ); d->trackData->setAllSocialActions( actions ); //emits socialActionsLoaded which gets propagated here d->trackData->blockSignals( false ); } } bool Track::isListened() const { bool isUnlistened = false; foreach ( Tomahawk::SocialAction action, allSocialActions() ) { if ( action.action == "Inbox" && action.value.toBool() == true ) { isUnlistened = true; break; } } return !isUnlistened; } void Track::updateSortNames() { Q_D( Track ); d->composerSortname = DatabaseImpl::sortname( d->composer, true ); d->albumSortname = DatabaseImpl::sortname( d->album ); } void Track::setAllSocialActions( const QList< SocialAction >& socialActions ) { Q_D( Track ); d->trackData->setAllSocialActions( socialActions ); } bool Track::equals( const Tomahawk::track_ptr& other, bool ignoreCase ) const { if ( other.isNull() ) return false; if ( ignoreCase ) return ( artist().toLower() == other->artist().toLower() && album().toLower() == other->album().toLower() && track().toLower() == other->track().toLower() ); else return ( artist() == other->artist() && album() == other->album() && track() == other->track() ); } QVariant Track::toVariant() const { QVariantMap m; m.insert( "artist", artist() ); m.insert( "album", album() ); m.insert( "track", track() ); m.insert( "duration", duration() ); return m; } QString Track::toString() const { return QString( "Track(%1 - %2%3)" ) .arg( artist() ) .arg( track() ) .arg( album().isEmpty() ? "" : QString( " on %1" ).arg( album() ) ); } query_ptr Track::toQuery() { Q_D( Track ); if ( d->query.isNull() ) { query_ptr query = Tomahawk::Query::get( weakRef().toStrongRef() ); if ( !query ) return query_ptr(); d->query = query->weakRef(); return query; } return d->query.toStrongRef(); } const QString& Track::composerSortname() const { Q_D( const Track ); return d->composerSortname; } const QString& Track::albumSortname() const { Q_D( const Track ); return d->albumSortname; } void Track::loadStats() { Q_D( Track ); d->trackData->loadStats(); } QList< Tomahawk::PlaybackLog > Track::playbackHistory( const Tomahawk::source_ptr& source ) const { Q_D( const Track ); return d->trackData->playbackHistory( source ); } unsigned int Track::playbackCount( const source_ptr& source ) { Q_D( Track ); return d->trackData->playbackCount( source ); } unsigned int Track::chartPosition() const { Q_D( const Track ); return d->trackData->chartPosition(); } unsigned int Track::chartCount() const { Q_D( const Track ); return d->trackData->chartCount(); } void Track::loadAttributes() { Q_D( Track ); d->trackData->loadAttributes(); } void Track::loadSocialActions( bool force ) { Q_D( Track ); d->trackData->loadSocialActions( force ); } QList< SocialAction > Track::allSocialActions() const { Q_D( const Track ); return d->trackData->allSocialActions(); } bool Track::loved() { Q_D( Track ); return d->trackData->loved(); } void Track::setLoved( bool loved, bool postToInfoSystem ) { Q_D( Track ); d->trackData->setLoved( loved ); if ( postToInfoSystem ) { QVariantMap loveInfo; Tomahawk::InfoSystem::InfoStringHash trackInfo; trackInfo["title"] = track(); trackInfo["artist"] = artist(); trackInfo["album"] = album(); loveInfo[ "trackinfo" ] = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( trackInfo ); Tomahawk::InfoSystem::InfoPushData pushData ( d->trackData->id(), ( loved ? Tomahawk::InfoSystem::InfoLove : Tomahawk::InfoSystem::InfoUnLove ), loveInfo, Tomahawk::InfoSystem::PushShortUrlFlag ); Tomahawk::InfoSystem::InfoSystem::instance()->pushInfo( pushData ); } } QString Track::socialActionDescription( const QString& action, DescriptionMode mode ) const { QString desc; QList< Tomahawk::SocialAction > socialActions = allSocialActions(); QStringList actionSources; int loveTotal = 0; foreach ( const Tomahawk::SocialAction& sa, socialActions ) { if ( sa.action == action ) { if ( actionSources.contains( sa.source->friendlyName() ) ) continue; actionSources << sa.source->friendlyName(); loveTotal++; } } QDateTime earliestTimestamp = QDateTime::currentDateTime(); actionSources.clear(); int loveCounter = 0; foreach ( const Tomahawk::SocialAction& sa, socialActions ) { if ( sa.action == action ) { if ( actionSources.contains( sa.source->friendlyName() ) ) continue; actionSources << sa.source->friendlyName(); if ( ++loveCounter > 3 ) continue; else if ( loveCounter > 1 ) { if ( loveCounter == loveTotal ) desc += tr( " and " ); else desc += ", "; } if ( sa.source->isLocal() ) { if ( loveCounter == 1 ) desc += "" + tr( "You" ) + ""; else desc += "" + tr( "you" ) + ""; } else desc += "" + sa.source->friendlyName() + ""; QDateTime saTimestamp = QDateTime::fromTime_t( sa.timestamp.toInt() ); if ( saTimestamp < earliestTimestamp && saTimestamp.toTime_t() > 0 ) earliestTimestamp = saTimestamp; } } if ( loveCounter > 0 ) { if ( loveCounter > 3 ) desc += " " + tr( "and" ) + " " + tr( "%n other(s)", "", loveCounter - 3 ) + ""; if ( mode == Short ) desc = "" + tr( "%n people", "", loveCounter ) + ""; //FIXME: more action descs required if ( action == "Love" ) desc += " " + tr( "loved this track" ); else if ( action == "Inbox" ) desc += " " + tr( "sent you this track %1" ) .arg( TomahawkUtils::ageToString( earliestTimestamp, true ) ); } return desc; } QList< Tomahawk::SocialAction > Track::socialActions( const QString& actionName, const QVariant& value, bool filterDupeSourceNames ) { Q_D( Track ); return d->trackData->socialActions( actionName, value, filterDupeSourceNames ); } artist_ptr Track::artistPtr() const { Q_D( const Track ); if ( !d->artistPtr ) { d->artistPtr = Artist::get( artist(), false ); connect( d->artistPtr.data(), SIGNAL( updated() ), SIGNAL( updated() ), Qt::UniqueConnection ); connect( d->artistPtr.data(), SIGNAL( coverChanged() ), SIGNAL( coverChanged() ), Qt::UniqueConnection ); } return d->artistPtr; } artist_ptr Track::albumArtistPtr() const { Q_D( const Track ); if ( !d->albumArtistPtr ) { d->albumArtistPtr = Artist::get( albumArtist(), false ); connect( d->albumArtistPtr.data(), SIGNAL( updated() ), SIGNAL( updated() ), Qt::UniqueConnection ); connect( d->albumArtistPtr.data(), SIGNAL( coverChanged() ), SIGNAL( coverChanged() ), Qt::UniqueConnection ); } return d->albumArtistPtr; } album_ptr Track::albumPtr() const { Q_D( const Track ); if ( !d->albumPtr ) { if ( albumArtist().isEmpty() ) d->albumPtr = Album::get( artistPtr(), album(), false ); else d->albumPtr = Album::get( albumArtistPtr(), album(), false ); connect( d->albumPtr.data(), SIGNAL( updated() ), SIGNAL( updated() ), Qt::UniqueConnection ); connect( d->albumPtr.data(), SIGNAL( coverChanged() ), SIGNAL( coverChanged() ), Qt::UniqueConnection ); } return d->albumPtr; } artist_ptr Track::composerPtr() const { Q_D( const Track ); if ( !d->composerPtr ) d->composerPtr = Artist::get( composer(), false ); return d->composerPtr; } QPixmap Track::cover( const QSize& size, bool forceLoad ) const { albumPtr()->cover( size, forceLoad ); if ( albumPtr()->coverLoaded() ) { if ( !albumPtr()->cover( size ).isNull() ) return albumPtr()->cover( size ); return artistPtr()->cover( size, forceLoad ); } return QPixmap(); } bool Track::coverLoaded() const { Q_D( const Track ); if ( d->albumPtr.isNull() ) return false; if ( d->albumPtr->coverLoaded() && !d->albumPtr->cover( QSize( 0, 0 ) ).isNull() ) return true; return d->artistPtr->coverLoaded(); } QList Track::similarTracks() const { Q_D( const Track ); return d->trackData->similarTracks(); } QStringList Track::lyrics() const { Q_D( const Track ); return d->trackData->lyrics(); } QString Track::artist() const { Q_D( const Track ); return d->trackData->artist(); } QString Track::albumArtist() const { Q_D( const Track ); return d->albumArtist; } QString Track::track() const { Q_D( const Track ); return d->trackData->track(); } QString Track::composer() const { Q_D( const Track ); return d->composer; } QString Track::album() const { Q_D( const Track ); return d->album; } int Track::duration() const { Q_D( const Track ); return d->duration; } QVariantMap Track::attributes() const { Q_D( const Track ); return d->trackData->attributes(); } int Track::year() const { Q_D( const Track ); return d->trackData->year(); } unsigned int Track::albumpos() const { Q_D( const Track ); return d->albumpos; } unsigned int Track::discnumber() const { Q_D( const Track ); return d->discnumber; } void Track::share( const Tomahawk::source_ptr& source ) { Q_D( Track ); d->trackData->share( source ); } const QString& Track::artistSortname() const { Q_D( const Track ); return d->trackData->artistSortname(); } const QString& Track::trackSortname() const { Q_D( const Track ); return d->trackData->trackSortname(); } tomahawk-player/src/libtomahawk/accounts/AccountDllMacro.h000664 001750 001750 00000002166 12661705042 025072 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2011, Jeff Mitchell * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef ACCOUNTDLLMACRO_H #define ACCOUNTDLLMACRO_H #include #ifndef ACCOUNTDLLEXPORT # if defined (ACCOUNTDLLEXPORT_PRO) # define ACCOUNTDLLEXPORT Q_DECL_EXPORT # else # define ACCOUNTDLLEXPORT Q_DECL_IMPORT # endif #endif #endif tomahawk-player/data/sql/dbmigrate-24_to_25.sql000664 001750 001750 00000001134 12661705042 022413 0ustar00stefanstefan000000 000000 -- Script to migate from db version 24 to 25. -- Added the social_attributes table. -- ALTER TABLE dynamic_playlist RENAME TO tmp_dynamic_playlist; CREATE TABLE IF NOT EXISTS dynamic_playlist ( guid TEXT NOT NULL REFERENCES playlist(guid) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, pltype TEXT, -- the generator type plmode INTEGER -- the mode of this playlist ); INSERT INTO dynamic_playlist( guid, pltype, plmode ) SELECT guid, pltype, plmode FROM tmp_dynamic_playlist; DROP TABLE tmp_dynamic_playlist; UPDATE settings SET v = '25' WHERE k == 'schema_version'; tomahawk-player/src/accounts/xmpp/xmpp-icon.png000664 001750 001750 00000010010 12661705042 022774 0ustar00stefanstefan000000 000000 PNG  IHDR@@iqsBIT|d pHYs$$IDATx[ipu"*k:i<Y܌i=(uN;qRKH-)#vj7N։$Jd&޵ZW."%qHqH }NݞP j1s޽w}+u^Zvb)??5`u!6W%m6˕{*;kjj|tاbƼıq{.CCA _ںpkk  J h4*233#6c߯p,mm6}\s og*1 NSLOOə3gd 䜾l pm}^Xxv{uWw"ʙL̞Q '1v}.f;1>ڦOAWC Hv+XlnIrzz2a:aS~蓾\~HNWD|qqmmmDRu: 3 g}9 8{}rt%i`JCOr r"7pܶl,٫G=5WTZ̥wRT68"73ຓEᲲKl}H E'trrl. XBa7>6&3sj;; #^r_̗f#8yN&U^+瓗dBDq?.}]OJKW7e _2YBJ' L*n'NǚEů(IV_94m{\jzT|6)O  |R?}T~rk_ƙg?ʶ`"I!wj-oXVWa6ur /~F^i]ici~v4nzT5B?/?+S`cz>SY@%5@˖ OvEw{jO,2KӓOY7Ȅ:%QGؾcx!z4?E99/6I A@-Դ YHsЩ 0c,Xl&@JBa`2b82b9EAS/G$qĿ<@.Gͤ",+Aҟ#c*5?x =Y,*.?ou78{*s>T9Ϛ]\RAro3=ǒ CDZ2;؋eY0?jj''geQQQ-55%`@HwKAHF:ixxXZ/0;P-x<916(ގ>y-LmAzlnjjR M4SWL#bcHi6jMoo 88VNXLEnفYPK9"{HTZ~oRKb ](y5pn0m] ߥv$..;~glTƾڨg?X5p3!!b+- DĔDcˊ0f8\JtDޗH%jG"J8:~X,zX@X#d=J)"k{+>iykF.+ U"QBiI)8_I2@Zz$NgꟚTmFh t5i'@#-* ?]Y.y(YK#-?''2$:'SUĄ`*e#.wHK:⠥"AH,mB{$6}S(?ED!PqqI~~~فC? u8VT[19rC+ ?'^Ž(xu__/_c9;8 |!y'Aɴ^HėJ~$uw^Q\bTv^CV{W\(Y24B <)}>*$ी)N=0 *.kأPsKb,BMAz^PV x`D'>ҋx=(9Nqe 9k67 )Dmh ~&`2gUD|XY#;P]-1]#.]-]K޻[FNBI ϏhTTJhƍDHo4fPpVG// v V+)Q6/^PLQN7d+*{O,)ԢQ~.h4=\^>C\?O/b@98ŵa WYڰZ}Fw'=ͅU"_2=29 m\Saɜ"aWK<6ro^뿔dtԣT;CǐۤGJii<,$ +5P 4YmA M,|O__:G{^ϗAo7 7:: 58(6K-)N b0ѷCq+fGFxԇ ^f$旡NcG.oNz :|mi3lmSɕQOZd`QA jeU0Kn=aAAHi-lvp7I>{;&2 vՅ"whY\Pfc^oWYe>'p_etS*hؒ"\Cê3?'`H:Aă;5,7zFAl_*TF,pl4.F {u,1Y¢L1gu{A䠶;p"7r$Wr^ !X,-*E'IT 2p'}zI\ȉK1^No0>|XzplM_\ 52A 8k߭|=<Ju:+Ch!Kg0< GMm2N'/k'NČ{!ǫ|78tSbTݦwLy}):p'.HF"mp-#1Nqv)E |Bo7vk~D}}t ωިǣfr8)ju $q g6h6i>>_s`NgXjwƣgȑzU8C8"`5SA[I߁}{vgi4uHSGk[MSA@*_1լh x6_"6c>}FaSM!l=a_nYJfjVn[uL 3-+}-s]Nܢ!unW5ۚp}\^p*m>BqSOmzak>keOm[ᕫ܎MwcBmFuldYe̳dMٲڐǍf &s s^d6nhLM0YsGFMyX|;)i!x,21ŲRc.\=_PV! ;uW{ax6߃mv8W>o{@ ]ٖ>{9֐[I)< <^>Z)y6 3 ^_KK@h{rX rv,vIENDB`tomahawk-player/CMakeModules/CheckStdFunctional.cmake000664 001750 001750 00000000361 12661705042 024020 0ustar00stefanstefan000000 000000 macro (CHECK_STD_FUNCTIONAL CXX_STD_FUNCTIONAL) include (CheckCXXSourceCompiles) check_cxx_source_compiles( " #include using std::function; int main() { return 0; }" ${CXX_STD_FUNCTIONAL}) endmacro() tomahawk-player/thirdparty/qt-certificate-addon/examples/000775 001750 001750 00000000000 12661705042 024771 5ustar00stefanstefan000000 000000 tomahawk-player/src/viewpages/dashboard/DashboardWidget.ui000664 001750 001750 00000002125 12661705042 025074 0ustar00stefanstefan000000 000000 DashboardWidget 0 0 828 635 Form 0 0 0 0 ContextView QWidget
playlist/ContextView.h
1
tomahawk-player/src/libtomahawk/playlist/dynamic/DynamicPlaylistRevision.cpp000664 001750 001750 00000002415 12661705042 030703 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "DynamicPlaylistRevision.h" #include "playlist/dynamic/DynamicControl.h" #include "PlaylistEntry.h" #include "Source.h" using namespace Tomahawk; DynamicPlaylistRevision::DynamicPlaylistRevision(const PlaylistRevision &other) { revisionguid = other.revisionguid; oldrevisionguid = other.oldrevisionguid; newlist = other.newlist; added = other.added; removed = other.removed; applied = other.applied; } DynamicPlaylistRevision::DynamicPlaylistRevision() {} tomahawk-player/src/infoplugins/generic/discogs/DiscogsPlugin.h000664 001750 001750 00000003542 12661705042 026114 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2012 Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef DISCOGS_PLUGIN_H #define DISCOGS_PLUGIN_H #include "Typedefs.h" #include "infosystem/InfoSystem.h" #include "infosystem/InfoSystemWorker.h" #include "../../InfoPluginDllMacro.h" class QNetworkReply; namespace Tomahawk { namespace InfoSystem { class INFOPLUGINDLLEXPORT DiscogsPlugin : public InfoPlugin { Q_PLUGIN_METADATA( IID "org.tomahawk-player.Player.InfoPlugin" ) Q_OBJECT Q_INTERFACES( Tomahawk::InfoSystem::InfoPlugin ) public: DiscogsPlugin(); virtual ~DiscogsPlugin(); protected slots: virtual void init() {} virtual void getInfo( Tomahawk::InfoSystem::InfoRequestData requestData ); virtual void notInCacheSlot( InfoStringHash criteria, InfoRequestData requestData ); virtual void pushInfo( Tomahawk::InfoSystem::InfoPushData ) {} private slots: void albumSearchSlot( const Tomahawk::InfoSystem::InfoRequestData& , QNetworkReply* ); void albumInfoSlot( const Tomahawk::InfoSystem::InfoRequestData& , QNetworkReply* ); private: bool isValidTrackData( Tomahawk::InfoSystem::InfoRequestData requestData ); }; } } #endif tomahawk-player/src/libtomahawk/database/DatabaseCommand_GenericSelect.h000664 001750 001750 00000005573 12661705042 027611 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef DATABASECOMMAND_GENERICSELECT_H #define DATABASECOMMAND_GENERICSELECT_H #include #include "DatabaseCommand.h" #include "Typedefs.h" #include #include #include "DllMacro.h" namespace Tomahawk { /** * This dbcmd takes a generic SELECT command that operates on the database and returns a list of query_ptrs * that match. * * In order for the conversion to query_ptr to work, the SELECT command should select the following items: * * track query: * track.name, artist.name [, optional extra values ] * * artist query: * artist.id, artist.name [, optional extra values ] * * album query: * album.id, album.name, artist.id, artist.name [, optional extra values ] * * Any extra values in the resultset will be returned as a QVariantList attached to the "data" property of each query_ptr * * Notes: * * Do not trail your SQL command with ; * * Do not use the LIMIT command if you pass limitResults > -1 * */ class DLLEXPORT DatabaseCommand_GenericSelect : public DatabaseCommand { Q_OBJECT public: enum QueryType { Track, Artist, Album }; explicit DatabaseCommand_GenericSelect( const QString& sqlSelect, QueryType type, int limitResults = -1, QObject* parent = 0 ); explicit DatabaseCommand_GenericSelect( const QString& sqlSelect, QueryType type, bool rawData, QObject* parent = 0 ); virtual void exec( DatabaseImpl* lib ); virtual bool doesMutates() const { return false; } virtual QString commandname() const { return "genericselect"; } signals: void tracks( const QList< Tomahawk::query_ptr >& tracks ); void artists( const QList< Tomahawk::artist_ptr >& artists ); void albums( const QList< Tomahawk::album_ptr >& albums ); void rawData( const QList< QStringList >& data ); private: QString m_sqlSelect; QueryType m_queryType; int m_limit; bool m_raw; }; } #if QT_VERSION < QT_VERSION_CHECK( 5, 0, 0 ) // Qt5 automatically generated this Metatype Q_DECLARE_METATYPE(QList) #endif #endif // DATABASECOMMAND_GENERICSELECT_H tomahawk-player/src/libtomahawk/database/DatabaseCommand_CollectionAttributes.h000664 001750 001750 00000003027 12661705042 031227 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef DATABASECOMMAND_COLLECTIONATTRIBUTES_H #define DATABASECOMMAND_COLLECTIONATTRIBUTES_H #include "Typedefs.h" #include "DatabaseCommand.h" #include "DatabaseCommand_SetCollectionAttributes.h" #include namespace Tomahawk { class DatabaseCommand_CollectionAttributes : public DatabaseCommand { Q_OBJECT public: DatabaseCommand_CollectionAttributes( DatabaseCommand_SetCollectionAttributes::AttributeType type ); virtual void exec( DatabaseImpl* lib ); virtual bool doesMutates() const { return false; } virtual QString commandname() const { return "collectionattributes"; } signals: void collectionAttributes( PairList ); private: DatabaseCommand_SetCollectionAttributes::AttributeType m_type; }; } #endif tomahawk-player/data/images/drop-local-songs.svg000664 001750 001750 00000014133 12661705042 023056 0ustar00stefanstefan000000 000000 drop-local-songs Created with Sketch (http://www.bohemiancoding.com/sketch) tomahawk-player/src/libtomahawk/database/DatabaseCommand_TrackAttributes.h000664 001750 001750 00000003435 12661705042 030203 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef DATABASECOMMAND_TRACKATTRIBUTES_H #define DATABASECOMMAND_TRACKATTRIBUTES_H #include "Typedefs.h" #include "DatabaseCommand.h" #include "DatabaseCommand_CollectionAttributes.h" #include "DatabaseCommand_SetTrackAttributes.h" #include namespace Tomahawk { class DatabaseCommand_TrackAttributes : public DatabaseCommand { Q_OBJECT public: // Get all tracks with this attribute DatabaseCommand_TrackAttributes( DatabaseCommand_SetTrackAttributes::AttributeType type ); // Get the specific tracks with this attribute DatabaseCommand_TrackAttributes( DatabaseCommand_SetTrackAttributes::AttributeType type, const QList< Tomahawk::QID > ids ); virtual void exec( DatabaseImpl* lib ); virtual bool doesMutates() const { return false; } virtual QString commandname() const { return "trackattributes"; } signals: void trackAttributes( PairList ); private: DatabaseCommand_SetTrackAttributes::AttributeType m_type; QList< Tomahawk::QID > m_ids; }; } #endif tomahawk-player/data/js/000775 001750 001750 00000000000 12661707214 016322 5ustar00stefanstefan000000 000000 tomahawk-player/src/libtomahawk/sip/PeerInfo.cpp000664 001750 001750 00000022164 12661705042 023076 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2012, Dominik Schmidt * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "PeerInfo_p.h" #include "accounts/Account.h" #include "network/ControlConnection.h" #include "network/Servent.h" #include "utils/Logger.h" #include "utils/TomahawkCache.h" #include "utils/TomahawkUtilsGui.h" #include "SipInfo.h" #include "SipPlugin.h" #include #include namespace Tomahawk { Tomahawk::Utils::WeakObjectHash< PeerInfo > PeerInfoPrivate::s_peersByCacheKey = Tomahawk::Utils::WeakObjectHash< PeerInfo >(); QHash< SipPlugin*, peerinfo_ptr > PeerInfo::s_selfPeersBySipPlugin = QHash< SipPlugin*, peerinfo_ptr >(); inline QString peerCacheKey( SipPlugin* plugin, const QString& peerId ) { return QString( "%1\t\t%2" ).arg( (quintptr) plugin ).arg( peerId ); } Tomahawk::peerinfo_ptr PeerInfo::getSelf( SipPlugin* parent, PeerInfo::GetOptions options ) { if ( s_selfPeersBySipPlugin.keys().contains( parent ) ) { return s_selfPeersBySipPlugin.value( parent ); } // if AutoCreate isn't enabled nothing to do here if ( ! ( options & AutoCreate ) ) { return peerinfo_ptr(); } peerinfo_ptr selfPeer( new PeerInfo( parent, "local peerinfo don't use this id for anything" ), &QObject::deleteLater ); selfPeer->setWeakRef( selfPeer.toWeakRef() ); selfPeer->setContactId( "localpeer" ); // parent->setSelfPeer( selfPeer ); s_selfPeersBySipPlugin.insert( parent, selfPeer ); return selfPeer; } QList< Tomahawk::peerinfo_ptr > PeerInfo::getAllSelf() { return s_selfPeersBySipPlugin.values(); } Tomahawk::peerinfo_ptr PeerInfo::get( SipPlugin* parent, const QString& id, GetOptions options ) { const QString key = peerCacheKey( parent, id ); if ( PeerInfoPrivate::s_peersByCacheKey.hash().contains( key ) && !PeerInfoPrivate::s_peersByCacheKey.hash().value( key ).isNull() ) { return PeerInfoPrivate::s_peersByCacheKey.hash().value( key ).toStrongRef(); } // if AutoCreate isn't enabled nothing to do here if ( ! ( options & AutoCreate ) ) { return peerinfo_ptr(); } peerinfo_ptr peerInfo( new PeerInfo( parent, id ), &QObject::deleteLater ); peerInfo->setWeakRef( peerInfo.toWeakRef() ); PeerInfoPrivate::s_peersByCacheKey.insert( key, peerInfo ); return peerInfo; } QList< Tomahawk::peerinfo_ptr > PeerInfo::getAll() { QList< Tomahawk::peerinfo_ptr > strongRefs; foreach ( Tomahawk::peerinfo_wptr wptr, PeerInfoPrivate::s_peersByCacheKey.hash().values() ) { if ( !wptr.isNull() ) strongRefs << wptr.toStrongRef(); } return strongRefs; } PeerInfo::PeerInfo( SipPlugin* parent, const QString& id ) : QObject() , d_ptr( new Tomahawk::PeerInfoPrivate ( this, parent, id ) ) { } PeerInfo::~PeerInfo() { tLog( LOGVERBOSE ) << Q_FUNC_INFO; } void PeerInfo::announce() { Q_ASSERT( !contactId().isEmpty() ); Servent::instance()->registerPeer( weakRef().toStrongRef() ); } QWeakPointer< PeerInfo > PeerInfo::weakRef() { return d_func()->ownRef; } void PeerInfo::setWeakRef( QWeakPointer< PeerInfo > weakRef ) { d_func()->ownRef = weakRef; } void PeerInfo::setControlConnection( ControlConnection* controlConnection ) { d_func()->controlConnection = controlConnection; } ControlConnection* PeerInfo::controlConnection() const { return d_func()->controlConnection; } bool PeerInfo::hasControlConnection() { return !d_func()->controlConnection.isNull(); } void PeerInfo::setType( Tomahawk::PeerInfo::Type type ) { d_func()->type = type; } PeerInfo::Type PeerInfo::type() const { return d_func()->type; } const QString PeerInfo::id() const { return d_func()->id; } SipPlugin* PeerInfo::sipPlugin() const { return d_func()->parent; } void PeerInfo::sendLocalSipInfos( const QList& sipInfos ) { sipPlugin()->sendSipInfos( weakRef().toStrongRef(), sipInfos ); } const QString PeerInfo::debugName() const { return QString("%1 : %2").arg( sipPlugin()->account()->accountFriendlyName() ).arg( id() ); } void PeerInfo::setContactId ( const QString& contactId ) { d_func()->contactId = contactId; } const QString PeerInfo::contactId() const { return d_func()->contactId; } const QString PeerInfo::nodeId() const { Q_D( const PeerInfo ); if ( d->sipInfos.isEmpty() ) { // Return an empty nodeId if we have not yet received the acutal nodeId. return QString(); } // All sip infos share the same nodeId return d->sipInfos.first().nodeId(); } const QString PeerInfo::key() const { Q_ASSERT( !d_func()->sipInfos.isEmpty() ); // All sip infos share the same key return d_func()->sipInfos.first().key(); } void PeerInfo::setStatus( PeerInfo::Status status ) { d_func()->status = status; if ( status == Online ) { announce(); } else if ( status == Offline && controlConnection() ) { controlConnection()->removePeerInfo( weakRef().toStrongRef() ); } // we need this to update the DiagnosticsDialog on new peers // if we ever happen to have a central PeerInfo manager object // we better add it there, but so far this would be the only // usage emit sipPlugin()->peerStatusChanged( weakRef().toStrongRef() ); } PeerInfo::Status PeerInfo::status() const { return d_func()->status; } void PeerInfo::setSipInfos( const QList& sipInfos ) { d_func()->sipInfos = sipInfos; tLog() << "id:" << id() << "info changed" << sipInfos; emit sipInfoChanged(); } const QList PeerInfo::sipInfos() const { return d_func()->sipInfos; } void PeerInfo::setFriendlyName( const QString& friendlyName ) { d_func()->friendlyName = friendlyName; } const QString PeerInfo::friendlyName() const { return d_func()->friendlyName; } void PeerInfo::setAvatar( const QPixmap& avatar ) { Q_D( PeerInfo ); QByteArray ba; QBuffer buffer( &ba ); buffer.open( QIODevice::WriteOnly ); avatar.save( &buffer, "PNG" ); // Check if the avatar is different by comparing a hash of the first 4096 bytes const QByteArray hash = QCryptographicHash::hash( ba.left( 4096 ), QCryptographicHash::Sha1 ); if ( d->avatarHash == hash ) return; d->avatarHash = hash; d->avatarBuffer = ba; d->avatar.reset(); d->fancyAvatar.reset(); Q_ASSERT( !contactId().isEmpty() ); TomahawkUtils::Cache::instance()->putData( "Sources", 7776000000 /* 90 days */, contactId(), ba ); } const QPixmap PeerInfo::avatar( TomahawkUtils::ImageMode style, const QSize& size ) const { Q_D( const PeerInfo ); if ( d->avatar.isNull() ) { // tDebug() << "Avatar for:" << id(); Q_ASSERT( !contactId().isEmpty() ); if ( d->avatarBuffer.isEmpty() && !contactId().isEmpty() ) d->avatarBuffer = TomahawkUtils::Cache::instance()->getData( "Sources", contactId() ).toByteArray(); d->avatar.reset( new QPixmap() ); if ( !d->avatarBuffer.isEmpty() ) d->avatar->loadFromData( d->avatarBuffer ); d->avatarBuffer.clear(); } if ( style == TomahawkUtils::RoundedCorners && d->avatar && !d->avatar->isNull() && d->fancyAvatar.isNull() ) d->fancyAvatar.reset( new QPixmap( TomahawkUtils::createRoundedImage( QPixmap( *d->avatar ), QSize( 0, 0 ) ) ) ); QPixmap pixmap; if ( style == TomahawkUtils::RoundedCorners && d->fancyAvatar ) { pixmap = *d->fancyAvatar; } else if ( d->avatar && !d->avatar->isNull() ) { pixmap = *d->avatar; } if ( !pixmap.isNull() && !size.isEmpty() ) { if ( d->coverCache[ style ].contains( size.width() ) ) { return d->coverCache[ style ].value( size.width() ); } QPixmap scaledCover; scaledCover = pixmap.scaled( size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); QHash< int, QPixmap > innerCache = d->coverCache[ style ]; innerCache.insert( size.width(), scaledCover ); d->coverCache[ style ] = innerCache; return scaledCover; } return pixmap; } void PeerInfo::setVersionString( const QString& versionString ) { d_func()->versionString = versionString; } const QString PeerInfo::versionString() const { return d_func()->versionString; } void PeerInfo::setData( const QVariant& data ) { d_func()->data = data; } const QVariant PeerInfo::data() const { return d_func()->data; } } // ns tomahawk-player/src/libtomahawk/playlist/PlaylistItemDelegate.h000664 001750 001750 00000012060 12661705042 026147 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2014, Christian Muehlhaeuser * Copyright 2013, Teo Mrnjavac * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef PLAYLISTITEMDELEGATE_H #define PLAYLISTITEMDELEGATE_H #include #include #include #include "DllMacro.h" #include "Typedefs.h" class TrackModel; class PlayableItem; class PlayableProxyModel; class TrackView; namespace Tomahawk { class PixmapDelegateFader; } class DLLEXPORT PlaylistItemDelegate : public QStyledItemDelegate { Q_OBJECT public: PlaylistItemDelegate( TrackView* parent, PlayableProxyModel* proxy ); void updateRowSize( const QModelIndex& index ); virtual QSize sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const; public slots: void resetHoverIndex(); signals: void updateIndex( const QModelIndex& idx ); private slots: void doUpdateIndex( const QPersistentModelIndex& index ); protected: void prepareStyleOption( QStyleOptionViewItemV4* option, const QModelIndex& index, PlayableItem* item ) const; void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; bool editorEvent( QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index ); QPersistentModelIndex hoveringOver() const { return m_hoveringOver; } QRect drawInfoButton( QPainter* painter, const QRect& rect, const QModelIndex& index, float height ) const; QRect drawSourceIcon( QPainter* painter, const QRect& rect, PlayableItem* item, float height ) const; QRect drawCover( QPainter* painter, const QRect& rect, PlayableItem* item, const QModelIndex& index ) const; QRect drawLoveBox( QPainter* painter, const QRect& rect, PlayableItem* item, const QModelIndex& index ) const; QRect drawGenericBox( QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QString& text, const QList< Tomahawk::source_ptr >& sources, const QModelIndex& index ) const; void drawRectForBox( QPainter* painter, const QRect& rect ) const; void drawAvatarsForBox( QPainter* painter, const QRect& avatarsRect, int avatarSize, int avatarMargin, int count, const QList< Tomahawk::source_ptr >& sources, const QModelIndex& index ) const; void drawRichText( QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, int flags, QTextDocument& text ) const; QRect drawSource( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, const QRect& rect, PlayableItem* item ) const; QRect drawTrack( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, const QRect& rect, PlayableItem* item ) const; void paintDetailed( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; void paintShort( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; QTextOption m_topOption; QTextOption m_bottomOption; QTextOption m_centerOption; QTextOption m_centerRightOption; QFont m_smallFont; QFont m_smallBoldFont; QFont m_boldFont; QFont m_bigBoldFont; QFontMetrics m_smallBoldFontMetrics; QFontMetrics m_bigBoldFontMetrics; protected slots: virtual void modelChanged(); virtual void onAudioEngineTick( qint64 ms ); virtual void onPlaybackChange(); private: mutable QHash< QPersistentModelIndex, QSharedPointer< Tomahawk::PixmapDelegateFader > > m_pixmaps; mutable QHash< QPersistentModelIndex, QRect > m_infoButtonRects; mutable QHash< QPersistentModelIndex, QRect > m_loveButtonRects; mutable QHash< QPersistentModelIndex, QRect > m_artistNameRects; mutable QHash< QPersistentModelIndex, QHash< Tomahawk::source_ptr, QRect > > m_avatarBoxRects; QPersistentModelIndex m_hoveringOver; QPersistentModelIndex m_hoveringOverArtist; mutable QPersistentModelIndex m_nowPlaying; TrackView* m_view; PlayableProxyModel* m_model; }; #endif // PLAYLISTITEMDELEGATE_H tomahawk-player/src/libtomahawk/utils/ItunesParser.h000664 001750 001750 00000004047 12661705042 024025 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Written by Hugo Lindström * But based on Leo Franchi's work from spotifyParser * Copyright 2010-2011, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef ITUNES_PARSER_H #define ITUNES_PARSER_H #include "DllMacro.h" #include "Typedefs.h" #include "Query.h" #include "DropJobNotifier.h" #include #include #include class NetworkReply; class TrackModel; namespace Tomahawk { /** * Small class to parse itunes links into query_ptrs * * Connect to the signals to get the results */ class DLLEXPORT ItunesParser : public QObject { Q_OBJECT public: explicit ItunesParser( const QString& trackUrl, QObject* parent = 0 ); explicit ItunesParser( const QStringList& trackUrls, QObject* parent = 0 ); virtual ~ItunesParser(); signals: void track( const Tomahawk::query_ptr& track ); void tracks( const QList< Tomahawk::query_ptr > tracks ); private slots: void itunesResponseLookupFinished(); private: QPixmap pixmap() const; void lookupItunesUri( const QString& track ); void checkTrackFinished(); bool m_single; QList< query_ptr > m_tracks; QSet< NetworkReply* > m_queries; QString m_title, m_info, m_creator; Tomahawk::playlist_ptr m_playlist; DropJobNotifier* m_browseJob; static QPixmap* s_pixmap; }; } #endif tomahawk-player/src/libtomahawk/playlist/dynamic/widgets/MiscControlWidgets.h000664 001750 001750 00000002372 12661705042 030756 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef MISC_CONTROL_WIDGETS_H #define MISC_CONTROL_WIDGETS_H #include class QLabel; class QSlider; namespace Tomahawk { class LabeledSlider : public QWidget { Q_OBJECT public: explicit LabeledSlider( const QString& leftT, const QString& rightT, QWidget* parent = 0 ); QSlider* slider() { return m_slider; } private: QSlider* m_slider; QLabel* m_leftLabel; QLabel* m_rightLabel; }; } #endif tomahawk-player/src/libtomahawk/utils/Json.h000664 001750 001750 00000005413 12661705042 022310 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2014, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #pragma once #ifndef TOMAHAWKUTILS_JSON_H #define TOMAHAWKUTILS_JSON_H #include "DllMacro.h" #include namespace TomahawkUtils { /** * Convert a QObject instance to a QVariantMap by adding its properties * as key-value pairs. * * @param object Object that shall be "serialised" * @return All properties of the object stored as QVariantMap */ DLLEXPORT QVariantMap qobject2qvariant( const QObject* object ); /** * Write out all key-value pairs into the respective properties of the * given object. * * @param variant The key-value pairs that shall be stored in the object. * @param object The destiation object where we store the key-value pairs of the map as properties. */ DLLEXPORT void qvariant2qobject( const QVariantMap& variant, QObject* object ); /** * Parse the JSON string and return the result as a QVariant. * * @param jsonData The string containing the data as JSON. * @param ok Set to true if the conversion was successful, otherwise false. * @return After a successful conversion the parsed data either as QVariantMap or QVariantList. */ DLLEXPORT QVariant parseJson( const QByteArray& jsonData, bool* ok = 0 ); /** * Convert a QVariant to a JSON representation. * * This function will accept Strings, Number, QVariantList and QVariantMaps * as input types. Although Qt5's JSON implementation itself does not * support the serialisation of QVariantHash, we will convert a QVariantHash * to a QVariantMap but it is suggest to convert all QVariantHash to * QVariantMap in your code than passing them here. * * @param variant The data to be serialised. * @param ok Set to true if the conversion was successful, otherwise false. * @return After a successful serialisation the data of the QVariant represented as JSON. */ DLLEXPORT QByteArray toJson( const QVariant &variant, bool* ok = 0 ); } #endif // TOMAHAWKUTILS_JSON_H tomahawk-player/src/tomahawk/sourcetree/items/QueueItem.cpp000664 001750 001750 00000011506 12661705042 025307 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2014, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "QueueItem.h" #include "utils/ImageRegistry.h" #include "utils/Logger.h" #include "playlist/ContextView.h" #include "playlist/TrackView.h" #include "playlist/PlayableProxyModel.h" #include "ViewManager.h" #include "ViewPage.h" #include "DropJob.h" #include #include QueueItem::QueueItem( SourcesModel* model, SourceTreeItem* parent ) : SourceTreeItem( model, parent, SourcesModel::Queue ) , m_sortValue( -150 ) { m_text = tr( "Queue" ); m_icon = ImageRegistry::instance()->icon( RESPATH "images/queue.svg" ); connect( ViewManager::instance()->queue()->view()->trackView()->proxyModel(), SIGNAL( itemCountChanged( uint ) ), SIGNAL( updated() ) ); } QueueItem::~QueueItem() { } QString QueueItem::text() const { return m_text; } QIcon QueueItem::icon() const { return m_icon; } int QueueItem::peerSortValue() const { return m_sortValue; } void QueueItem::setSortValue( int value ) { m_sortValue = value; } int QueueItem::unlistenedCount() const { return ViewManager::instance()->queue()->view()->trackView()->proxyModel()->rowCount(); } void QueueItem::activate() { Tomahawk::ViewPage* page = ViewManager::instance()->showQueuePage(); model()->linkSourceItemToPage( this, page ); } bool QueueItem::willAcceptDrag( const QMimeData* data ) const { return DropJob::acceptsMimeData( data, DropJob::Track ); } SourceTreeItem::DropTypes QueueItem::supportedDropTypes( const QMimeData* data ) const { if ( data->hasFormat( "application/tomahawk.mixed" ) ) { // If this is mixed but only queries/results, we can still handle them bool mixedQueries = true; QByteArray itemData = data->data( "application/tomahawk.mixed" ); QDataStream stream( &itemData, QIODevice::ReadOnly ); QString mimeType; qlonglong val; while ( !stream.atEnd() ) { stream >> mimeType; if ( mimeType != "application/tomahawk.query.list" && mimeType != "application/tomahawk.result.list" ) { mixedQueries = false; break; } stream >> val; } if ( mixedQueries ) return (DropTypes)(DropTypeThisTrack | DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50); else return DropTypesNone; } if ( data->hasFormat( "application/tomahawk.query.list" ) ) return (DropTypes)(DropTypeThisTrack | DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50); else if ( data->hasFormat( "application/tomahawk.result.list" ) ) return (DropTypes)(DropTypeThisTrack | DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50); else if ( data->hasFormat( "application/tomahawk.metadata.album" ) ) return (DropTypes)(DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50); else if ( data->hasFormat( "application/tomahawk.metadata.artist" ) ) return (DropTypes)(DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50); else if ( data->hasFormat( "text/plain" ) ) { return DropTypesNone; } return DropTypesNone; } bool QueueItem::dropMimeData( const QMimeData* data, Qt::DropAction action ) { Q_UNUSED( action ); if ( !DropJob::acceptsMimeData( data, DropJob::Track ) ) return false; DropJob *dj = new DropJob(); connect( dj, SIGNAL( tracks( QList< Tomahawk::query_ptr > ) ), SLOT( parsedDroppedTracks( QList< Tomahawk::query_ptr > ) ) ); dj->setDropTypes( DropJob::Track ); dj->setDropAction( DropJob::Append ); dj->tracksFromMimeData( data, false, false ); // TODO can't know if it works or not yet... return true; } void QueueItem::parsedDroppedTracks( const QList< Tomahawk::query_ptr >& tracks ) { if ( tracks.count() ) { ViewManager::instance()->queue()->view()->trackView()->model()->appendQueries( tracks ); } else tDebug() << "ERROR: Could not add empty track list to queue!"; } tomahawk-player/data/images/jump-link.svg000664 001750 001750 00000004626 12661705042 021607 0ustar00stefanstefan000000 000000 jump-link Created with Sketch (http://www.bohemiancoding.com/sketch) tomahawk-player/src/libtomahawk/playlist/TreeProxyModel.cpp000664 001750 001750 00000027036 12661705042 025362 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2012, Jeff Mitchell * Copyright 2013, Teo Mrnjavac * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "TreeProxyModel.h" #include "TreeProxyModelPlaylistInterface.h" #include "Source.h" #include "Query.h" #include "database/Database.h" #include "database/DatabaseImpl.h" #include "collection/AlbumsRequest.h" #include "collection/ArtistsRequest.h" #include "database/DatabaseCommand_AllAlbums.h" #include "PlayableItem.h" #include "utils/Logger.h" #include TreeProxyModel::TreeProxyModel( QObject* parent ) : PlayableProxyModel( parent ) , m_artistsFilterCmd( 0 ) , m_model( 0 ) { setPlaylistInterface( Tomahawk::playlistinterface_ptr( new Tomahawk::TreeProxyModelPlaylistInterface( this ) ) ); } void TreeProxyModel::setSourcePlayableModel( TreeModel* model ) { if ( m_model ) { disconnect( m_model, SIGNAL( rowsInserted( QModelIndex, int, int ) ), this, SLOT( onRowsInserted( QModelIndex, int, int ) ) ); disconnect( m_model, SIGNAL( modelReset() ), this, SLOT( onModelReset() ) ); } PlayableProxyModel::setSourcePlayableModel( model ); m_model = model; if ( m_model ) { connect( m_model, SIGNAL( rowsInserted( QModelIndex, int, int ) ), SLOT( onRowsInserted( QModelIndex, int, int ) ) ); connect( m_model, SIGNAL( modelReset() ), SLOT( onModelReset() ) ); } } void TreeProxyModel::onRowsInserted( const QModelIndex& parent, int /* start */, int /* end */ ) { if ( m_filter.isEmpty() ) return; if ( sender() != m_model ) return; PlayableItem* pi = m_model->itemFromIndex( m_model->index( parent.row(), 0, parent.parent() ) ); if ( pi->artist().isNull() ) return; Tomahawk::AlbumsRequest* cmd = 0; if ( !m_model->collection().isNull() ) cmd = m_model->collection()->requestAlbums( pi->artist() ); else cmd = new Tomahawk::DatabaseCommand_AllAlbums( Tomahawk::collection_ptr(), pi->artist() ); cmd->setFilter( m_filter ); connect( dynamic_cast< QObject* >( cmd ), SIGNAL( albums( QList ) ), SLOT( onFilterAlbums( QList ) ) ); cmd->enqueue(); } void TreeProxyModel::onModelReset() { m_cache.clear(); m_artistsFilter.clear(); m_albumsFilter.clear(); } void TreeProxyModel::setFilter( const QString& pattern ) { emit filteringStarted(); m_filter = pattern; beginResetModel(); m_albumsFilter.clear(); endResetModel(); if ( m_artistsFilterCmd ) { disconnect( dynamic_cast< QObject* >( m_artistsFilterCmd ), SIGNAL( artists( QList ) ), this, SLOT( onFilterArtists( QList ) ) ); delete m_artistsFilterCmd; m_artistsFilterCmd = 0; } if ( m_filter.isEmpty() ) { filterFinished(); } else { Tomahawk::ArtistsRequest* cmd = 0; if ( !m_model->collection().isNull() ) cmd = m_model->collection()->requestArtists(); else cmd = new Tomahawk::DatabaseCommand_AllArtists(); //for SuperCollection, TODO: replace with a proper proxy-ArtistsRequest cmd->setFilter( pattern ); m_artistsFilterCmd = cmd; connect( dynamic_cast< QObject* >( cmd ), SIGNAL( artists( QList ) ), SLOT( onFilterArtists( QList ) ) ); cmd->enqueue(); } } QString TreeProxyModel::filter() const { return m_filter; } void TreeProxyModel::onFilterArtists( const QList& artists ) { bool finished = true; m_artistsFilter = artists; m_artistsFilterCmd = 0; foreach ( const Tomahawk::artist_ptr& artist, artists ) { QModelIndex idx = m_model->indexFromArtist( artist ); if ( m_model->rowCount( idx ) ) { finished = false; Tomahawk::AlbumsRequest* cmd = m_model->collection()->requestAlbums( artist ); cmd->setFilter( m_filter ); connect( dynamic_cast< QObject* >( cmd ), SIGNAL( albums( QList ) ), SLOT( onFilterAlbums( QList ) ) ); cmd->enqueue(); } } if ( finished ) filterFinished(); } void TreeProxyModel::onFilterAlbums( const QList& albums ) { foreach ( const Tomahawk::album_ptr& album, albums ) m_albumsFilter << album->id(); filterFinished(); } void TreeProxyModel::filterFinished() { if ( m_artistsFilterCmd ) { disconnect( dynamic_cast< QObject* >( m_artistsFilterCmd ), SIGNAL( artists( QList ) ), this, SLOT( onFilterArtists( QList ) ) ); delete m_artistsFilterCmd; m_artistsFilterCmd = 0; } setFilterRegExp( m_filter ); emit filterChanged( m_filter ); emit filteringFinished(); } bool TreeProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const { PlayableItem* item = sourceModel()->itemFromIndex( sourceModel()->index( sourceRow, 0, sourceParent ) ); Q_ASSERT( item ); if ( m_model->mode() == Tomahawk::DatabaseMode && !item->query().isNull() ) { QList< Tomahawk::query_ptr > rl = m_cache.values( sourceParent ); foreach ( const Tomahawk::query_ptr& cachedQuery, rl ) { if ( cachedQuery.isNull() ) continue; if ( cachedQuery->track()->track() == item->query()->track()->track() && ( cachedQuery->track()->albumpos() == item->query()->track()->albumpos() || cachedQuery->track()->albumpos() == 0 ) ) { return ( cachedQuery.data() == item->query().data() ); } } for ( int i = 0; i < sourceModel()->rowCount( sourceParent ); i++ ) { if ( i == sourceRow ) continue; PlayableItem* ti = sourceModel()->itemFromIndex( sourceModel()->index( i, 0, sourceParent ) ); if ( ti && ti->name() == item->name() && !ti->query().isNull() ) { if ( ti->query()->track()->albumpos() == item->query()->track()->albumpos() || ti->query()->track()->albumpos() == 0 || item->query()->track()->albumpos() == 0 ) { if ( item->result().isNull() ) return false; if ( !ti->result().isNull() ) { if ( !item->result()->isOnline() && ti->result()->isOnline() ) return false; if ( ( item->result()->collection().isNull() || !item->result()->collection()->source()->isLocal() ) && !ti->result()->collection().isNull() && ti->result()->collection()->source()->isLocal() ) { return false; } } } } } } bool accepted = false; if ( m_filter.isEmpty() ) accepted = true; else if ( !item->artist().isNull() ) accepted = m_artistsFilter.contains( item->artist() ); else if ( !item->album().isNull() ) accepted = m_albumsFilter.contains( item->album()->id() ); if ( !accepted ) { QStringList sl = m_filter.split( " ", QString::SkipEmptyParts ); foreach( const QString& s, sl ) { if ( !item->name().contains( s, Qt::CaseInsensitive ) && !item->albumName().contains( s, Qt::CaseInsensitive ) && !item->artistName().contains( s, Qt::CaseInsensitive ) ) { return false; } } } m_cache.insertMulti( sourceParent, item->query() ); return true; } bool TreeProxyModel::lessThan( const QModelIndex& left, const QModelIndex& right ) const { PlayableItem* p1 = sourceModel()->itemFromIndex( left ); PlayableItem* p2 = sourceModel()->itemFromIndex( right ); if ( !p1 ) return true; if ( !p2 ) return false; /* if ( !p1->result().isNull() && p2->result().isNull() ) return true; if ( p1->result().isNull() && !p2->result().isNull() ) return false;*/ unsigned int albumpos1 = 0; unsigned int albumpos2 = 0; unsigned int discnumber1 = 0; unsigned int discnumber2 = 0; if ( !p1->query().isNull() ) { albumpos1 = p1->query()->track()->albumpos(); discnumber1 = p1->query()->track()->discnumber(); } if ( !p2->query().isNull() ) { albumpos2 = p2->query()->track()->albumpos(); discnumber2 = p2->query()->track()->discnumber(); } if ( !p1->result().isNull() ) { if ( albumpos1 == 0 ) albumpos1 = p1->result()->track()->albumpos(); if ( discnumber1 == 0 ) discnumber1 = p1->result()->track()->discnumber(); } if ( !p2->result().isNull() ) { if ( albumpos2 == 0 ) albumpos2 = p2->result()->track()->albumpos(); if ( discnumber2 == 0 ) discnumber2 = p2->result()->track()->discnumber(); } discnumber1 = qMax( 1, (int)discnumber1 ); discnumber2 = qMax( 1, (int)discnumber2 ); if ( discnumber1 != discnumber2 ) { return discnumber1 < discnumber2; } else { if ( albumpos1 != albumpos2 ) return albumpos1 < albumpos2; } const QString& lefts = textForItem( p1 ); const QString& rights = textForItem( p2 ); if ( lefts == rights ) return (qint64)&p1 < (qint64)&p2; return QString::localeAwareCompare( lefts, rights ) < 0; } QString TreeProxyModel::textForItem( PlayableItem* item ) const { if ( !item ) return QString(); if ( !item->artist().isNull() ) { return item->artist()->sortname(); } else if ( !item->album().isNull() ) { return Tomahawk::DatabaseImpl::sortname( item->album()->name() ); } else if ( !item->result().isNull() ) { return item->result()->track()->trackSortname(); } else if ( !item->query().isNull() ) { return item->query()->track()->track(); } return QString(); } QModelIndex TreeProxyModel::indexFromArtist( const Tomahawk::artist_ptr& artist ) const { return mapFromSource( m_model->indexFromArtist( artist ) ); } QModelIndex TreeProxyModel::indexFromAlbum( const Tomahawk::album_ptr& album ) const { return mapFromSource( m_model->indexFromAlbum( album ) ); } QModelIndex TreeProxyModel::indexFromResult( const Tomahawk::result_ptr& result ) const { return mapFromSource( m_model->indexFromResult( result ) ); } QModelIndex TreeProxyModel::indexFromQuery( const Tomahawk::query_ptr& query ) const { return mapFromSource( m_model->indexFromQuery( query ) ); } tomahawk-player/thirdparty/qt-certificate-addon/src/certificate/certificatebuilder_p.h000664 001750 001750 00000004215 12661705042 032547 0ustar00stefanstefan000000 000000 /**************************************************************************** ** ** Copyright (C) 2012-2013 Richard J. Moore ** ** $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 CERTIFICATEBUILDER_P_H #define CERTIFICATEBUILDER_P_H #include #include #include "certificatebuilder.h" QT_BEGIN_NAMESPACE_CERTIFICATE struct CertificateBuilderPrivate { int errnumber; gnutls_x509_crt_t crt; }; QT_END_NAMESPACE_CERTIFICATE #endif // CERTIFICATEBUILDER_P_H tomahawk-player/src/tomahawk/sourcetree/SourcesModel.cpp000664 001750 001750 00000052130 12661705042 024665 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2014, Christian Muehlhaeuser * Copyright 2010-2011, Leo Franchi * Copyright 2010-2012, Jeff Mitchell * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "sourcetree/SourcesModel.h" #include "sourcetree/items/ScriptCollectionItem.h" #include "sourcetree/items/SourceTreeItem.h" #include "sourcetree/items/SourceItem.h" #include "sourcetree/items/GroupItem.h" #include "sourcetree/items/GenericPageItems.h" #include "sourcetree/items/HistoryItem.h" #include "sourcetree/items/LovedTracksItem.h" #include "sourcetree/items/InboxItem.h" #include "sourcetree/items/QueueItem.h" #include "SourceList.h" #include "Playlist.h" #include "collection/Collection.h" #include "Source.h" #include "ViewManager.h" #include "GlobalActionManager.h" #include "DropJob.h" #include "items/PlaylistItems.h" #include "playlist/dynamic/widgets/DynamicWidget.h" #include "utils/Closure.h" #include "utils/ImageRegistry.h" #include "utils/Logger.h" #include "utils/PluginLoader.h" #include #include #include #include #include using namespace Tomahawk; SourcesModel::SourcesModel( QObject* parent ) : QAbstractItemModel( parent ) , m_rootItem( 0 ) , m_viewPageDelayedCacheItem( 0 ) { m_rootItem = new SourceTreeItem( this, 0, Invalid ); connect( ViewManager::instance(), SIGNAL( viewPageAdded( QString, Tomahawk::ViewPage*, int ) ), SLOT( appendPageItem( QString, Tomahawk::ViewPage*, int ) ) ); appendGroups(); onSourcesAdded( SourceList::instance()->sources() ); connect( SourceList::instance(), SIGNAL( sourceAdded( Tomahawk::source_ptr ) ), SLOT( onSourceAdded( Tomahawk::source_ptr ) ) ); connect( SourceList::instance(), SIGNAL( sourceRemoved( Tomahawk::source_ptr ) ), SLOT( onSourceRemoved( Tomahawk::source_ptr ) ) ); connect( ViewManager::instance(), SIGNAL( viewPageActivated( Tomahawk::ViewPage* ) ), SLOT( viewPageActivated( Tomahawk::ViewPage* ) ) ); foreach ( const collection_ptr& c, SourceList::instance()->scriptCollections() ) { onScriptCollectionAdded( c ); } connect( SourceList::instance(), SIGNAL( scriptCollectionAdded( Tomahawk::collection_ptr ) ), this, SLOT( onScriptCollectionAdded( Tomahawk::collection_ptr ) ) ); connect( SourceList::instance(), SIGNAL( scriptCollectionRemoved( Tomahawk::collection_ptr ) ), this, SLOT( onScriptCollectionRemoved( Tomahawk::collection_ptr ) ) ); } SourcesModel::~SourcesModel() { delete m_rootItem; } QString SourcesModel::rowTypeToString( RowType type ) { switch ( type ) { case Group: return tr( "Group" ); case Source: return tr( "Source" ); case Collection: return tr( "Collection" ); case StaticPlaylist: return tr( "Playlist" ); case AutomaticPlaylist: return tr( "Automatic Playlist" ); case Station: return tr( "Station" ); default: return QString( "Unknown" ); } } QVariant SourcesModel::data( const QModelIndex& index, int role ) const { if ( !index.isValid() ) return QVariant(); SourceTreeItem* item = itemFromIndex( index ); if ( !item ) return QVariant(); switch ( role ) { case Qt::SizeHintRole: return QSize( 0, 18 ); case SourceTreeItemRole: return QVariant::fromValue< SourceTreeItem* >( item ); case SourceTreeItemTypeRole: return item->type(); case Qt::DisplayRole: case Qt::EditRole: return item->text(); case Qt::DecorationRole: return item->icon(); case SourcesModel::SortRole: return item->peerSortValue(); case SourcesModel::IDRole: return item->IDValue(); case SourcesModel::LatchedOnRole: { if ( item->type() == Source ) { SourceItem* cItem = qobject_cast< SourceItem* >( item ); return cItem->localLatchedOn(); } return false; } case SourcesModel::LatchedRealtimeRole: { if ( item->type() == Source ) { SourceItem* cItem = qobject_cast< SourceItem* >( item ); return cItem->localLatchMode() == Tomahawk::PlaylistModes::RealTime; } return false; } case SourcesModel::CustomActionRole: { return QVariant::fromValue< QList< QAction* > >( item->customActions() ); } case Qt::ToolTipRole: if ( !item->tooltip().isEmpty() ) return item->tooltip(); } return QVariant(); } int SourcesModel::columnCount( const QModelIndex& ) const { return 1; } int SourcesModel::rowCount( const QModelIndex& parent ) const { if ( !parent.isValid() ) { return m_rootItem->children().count(); } return itemFromIndex( parent )->children().count(); } QModelIndex SourcesModel::parent( const QModelIndex& child ) const { if ( !child.isValid() ) { return QModelIndex(); } SourceTreeItem* node = itemFromIndex( child ); SourceTreeItem* parent = node->parent(); if ( parent == m_rootItem ) return QModelIndex(); return createIndex( rowForItem( parent ), 0, parent ); } QModelIndex SourcesModel::index( int row, int column, const QModelIndex& parent ) const { if ( row < 0 || column < 0 ) return QModelIndex(); if ( hasIndex( row, column, parent ) ) { SourceTreeItem *parentNode = itemFromIndex( parent ); SourceTreeItem *childNode = parentNode->children().at( row ); return createIndex( row, column, childNode ); } return QModelIndex(); } bool SourcesModel::setData( const QModelIndex& index, const QVariant& value, int role ) { SourceTreeItem* item = itemFromIndex( index ); return item->setData( value, role ); } QStringList SourcesModel::mimeTypes() const { return DropJob::mimeTypes(); } QMimeData* SourcesModel::mimeData( const QModelIndexList& ) const { // TODO return new QMimeData(); } bool SourcesModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent ) { SourceTreeItem* item = 0; if ( row == -1 && column == -1 ) item = itemFromIndex( parent ); else item = itemFromIndex( index( row, column > 0 ? column : 0, parent ) ); Q_ASSERT( item ); if ( !item ) return false; // tDebug() << "Dropping on:" << item->text() << row << column; return item->dropMimeData( data, action ); } Qt::DropActions SourcesModel::supportedDropActions() const { #ifdef Q_WS_MAC return Qt::CopyAction | Qt::MoveAction; #else return Qt::CopyAction; #endif } Qt::ItemFlags SourcesModel::flags( const QModelIndex& index ) const { if ( index.isValid() ) { return itemFromIndex( index )->flags(); } else return 0; } void SourcesModel::appendGroups() { beginInsertRows( QModelIndex(), rowCount(), rowCount() + 5 ); m_browse = new GroupItem( this, m_rootItem, tr( "Discover" ), 0 ); new HistoryItem( this, m_rootItem, tr( "Open Pages" ), 1 ); // new SourceTreeItem( this, m_rootItem, SourcesModel::Divider, 2 ); m_myMusicGroup = new GroupItem( this, m_rootItem, tr( "Your Music" ), 3 ); QueueItem* queue = new QueueItem( this, m_browse ); queue->setSortValue( 3 ); InboxItem* inbox = new InboxItem( this, m_browse ); inbox->setSortValue( 4 ); m_cloudGroup = new GroupItem( this, m_rootItem, tr( "Cloud Collections" ), 4 ); m_collectionsGroup = new GroupItem( this, m_rootItem, tr( "Friends" ), 5 ); endInsertRows(); QHash< QString, ViewPagePlugin* > plugins = Tomahawk::Utils::PluginLoader( "viewpage" ).loadPlugins< ViewPagePlugin >(); foreach ( ViewPagePlugin* plugin, plugins.values() ) { ViewManager::instance()->addDynamicPage( plugin ); } } void SourcesModel::appendPageItem( const QString& name, ViewPage* page, int sortValue ) { // If there should be no page item, there is nothing to do for us here. if ( !page->addPageItem() ) return; QModelIndex parentIndex = indexFromItem( m_browse ); beginInsertRows( parentIndex, rowCount( parentIndex ), rowCount( parentIndex ) ); GenericPageItem* pageItem = new GenericPageItem( this, m_browse, page->title(), page->pixmap(), boost::bind( &ViewManager::showDynamicPage, ViewManager::instance(), name ), boost::bind( &ViewManager::dynamicPageWidget, ViewManager::instance(), name ) ); pageItem->setDeletable( page->isDeletable() ); if ( sortValue ) { pageItem->setSortValue( sortValue ); } else { pageItem->setSortValue( rowCount( parentIndex ) ); } endInsertRows(); linkSourceItemToPage( pageItem, page ); } void SourcesModel::appendItem( const Tomahawk::source_ptr& source ) { GroupItem* parent; if ( !source.isNull() && source->isLocal() ) { parent = m_myMusicGroup; } else { parent = m_collectionsGroup; } QModelIndex idx = indexFromItem( parent ); beginInsertRows( idx, rowCount( idx ), rowCount( idx ) ); new SourceItem( this, parent, source ); endInsertRows(); parent->checkExpandedState(); } bool SourcesModel::removeItem( const Tomahawk::source_ptr& source ) { // qDebug() << "Removing source item from SourceTree:" << source->friendlyName(); QModelIndex idx; int rows = rowCount(); for ( int row = 0; row < rows; row++ ) { QModelIndex idx = index( row, 0, QModelIndex() ); SourceItem* item = static_cast< SourceItem* >( idx.internalPointer() ); if ( item && item->source() == source ) { // qDebug() << "Found removed source item:" << item->source()->userName(); beginRemoveRows( QModelIndex(), row, row ); m_rootItem->removeChild( item ); endRemoveRows(); // onItemOffline( idx ); delete item; return true; } } return false; } void SourcesModel::viewPageActivated( Tomahawk::ViewPage* page ) { if ( !m_sourcesWithViewPage.isEmpty() ) { // Hide again any offline sources we exposed, since we're showing a different page now. they'll be re-shown if the user selects a playlist that is from an offline user QList< source_ptr > temp = m_sourcesWithViewPage; m_sourcesWithViewPage.clear(); foreach ( const source_ptr& s, temp ) { QModelIndex idx = indexFromItem( m_sourcesWithViewPageItems.value( s ) ); emit dataChanged( idx, idx ); } m_sourcesWithViewPageItems.clear(); } if ( m_sourceTreeLinks.contains( page ) ) { Q_ASSERT( m_sourceTreeLinks[ page ] ); tDebug() << "Got view page activated for item:" << m_sourceTreeLinks[ page ]->text(); QPersistentModelIndex idx = indexFromItem( m_sourceTreeLinks[ page ] ); tDebug() << "Got view page activated for index:" << idx; if ( !idx.isValid() ) m_sourceTreeLinks.remove( page ); else emit selectRequest( idx ); } else { playlist_ptr p = ViewManager::instance()->playlistForPage( page ); // HACK // try to find it if it is a playlist. not pretty at all.... but this happens when ViewManager loads a playlist or dynplaylist NOT from the sidebar but from somewhere else // we don't know which sourcetreeitem is related to it, so we have to find it. we also don't know if this page is a playlist or dynplaylist or not, but we can't check as we can't // include DynamicWidget.h here (so can't dynamic_cast). // this could also be fixed by keeping a master list of playlists/sourcetreeitems... but that's even uglier i think. this is only called the first time a certain viewpage is clicked from external // sources. SourceTreeItem* item = activatePlaylistPage( page, m_rootItem ); m_viewPageDelayedCacheItem = page; if ( !p.isNull() ) { source_ptr s= p->author(); if ( !s.isNull() && !s->isOnline() && item ) { m_sourcesWithViewPage << s; // show the collection now... yeah. if ( !item->parent() || !item->parent()->parent() ) { tLog() << "Found playlist item with no category parent or collection parent!" << item->text(); return; } SourceTreeItem* collectionOfPlaylist = item->parent()->parent(); if ( !m_rootItem->children().contains( collectionOfPlaylist ) ) // verification to make sure we're not stranded { tLog() << "Got what we assumed to be a parent col of a playlist not as a child of our root node...:" << collectionOfPlaylist; return; } QModelIndex idx = indexFromItem( collectionOfPlaylist ); m_sourcesWithViewPageItems[ s ] = collectionOfPlaylist; tDebug() << "Emitting dataChanged for offline source:" << idx << idx.isValid() << collectionOfPlaylist << collectionOfPlaylist->text(); emit dataChanged( idx, idx ); } } } } SourceTreeItem* SourcesModel::activatePlaylistPage( ViewPage* p, SourceTreeItem* i ) { if ( !i ) return 0; if ( qobject_cast< PlaylistItem* >( i ) && qobject_cast< PlaylistItem* >( i )->activateCurrent() ) return i; SourceTreeItem* ret = 0; for ( int k = 0; k < i->children().size(); k++ ) { if ( SourceTreeItem* retItem = activatePlaylistPage( p, i->children().at( k ) ) ) ret = retItem; } return ret; } void SourcesModel::loadSources() { QList sources = SourceList::instance()->sources(); foreach ( const source_ptr& source, sources ) appendItem( source ); } void SourcesModel::onSourcesAdded( const QList& sources ) { foreach ( const source_ptr& source, sources ) appendItem( source ); } void SourcesModel::onSourceAdded( const source_ptr& source ) { appendItem( source ); } void SourcesModel::onSourceRemoved( const source_ptr& source ) { removeItem( source ); } void SourcesModel::onScriptCollectionAdded( const collection_ptr& collection ) { if ( m_scriptCollections.contains( collection ) ) return; QModelIndex parent = indexFromItem( m_cloudGroup ); beginInsertRows( parent, rowCount( parent ), rowCount( parent ) ); ScriptCollectionItem* item = new ScriptCollectionItem( this, m_cloudGroup, collection ); endInsertRows(); m_scriptCollections.insert( collection, item ); m_cloudGroup->checkExpandedState(); } void SourcesModel::onScriptCollectionRemoved( const collection_ptr& collection ) { SourceTreeItem* item = m_scriptCollections.value( collection ); int row = indexFromItem( item ).row(); QModelIndex parent = indexFromItem( m_cloudGroup ); beginRemoveRows( parent, row, row ); m_cloudGroup->removeChild( item ); endRemoveRows(); m_scriptCollectionPages.remove( collection ); m_scriptCollections.remove( collection ); item->deleteLater(); } void SourcesModel::onViewPageRemoved( Tomahawk::ViewPage* p ) { p->onItemDeleted(); } ViewPage* SourcesModel::scriptCollectionClicked( const Tomahawk::collection_ptr& collection ) { m_scriptCollectionPages.insert( collection, ViewManager::instance()->show( collection ) ); return m_scriptCollectionPages[ collection ]; } ViewPage* SourcesModel::getScriptCollectionPage( const Tomahawk::collection_ptr& collection ) const { return m_scriptCollectionPages[ collection ]; } void SourcesModel::itemUpdated() { Q_ASSERT( qobject_cast< SourceTreeItem* >( sender() ) ); SourceTreeItem* item = qobject_cast< SourceTreeItem* >( sender() ); if ( !item ) return; QModelIndex idx = indexFromItem( item ); if ( idx.isValid() ) emit dataChanged( idx, idx ); } void SourcesModel::onItemRowsAddedBegin( int first, int last ) { Q_ASSERT( qobject_cast< SourceTreeItem* >( sender() ) ); SourceTreeItem* item = qobject_cast< SourceTreeItem* >( sender() ); if ( !item ) return; QModelIndex idx = indexFromItem( item ); beginInsertRows( idx, first, last ); } void SourcesModel::onItemRowsAddedDone() { Q_ASSERT( qobject_cast< SourceTreeItem* >( sender() ) ); endInsertRows(); } void SourcesModel::onItemRowsRemovedBegin( int first, int last ) { Q_ASSERT( qobject_cast< SourceTreeItem* >( sender() ) ); SourceTreeItem* item = qobject_cast< SourceTreeItem* >( sender() ); if ( !item ) return; QModelIndex idx = indexFromItem( item ); beginRemoveRows( idx, first, last ); } void SourcesModel::onItemRowsRemovedDone() { Q_ASSERT( qobject_cast< SourceTreeItem* >( sender() ) ); endRemoveRows(); } void SourcesModel::linkSourceItemToPage( SourceTreeItem* item, ViewPage* p ) { // TODO handle removal m_sourceTreeLinks[ p ] = item; if ( p && m_viewPageDelayedCacheItem == p ) emit selectRequest( QPersistentModelIndex( indexFromItem( item ) ) ); if ( QObject* obj = dynamic_cast< QObject* >( p ) ) { if ( obj->metaObject()->indexOfSignal( "destroyed(QWidget*)" ) > -1 ) connect( obj, SIGNAL( destroyed( QWidget* ) ), SLOT( onWidgetDestroyed( QWidget* ) ), Qt::UniqueConnection ); } m_viewPageDelayedCacheItem = 0; if ( p && p->isDeletable() ) { NewClosure( item, SIGNAL( removed() ), this, SLOT( onViewPageRemoved( Tomahawk::ViewPage* ) ), p ); } } void SourcesModel::onWidgetDestroyed( QWidget* w ) { int ret = m_sourceTreeLinks.remove( dynamic_cast< Tomahawk::ViewPage* > ( w ) ); tDebug() << "Removed stale source page:" << ret; } void SourcesModel::removeSourceItemLink( SourceTreeItem* item ) { QList< ViewPage* > pages = m_sourceTreeLinks.keys( item ); foreach ( ViewPage* p, pages ) m_sourceTreeLinks.remove( p ); } SourceTreeItem* SourcesModel::itemFromIndex( const QModelIndex& idx ) const { if ( !idx.isValid() ) return m_rootItem; Q_ASSERT( idx.internalPointer() ); return reinterpret_cast< SourceTreeItem* >( idx.internalPointer() ); } QModelIndex SourcesModel::indexFromItem( SourceTreeItem* item ) const { if ( !item || !item->parent() ) // should never happen.. return QModelIndex(); // reconstructs a modelindex from a sourcetreeitem that is somewhere in the tree // traverses the item to the root node, then rebuilds the qmodeindices from there back down // each int is the row of that item in the parent. /** * In this diagram, if the \param item is G, childIndexList will contain [0, 2, 0] * * A * D * E * F * G * H * B * C * **/ QList< int > childIndexList; SourceTreeItem* curItem = item; while ( curItem != m_rootItem ) { int row = rowForItem( curItem ); if ( row < 0 ) // something went wrong, bail return QModelIndex(); childIndexList << row; curItem = curItem->parent(); } // qDebug() << "build child index list:" << childIndexList; // now rebuild the qmodelindex we need QModelIndex idx; for ( int i = childIndexList.size() - 1; i >= 0 ; i-- ) { idx = index( childIndexList[ i ], 0, idx ); } // qDebug() << "Got index from item:" << idx << idx.data( Qt::DisplayRole ).toString(); // qDebug() << "parent:" << idx.parent(); return idx; } int SourcesModel::rowForItem( SourceTreeItem* item ) const { if ( !item || !item->parent() || !item->parent()->children().contains( item ) ) return -1; return item->parent()->children().indexOf( item ); } void SourcesModel::itemSelectRequest( SourceTreeItem* item ) { emit selectRequest( QPersistentModelIndex( indexFromItem( item ) ) ); } void SourcesModel::itemExpandRequest( SourceTreeItem *item ) { emit expandRequest( QPersistentModelIndex( indexFromItem( item ) ) ); } void SourcesModel::itemToggleExpandRequest( SourceTreeItem *item ) { emit toggleExpandRequest( QPersistentModelIndex( indexFromItem( item ) ) ); } QList< source_ptr > SourcesModel::sourcesWithViewPage() const { return m_sourcesWithViewPage; } tomahawk-player/src/tomahawk/sourcetree/items/CollectionItem.cpp000664 001750 001750 00000003650 12661705042 026317 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2014, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "CollectionItem.h" #include "utils/ImageRegistry.h" #include "playlist/TrackView.h" #include "playlist/PlayableProxyModel.h" #include "ViewManager.h" #include "ViewPage.h" #include #include CollectionItem::CollectionItem( SourcesModel* model, SourceTreeItem* parent, const Tomahawk::collection_ptr& collection ) : SourceTreeItem( model, parent, SourcesModel::Collection ) , m_sortValue( -150 ) , m_collection( collection ) { m_text = tr( "Collection" ); m_icon = ImageRegistry::instance()->icon( RESPATH "images/collection.svg" ); } CollectionItem::~CollectionItem() {} QString CollectionItem::text() const { return m_text; } QIcon CollectionItem::icon() const { return m_icon; } int CollectionItem::peerSortValue() const { return m_sortValue; } void CollectionItem::setSortValue( int value ) { m_sortValue = value; } int CollectionItem::trackCount() const { return m_collection->trackCount(); } void CollectionItem::activate() { Tomahawk::ViewPage* page = ViewManager::instance()->show( m_collection ); model()->linkSourceItemToPage( this, page ); } tomahawk-player/src/tomahawk/dialogs/Settings_Accounts.ui000664 001750 001750 00000005044 12661705042 025017 0ustar00stefanstefan000000 000000 Settings_Accounts 0 0 553 439 Form 2 2 2 2 0 0 Qt::Horizontal 40 20 Filter by Capability: true QAbstractItemView::ScrollPerPixel QAbstractItemView::ScrollPerPixel QButton QWidget
thirdparty/Qocoa/qbutton.h
1
AccountListView QListView
widgets/AccountListView.h
tomahawk-player/src/libtomahawk/widgets/Breadcrumb.cpp000664 001750 001750 00000012355 12661705042 024311 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "Breadcrumb.h" #include "BreadcrumbButton.h" #include "utils/TomahawkStyle.h" #include "utils/TomahawkUtilsGui.h" #include "utils/Logger.h" #include #include #include #include #include using namespace Tomahawk; Breadcrumb::Breadcrumb( QWidget* parent, Qt::WindowFlags f ) : QWidget( parent, f ) , m_model( 0 ) , m_buttonlayout( new QHBoxLayout( this ) ) { TomahawkUtils::unmarginLayout( m_buttonlayout ); m_buttonlayout->setAlignment( Qt::AlignLeft ); setAutoFillBackground( true ); setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); setLayoutDirection( Qt::LeftToRight ); setLayout( m_buttonlayout ); show(); } Breadcrumb::~Breadcrumb() { } void Breadcrumb::setModel( QAbstractItemModel* model ) { foreach ( BreadcrumbButton* b, m_buttons ) b->deleteLater();; m_buttons.clear(); m_model = model; updateButtons( QModelIndex() ); } void Breadcrumb::setRootIcon( const QPixmap& pm ) { m_rootIcon = TomahawkUtils::tinted( pm, Qt::white ); QPushButton* button = new QPushButton( QIcon( m_rootIcon ), "", this ); button->setFlat( true ); button->setStyleSheet( "QPushButton{ background-color: transparent; border: none; width:36px; height:36px; }" ); m_buttonlayout->insertWidget( 0, button ); m_buttonlayout->insertSpacing( 0, 5 ); m_buttonlayout->insertSpacing( 2, 5 ); } void Breadcrumb::paintEvent( QPaintEvent* ) { QStylePainter p( this ); TomahawkStyle::horizontalHeader( &p, rect() ); } // updateFrom is the item that has changed---all children must be recomputed // if invalid, redo the whole breadcrumb void Breadcrumb::updateButtons( const QModelIndex& updateFrom ) { // qDebug() << "Updating buttons:" << updateFrom.data(); int cur = 0; QModelIndex idx = updateFrom; for ( int i = 0; i < m_buttons.count(); i++ ) { // qDebug() << "Checking if this breadcrumb item changed:" << m_buttons[ i ]->currentIndex().data() << updateFrom.data() << ( m_buttons[ i ]->currentIndex() != updateFrom); if ( m_buttons[ i ]->currentIndex() == updateFrom ) { cur = i; break; } } // We set the parent index, so go up one idx = idx.parent(); // Ok, changed all indices that are at cur or past it. lets update them // When we get to the "end" of the tree, the leaf node is the chart itself // qDebug() << "DONE and beginning iteration:" << idx.data(); while ( m_model->rowCount( idx ) > 0 ) { // qDebug() << "CHANGED AND iterating:" << idx.data(); BreadcrumbButton* btn = 0; if ( m_buttons.size() <= cur ) { // We have to create a new button, doesn't exist yet btn = new BreadcrumbButton( this, m_model ); connect( btn, SIGNAL( currentIndexChanged( QModelIndex ) ), this, SLOT( breadcrumbComboChanged( QModelIndex ) ) ); m_buttonlayout->addWidget( btn ); btn->show(); // Animate all buttons except the first if ( m_buttons.count() > 0 && isVisible() ) { QPropertyAnimation* animation = new QPropertyAnimation( btn, "pos" ); animation->setDuration( 300 ); animation->setStartValue( m_buttons.last()->pos() ); animation->setEndValue( btn->pos() ); animation->start( QAbstractAnimation::DeleteWhenStopped ); } m_buttons.append( btn ); } else { // Got a button already, we just want to change the contents btn = m_buttons[ cur ]; } // The children of idx are what populates this combobox. // It takes care of setting the default/user-populated value. btn->setParentIndex( idx ); // Repeat with children idx = btn->currentIndex(); cur++; } // extra buttons to delete! (cur is 0-indexed) while ( m_buttons.size() > cur ) { BreadcrumbButton* b = m_buttons.takeLast(); m_buttonlayout->removeWidget( b ); b->deleteLater(); } // Now we're at the leaf, lets activate the chart emit activateIndex( idx ); } void Breadcrumb::breadcrumbComboChanged( const QModelIndex& childSelected ) { // Some breadcrumb buttons' combobox changed. lets update the child breadcrumbs tDebug() << "Combo changed:" << childSelected.data(); updateButtons( childSelected ); } tomahawk-player/src/libtomahawk/database/DatabaseCommand_CreateDynamicPlaylist.h000664 001750 001750 00000004405 12661705042 031320 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef DATABASECOMMAND_CREATEDYNAMICPLAYLIST_H #define DATABASECOMMAND_CREATEDYNAMICPLAYLIST_H #include "Typedefs.h" #include "DatabaseCommand_CreatePlaylist.h" namespace Tomahawk { /** * Create a new dynamic playlist in the database, based on an existing playlist. * * If autoLoad is true, this playlist will *not* show up in the sidebar under the playlist tree, and * it will *not* be replicated to peers. It is useful to show a "specially crafted" playlist in other places */ class DatabaseCommand_CreateDynamicPlaylist : public DatabaseCommand_CreatePlaylist { Q_OBJECT Q_PROPERTY( QVariant playlist READ playlistV WRITE setPlaylistV ) public: explicit DatabaseCommand_CreateDynamicPlaylist( QObject* parent = 0 ); explicit DatabaseCommand_CreateDynamicPlaylist( const Tomahawk::source_ptr& author, const Tomahawk::dynplaylist_ptr& playlist, bool autoLoad = true ); virtual ~DatabaseCommand_CreateDynamicPlaylist(); QString commandname() const { return "createdynamicplaylist"; } virtual void exec( DatabaseImpl* lib ); virtual void postCommitHook(); virtual bool doesMutates() const { return true; } virtual bool loggable() const { return m_autoLoad; } QVariant playlistV() const; void setPlaylistV( const QVariant& v ) { m_v = v; } protected: virtual bool report() { return m_autoLoad; } private: Tomahawk::dynplaylist_ptr m_playlist; bool m_autoLoad; }; } #endif // DATABASECOMMAND_CREATEDYNAMICPLAYLIST_H tomahawk-player/thirdparty/libportfwd/third-party/miniupnpc-1.6/miniupnpc.c000664 001750 001750 00000060237 12661705042 030271 0ustar00stefanstefan000000 000000 /* $Id: miniupnpc.c,v 1.95 2011/05/15 21:42:26 nanard Exp $ */ /* Project : miniupnp * Author : Thomas BERNARD * copyright (c) 2005-2011 Thomas Bernard * This software is subjet to the conditions detailed in the * provided LICENSE file. */ #define __EXTENSIONS__ 1 #if !defined(MACOSX) && !defined(__sun) #if !defined(_XOPEN_SOURCE) && !defined(__OpenBSD__) && !defined(__NetBSD__) #ifndef __cplusplus #define _XOPEN_SOURCE 600 #endif #endif #ifndef __BSD_VISIBLE #define __BSD_VISIBLE 1 #endif #endif #include #include #include #ifdef WIN32 /* Win32 Specific includes and defines */ #include #include #include #include #define snprintf _snprintf #ifndef strncasecmp #if defined(_MSC_VER) && (_MSC_VER >= 1400) #define strncasecmp _memicmp #else /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ #define strncasecmp memicmp #endif /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ #endif /* #ifndef strncasecmp */ #define MAXHOSTNAMELEN 64 #else /* #ifdef WIN32 */ /* Standard POSIX includes */ #include #if defined(__amigaos__) && !defined(__amigaos4__) /* Amiga OS 3 specific stuff */ #define socklen_t int #else #include #endif #include #include #include #include #include #include #include #if !defined(__amigaos__) && !defined(__amigaos4__) #include #endif #include #include #define closesocket close #endif /* #else WIN32 */ #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT #include #endif #if defined(__amigaos__) || defined(__amigaos4__) /* Amiga OS specific stuff */ #define TIMEVAL struct timeval #endif #include "miniupnpc.h" #include "minissdpc.h" #include "miniwget.h" #include "minisoap.h" #include "minixml.h" #include "upnpcommands.h" #include "connecthostport.h" #include "receivedata.h" #ifdef WIN32 #define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); #else #define PRINT_SOCKET_ERROR(x) perror(x) #endif #define SOAPPREFIX "s" #define SERVICEPREFIX "u" #define SERVICEPREFIX2 'u' /* root description parsing */ LIBSPEC void parserootdesc(const char * buffer, int bufsize, struct IGDdatas * data) { struct xmlparser parser; /* xmlparser object */ parser.xmlstart = buffer; parser.xmlsize = bufsize; parser.data = data; parser.starteltfunc = IGDstartelt; parser.endeltfunc = IGDendelt; parser.datafunc = IGDdata; parser.attfunc = 0; parsexml(&parser); #ifdef DEBUG printIGD(data); #endif } /* simpleUPnPcommand2 : * not so simple ! * return values : * pointer - OK * NULL - error */ char * simpleUPnPcommand2(int s, const char * url, const char * service, const char * action, struct UPNParg * args, int * bufsize, const char * httpversion) { char hostname[MAXHOSTNAMELEN+1]; unsigned short port = 0; char * path; char soapact[128]; char soapbody[2048]; char * buf; int n; *bufsize = 0; snprintf(soapact, sizeof(soapact), "%s#%s", service, action); if(args==NULL) { /*soapbodylen = */snprintf(soapbody, sizeof(soapbody), "\r\n" "<" SOAPPREFIX ":Envelope " "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" " SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" "<" SOAPPREFIX ":Body>" "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">" "" "" "\r\n", action, service, action); } else { char * p; const char * pe, * pv; int soapbodylen; soapbodylen = snprintf(soapbody, sizeof(soapbody), "\r\n" "<" SOAPPREFIX ":Envelope " "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" " SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" "<" SOAPPREFIX ":Body>" "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">", action, service); p = soapbody + soapbodylen; while(args->elt) { /* check that we are never overflowing the string... */ if(soapbody + sizeof(soapbody) <= p + 100) { /* we keep a margin of at least 100 bytes */ return NULL; } *(p++) = '<'; pe = args->elt; while(*pe) *(p++) = *(pe++); *(p++) = '>'; if((pv = args->val)) { while(*pv) *(p++) = *(pv++); } *(p++) = '<'; *(p++) = '/'; pe = args->elt; while(*pe) *(p++) = *(pe++); *(p++) = '>'; args++; } *(p++) = '<'; *(p++) = '/'; *(p++) = SERVICEPREFIX2; *(p++) = ':'; pe = action; while(*pe) *(p++) = *(pe++); strncpy(p, ">\r\n", soapbody + sizeof(soapbody) - p); } if(!parseURL(url, hostname, &port, &path)) return NULL; if(s<0) { s = connecthostport(hostname, port); if(s < 0) { return NULL; } } n = soapPostSubmit(s, path, hostname, port, soapact, soapbody, httpversion); if(n<=0) { #ifdef DEBUG printf("Error sending SOAP request\n"); #endif closesocket(s); return NULL; } buf = getHTTPResponse(s, bufsize); #ifdef DEBUG if(*bufsize > 0 && buf) { printf("SOAP Response :\n%.*s\n", *bufsize, buf); } #endif closesocket(s); return buf; } /* simpleUPnPcommand : * not so simple ! * return values : * pointer - OK * NULL - error */ char * simpleUPnPcommand(int s, const char * url, const char * service, const char * action, struct UPNParg * args, int * bufsize) { char * buf; buf = simpleUPnPcommand2(s, url, service, action, args, bufsize, "1.1"); /* buf = simpleUPnPcommand2(s, url, service, action, args, bufsize, "1.0"); if (!buf || *bufsize == 0) { #if DEBUG printf("Error or no result from SOAP request; retrying with HTTP/1.1\n"); #endif buf = simpleUPnPcommand2(s, url, service, action, args, bufsize, "1.1"); } */ return buf; } /* parseMSEARCHReply() * the last 4 arguments are filled during the parsing : * - location/locationsize : "location:" field of the SSDP reply packet * - st/stsize : "st:" field of the SSDP reply packet. * The strings are NOT null terminated */ static void parseMSEARCHReply(const char * reply, int size, const char * * location, int * locationsize, const char * * st, int * stsize) { int a, b, i; i = 0; a = i; /* start of the line */ b = 0; /* end of the "header" (position of the colon) */ while(isin6_family = AF_INET6; if(sameport) p->sin6_port = htons(PORT); p->sin6_addr = in6addr_any; /* in6addr_any is not available with MinGW32 3.4.2 */ } else { struct sockaddr_in * p = (struct sockaddr_in *)&sockudp_r; p->sin_family = AF_INET; if(sameport) p->sin_port = htons(PORT); p->sin_addr.s_addr = INADDR_ANY; } #ifdef WIN32 /* This code could help us to use the right Network interface for * SSDP multicast traffic */ /* Get IP associated with the index given in the ip_forward struct * in order to give this ip to setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF) */ if(!ipv6 && (GetBestRoute(inet_addr("223.255.255.255"), 0, &ip_forward) == NO_ERROR)) { DWORD dwRetVal = 0; PMIB_IPADDRTABLE pIPAddrTable; DWORD dwSize = 0; #ifdef DEBUG IN_ADDR IPAddr; #endif int i; #ifdef DEBUG printf("ifIndex=%lu nextHop=%lx \n", ip_forward.dwForwardIfIndex, ip_forward.dwForwardNextHop); #endif pIPAddrTable = (MIB_IPADDRTABLE *) malloc(sizeof (MIB_IPADDRTABLE)); if (GetIpAddrTable(pIPAddrTable, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER) { free(pIPAddrTable); pIPAddrTable = (MIB_IPADDRTABLE *) malloc(dwSize); } if(pIPAddrTable) { dwRetVal = GetIpAddrTable( pIPAddrTable, &dwSize, 0 ); #ifdef DEBUG printf("\tNum Entries: %ld\n", pIPAddrTable->dwNumEntries); #endif for (i=0; i < (int) pIPAddrTable->dwNumEntries; i++) { #ifdef DEBUG printf("\n\tInterface Index[%d]:\t%ld\n", i, pIPAddrTable->table[i].dwIndex); IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwAddr; printf("\tIP Address[%d]: \t%s\n", i, inet_ntoa(IPAddr) ); IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwMask; printf("\tSubnet Mask[%d]: \t%s\n", i, inet_ntoa(IPAddr) ); IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwBCastAddr; printf("\tBroadCast[%d]: \t%s (%ld)\n", i, inet_ntoa(IPAddr), pIPAddrTable->table[i].dwBCastAddr); printf("\tReassembly size[%d]:\t%ld\n", i, pIPAddrTable->table[i].dwReasmSize); printf("\tType and State[%d]:", i); printf("\n"); #endif if (pIPAddrTable->table[i].dwIndex == ip_forward.dwForwardIfIndex) { /* Set the address of this interface to be used */ struct in_addr mc_if; memset(&mc_if, 0, sizeof(mc_if)); mc_if.s_addr = pIPAddrTable->table[i].dwAddr; if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) { PRINT_SOCKET_ERROR("setsockopt"); } ((struct sockaddr_in *)&sockudp_r)->sin_addr.s_addr = pIPAddrTable->table[i].dwAddr; #ifndef DEBUG break; #endif } } free(pIPAddrTable); pIPAddrTable = NULL; } } #endif #ifdef WIN32 if (setsockopt(sudp, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof (opt)) < 0) #else if (setsockopt(sudp, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)) < 0) #endif { if(error) *error = UPNPDISCOVER_SOCKET_ERROR; PRINT_SOCKET_ERROR("setsockopt"); return NULL; } if(multicastif) { if(ipv6) { #if !defined(WIN32) /* according to MSDN, if_nametoindex() is supported since * MS Windows Vista and MS Windows Server 2008. * http://msdn.microsoft.com/en-us/library/bb408409%28v=vs.85%29.aspx */ unsigned int ifindex = if_nametoindex(multicastif); /* eth0, etc. */ if(setsockopt(sudp, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, sizeof(&ifindex)) < 0) { PRINT_SOCKET_ERROR("setsockopt"); } #else #ifdef DEBUG printf("Setting of multicast interface not supported in IPv6 under Windows.\n"); #endif #endif } else { struct in_addr mc_if; mc_if.s_addr = inet_addr(multicastif); /* ex: 192.168.x.x */ ((struct sockaddr_in *)&sockudp_r)->sin_addr.s_addr = mc_if.s_addr; if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) { PRINT_SOCKET_ERROR("setsockopt"); } } } /* Avant d'envoyer le paquet on bind pour recevoir la reponse */ if (bind(sudp, (const struct sockaddr *)&sockudp_r, ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) != 0) { if(error) *error = UPNPDISCOVER_SOCKET_ERROR; PRINT_SOCKET_ERROR("bind"); closesocket(sudp); return NULL; } if(error) *error = UPNPDISCOVER_SUCCESS; /* Calculating maximum response time in seconds */ mx = ((unsigned int)delay) / 1000u; /* receiving SSDP response packet */ for(n = 0; deviceList[deviceIndex]; deviceIndex++) { if(n == 0) { /* sending the SSDP M-SEARCH packet */ n = snprintf(bufr, sizeof(bufr), MSearchMsgFmt, ipv6 ? (linklocal ? "[" UPNP_MCAST_LL_ADDR "]" : "[" UPNP_MCAST_SL_ADDR "]") : UPNP_MCAST_ADDR, deviceList[deviceIndex], mx); #ifdef DEBUG printf("Sending %s", bufr); #endif #ifdef NO_GETADDRINFO /* the following code is not using getaddrinfo */ /* emission */ memset(&sockudp_w, 0, sizeof(struct sockaddr_storage)); if(ipv6) { struct sockaddr_in6 * p = (struct sockaddr_in6 *)&sockudp_w; p->sin6_family = AF_INET6; p->sin6_port = htons(PORT); inet_pton(AF_INET6, linklocal ? UPNP_MCAST_LL_ADDR : UPNP_MCAST_SL_ADDR, &(p->sin6_addr)); } else { struct sockaddr_in * p = (struct sockaddr_in *)&sockudp_w; p->sin_family = AF_INET; p->sin_port = htons(PORT); p->sin_addr.s_addr = inet_addr(UPNP_MCAST_ADDR); } n = sendto(sudp, bufr, n, 0, &sockudp_w, ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); if (n < 0) { if(error) *error = UPNPDISCOVER_SOCKET_ERROR; PRINT_SOCKET_ERROR("sendto"); break; } #else /* #ifdef NO_GETADDRINFO */ memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; // AF_INET6 or AF_INET hints.ai_socktype = SOCK_DGRAM; /*hints.ai_flags = */ if ((rv = getaddrinfo(ipv6 ? (linklocal ? UPNP_MCAST_LL_ADDR : UPNP_MCAST_SL_ADDR) : UPNP_MCAST_ADDR, XSTR(PORT), &hints, &servinfo)) != 0) { if(error) *error = UPNPDISCOVER_SOCKET_ERROR; #ifdef WIN32 fprintf(stderr, "getaddrinfo() failed: %d\n", rv); #else fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); #endif break; } for(p = servinfo; p; p = p->ai_next) { n = sendto(sudp, bufr, n, 0, p->ai_addr, p->ai_addrlen); if (n < 0) { PRINT_SOCKET_ERROR("sendto"); continue; } } freeaddrinfo(servinfo); if(n < 0) { if(error) *error = UPNPDISCOVER_SOCKET_ERROR; break; } #endif /* #ifdef NO_GETADDRINFO */ } /* Waiting for SSDP REPLY packet to M-SEARCH */ n = receivedata(sudp, bufr, sizeof(bufr), delay); if (n < 0) { /* error */ if(error) *error = UPNPDISCOVER_SOCKET_ERROR; break; } else if (n == 0) { /* no data or Time Out */ if (devlist) { /* no more device type to look for... */ if(error) *error = UPNPDISCOVER_SUCCESS; break; } if(ipv6) { if(linklocal) { linklocal = 0; --deviceIndex; } else { linklocal = 1; } } } else { const char * descURL=NULL; int urlsize=0; const char * st=NULL; int stsize=0; /*printf("%d byte(s) :\n%s\n", n, bufr);*/ /* affichage du message */ parseMSEARCHReply(bufr, n, &descURL, &urlsize, &st, &stsize); if(st&&descURL) { #ifdef DEBUG printf("M-SEARCH Reply:\nST: %.*s\nLocation: %.*s\n", stsize, st, urlsize, descURL); #endif for(tmp=devlist; tmp; tmp = tmp->pNext) { if(memcmp(tmp->descURL, descURL, urlsize) == 0 && tmp->descURL[urlsize] == '\0' && memcmp(tmp->st, st, stsize) == 0 && tmp->st[stsize] == '\0') break; } /* at the exit of the loop above, tmp is null if * no duplicate device was found */ if(tmp) continue; tmp = (struct UPNPDev *)malloc(sizeof(struct UPNPDev)+urlsize+stsize); if(!tmp) { /* memory allocation error */ if(error) *error = UPNPDISCOVER_MEMORY_ERROR; break; } tmp->pNext = devlist; tmp->descURL = tmp->buffer; tmp->st = tmp->buffer + 1 + urlsize; memcpy(tmp->buffer, descURL, urlsize); tmp->buffer[urlsize] = '\0'; memcpy(tmp->buffer + urlsize + 1, st, stsize); tmp->buffer[urlsize+1+stsize] = '\0'; devlist = tmp; } } } closesocket(sudp); return devlist; } /* freeUPNPDevlist() should be used to * free the chained list returned by upnpDiscover() */ LIBSPEC void freeUPNPDevlist(struct UPNPDev * devlist) { struct UPNPDev * next; while(devlist) { next = devlist->pNext; free(devlist); devlist = next; } } static void url_cpy_or_cat(char * dst, const char * src, int n) { if( (src[0] == 'h') &&(src[1] == 't') &&(src[2] == 't') &&(src[3] == 'p') &&(src[4] == ':') &&(src[5] == '/') &&(src[6] == '/')) { strncpy(dst, src, n); } else { int l = strlen(dst); if(src[0] != '/') dst[l++] = '/'; if(l<=n) strncpy(dst + l, src, n - l); } } /* Prepare the Urls for usage... */ LIBSPEC void GetUPNPUrls(struct UPNPUrls * urls, struct IGDdatas * data, const char * descURL) { char * p; int n1, n2, n3, n4; n1 = strlen(data->urlbase); if(n1==0) n1 = strlen(descURL); n1 += 2; /* 1 byte more for Null terminator, 1 byte for '/' if needed */ n2 = n1; n3 = n1; n4 = n1; n1 += strlen(data->first.scpdurl); n2 += strlen(data->first.controlurl); n3 += strlen(data->CIF.controlurl); n4 += strlen(data->IPv6FC.controlurl); urls->ipcondescURL = (char *)malloc(n1); urls->controlURL = (char *)malloc(n2); urls->controlURL_CIF = (char *)malloc(n3); urls->controlURL_6FC = (char *)malloc(n4); /* maintenant on chope la desc du WANIPConnection */ if(data->urlbase[0] != '\0') strncpy(urls->ipcondescURL, data->urlbase, n1); else strncpy(urls->ipcondescURL, descURL, n1); p = strchr(urls->ipcondescURL+7, '/'); if(p) p[0] = '\0'; strncpy(urls->controlURL, urls->ipcondescURL, n2); strncpy(urls->controlURL_CIF, urls->ipcondescURL, n3); strncpy(urls->controlURL_6FC, urls->ipcondescURL, n4); url_cpy_or_cat(urls->ipcondescURL, data->first.scpdurl, n1); url_cpy_or_cat(urls->controlURL, data->first.controlurl, n2); url_cpy_or_cat(urls->controlURL_CIF, data->CIF.controlurl, n3); url_cpy_or_cat(urls->controlURL_6FC, data->IPv6FC.controlurl, n4); #ifdef DEBUG printf("urls->ipcondescURL='%s' %u n1=%d\n", urls->ipcondescURL, (unsigned)strlen(urls->ipcondescURL), n1); printf("urls->controlURL='%s' %u n2=%d\n", urls->controlURL, (unsigned)strlen(urls->controlURL), n2); printf("urls->controlURL_CIF='%s' %u n3=%d\n", urls->controlURL_CIF, (unsigned)strlen(urls->controlURL_CIF), n3); printf("urls->controlURL_6FC='%s' %u n4=%d\n", urls->controlURL_6FC, (unsigned)strlen(urls->controlURL_6FC), n4); #endif } LIBSPEC void FreeUPNPUrls(struct UPNPUrls * urls) { if(!urls) return; free(urls->controlURL); urls->controlURL = 0; free(urls->ipcondescURL); urls->ipcondescURL = 0; free(urls->controlURL_CIF); urls->controlURL_CIF = 0; free(urls->controlURL_6FC); urls->controlURL_6FC = 0; } int UPNPIGD_IsConnected(struct UPNPUrls * urls, struct IGDdatas * data) { char status[64]; unsigned int uptime; status[0] = '\0'; UPNP_GetStatusInfo(urls->controlURL, data->first.servicetype, status, &uptime, NULL); if(0 == strcmp("Connected", status)) { return 1; } else return 0; } /* UPNP_GetValidIGD() : * return values : * 0 = NO IGD found * 1 = A valid connected IGD has been found * 2 = A valid IGD has been found but it reported as * not connected * 3 = an UPnP device has been found but was not recognized as an IGD * * In any non zero return case, the urls and data structures * passed as parameters are set. Donc forget to call FreeUPNPUrls(urls) to * free allocated memory. */ LIBSPEC int UPNP_GetValidIGD(struct UPNPDev * devlist, struct UPNPUrls * urls, struct IGDdatas * data, char * lanaddr, int lanaddrlen) { char * descXML; int descXMLsize = 0; struct UPNPDev * dev; int ndev = 0; int state; /* state 1 : IGD connected. State 2 : IGD. State 3 : anything */ if(!devlist) { #ifdef DEBUG printf("Empty devlist\n"); #endif return 0; } for(state = 1; state <= 3; state++) { for(dev = devlist; dev; dev = dev->pNext) { /* we should choose an internet gateway device. * with st == urn:schemas-upnp-org:device:InternetGatewayDevice:1 */ descXML = miniwget_getaddr(dev->descURL, &descXMLsize, lanaddr, lanaddrlen); if(descXML) { ndev++; memset(data, 0, sizeof(struct IGDdatas)); memset(urls, 0, sizeof(struct UPNPUrls)); parserootdesc(descXML, descXMLsize, data); free(descXML); descXML = NULL; if(0==strcmp(data->CIF.servicetype, "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1") || state >= 3 ) { GetUPNPUrls(urls, data, dev->descURL); #ifdef DEBUG printf("UPNPIGD_IsConnected(%s) = %d\n", urls->controlURL, UPNPIGD_IsConnected(urls, data)); #endif if((state >= 2) || UPNPIGD_IsConnected(urls, data)) return state; FreeUPNPUrls(urls); if(data->second.servicetype[0] != '\0') { #ifdef DEBUG printf("We tried %s, now we try %s !\n", data->first.servicetype, data->second.servicetype); #endif /* swaping WANPPPConnection and WANIPConnection ! */ memcpy(&data->tmp, &data->first, sizeof(struct IGDdatas_service)); memcpy(&data->first, &data->second, sizeof(struct IGDdatas_service)); memcpy(&data->second, &data->tmp, sizeof(struct IGDdatas_service)); GetUPNPUrls(urls, data, dev->descURL); #ifdef DEBUG printf("UPNPIGD_IsConnected(%s) = %d\n", urls->controlURL, UPNPIGD_IsConnected(urls, data)); #endif if((state >= 2) || UPNPIGD_IsConnected(urls, data)) return state; FreeUPNPUrls(urls); } } memset(data, 0, sizeof(struct IGDdatas)); } #ifdef DEBUG else { printf("error getting XML description %s\n", dev->descURL); } #endif } } return 0; } /* UPNP_GetIGDFromUrl() * Used when skipping the discovery process. * return value : * 0 - Not ok * 1 - OK */ int UPNP_GetIGDFromUrl(const char * rootdescurl, struct UPNPUrls * urls, struct IGDdatas * data, char * lanaddr, int lanaddrlen) { char * descXML; int descXMLsize = 0; descXML = miniwget_getaddr(rootdescurl, &descXMLsize, lanaddr, lanaddrlen); if(descXML) { memset(data, 0, sizeof(struct IGDdatas)); memset(urls, 0, sizeof(struct UPNPUrls)); parserootdesc(descXML, descXMLsize, data); free(descXML); descXML = NULL; GetUPNPUrls(urls, data, rootdescurl); return 1; } else { return 0; } } tomahawk-player/src/libtomahawk/database/DatabaseCommand_TrackStats.cpp000664 001750 001750 00000007071 12661705042 027506 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2012, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "DatabaseCommand_TrackStats.h" #include "utils/Logger.h" #include "Artist.h" #include "DatabaseImpl.h" #include "PlaylistEntry.h" #include "SourceList.h" // Forward Declarations breaking QSharedPointer #if QT_VERSION < QT_VERSION_CHECK( 5, 0, 0 ) #include "collection/Collection.h" #endif using namespace Tomahawk; DatabaseCommand_TrackStats::DatabaseCommand_TrackStats( const trackdata_ptr& track, QObject* parent ) : DatabaseCommand( parent ) , m_track( track ) { } DatabaseCommand_TrackStats::DatabaseCommand_TrackStats( const artist_ptr& artist, QObject* parent ) : DatabaseCommand( parent ) , m_artist( artist ) { } void DatabaseCommand_TrackStats::exec( DatabaseImpl* dbi ) { TomahawkSqlQuery query = dbi->newquery(); if ( m_track ) { if ( m_track->trackId() == 0 ) return; query.prepare( "SELECT COUNT(*) AS counter, track.id " "FROM playback_log, track " "WHERE playback_log.source IS NULL AND track.id = playback_log.track " "GROUP BY track.id " "ORDER BY counter DESC" ); query.exec(); unsigned int chartPos = 0; unsigned int chartCount = 0; const unsigned int trackId = m_track->trackId(); QHash< QString, unsigned int > charts; while ( query.next() ) { if ( query.value( 0 ).toUInt() < 2 ) break; chartCount++; if ( chartPos == 0 && query.value( 1 ).toUInt() == trackId ) { chartPos = chartCount; } } if ( chartPos == 0 ) chartPos = chartCount; emit trackStats( chartPos, chartCount ); query.prepare( "SELECT * " "FROM playback_log " "WHERE track = ? ORDER BY playtime ASC" ); query.addBindValue( m_track->trackId() ); query.exec(); } else if ( m_artist ) { query.prepare( "SELECT playback_log.* " "FROM playback_log, track " "WHERE playback_log.track = track.id AND track.artist = ?" ); query.addBindValue( m_artist->id() ); query.exec(); } QList< Tomahawk::PlaybackLog > playbackData; while ( query.next() ) { Tomahawk::PlaybackLog log; log.source = SourceList::instance()->get( query.value( 1 ).toInt() ); // source log.timestamp = query.value( 3 ).toUInt(); log.secsPlayed = query.value( 4 ).toUInt(); if ( log.source ) playbackData.append( log ); } if ( m_track ) m_track->setPlaybackHistory( playbackData ); else m_artist->setPlaybackHistory( playbackData ); emit done( playbackData ); } tomahawk-player/src/infoplugins/mac/adium/000775 001750 001750 00000000000 12661705042 021755 5ustar00stefanstefan000000 000000 tomahawk-player/data/sql/000775 001750 001750 00000000000 12661705042 016502 5ustar00stefanstefan000000 000000 tomahawk-player/src/infoplugins/generic/rovi/RoviPlugin.h000664 001750 001750 00000003703 12661705042 024763 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Leo Franchi * Copyright 2010-2011, Jeff Mitchell * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef ROVIPLUGIN_H #define ROVIPLUGIN_H #include "infosystem/InfoSystem.h" #include "../../InfoPluginDllMacro.h" #include class QNetworkAccessManager; namespace Tomahawk { namespace InfoSystem { class INFOPLUGINDLLEXPORT RoviPlugin : public InfoPlugin { Q_PLUGIN_METADATA( IID "org.tomahawk-player.Player.InfoPlugin" ) Q_OBJECT Q_INTERFACES( Tomahawk::InfoSystem::InfoPlugin ) public: RoviPlugin(); virtual ~RoviPlugin(); protected: virtual void init() {} virtual void notInCacheSlot( Tomahawk::InfoSystem::InfoStringHash criteria, Tomahawk::InfoSystem::InfoRequestData requestData ); virtual void pushInfo( Tomahawk::InfoSystem::InfoPushData pushData ) { Q_UNUSED( pushData ); } virtual void getInfo( Tomahawk::InfoSystem::InfoRequestData requestData ); private slots: void albumLookupFinished(); void albumLookupError( QNetworkReply::NetworkError ); private: QNetworkReply* makeRequest( QUrl url ); QByteArray generateSig() const; QByteArray m_apiKey; QByteArray m_secret; }; } } #endif // ROVIPLUGIN_H tomahawk-player/src/libtomahawk/collection/ArtistsRequest.cpp000664 001750 001750 00000001545 12661705042 025731 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2013, Teo Mrnjavac * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "ArtistsRequest.h" Tomahawk::ArtistsRequest::~ArtistsRequest() {} tomahawk-player/thirdparty/libportfwd/third-party/miniupnpc-1.6/miniupnpc.h000664 001750 001750 00000007123 12661705042 030271 0ustar00stefanstefan000000 000000 /* $Id: miniupnpc.h,v 1.23 2011/04/11 08:21:46 nanard Exp $ */ /* Project: miniupnp * http://miniupnp.free.fr/ * Author: Thomas Bernard * Copyright (c) 2005-2011 Thomas Bernard * This software is subjects to the conditions detailed * in the LICENCE file provided within this distribution */ #ifndef __MINIUPNPC_H__ #define __MINIUPNPC_H__ #include "declspec.h" #include "igd_desc_parse.h" /* error codes : */ #define UPNPDISCOVER_SUCCESS (0) #define UPNPDISCOVER_UNKNOWN_ERROR (-1) #define UPNPDISCOVER_SOCKET_ERROR (-101) #define UPNPDISCOVER_MEMORY_ERROR (-102) #ifdef __cplusplus extern "C" { #endif /* Structures definitions : */ struct UPNParg { const char * elt; const char * val; }; char * simpleUPnPcommand(int, const char *, const char *, const char *, struct UPNParg *, int *); struct UPNPDev { struct UPNPDev * pNext; char * descURL; char * st; char buffer[2]; }; /* upnpDiscover() * discover UPnP devices on the network. * The discovered devices are returned as a chained list. * It is up to the caller to free the list with freeUPNPDevlist(). * delay (in millisecond) is the maximum time for waiting any device * response. * If available, device list will be obtained from MiniSSDPd. * Default path for minissdpd socket will be used if minissdpdsock argument * is NULL. * If multicastif is not NULL, it will be used instead of the default * multicast interface for sending SSDP discover packets. * If sameport is not null, SSDP packets will be sent from the source port * 1900 (same as destination port) otherwise system assign a source port. */ LIBSPEC struct UPNPDev * upnpDiscover(int delay, const char * multicastif, const char * minissdpdsock, int sameport, int ipv6, int * error); /* freeUPNPDevlist() * free list returned by upnpDiscover() */ LIBSPEC void freeUPNPDevlist(struct UPNPDev * devlist); /* parserootdesc() : * parse root XML description of a UPnP device and fill the IGDdatas * structure. */ LIBSPEC void parserootdesc(const char *, int, struct IGDdatas *); /* structure used to get fast access to urls * controlURL: controlURL of the WANIPConnection * ipcondescURL: url of the description of the WANIPConnection * controlURL_CIF: controlURL of the WANCommonInterfaceConfig * controlURL_6FC: controlURL of the WANIPv6FirewallControl */ struct UPNPUrls { char * controlURL; char * ipcondescURL; char * controlURL_CIF; char * controlURL_6FC; }; /* UPNP_GetValidIGD() : * return values : * 0 = NO IGD found * 1 = A valid connected IGD has been found * 2 = A valid IGD has been found but it reported as * not connected * 3 = an UPnP device has been found but was not recognized as an IGD * * In any non zero return case, the urls and data structures * passed as parameters are set. Donc forget to call FreeUPNPUrls(urls) to * free allocated memory. */ LIBSPEC int UPNP_GetValidIGD(struct UPNPDev * devlist, struct UPNPUrls * urls, struct IGDdatas * data, char * lanaddr, int lanaddrlen); /* UPNP_GetIGDFromUrl() * Used when skipping the discovery process. * return value : * 0 - Not ok * 1 - OK */ LIBSPEC int UPNP_GetIGDFromUrl(const char * rootdescurl, struct UPNPUrls * urls, struct IGDdatas * data, char * lanaddr, int lanaddrlen); LIBSPEC void GetUPNPUrls(struct UPNPUrls *, struct IGDdatas *, const char *); LIBSPEC void FreeUPNPUrls(struct UPNPUrls *); /* return 0 or 1 */ LIBSPEC int UPNPIGD_IsConnected(struct UPNPUrls *, struct IGDdatas *); #ifdef __cplusplus } #endif #endif tomahawk-player/src/accounts/xmpp/XmppInfoPlugin.cpp000664 001750 001750 00000010654 12661705042 024015 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2012, Dominik Schmidt * Copyright 2012, Jeff Mitchell * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "XmppInfoPlugin.h" #include "GlobalActionManager.h" #include "sip/XmppSip.h" #include "utils/Logger.h" #include "TomahawkSettings.h" // Forward Declarations breaking QSharedPointer #if QT_VERSION < QT_VERSION_CHECK( 5, 0, 0 ) #include "Source.h" #endif // remove now playing status after PAUSE_TIMEOUT seconds static const int PAUSE_TIMEOUT = 10; Tomahawk::InfoSystem::XmppInfoPlugin::XmppInfoPlugin( XmppSipPlugin* sipPlugin ) : m_sipPlugin( sipPlugin ) , m_pauseTimer( this ) { Q_ASSERT( sipPlugin->m_client ); m_supportedPushTypes << InfoNowPlaying << InfoNowPaused << InfoNowResumed << InfoNowStopped; m_pauseTimer.setSingleShot( true ); connect( &m_pauseTimer, SIGNAL( timeout() ), this, SLOT( audioStopped() ) ); } Tomahawk::InfoSystem::XmppInfoPlugin::~XmppInfoPlugin() { } void Tomahawk::InfoSystem::XmppInfoPlugin::init() { if ( QThread::currentThread() != Tomahawk::InfoSystem::InfoSystem::instance()->workerThread().data() ) { QMetaObject::invokeMethod( this, "init", Qt::QueuedConnection ); return; } if ( m_sipPlugin.isNull() ) return; connect( this, SIGNAL( publishTune( QUrl, Tomahawk::InfoSystem::InfoStringHash ) ), m_sipPlugin.data(), SLOT( publishTune( QUrl, Tomahawk::InfoSystem::InfoStringHash ) ), Qt::QueuedConnection ); } void Tomahawk::InfoSystem::XmppInfoPlugin::pushInfo( Tomahawk::InfoSystem::InfoPushData pushData ) { switch ( pushData.type ) { case InfoNowPlaying: case InfoNowResumed: m_pauseTimer.stop(); audioStarted( pushData.infoPair ); break; case InfoNowPaused: m_pauseTimer.start( PAUSE_TIMEOUT * 1000 ); audioPaused(); break; case InfoNowStopped: m_pauseTimer.stop(); audioStopped(); break; default: return; } } void Tomahawk::InfoSystem::XmppInfoPlugin::audioStarted( const Tomahawk::InfoSystem::PushInfoPair &pushInfoPair ) { if ( !pushInfoPair.second.canConvert< QVariantMap >() ) { tDebug() << Q_FUNC_INFO << "Failed to convert data to a QVariantMap"; return; } QVariantMap map = pushInfoPair.second.toMap(); if ( map.contains( "private" ) && map[ "private" ] == TomahawkSettings::FullyPrivate ) { emit publishTune( QUrl(), Tomahawk::InfoSystem::InfoStringHash() ); return; } if ( !map.contains( "trackinfo" ) || !map[ "trackinfo" ].canConvert< Tomahawk::InfoSystem::InfoStringHash >() ) { tDebug() << Q_FUNC_INFO << "did not find an infostringhash"; return; } Tomahawk::InfoSystem::InfoStringHash info = map[ "trackinfo" ].value< Tomahawk::InfoSystem::InfoStringHash >(); QUrl url; if ( pushInfoPair.first.contains( "shorturl" ) ) url = pushInfoPair.first[ "shorturl" ].toUrl(); else url = GlobalActionManager::instance()->openLink( info.value( "title" ), info.value( "artist" ), info.value( "album" ) ); emit publishTune( url, info ); } void Tomahawk::InfoSystem::XmppInfoPlugin::audioPaused() { } void Tomahawk::InfoSystem::XmppInfoPlugin::audioStopped() { emit publishTune( QUrl(), Tomahawk::InfoSystem::InfoStringHash() ); } void Tomahawk::InfoSystem::XmppInfoPlugin::getInfo(Tomahawk::InfoSystem::InfoRequestData requestData) { Q_UNUSED( requestData ); } void Tomahawk::InfoSystem::XmppInfoPlugin::notInCacheSlot(const Tomahawk::InfoSystem::InfoStringHash criteria, Tomahawk::InfoSystem::InfoRequestData requestData) { Q_UNUSED( criteria ); Q_UNUSED( requestData ); } tomahawk-player/src/tomahawk/dialogs/DiagnosticsDialog.h000664 001750 001750 00000002700 12661705042 024555 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2011, Dominik Schmidt * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef DIAGNOSTICSDIALOG_H #define DIAGNOSTICSDIALOG_H #include "accounts/Account.h" #include #include class QLabel; class SipInfo; namespace Ui { class DiagnosticsDialog; } class DiagnosticsDialog : public QDialog { Q_OBJECT public: explicit DiagnosticsDialog( QWidget* parent = 0 ); ~DiagnosticsDialog() {}; private slots: void updateLogView(); void copyToClipboard(); void openLogfile(); QString accountLog( Tomahawk::Accounts::Account* ); private: Ui::DiagnosticsDialog* ui; QString peerLog( const QString& nodeid, const QList& peerInfos ); }; #endif // DIAGNOSTICSDIALOG_H tomahawk-player/src/libtomahawk/database/DatabaseCommand_SourceOffline.cpp000664 001750 001750 00000002454 12661705042 030166 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "DatabaseCommand_SourceOffline.h" #include "DatabaseImpl.h" #include "TomahawkSqlQuery.h" #include "utils/Logger.h" #include "Source.h" using namespace Tomahawk; DatabaseCommand_SourceOffline::DatabaseCommand_SourceOffline( int id ) : DatabaseCommand() , m_id( id ) { } void DatabaseCommand_SourceOffline::exec( DatabaseImpl* lib ) { TomahawkSqlQuery q = lib->newquery(); q.exec( QString( "UPDATE source SET isonline = 'false' WHERE id = %1" ) .arg( m_id ) ); } tomahawk-player/src/libtomahawk/filemetadata/MusicScanner.h000664 001750 001750 00000010564 12661705042 025254 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2012, Jeff Mitchell * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef MUSICSCANNER_H #define MUSICSCANNER_H #include "database/Database.h" #include "database/DatabaseCommand.h" #include "TomahawkSettings.h" /* taglib */ #include #include #include #include #include #include #include #include #include #include #include #include // descend dir tree comparing dir mtimes to last known mtime // emit signal for any dir with new content, so we can scan it. // finally, emit the list of new mtimes we observed. class DirLister : public QObject { Q_OBJECT public: DirLister( const QStringList& dirs ) : QObject(), m_dirs( dirs ), m_opcount( 0 ), m_deleting( false ) { qDebug() << Q_FUNC_INFO; } ~DirLister() { qDebug() << Q_FUNC_INFO; } bool isDeleting() { QMutexLocker locker( &m_deletingMutex ); return m_deleting; }; void setIsDeleting() { QMutexLocker locker( &m_deletingMutex ); m_deleting = true; }; signals: void fileToScan( QFileInfo ); void finished(); private slots: void go(); void scanDir( QDir dir, int depth ); private: QStringList m_dirs; QSet< QString > m_processedDirs; uint m_opcount; QMutex m_deletingMutex; bool m_deleting; }; class DirListerThreadController : public QThread { Q_OBJECT public: DirListerThreadController( QObject* parent ); virtual ~DirListerThreadController(); void setPaths( const QStringList& paths ) { m_paths = paths; } void run(); private: QPointer< DirLister > m_dirLister; QStringList m_paths; }; class DLLEXPORT MusicScanner : public QObject { Q_OBJECT public: enum ScanMode { DirScan, FileScan }; enum ScanType { None, Full, Normal, File }; static QVariant readTags( const QFileInfo& fi ); MusicScanner( MusicScanner::ScanMode scanMode, const QStringList& paths, quint32 bs = 0 ); ~MusicScanner(); /** * Specify if we want a dry run, i.e. not change any of the internal data stores. * * This is useful for testing if the scanner works (while Tomahawk is running). */ void setDryRun( bool _dryRun ); bool dryRun(); /** * Adjust the verbosity of logging. * * For example in verbose mode we will log each file we have scanned. */ void setVerbose( bool _verbose ); bool verbose(); signals: //void fileScanned( QVariantMap ); void finished(); void batchReady( const QVariantList&, const QVariantList& ); void progress( unsigned int files ); private: QVariant readFile( const QFileInfo& fi ); void executeCommand( Tomahawk::dbcmd_ptr cmd ); private slots: void postOps(); void scanFile( const QFileInfo& fi ); void setFileMtimes( const QMap< QString, QMap< unsigned int, unsigned int > >& m ); void startScan(); void scan(); void cleanup(); void commitBatch( const QVariantList& tracks, const QVariantList& deletethese ); void commandFinished(); private: void scanFilePaths(); MusicScanner::ScanMode m_scanMode; QStringList m_paths; unsigned int m_scanned; unsigned int m_skipped; bool m_dryRun; bool m_verbose; QList m_skippedFiles; QMap > m_filemtimes; unsigned int m_cmdQueue; QSet< QString > m_processedFiles; QVariantList m_scannedfiles; QVariantList m_filesToDelete; quint32 m_batchsize; DirListerThreadController* m_dirListerThreadController; }; #endif tomahawk-player/src/libtomahawk/database/DatabaseCommand_ArtistStats.cpp000664 001750 001750 00000004466 12661705042 027715 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2013, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "DatabaseCommand_ArtistStats.h" #include "utils/Logger.h" #include "Artist.h" #include "DatabaseImpl.h" #include "PlaylistEntry.h" #include "SourceList.h" // Forward Declarations breaking QSharedPointer #if QT_VERSION < QT_VERSION_CHECK( 5, 0, 0 ) #include "collection/Collection.h" #endif using namespace Tomahawk; DatabaseCommand_ArtistStats::DatabaseCommand_ArtistStats( const artist_ptr& artist, QObject* parent ) : DatabaseCommand( parent ) , m_artist( artist ) { } void DatabaseCommand_ArtistStats::exec( DatabaseImpl* dbi ) { TomahawkSqlQuery query = dbi->newquery(); query.prepare( "SELECT COUNT(*) AS counter, artist.id " "FROM playback_log, track, artist " "WHERE playback_log.source IS NULL AND track.id = playback_log.track AND artist.id = track.artist " "GROUP BY track.artist " "ORDER BY counter DESC" ); query.exec(); unsigned int plays = 0; unsigned int chartPos = 0; unsigned int chartCount = 0; const unsigned int artistId = m_artist->id(); QHash< QString, unsigned int > charts; while ( query.next() ) { if ( query.value( 0 ).toUInt() < 2 ) break; chartCount++; if ( chartPos == 0 && query.value( 1 ).toUInt() == artistId ) { chartPos = chartCount; plays = query.value( 0 ).toUInt(); } } if ( chartPos == 0 ) chartPos = chartCount; emit done( plays, chartPos, chartCount ); } tomahawk-player/src/libtomahawk/playlist/PlaylistModel.cpp000664 001750 001750 00000043360 12661705042 025220 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2014, Christian Muehlhaeuser * Copyright 2010-2012, Jeff Mitchell * Copyright 2010-2012, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "PlaylistModel_p.h" #include #include #include "database/Database.h" #include "database/DatabaseCommand_PlaybackHistory.h" #include "playlist/dynamic/GeneratorInterface.h" #include "utils/Logger.h" #include "utils/TomahawkUtils.h" #include "Album.h" #include "Artist.h" #include "DropJob.h" #include "Pipeline.h" #include "PlayableItem.h" #include "PlaylistEntry.h" #include "Source.h" #include "SourceList.h" using namespace Tomahawk; void PlaylistModel::init() { Q_D( PlaylistModel ); d->dropStorage.parent = QPersistentModelIndex(); d->dropStorage.row = -10; setReadOnly( true ); } PlaylistModel::PlaylistModel( QObject* parent ) : PlayableModel( parent, new PlaylistModelPrivate( this ) ) { init(); } PlaylistModel::PlaylistModel( QObject* parent, PlaylistModelPrivate* d ) : PlayableModel( parent, d ) { init(); } PlaylistModel::~PlaylistModel() { } QString PlaylistModel::guid() const { Q_D( const PlaylistModel ); if ( d->playlist ) { return QString( "playlistmodel/%1" ).arg( d->playlist->guid() ); } else return QString(); } void PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEntries ) { Q_D( PlaylistModel ); if ( d->playlist ) { disconnect( d->playlist.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), this, SLOT( onRevisionLoaded( Tomahawk::PlaylistRevision ) ) ); disconnect( d->playlist.data(), SIGNAL( deleted( Tomahawk::playlist_ptr ) ), this, SIGNAL( playlistDeleted() ) ); disconnect( d->playlist.data(), SIGNAL( changed() ), this, SLOT( onPlaylistChanged() ) ); } if ( loadEntries ) { startLoading(); clear(); } d->playlist = playlist; connect( playlist.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), SLOT( onRevisionLoaded( Tomahawk::PlaylistRevision ) ) ); connect( playlist.data(), SIGNAL( deleted( Tomahawk::playlist_ptr ) ), SIGNAL( playlistDeleted() ) ); connect( playlist.data(), SIGNAL( changed() ), SLOT( onPlaylistChanged() ) ); setReadOnly( !d->playlist->author()->isLocal() ); d->isTemporary = false; onPlaylistChanged(); if ( !loadEntries ) return; if ( playlist->loaded() ) { QList entries = playlist->entries(); appendEntries( entries ); // finishLoading() will be called by appendEntries, so it can keep showing it until all tracks are resolved // finishLoading(); /* foreach ( const plentry_ptr& p, entries ) qDebug() << p->guid() << p->query()->track() << p->query()->artist(); */ } } void PlaylistModel::onPlaylistChanged() { Q_D( PlaylistModel ); QString age = TomahawkUtils::ageToString( QDateTime::fromTime_t( d->playlist->createdOn() ), true ); QString desc; // we check for "someone" to work-around an old bug if ( d->playlist->creator().isEmpty() || d->playlist->creator() == "someone" ) { if ( d->playlist->author()->isLocal() ) { desc = tr( "A playlist you created %1." ) .arg( age ); } else { desc = tr( "A playlist by %1, created %2." ) .arg( d->playlist->author()->friendlyName() ) .arg( age ); } } else { desc = tr( "A playlist by %1, created %2." ) .arg( d->playlist->creator() ) .arg( age ); } setTitle( d->playlist->title() ); setDescription( desc ); emit changed(); } void PlaylistModel::clear() { Q_D( PlaylistModel ); d->waitingForResolved.clear(); PlayableModel::clear(); } void PlaylistModel::appendEntries( const QList< plentry_ptr >& entries ) { insertEntries( entries, rowCount( QModelIndex() ) ); } void PlaylistModel::insertAlbums( const QList< Tomahawk::album_ptr >& albums, int row ) { Q_D( PlaylistModel ); // FIXME: This currently appends, not inserts! Q_UNUSED( row ); foreach ( const album_ptr& album, albums ) { if ( !album ) return; connect( album.data(), SIGNAL( tracksAdded( QList, Tomahawk::ModelMode, Tomahawk::collection_ptr ) ), SLOT( appendQueries( QList ) ) ); appendQueries( album->playlistInterface( Mixed )->tracks() ); } if ( rowCount( QModelIndex() ) == 0 && albums.count() == 1 ) { setTitle( albums.first()->name() ); setDescription( tr( "All tracks by %1 on album %2" ).arg( albums.first()->artist()->name() ).arg( albums.first()->name() ) ); d->isTemporary = true; } } void PlaylistModel::insertArtists( const QList< Tomahawk::artist_ptr >& artists, int row ) { Q_D( PlaylistModel ); // FIXME: This currently appends, not inserts! Q_UNUSED( row ); foreach ( const artist_ptr& artist, artists ) { if ( !artist ) return; connect( artist.data(), SIGNAL( tracksAdded( QList, Tomahawk::ModelMode, Tomahawk::collection_ptr ) ), SLOT( appendQueries( QList ) ) ); appendQueries( artist->playlistInterface( Mixed )->tracks() ); } if ( rowCount( QModelIndex() ) == 0 && artists.count() == 1 ) { setTitle( artists.first()->name() ); setDescription( tr( "All tracks by %1" ).arg( artists.first()->name() ) ); d->isTemporary = true; } } void PlaylistModel::insertQueries( const QList< Tomahawk::query_ptr >& queries, int row, const QList< Tomahawk::PlaybackLog >& logs, const QModelIndex& /* parent */ ) { Q_D( PlaylistModel ); QList< Tomahawk::plentry_ptr > entries; foreach ( const query_ptr& query, queries ) { if ( d->acceptPlayableQueriesOnly && query && query->resolvingFinished() && !query->playable() ) continue; plentry_ptr entry = plentry_ptr( new PlaylistEntry() ); entry->setDuration( query->track()->duration() ); entry->setLastmodified( 0 ); QString annotation = ""; if ( !query->property( "annotation" ).toString().isEmpty() ) annotation = query->property( "annotation" ).toString(); entry->setAnnotation( annotation ); entry->setQuery( query ); entry->setGuid( uuid() ); entries << entry; } insertEntries( entries, row, QModelIndex(), logs ); } void PlaylistModel::insertEntries( const QList< Tomahawk::plentry_ptr >& entries, int row, const QModelIndex& parent, const QList< Tomahawk::PlaybackLog >& logs ) { Q_D( PlaylistModel ); if ( !entries.count() ) { emit itemCountChanged( rowCount( QModelIndex() ) ); finishLoading(); return; } int c = row; QPair< int, int > crows; crows.first = c; crows.second = c + entries.count() - 1; if ( !d->isLoading ) { d->savedInsertPos = row; d->savedInsertTracks = entries; } emit beginInsertRows( parent, crows.first, crows.second ); QList< Tomahawk::query_ptr > queries; int i = 0; PlayableItem* plitem; foreach( const plentry_ptr& entry, entries ) { PlayableItem* pItem = itemFromIndex( parent ); plitem = new PlayableItem( entry, pItem, row + i ); plitem->index = createIndex( row + i, 0, plitem ); if ( logs.count() > i ) plitem->setPlaybackLog( logs.at( i ) ); i++; if ( entry->query()->id() == currentItemUuid() ) setCurrentIndex( plitem->index ); if ( !entry->query()->resolvingFinished() && !entry->query()->playable() ) { queries << entry->query(); d->waitingForResolved.append( entry->query().data() ); connect( entry->query().data(), SIGNAL( playableStateChanged( bool ) ), SLOT( onQueryBecamePlayable( bool ) ), Qt::UniqueConnection ); connect( entry->query().data(), SIGNAL( resolvingFinished( bool ) ), SLOT( trackResolved( bool ) ) ); } connect( plitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); } if ( !d->waitingForResolved.isEmpty() ) { startLoading(); Pipeline::instance()->resolve( queries ); } else { finishLoading(); } emit endInsertRows(); emit itemCountChanged( rowCount( QModelIndex() ) ); emit selectRequest( index( 0, 0, parent ) ); if ( parent.isValid() ) emit expandRequest( parent ); } void PlaylistModel::trackResolved( bool ) { Q_D( PlaylistModel ); Tomahawk::Query* q = qobject_cast< Query* >( sender() ); if ( !q ) { // Track has been removed from the playlist by now return; } if ( d->waitingForResolved.contains( q ) ) { d->waitingForResolved.removeAll( q ); disconnect( q, SIGNAL( resolvingFinished( bool ) ), this, SLOT( trackResolved( bool ) ) ); } if ( d->waitingForResolved.isEmpty() ) { finishLoading(); } } void PlaylistModel::onRevisionLoaded( Tomahawk::PlaylistRevision revision ) { Q_D( PlaylistModel ); if ( !d->waitForRevision.contains( revision.revisionguid ) ) { loadPlaylist( d->playlist ); } else { d->waitForRevision.removeAll( revision.revisionguid ); } } QMimeData* PlaylistModel::mimeData( const QModelIndexList& indexes ) const { Q_D( const PlaylistModel ); // Add the playlist id to the mime data so that we can detect dropping on ourselves QMimeData* data = PlayableModel::mimeData( indexes ); if ( d->playlist ) data->setData( "application/tomahawk.playlist.id", d->playlist->guid().toLatin1() ); return data; } bool PlaylistModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent ) { Q_D( PlaylistModel ); Q_UNUSED( column ); if ( action == Qt::IgnoreAction || isReadOnly() ) return true; if ( !DropJob::acceptsMimeData( data ) ) return false; d->dropStorage.row = row; d->dropStorage.parent = QPersistentModelIndex( parent ); d->dropStorage.action = action; DropJob* dj = new DropJob(); if ( !DropJob::acceptsMimeData( data, DropJob::Track | DropJob::Playlist | DropJob::Album | DropJob::Artist ) ) return false; dj->setDropTypes( DropJob::Track | DropJob::Playlist | DropJob::Artist | DropJob::Album ); dj->setDropAction( DropJob::Append ); /* if ( action & Qt::MoveAction ) dj->setDropAction( DropJob::Move ); */ #ifdef Q_WS_MAC // On mac, drags from outside the app are still Qt::MoveActions instead of Qt::CopyAction by default // so check if the drag originated in this playlist to determine whether or not to copy if ( !data->hasFormat( "application/tomahawk.playlist.id" ) || ( d->playlist && data->data( "application/tomahawk.playlist.id" ) != d->playlist->guid() ) ) { dj->setDropAction( DropJob::Append ); } #endif connect( dj, SIGNAL( tracks( QList< Tomahawk::query_ptr > ) ), SLOT( parsedDroppedTracks( QList< Tomahawk::query_ptr > ) ) ); dj->tracksFromMimeData( data ); return true; } playlist_ptr PlaylistModel::playlist() const { Q_D( const PlaylistModel ); return d->playlist; } void PlaylistModel::parsedDroppedTracks( QList< query_ptr > tracks ) { Q_D( PlaylistModel ); if ( d->dropStorage.row == -10 ) // nope return; int beginRow; if ( d->dropStorage.row != -1 ) beginRow = d->dropStorage.row; else if ( d->dropStorage.parent.isValid() ) beginRow = d->dropStorage.parent.row(); else beginRow = rowCount( QModelIndex() ); if ( tracks.count() ) { bool update = ( d->dropStorage.action & Qt::CopyAction || d->dropStorage.action & Qt::MoveAction ); if ( update ) beginPlaylistChanges(); insertQueries( tracks, beginRow ); if ( update && d->dropStorage.action & Qt::CopyAction ) endPlaylistChanges(); } d->dropStorage.parent = QPersistentModelIndex(); d->dropStorage.row = -10; } void PlaylistModel::beginPlaylistChanges() { Q_D( PlaylistModel ); if ( !d->playlist || !d->playlist->author()->isLocal() ) return; Q_ASSERT( !d->changesOngoing ); d->changesOngoing = true; } void PlaylistModel::endPlaylistChanges() { Q_D( PlaylistModel ); if ( !d->playlist || !d->playlist->author()->isLocal() ) { d->savedInsertPos = -1; d->savedInsertTracks.clear(); d->savedRemoveTracks.clear(); return; } if ( d->changesOngoing ) { d->changesOngoing = false; } else { tDebug() << "Called" << Q_FUNC_INFO << "unexpectedly!"; Q_ASSERT( false ); } QList l = playlistEntries(); QString newrev = uuid(); d->waitForRevision << newrev; if ( dynplaylist_ptr dynplaylist = d->playlist.dynamicCast() ) { if ( dynplaylist->mode() == OnDemand ) { dynplaylist->createNewRevision( newrev ); } else if ( dynplaylist->mode() == Static ) { dynplaylist->createNewRevision( newrev, dynplaylist->currentrevision(), dynplaylist->type(), dynplaylist->generator()->controls(), l ); } } else { d->playlist->createNewRevision( newrev, d->playlist->currentrevision(), l ); } if ( d->savedInsertPos >= 0 && !d->savedInsertTracks.isEmpty() && !d->savedRemoveTracks.isEmpty() ) { // If we have *both* an insert and remove, then it's a move action // However, since we got the insert before the remove (Qt...), the index we have as the saved // insert position is no longer valid. Find the proper one by finding the location of the first inserted // track for ( int i = 0; i < rowCount( QModelIndex() ); i++ ) { const QModelIndex idx = index( i, 0, QModelIndex() ); if ( !idx.isValid() ) continue; const PlayableItem* item = itemFromIndex( idx ); if ( !item || !item->entry() ) continue; // qDebug() << "Checking for equality:" << (item->entry() == m_savedInsertTracks.first()) << m_savedInsertTracks.first()->query()->track() << m_savedInsertTracks.first()->query()->artist(); if ( item->entry() == d->savedInsertTracks.first() ) { // Found our index emit d->playlist->tracksMoved( d->savedInsertTracks, i ); break; } } d->savedInsertPos = -1; d->savedInsertTracks.clear(); d->savedRemoveTracks.clear(); } else if ( d->savedInsertPos >= 0 ) { emit d->playlist->tracksInserted( d->savedInsertTracks, d->savedInsertPos ); d->savedInsertPos = -1; d->savedInsertTracks.clear(); } else if ( !d->savedRemoveTracks.isEmpty() ) { emit d->playlist->tracksRemoved( d->savedRemoveTracks ); d->savedRemoveTracks.clear(); } } QList PlaylistModel::playlistEntries() const { QList l; for ( int i = 0; i < rowCount( QModelIndex() ); i++ ) { QModelIndex idx = index( i, 0, QModelIndex() ); if ( !idx.isValid() ) continue; PlayableItem* item = itemFromIndex( idx ); if ( item && item->entry() ) l << item->entry(); } return l; } void PlaylistModel::removeIndex( const QModelIndex& index, bool moreToCome ) { Q_D( PlaylistModel ); PlayableItem* item = itemFromIndex( index ); if ( item && d->waitingForResolved.contains( item->query().data() ) ) { disconnect( item->query().data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( trackResolved( bool ) ) ); d->waitingForResolved.removeAll( item->query().data() ); if ( d->waitingForResolved.isEmpty() ) finishLoading(); } if ( !d->changesOngoing ) beginPlaylistChanges(); if ( item && !d->isLoading ) d->savedRemoveTracks << item->query(); PlayableModel::removeIndex( index, moreToCome ); if ( !moreToCome ) endPlaylistChanges(); } bool PlaylistModel::waitForRevision( const QString& revisionguid ) const { Q_D( const PlaylistModel ); return d->waitForRevision.contains( revisionguid ); } void PlaylistModel::removeFromWaitList( const QString& revisionguid ) { Q_D( PlaylistModel ); d->waitForRevision.removeAll( revisionguid ); } bool PlaylistModel::isTemporary() const { Q_D( const PlaylistModel ); return d->isTemporary; } bool PlaylistModel::acceptPlayableQueriesOnly() const { Q_D( const PlaylistModel ); return d->acceptPlayableQueriesOnly; } void PlaylistModel::setAcceptPlayableQueriesOnly( bool b ) { Q_D( PlaylistModel ); d->acceptPlayableQueriesOnly = b; } tomahawk-player/src/libtomahawk/network/StreamConnection.cpp000664 001750 001750 00000021276 12661705042 025543 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2011, Jeff Mitchell * Copyright 2013, Teo Mrnjavac * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "StreamConnection.h" #include "database/DatabaseCommand_LoadFiles.h" #include "database/Database.h" #include "network/ControlConnection.h" #include "network/Servent.h" #include "utils/Logger.h" #include "BufferIoDevice.h" #include "Msg.h" #include "MsgProcessor.h" #include "Result.h" #include "SourceList.h" #include "UrlHandler.h" #include #include #include #include using namespace Tomahawk; StreamConnection::StreamConnection( Servent* s, ControlConnection* cc, QString fid, const Tomahawk::result_ptr& result ) : Connection( s ) , m_cc( cc ) , m_fid( fid ) , m_type( RECEIVING ) , m_curBlock( 0 ) , m_badded( 0 ) , m_bsent( 0 ) , m_allok( false ) , m_result( result ) , m_transferRate( 0 ) { qDebug() << Q_FUNC_INFO; BufferIODevice* bio = new BufferIODevice( result->size() ); m_iodev = QSharedPointer( bio, &QObject::deleteLater ); // device audio data gets written to m_iodev->open( QIODevice::ReadWrite ); Servent::instance()->registerStreamConnection( this ); // if the audioengine closes the iodev (skip/stop/etc) then kill the connection // immediately to avoid unnecessary network transfer connect( m_iodev.data(), SIGNAL( aboutToClose() ), SLOT( shutdown() ), Qt::QueuedConnection ); connect( m_iodev.data(), SIGNAL( blockRequest( int ) ), SLOT( onBlockRequest( int ) ) ); // auto delete when connection closes: connect( this, SIGNAL( finished() ), SLOT( deleteLater() ), Qt::QueuedConnection ); // don't fuck with our messages at all. No compression, no parsing, nothing: this->setMsgProcessorModeIn ( MsgProcessor::NOTHING ); this->setMsgProcessorModeOut( MsgProcessor::NOTHING ); } StreamConnection::StreamConnection( Servent* s, ControlConnection* cc, QString fid ) : Connection( s ) , m_cc( cc ) , m_fid( fid ) , m_type( SENDING ) , m_badded( 0 ) , m_bsent( 0 ) , m_allok( false ) , m_transferRate( 0 ) { Servent::instance()->registerStreamConnection( this ); // auto delete when connection closes: connect( this, SIGNAL( finished() ), SLOT( deleteLater() ), Qt::QueuedConnection ); } StreamConnection::~StreamConnection() { qDebug() << Q_FUNC_INFO << "TX/RX:" << bytesSent() << bytesReceived(); if ( m_type == RECEIVING && !m_allok ) { qDebug() << "FTConnection closing before last data msg received, shame."; //TODO log the fact that our peer was bad-mannered enough to not finish the upload // protected, we could expose it: //m_iodev->setErrorString("FTConnection providing data went away mid-transfer"); if ( !m_iodev.isNull() ) ((BufferIODevice*)m_iodev.data())->inputComplete(); } Servent::instance()->onStreamFinished( this ); } QString StreamConnection::id() const { return QString( "FTC[%1 %2]" ) .arg( m_type == SENDING ? "TX" : "RX" ) .arg( m_fid ); } Tomahawk::source_ptr StreamConnection::source() const { return m_source; } void StreamConnection::showStats( qint64 tx, qint64 rx ) { if ( tx > 0 || rx > 0 ) { qDebug() << id() << QString( "Down: %L1 bytes/sec," ).arg( rx ) << QString( "Up: %L1 bytes/sec" ).arg( tx ); } m_transferRate = tx + rx; emit updated(); } void StreamConnection::setup() { QList sources = SourceList::instance()->sources(); foreach ( const source_ptr& src, sources ) { // local src doesnt have a control connection, skip it: if ( src.isNull() || src->isLocal() ) continue; if ( src->controlConnection() == m_cc ) { m_source = src; break; } } connect( this, SIGNAL( statsTick( qint64, qint64 ) ), SLOT( showStats( qint64, qint64 ) ) ); if ( m_type == RECEIVING ) { qDebug() << "in RX mode"; emit updated(); return; } qDebug() << "in TX mode, fid:" << m_fid; DatabaseCommand_LoadFiles* cmd = new DatabaseCommand_LoadFiles( m_fid.toUInt() ); connect( cmd, SIGNAL( result( Tomahawk::result_ptr ) ), SLOT( startSending( Tomahawk::result_ptr ) ) ); Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) ); } void StreamConnection::startSending( const Tomahawk::result_ptr& result ) { if ( result.isNull() ) { qDebug() << "Can't handle invalid result!"; shutdown(); return; } m_result = result; qDebug() << "Starting to transmit" << m_result->url(); boost::function< void ( const QString, QSharedPointer< QIODevice > ) > callback = boost::bind( &StreamConnection::reallyStartSending, this, result, _1, _2 ); Tomahawk::UrlHandler::getIODeviceForUrl( m_result, m_result->url(), callback ); } void StreamConnection::reallyStartSending( const Tomahawk::result_ptr result, const QString url, QSharedPointer< QIODevice > io ) { Q_UNUSED( url ); // Note: We don't really need to pass in 'result' here, since we already have it stored // as a member variable. The callback-signature of getIODeviceForUrl requires it, though. if ( !io || io.isNull() ) { qDebug() << "Couldn't read from source:" << result->url(); shutdown(); return; } m_readdev = QSharedPointer( io ); sendSome(); emit updated(); } void StreamConnection::handleMsg( msg_ptr msg ) { Q_ASSERT( msg->is( Msg::RAW ) ); if ( msg->payload().startsWith( "block" ) ) { int block = QString( msg->payload() ).mid( 5 ).toInt(); m_readdev->seek( block * BufferIODevice::blockSize() ); qDebug() << "Seeked to block:" << block; QByteArray sm; sm.append( QString( "doneblock%1" ).arg( block ) ); sendMsg( Msg::factory( sm, Msg::RAW | Msg::FRAGMENT ) ); QTimer::singleShot( 0, this, SLOT( sendSome() ) ); } else if ( msg->payload().startsWith( "doneblock" ) ) { int block = QString( msg->payload() ).mid( 9 ).toInt(); ( (BufferIODevice*)m_iodev.data() )->seeked( block ); m_curBlock = block; qDebug() << "Next block is now:" << block; } else if ( msg->payload().startsWith( "data" ) ) { m_badded += msg->payload().length() - 4; ( (BufferIODevice*)m_iodev.data() )->addData( m_curBlock++, msg->payload().mid( 4 ) ); } //qDebug() << Q_FUNC_INFO << "flags" << (int) msg->flags() // << "payload len" << msg->payload().length() // << "written to device so far: " << m_badded; if ( m_iodev && ( (BufferIODevice*)m_iodev.data() )->nextEmptyBlock() < 0 ) { m_allok = true; // tell our iodev there is no more data to read, no args meaning a success: ( (BufferIODevice*)m_iodev.data() )->inputComplete(); shutdown(); } } Connection* StreamConnection::clone() { Q_ASSERT( false ); return 0; } void StreamConnection::sendSome() { Q_ASSERT( m_type == StreamConnection::SENDING ); QByteArray ba = "data"; ba.append( m_readdev->read( BufferIODevice::blockSize() ) ); m_bsent += ba.length() - 4; if ( m_readdev->atEnd() ) { sendMsg( Msg::factory( ba, Msg::RAW ) ); return; } else { // more to come -> FRAGMENT sendMsg( Msg::factory( ba, Msg::RAW | Msg::FRAGMENT ) ); } // HINT: change the 0 to 50 to transmit at 640Kbps, for example // (this is where upload throttling could be implemented) QTimer::singleShot( 0, this, SLOT( sendSome() ) ); } void StreamConnection::onBlockRequest( int block ) { qDebug() << Q_FUNC_INFO << block; if ( m_curBlock == block ) return; QByteArray sm; sm.append( QString( "block%1" ).arg( block ) ); sendMsg( Msg::factory( sm, Msg::RAW | Msg::FRAGMENT ) ); } tomahawk-player/data/images/000775 001750 001750 00000000000 12661705042 017150 5ustar00stefanstefan000000 000000 tomahawk-player/src/libtomahawk-playdarapi/Api_v1.cpp000664 001750 001750 00000037360 12661705042 024043 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2011, Jeff Mitchell * Copyright 2010-2011, Leo Franchi * Copyright 2013, Teo Mrnjavac * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "Api_v1.h" #include "database/Database.h" #include "database/DatabaseCommand_AddClientAuth.h" #include "database/DatabaseCommand_ClientAuthValid.h" #include "network/Servent.h" #include "utils/Json.h" #include "utils/Logger.h" #include "utils/TomahawkUtils.h" #include "Api_v1_5.h" #include "Pipeline.h" #include "Result.h" #include "Source.h" #include "StatResponseHandler.h" #include "UrlHandler.h" #include #include using namespace Tomahawk; using namespace TomahawkUtils; Api_v1::Api_v1( QxtAbstractWebSessionManager* sm, QObject* parent ) : QxtWebSlotService(sm, parent) , m_api_v1_5( new Api_v1_5( this ) ) { } Api_v1::~Api_v1() { delete m_api_v1_5; } void Api_v1::auth_1( QxtWebRequestEvent* event, QString arg ) { tDebug( LOGVERBOSE ) << "AUTH_1 HTTP" << event->url.toString() << arg; if ( !urlHasQueryItem( event->url, "website" ) || !urlHasQueryItem( event->url, "name" ) ) { tDebug( LOGVERBOSE ) << "Malformed HTTP resolve request"; send404( event ); return; } QString formToken = uuid(); if ( urlHasQueryItem( event->url, "json" ) ) { // JSON response QVariantMap m; m[ "formtoken" ] = formToken; sendJSON( m, event ); } else { // webpage request QString authPage = RESPATH "www/auth.html"; QHash< QString, QString > args; if ( urlHasQueryItem( event->url, "receiverurl" ) ) args[ "url" ] = urlQueryItemValue( event->url, "receiverurl" ).toUtf8(); args[ "formtoken" ] = formToken; args[ "website" ] = urlQueryItemValue( event->url, "website" ).toUtf8(); args[ "name" ] = urlQueryItemValue( event->url, "name" ).toUtf8(); sendWebpageWithArgs( event, authPage, args ); } } void Api_v1::auth_2( QxtWebRequestEvent* event, QString arg ) { tDebug( LOGVERBOSE ) << "AUTH_2 HTTP" << event->url.toString() << arg; if ( event->content.isNull() ) { tDebug( LOGVERBOSE ) << "Null content"; send404( event ); return; } QString params = QUrl::fromPercentEncoding( event->content->readAll() ); params = params.mid( params.indexOf( '?' ) ); QStringList pieces = params.split( '&' ); QHash< QString, QString > queryItems; foreach ( const QString& part, pieces ) { QStringList keyval = part.split( '=' ); if ( keyval.size() == 2 ) queryItems.insert( keyval.first(), keyval.last() ); else tDebug( LOGVERBOSE ) << "Failed parsing url parameters:" << part; } tDebug( LOGVERBOSE ) << "has query items:" << pieces; if ( !params.contains( "website" ) || !params.contains( "name" ) || !params.contains( "formtoken" ) ) { tDebug( LOGVERBOSE ) << "Malformed HTTP resolve request"; send404( event ); return; } QString website = queryItems[ "website" ]; QString name = queryItems[ "name" ]; QByteArray authtoken = uuid().toLatin1(); tDebug( LOGVERBOSE ) << "HEADERS:" << event->headers; if ( !queryItems.contains( "receiverurl" ) || queryItems.value( "receiverurl" ).isEmpty() ) { //no receiver url, so do it ourselves if ( queryItems.contains( "json" ) ) { QVariantMap m; m[ "authtoken" ] = authtoken; sendJSON( m, event ); } else { QString authPage = RESPATH "www/auth.na.html"; QHash< QString, QString > args; args[ "authcode" ] = authtoken; args[ "website" ] = website; args[ "name" ] = name; sendWebpageWithArgs( event, authPage, args ); } } else { // do what the client wants QUrl receiverurl = QUrl( queryItems.value( "receiverurl" ), QUrl::TolerantMode ); urlAddQueryItem( receiverurl, "authtoken", "#" + authtoken ); tDebug( LOGVERBOSE ) << "Got receiver url:" << receiverurl.toString(); QxtWebRedirectEvent* e = new QxtWebRedirectEvent( event->sessionID, event->requestID, receiverurl.toString() ); postEvent( e ); // TODO validation of receiverurl? } DatabaseCommand_AddClientAuth* dbcmd = new DatabaseCommand_AddClientAuth( authtoken, website, name, event->headers.key( "ua" ) ); Database::instance()->enqueue( Tomahawk::dbcmd_ptr(dbcmd) ); } /** * Handle API calls. * * All v1.0 API (standard Playdar) calls go to /api/?method= * All v1.5 API (simple remote control) calls go to /api/1.5/ */ void Api_v1::api( QxtWebRequestEvent* event, const QString& version, const QString& method, const QString& arg1, const QString& arg2, const QString& arg3 ) { if ( version.isEmpty() ) { // We dealing with API 1.0 const QUrl& url = event->url; if ( urlHasQueryItem( url, "method" ) ) { const QString method = urlQueryItemValue( url, "method" ); if ( method == "stat" ) return stat( event ); if ( method == "resolve" ) return resolve( event ); if ( method == "get_results" ) return get_results( event ); } send404( event ); } else if ( version == "1.5" ) { if ( !arg3.isEmpty() ) { if ( !QMetaObject::invokeMethod( m_api_v1_5, method.toLatin1().constData(), Q_ARG( QxtWebRequestEvent*, event ), Q_ARG( QString, arg1 ), Q_ARG( QString, arg2 ), Q_ARG( QString, arg3 ) ) ) { apiCallFailed(event, method); } } else if ( !arg2.isEmpty() ) { if ( !QMetaObject::invokeMethod( m_api_v1_5, method.toLatin1().constData(), Q_ARG( QxtWebRequestEvent*, event ), Q_ARG( QString, arg1 ), Q_ARG( QString, arg2 ) ) ) { apiCallFailed(event, method); } } else if ( !arg1.isEmpty() ) { if ( !QMetaObject::invokeMethod( m_api_v1_5, method.toLatin1().constData(), Q_ARG( QxtWebRequestEvent*, event ), Q_ARG( QString, arg1 ) ) ) { apiCallFailed(event, method); } } else { if ( !QMetaObject::invokeMethod( m_api_v1_5, method.toLatin1().constData(), Q_ARG( QxtWebRequestEvent*, event ) ) ) { apiCallFailed(event, method); } } } else { sendPlain404( event, QString( "Unknown API version %1" ).arg( version ), "API version not found" ); } } // request for stream: /sid/ void Api_v1::sid( QxtWebRequestEvent* event, QString unused ) { Q_UNUSED( unused ); RID rid = event->url.path().mid( 5 ); tDebug( LOGVERBOSE ) << "Request for sid" << rid; result_ptr rp = Pipeline::instance()->result( rid ); if ( rp.isNull() ) { return send404( event ); } boost::function< void ( const QString, QSharedPointer< QIODevice > ) > callback = boost::bind( &Api_v1::processSid, this, event, rp, _1, _2 ); Tomahawk::UrlHandler::getIODeviceForUrl( rp, rp->url(), callback ); } void Api_v1::processSid( QxtWebRequestEvent* event, const Tomahawk::result_ptr rp, const QString url, QSharedPointer< QIODevice > iodev ) { Q_UNUSED( url ); tDebug( LOGVERBOSE ) << Q_FUNC_INFO; if ( !iodev || !rp ) { return send404( event ); // 503? } m_ioDevice = iodev; QxtWebPageEvent* e = new QxtWebPageEvent( event->sessionID, event->requestID, iodev.data() ); e->streaming = iodev->isSequential(); e->contentType = rp->mimetype().toLatin1(); if ( rp->size() > 0 ) e->headers.insert( "Content-Length", QString::number( rp->size() ) ); postEvent( e ); } void Api_v1::send404( QxtWebRequestEvent* event ) { tDebug() << "404" << event->url.toString(); QxtWebPageEvent* wpe = new QxtWebPageEvent( event->sessionID, event->requestID, "

Not Found

" ); wpe->status = 404; wpe->statusMessage = "no event found"; postEvent( wpe ); } void Api_v1::sendPlain404( QxtWebRequestEvent* event, const QString& message, const QString& statusmessage ) { QxtWebPageEvent * e = new QxtWebPageEvent( event->sessionID, event->requestID, message.toUtf8() ); e->contentType = "text/plain"; e->status = 404; e->statusMessage = statusmessage.toLatin1().constData(); postEvent( e ); } void Api_v1::stat( QxtWebRequestEvent* event ) { tDebug( LOGVERBOSE ) << "Got Stat request:" << event->url.toString(); if ( !event->content.isNull() ) tDebug( LOGVERBOSE ) << "BODY:" << event->content->readAll(); StatResponseHandler* handler = new StatResponseHandler( this, event ); if ( urlHasQueryItem( event->url, "auth" ) ) { // check for auth status DatabaseCommand_ClientAuthValid* dbcmd = new DatabaseCommand_ClientAuthValid( urlQueryItemValue( event->url, "auth" ) ); connect( dbcmd, SIGNAL( authValid( QString, QString, bool ) ), handler, SLOT( statResult( QString, QString, bool ) ) ); Database::instance()->enqueue( Tomahawk::dbcmd_ptr(dbcmd) ); } else { handler->statResult( QString(), QString(), false ); } } void Api_v1::resolve( QxtWebRequestEvent* event ) { if ( !urlHasQueryItem( event->url, "artist" ) || !urlHasQueryItem( event->url, "track" ) ) { tDebug( LOGVERBOSE ) << "Malformed HTTP resolve request"; return send404( event ); } const QString artist = urlQueryItemValue( event->url, "artist" ); const QString track = urlQueryItemValue( event->url, "track" ); const QString album = urlQueryItemValue( event->url, "album" ); if ( artist.trimmed().isEmpty() || track.trimmed().isEmpty() ) { tDebug( LOGVERBOSE ) << "Malformed HTTP resolve request"; return send404( event ); } QString qid; if ( urlHasQueryItem( event->url, "qid" ) ) qid = urlQueryItemValue( event->url, "qid" ); else qid = uuid(); query_ptr qry = Query::get( artist, track, album, qid, false ); if ( qry.isNull() ) { return send404( event ); } Pipeline::instance()->resolve( qry, true, true ); QVariantMap r; r.insert( "qid", qid ); sendJSON( r, event ); } void Api_v1::staticdata( QxtWebRequestEvent* event, const QString& file ) { tDebug( LOGVERBOSE ) << "STATIC request:" << event << file; bool whitelisted = ( file == QString( "tomahawk_auth_logo.png" ) || file.startsWith( "css/" ) || file.startsWith( "js/" ) ); if ( whitelisted ) { QFile f( RESPATH "www/" + file ); f.open( QIODevice::ReadOnly ); QByteArray data = f.readAll(); QxtWebPageEvent* e = new QxtWebPageEvent( event->sessionID, event->requestID, data ); if ( file.endsWith( ".png" ) ) e->contentType = "image/png"; if ( file.endsWith( ".css" ) ) e->contentType = "text/css"; if ( file.endsWith( ".js" ) ) e->contentType = "application/javascript"; postEvent( e ); } else { send404( event ); return; } } void Api_v1::staticdata( QxtWebRequestEvent* event, const QString& path, const QString& file ) { return staticdata( event, path + "/" + file ); } void Api_v1::get_results( QxtWebRequestEvent* event ) { if ( !urlHasQueryItem( event->url, "qid" ) ) { tDebug( LOGVERBOSE ) << "Malformed HTTP get_results request"; send404( event ); return; } query_ptr qry = Pipeline::instance()->query( urlQueryItemValue( event->url, "qid" ) ); if ( qry.isNull() ) { send404( event ); return; } QVariantMap r; r.insert( "qid", qry->id() ); r.insert( "poll_interval", 1300 ); r.insert( "refresh_interval", 1000 ); r.insert( "poll_limit", 14 ); r.insert( "solved", qry->playable() ); r.insert( "query", qry->toVariant() ); QVariantList res; foreach( const result_ptr& rp, qry->results() ) { if ( rp->isOnline() ) res << rp->toVariant(); } r.insert( "results", res ); sendJSON( r, event ); } void Api_v1::sendJSON( const QVariantMap& m, QxtWebRequestEvent* event ) { QByteArray ctype; bool ok; QByteArray body = TomahawkUtils::toJson( m, &ok ); Q_ASSERT( ok ); if ( urlHasQueryItem( event->url, "jsonp" ) && !urlQueryItemValue( event->url, "jsonp" ).isEmpty() ) { ctype = "text/javascript; charset=utf-8"; body.prepend( QString("%1( ").arg( urlQueryItemValue( event->url, "jsonp" ) ).toLatin1() ); body.append( " );" ); } else { ctype = "appplication/json; charset=utf-8"; } QxtWebPageEvent * e = new QxtWebPageEvent( event->sessionID, event->requestID, body ); e->contentType = ctype; e->headers.insert( "Content-Length", QString::number( body.length() ) ); e->headers.insert( "Access-Control-Allow-Origin", "*" ); postEvent( e ); tDebug( LOGVERBOSE ) << "JSON response" << event->url.toString() << body; } // load an html template from a file, replace args from map // then serve void Api_v1::sendWebpageWithArgs( QxtWebRequestEvent* event, const QString& filenameSource, const QHash< QString, QString >& args ) { if ( !QFile::exists( filenameSource ) ) qWarning() << "Passed invalid file for html source:" << filenameSource; QFile f( filenameSource ); f.open( QIODevice::ReadOnly ); QByteArray html = f.readAll(); foreach( const QString& param, args.keys() ) { html.replace( QString( "<%%1%>" ).arg( param.toUpper() ), args.value( param ).toUtf8() ); } // workaround for receiverurl if ( !args.keys().contains( "URL" ) ) html.replace( QString( "<%URL%>" ).toLatin1(), QByteArray() ); QxtWebPageEvent* e = new QxtWebPageEvent( event->sessionID, event->requestID, html ); postEvent( e ); } void Api_v1::index( QxtWebRequestEvent* event ) { QString indexPage = RESPATH "www/index.html"; QHash< QString, QString > args; sendWebpageWithArgs( event, indexPage, args ); } void Api_v1::apiCallFailed( QxtWebRequestEvent* event, const QString& method ) { sendPlain404( event, QString( "Method \"%1\" for API 1.5 not found" ).arg( method ), "Method in API 1.5 not found" ); } void Api_v1::sendJsonOk( QxtWebRequestEvent* event ) { QxtWebPageEvent * e = new QxtWebPageEvent( event->sessionID, event->requestID, "{ \"result\": \"ok\" }" ); e->headers.insert( "Access-Control-Allow-Origin", "*" ); e->contentType = "application/json"; postEvent( e ); } void Api_v1::sendJsonError( QxtWebRequestEvent* event, const QString& message ) { QxtWebPageEvent * e = new QxtWebPageEvent( event->sessionID, event->requestID, QString( "{ \"result\": \"error\", \"error\": \"%1\" }" ).arg( message ).toUtf8().constData() ); e->headers.insert( "Access-Control-Allow-Origin", "*" ); e->contentType = "application/json"; e->status = 500; e->statusMessage = "Method call failed."; postEvent( e ); } tomahawk-player/src/libtomahawk/viewpages/PlaylistViewPage.h000664 001750 001750 00000003761 12661705042 025466 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2012-2014, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef PLAYLISTVIEWPAGE_H #define PLAYLISTVIEWPAGE_H #include "ViewPage.h" #include "PlaylistInterface.h" #include "DllMacro.h" class QStackedWidget; class ContextView; class FilterHeader; class DLLEXPORT PlaylistViewPage : public QWidget, public Tomahawk::ViewPage { Q_OBJECT public: explicit PlaylistViewPage( QWidget* parent = 0, QWidget* extraHeader = 0 ); ~PlaylistViewPage(); virtual QWidget* widget() { return this; } virtual Tomahawk::playlistinterface_ptr playlistInterface() const; virtual QString title() const; virtual QString description() const; virtual QPixmap pixmap() const; virtual bool jumpToCurrentTrack(); virtual bool isTemporaryPage() const; virtual bool isBeingPlayed() const; void setTemporaryPage( bool b ); ContextView* view() const; void setPixmap( const QPixmap& pixmap ); public slots: virtual bool setFilter( const QString& pattern ); signals: void destroyed( QWidget* widget ); private slots: void onModelChanged(); void onWidgetDestroyed( QWidget* widget ); private: FilterHeader* m_header; ContextView* m_view; QPixmap m_pixmap; bool m_temporary; }; #endif // PLAYLISTVIEWPAGE_H tomahawk-player/thirdparty/libportfwd/third-party/miniupnpc-1.6/declspec.h000664 001750 001750 00000000370 12661705042 030046 0ustar00stefanstefan000000 000000 #ifndef __DECLSPEC_H__ #define __DECLSPEC_H__ #if defined(WIN32) && !defined(STATICLIB) #ifdef MINIUPNP_EXPORTS #define LIBSPEC __declspec(dllexport) #else #define LIBSPEC __declspec(dllimport) #endif #else #define LIBSPEC #endif #endif tomahawk-player/src/libtomahawk/database/DatabaseCommand_TrendingTracks.cpp000664 001750 001750 00000010634 12661705042 030344 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "DatabaseCommand_TrendingTracks_p.h" #include "database/DatabaseImpl.h" #include "database/TomahawkSqlQuery.h" #include "Track.h" #include #include namespace Tomahawk { DatabaseCommand_TrendingTracks::DatabaseCommand_TrendingTracks( QObject* parent ) : DatabaseCommand( parent, new DatabaseCommand_TrendingTracksPrivate( this ) ) { } DatabaseCommand_TrendingTracks::~DatabaseCommand_TrendingTracks() { } void DatabaseCommand_TrendingTracks::exec( DatabaseImpl* dbi ) { Q_D( DatabaseCommand_TrendingTracks ); QString limit; if ( d->amount > 0 ) { limit = QString( "LIMIT 0, %1" ).arg( d->amount ); } QDateTime now = QDateTime::currentDateTime(); QDateTime _1WeekAgo = now.addDays( -7 ); QDateTime _2WeeksAgo = now.addDays( -14 ); uint peersLastWeek = 1; // Use a default of 1 to be able to do certain mathematical computations without Div-by-0 Errors. { // Get the number of active peers in the last week. // We could just use the number of peers instead but that would include old peers that may have been inactive for a long while. QString peersLastWeekSql = QString( " SELECT COUNT(DISTINCT source ) " " FROM playback_log " " WHERE playback_log.source IS NOT NULL " // exclude self " AND playback_log.playtime >= %1 " ).arg( _1WeekAgo.toTime_t() ); TomahawkSqlQuery query = dbi->newquery(); query.prepare( peersLastWeekSql ); query.exec(); while ( query.next() ) { peersLastWeek = std::max( 1u, query.value( 0 ).toUInt() ); } } QString timespanSql = QString( " SELECT COUNT(*) as counter, track " " FROM playback_log " " WHERE playback_log.source IS NOT NULL " // exclude self " AND playback_log.playtime >= %1 AND playback_log.playtime <= %2 " " GROUP BY playback_log.track " " HAVING counter > 0 " ); QString lastWeekSql = timespanSql.arg( _1WeekAgo.toTime_t() ).arg( now.toTime_t() ); QString _1BeforeLastWeekSql = timespanSql.arg( _2WeeksAgo.toTime_t() ).arg( _1WeekAgo.toTime_t() ); QString formula = QString( " ( lastweek.counter / weekbefore.counter ) " " * " " max(0, 1 - (%1 / (4*min(lastweek.counter, weekbefore.counter )) ) )" ).arg( peersLastWeek ); QString sql = QString( " SELECT track.name, artist.name, ( %4 ) as trending " " FROM ( %1 ) lastweek, ( %2 ) weekbefore, track, artist " " WHERE lastweek.track = weekbefore.track " " AND track.id = lastweek.track AND artist.id = track.artist " " AND ( lastweek.counter - weekbefore.counter ) > 0" " ORDER BY trending DESC %3 " ).arg( lastWeekSql ).arg( _1BeforeLastWeekSql ).arg( limit ).arg( formula ); TomahawkSqlQuery query = dbi->newquery(); query.prepare( sql ); query.exec(); QList< QPair< double, Tomahawk::track_ptr > > tracks; while ( query.next() ) { Tomahawk::track_ptr track = Tomahawk::Track::get( query.value( 1 ).toString(), query.value( 0 ).toString() ); if ( !track ) continue; tracks << QPair< double, track_ptr >( query.value( 2 ).toDouble(), track ); } emit done( tracks ); } void DatabaseCommand_TrendingTracks::setLimit( unsigned int amount ) { Q_D( DatabaseCommand_TrendingTracks ); d->amount = amount; } } // namespace Tomahawk tomahawk-player/src/libtomahawk-playdarapi/StatResponseHandler.h000664 001750 001750 00000002375 12661705042 026317 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2014, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #pragma once #ifndef STATRESPONSEHANDLER_H #define STATRESPONSEHANDLER_H #include class Api_v1; class QxtWebRequestEvent; class StatResponseHandler : public QObject { Q_OBJECT public: StatResponseHandler( Api_v1* parent, QxtWebRequestEvent* event ); public slots: void statResult( const QString& clientToken, const QString& name, bool valid ); private: Api_v1* m_parent; QxtWebRequestEvent* m_storedEvent; }; #endif // STATRESPONSEHANDLER_H tomahawk-player/src/libtomahawk/database/DatabaseCommand_SocialAction.h000664 001750 001750 00000014234 12661705042 027437 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2011, Christopher Reichert * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef DATABASECOMMAND_SOCIALACTION_H #define DATABASECOMMAND_SOCIALACTION_H #include #include "database/DatabaseCommandLoggable.h" #include "SourceList.h" #include "Typedefs.h" #include "Artist.h" #include "Track.h" #include "DllMacro.h" namespace Tomahawk { /** * \class DatabaseCommand_SocialAction * \brief Database command used to write social actions to database. * * This Database command allows Tomahawk to write social actions to * the local database. These social actions can be interfaced with social * networking API's such as LastFm, Facebook, or Twitter to allow the user * to sync these actions with their accounts on these sites. * * \see DatabaseCommand_LoadSocialActions */ class DLLEXPORT DatabaseCommand_SocialAction : public DatabaseCommandLoggable { Q_OBJECT Q_PROPERTY( QString action READ action WRITE setAction ) Q_PROPERTY( QString comment READ comment WRITE setComment ) Q_PROPERTY( int timestamp READ timestamp WRITE setTimestamp ) Q_PROPERTY( QString artist READ artist WRITE setArtist ) Q_PROPERTY( QString track READ track WRITE setTrack ) public: /** * \brief Default constructor for DatabaseCommand_SocialAction. * * Constructs an empty database command for a social action. */ explicit DatabaseCommand_SocialAction( QObject* parent = 0 ) : DatabaseCommandLoggable( parent ) {} /** * \brief Overloaded constructor for DatabaseCommand_SocialAction. * \param track A Tomahawk Track object. * \param action Name of the social action to be written to the database. * \param comment Comment associated with this social action. * \param parent Parent class. * * Constructor which creates a new database command for the specified social action. */ explicit DatabaseCommand_SocialAction( const Tomahawk::trackdata_ptr& track, QString action, QString comment = "", QObject* parent = 0 ) : DatabaseCommandLoggable( parent ) , m_track( track ) , m_comment( comment ) , m_action( action ) { setSource( SourceList::instance()->getLocal() ); m_artist = track->artist(); m_title = track->track(); m_timestamp = QDateTime::currentDateTime().toTime_t(); } /** * \brief Returns the name of this database command. * \return QString containing the database command name 'socialaction'. */ QString commandname() const Q_DECL_OVERRIDE { return "socialaction"; } /** * \brief Executes the database command. * \param dbi Database instance. * * This method prepares an sql query to write this social action * into the local database. */ void exec( DatabaseImpl* dbi ) Q_DECL_OVERRIDE; /** * \brief Triggers a Database Sync. */ void postCommitHook() Q_DECL_OVERRIDE; /** * \brief Returns the artist associated with this database command. * \return Name of the artist. * \see setArtist() */ virtual QString artist() const { return m_artist; } /** * \brief Sets the artist name for this database command. * \param s QString containing the artist name. * \see artist() */ virtual void setArtist( const QString& s ) { m_artist = s; } /** * \brief Returns the track name associated with this social action. * \return QString containing the track name. * \see setTrack() */ virtual QString track() const { return m_title; } /** * \brief Sets the track name associated with this database command. * \param track QString containing the track name. * \see track() */ virtual void setTrack( const QString& title ) { m_title = title; } /** * \brief Returns the social action for this database command instance. * \return QString containing the action name. * \see setAction() */ QString action() const { return m_action; } /** * \brief Sets the social actions * \param a QString containing action to be set in this class. * \see action() */ void setAction( QString a ) { m_action = a; } /** * \brief Returns comment associated with this social action. * \return QString containing comment associated with this social action. * \see setComment() */ virtual QString comment() const { return m_comment; } /** * \brief Sets the comment associated with this social action. * \param com Comment associated with this social action. * \see comment() */ virtual void setComment( const QString& com ) { m_comment = com; } /** * \brief Returns the timestamp associated with this social action. * \return unsigned integer containing timestamp * \see setTimesetamp() */ virtual int timestamp() const { return m_timestamp; } /** * \brief Sets the timestamp associated with this social action. * \param ts unsigned integer associated with this social action. * \see timestamp() */ virtual void setTimestamp( const int ts ) { m_timestamp = ts; } bool doesMutates() const Q_DECL_OVERRIDE { return true; } bool groupable() const Q_DECL_OVERRIDE { return true; } protected: Tomahawk::trackdata_ptr m_track; private: QString m_artist; QString m_title; int m_timestamp; QString m_comment; QString m_action; //! currently used values: Love, Inbox }; } #endif // DATABASECOMMAND_SOCIALACTION_H tomahawk-player/lang/translations.cmake000664 001750 001750 00000004050 12661705042 021435 0ustar00stefanstefan000000 000000 macro(add_tomahawk_translations language) list( APPEND TOMAHAWK_LANGUAGES ${ARGV} ) set( tomahawk_i18n_qrc_content "\n" ) # tomahawk and qt language files set( tomahawk_i18n_qrc_content "${tomahawk_i18n_qrc_content}\n" ) foreach( lang ${TOMAHAWK_LANGUAGES} ) set( tomahawk_i18n_qrc_content "${tomahawk_i18n_qrc_content}tomahawk_${lang}.qm\n" ) if( NOT lang STREQUAL "en" AND EXISTS ${QT_TRANSLATIONS_DIR}/qt_${lang}.qm ) file( COPY ${QT_TRANSLATIONS_DIR}/qt_${lang}.qm DESTINATION ${CMAKE_CURRENT_BINARY_DIR} ) set( tomahawk_i18n_qrc_content "${tomahawk_i18n_qrc_content}qt_${lang}.qm\n" ) endif() # build explicitly enabled languages list( APPEND TS_FILES "${CMAKE_SOURCE_DIR}/lang/tomahawk_${lang}.ts" ) endforeach() set( tomahawk_i18n_qrc_content "${tomahawk_i18n_qrc_content}\n" ) set( tomahawk_i18n_qrc_content "${tomahawk_i18n_qrc_content}\n" ) file( WRITE ${CMAKE_BINARY_DIR}/lang/tomahawk_i18n.qrc "${tomahawk_i18n_qrc_content}" ) qt_add_translation(QM_FILES ${TS_FILES}) ## HACK HACK HACK - around rcc limitations to allow out of source-tree building set( trans_file tomahawk_i18n ) set( trans_srcfile ${CMAKE_BINARY_DIR}/lang/${trans_file}.qrc ) set( trans_infile ${CMAKE_CURRENT_BINARY_DIR}/${trans_file}.qrc ) set( trans_outfile ${CMAKE_CURRENT_BINARY_DIR}/qrc_${trans_file}.cxx ) # Copy the QRC file to the output directory add_custom_command( OUTPUT ${trans_infile} COMMAND ${CMAKE_COMMAND} -E copy ${trans_srcfile} ${trans_infile} MAIN_DEPENDENCY ${trans_srcfile} ) # Run the resource compiler (rcc_options should already be set) add_custom_command( OUTPUT ${trans_outfile} COMMAND ${QT_RCC_EXECUTABLE} ARGS ${rcc_options} -name ${trans_file} -o ${trans_outfile} ${trans_infile} MAIN_DEPENDENCY ${trans_infile} DEPENDS ${QM_FILES} ) endmacro() tomahawk-player/src/libtomahawk/utils/Json.cpp000664 001750 001750 00000007422 12661705042 022645 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2014, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "Json.h" // Qt version specific includes #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) #include #include #include #else #include #include #include #endif namespace TomahawkUtils { QVariantMap qobject2qvariant( const QObject* object ) { #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) QVariantMap map; if ( object == NULL ) { return map; } const QMetaObject* metaObject = object->metaObject(); for ( int i = 0; i < metaObject->propertyCount(); ++i ) { QMetaProperty metaproperty = metaObject->property( i ); if ( metaproperty.isReadable() ) { map[ QLatin1String( metaproperty.name() ) ] = object->property( metaproperty.name() ); } } return map; #else return QJson::QObjectHelper::qobject2qvariant( object ); #endif } void qvariant2qobject( const QVariantMap& variant, QObject* object ) { #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) for ( QVariantMap::const_iterator iter = variant.begin(); iter != variant.end(); ++iter ) { QVariant property = object->property( iter.key().toLatin1() ); Q_ASSERT( property.isValid() ); if ( property.isValid() ) { QVariant value = iter.value(); if ( value.canConvert( property.type() ) ) { value.convert( property.type() ); object->setProperty( iter.key().toLatin1(), value ); } else if ( QString( QLatin1String("QVariant") ).compare( QLatin1String( property.typeName() ) ) == 0 ) { object->setProperty( iter.key().toLatin1(), value ); } } } #else QJson::QObjectHelper::qvariant2qobject( variant, object ); #endif } QVariant parseJson( const QByteArray& jsonData, bool* ok ) { #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson( jsonData, &error ); if ( ok != NULL ) { *ok = ( error.error == QJsonParseError::NoError ); } return doc.toVariant(); #else QJson::Parser p; return p.parse( jsonData, ok ); #endif } QByteArray toJson( const QVariant &variant, bool* ok ) { #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) QVariant _variant = variant; if ( variant.type() == QVariant::Hash ) { // QJsonDocument cannot deal with QVariantHash, so convert. const QVariantHash hash = variant.toHash(); QVariantMap map; QHashIterator it(hash); while ( it.hasNext() ) { it.next(); map.insert( it.key(), it.value() ); } _variant = map; } QJsonDocument doc = QJsonDocument::fromVariant( _variant ); if ( ok != NULL ) { *ok = !doc.isNull(); } return doc.toJson( QJsonDocument::Compact ); #else QJson::Serializer serializer; return serializer.serialize( variant, ok ); #endif } } tomahawk-player/data/fonts/Roboto-Thin.ttf000664 001750 001750 00000371140 12661705042 021725 0ustar00stefanstefan000000 000000 `GDEF + ,[HGPOS.[XGSUB$= >OS/2)Vh`VDMXnvOcmapdlglyf/head@e6hhea -$$hmtx6Xloca.p'8dmaxpP;H name&Qw1post>6 %z_< .Ϯ7 s l &7 11j33fP [ pyrsf O:  AayrsT_oXI\_=*a}aauagaRaaaMadaglxlQRvr}]nx!/<~eZ!TT<`7U#?=6!^9Q[M9\aaazWBcyaq,agZaazl&a+S7+WIV~_r:s{Mzs+\5HVP~acQz2|l2bT]IaWam<%aQp\bWUmcWYJrG1Z.{7b:T!Wapa9d)z}z+aWbt&a5a>>aAz]WI1UVRL'B-6].\d"!F-JUQp-h_baacX;& mab,aa(z`+u:8(8Az{MaAp"aa,8!"+$9mGpTW#+TaW ,z8bq`'`^ (P-b- azzbK5??zL1!,?rh@k'A-)) \0,mM@e@M,/@d`M/fAuR{}s\l\jAA]|~caHrNo sj}j9AaevLj D }+B)V5\VcK~"(uzK\r%q{R. 94.Ppg\HEXU^ePA}kBPN+l]o4Z&zaW<rraM~8^d>ke.J_6_&d_dbdgZ__daWcyaa_<T9\Y_=ZZZZ=7&!!!!!!!/:TTTTTUUUU!9\9\9\9\9\9\9\aWWWWagZgZgZgZgZaaaa++!9\!9\!9\/a/a/a/a<zWWWWW~cy~cy~cy~cya.{ceZc!!x!<!aaaaTgZTgZTgZ<<i<`l`l`l`l`l7&7&7&UaUaUaUaUaUa?=S!+!^W^W^W2]TaW."""""""uzrrrrr"""uzuzuzuz K\rrr{{{RRRR..94PPP"R!(zBhM+=y5!^T7!6!}zbta51<gZa+751gZ1b`eZ!!bT/769\WagZaa+7Wclq+?=S?=S?=S!+T +Bc@M,!9\TBBWaV#9Z &d m/a!+&!9\!9\2]WWW&d maaTgZTWTW-Az!+!+!+-+up67az"b,!9\!9\!9\!9\!9\!9\!9\!9\!9\!9\!9\!9\WWWWWWWWTgZTgZTgZTgZTgZTgZTgZbWbWbWbWbWUaUaUmUmUmUmUm!+!+!+zza7(67-+u-+ubc& a&&bSc(?aa"b,!+67bt!m   ! "!#"$#%$&%'&(')'*(+),*-+.,/-0.1/2031425364758697:8;9<:=:>;?<@=A>B?C@DAEBFCGDHEIFJGKHLIMJNKOLPMQMRNSOTPUQVRWSXTYUZV[W\X]Y^Z_[`\a]b^c_d`e`fagbhcidjekflgmhniojpkqlrmsntoupvqwrxsyszt{u|v}w~xyz{|}~x&   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a   "!#%$&(*)+,.-/0132465879:mpcdhovnj~tiqfukzbml{q cdjkfg<uzstenwhlroxRh ~%'08@S_g~7Y #%/EOboy?M   " & 0 3 : < D t  !!!!"!&!.!^"""""""+"H"`"e% &(19AT`h7Y #&0FPcpz>M   % 0 2 9 < D t  !!!!"!&!.!["""""""+"H"`"d%'FA=;9713vy{bAeFj* n WNMKB0:(1vig\ߐQ%߂ިvunk_C,) ***6Xd\@Hd|&6    !"#$%&123456789:;jklmnoxyz{|}~ '  !"#$%&'()*,-+.o456789;p<=>?@A:BCDEFGHIJKLMNOPQqRSTUVWXrYstuvxw$% !&'()*+,yz-./0  123456{|  }~() 789:;<=>?&'@A"#$%BCDEF*+G,cd.-xzemnRh ~%'08@S_g~7Y #%/EOboy?M   " & 0 3 : < D t  !!!!"!&!.!^"""""""+"H"`"e% &(19AT`h7Y #&0FPcpz>M   % 0 2 9 < D t  !!!!"!&!.!["""""""+"H"`"d%'FA=;9713vy{bAeFj* n WNMKB0:(1vig\ߐQ%߂ިvunk_C,) ***6Xd\@Hd|&6    !"#$%&123456789:;jklmnoxyz{|}~ '  !"#$%&'()*,-+.o456789;p<=>?@A:BCDEFGHIJKLMNOPQqRSTUVWXrYstuvxw$% !&'()*+,yz-./0  123456{|  }~() 789:;<=>?&'@A"#$%BCDEF*+G,cd.-xzemn8tN4Xp@FZ".BVjJh.D2p  t  , B V d v D 6  $ 0 x (l8Lt*jFh Vbr>^"<Rx V~&b0Zf^,z:X0X2HV,>` " z !\!!"""h""#0#^##$8$`$$%%v%&&t&&'8'p'''((F((())P)h))))*.*Z**++p+++,,n,,,,,- -----. .>.r../&/\//0&0^0001:1r12 202V2223>334<445(5N5x566767788D8p88889::F:|:::;";R;z;;;;<>`>>??B?j??@@,@<@j@AAnABB^BBC(CfCCD"DZDDDDDDDDDDDDDDDDDEEE E,E8EZEtEEEEFFFFFFGBGGH\HHI"I`IrIIIJJJ:JHJbJKKzKKKKKKLLFLFM*M|MMMNN$NDNNNOOO`OxOOOOOPPLPzPPQFQZQQQQRRRTRfRRSSRSST"TnTTTU.ULUUUVPVVVVWdJdVdbdndzddddddddddde ee"e.e:eFeRe^ejeveeeeeeeeeeef6fffffffgggg&g2g>gJgVgbgnghhh&h2h>hJhVhbhnhzhhhhhhhhhiiNiZifiri~iiiiiiiiiijjjj&j2j>jJjVjbjnjzjjjjjjjjjjjk kk"k.k:kFkRk^kjkvkkkkkkkkkkkllllhlllllllmmmm*m6mBmNmZmfmrm~mmmmmmmmmmnnnn&n2n>nJnVnbnnnznnnnnnnnno&o2o>oJoVobonozoooooooppp p,pXpdppp|ppppppppppqq qq$q0q753#4.#"#5.5332>$Vj_j86eY7Zc56/[X\X+Qu\k;7dV7S}L6KvGU]1XAdRI& D\~ZP_8bTK' C]~ZQ^7/eulW%4Wqr+AW[4>32#".5332>=4.#"4>32#".5332>=4.#"'r'Hd==dG''Fd<>eH'76R86Q56Q76Q7'Gd==eG''Gc=>dH'76R87Q56R76Q6 -->mR00Rm>M=mQ00Qm=-XE**EW.M.YE**EY.=nQ00Qn=N>mQ00Qm>.XE**EX.N2YC''CY2rs/AT.54>32>53#'#".54>7267.'7>54.#"(=**PpGCgF$':C6=7H=C*^iuAep<)F]4oL? 4K[2.^HN :-8R6=^? "1[YY/DrR.*H`6.WL?yKpRذ*H54aV>oe\,(_X  )AkZL!>sX5oL\~:@E%)L:#)E\b#7532 7W*EW4>7 . 57M\h8Ji?3GT^2TqCJsϹu#+1ǢlƳp!+5J*W '>=4 &'7CqT1_TG3?iJ8h\M765+!pl9 1+#us@%73% 'j8`/-|6u5!B I/ !!#!5!3N7275 5\ 7'>=3:0%-,6]3 :qM=!5!*6i 74632#"& !! 1!! *#3e;`;}-}+#".54>32#4.#"32>58njip:9oiio96/_a`^//``a^.RUU UU}ϔQQ}{ΖSS{!#5%36^ l;u$)5>54.#"#4>32!Db@-ZWNb76=o]^k:4Ul7Z 6&Nuh0M}X/9eQXvD2c`DAg@32>54.#"#4>32#".5332>54.+o`d3*W]Of:6EuX^k:&Fe?At^7ph\D'6?pY[a3?q]o3Wr?Ez\60ZQ]h87f\:iWC!ah6.E^xJQc70ZQY~P%RF 3##!53!J6>A\nT6s*!!>32#".'332654.#"Dk;>O_3^r@-cnVsI71_Z:^NA5#=ujiI1dfUk=+!6#">32#".=4>3"32>54.H}kRi}Fdl8o\R]2.]6O0R<"HgjMGv5\+Kh7.54>324.#"32>4.#"32>(Hc>mWAD_uDcd22dcDu_DAWm>_c33cOZ00ZOS~W,,W~ IyX1.U{MJvS,,Svg"7%2>=#".54>32+72>754.#"LHUkF`n;=q`mv?Q"[hA7h_Wb51^"_;^C$OcjSGאe6;\q6>ÂAOXTzIx+&Q+'R-5E;{2z:!5!!5!BBK636r 0 55h DY;2;]!'3>7>54.#"#>324632#"&%<,.H2$HmHHsO,6;dJTW,6S8J:N !! 7ZPJ),LSdECrS/-RqCV\08bNBk`\3:V!! x;Re#"&'#".7>3232>76&$#"32>7#"$&766$3232>7.#")d| nDg@ KtU/G;11)-*IoN+ GnʰkA W,[VN W`c,b 9)S 0R;.]XO + aCB|gI @yiN-zlAxg~̐N(:S:"Ji4t7iꇺ ." YY}Tm?Dya&,;y! !#3#!7;!B!;MPe#,3!2#!2>54.#%!2654&#!n{B!A`>DvW1Bwc[\f70`aQ],\`?oV: >_xBaf4g/XQ?sY56)#".=4>32#.#"32>7 SmxÉKKxmS 6j}DD}jewH vt9]ڎ]:suSׄ܄ؙT1ek 3!2#!2>=4.#ܡ[[܁]uɒSSu]뎲]zW׀֚W> !!!!!!&@vd66N !#!!!646/%#".=4>32#.#"32>7!5!O}j}ΔRPxvJ 6 =mroFHtYqLC=6$]Z@oWGc;S{כW,47 !#!#3!36^666.X&!#3&66Z3#".5332>5l6AqY`m;6Kd: pu<3ik4fe #373 #66^NwFas7!!3766 3###WYO6+6aP`Y !##336T776TQ+#".=4>32'4.#"32>5M~}ːNM}~ˏN6CvuBCuv~Bk]]ڎ]]֛VVր܁כVVׁe#!2#%!2>54.#!6tv<;vuone//enog=o``l:69bJMe;1#".=4>32'4.#"32>5*OrH '*\3}ːNM}~ˏN6CvuBCuv~Bkhq$#]ڎ]]֛VVր܁כVVׁ!#!2#!2>54.#!6hG3YwE9]^/32#4.#"#".5332>00j|o}BJgjH6>qagm9(dlHNf>xhN-6*I`kp5`uA]EkWH"D[}XS\1>q`Qe91Sm;:cVK"Ea\Y^1)B`RIpT9$.St7!#!5!6Zzz6#".5332>5KjkL6Dw_^vD%{z==z{%in99ni#%73#3G;B;XX"P=7373#'#3BBX<VAD,;x=31=w<JffPB6 3 # # 3cD CA D7C! 3#3aB6HZVy)^j 7!!5!5!66,O5&#3!!J693#9:`:!!53#H6L[ #3#;+4+=fs)M!5!MN66#3;B \N.?!.5#".54>3!54&#"'4>32%2>7!"g PjQL{W/DuJ~]56;kWTf9 NkQit>)Jf&k5,O<#+NlAHzW1(Ga8?sW3+UU=70 !$D_<)Jg=1VA%/#".'#3>32#4.#"32>57h`Cq\H66G\pB`i78+WYW~W5@[yKXV+|ˑO3F)u1P9L̀lJ3Qb03XA$JlaN+%2>73#".=4>32#.#"?y`>6CmOiq;;qiNnD6<_{Cc_..`""GoNSY/Pw*wƏP0_]QyQ(Le*fLz/4>323#5#".5332>7.#"z7i`Cp[G66G]qC`h78+VYJx\@6W}VYW+(̎L 9Q1*F4O|lJ#?V2/aO2JlWN#/".=4>32!32>7"!54.1cJH{^Zm=@qY.WPJ"&FYlURhA 3]Pv,vǏPCzg?,eL"9,'@.,@qX QoAB3#5354>32.#"!!'IiBA  k{7NwR* 47yLN+A4>3253#".'732>=#".5332>7.#"y7i`Bp[G78j`;hXJ&?eXZ-G\qC`h77+WYLx\?4V~XYW+(̎L 9P1hu?0C($PU8h[)F3O|lJ%AX40dQ4Jl>32#4.#"#3KbwDRX.6+Nl@;bO>-66>9dI*-alDgO !8HPP#K 3#34632#"&66R !! :R!! qK#"'732654632#"&113pw !! :E4 yR!!  #373 #66MFI@x33#366N2!#4.#"#3>32>32#4.6,Om@Z\6 77IbwEKf~IRY/6,Om@@iT>+aKJo5T::eJ+x6]E(-alDgO !6EHGN>32#4.#"#3KbwDRX.6+Nl@;bO>-66>9dI*-alDgO !8HPP#K:Z N+4>32#".5332>=4.#"ZE}ij|ED}ij}E69ldck99lcck92wƏPPw*wƏPPwaOOa*_QQ_`N/#".'#3>32#4.#"32>57h`Bq\I66H\oB`i77+WYV}W7A\xJXW+|ˑO2E)0N8L̀lJ2Oa/3V?#Jlz`N/4>3253##".5332>7.#"z7i`Bo\H66H]pB`h78+VYJx\@7X}TYW+(̎L8N0&H)E2O|lJ#?V2.`P3JlN.#"#3>32#KxX9 66,& 4[|I;:ql}N=4.'.54>32#4.#"#".5332>GJd[\.2]U[d57,T|PRuJ#GiaZ*5cWbg56Ccy;PzR* %LG=0E_B9cJ+-QrE2\G*'?M&*G=68Kb@?hK*/Qk730P]46-PnAZZ4 7Zi-cqOcU&.Nh;+:%3#3:[5\:V:S:7373#'#3n;J7I=?6376>9-:7: 3 # # 3YDCCDZ++K:%73#"./32>?3v< %?bJ2K7*AQ;T"YO73 9N/4W: 7!!5!5!X66,70IG=..=4.#52>=4>7\zJ=_BB_=Jz\JjD 3P88P3"FkJG\wO?gI'B'Hf?Oy]-QlK9eQ9 :Qe9KjO#366GL=,>=4>7.=4.'73"3P88P3 CjJ\{I<_BB_<I{\u2՘9eQ: 9Qe9KlQ-]yO?fH'B'Ig?Ow\'#".'.#"#4>3232>5!CeD-JEF(%=9<$dq7 BeE-LFF'&?:9!4P6=pU3&8$!0?rU2'7##1$C_;O3##"&5463266J !! !!  &1%2>73#5.=4>753#.#"@?y`>6>eJ7cj77kc7Ie>6<_{Cc_..`""GoNO}X3Ts*sŽS4^XQyQ(Le*fL_%*!!53>'#534>32#4.#"!h G>x&  4`SZ_163WtBHtQ+ rX668P]-K6>gp;9fUXP&2a]6rd#7%#"&''7.5467'7>32732>54.#"QyyQ(DLRH(PvvP)GQKB)VooēVVooœVS]\R'Sy}S'LVWM(S|wR({wҜZZwwћZZ:y3!!!!#!5!5!5!3377B<<6>>A77F77 2333#3666sFQh#".5732>54.'.54>7.54>32#4.#"%&'>54.F*OqF?aB"@vfZT6N}K_e4!WunF'JjC:Z= @vgju>64fagb/"Xxp{B;1OtK%'dESY.,h5XE/ 9K_>RY/,dzmW%+Nj?9[K?@XzU6[E/ 9J]\KA"^NDV 4632#"&%4632#"& !!  !! !! !! {'C_#".=4>32#4&#"3265%32>54.#"4>32#".d*NnDHvT..TvHDnN+4w?cD##Dc?w/VwZYxV//VxYZwV/83\bb\33\bb\3hOvN'8fVOVf9'NvN4ZzEPFyZ3|qa~Z11Z~aa}Y11Y}aj_44_jja44a%4.'#"&546;54&#"#4>32%2>75#" 2=I(ycdm6*LkB8]D% 'MB0 /J; 0$r_m{EhuX[3U>##EgE1X,)+7TZ#:*&s%sDz#!5!605s7Xe4>32#".732>54.#"#32#.4=4.#'32>54.+s3\bb\33\bb\37/VxZYxV//VxYZxV/7QH%3 ;+F3+N:"7ZBj_44_jja44aja~Z11Z~aa}Y11Y}>ywKf )6@#$&B (*& "/B*8/D-/E.\{!5!s{5p'4>32#".732>54.#"%@V10T?%%?T01V@%70B'&A//A&'B01WB&&BW12V@%%@V2'C00C'(D11DH !!#!5!3!5!9]6E6C%626 6V"!5>54&#"#4>32!"):%ba5Q66%Fd?8\A$2A",'(D;4M[3E)1VA&8P3%HHG#P}:32>54.#"#4>32#".5332654.+G7S81M4-M9!5)Ha7:_C$PKVX)Hc:4fR36#>V2gq$@Y5GB,9!"=-+?'1Q98Q49cdH5R99U;(B0\Q-?(~}3#;B<`:32>73#'#".'#3Vn;fT)6) -/[RF4:~y\$9fRiz-I6c!#".54>;}x}@@}xF}ed}GP" 4632#"& !! !! QMu3#'2>54.'7 !@2(IjB(TE,0G.L'>-+F21#5%!+y#5%36% Q5a+4>32#".5332>=4.#"*OpGGqO**NqFGrO*6>_B?^>?^@?^>uI{Y22Y{IuI{X22X{I:fL,,Lf:u:fL--Lf:&tt8\''uzp'u'|'u'7,l0M)532>73#".54>7>5#"&546322,.R>%'LpHHoL(68`ITZ0%C]8%(N !! 8J><(,S`vOIsP+-RpD[Z,5aQL}mc35:C+_!! 2)!#!!!!!!AqHLQ'W66Zb@ 7 7   b'de''$l'l'w%y*7#"&'#7.=4>3273.#"!4&'32>5M~dCjBTZM}vHoB?DLI?puB65L<]v~Bk]:6Uڎ]RKS݆ڋNIGNVրtK25Vׁ3!2#!#!2>54.#smr;;rm66si`--`i=mWWl=C>d|>A~d>&E3#4632#".'732>54.54>54.#"6;pW58TaT8Ej=,]UF>KR(FvV/8TaT8)DV.AiJ)w(Hg?/H<8?N44QIHSgDLvQ+ 1,Kd79YKGN]>3OA:=E-/Q<"$Q\]yNDS_"&'3#".54>3!54&#"'4>32>32!3267%2>7!""!54.4GiWVZ/7lj"SY.58h\DsW9 OexB^i7.aik:AWoG~a? a]-Rb; ,Xs (UE,+PpEM\2Z*Jf;FwV1$HlHBkK(=rgS @a~IG;*3(4(;C^0Qk;t@pX Qh;)'?#".54>32.''7.'774&5.#"32>5"yv=nZeyC:oe8nbRtYNi{O@`Pb[+5gbQ\2}P{͒QKft}A+A*Z0{Dd'2-pHq0)P?'CtZPxIJlI/o!5!4632#"&4632#"&/ !! !! 4U!! !! Wo .<4>3273#"&'#7.53.#"!4.'32>5WE}i?p1T?c_kD}i;h/T?b3P760E-+c9ck9G,A*RSgck92wƏPF*wƏP"`vM@xkY!xQ_32#".'#34.#"32>5H\oB`i77h`Bq\I66+WYV}W7A\xJXW+y0N8L̀|ˑO2E)(lJ2Oa/3V?#JlH3##!##533!3!5!6^666(^95.5wwX:3#366:: ##333 #77E;G:  %!!573_޸7o9oX6:9: 7#57366RP;PU;UK#"&'7326=#3◆4 6oxR66 4 YTSKN%>32#"&'732654.#"#0WZ-%>" D"ox.Qm?]Y3 6:q5p{* 4 y]%=fHX:p -)#".54>32!!!!!267.#" LXsLKsXLH&@nGABLjyABy W܄0ܝW 66dSNywǐQ\?N5KW4>32>32!32>7#".'#".5332>=4.#""!54.\E}iTsSSoJZn>=lU?fSC>HrVRsSj}E69ldck99lcck9Rf@ 3_2wƏP2]QQ]2=rgS @a~I"0 *EJ2\OP[2PwaOOa*_QQ_@pX!Qg;H34632.#" A2)kzԜ 4,K'!#"&'73265#5354>32.#"!Q▇4 7pw'IiBA  k{ 4 \7NwR* 4!7#".=4>32>53'4.#"32>5M~}ːNM}HKa76@hK$'6CvuBCuv~Bk]]ڎ]~p*LpHRZ3Id֛VVր܁כVVׁW!74>32>53#".5332>=4.#"WE}ij?@O,73T=-1D}ij}E69ldck99lcck92wƏPNG">X9BgK,Dd*wƏPPwaOOa*_QQ_%>53#".5332>5F[56DnPKjkL6Dw_^vD+MnFTZ2+{z==z{%in99ni'#5#".5332>73>57ZB70P]46-PnAZZ4 79F'DjK+,jZi-cqOcU&.Nh;%=W6cK:#"&'732654 7pw:m 4 WN#/2#".=!46=4.#"'>2>7!Z~JGz^Zn>*=lU?fSC>Rf@ 3_NNq@wŏO=rgS@aI!2*EK@pY!Qg;#'#573D@( y73#'535;9 #".5332>5&HgAAhH&65V>  4632#"& !! u!! r4>32#".732654&#"r/>$$>..>$$>/6B33BB33Bf%?//?%%>-->%4@@46BBGuX!3267#"&54>79$C5 $(4=.CP#8H$93232>50@%$6,&(.,!70@%#6-')-,!*D1! /)B/ "0 3#3#PILE04>32#".732654&#"%21$$12%:/ ,, /,!!,++""##.#3BK#3#aC=Ip{?5>54.#72cZ,DT(>iL*5H)c28+ 1(<%(:&67#3#3IPEL  ^ 4632#"& !! !! t*3#5?g5*Vy3#'4632#"&%4632#"&O+; !!  !! y!! !! "w!#!6^z:L3!7!'AiRmP6PP/!5!#".=4>32'4.#"32>5OM~}ːNM}~ˏN6CvuBCuv~B5}]]ڎ]]֛VVր܁כVVׁW #3#;A;jPp 7!!!!!!pwlWWM6665!#!#!6^6z9 !!5 535!! #%}66,q,5dR$/2##5.54>7534.#2>%SS7ߛSSߍ7&L̀̎LKˀˍKTmnTSmlT3dIKIceHJ>53#.533iG6Ot7tO7G~h7E{l}ćH`I}k{Ez3%>=4.#"!5!.=4>32!!>gBAzjkxADj>MnA}c=KtsL=b|@pNNdۖwxЛXXxwۚcN6%sێuZZuۤs&6z}N'=3267#"&'#".=4>325732>7.#"!- %VXG\rD`h77i`Dp[F,+VYN{Z=8X}SYW+:AU41 n3X?$M|ϒO'Fa;lG0TqA_:}hCMlc7#E2#".'#.574>2>54.#"32>54.+5GYf7miQwP'Auc2mhZ 6>jK[R'(RXFzZ4LfyB`b25fc8cRo,He}Fah6*@+ ]q>S,RuI:rX77bQ/N80ZQR[18+a: 3#37m:]6]:N67:\8W 5K4>32.#"#".=4>75.32>=4.'"5^MOH"=AM332#4.#";#"32>53#".t|}8V:;l_WuE6;eN^W*)W`2a[Xg87NR_sA"g3@L)HpM'+OpE9\A#%BY33T: 7p~:]B#(Ha9RyO'*Ps&xw0'>54./.54>7!5wFBU1)UYY6P3/6%0**=(Zf_.4[G%Uzh.5P@3 ,8%0TD.  +32#4.#"#3/WZ-6.Qm?_X0 66Yq0gqGmT!@jIe:"/#".54>32!54.#"!32>58njip:9oiio9/_a`^//``a^.RUU UUqq}ϔQQ}c{ΖSS{:3267#".5-;!0   4R9:GX13@lQ+>?(23267#".''#'.#"'>@T7! v &.! $ 6G/ g;A ,:*! $7OZ"-F03+AL!3-N9 2F.#";#"'>54&/.54>7.54>32?Gc],-bkzzaO=qcN8O1#04!-*IXNeK1ZM@bB!>qcH>a*Ie;<_B$8+^i[\@ (3",I8& !!0>%)5FjkRgH9IW0R}S*>:#3267#".5!##5!j-;!0   4R96, GX13@lQ7`N1#".'#&554>32#4.#"32>57h`Bp\H6AqY`i77+WYfV&A\vJXW+ |ȍL2D);su54.'.=4>=Nm@68^zCcc1>scAT2*1#*$(D4iH?uN/_`T|Q'Le*fwQ#/>)!F<- #0;!32>=4.#"]=aD$D}ij}EE|i "09ldck99lcck9\vP+wǏPPw*qNaOOa*[MM[I:!3267#".5!5!L1D)/ ;>[<zqG_92!HrQ7:32>54'3#".5*Kf=c[*@0B#7khK|X1:G_T'R]v3pfvǑQ.bl!;'7.54>74>32#>54.#"|:+H_55W="*i 3>Xl=Cł66wv:4]N,!Xxag", hN^Z[1G/Ovnɜ_5Y_`O#2a:)>54.'3#.53]jF,)7 C2(<Ȍ6qI6Bwd:.MhzEH|q3-buYݡ]tK͆D:@32>5332>54.'3#".'#".54>7e 7)$MvS@nQ-7-PnARwM$)8B2(0]X;gT@@Tg;Y\0(1:=UYO2myym2OYU=5sirNBgGGgBNris5q2A#".5732>5.=4>32>74&#"q+258i]Xk;62[NUX.=,Ia5KtN(5^F/m|)M;$ xj06mpEe^.([l M~`KwR+:la ~PqGR AdD*>32.#"#.#"'>327,7?J- 6 +3597942, 6 -J?656>W6 ) *QFoPFQ* ) 6W>E'0;?##"&'#".54>7#5!4'!32>5332>0) /]Yv''vY\0 ) G66G$MvSAnO-80Ok?RwM$0k\rNNr\k07rrYO2my qd/OB'!>32#52>54.#"#!5!)^a`+fJ0IfRef3BrZc]6>Zz!1n,b^VA'3AkEoc.1+!z6-#".=4>32#.#"!!32>7 SmxÉKKxmS 6j}D|D}jewH vt9]ڎ]:suSׄI6]ؙT1ek6 -!2#!!+532>7!2>54.#quw;54.#6quw;32#4.#"#!5!`jsz?67lghg6*Zzy%0kzsi_,!Cz6 3!3!#!666zP]?!!2#!!!2>54.#quw;7!!!x76L4=A9, = =Q]*6m]?h//D#### 33333 #*6HPG6FQH00VV1d_@2>54.#"#4>32#".5332>54.+5Vvl23lrWuD6N`pF%JlHMp76;& 'Ph #4Q@/"*PzY6Bpz!73#"&'732>?3B5SuR6+7WE7AC-Q#_W=3&AT.'Fp*732+#5#".54>;5332>54.+";TT7SS7̎LL̀n͎LĹYuvϛZYvuКZOnlOPlnNK 3!33#!666zk_j##".533267j6^gsz?67lgfeP#0jz_i_, !3!3!76.zzP!3!33#!766zzk_!!2#!!!2>54.#%mquw;54.##3quw;54.#quw;=!5!54.#"#>32#".' Hwej}D~D}j6 SnxÉKKxnS ke1T؄[6KיSus:]ڏ]9tv3#".=!#3!54>32'4.#"32>5M~}ːN66GM}~ˏN6CvuBCuv~Bk]]r#c2]]֛VVր܁כVVׁ_!!#'.54>3!3!!" >[FlI&B{n1]Thj5kxIayEio:PM[34ba(>2#".=4>7>53>"32>=4.:j}EE|ii~ED~o]uC6TmJ<Љck99ldck99lGi*nJJn*7w &2;!j| Pqes6FvT*Y{II{Y*TuF:"+3!23#!2>54.#%!2654&#!t^i8og8_F'8fU}L{V..V{L}R:"EiHZ.FZ2InI% -7!3#!#!!DA1EV6,P%8C"$6 \ܟx>tϝs):#### 33333 #6G@C6E=G(  mN:2>54.#"#4>32#".5332654&+5[O$%O~YBx\77@lLZd54O4vo;jZGuJ76^KD :T33YB%#A\9EpO+'MpH)L@3gIsO*'OxR9aG(t~p7: 3##377c66:&:&: ##333 #77N4 Q: ,:#!+532>77!@gM #6O7 :KB51yП: %3###3A7*6JR.:: !#!#3!37c667: :!#!#!7c6 :(:!#!5!W6mr7z`R'=S4>323>32#"&'##".5332>7.#"!4.#"32>5z5c\Jd 6cJ[c54c[If6 gI[c48(R|S+E3# 0G3T|S(j)R~T2F0 #3D+T|R)ϒO) (Oπ|ȍM'7'M|lG] MllM Gl: 3!33#!67[6:Au:!##".=33>736OTU$id16)V])YUJ6 #[{jO: !3!3!77.6:: :!3!33#!77.6l6:A:(:!!2#!!!2>54.#(Y^b33b^qYXP&&PX:d3Y{HI{Y2d/Nf85eN/#:!2#!3!2>54.##3Y^b33b^q6YXP&&PX663Y{HI{Y2:./Nf85eN/::!2#!3!2>54.#Y^b33b^q6YXP&&PX3Y{HI{Y2:./Nf85eN/zN-"#4>32#".5332>7!5!.>uiNlA88]zCbc2<3c HrQVY.Pw*wƏP/_aU{Q'Ld6dJN2!54>32#".'!#332>=4.#"fE}ij|ED}ih}G669ldck99lcck9-wƏPPw*wƏPMt :aOOa*_QQ_:#!##.54>33!!"6|@>^@!0\YAfHSvK#:> 7Qi32#"&'732654.#"##5353!.KbwDRX.$>" D!px+Nl@;bO>-66LQ9dI*-al몲 4 gO !8HPP#K6pN-%2>73#".=4>32#.#"!!*?y`>6CmOiq;;qiNnD6<_{Cc_/E/_""GoNSY/Pw*wƏP0_]QyQ(Kc6eK":+!2#!!+532>5!2>54.#Y^b33b^pClM #6T:YXP&&PX:<0TsDDtT/KB60zϟ+I_41^H,:#!3!2#!!#3!2>54.#7Y^b33b^pc66YXP&&PXx<0TsDDtT/B:+I_41^H,%!>32#4.#"##5353!.KbwDRX.6+Nl@;bO>-66L>9dI*-alDgO !8HPP#K6: !3!#!76:^:i0#".'#".5332>5332>5i9dQ>nZEEZn=Qe961WvFEwV161VwFEwV1qp8 @`AA`@ 8pq`d44d``d44d`:.#".'#".5332>5332>56_M:gVA(sM`66.QoBBoQ-6.QpABoQ-:_ll5<[54.#4Y^b33b^q6IYXP&&PX:d3Y{HI{Y2:6n/Nf85eN/y5!54>32#.#"!!32>73#".=!#3#@KxmS 6j}DD}jewH 6 SmxÉK66,]:suSׄ.6xؙT1ekvt9]xfN5!>32#.#"!!32>73#".=!#32>qfNnD6<_{C`_1.`d?y`>6CmOiq;66LrK0_]QyQ(G}`6fL"GoNSY/Pw:" !#!#3#!'6پ; B!;hP2^^$: ####53#!' 6;4:kϱ67uu8!3#!#!#!#3!'5NB!;6پ;D66 h2~P^^:!53#####!#3!' 4:6;66ϱ67wuuu:#)34>;!32#4.+##"!378i_'*&`i76*WZ6ZW* aW'='Wa{UuH I HuU{z"":%+32#54.+#'#"#54>;!!37,`i76+WZU6XZW+78i_/yj[BI'WaTuH  HuTbV'7Q )/!;!32#4.+##"#467!#3!37"'*&`i76*WZ6ZW*7NI66 ='Wa{UuH I HuU{t)I6""\:-3!;!32#54.+#'+#5467!#3!37/y,`i76+WZU6X V~S(7LH66[BI'WaTuH  "HsRs*:7Q iBK2>54.#!5!2+".54>;2>54.+573#'53vk21fmPl{C%IlGTlR6J.%6? LB,:^DRj}D=vq{5;9,RuIEsT/60^\9iXD"\c3$5"!=1#4 +>N,)H5-UyMYP%8 pb,>G2>54.#!5!2+".54>;2>54&+573#'53f\+*W]^l:tp{J_)6J.%6?LB-;^C)\l;z5;9I N,)H5"?\9{j8 "/#".=4>32!54.#"!32>5M~}ːNM}~ˏNCvuB#Cuv~Bk]]ڎ]]RT֛VVրRכVVׁW N +4>32#".5"!.2>7!WE}ij|ED}ij}Eck9G9laak::l2wƏPPw*wƏPPwP^^P N``N#%7>;#"#3`:HX4  AA=xA(;VV>F`9/ ,ZQL+M%7>32.#"#5368 \D/ ($$$4:圜]J- ;3s9p>3#3#3#".=4>32'4.#"32>56666 M~}ːNM}~ˏN6CvuBCuv~B 323ȏ]]ڎ]]֛VVր܁כVVׁWn 3#3#34>32#".5332>=4.#"L6666 E}ij|ED}ij}E69ldck99lcck933wƏPPw*wƏPPwaOOa*_QQ_iDbp2#".'#".54>3"32>5332>54.#7#".#"#54>323>=3Qe99eQ>nZDEZn=Qe99eQFvW11WvFEwV161VwEFvW11WvFy:XG<;A(0F-7 =W8-IA>ER4 6%8pqqp8 @`AA`@ 8pq?qp864e``e44e``e44e`?`e4,)1)3B#.S?$,2,  (cf 6, B`n2#".'#".54>3"32>=332>54.##".#"#54>323>=3M`66`M:gUA(sM`66`MBoQ..QoBBoQ-6-QoBBoQ..QoB:XG<;A(0F-7 =W8-IA>ER4 6%I5llll5<[5332>5569dQ>nZEEZn=Qe961WvFEwV161VwFEwV188ggqp8 @`AA`@ 8pq`d44d``d44d`67!!#5#".'#".5332>5332>5 66_M:gVA(sM`66.QoBBoQ-6.QpABoQ-x88_ll5<[32#.#"37vIN}jK6oGCzh7JkNf:tu\PazN$#.=4>32#4.#"3m6ht=?uhNm@68]{Ccc10ab6JRv*wƏP/_aT|Q'Le*eNb>%#%7%73%*!'ݿf&%(#c%+DDFMC#5!53:7RylG2>32#54.#"+54RE>AI-7X= 7-F0(A;32#4.#"4>32#4.#"4>32#4.#"4>32#4.#"4>32#4.#"4>32#4.#"4>32#4.#"4>32#4.#"6N11N7p &% 7N01O7q &% I7N01N7p &% 7N01N7p &% O6N11N7p &% M7N11N7p &% 7N01N7p &% 57N01O7q &% )E22E)$$)E22E)$$ )E22E)$$)E22E)$$)E22E)$$)E22E)$$ )E22E)$$)E22E)$$c "'#'37%%57%'%'7 z`F: z`F Mt ZADE&+A`B<aR |bG; |bGDEFEG+V#!!2#!#5353!!2>54.#4Y^b33b^q6IYXP&&PXz$3Y{HI{Y2z6/Nf85eN/^7#!2#%!2>54.#!=))6tv<;vuone//eno#a#=o``l:69bJMe;`N37%#".'#3>32#4.#"32>5,-7h`Bq\I66H\oB`i77+WYV}W7A\xJXW+?!Z |ˑO2E)0N8L̀lJ2Oa/3V?#Jl$ #5!#!366(6yt8!#!386V6:t!32#52>54.+#!v͗V-GaO``1Nj6^z;ם6zwmS13VW{9:!32'>76.+#!6nPSm`}JGb6[/i{2~yb*Tjs2j^+: #####3333]*"Gf7667v3D/V3: #####3333R|e7777o>::  ^V##!5!33 #h6 +jG|G9z6L*&(:##!5!33 #7{N4 Q6  !!!#!#3`6^666.: !!!#!#37c66G7:!32#52>54.+#!#!v͗V-GaO``1Nj6^6F;ם6zwmS13VW{9zb:32'>76.+#!#!nPSm`}JGb7c6 ^/i{2~yb*Tjs2j^+:7M"&'#".=4>3"3267.54>323>54.#"gOEZQAxf]i8NρF|6TZ05cXWf8BW(-ZXJqM'-U}PNzR+# "lv̖V6Sbc,{^sNNsUpYt''r[c~HIaeN4H"&'#".=4>3"327.=4>323>=4.#"eP>5yEj|D0WzK7dM.8lceS)LjAAkL*h^3r@Ս1K3">W59W<  Uv^fH6?qZ`iM%HlbvBH}cfK= [oEiToA738856OTU$id16)V])YUJ6, #[{jOj33>32#4.#"6^gsz?67lgfe1#0kz_i_,T c(5".=.5354>32!3267!54.#"vדK7{yPszǍL=ƉN"Uo@{tp{AUԀ s 5ޠYUgnRRE+:-F3sʔVVrDN+9".=.53>32!32>7!54.#"3cJep7QQ OxVZm=@qY.WPJ"&FYl*3]QQgA Pv,sYlgzCCzg?,eL"9,'@. QoA@oW!32#52>54.#!#333@v͗V-GaO``1Nj66BG;ם6zwmS13VW{9/V:"'>76.+##333fH !7QoH`~IGb77NG5ju!OSRG6*Tjs2j^,:K!3#"&'73265!#658nx^6= 4 IK:!3#"&'73265!#74 8pwc6: m 4 j:/ ,2#".=!54.#"'>2>7!ؑNV|ݜU]?ΏQ$Xs{yHH_썬dU{|z֟\VD+;.^aHsɔVz%32#".5332>54.+5!5'At^SO89gY[a32a]eDah73hnQc70ZQi`-786zu:'!5!32#".5332>54.+5e mw?At^SO89gY[a33!"36Xuv<;wuroe//eo(P=lWWl=;b}BEb:?+!".54>3!37>76&'3#'!"3uv<;wur6!2)9]zCoe//eo=lWWl=XXX\a_/6;b}BEb:z-0I4>3233>76&'7#&'#".5%.#"32>7.5z7i`BoZG7;T5TX.)8+=kWeH`yH`h75V|WYW++VYMz[?ϒO8O0AQuM%FqddbgψDhw/Q;"M|0aP2MllG&C\51@4.+532654.#!5!23>76&'3#.'$MvR1jug(OsK?T4 ;U4;eQ?*+9  ?o^?fI)HxU/7CnM*6E|cD ?Wn>Y76&'3#.'54&'#'32>54.#!'!2+I8JqN(')A+54&#!5!2#.=4.#'>=3xf-!&JmGRpE (5#&3c_ :0%-,668\v>6:q`G>Xp@?n%*xKIuQ+2]3 :qMhf:+5'32>54.#!7!2#.4=4&#'>=3Z~O##O~ZI0K47J-;:0%-,66=Y9:Y> 7.TE4 /@O-c/O03. `jw ]3 :qM@/-3>76&'7#.'!+532>5;T5TX.(8+=kW?fH)t)\i5)[S'QuM%FqddbgψD)Yb!߿^6Q':-!+532>5!3>76&'7#.'a9-76&'7#.'!#3!c ;U4TX.)8  =kV@fH)66%QuM%Fqdd1ceg3ψD)Yb\K;:'!#3!33>76&'7#.'ar6666M0OyS+*8  :fR;^C&:QuM%:k^__/__a0vs9)Yb-".54>32.#"3>76&'3xǍNNxf<AXr}AA}rcg7:M^^! 9!#Ytu͙Y7eZZYX\qo7N-%>74&'3#".=4>32.#"LZyJ! 7 9cOns<9ohZ).yFe\+1d!,I`69z9:w;RyP'Xl*lĔX 1VX*YV!!5!!3>76&'7#.'F ;T5TX.)8+>jV?fI*z66QuM%FqddbgψD)YbA{:#!5!!3>76&'7#.'`L32#4.#";#"32>53#".MvP(KzhR6G|`}u87v|BByl}E6+JfvAz̔R"DXi9ac38h]QZ02Z{IIuR,8%PYQZ07bQIx^E.6hFL@mGiff@M'BNBNe 467#e:0%! 6]3 8;A'jM '>=3:0%-,6]3 :qMi/J 7'>=3:0%-,6d]3 :qMdGg@@`&ffM&gg/Ji 7'>=3'>=3:0%-,6:0%-,6d]3 :qM]3 :qMA !#!5!3!36%6:vR`/)#!5!!5!3!!!/7;;7`67v73!4>32#".5+>&'>,,=''=,";++;"F":**:"i&&i&&'sv)?Uk4>32>32#".'#".54>32#".532>=4.#"32>=4.#"32>=4.#"'f'Gd<-M>/2@O,=eG''Gc=-OA2.>L-=dH' 'Gd==dH''Gc=>dH'*6Q87Q66R75Q65R87Q45R76Q5%6R87Q56R76Q6--e=nQ0/@''@/0Qn=N>mQ0/@''@/0Qm>>mR00Rm>M=mQ00Qm=.XE**EX.N2YC''CY2N.XE**EX.N2YC''CY2-XE**EW.M.YE**EY.rl#53&F'F0rrj# &F%{A57'n--rA|>32#4.#"# )\CmK)5"?X66XC/ 7MY#KtQFa;!:R1%c%2!!!53>/#53'#53'4>32#4.#"!!!G>x& 4`SZ_163WtBHtQ+rjX668P]-78gp;9fUXP&2a]89#!2#%!2>54.#!3#3267#".5#5369uv<;wuod//do-;!0   4R9]@r[\r@6?gGIg>7GX13@lQ7$H3!3267#".=#535#5354>32.#"!!!9D~n:o33p9vōONv:k93p;n|D9H2{~A9GЉ278$όF:>|}&8r+AW[#".=4>32#4.#"32>54>32#".5332>=4.#"'|$A[89dJ++Jc88\B$70J11P99Q31I0!'Gd==eG''Gc=>dH'76R87Q56R76Q6--,M;"0Qm=M>mR0";O,:-*EY.M-XE*-9o=nQ00Qn=N>mQ00Qm>.XE**EX.N2YC''CY2rN$1".=#52674>323>=4&#"S|R(?OR<7P3+H5/\WAiNOsK$P?PR@wfI5"UYa4,QvI _3eKpD/3K"/9!5!4>32#".5332>=4.#"##33D*OpGGqO**NqFGrO*6>_B?^>?^@?^>67766vI{Y22Y{IuI{X22X{I:fL,,Lf:u:fL--Lf:JGo; ##33####5!47EE78LK=5 9N(%#".54>32!3267"!.UcHt`D%)IcuBgN7N^YK95^5=(Jf}MM}fJ(Ru/3;;?*A94=s''u<'L'u''u}';'u Aj2*@2&.#"'>32#".=4>"32>=.ELmW!Duf2RG?8RmEkNHni|EE}jcl99kciq; ?j$?W3W.)/*]HaJn*nJ6I{X*Y{Ibv1%dZ>A#!#!A66v9 !!5 535!!T%2HD6+34,5!5!C6A %73#5#5352z:54|P6N1Mi#".'#".=4>32>32#4.#"5432>5!32>5.#"?uiLhR>,+>RgLjv??uiLhR?,,?ShLiu?62ccBn\J8) /KjWdd22deViK0 (9I\nAdd2lēW1Pfke('fjgP1Wl0lÓW1Pekf((fkeP1WlWV*EX[XD)*Gm~mIUXXUIm~mG*)DX[XE*VWaKR#"&'732654632.#"4 7pw A2)kzY 4 - 4e!C>363267#".'.">363267#".'."o0zB(;43 119%Bv00zB%71/ 46<(Bv00zB(;43 119%Bv00zB%71/ 46<(Bv0EEM  QEBEM  QEEM  QE@EM  QE!'#5!!5!!!!/$/ 6a66LfZ@@B0jf!]@@B1D 3 # 3\3[yz')hg' n}:#3#37766yy5t %'>=30&%#"6'Ox+ 1^AOBt!3#5354>32.#"!!!#3-SxK?E8dF&@667RV- 4 'IkD7:Vr$2DJPV\fjnrvz~#".=4>3232#4&#"32653#"&53326533!5353!#%5!#532654&'#5!!5!!5!5!!5!!5!3254&+#535#53#53%#535#53#534!=T34U="" F-MNHENNEpENNEO(?,QS/,6,;qqtt,'&.Jtttttt8qqqqqqP)~~~\s)-15 4>7>54.#"3>32#33#3#AD"<-+QvJ@nS0$985(#4"KR11(!4?J)@gI'AeF' @44M+3B0[L V ")5>54&#"#4>32!"):%ba5Q66%Fd?8\A$2A",'(D;4M[3E)1VA&8P3%HHG#cKv7#"&'7326=4 7pwvϪ 4 K:0 3#'#3#CPTTI?ߺL~#".5332>5#'3)MnFFoN)7:]DA];S^2S" 467#$,6\54.#%!2654&#!N_j9kf9aG(9gVdN|W.(Q{R)$JqNc 3L^4NyQ*;$Dc?2[E)6w{zyz'#".=4>32#.#"3267 Gs_hwAAwh_sG 6ı[i:9i[f]^/KssK0^\BxggyC  3!2#!2>=4.#[pNNp$e|FF|dKstKVD{ffzC !!!!!!N;nC67# !#!!!Y6/y57/%#".=4>32#.#"32>7!5!Ej]lGD|hfn@ 6 4\`_l;=qbL}_?2,JuqI4ZzG7fM.Bvbg{D"(I6+ !#!#3!3+6666&1!#366K"3#".5332>575^MSb66?nP.R[0)T~V)MoE$ #373 #66LVH_Ofz`7!!3J666= 3###M6+6;s%>4 !##334666630r%+#".=4>32'4.#"32>5%C{ll|CC{ll|C59ldcl89lcdl8uJJuuKKugzCCzgh{CC{h#!2#%!2>54.#!6dg44fdV]U((U]2Z~MN|W.7,Kf:32'4.#"32>5%y&JUl|CC{ll|C69ldcl89kcdl8;%JuuKKugzCCzgh{CC{h!#!2#!2>54.#!U6[n=+Ke;J9OxP(2]R/YR=jS9  /Md5BlM)R=4.'.54>32#4.#"#".5332>(Yi`k9?oY\o>64_SW\0"Vn]o=CsXQT5MxCQb75SD77KeFClK(3\L?jN,&@U.-MC:8OkIGoL($Q`TpC#AZ.!#!5!<6;VV7#".5332>5At[\sA59ePPe9ba11abR~V,,V~R%73#3;,>,32'4.#"32>53c_^d54c__d46*TVVT)*UVVS)uCCuuCCucs??scbtAAtbT!#5%3T6 E{;g")5>54&#"#4>32!=X9R}U+66dZP_5.L`23>h[Q&x,Nk?G~_7*PuL6lkj3~\>32>54.#"#4>32#".5332>54.+ueUY-%MxRFzZ46>iPU`4!=Y8;hUKzL68cOP~V-8dSem'DX26_F)%Ec>IwT.+SvK,RE7oNyT+(TW?hL*%Ee?Eb?H 3##!53!6p@)Im76!6r,!!>32#".'332654.#"G97HW0Uk<+^hAhF77Vm=-WR9WE8^/8A 2^UTh:'PyRAeD#FsQ- "!6#">32#".=4>3"32>54.nÔ\FYi;[a26cRXm>^;o\A 5^NIvR-)S}6@Ƈ$>-;gQTl=7.54>324.#"32>4.#"32>v#>W4";fPOh;9bKMa88bNKb8$3YwDFwW22WxGDvX3c1VE47K]5PxQ((QxP6]K64FU1LuP))Pu=bF&&Fb=@cC##Ccx8^D&#B_<9[A""A[Um!6%2>5#".54>32+72>754.#"*Ł?H[k;We7:gW_f6FڔKwW9 /XPM|W/,Sy(Lw+G2@lNTpB=uowV6,FV+1de35x56Q66Q67Q67Q5GAiJ((JiA@iK))Ki@!#5%36% Q5ae#".5332>5'3#'HhBBiH'66W?54.#"#4>32#".5332654.+G7S81M4-M9!5)Ha7:_C$PKVX)Hc:4fR36#>V2gq$@Y5G,9!"=-+?'1Q98Q49cdH5R99U;(B0\Q-?(A %3##5!'3!5Y32#".'332654&#"/n%`>:cH(AeF.XH06#7G(ofsj%7,$~3"A]:8`F(6R8*?,og[o  k4#">32#".=4>3"32>54. K`: uM>`A!%C^:>gJ))F_69cH**Lh?Pmp8$2<7X=!$324.#"32>4.#"32>oSD'@.,Lg;T01T=##=U20T=# 8L+,K78K-+K7 JA_ &2>#6R77R6Hf^A4P66PQ'@--@')?,,?$<,*=&%;));Nl 072>7#".54>32+72>754&#"ZT) xM<`C$&Gc=.ai.K8& ue2O75M(0W{J4A+J`59eM,K^n=0*6&&?S,*O=$l!5!K6(3#'4>7#U;4$2 72:9g%E;-)![Ab.#3#4>32#".732654&#"PBr&22%%22&34$$22$$44''42&&2&22&(33+#".5332>55>54.#72&Fe@@fF&55T=:S4`V*BP'57#".#"'4>323265$Da=>aC$62O97N28*8!(4+, ->0*8!&4--,?/N99N/"<--54.'/$&2.=4'"$.:>A!!2"0 !;630 #'#73#'3TTP?I人_ZJNAWm##"';2#".54>7.54>7.=4>32!32>54.+32>=4.#"@I9fSGA!$.Oc8A~wdf3'AU."+ ,G1:fSIA68eL,*VZjp:%MxR/VzLKyV./VzKKyV/.TBtX3 &."+ $GkG9v`<-Nh;7\K9D83+#;HS,J}Z3B3K]10U?$3Qb/1T>$h1`K//K`18hO//Oh8F32#52>54.+cv͗V-GaO``1NjF;ם6zwmS13VW{9c&]$B/233267#".'#.#"'>r7O9(j?s%(/ -  ':11w;")6* 0B3KT V/*RB(2 A`@6+H54zN2#".=4>323#32>75.#" @cR`h77i`NaB: (9K^:YW+PuHM|ϒODuV lGAu_x-hf^G+MlW &A!#".=4>3:.'532>=4.'.#"*^lyDxD}ij}EMr!+0;v|9ldck9(7=464lx@5;H\@q*rMMr*nK:oaO6D]LL]*SmK K~W +#!2#.=4&#%!2>54&#!6rz@&KmGRoB(5#%þExg-fT1ba:q`G>Xp@?n%*xK68\v> ##333 #66jG|G9L*& ##333 #v66G F)_{ ##333 # 66 Q3Q9;+%i ##333 #66I>H3r#'#735>54.#72TTP*:"!5B"5Y@$-<"人k] "7"3#1 ,r"#'#%37#".#"'4>323265TT<#0(4+-!%)3#/'5--&)人߽ 3%!'!A09+!(!10M #767!5!\p6alzk}[[lF!6/#".=4>32#.#"32>=!5!IvxǏOPxvJ 6 =mroFEoa}HC$ՏH]Z@oWGc;S{לW7{ŎK7+#".=4>32#.#"3267TmؚS&GfYmT6GtgxČMMxqv=o:e\3>vp_k9f32'4.#"32>5V~}՛WW}~ԛW6LvuLLuvKll:llff32#.#"32>7!5!Lxf]Uׁ{L 6 ?rvPv]@"S؆UlIC=6%s&o@oWGc;0Vy[(l,47 2#".=4>32'4.#"32>5-RvH+'an}՛WW}~ԛW6"A]uNuLLuvKoȦ*# +l0 oo^{W/i2ff 3!2#!2>=4.#eeIޣ\\ނf:fz_=_F!#5%3F6G5 ")5>54.#"#4>32! D7A# #P]Ul>6Dycd`.d` "2VKA=jN-3]QTpA4[{FaYJ"!5!#"&'732>54.+5hO;zCUzkjUeÁAHeNW6<'Bwg~ĆE34-)3Hclc.> %3##!53!6QL@56I$j'\e(!!>32#"&'732>54.#"T:KHuao=#".54>32#"&'72>754.#"j~FTkF`p>@s`mv?OtEC:>Cd[hA7h_Wb51^Yi;^B$OcjSHא^" 3 ;\r6>ÂBOXTyJo+#".=4>32'4.#"32>5oPfeRQefQ6Gz\[zHH{[]zF*בJJ׍8בJJ׍{BB{R{CC{6 #!5!7ifsy6/#".'#3>32#4.#"32>5=p`Cr]H-6G\pB`q=81_YW~W5@[yKX^1|ˑP4G*u1P9L̀lJ3Qb03Y@%KldN+%2>73#".=4>32#.#"2?y`>6CmOiyAAxiNnD6<_{Ccg44g!"HoNSZ/Pw*wƏP0_]QyQ(Le*fLd/4>323#'#".5332>7.#"d=q`Cp[G64G\rC`p=81^YJx\@6W}VY^1(̎L 9Q1*G4P|lK$?V2/aO2JldLN-C4>3273#".'732>=#".5332>7.#"d=q`Cp[G.8j`&RSO!#DGM-XZ-G\qC`p=71_YLx\?4V~XY_1(̎L :Q1hu? /8h[)G3P|lK%AY40dQ4JlZ N+4>32#".5332>=4.#"ZHef~HH~efH632#4.#"32>5=p`Bq\I6* 4`q=71_YV}W7A\xJX_1|ˑP3E)`vL̀lJ2Oa/3V?$Kld`N/4>3273##".5332>7.#"d=q`Bo\H16H]pB`p=81^YJx\@7X}TY^1ϒO8O0&H)E3M|lG$?V2.`P3MlaN+%2>7#".=4>32.#"9m[C5IhFiq;;qiFiK5A[o BgH%Pw*wƏP&KpJ @aA Le*fLWN!-".=4>32!3267"!54.1cJH{^Zm=@qY]KEVhTRgA 3]Nq@vƏO=rgS @aI=G*5'-@pX!Qg;yLN-C4>3273#".'732>=#".5332>7.#"y7i`Cp[G.8j`&RRO!#DGM-XZ-G\qC`h77+WYLx\?4V~XYW+ϒO :Q1hu?68h[)G3M|lG%AY40dQ4Ml*?2.#">32#".=4>"32>54.A@<24:$YxGMcyDdl8#HgjMHvR1TsB`hyBJ{YXrB_'<%2>=#".54>32#"'72>754.#"ap=QfxB`p>@s`hrCFVc?4cZWb51^"Qهe9]B#OcjSHאV@5:\q6@ÂBOXTyJ 3!2#!2>54.#ٝXXف]uƎPPuKЅЏKzDwUvD/#".54>32'4.#"32>5Ř'+^3}ȌKJ}~ȋK6@{vu{?@{uv{?<# KІzюKKхwDDwxDDx\N.=!.'#".54>3!54&#"'4>32%267!"g PjQL{W/;vu!J~]56;kWTf9 6of0)Jf#b2)J8!$JnKHzW1y%Ea=CtU0!KwU=70 p)Jg=;Y<=Z#3#53!2#!!2>=4.#!!挌ܡ[[܁,w]uɒSSu6]뎲]mW׀֚WZ#3#53!2#!!2>=4.#!!挌ܡ[[܁,w]uɒSSu6]뎲]mW׀֚W=%!>32#4.#"##5353!xKbwDRX.6+Nl@;bO>-66=9dI*-alDgO !8HPP#K67##!5!!5!!3x6ZOO666 &t^%3#!!3267#".5#53#53%O-;!0   4R9^76GX13@lQw6S7$!+&$C?!+&$t?!,&$?!&$N!&$i?!S&$I?!&$D&&x>+&(C?>+&(t?>,&(?>&(i?:8+&,C?+&,tf? ,&,{?/&,iK?&1EN@&2C<T@&2tTA&2T#&2c&2iT+&8CQ?+&8t?,&8'?&8i?! &<t4\&DC\&Dt\&D\&D\&Di\&D&\A&DaDN&FxpW&HCW&HtW&HW&Hiu&Cj&t+&@&i&Q Z &RCZ &RtZ &RZ &R Z &Ri&XC&Xt&X&Xi+K&\ta+K&\iE!&$oJ\&Do !&$D\&D!u" 3267#"&54>7!# !!$C5 $(4=.CP2A"7;!P93!54&#"'4>323267#"&54>%2>7!"f PjQL{W/DuJ~]56;kWTf9 $C5 $(4=.CP2AeNkQit>)Jf&i3,O<#+NlAHzW1(Ga8?sW3+UU=70 9&(oJW&Ho >&(DW&H>&(?W&Hku>%!!#3267#"&54>7!!!!&@;$C5 $(4=.CP0?! vd6932!32>73267#"&5467"!54.1cJH{^Zm=@qY.WPJ"&.$C5 $(4=.CP;,A9RhA 3]Pv,vǏPCzg?,eL"9,09&&(DW&HA&*-TyL&J'&*YyL&J&*TyL&Jk_&**yL6&J,&+:?+&KG>U&,|N&A?&,opJ&o5"&,bD&'.u?!#3267#"&54>73&$C5 $(4=.CP"6F$69u (3#3267#"&54>734632#"&$C5 $(4=.CP!5D#6R !! 97#".5332>5o$C5 $(4=.CP"/DFkL6Dw_^vD%6975#".5332>73$C5 $(4=.CP 5D#0P]46-PnAZZ4 797!#!'0$C5 $(4=.CP4A#;7s97!!!!N0$C5 $(4=.CP0?!y;nC6973$C5 $(4=.CP"6F$69}%&K&re$&"0`&t#g`&#2`&g`&n4&tg4&y24& r%&o&r%& r%&&tcg&2& R&tyR&RH&xmR& ._&*.& &*&o&& /&;& 333267#"&54>7#".5332>5{g$C5 $(4=.CP&305\sA59ePPe9ꉰ*9(^j=+&,.012e377!<6;/&,iK?!&<i4z}&rt&(`&C&ƙb&(:Z NR`:u+:Y7:[&i&iBZ &RJ&&g>&(i?+&t?`gA4.'.54>32#4.#"#".5332>00j|o}BJgjH6>qagm9(dlHNf>xhN-6*I`kp5`uA]EkWH"D[}XS\1>q`Qe91Sm;:cVK"Ea\Y^1)B`RIpT9$.St&,/&,iK?Z-&.t!&D!$e%>(&5D0+2e3&776;\NDWNH&Z NR`NSaN+%2>73#".=4>32#.#"?y`>6CmOiq;;qiNnD6<_{Cc_..`""GoNSY/Pw*wƏP0_]QyQ(Le*fL+K:\7:[W&Hiu6&tl}NV L&iqKM&tu+K&\\=+&:C6?S&ZC=+&:t?S&ZtY=&:i?S&Zi=! &<C4+K&\Cb b&Bt&IOcK&OMg+&0t?&Pt!&$:\N&Do&2B&IB&I'IO >+&(C?+&Cx?W&HC&Ca:#&B&ZKN&R\gK &2\TdQ_&mRN&'Q&&aQN&FR!<+a:&,&D&\&,!&$D\&D!&$i?\&Di2]yN>&(DW&H/&BiWNW&iu&i?&iEd_&iTm&i6&oCJ&o&i?&ii&2iTZ &RiW N&i:W &im&iUz&ia!&oJ+K&\oj !&i?+K&\iE!?&<?+K&\j&i?u&i/&',i?#&'8ib6K&;7K:&[zG"K&,K:&!&$\N&D7!&$h\g&D(!q&$A\ 1&D!q&$A\1&D!&$T\/&D!&$6\&D!,&$'?\&D'7!&$T\y&D!&$z\&D:!0&$I\&D !&$Q\&D!&$'D\&D'7>&( WN&Hq>&(hWf&Hu'>&(NW&H >q&(AW0&H>q&(AW0&HY&(TW%&H>&(6W&H>,&('? W&H'q&,JhP&?&,H &L &2Z N&R&2}Z e&R&&25VZ0&R&2 VZ 0&R&2iZ7&R&2KZ &RA&2'TZ &R'5&t0IW&t5&CnIW&C&rWe&&&EXW& &W&&8:&X>&8hh&X)+&t?&t+&C1?&C&hP&&N&&&>!&< +K:&\ c!&<]+Kh&\E)!&<C+K&\vzW!7##5#".=4>32!5!53332>7.#"W6G]qC`h77i`Cp[Gu6[+VYJx\@6W}VYW+*F4O|̎L 9Q16lJ#?V2/aO2Jlz.W&G'0Bd~&:& &+ :&7&7(:&=6&;7:&[&u:&j&Gu:&^&B6:&+&:& /c&<[DN&=K&?#!!2#!#5353!!2>54.#qquw;54.#qquw;   K W $_  ,  @  ' &; \a T   Font data copyright Google 2014RobotoThinGoogle:Roboto:2014Roboto ThinVersion 2.000980; 2014Roboto-ThinRoboto is a trademark of Google.GoogleGoogle.comChristian RobertsonLicensed under the Apache License, Version 2.0http://www.apache.org/licenses/LICENSE-2.0Roboto ThinFont data copyright Google 2014RobotoThinGoogle:Roboto:2014Roboto ThinVersion 2.000980; 2014Roboto-ThinRoboto is a trademark of Google.GoogleGoogle.comChristian RobertsonLicensed under the Apache License, Version 2.0http://www.apache.org/licenses/LICENSE-2.0RobotoThinjd1  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPbcQdefghjikmlnRoqprsutvwxzy{}|~STUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./012345678uni0002macronperiodcenteredHbar kgreenlandicEngenglongsOhornohornUhornuhornuni0237schwauni02F3 gravecomb acutecomb tildecombhookuni030Fdotbelowtonos dieresistonos anoteleiaGammaDeltaThetaLambdaXiPiSigmaPhiPsialphabetagammadeltaepsilonzetaetathetaiotalambdaxirhosigma1sigmatauupsilonphipsiomegauni03D1uni03D2uni03D6uni0402uni0404uni0409uni040Auni040Buni040Funi0411uni0414uni0416uni0417uni0418uni041Buni0423uni0424uni0426uni0427uni0428uni0429uni042Auni042Buni042Cuni042Duni042Euni042Funi0431uni0432uni0433uni0434uni0436uni0437uni0438uni043Auni043Buni043Cuni043Duni043Funi0442uni0444uni0446uni0447uni0448uni0449uni044Auni044Buni044Cuni044Duni044Euni044Funi0452uni0454uni0459uni045Auni045Buni045Funi0460uni0461uni0463uni0464uni0465uni0466uni0467uni0468uni0469uni046Auni046Buni046Cuni046Duni046Euni046Funi0472uni0473uni0474uni0475uni047Auni047Buni047Cuni047Duni047Euni047Funi0480uni0481uni0482uni0483uni0484uni0485uni0486uni0488uni0489uni048Duni048Euni048Funi0490uni0491uni0494uni0495uni049Cuni049Duni04A0uni04A1uni04A4uni04A5uni04A6uni04A7uni04A8uni04A9uni04B4uni04B5uni04B8uni04B9uni04BAuni04BCuni04BDuni04C3uni04C4uni04C7uni04C8uni04D8uni04E0uni04E1uni04FAuni04FBuni0500uni0502uni0503uni0504uni0505uni0506uni0507uni0508uni0509uni050Auni050Buni050Cuni050Duni050Euni050Funi0510uni2000uni2001uni2002uni2003uni2004uni2005uni2006uni2007uni2008uni2009uni200Auni200B underscoredbl quotereverseduni2025uni2074 nsuperiorlirapesetaEurouni2105uni2113uni2116 estimated oneeighth threeeighths fiveeighths seveneighths colon.lnum quotedblx commaaccentuniFEFFuniFFFCuniFFFDtwo.sup cyrillichookcircumflexacutecombbrevegravecombcommaaccentrotateA.smcpB.smcpC.smcpD.smcpE.smcpF.smcpG.smcpH.smcpI.smcpJ.smcpK.smcpL.smcpM.smcpN.smcpO.smcpP.smcpQ.smcpR.smcpS.smcpT.smcpU.smcpV.smcpW.smcpX.smcpY.smcpZ.smcp zero.smcpone.smcptwo.smcp three.smcp four.smcp five.smcpsix.smcp seven.smcp eight.smcp nine.smcpzero.supone.supbreveacutecomb three.supfour.supfive.supsix.sup seven.sup eight.supnine.supcrossbar dasiaoxia ringacute brevehookcombbrevetildecomb cyrillicticcyrillichookleftcircumflexgravecombg.altlargerighthookone.lnumchi.alt alpha.alt delta.altR.altK.altk.altK.alt2k.alt2circumflexhookcombcircumflextildecomb seven.altG.altC.ss06O.ss06G.ss06Q.ss06D.ss06one.onumtwo.onum three.onum four.onum five.onum seven.onum nine.onum zero.onum seven.lnumb.ss06c.ss06d.ss06g.ss06o.ss06p.ss06q.ss06c.ss07e.ss07g.ss07six.altnine.altD.cnQ.cna.cn cyrillicbreveuni00ADDcroathbarTbartbar Aringacute aringacuteAmacronamacronAbreveabreveAogonekaogonek Ccircumflex ccircumflexuni010Auni010BDcarondcaronEmacronemacronEbreveebreve Edotaccent edotaccentEogonekeogonekEcaronecaron Gcircumflex gcircumflexuni0120uni0121 Gcommaaccent gcommaaccent Hcircumflex hcircumflexItildeitildeImacronimacronIbreveibreveIogonekiogonek IdotaccentIJij Jcircumflex jcircumflex Kcommaaccent kcommaaccentLacutelacute Lcommaaccent lcommaaccentLcaronlcaronLdotldotNacutenacute Ncommaaccent ncommaaccentNcaronncaron napostropheOmacronomacronObreveobreve Ohungarumlaut ohungarumlautRacuteracute Rcommaaccent rcommaaccentRcaronrcaronSacutesacute Scircumflex scircumflexuni0218uni0219uni021Auni021Buni0162uni0163TcarontcaronUtildeutildeUmacronumacronUbreveubreveUringuring Uhungarumlaut uhungarumlautUogonekuogonek Wcircumflex wcircumflex Ycircumflex ycircumflexZacutezacute Zdotaccent zdotaccentAEacuteaeacute Oslashacute oslashacute Dcroat.smcpEth.smcp Tbar.smcp Agrave.smcp Aacute.smcpAcircumflex.smcp Atilde.smcpAdieresis.smcp Aring.smcpAringacute.smcp Ccedilla.smcp Egrave.smcp Eacute.smcpEcircumflex.smcpEdieresis.smcp Igrave.smcp Iacute.smcpIcircumflex.smcpIdieresis.smcp Ntilde.smcp Ograve.smcp Oacute.smcpOcircumflex.smcp Otilde.smcpOdieresis.smcp Ugrave.smcp Uacute.smcpUcircumflex.smcpUdieresis.smcp Yacute.smcp Amacron.smcp Abreve.smcp Aogonek.smcp Cacute.smcpCcircumflex.smcp uni010A.smcp Ccaron.smcp Dcaron.smcp Emacron.smcp Ebreve.smcpEdotaccent.smcp Eogonek.smcp Ecaron.smcpGcircumflex.smcp Gbreve.smcp uni0120.smcpGcommaaccent.smcpHcircumflex.smcp Itilde.smcp Imacron.smcp Ibreve.smcp Iogonek.smcpIdotaccent.smcpJcircumflex.smcpKcommaaccent.smcp Lacute.smcpLcommaaccent.smcp Lcaron.smcp Ldot.smcp Nacute.smcpNcommaaccent.smcp Ncaron.smcp Omacron.smcp Obreve.smcpOhungarumlaut.smcp Racute.smcpRcommaaccent.smcp Rcaron.smcp Sacute.smcpScircumflex.smcp Scedilla.smcp Scaron.smcpTcommaaccent.smcp Tcaron.smcp Utilde.smcp Umacron.smcp Ubreve.smcp Uring.smcpUhungarumlaut.smcp Uogonek.smcpWcircumflex.smcpYcircumflex.smcpYdieresis.smcp Zacute.smcpZdotaccent.smcp Zcaron.smcpgermandbls.smcp Alphatonos EpsilontonosEtatonos Iotatonos Omicrontonos Upsilontonos OmegatonosiotadieresistonosAlphaBetaEpsilonZetaEtaIotaKappaMuNuOmicronRhoTauUpsilonChi IotadieresisUpsilondieresis alphatonos epsilontonosetatonos iotatonosupsilondieresistonoskappaomicronuni03BCnuchi iotadieresisupsilondieresis omicrontonos upsilontonos omegatonosuni0401uni0403uni0405uni0406uni0407uni0408uni041Auni040Cuni040Euni0410uni0412uni0413uni0415uni0419uni041Cuni041Duni041Euni041Funi0420uni0421uni0422uni0425uni0430uni0435uni0439uni043Euni0440uni0441uni0443uni0445uni0451uni0453uni0455uni0456uni0457uni0458uni045Cuni045EWgravewgraveWacutewacute Wdieresis wdieresisYgraveygraveminutesecond exclamdbluniFB02uni01F0uni02BCuni1E3Euni1E3Funi1E00uni1E01uni1F4DuniFB03uniFB04uni0400uni040Duni0450uni045Duni0470uni0471uni0476uni0477uni0479uni0478uni0498uni0499uni04AAuni04ABuni04AEuni04AFuni04C0uni04C1uni04C2uni04CFuni04D0uni04D1uni04D2uni04D3uni04D4uni04D5uni04D6uni04D7uni04DAuni04D9uni04DBuni04DCuni04DDuni04DEuni04DFuni04E2uni04E3uni04E4uni04E5uni04E6uni04E7uni04E8uni04E9uni04EAuni04EBuni04ECuni04EDuni04EEuni04EFuni04F0uni04F1uni04F2uni04F3uni04F4uni04F5uni04F8uni04F9uni04FCuni04FDuni0501uni0512uni0513uni1EA0uni1EA1uni1EA2uni1EA3uni1EA4uni1EA5uni1EA6uni1EA7uni1EA8uni1EA9uni1EAAuni1EABuni1EACuni1EADuni1EAEuni1EAFuni1EB0uni1EB1uni1EB2uni1EB3uni1EB4uni1EB5uni1EB6uni1EB7uni1EB8uni1EB9uni1EBAuni1EBBuni1EBCuni1EBDuni1EBEuni1EBFuni1EC0uni1EC1uni1EC2uni1EC3uni1EC4uni1EC5uni1EC6uni1EC7uni1EC8uni1EC9uni1ECAuni1ECBuni1ECCuni1ECDuni1ECEuni1ECFuni1ED0uni1ED1uni1ED2uni1ED3uni1ED4uni1ED5uni1ED6uni1ED7uni1ED8uni1ED9uni1EDAuni1EDBuni1EDCuni1EDDuni1EDEuni1EDFuni1EE0uni1EE1uni1EE2uni1EE3uni1EE4uni1EE5uni1EE6uni1EE7uni1EE8uni1EE9uni1EEAuni1EEBuni1EECuni1EEDuni1EEEuni1EEFuni1EF0uni1EF1uni1EF4uni1EF5uni1EF6uni1EF7uni1EF8uni1EF9dcroatuni20ABuni049Auni049Buni04A2uni04A3uni04ACuni04ADuni04B2uni04B3uni04B6uni04B7uni04CBuni04CCuni04F6uni04F7uni0496uni0497uni04BEuni04BFuni04BBuni048Cuni0462uni0492uni0493uni049Euni049Funi048Auni048Buni04C9uni04CAuni04CDuni04CEuni04C5uni04C6uni04B0uni04B1uni04FEuni04FFuni0511uni2015NULLuni0009 %Wbww|} ,DFLTkernNTt|\V\bh2<^$Fl6",:DNX  @ f  & P Z p * P v l 2T6<R .HNp&Pvpz &,28>dnx(NthL0 .Pr(Ntz<Zx"Lv02P(Ndn   < ^ !!F!t!!!!""("J"T"^""""#$#N#\#j#x$b%L&6&<&B&H&N&T&Z&''0''((((((())**(*>*`***++6+\+,l,-`-...H.f../h///00N01 1112202V2|23x3344,4F4d4j4t4~444555555556n6666677(7778\8b889999999:::@:f:::;;<;;;< >$>>?&?D??@F@d@@AfAABBBCC4CJCTCjCtCCCCCCCCD DD*D4DVDxDDDEE>EhEEEFF.FXF~FFFFGpGH H>HHI?V@AVBDFHJLNPRTVXZ\^l VV%)+-0129=VHJKMOPRYajVkqvw|~VVVVVVVVVVVVVV 79:</7 !$HZ\^`u} (79</7 !$H`u} (-8l9G FGHJT6HISfgijk()*+,DFHJLNPRTVXZ\^%KORbcgqw|+'XN~tjDx >   0 Zz,~rL=FGHJT6HIS()*+,DFHJLNPRTVXZ\^%KORqw|R23456+1Mhlpq-8l9 Y\fgijk;<-PYabcgv~=&*245R CEGIWY[]DGlx{=&*245R CEGIWY[]DGlx{6$;<      =?A !"$=I`j} (&$ hlpq    =?A =jFGHJPQRSTXY\26AFHIS()*+,123456789:;<DFHJLNPRTVXZ\^y{}~%')+-012KLMNOPRSXYaiqrvw|~ !#%q FGHJTXY\6HISfgijk()*+,789:;<DFHJLNPRTVXZ\^%)-02KOPRYabcgqvw|~4 XY\fgijk789:;<)-02PYabcgv~=FGHJT6HIS()*+,DFHJLNPRTVXZ\^%KORqw|q FGHJRTY \  6HISf g i j k ()*+,23456; < DFHJLNPRTVXZ\^ %+- 1KMOP RY a b c g qv w|~       []Q []QY[\];<-PQYav~y 79;<PQSY[\/27AFfgijk 1;<y{}~!"$'-<HILNPQSXY`abcgiruv}~   !#%(7Y\/7;<-HPYav~ 479;<[/7 !"$<HIQ`u}  ($&*2479< /57NR      =?ACEGIWY[] !$=DGH`jlux{} &'(-79;</7N !"$<HI`u}  &(Y$79;<  /7N     =?A !"$< =HI`ju}     &'(.7;</7N !"$<HI`}  &'(!7;/7N"<HI  &'7</7 !$H`} (0PQS[2AF1y{}~'LNQSXir !#% Y\fgijk;<-PYabcgv~  fgijkbcgTFGHJRT6HIS()*+,23456DFHJLNPRTVXZ\^%+1KMORqw| DFGHJRTY \  6HISf g i j k !"#$%&'()*+,23456; < >@BDFHJLNPRTVXZ\^ %+- 1JKMOP RY a b c g kqv w|~       ''EF G H J T    6 H I S ( ) * + , D F H J L N P R T V X Z \ ^ % K O R q w |                  'Y[\;<-PQYav~'  fgijkbcg  fgijkbcg!K N O RV23456+1MT^ FGHJRT6HISf g i j k ()*+,23456DFHJLNPRTVXZ\^%+1KMORb c g qw| " \! |~&r DK>zDjb3,'{qQv8 - mxVy $)+4 7=DEHIKKPSUU#YY$[\%'().12469?@KLORWX[]`b  cd(*g--j//kFFlfgmikorstvwyz{}~'),16;CEEGGIIKKMV_acceeggiillnnpprrttvvx $''++-- 11 45 7@ BDFKMN PS"YZ&\\(^^)`c*gl.oo4qq5uv6{{8}9CEIKLSU~     ""$$(( )45>I'NWZ^gx $(+3 7=DFHHKKPSUU"YY#[]$'(,-./1236789:;<=>@B  CD(*G--J//KFFLflMpqTVXY[,\16;KMV_acceeggiillnnpprrttvvx $''++--11457@BDFKMSYZ \\ ^^ `cglooqquv{(*.018:cdrstu{  |  } ~""$$((; %%&&''((+,-- .. // 012233 7788 99 ::;;<<==DDEEFFHHKKPQRRSSUUYY[[\\]]    (())**-- // FFfghhikllpq     !'((),1126;<>>@@BBCCDDEEFFGGHHIIJJKKMMNNOOPPQQRRSSTTUUVV__``aacceeggiill nn pp rr tt vv xxyyzz{{||}~          !!""##$$''++--1144557899 :; <<>>??@@BCDDFF GGHHIIJJKKMMNNOOPPQQRRSSYYZZ\\^^``aabcgghhiikkllooqquu vv{{||}}~~          ""$$((*                               4DFLT  ligaJlnumPonumVpnum\smcpbss01hss02nss03tss04zss05ss06ss07   "*2:BJRZbjr` "  6DFJ       L n $=D]456F8IZ{]il}IJ5.N &'*24EFGJRSTHJtomahawk-player/src/libtomahawk/network/Servent.cpp000664 001750 001750 00000130264 12661705042 023714 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2012, Jeff Mitchell * Copyright 2013, Teo Mrnjavac * Copyright 2013-2014, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "Servent_p.h" #include "accounts/AccountManager.h" #include "database/Database.h" #include "database/DatabaseImpl.h" #include "network/acl/AclRegistry.h" #include "network/Msg.h" #include "network/ConnectionManager.h" #include "network/DbSyncConnection.h" #include "sip/SipInfo.h" #include "sip/PeerInfo.h" #include "sip/SipPlugin.h" #include "utils/Closure.h" #include "utils/Json.h" #include "utils/TomahawkUtils.h" #include "utils/Logger.h" #include "utils/NetworkAccessManager.h" #include "utils/NetworkReply.h" #include "Connection.h" #include "ControlConnection.h" #include "PortFwdThread.h" #include "QTcpSocketExtra.h" #include "Source.h" #include "SourceList.h" #include "StreamConnection.h" #include "UrlHandler.h" #include #include #include #include #include #include #include #include #include typedef QPair< QList< SipInfo >, Connection* > sipConnectionPair; Q_DECLARE_METATYPE( sipConnectionPair ) Q_DECLARE_METATYPE( QList< SipInfo > ) Q_DECLARE_METATYPE( Connection* ) Q_DECLARE_METATYPE( QTcpSocketExtra* ) Q_DECLARE_METATYPE( Tomahawk::peerinfo_ptr ) using namespace Tomahawk; Servent* Servent::s_instance = 0; Servent* Servent::instance() { return s_instance; } Servent::Servent( QObject* parent ) : QTcpServer( parent ), d_ptr( new ServentPrivate( this ) ) { s_instance = this; d_func()->noAuth = qApp->arguments().contains( "--noauth" ); setProxy( QNetworkProxy::NoProxy ); IODeviceFactoryFunc fac = boost::bind( &Servent::remoteIODeviceFactory, this, _1, _2, _3 ); Tomahawk::UrlHandler::registerIODeviceFactory( "servent", fac ); } Servent::~Servent() { tDebug() << Q_FUNC_INFO; foreach ( ControlConnection* cc, d_func()->controlconnections ) delete cc; if ( d_func()->portfwd ) { d_func()->portfwd.data()->quit(); d_func()->portfwd.data()->wait( 60000 ); delete d_func()->portfwd.data(); } delete d_ptr; } bool Servent::startListening( QHostAddress ha, bool upnp, int port, Tomahawk::Network::ExternalAddress::Mode mode, int defaultPort, bool autoDetectExternalIp, const QString& externalHost, int externalPort ) { Q_D( Servent ); d->externalAddresses = QList(); d->port = port; // Listen on both the selected port and, if not the same, the default port -- the latter sometimes necessary for zeroconf // TODO: only listen on both when zeroconf sip is enabled // TODO: use a real zeroconf system instead of a simple UDP broadcast? if ( !listen( ha, d->port ) ) { if ( d->port != defaultPort ) { if ( !listen( ha, defaultPort ) ) { tLog() << Q_FUNC_INFO << "Failed to listen on both port" << d->port << "and port" << defaultPort; tLog() << Q_FUNC_INFO << "Error string is:" << errorString(); return false; } else d->port = defaultPort; } } d->externalListenAll = false; if ( ha == QHostAddress::Any || ha == QHostAddress::AnyIPv6 ) { // We are listening on all available addresses, so we should send a SipInfo for all of them. d->externalAddresses = QNetworkInterface::allAddresses(); cleanAddresses( d->externalAddresses ); tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Listening to" << d->externalAddresses; d->externalListenAll = true; } else if ( ( ha.toString() != "127.0.0.1" ) && ( ha.toString() != "::1" ) && ( ha.toString() != "::7F00:1" ) ) { // We listen only to one specific Address, only announce this. d->externalAddresses.append( ha ); } // If we only accept connections via localhost, we'll announce nothing. tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Servent listening on port" << d->port << "using address" << ha.toString() << "- servent thread:" << thread() << "- address mode:" << (int)( mode ); switch ( mode ) { case Tomahawk::Network::ExternalAddress::Static: d->externalPort = externalPort; if ( autoDetectExternalIp ) { QNetworkReply* reply = Tomahawk::Utils::nam()->get( QNetworkRequest( QUrl( "http://toma.hk/?stat=1" ) ) ); connect( reply, SIGNAL( finished() ), SLOT( ipDetected() ) ); // Not emitting ready here as we are not done. } else { d->externalHostname = externalHost; d->ready = true; // All setup is made, were done. emit ready(); } break; case Tomahawk::Network::ExternalAddress::Lan: // Nothing has to be done here. d->ready = true; emit ready(); break; case Tomahawk::Network::ExternalAddress::Upnp: if ( upnp ) { // upnp could be turned off on the cli with --noupnp tLog( LOGVERBOSE ) << Q_FUNC_INFO << "External address mode set to upnp..."; d->portfwd = QPointer< PortFwdThread >( new PortFwdThread( d->port ) ); Q_ASSERT( d->portfwd ); connect( d->portfwd.data(), SIGNAL( externalAddressDetected( QHostAddress, unsigned int ) ), SLOT( setExternalAddress( QHostAddress, unsigned int ) ) ); d->portfwd.data()->start(); } else { d->ready = true; emit ready(); } break; } connect( ACLRegistry::instance(), SIGNAL( aclResult( QString, QString, Tomahawk::ACLStatus::Type ) ), this, SLOT( checkACLResult( QString, QString, Tomahawk::ACLStatus::Type ) ), Qt::QueuedConnection ); return true; } void Servent::setExternalAddress( QHostAddress ha, unsigned int port ) { if ( isValidExternalIP( ha ) ) { d_func()->externalHostname = ha.toString(); d_func()->externalPort = port; } if ( d_func()->externalPort == 0 || !isValidExternalIP( ha ) ) tLog() << Q_FUNC_INFO << "UPnP failed, no further external address could be acquired!"; else tLog( LOGVERBOSE ) << Q_FUNC_INFO << "UPnP setup successful"; d_func()->ready = true; emit ready(); } QString Servent::createConnectionKey( const QString& name, const QString &nodeid, const QString &key, bool onceOnly ) { Q_ASSERT( this->thread() == QThread::currentThread() ); QString _key = ( key.isEmpty() ? uuid() : key ); ControlConnection* cc = new ControlConnection( this ); cc->setName( name.isEmpty() ? QString( "KEY(%1)" ).arg( key ) : name ); if ( !nodeid.isEmpty() ) cc->setId( nodeid ); cc->setOnceOnly( onceOnly ); tDebug( LOGVERBOSE ) << "Creating connection key with name of" << cc->name() << "and id of" << cc->id() << "and key of" << _key << "; key is once only? :" << (onceOnly ? "true" : "false"); registerOffer( _key, cc ); return _key; } bool Servent::isValidExternalIP( const QHostAddress& addr ) { QString ip = addr.toString(); if (addr.protocol() == QAbstractSocket::IPv4Protocol) { // private network if ( addr.isInSubnet(QHostAddress::parseSubnet( "10.0.0.0/8" ) ) ) return false; // localhost if ( addr.isInSubnet(QHostAddress::parseSubnet( "127.0.0.0/8" ) ) ) return false; // private network if ( addr.isInSubnet(QHostAddress::parseSubnet( "169.254.0.0/16" ) ) ) return false; // private network if ( addr.isInSubnet(QHostAddress::parseSubnet( "172.16.0.0/12" ) ) ) return false; // private network if ( addr.isInSubnet(QHostAddress::parseSubnet( "192.168.0.0/16" ) ) ) return false; // multicast if ( addr.isInSubnet(QHostAddress::parseSubnet( "224.0.0.0/4" ) ) ) return false; } else if (addr.protocol() == QAbstractSocket::IPv4Protocol) { // "unspecified address" if ( addr.isInSubnet(QHostAddress::parseSubnet( "::/128" ) ) ) return false; // link-local if ( addr.isInSubnet(QHostAddress::parseSubnet( "fe80::/10" ) ) ) return false; // unique local addresses if ( addr.isInSubnet(QHostAddress::parseSubnet( "fc00::/7" ) ) ) return false; // benchmarking only if ( addr.isInSubnet(QHostAddress::parseSubnet( "2001:2::/48" ) ) ) return false; // non-routed IPv6 addresses used for Cryptographic Hash Identifiers if ( addr.isInSubnet(QHostAddress::parseSubnet( "2001:10::/28" ) ) ) return false; // documentation prefix if ( addr.isInSubnet(QHostAddress::parseSubnet( "2001:db8::/32" ) ) ) return false; // multicast if ( addr.isInSubnet(QHostAddress::parseSubnet( "ff00::0/8" ) ) ) return false; } else { return false; } return !addr.isNull(); } void Servent::registerOffer( const QString& key, Connection* conn ) { d_func()->offers[key] = QPointer(conn); } void Servent::registerLazyOffer(const QString &key, const peerinfo_ptr &peerInfo, const QString &nodeid, const int timeout ) { d_func()->lazyoffers[key] = QPair< peerinfo_ptr, QString >( peerInfo, nodeid ); QTimer* timer = new QTimer( this ); timer->setInterval( timeout ); timer->setSingleShot( true ); NewClosure( timer, SIGNAL( timeout() ), this, SLOT( deleteLazyOffer( const QString& ) ), key ); timer->start(); } void Servent::deleteLazyOffer( const QString& key ) { d_func()->lazyoffers.remove( key ); // Cleanup. QTimer* timer = (QTimer*)sender(); if ( timer ) { timer->deleteLater(); } } void Servent::registerControlConnection( ControlConnection* conn ) { Q_D( Servent ); Q_ASSERT( conn ); QMutexLocker locker( &d->controlconnectionsMutex ); tLog( LOGVERBOSE ) << Q_FUNC_INFO << conn->name(); d->controlconnections << conn; d->connectedNodes << conn->id(); } void Servent::unregisterControlConnection( ControlConnection* conn ) { Q_D( Servent ); Q_ASSERT( conn ); QMutexLocker locker( &d->controlconnectionsMutex ); tLog( LOGVERBOSE ) << Q_FUNC_INFO << conn->name(); d->connectedNodes.removeAll( conn->id() ); d->controlconnections.removeAll( conn ); } ControlConnection* Servent::lookupControlConnection( const SipInfo& sipInfo ) { Q_D( Servent ); QMutexLocker locker( &d->controlconnectionsMutex ); foreach ( ControlConnection* c, d_func()->controlconnections ) { tLog() << sipInfo.port() << c->peerPort() << sipInfo.host() << c->peerIpAddress().toString(); if ( sipInfo.port() == c->peerPort() && sipInfo.host() == c->peerIpAddress().toString() ) return c; } return NULL; } ControlConnection* Servent::lookupControlConnection( const QString& nodeid ) { Q_D( Servent ); QMutexLocker locker( &d->controlconnectionsMutex ); foreach ( ControlConnection* c, d_func()->controlconnections ) { if ( c->id() == nodeid ) return c; } return NULL; } QList Servent::getLocalSipInfos( const QString& nodeid, const QString& key ) { Q_D( Servent ); QList sipInfos = QList(); QList addresses = d->externalAddresses; if ( d->externalListenAll ) { addresses = QNetworkInterface::allAddresses(); cleanAddresses( addresses ); } foreach ( QHostAddress ha, addresses ) { SipInfo info = SipInfo(); info.setHost( ha.toString() ); info.setPort( d_func()->port ); info.setKey( key ); info.setVisible( true ); info.setNodeId( nodeid ); sipInfos.append( info ); } if ( d_func()->externalHostname.length() > 0) { SipInfo info = SipInfo(); info.setHost( d_func()->externalHostname ); info.setPort( d_func()->externalPort ); info.setKey( key ); info.setVisible( true ); info.setNodeId( nodeid ); sipInfos.append( info ); } if ( sipInfos.isEmpty() ) { // We are not visible via any IP, send a dummy SipInfo SipInfo info = SipInfo(); info.setVisible( false ); info.setKey( key ); info.setNodeId( nodeid ); tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Only accepting connections, no usable IP to listen to found."; } return sipInfos; } void Servent::queueForAclResult( const QString& username, const QSet& peerInfos ) { if ( peerInfos.isEmpty() || (*peerInfos.begin())->sipInfos().isEmpty() ) { // If all peerInfos disappeared, do not queue. // If the peerInfo has not got a sipInfo anymore, do not queue either. return; } if ( !d_func()->queuedForACLResult.contains( username ) ) { d_func()->queuedForACLResult[username] = QMap >(); } d_func()->queuedForACLResult[username][ (*peerInfos.begin())->nodeId() ] = QSet( peerInfos ); } SipInfo Servent::getSipInfoForOldVersions( const QList& sipInfos ) { SipInfo info = SipInfo(); info.setVisible( false ); foreach ( SipInfo _info, sipInfos ) { QHostAddress ha = QHostAddress( _info.host() ); if ( ( Servent::isValidExternalIP( ha ) && ha.protocol() == QAbstractSocket::IPv4Protocol ) || ( ha.protocol() == QAbstractSocket::UnknownNetworkLayerProtocol ) || ( ha.isNull() && !_info.host().isEmpty() )) { info = _info; break; } } return info; } void Servent::registerPeer( const Tomahawk::peerinfo_ptr& peerInfo ) { if ( peerInfo->hasControlConnection() ) { peerInfoDebug( peerInfo ) << "already had control connection, doing nothing: " << peerInfo->controlConnection()->name(); tLog() << "existing control connection has following peers:"; foreach ( const peerinfo_ptr& otherPeerInfo, peerInfo->controlConnection()->peerInfos() ) { peerInfoDebug( otherPeerInfo ); } tLog() << "end peers"; return; } if ( peerInfo->type() == Tomahawk::PeerInfo::Local ) { peerInfoDebug(peerInfo) << "we need to establish the connection now... thinking"; if ( !connectedToSession( peerInfo->nodeId() ) ) { handleSipInfo( peerInfo ); } else { // FIXME: do we need to port this?! // qDebug() << "Already connected to" << host; // so peerInfo was 0 before // qDebug() << "They connected to us and we don't have a PeerInfo object, created one..."; // m_peersOnline.append( peerInfo ); // // attach to control connection // ControlConnection* conn = Servent::instance()->lookupControlConnection( sipInfo ); // // we're connected to this nodeid, so we should find a control connection for this sipinfo, no? // Q_ASSERT( conn ); // conn->addPeerInfo( peerInfo ); } } else { QString key = uuid(); const QString& nodeid = Database::instance()->impl()->dbid(); QList sipInfos = getLocalSipInfos( nodeid, key ); // The offer should be removed after some time or we will build up a heap of unused PeerInfos registerLazyOffer( key, peerInfo, nodeid, sipInfos.length() * 1.5 * CONNECT_TIMEOUT ); // SipInfos were single-value before 0.7.100 if ( !peerInfo->versionString().isEmpty() && TomahawkUtils::compareVersionStrings( peerInfo->versionString().split(' ').last(), "0.7.100" ) < 0) { SipInfo info = getSipInfoForOldVersions( sipInfos ); peerInfo->sendLocalSipInfos( QList() << info ); } else { peerInfo->sendLocalSipInfos( sipInfos ); } handleSipInfo( peerInfo ); connect( peerInfo.data(), SIGNAL( sipInfoChanged() ), SLOT( onSipInfoChanged() ) ); } } void Servent::onSipInfoChanged() { Tomahawk::PeerInfo* peerInfo = qobject_cast< Tomahawk::PeerInfo* >( sender() ); if ( !peerInfo ) return; handleSipInfo( peerInfo->weakRef().toStrongRef() ); } void Servent::handleSipInfo( const Tomahawk::peerinfo_ptr& peerInfo ) { // We do not have received the initial SipInfo for this client yet, so wait for it. // Each client will have at least one non-visible SipInfo if ( peerInfo->sipInfos().isEmpty() ) return; QSharedPointer manager = ConnectionManager::getManagerForNodeId( peerInfo->nodeId() ); manager->handleSipInfo( peerInfo ); } void #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) Servent::incomingConnection( qintptr sd ) #else Servent::incomingConnection( int sd ) #endif { Q_ASSERT( this->thread() == QThread::currentThread() ); QTcpSocketExtra* sock = new QTcpSocketExtra; tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Accepting connection, sock" << sock; sock->moveToThread( thread() ); sock->_disowned = false; sock->_outbound = false; if ( !sock->setSocketDescriptor( sd ) ) { Q_ASSERT( false ); return; } connect( sock, SIGNAL( readyRead() ), SLOT( readyRead() ) ); connect( sock, SIGNAL( disconnected() ), sock, SLOT( deleteLater() ) ); } void Servent::readyRead() { Q_D( Servent ); Q_ASSERT( this->thread() == QThread::currentThread() ); QPointer< QTcpSocketExtra > sock = (QTcpSocketExtra*)sender(); tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Starting to read from new incoming connection from: " << sock->peerAddress().toString(); if ( sock.isNull() || sock.data()->_disowned ) { return; } if ( sock.data()->_msg.isNull() ) { char msgheader[ Msg::headerSize() ]; if ( sock.data()->bytesAvailable() < Msg::headerSize() ) return; sock.data()->read( (char*) &msgheader, Msg::headerSize() ); sock.data()->_msg = Msg::begin( (char*) &msgheader ); } if ( sock.data()->bytesAvailable() < sock.data()->_msg->length() ) return; QByteArray ba = sock.data()->read( sock.data()->_msg->length() ); sock.data()->_msg->fill( ba ); if ( !sock.data()->_msg->is( Msg::JSON ) ) { tDebug() << ba; tDebug() << sock.data()->_msg->payload(); Q_ASSERT( sock.data()->_msg->is( Msg::JSON ) ); } ControlConnection* cc = 0; bool ok; QString key, conntype, nodeid, controlid; QVariantMap m = TomahawkUtils::parseJson( sock.data()->_msg->payload(), &ok ).toMap(); if ( !ok ) { tDebug() << "Invalid JSON on new connection, aborting"; goto closeconnection; } conntype = m.value( "conntype" ).toString(); key = m.value( "key" ).toString(); nodeid = m.value( "nodeid" ).toString(); controlid = m.value( "controlid" ).toString(); tDebug( LOGVERBOSE ) << "Incoming connection details:" << m; if ( !nodeid.isEmpty() ) // only control connections send nodeid { QMutexLocker locker( &d->controlconnectionsMutex ); bool dupe = false; if ( d_func()->connectedNodes.contains( nodeid ) ) { tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Connected nodes contains it."; dupe = true; } foreach ( ControlConnection* con, d_func()->controlconnections ) { Q_ASSERT( con ); tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Known connection:" << con->id(); // Only check for known inboud ControlConnections if ( con->id() == nodeid && !con->outbound() ) { dupe = true; break; } } // for zeroconf there might be no offer, that case is handled later ControlConnection* ccMatch = qobject_cast< ControlConnection* >( d_func()->offers.value( key ).data() ); if ( dupe && ccMatch ) { tLog() << "Duplicate control connection detected, dropping:" << nodeid << conntype; tDebug() << "PEERINFO: to be dropped connection has following peers"; foreach ( const peerinfo_ptr& currentPeerInfo, ccMatch->peerInfos() ) { peerInfoDebug( currentPeerInfo ); } foreach ( ControlConnection* keepConnection, d_func()->controlconnections ) { Q_ASSERT( keepConnection ); // Only check for known inboud ControlConnections if ( keepConnection->id() == nodeid && !keepConnection->outbound() ) { tDebug() << "Keep connection" << keepConnection->name() << "with following peers"; foreach ( const peerinfo_ptr& currentPeerInfo, keepConnection->peerInfos() ) peerInfoDebug( currentPeerInfo ); tDebug() << "Add these peers now"; foreach ( const peerinfo_ptr& currentPeerInfo, ccMatch->peerInfos() ) { tDebug() << "Adding" << currentPeerInfo->id(); keepConnection->addPeerInfo( currentPeerInfo ); } tDebug() << "Done adding."; } } goto closeconnection; } } { QMutexLocker locker( &d->controlconnectionsMutex ); foreach ( ControlConnection* con, d_func()->controlconnections ) { Q_ASSERT( con ); // Only check for known inboud ControlConnections if ( con->id() == controlid && !con->outbound() ) { cc = con; break; } } } // they connected to us and want something we are offering if ( conntype == "accept-offer" || conntype == "push-offer" ) { sock.data()->_msg.clear(); tDebug( LOGVERBOSE ) << Q_FUNC_INFO << key << nodeid << "socket peer address =" << sock.data()->peerAddress() << "socket peer name =" << sock.data()->peerName(); Connection* conn = claimOffer( cc, nodeid, key, sock.data()->peerAddress() ); if ( !conn ) { tLog() << "claimOffer FAILED, key:" << key << nodeid; goto closeconnection; } if ( sock.isNull() ) { tLog() << "Socket has become null, possibly took too long to make an ACL decision, key:" << key << nodeid; return; } else if ( !sock.data()->isValid() ) { tLog() << "Socket has become invalid, possibly took too long to make an ACL decision, key:" << key << nodeid; goto closeconnection; } tDebug( LOGVERBOSE ) << "claimOffer OK:" << key << nodeid; if ( !nodeid.isEmpty() ) { conn->setId( nodeid ); registerControlConnection( qobject_cast(conn) ); } handoverSocket( conn, sock.data() ); return; } else { tLog() << "Invalid or unhandled conntype"; } // fallthru to cleanup: closeconnection: tLog() << "Closing incoming connection, something was wrong."; sock.data()->_msg.clear(); sock.data()->disconnectFromHost(); } // creates a new tcp connection to peer from conn, handled by given connector // new_conn is responsible for sending the first msg, if needed void Servent::createParallelConnection( Connection* orig_conn, Connection* new_conn, const QString& key ) { tDebug( LOGVERBOSE ) << Q_FUNC_INFO << ", key:" << key << thread() << orig_conn; // if we can connect to them directly: if ( orig_conn && orig_conn->outbound() ) { SipInfo info = SipInfo(); info.setVisible( true ); info.setKey( key ); info.setNodeId( orig_conn->id() ); info.setHost( orig_conn->socket()->peerName() ); info.setPort( orig_conn->peerPort() ); Q_ASSERT( info.isValid() ); initiateConnection( info, new_conn ); } else // ask them to connect to us: { QString tmpkey = uuid(); tLog() << "Asking them to connect to us using" << tmpkey ; registerOffer( tmpkey, new_conn ); QVariantMap m; m.insert( "conntype", "request-offer" ); m.insert( "key", tmpkey ); m.insert( "offer", key ); m.insert( "controlid", Database::instance()->impl()->dbid() ); if (orig_conn) { orig_conn->sendMsg( Msg::factory( TomahawkUtils::toJson( m ), Msg::JSON ) ); } } } void Servent::socketConnected() { QTcpSocketExtra* sock = (QTcpSocketExtra*)sender(); tLog( LOGVERBOSE ) << Q_FUNC_INFO << thread() << "socket:" << sock << ", hostaddr:" << sock->peerAddress() << ", hostname:" << sock->peerName(); if ( sock->_conn.isNull() ) { sock->close(); sock->deleteLater(); tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Socket's connection was null, could have timed out or been given an invalid address"; return; } Connection* conn = sock->_conn.data(); handoverSocket( conn, sock ); } // transfers ownership of socket to the connection and inits the connection void Servent::handoverSocket( Connection* conn, QTcpSocketExtra* sock ) { Q_ASSERT( conn ); Q_ASSERT( sock ); Q_ASSERT( conn->socket().isNull() ); Q_ASSERT( sock->isValid() ); disconnect( sock, SIGNAL( readyRead() ), this, SLOT( readyRead() ) ); disconnect( sock, SIGNAL( disconnected() ), sock, SLOT( deleteLater() ) ); disconnect( sock, SIGNAL( error( QAbstractSocket::SocketError ) ), this, SLOT( socketError( QAbstractSocket::SocketError ) ) ); sock->_disowned = true; conn->setOutbound( sock->_outbound ); conn->setPeerPort( sock->peerPort() ); conn->start( sock ); } void Servent::cleanupSocket( QTcpSocketExtra* sock ) { if ( !sock ) { tLog() << "SocketError, sock is null"; return; } if ( sock->_conn.isNull() ) { tLog() << "SocketError, connection is null"; } sock->deleteLater(); } void Servent::initiateConnection( const SipInfo& sipInfo, Connection* conn ) { Q_D( Servent ); Q_ASSERT( sipInfo.isValid() ); Q_ASSERT( sipInfo.isVisible() ); Q_ASSERT( sipInfo.port() > 0 ); Q_ASSERT( conn ); // Check that we are not connecting to ourselves QList addresses = d->externalAddresses; if ( d->externalListenAll ) { addresses = QNetworkInterface::allAddresses(); } foreach ( QHostAddress ha, addresses ) { if ( sipInfo.host() == ha.toString() && sipInfo.port() == d_func()->port ) { tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Tomahawk won't try to connect to" << sipInfo.host() << ":" << sipInfo.port() << ": same IP as ourselves."; return; } } if ( sipInfo.host() == d_func()->externalHostname && sipInfo.port() == d_func()->port ) { tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Tomahawk won't try to connect to" << sipInfo.host() << ":" << sipInfo.port() << ": same IP as ourselves."; return; } if ( conn->firstMessage().isNull() ) { QVariantMap m; m["conntype"] = "accept-offer"; m["key"] = sipInfo.key(); m["controlid"] = Database::instance()->impl()->dbid(); conn->setFirstMessage( m ); } QTcpSocketExtra* sock = new QTcpSocketExtra(); sock->setConnectTimeout( CONNECT_TIMEOUT ); sock->_disowned = false; sock->_conn = conn; sock->_outbound = true; connect( sock, SIGNAL( connected() ), SLOT( socketConnected() ) ); connect( sock, SIGNAL( error( QAbstractSocket::SocketError ) ), this, SLOT( socketError( QAbstractSocket::SocketError ) ) ); if ( !conn->peerIpAddress().isNull() ) sock->connectToHost( conn->peerIpAddress(), sipInfo.port(), QTcpSocket::ReadWrite ); else sock->connectToHost( sipInfo.host(), sipInfo.port(), QTcpSocket::ReadWrite ); sock->moveToThread( thread() ); } void Servent::socketError( QAbstractSocket::SocketError e ) { QTcpSocketExtra* sock = (QTcpSocketExtra*)sender(); if ( !sock ) { tLog() << "SocketError, sock is null"; return; } if ( !sock->_conn.isNull() ) { Connection* conn = sock->_conn.data(); tLog() << "Servent::SocketError:" << e << conn->id() << conn->name(); if ( !sock->_disowned ) { // connection will delete if we already transferred ownership, otherwise: sock->deleteLater(); } conn->markAsFailed(); // will emit failed, then finished } else { tLog() << "SocketError, connection is null"; sock->deleteLater(); } } void Servent::checkACLResult( const QString& nodeid, const QString& username, Tomahawk::ACLStatus::Type peerStatus ) { if ( !d_func()->queuedForACLResult.contains( username ) ) { return; } if ( !d_func()->queuedForACLResult.value( username ).contains( nodeid ) ) { return; } tDebug( LOGVERBOSE ) << Q_FUNC_INFO << QString( "ACL status for user %1 is" ).arg( username ) << peerStatus; QSet peerInfos = d_func()->queuedForACLResult.value( username ).value( nodeid ); if ( peerStatus == Tomahawk::ACLStatus::Stream ) { foreach ( Tomahawk::peerinfo_ptr peerInfo, peerInfos ) { registerPeer( peerInfo ); } } // We have a result, so remove from queue d_func()->queuedForACLResult[username].remove( nodeid ); } void Servent::ipDetected() { Q_D( Servent ); QNetworkReply* reply = qobject_cast( sender() ); if ( reply->error() == QNetworkReply::NoError ) { bool ok; // We are called when the NetworkReply has finished so we should have all data available. const QVariantMap res = TomahawkUtils::parseJson( reply->readAll(), &ok ).toMap(); if ( !ok ) { tLog() << Q_FUNC_INFO << "Failed parsing ip-autodetection response"; d->externalPort = -1; emit ipDetectionFailed( QNetworkReply::NoError, tr( "Automatically detecting external IP failed: Could not parse JSON response." ) ); } else { QString externalIP = res.value( "ip" ).toString(); tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Found external IP:" << externalIP; d->externalHostname = externalIP; } } else { d->externalPort = -1; tLog() << Q_FUNC_INFO << "ip-autodetection returned an error:" << reply->errorString(); emit ipDetectionFailed( reply->error(), tr( "Automatically detecting external IP failed: %1" ).arg( reply->errorString() ) ); } d->ready = true; emit ready(); } void Servent::reverseOfferRequest( ControlConnection* orig_conn, const QString& theirdbid, const QString& key, const QString& theirkey ) { Q_ASSERT( this->thread() == QThread::currentThread() ); tDebug( LOGVERBOSE ) << "Servent::reverseOfferRequest received for" << key; Connection* new_conn = claimOffer( orig_conn, theirdbid, key ); if ( !new_conn ) { tDebug() << "claimOffer failed, killing requesting connection out of spite"; orig_conn->shutdown(); return; } QVariantMap m; m["conntype"] = "push-offer"; m["key"] = theirkey; m["controlid"] = Database::instance()->impl()->dbid(); new_conn->setFirstMessage( m ); createParallelConnection( orig_conn, new_conn, theirkey ); } bool Servent::visibleExternally() const { return (!d_func()->externalHostname.isNull()) || (d_func()->externalAddresses.length() > 0); } bool Servent::ipv6ConnectivityLikely() const { foreach ( QHostAddress ha, d_func()->externalAddresses ) { if ( ha.protocol() == QAbstractSocket::IPv6Protocol && Servent::isValidExternalIP( ha ) ) { return true; } } return false; } int Servent::port() const { return d_func()->port; } QList Servent::addresses() const { Q_D( const Servent ); if ( d->externalListenAll ) { QList addresses( QNetworkInterface::allAddresses() ); cleanAddresses( addresses ); return addresses; } return d->externalAddresses; } QString Servent::additionalAddress() const { return d_func()->externalHostname; } int Servent::additionalPort() const { return d_func()->externalPort; } bool equalByIPv6Address( QHostAddress a1, QHostAddress a2 ) { Q_IPV6ADDR addr1 = a1.toIPv6Address(); Q_IPV6ADDR addr2 = a2.toIPv6Address(); for (int i = 0; i < 16; ++i) { if ( addr1[i] != addr2[i] ) return false; } return true; } // return the appropriate connection for a given offer key, or NULL if invalid Connection* Servent::claimOffer( ControlConnection* cc, const QString &nodeid, const QString &key, const QHostAddress peer ) { Q_D( Servent ); // magic key for stream connections: if ( key.startsWith( "FILE_REQUEST_KEY:" ) ) { // check if the source IP matches an existing, authenticated connection if ( !d->noAuth && peer != QHostAddress::Any && !isIPWhitelisted( peer ) ) { bool authed = false; tDebug() << Q_FUNC_INFO << "Checking for ControlConnection with IP" << peer; QMutexLocker locker( &d->controlconnectionsMutex ); foreach ( ControlConnection* cc, d->controlconnections ) { if ( cc->socket() ) tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Probing:" << cc->name() << cc->socket()->peerAddress(); else tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Probing error:" << cc->name() << "has invalid socket"; // Always compare IPv6 addresses as IPv4 address are sometime simply IPv4 addresses, sometimes mapped IPv6 addresses if ( cc->socket() && equalByIPv6Address( cc->socket()->peerAddress(), peer ) ) { authed = true; break; } } if ( !authed ) { tLog() << "File transfer request rejected as the request came from an IP which we could not match to existing peer connections."; return NULL; } } QString fid = key.right( key.length() - 17 ); StreamConnection* sc = new StreamConnection( this, cc, fid ); return sc; } if ( key == "whitelist" ) // LAN IP address, check source IP { if ( isIPWhitelisted( peer ) ) { tDebug() << "Connection is from whitelisted IP range (LAN)"; ControlConnection* conn = new ControlConnection( this ); conn->setName( peer.toString() ); Tomahawk::Accounts::Account* account = Tomahawk::Accounts::AccountManager::instance()->zeroconfAccount(); // if we get this connection the account should exist and be enabled Q_ASSERT( account ); Q_ASSERT( account->enabled() ); // this is terrible, actually there should be a way to let this be created by the zeroconf plugin // because this way we rely on the ip being used as id in two totally different parts of the code Tomahawk::peerinfo_ptr peerInfo = Tomahawk::PeerInfo::get( account->sipPlugin(), peer.toString(), Tomahawk::PeerInfo::AutoCreate ); peerInfo->setContactId( peer.toString() ); peerInfoDebug( peerInfo ); conn->addPeerInfo( peerInfo ); return conn; } else { tDebug() << "Connection claimed to be whitelisted, but wasn't."; return NULL; } } if ( d->lazyoffers.contains( key ) ) { ControlConnection* conn = new ControlConnection( this ); conn->setName( d->lazyoffers.value( key ).first->contactId() ); conn->addPeerInfo( d->lazyoffers.value( key ).first ); conn->setId( d->lazyoffers.value( key ).second ); if ( !nodeid.isEmpty() ) { // Used by the connection for the ACL check // If there isn't a nodeid it's not the first connection and will already have been stopped conn->setNodeId( nodeid ); } return conn; } else if ( d->offers.contains( key ) ) { QPointer conn = d->offers.value( key ); if ( conn.isNull() ) { // This can happen if it's a streamconnection, but the audioengine has // already closed the iodevice, causing the connection to be deleted before // the peer connects and provides the first byte tLog() << Q_FUNC_INFO << "invalid/expired offer:" << key; return NULL; } tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "nodeid is: " << nodeid; if ( !nodeid.isEmpty() ) { // Used by the connection for the ACL check // If there isn't a nodeid it's not the first connection and will already have been stopped conn->setNodeId( nodeid ); } if ( conn.data()->onceOnly() ) { d->offers.remove( key ); return conn.data(); } else { return conn.data()->clone(); } } else if ( d->noAuth ) { Connection* conn; conn = new ControlConnection( this ); conn->setName( key ); return conn; } else { tLog() << "Invalid offer key:" << key; return NULL; } } void Servent::remoteIODeviceFactory( const Tomahawk::result_ptr& result, const QString& url, boost::function< void ( const QString&, QSharedPointer< QIODevice >& ) > callback ) { QSharedPointer sp; QStringList parts = url.mid( QString( "servent://" ).length() ).split( "\t" ); const QString sourceName = parts.at( 0 ); const QString fileId = parts.at( 1 ); source_ptr s = SourceList::instance()->get( sourceName ); if ( s.isNull() || !s->controlConnection() ) { callback( result->url(), sp ); return; } ControlConnection* cc = s->controlConnection(); StreamConnection* sc = new StreamConnection( this, cc, fileId, result ); createParallelConnection( cc, sc, QString( "FILE_REQUEST_KEY:%1" ).arg( fileId ) ); //boost::functions cannot accept temporaries as parameters sp = sc->iodevice(); callback( result->url(), sp ); } void Servent::registerStreamConnection( StreamConnection* sc ) { Q_ASSERT( !d_func()->scsessions.contains( sc ) ); tDebug( LOGVERBOSE ) << "Registering Stream" << d_func()->scsessions.length() + 1; QMutexLocker lock( &d_func()->ftsession_mut ); d_func()->scsessions.append( sc ); printCurrentTransfers(); emit streamStarted( sc ); } void Servent::onStreamFinished( StreamConnection* sc ) { Q_ASSERT( sc ); tDebug( LOGVERBOSE ) << "Stream Finished, unregistering" << sc->id(); QMutexLocker lock( &d_func()->ftsession_mut ); d_func()->scsessions.removeAll( sc ); printCurrentTransfers(); emit streamFinished( sc ); } // used for debug output: void Servent::printCurrentTransfers() { int k = 1; // qDebug() << "~~~ Active file transfer connections:" << m_scsessions.length(); foreach ( StreamConnection* i, d_func()->scsessions ) { qDebug() << k << ") " << i->id(); } qDebug() << endl; } void Servent::cleanAddresses( QList& addresses ) const { QList::iterator iter = addresses.begin(); while ( iter != addresses.end() ) { QString hostString = iter->toString(); if ( hostString.startsWith( QLatin1String( "127.0.0." ) ) //< IPv4 localhost // IPv6 localhost || hostString == "::1" // IPv4 localhost as IPv6 address || hostString == "::7F00:1" ) { iter = addresses.erase( iter ); // Always continue if we changed iter as we might have reached the end continue; } // Remove IPv6 link local addresses if ( iter->isInSubnet( QHostAddress::parseSubnet( "fe80::/10" ) ) ) { iter = addresses.erase( iter ); continue; } // Advance to next element ++iter; } } bool Servent::isIPWhitelisted( QHostAddress ip ) { tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Performing checks against ip" << ip.toString(); typedef QPair< QHostAddress, int > range; QList< range > subnetEntries; QList< QNetworkInterface > networkInterfaces = QNetworkInterface::allInterfaces(); foreach ( QNetworkInterface interface, networkInterfaces ) { tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Checking interface" << interface.humanReadableName(); QList< QNetworkAddressEntry > addressEntries = interface.addressEntries(); foreach ( QNetworkAddressEntry addressEntry, addressEntries ) { tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Checking address entry with ip" << addressEntry.ip().toString() << "and prefix length" << addressEntry.prefixLength(); if ( ip.isInSubnet( addressEntry.ip(), addressEntry.prefixLength() ) ) { tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "success"; return true; } } } #if QT_VERSION < QT_VERSION_CHECK( 5, 0, 0 ) // Qt4 cannot cope correctly with IPv4 addresses mapped into the IPv6 // address space if ( ip.protocol() == QAbstractSocket::IPv6Protocol ) { Q_IPV6ADDR ipv6 = ip.toIPv6Address(); // Check if it is actually an IPv4 address // First 80 bits are zero, then 16 bits 1s bool isIPv4 = true; for ( int i = 0; i < 9; i++) { isIPv4 &= ( ipv6[i] == 0 ); } isIPv4 &= ( ipv6[10] == 0xff ); isIPv4 &= ( ipv6[11] == 0xff ); if ( isIPv4 ) { // Convert to a real IPv4 address and rerun checks quint32 ipv4 = (static_cast(ipv6[12]) << 24) | (static_cast(ipv6[13]) << 16) | (static_cast(ipv6[14]) << 8) | static_cast(ipv6[15]); QHostAddress addr( ipv4 ); return isIPWhitelisted( addr ); } } #endif tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "failure"; return false; } bool Servent::connectedToSession( const QString& session ) { Q_D( Servent ); QMutexLocker locker( &d->controlconnectionsMutex ); foreach ( ControlConnection* cc, d_func()->controlconnections ) { Q_ASSERT( cc ); if ( cc->id() == session ) return true; } return false; } unsigned int Servent::numConnectedPeers() const { return d_func()->controlconnections.length(); } QList Servent::streams() const { return d_func()->scsessions; } void Servent::triggerDBSync() { // tell peers we have new stuff they should sync QList< source_ptr > sources = SourceList::instance()->sources(); foreach ( const source_ptr& src, sources ) { // skip local source if ( src.isNull() || src->isLocal() ) continue; if ( src->controlConnection() && src->controlConnection()->dbSyncConnection() ) // source online? src->controlConnection()->dbSyncConnection()->trigger(); } emit dbSyncTriggered(); } bool Servent::isReady() const { return d_func()->ready; } tomahawk-player/thirdparty/libportfwd/src/main.cpp000664 001750 001750 00000002752 12661705042 023565 0ustar00stefanstefan000000 000000 /* * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "portfwd/portfwd.h" int main(int argc, char** argv) { if(argc!=2) { printf("Usage: %s \n", argv[0]); return 1; } int port = atoi(argv[1]); Portfwd pf; if(!pf.init(2000)) { printf("Portfwd.init() failed.\n"); return 2; } printf("External IP: %s\n", pf.external_ip().c_str()); printf("LAN IP: %s\n", pf.lan_ip().c_str()); printf("Max upstream: %d bps, max downstream: %d bps\n", pf.max_upstream_bps(), pf.max_downstream_bps() ); printf("%s\n", ((pf.add( port ))?"Added":"Failed to add") ); printf("Any key to exit...\n"); char foo; scanf("%c",&foo); printf("%s\n", ((pf.remove( port ))?"Removed.":"Failed to remove") ); return 0; } tomahawk-player/lang/tomahawk_fi.ts000664 001750 001750 00000641144 12661705042 020566 0ustar00stefanstefan000000 000000 ACLJobDelegate Allow %1 to connect and stream from you? Sallitaanko käyttäjän %1 yhdistää ja toistaa sinulta virtaa? Allow Streaming Salli virran toisto Deny Access Kiellä pääsy ACLJobItem Tomahawk needs you to decide whether %1 is allowed to connect. Tomahawk tarvitsee sinua päättämään, sallitaanko käyttäjän %1 yhdistää. AccountFactoryWrapper Add Account Lisää tili AccountFactoryWrapperDelegate Online Verkossa Connecting... Yhdistetään… Offline Ei verkossa AccountListWidget Connections Yhteydet Connect &All Yhdistä &kaikki Disconnect &All Katkaise &kaikki ActionCollection &Listen Along Kuuntele &mukana Stop &Listening Along Lopeta &mukana kuunteleminen &Follow in Real-Time &Seuraa reaaliajassa &Listen Privately &Kuuntele yksityisesti &Load Playlist &Lataa soittolista &Load Station &Lataa asema &Rename Playlist &Muuta soittolistan nimeä &Rename Station &Muuta aseman nimeä &Copy Playlist Link &Kopioi soittolistan linkki &Play &Soita &Stop &Pysäytä &Previous Track &Edellinen kappale &Next Track S&euraava kappale &Quit &Lopeta U&pdate Collection &Päivitä kokoelma Fully &Rescan Collection &Muodosta kokoelma alusta alkaen Import Playlist... Tuo soittolista… Show Offline Friends Näytä poissa verkossa olevat kaverit &Configure Tomahawk... Tomahawkin &asetukset… Minimize Pienennä Zoom Zoomaa Enter Full Screen Siirry koko näyttöön Hide Menu Bar Piilota valikkorivi Diagnostics... Diagnostiikka… About &Tomahawk... Tietoa &Tomahawkista… &Legal Information... Lakitiet&oa… &View Logfile &Näytä lokitiedosto Check For Updates... Tarkista päivitykset… 0.8 0.8 Report a Bug Ilmoita virheestä Get Support Hanki tukea Help Us Translate Auta kääntämisessä &Controls &Ohjaus &Settings &Asetukset &Help O&hje What's New in ... Mitä uutta versiossa… &Window &Ikkuna Main Menu Päävalikko AlbumInfoWidget Top Hits Parhaat hitit Album Details Albumin tiedot AlbumModel All albums from %1 Kaikki artistin %1 albumit All albums Kaikki albumit ArtistInfoWidget Top Albums Parhaat albumit Top Hits Parhaat hitit Show More Näytä lisää Biography Biografia Related Artists Samankaltaisia artisteja Sorry, we could not find any albums for this artist! Valitettavasti emme löytänet yhtään tämän artistin albumia! Sorry, we could not find any related artists! Valitettavasti emme löytäneet yhtään samankaltaista artistia! Sorry, we could not find any top hits for this artist! Valitettavasti emme löytäneet yhtään tämän artistin parhaista hiteistä! Music Musiikki Songs Kappaleet Albums Albumit AudioControls Shuffle Sekoita Repeat Toista Time Elapsed Kulunut aika Time Remaining Jäljellä oleva aika Playing from %1 Soitetaan lähteestä %1 AudioEngine Sorry, Tomahawk couldn't find the track '%1' by %2 Valitettavasti Tomahawk ei löytänyt artistin %2 kappaletta ”%1” Sorry, Tomahawk couldn't find the artist '%1' Valitettavasti Tomahawk ei löytänyt artistia ”%1” Sorry, Tomahawk couldn't find the album '%1' by %2 Valitettavasti Tomahawk ei löytänyt artistin %2 albumia ”%1” Sorry, couldn't find any playable tracks Valitettavasti emme löytäneet yhtään soittokelpoista kappaletta CaptionLabel Close Sulje CategoryAddItem Create new Playlist Luo uusi soittolista Create new Station Luo uusi asema New Station Uusi asema %1 Station %1 – asema CategoryItem Playlists Soittolistat Stations Asemat ClearButton Clear Tyhjennä CollectionItem Collection Kokoelma CollectionViewPage Sorry, there are no albums in this collection! Tässä kokoelmassa ei ole albumeita! Artists Artistit Albums Albumit Songs Kappaleet After you have scanned your music collection you will find your tracks right here. Löydät kappaleesi tästä, kunhan musiikkikokoelmasi haku on valmistunut. This collection is empty. Tämä kokoelma on tyhjä. Cloud collections aren't supported in the flat view yet. We will have them covered soon. Switch to another view to navigate them. Pilvikokoelmia ei vielä tueta litteässä näkymässä. Lisäämme tuen niille pian. Tällä välin voit selata niitä vaihtamalla toiseen näkymään. ColumnItemDelegate Unknown Tuntematon ColumnView Sorry, your filter '%1' did not match any results. Valitettavasti suodattimesi ”%1” ei tuottanut yhtään tuloksia. ColumnViewPreviewWidget Composer: Säveltäjä: Duration: Kesto: Bitrate: Bittinopeus: Year: Vuosi: Age: Ikä: %1 kbps %1 kbps ContextView Playlist Details Soittolistan tiedot This playlist is currently empty. Tämä soittolista on nyt tyhjä. This playlist is currently empty. Add some tracks to it and enjoy the music! Tämä soittolista on parhaillaan tyhjä. Lisää kappaleita ja nauti musiikista! DelegateConfigWrapper About Tietoa Delete Account Poista tili Config Error Asetusvirhe About this Account Tietoa tästä tilistä DiagnosticsDialog Tomahawk Diagnostics Tomahawk-diagnostiikka &Copy to Clipboard &Kopioi leikepöydälle Open &Log-file &Avaa lokitiedosto EchonestSteerer Steer this station: Ohjaa tätä asemaa: Much less Paljon vähemmän Less Vähemän A bit less Hieman vähemmän Keep at current Pidä nykyisellä A bit more Hieman enemmän More Enemmän Much more Paljon enemmän Tempo Tempo Loudness Äänekkyys Danceability Tanssittavuus Energy Energia Song Hotttnesss Kappaleen suosittuus Artist Hotttnesss Artistin suosittuus Artist Familiarity Artistin tuttuus By Description Kuvauksen perusteella Enter a description Anna kuvaus Apply steering command Käytä ohjauskomentoa Reset all steering commands Nollaa kaikki ohjauskomennot FilterHeader Filter... Suodata… GlobalActionManager Resolver installation from file %1 failed. Selvittimen asennus tiedostosta %1 epäonnistui. Install plug-in Asenna liitännäinen <b>%1</b> %2<br/>by <b>%3</b><br/><br/>You are attempting to install a Tomahawk plug-in from an unknown source. Plug-ins from untrusted sources may put your data at risk.<br/>Do you want to install this plug-in? <b>%1</b> %2<br/>Tekijä: <b>%3</b><br/><br/>Yrität asentaa Tomahawk-liitännäistä tuntemattomasta lähteestä. Luottamattomien lähteiden liitännäiset saattavat vaarantaa tietosi.<br/>Haluatko asentaa tämän liitännäisen? HatchetAccountConfig Connect to your Hatchet account Yhdistä Hatchet-tiliisi <html><head/><body><p><a href="http://blog.hatchet.is">Learn More</a> and/or <a href="http://hatchet.is/register"><span style=" text-decoration: underline; color:#0000ff;">Create Account</span></a></p></body></html> <html><head/><body><p><a href="http://blog.hatchet.is">Lue lisää</a> ja/tai <a href="http://hatchet.is/register"><span style=" text-decoration: underline; color:#0000ff;">luo tili</span></a></p></body></html> Enter One-time Password (OTP) Anna kertakäyttöinen salasana (OTP) Username Käyttäjätunnus Hatchet username Hatchet-käyttäjätunnus Password: Salasana: Hatchet password Hatchet-salasana Login Kirjaudu HistoryWidget Recently Played Tracks Viime aikoina kuunnellut kappaleet Your recently played tracks Viime aikoina kuuntelemasi kappaleet %1's recently played tracks Käyttäjän %1 viime aikoina kuuntelemat kappaleet Sorry, we could not find any recent plays! Valitettavasti emme löytäneet yhtään viimeaikaisia soittoja! HostDialog Host Settings Koneasetukset Configure your external IP address or host name here. Make sure to manually forward the selected port to this host on your router. Aseta julkinen IP-osoitteesi tai konenimesi tähän. Varmista, että ohjaat reitittimestäsi valitun portin tähän koneeseen manuaalisesti. Static Host Name: Kiinteä konenimi: Static Port: Kiinteä portti: Automatically detect external IP address Tunnista ulkoinen IP-osoite automaattisesti InboxItem Inbox Saapuneet InboxJobItem Sent %1 by %2 to %3. Lähetettiin artistin %2 kappale %1 kaverille %3. %1 sent you %2 by %3. %1 lähetti sinulle artistin %3 kappaleen %2. InboxPage Inbox Details Saapuneiden tiedot Your friends have not shared any recommendations with you yet. Connect with them and share your musical gems! Kaverisi eivät ole vielä jakaneet kanssasi yhtään suosituksia. Ole yhteydessä heihin ja jaa musiikkihelmesi! IndexingJobItem Indexing Music Library Indeksoidaan musiikkikirjastoa JSResolver Script Resolver Warning: API call %1 returned data synchronously. Skriptiselvittimen varoitus: API-kutsu %1 palautti dataa synkronisesti. LastFmConfig Scrobble tracks to Last.fm Skrobblaa kappaleet Last.fm:ään Username: Käyttäjätunnus: Password: Salasana: Test Login Kokeile kirjautumista Import Playback History Tuo soittohistoria Synchronize Loved Tracks Synkronoi tykätyt kappaleet LatchedStatusItem %1 is listening along with you! %1 kuuntelee kanssasi! LoadPlaylist Load Playlist Lataa soittolista Enter the URL of the hosted playlist (e.g. .xspf format) or click the button to select a local M3U of XSPF playlist to import. Anna ulkoisen soittolistan (esim. xspf-muotoisen) verkko-osoite tai valitse tuotava paikallinen M3U- tai XSPF-soittolista painamalla painikkeesta. Playlist URL Soittolistan osoite Enter URL... Anna osoite… ... Automatically Update (upon changes to hosted playlist) Päivitä automaattisesti (ulkoisen soittolistan muuttuessa) To import a playlist from Spotify, Rdio, Beats, etc. - simply drag the link into Tomahawk. Tuo soittolista Spotifysta, Rdiosta, Beatsista jne. vetämällä linkki Tomahawkiin. LoadPlaylistDialog Load Playlist Lataa soittolista Playlists (*.xspf *.m3u *.jspf) Soittolistat (*.xspf *.m3u *.jspf) LovedTracksItem Top Loved Tracks Tykätyimmät kappaleet Favorites Suosikit Sorry, we could not find any of your Favorites! Valitettavasti emme löytäneet yhtään suosikeistasi! The most loved tracks from all your friends Kaikkien kaveriesi tykätyimmät kappaleet All of your loved tracks Kaikki tykkäämäsi kappaleet All of %1's loved tracks Kaikki käyttäjän %1 tykkäämät kappaleet MetadataEditor Tags Tagit Title: Nimi: Title... Nimi… Artist: Artisti: Artist... Artisti… Album: Albumi: Album... Albumi… Track Number: Kappaleen numero: Duration: Kesto: 00.00 00,00 Year: Vuosi: Bitrate: Bittinopeus: File Tiedosto File Name: Tiedostonimi: File Name... Tiedostonimi… File Size... Tiedostokoko… File size... Tiedostokoko… File Size: Tiedostokoko: Back Edellinen Forward Seuraava Error Virhe Could not write tags to file: %1 Tunnisteiden kirjoittaminen epäonnistui tiedostoon: %1 Properties Ominaisuudet NetworkActivityWidget Trending Tracks Suositut kappaleet Hot Playlists Suositut soittolistat Trending Artists Suositut artistit PlayableModel Artist Artisti Title Nimi Composer Säveltäjä Album Albumi Track Kappale Duration Kesto Bitrate Bittinopeus Age Ikä Year Vuosi Size Koko Origin Alkuperä Accuracy Tarkkuus Perfect match Täysosuma Very good match Erittäin hyvä osuma Good match Hyvä osuma Vague match Epämääräinen osuma Bad match Kehno osuma Very bad match Erittäin kehno osuma Not available Ei saatavilla Searching... Haetaan… Name Nimi PlaylistItemDelegate played %1 by you kuuntelit %1 played %1 by %2 %2 kuunteli %1 PlaylistModel A playlist you created %1. Soittolista, jonka loit %1. A playlist by %1, created %2. Soittolista, jonka %1 loi %2. All tracks by %1 on album %2 Kaikki artistin %1 kappaleet albumilla %2 All tracks by %1 Kaikki artistin %1 kappaleet ProxyDialog Proxy Settings Välityspalvelinasetukset Hostname of proxy server Välityspalvelimen konenimi Host Kone Port Portti Proxy login Välityspalvelimen tunnus User Käyttäjä Password Salasana Proxy password Välityspalvelimen salasana No Proxy Hosts: (Overrides system proxy) Välityspalvelittomat osoitteet: (ohittaa järjestelmän välityspalvelimen) localhost *.example.com (space separated) localhost *.esimerkki.fi (välilyönnein eroteltuna) Use proxy for DNS lookups? Käytetäänkö välityspalvelinta DNS-hauissa? QObject %n year(s) ago %n vuosi sitten%n vuotta sitten %n year(s) %n vuosi%n vuotta %n month(s) ago %n kuukausi sitten%n kuukautta sitten %n month(s) %n kuukausi%n kuukautta %n week(s) ago %n viikko sitten%n viikkoa sitten %n week(s) %n viikko%n viikkoa %n day(s) ago %n päivä sitten%n päivää sitten %n day(s) %n päivä%n päivää %n hour(s) ago %n tunti sitten%n tuntia sitten %n hour(s) %n tunti%n tuntia %1 minutes ago %1 minuuttia sitten %1 minutes %1 minuuttia just now juuri nyt Friend Finders Kaverietsimet Music Finders Musiikkietsimet Status Updaters Tilanpäivittimet %1 Config %1-asetukset Songs Beginning of a sentence summary Kappaleet No configured filters! Ei asetettuja suodattimia! and Inserted between items in a list of two ja , Inserted between items in a list , . Inserted when ending a sentence summary . , and Inserted between the last two items in a list of more than two ja and Inserted before the last item in a list ja and Inserted before the sorting summary in a sentence summary ja QSQLiteResult No query Ei kyselyä Parameter count mismatch Parametrien määrä ei täsmää QueueItem Queue Jono QueueView Open Queue Avaa jono Queue Details Jonon tiedot Queue Jono The queue is currently empty. Drop something to enqueue it! Tämä jono on nyt tyhjä. Laita jotain jonoon pudottamalla se tähän! ResolverConfigDelegate Not found: %1 Ei löydy: %1 Failed to load: %1 Lataaminen epäonnistui: %1 ScannerStatusItem Scanning Collection Etsitään kokoelmaa ScriptCollectionHeader Reload Collection Päivitä kokoelma ScriptEngine Resolver Error: %1:%2 %3 Selvittimen virhe: %1:%2 %3 SSL Error SSL-virhe You have asked Tomahawk to connect securely to <b>%1</b>, but we can't confirm that your connection is secure:<br><br><b>%2</b><br><br>Do you want to trust this connection? Olet pyytänyt Tomahawkia yhdistämään turvallisesti palvelimeen <b>%1</b>, mutta yhteyden turvallisuutta ei voida varmistaa:<br><br><b>%2</b><br><br>Haluatko luottaa tähän yhteyteen? Trust certificate Luota varmenteeseen SearchLineEdit Search Hae SearchWidget Search: %1 Haku: %1 Results for '%1' Tulokset haulle ”%1” Songs Kappaleet Show More Näytä lisää Artists Artistit Albums Albumit Sorry, we could not find any artists! Yhtään artistia ei löytynyt. Sorry, we could not find any albums! Yhtään albumia ei löytynyt. Sorry, we could not find any songs! Yhtään kappaletta ei löytynyt. Servent Automatically detecting external IP failed: Could not parse JSON response. Ulkoisen IP-osoitteen automaattinen tunnistus epäonnistui: JSON-vastausta ei saatu jäsennettyä. Automatically detecting external IP failed: %1 Ulkoisen IP-osoitteen automaattinen tunnistus epäonnistui: %1 SettingsDialog Collection Kokoelma Advanced Lisäasetukset All Kaikki Install Plug-In... Asenna liitännäinen… Some changed settings will not take effect until Tomahawk is restarted Jotkin asetusmuutokset tulevat voimaan vasta, kun Tomahawk käynnistetään uudelleen. Configure the accounts and services used by Tomahawk to search and retrieve music, find your friends and update your status. Aseta tilit ja palvelut, joita Tomahawk käyttää musiikin etsimiseen ja noutamiseen, kaveriesi etsimiseen sekä tilasi päivittämiseen. Plug-Ins Liitännäiset Manage how Tomahawk finds music on your computer. Hallitse Tomahawkin tapaa etsiä musiikkia tietokoneeltasi. Configure Tomahawk's advanced settings, including network connectivity settings, browser interaction and more. Aseta Tomahawkin lisäasetuksia, joihin kuuluu verkkoyhteysasetukset, selainyhteys ja muuta. Open Directory Avaa kansio Install resolver from file Asenna selvitin tiedostosta Tomahawk Resolvers (*.axe *.js);;All files (*) Tomahawk-selvittimet (*.axe *.js);;Kaikki tiedostot (*) Delete all Access Control entries? Poistetaanko kaikki pääsynvalvontatietueet? Do you really want to delete all Access Control entries? You will be asked for a decision again for each peer that you connect to. Haluatko varmasti poistaa kaikki pääsynvalvontatietueet? Sinulta tullaan kysymään päätös uudelleen jokaisen sellaisen vertaisen kohdalla, johon yhdistät. Information Tiedoksi Settings_Accounts Filter by Capability: Suodata ominaisuuden perusteella: Settings_Advanced Remote Peer Connection Method Etävertaisten yhteystapa Active (your host needs to be directly reachable) Aktiivinen (koneesi tarvitsee olla tavoitettavissa verkosta suoraan) UPnP / Automatic Port Forwarding (recommended) UPnP / aatomaattinen porttien uudelleenohjaus (suositellaan) Manual Port Forwarding Manuaalinen porttien uudelleenohjaus Host Settings... Koneasetukset… SOCKS Proxy SOCKS-välityspalvelin Use SOCKS Proxy Käytä SOCKS-välityspalvelinta Proxy Settings... Välityspalvelinasetukset… Other Settings Muut asetukset Allow web browsers to interact with Tomahawk (recommended) Salli verkkoselainten olla yhteydessä Tomahawkiin (suositellaan) Allow other computers to interact with Tomahawk (not recommended yet) Salli muiden tietokoneiden olla yhteydessä Tomahawkiin (ei vielä suositeltavaa) Send Tomahawk Crash Reports Lähetä Tomahawkin kaatumisilmoitukset Show Notifications on song change Näytä ilmoituksia kappaleen vaihtuessa Clear All Access Control Entries Tyhjennä kaikki pääsynvalvontatietueet Settings_Collection Folders to scan for music: Kansiot, joista etsitään musiikkia: Due to the unique way Tomahawk works, your music files must at least have Artist & Title metadata/ID3 tags to be added to your Collection. Tomahawkin ainutlaatuisen toimintatavan vuoksi musiikkitiedoston metatiedoissa/ID3-tunnisteissa tarvitsee olla ainakin artistin ja kappaleen nimi, jotta tiedosto lisätään kokoelmaasi. + + - The Echo Nest supports keeping track of your catalog metadata and using it to craft personalized radios. Enabling this option will allow you (and all your friends) to create automatic playlists and stations based on your personal taste profile. The Echo Nest voi pitää kirjaa metadatahakemistostasi ja käyttää sitä personoitujen radioiden tekemiseen. Tämän valinnan käyttöönottaminen mahdollistaa sinun (ja kaikkien kaveriesi) luoda automaattisia soittolistoja ja asemia sinun henkilökohtaisen musiikkimakuprofiilisi perusteella. Upload Collection info to enable personalized "User Radio" Ota personoitu ”Käyttäjäradio” käyttöön lähettämällä kokoelman tiedot Watch for changes (automatically update Collection) Tarkkaile muutoksia (päivitä kokoelma automaattisesti) Time between scans (in seconds): Etsimisten aikaväli (sekunteina): SlideSwitchButton On Päällä Off Pois SocialWidget Facebook Facebook Twitter Twitter Tweet Tviittaa Listening to "%1" by %2. %3 Kuuntelen ”%1” artistilta %2. %3 Listening to "%1" by %2 on "%3". %4 Kuuntelen ”%1” artistilta %2, albumilta ”%3”. %4 %1 characters left %1 merkkiä jäljellä SourceDelegate All available tracks Kaikki saatavilla olevat kappaleet Drop to send tracks Lähetä kappaleet pudottamalla Show Näytä Hide Piilota SourceInfoWidget Recent Albums Viimeaikaiset albumit Latest Additions Uusimmat lisäykset Recently Played Tracks Viime aikoina kuunnellut kappaleet New Additions Uudet lisäykset My recent activity Viimeaikainen toimintani Recent activity from %1 Käyttäjän %1 viimeaikainen toiminta SourceItem Latest Additions Uusimmat lisäykset History Historia SuperCollection Superkokoelma Latest additions to your collection Uusimmat lisäykset kokoelmaasi Latest additions to %1's collection Uusimmat lisäykset käyttäjän %1 kokoelmaan Sorry, we could not find any recent additions! Valitettavasti emme löytäneet yhtään viimeaikaisia lisäyksiä! SourceTreeView &Copy Link &Kopioi linkki &Delete %1 &Poista %1 Add to my Playlists Lisää omiin soittolistoihin Add to my Automatic Playlists Lisää omiin automaattisiin soittolistoihin Add to my Stations Lisää omiin asemiin &Export Playlist &Vie soittolista playlist soittolistan automatic playlist automaattisen soittolistan station aseman Would you like to delete the %1 <b>"%2"</b>? e.g. Would you like to delete the playlist named Foobar? Haluatko poistaa %1 <b>”%2”</b>? Delete Poista Save XSPF Tallenna XSPF Playlists (*.xspf) Soittolistat (*.xspf) SourcesModel Group Ryhmä Source Lähde Collection Kokoelma Playlist soittolista Automatic Playlist automaattinen soittolista Station asema Cloud Collections Pilvikokoelmat Discover Löydöt Open Pages Avoimet sivut Your Music Oma musiikki Friends Kaverit SpotifyConfig Configure your Spotify account Spotify-tilisi asetukset Username or Facebook Email Käyttäjätunnus tai Facebookin sähköposti Log In Kirjaudu Right click on any Tomahawk playlist to sync it to Spotify. Synkronoi Tomahawk-soittolista Spotifyn kanssa napsauttamalla hiiren oikealla. Select All Valitse kaikki Sync Starred tracks to Loved tracks Synkronoi tähdellä merkityt kappaleet tykättyihin kappaleisiin High Quality Streams Laadukkaat virrat Use this to force Spotify to never announce listening data to Social Networks Pakota Spotify olemaan lähettämättä kuuntelutietoja sosiaalisiin verkkoihin Always run in Private Mode Ole aina yksityisessä tilassa Spotify playlists to keep in sync: Synkronoitavat Spotify-soittolistat: Delete Tomahawk playlist when removing synchronization Poista Tomahawkin soittolista, kun synkronointi lopetetaan Username: Käyttäjätunnus: Password: Salasana: SpotifyPlaylistUpdater Delete associated Spotify playlist? Poistetaanko liitetty Spotify-soittolista? TemporaryPageItem Copy Artist Link Kopioi artistin linkki Copy Album Link Kopioi albumin linkki Copy Track Link Kopioi kappaleen linkki Tomahawk::Accounts::AccountDelegate Add Account Lisää tili Remove Poista %1 downloads %1 latausta Online Verkossa Connecting... Yhdistetään… Offline Ei verkossa Tomahawk::Accounts::AccountModel Manual Install Required Manuaalinen asennus tarvitaan Unfortunately, automatic installation of this resolver is not available or disabled for your platform.<br /><br />Please use "Install from file" above, by fetching it from your distribution or compiling it yourself. Further instructions can be found here:<br /><br />http://www.tomahawk-player.org/resolvers/%1 Valitettavasti tämän selvittimen automaattinen asennus ei ole saatavilla tai on poissa käytöstä alustallasi.<br /><br />Hae selvitin jakelusi kautta tai kääntämällä se itse, ja käytä sitten Asenna tiedostosta -painiketta. Lisäohjeita on osoitteessa:<br /><br />http://www.tomahawk-player.org/resolvers/%1 Tomahawk::Accounts::GoogleWrapper Configure this Google Account Tämän Google-tilin asetukset Google Address: Google-osoite: Enter your Google login to connect with your friends using Tomahawk! Ole yhteydessä Tomahawkia käyttäviin kavereihisi antamalla Google-tunnuksesi! username@gmail.com käyttäjätunnus@gmail.com You may need to change your %1Google Account Settings%2 to login. %1 is <a href>, %2 is </a> Sinun tarvitsee ehkä muuttaa %1Google-tilin asetuksia%2 voidaksesi kirjautua sisään. Tomahawk::Accounts::GoogleWrapperFactory Login to directly connect to your Google Talk contacts that also use Tomahawk. Kirjaudu ollaksesi suoraan yhteydessä Google Talk -yhteystietoihisi, jotka käyttävät Tomahawkia. Tomahawk::Accounts::GoogleWrapperSip Enter Google Address Anna Google-osoite Add Friend Lisää kaveri Enter Google Address: Anna Google-osoite: Tomahawk::Accounts::HatchetAccountConfig Logged in as: %1 Kirjauduttu käyttäjänä: %1 Log out Kirjaudu ulos Log in Kirjaudu Continue Jatka Tomahawk::Accounts::HatchetAccountFactory Connect to Hatchet to capture your playback data, sync your playlists to Android and more. Yhdistä Hatchetiin ja ota soittotiedot talteen, synkronoi soittolistasi Androidiin sekä muuta. Tomahawk::Accounts::LastFmAccountFactory Scrobble your tracks to last.fm, and find freely downloadable tracks to play Skrobblaa kappaleet Last.fm:ään, ja löydä kuunneltavaksi ilmaisesti ladattavia kappaleita Tomahawk::Accounts::LastFmConfig Testing... Kokeillaan… Test Login Kokeile kirjautumista Importing %1 e.g. Importing 2012/01/01 Tuodaan %1 Importing History... Tuodaan historiaa… History Incomplete. Resume Text on a button that resumes import Historia epätäydellinen. Jatka tuontia Playback History Imported Soittohistoria tuotu Failed Epäonnistui Success Onnistui Could not contact server Palvelimeen ei saatu yhteyttä Synchronizing... Synkronoidaan… Synchronization Finished Synkronointi valmis Tomahawk::Accounts::ResolverAccountFactory Resolver installation error: cannot open bundle. Selvittimen asennusvirhe: pakkausta ei voi avata. Resolver installation error: incomplete bundle. Selvittimen asennusvirhe: vaillinainen pakkaus. Resolver installation error: bad metadata in bundle. Selvittimen asennusvirhe: pakkauksessa on virheellistä metatietoa. Resolver installation error: platform mismatch. Selvittimen asennusvirhe: käyttöjärjestelmäalusta ei täsmää. Resolver installation error: Tomahawk %1 or newer is required. Selvittimen asennusvirhe: Tomahawk %1 tai uudempi vaaditaan. Tomahawk::Accounts::SpotifyAccount Sync with Spotify Synkronoi Spotifyn kanssa Re-enable syncing with Spotify Ota Spotifyn kanssa synkronointi käyttöön Create local copy Luo paikallinen kopio Subscribe to playlist changes Tilaa soittolistojen muutokset Re-enable playlist subscription Tilaa soittolistojen muutokset uudelleen Stop subscribing to changes Lopeta muutosten tilaus Enable Spotify collaborations Käytä Spotify-yhteistöitä Disable Spotify collaborations Poista Spotify-yhteistyöt käytöstä Stop syncing with Spotify Lopeta Spotifyn kanssa synkronointi Tomahawk::Accounts::SpotifyAccountConfig Logging in... Kirjaudutaan… Failed: %1 Epäonnistui: %1 Logged in as %1 Kirjauduttu käyttäjänä %1 Log Out Kirjaudu ulos Log In Kirjaudu Tomahawk::Accounts::SpotifyAccountFactory Play music from and sync your playlists with Spotify Premium Toista musiikkia Spotify Premiumin kautta ja synkronoi soittolistasi sen kanssa Tomahawk::Accounts::TelepathyConfigStorage the KDE instant messaging framework KDE:n pikaviestintäjärjestelmä KDE Instant Messaging Accounts KDE:n pikaviestintätilit Tomahawk::Accounts::XmppAccountFactory Login to connect to your Jabber/XMPP contacts that also use Tomahawk. Kirjaudu ollaksesi yhteydessä Jabber/XMPP-yhteystietoihisi, jotka käyttävät Tomahawkia. Tomahawk::Accounts::XmppConfigWidget Account provided by %1. Tilin lähde: %1 You forgot to enter your username! Unohdit antaa käyttäjänimesi! Your Xmpp Id should look like an email address XMpp-tunnuksesi pitäisi näyttää sähköpostiosoitteelta Example: username@jabber.org Esimerkiksi: käyttäjätunnus@jabber.org Tomahawk::Accounts::ZeroconfAccount Local Network Lähiverkko Tomahawk::Accounts::ZeroconfFactory Local Network Lähiverkko Automatically connect to Tomahawk users on the same local network. Yhdistä saman lähiverkon Tomahawk-käyttäjiin automaattisesti. Tomahawk::Collection Collection Kokoelma Tomahawk::ContextMenu &Play &Soita Add to &Queue &Lisää jonoon Add to &Playlist Lisää s&oittolistalle Send to &Friend L&ähetä kaverille Continue Playback after this &Track Jatka s&oittoa tämän kappaleen jälkeen Stop Playback after this &Track Pysäytä s&oitto tämän &kappaleen jälkeen &Love &Tykkää View Similar Tracks to "%1" Näytä samankaltaisia kappaleita kuin ”%1” &Go to "%1" Sii&rry sivulle ”%1” Go to "%1" Siirry sivulle ”%1” &Copy Track Link &Kopioi kappaleen linkki Mark as &Listened &Merkitse kuunnelluksi &Remove Items &Poista kohteet &Remove Item &Poista kohde Copy Album &Link &Kopioi albumin linkki Copy Artist &Link &Kopioi artistin linkki Un-&Love Lakkaa &tykkäämästä Properties... Ominaisuudet… Tomahawk::DatabaseCommand_AllAlbums Unknown Tuntematon Tomahawk::DropJobNotifier playlist soittolistaa artist artistia track kappaletta album albumia Fetching %1 from database Noudetaan %1 tietokannasta Parsing %1 %2 Jäsennetään %1 -%2 Tomahawk::DynamicControlList Save Settings Tallenna asetukset Tomahawk::DynamicModel Could not find a playable track. Please change the filters or try again. Soittokelpoista kappaletta ei löydy. Muuta suodattimia tai yritä uudelleen. Failed to generate preview with the desired filters Esikatselun luonti halutuilla suodattimilla epäonnistui Tomahawk::DynamicSetupWidget Type: Tyyppi: Generate Muodosta Tomahawk::DynamicView Add some filters above to seed this station! Alusta tämä asema lisäämällä hieman suodattimia! Press Generate to get started! Aloita painamalla Muodosta! Add some filters above, and press Generate to get started! Lisää yllä hieman suodattimia, ja aloita sitten painamalla Muodosta! Tomahawk::DynamicWidget Station ran out of tracks! Try tweaking the filters for a new set of songs to play. Asemalta loppui kappaleet! Koeta säätää suodattimia saadaksesi uuden joukon kappaleita kuunneltavaksi. Tomahawk::EchonestControl Similar To Samankaltainen kuin Limit To Rajoita Artist name Artistin nimi is on from user käyttäjältä No users with Echo Nest Catalogs enabled. Try enabling option in Collection settings Ei käyttäjiä, joilla on Echo Nestin hakemisto käytössä. Kokeile ottaa valinta käyttöön kokoelma-asetuksista similar to samankaltainen kuin Enter any combination of song name and artist here... Kirjoita mikä tahansa kappaleen nimen ja artistin yhdistelmä tähän… Less Vähemmän More Enemmän 0 BPM 0 BPM 500 BPM 500 BPM 0 secs 0 sekuntia 3600 secs 3600 sekuntia -100 dB −100 dB 100 dB 100 dB -180%1 −180%1 180%1 180%1 Major Duuri Minor Molli C C C Sharp Ylennetty C D D E Flat Alennettu E E E F F F Sharp Ylennetty F G G A Flat Alennettu A A A B Flat Alennettu H B H Ascending Nouseva Descending Laskeva Tempo Tempo Duration Kesto Loudness Äänekkyys Artist Familiarity Artistin tuttuus Artist Hotttnesss Artistin suosittuus Song Hotttnesss Kappaleen suosittuus Latitude Leveysaste Longitude Pituusaste Mode Moodi Key Sävellaji Energy Energia Danceability Tanssittavuus is not ei ole Studio Song type: The song was recorded in a studio. Studio Live Song type: The song was a life performance. Live Acoustic Song type Akustinen Electric Song type Elektroninen Christmas Song type: A christmas song Joululaulu At Least Vähintään At Most Enintään only by ~%1 vain likimäärin artistilta %1 similar to ~%1 likimäärin artistin %1 samankaltaisia artisteja with genre ~%1 likimäärin lajityypillä %1 from no one ei keneltäkään You Sinä from my radio omasta radiosta from %1 radio käyttäjän %1 radiosta Variety Vaihtelevuus Adventurousness Seikkailunhaluisuus very low erittäin pieni low pieni moderate kohtalainen high suuri very high erittäin suuri with %1 %2 , jonka %2 on %1 about %1 BPM noin %1 BPM about %n minute(s) long noin %n minuutti pitkänoin %n minuuttia pitkä about %1 dB noin %1 dB at around %1%2 %3 noin %1%2 %3elta in %1 sävellajissa %1 in a %1 key %1ssa sorted in %1 %2 order lajiteltu %1, lajitteluperusteena %2 with a %1 mood %1 mielialalla in a %1 style %1 tyylillä where genre is %1 lajityypillä %1 where song type is %1 jossa kappaleen tyyppi on %1 where song type is not %1 jossa kappaleen tyyppi ei ole %1 Tomahawk::GroovesharkParser Error fetching Grooveshark information from the network! Grooveshark-tietojen hakeminen verkosta epäonnistui! Tomahawk::InfoSystem::ChartsPlugin Artists Artistit Albums Albumit Tracks Kappaleet Tomahawk::InfoSystem::FdoNotifyPlugin on 'on' is followed by an album name albumilla %1%4 %2%3. %1 is a title, %2 is an artist and %3 is replaced by either the previous message or nothing, %4 is the preposition used to link track and artist ('by' in english) %1%4 %2%3. by preposition to link track and artist artistilta The current track could not be resolved. Tomahawk will pick back up with the next resolvable track from this source. Nykyisen kappaleen selvittäminen epäonnistui. Tomahawk valitsee samasta lähteestä seuraavan selvitettävissä olevan kappaleen. Tomahawk is stopped. Tomahawk on pysäytetty. %1 sent you %2%4 %3. %1 is a nickname, %2 is a title, %3 is an artist, %4 is the preposition used to link track and artist ('by' in english) %1 lähetti sinulle kappaleen %2%4 %3. %1 sent you "%2" by %3. %1 is a nickname, %2 is a title, %3 is an artist %1 lähetti sinulle kappaleen ”%2” artistilta %3. on "%1" %1 is an album name albumilla ”%1” "%1" by %2%3. %1 is a title, %2 is an artist and %3 is replaced by either the previous message or nothing ”%1” artistilta %2%3. Tomahawk - Now Playing Tomahawk – Nyt soi Tomahawk::InfoSystem::LastFmInfoPlugin Top Tracks Parhaat kappaleet Loved Tracks Tykätyt kappaleet Hyped Tracks Hypetetyt kappaleet Top Artists Parhaat artistit Hyped Artists Hypetetyt artistit Tomahawk::InfoSystem::NewReleasesPlugin Albums Albumit Tomahawk::InfoSystem::SnoreNotifyPlugin Notify User Ilmoitus käyttäjälle Now Playing Nyt soi Unresolved track Selvittämätön kappale Playback Stopped Soitto pysäytetty You received a Song recommendation Sait kappalesuosituksen on 'on' is followed by an album name albumilta %1%4 %2%3. %1 is a title, %2 is an artist and %3 is replaced by either the previous message or nothing, %4 is the preposition used to link track and artist ('by' in english) %1%4 %2%3. by preposition to link track and artist artistilta on "%1" %1 is an album name albumilta ”%1” "%1" by %2%3. %1 is a title, %2 is an artist and %3 is replaced by either the previous message or nothing ”%1” artistilta %2%3. %1 sent you %2%4 %3. %1 is a nickname, %2 is a title, %3 is an artist, %4 is the preposition used to link track and artist ('by' in english) %1 lähetti sinulle %2%4 %3. %1 sent you "%2" by %3. %1 is a nickname, %2 is a title, %3 is an artist %1 lähetti sinulle ”%2” artistilta %3. Tomahawk::ItunesParser Error fetching iTunes information from the network! iTunes-tietojen hakeminen verkosta epäonnistui! Tomahawk::JSPFLoader New Playlist Uusi soittolista Failed to save tracks Kappaleiden tallentaminen epäonnistui Some tracks in the playlist do not contain an artist and a title. They will be ignored. Joillakin soittolistan kappaleilla ei ole artistia ja nimeä. Ne jätetään huomiotta. XSPF Error XSPF-virhe This is not a valid XSPF playlist. Tämä ei ole kelvollinen XSPF-soittolista. Tomahawk::LatchManager &Catch Up &Ota kiinni &Listen Along Kuuntele &mukana Tomahawk::LocalCollection Your Collection Kokoelmasi Tomahawk::RemoteCollection Collection of %1 Käyttäjän %1 kokoelma Tomahawk::ScriptCollection %1 Collection Name of a collection based on a resolver, e.g. Subsonic Collection %1-kokoelma Tomahawk::ShortenedLinkParser Network error parsing shortened link! Verkkovirhe jäsennettäessä lyhennettyä linkkiä! Tomahawk::Source Scanning (%L1 tracks) Etsitään (%L1 kappaletta) Checking Tarkistetaan Syncing Synkronoidaan Importing Tuodaan Saving (%1%) Tallennetaan (%1 %) Online Verkossa Offline Ei verkossa Tomahawk::SpotifyParser Error fetching Spotify information from the network! Spotify-tietojen hakeminen verkosta epäonnistui! Tomahawk::Track and ja You Sinä you sinä and ja %n other(s) %n muu%n muuta %n people %n ihminen%n ihmistä loved this track tykkäsi tästä kappaleesta sent you this track %1 lähetti sinulle kappaleen %1 Tomahawk::Widgets::ChartsPage Charts Listat Tomahawk::Widgets::Dashboard Feed Syöte An overview of your friends' recent activity Yleiskatsaus kaveriesi viimeaikaisesta toiminnasta Tomahawk::Widgets::DashboardWidget Recently Played Tracks Viime aikoina kuunnellut kappaleet Feed Syöte Tomahawk::Widgets::NetworkActivity Trending Trendit What's hot amongst your friends Mikä on suosittua kaveriesi keskuudessa Tomahawk::Widgets::NetworkActivityWidget Charts Listat Last Week Viime viikko Loved Tracks Tykätyt kappaleet Top Loved Tykätyimmät Recently Loved Viime aikoina tykätyt Sorry, we are still loading the charts. Kaaviot ovat vielä latautumassa. Sorry, we couldn't find any trending tracks. Valitettavasti emme löytäneet yhtään suosittua kappaletta. Last Month Viime kuukausi Last Year Viime vuosi Overall Kaikkiaan Tomahawk::Widgets::NewReleasesPage New Releases Uudet julkaisut Tomahawk::Widgets::WhatsNew_0_8 What's new in 0.8? Mitä uutta 0.8:ssa? An overview of the changes and additions since 0.7. Yleiskatsaus muutoksista ja lisäyksistä version 0.7 jälkeen. Tomahawk::XspfUpdater Automatically update from XSPF Päivitä automaattisesti XSPF:stä TomahawkApp You Sinä Tomahawk is updating the database. Please wait, this may take a minute! Tomahawk päivittää tietokantaa. Odota hetki, tässä voi kestää minuutti! Tomahawk Tomahawk Updating database Päivitetään tietokantaa Updating database %1 Päivitetään tietokantaa %1 Automatically detecting external IP failed. Ulkoisen IP-osoitteen automaattinen tunnistus epäonnistui. TomahawkSettings Local Network Lähiverkko TomahawkTrayIcon &Stop Playback after current Track Pysäytä s&oitto nykyisen kappaleen jälkeen Hide Tomahawk Window Piilota Tomahawk-ikkuna Show Tomahawk Window Näytä Tomahawk-ikkuna Currently not playing. Mikään ei soi. Play Soita Pause Tauko &Love &Tykkää Un-&Love Lakkaa &tykkäämästä &Continue Playback after current Track Jatka s&oittoa nykyisen kappaleen jälkeen TomahawkUtils Configure Accounts Tilien asetukset Invite Kutsu TomahawkWindow Tomahawk Tomahawk Back Takaisin Go back one page Mene yksi sivu takaisin Forward Eteenpäin Go forward one page Mene yksi sivu eteenpäin Hide Menu Bar Piilota valikkorivi Show Menu Bar Näytä valikkorivi &Main Menu &Päävalikko Play Soita Next Seuraava Love Tykkää Unlove Lakkaa tykkäämästä Exit Full Screen Poistu koko näytöstä Enter Full Screen Siirry koko näyttöön This is not a valid XSPF playlist. Tämä ei ole kelvollinen XSPF-soittolista. Some tracks in the playlist do not contain an artist and a title. They will be ignored. Joillakin soittolistan kappaleilla ei ole artistia ja nimeä. Ne jätetään huomiotta. Failed to load JSPF playlist JSPF-soittolistan lataaminen epäonnistui Sorry, there is a problem accessing your audio device or the desired track, current track will be skipped. Make sure you have a suitable Phonon backend and required plugins installed. Valitettavasti äänilaitteen tai halutun kappaleen kanssa on ongelmia ja nykyinen kappale ohitetaan. Varmista, että sopiva Phononin taustaosa ja vaaditut liitännäiset on asennettu. Sorry, there is a problem accessing your audio device or the desired track, current track will be skipped. Valitettavasti äänilaitteen tai halutun kappaleen kanssa on ongelmia ja nykyinen kappale ohitetaan. Station Asema Create New Station Luo uusi asema Name: Nimi: Playlist Soittolista Create New Playlist Luo uusi soittolista Pause Tauko Search Etsi &Play &Soita %1 by %2 track, artist name %1 artistilta %2 %1 - %2 current track, some window title %1 – %2 <h2><b>Tomahawk %1<br/>(%2)</h2> <h2><b>Tomahawk %1<br/>(%2)</h2> <h2><b>Tomahawk %1</h2> <h2><b>Tomahawk %1</h2> Copyright 2010 - 2015 Copyright 2010–2015 Thanks to: Kiitokset: About Tomahawk Tietoa Tomahawkista TrackDetailView Marked as Favorite Merkitty suosikiksi Alternate Sources: Vaihtoehtoiset lähteet: Unknown Release-Date Tuntematon julkaisupäivä on %1 albumilla %1 TrackInfoWidget Similar Tracks Samankaltaisia kappaleita Sorry, but we could not find similar tracks for this song! Valitettavasti emme löytäneet tälle kappaleelle samankaltaisia kappaleita! Top Hits Parhaat hitit TrackView Sorry, your filter '%1' did not match any results. Valitettavasti suodattimesi ”%1” ei tuottanut yhtään tuloksia. TransferStatusItem from streaming artist - track from friend kaverilta to streaming artist - track to friend kaverille Type selector Artist Artisti Artist Description Artistin kuvaus User Radio Käyttäjäradio Song Kappale Genre Lajityyppi Mood Mieliala Style Tyyli Adventurousness Seikkailunhaluisuus Variety Vaihtelevuus Tempo Tempo Duration Kesto Loudness Äänekkyys Danceability Tanssittavuus Energy Energia Artist Familiarity Artistin tuttuus Artist Hotttnesss Artistin suosittuus Song Hotttnesss Kappaleen suosittuus Longitude Pituusaste Latitude Leveysaste Mode Moodi Key Sävellaji Sorting Lajittelu Song Type Kappaleen tyyppi ViewManager Inbox Saapuneet Listening suggestions from your friends Kaveriesi lähettämät kuunteluehdotukset WhatsNewWidget_0_8 WHAT'S NEW MITÄ UUTTA If you are reading this it likely means you have already noticed the biggest change since our last release - a shiny new interface and design. New views, fonts, icons, header images and animations at every turn. Below you can find an overview of the functional changes and additions since 0.7 too: Kun kerran luet tätä, olet jo varmaan huomannut suurimman muutoksen viime julkaisuun verrattuna – uudenkiiltävän käyttöliittymän ja designin. Uusia näkymiä, fontteja, kuvakkeita, otsikkokuvia ja animaatioita on kaikkialla. Alla on yleiskatsaus toiminnallisista muutoksista ja lisäyksistä sitten 0.7-version: Inbox Saapuneet <html><head/><body><p>Send tracks to your friends just by dragging it onto their avatar in the Tomahawk sidebar. Check out what your friends think you should listen to in your inbox.</p></body></html> <html><head/><body><p>Lähetä kappaleita kavereillesi vetämällä niitä heidän avatareihinsa Tomahawk-sivupalkissa. Katso myös saapuneista, mitä sinun pitäisi kavereiden mielestä kuunnella.</p></body></html> Universal Link Support Yleisten linkkien tuki <html><head/><body><p>Love that your friends and influencers are posting music links but hate that they are links for music services you don't use? Just drag Rdio, Deezer, Beats or other music service URLs into your Tomahawk queue or playlists and have them automatically play from your preferred source.</p></body></html> <html><head/><body><p>Pidätkö siitä, että kaverisi ja vaikuttajat julkaisevat musiikkilinkkejä, mutta et pidä siitä, että ne ovat musiikkipalveluihin, joita et käytä? Vedä Rdio-, Deezer-, Beats- tai muun palvelun osoitteita Tomahawk-jonoosi tai soittolistaasi, ja ne soitetaan suosimastasi lähteestä.</p></body></html> Beats Music Beats Music <html><head/><body><p>Beats Music (recently aquired by Apple) is now supported. This means that Beats Music subscribers can also benefit by resolving other music service links so they stream from their Beats Music account. Welcome aboard!</p></body></html> <html><head/><body><p>Beats Music (jonka Apple osti hiljattain) on nyt tuettu.Tämä tarkoittaa, että Beats Musicin tilaajat hyötyvät muiden musiikkipalveluiden linkkien selvittämisestä siten, että niitä suoratoistetaan Beast Music -tililtä. Tervetuloa mukaan!</p></body></html> Google Music Google Music <html><head/><body><p>Google Music is another of our latest supported services - both for the music you've uploaded to Google Music as well as their full streaming catalog for Google Play Music All Access subscribers. Not only is all of your Google Play Music a potential source for streaming - your entire online collection is browseable too.</p></body></html> <html><head/><body><p>Google Music on toinen uusimmista tukemistamme palveluista – sekä Google Musiciin laittamasi musiikki että Google Play Music All Access -tilaajien koko suoratoistovalikoima on tuettu. Google Play Music ei ole pelkästään mahdollinen suoratoiston lähde – voit myös selata koko verkkokokoelmaasi.</p></body></html> <html><head/><body><p>Tomahawk for Android is now in beta! The majority of the same resolvers are supported in the Android app - plus a couple of additional ones in Rdio &amp; Deezer. <a href="http://hatchet.is/register">Create a Hatchet account</a> to sync all of your playlists from your desktop to your mobile. Find current and future music influencers and follow them to discover and hear what they love. Just like on the desktop, Tomahawk on Android can open other music service links and play them from yours. Even when you are listening to other music apps, Tomahawk can capture all of that playback data and add it to your Hatchet profile.</p></body></html> <html><head/><body><p>Tomahawk Androidille on nyt beetavaiheessa! Android-sovellus tukee suurinta osaa samoista selvittimistä – sekä muutamia muita kuten Rdio &amp; Deezer. <a href="http://hatchet.is/register">Luo Hatchet-tili</a> ja synkronoi kaikki soittolistasi työpöydältä puhelimeesi. Löydä nykyisyyden ja tulevaisuuden musiikkivaikuttajia ja seuraa heitä löytääksesi ja kuunnellaksesi sitä, mistä he pitävät. Aivan niin kuin työpöydällä, myös Androidilla Tomahawk osaa avata muiden musiikkipalveluiden linkkejä ja soittaa ne omistasi. Tomahawk osaa ottaa soittotiedot talteen ja lisätä ne Hatchet-profiiliisi silloinkin, kun kuuntelet musiikkia muilla sovelluksilla.</p></body></html> Connectivity Yhdistettävyys <html><head/><body><p>Tomahawk now supports IPv6 and multiple local IP addresses. This improves the discoverability and connection between Tomahawk users - particularly large local networks often found in work and university settings. The more friends, the more music, the more playlists and the more curation from those people whose musical tastes you value. Sit back and just Listen Along!</p></body></html> <html><head/><body><p>Tomahawk tukee nyt IPv6:ta ja useita paikallisia IP-osoitteita. Tämä parantaa Tomahawk-käyttäjien välistä löydettävyyttä ja yhteydenpitoa – erityisesti suurissa lähiverkoissa kuten työpaikoilla ja yliopistoilla. Mitä enemmän kavereita, sitä enemmän musiikkia, soittolistoja ja kuratointia ihmisiltä, joiden musiikkimakua arvostat. Ota mukava asento ja kuuntele mukana!</p></body></html> Android Android XMPPBot Terms for %1: Artistin %1 nimet: No terms found, sorry. Nimiä ei löydy. Hotttness for %1: %2 Artistin %1 suosittuus: %2 Familiarity for %1: %2 Artistin %1 tuttuus: %2 Lyrics for "%1" by %2: %3 Sanat artistin %2 kappaleelle ”%1”: %3 XSPFLoader Failed to parse contents of XSPF playlist XSPF-soittolistan sisällön jäsentäminen epäonnistui Some playlist entries were found without artist and track name, they will be omitted Joillakin soittolistan kappaleilla ei ole artistia ja nimeä. Ne jätetään huomiotta Failed to fetch the desired playlist from the network, or the desired file does not exist Halutun soittolistan hakeminen verkosta epäonnistui tai haluttua tiedostoa ei ole olemassa New Playlist Uusi soittolista XmlConsole Xml stream console XML-virtakonsoli Filter Suodata Save log Tallenna loki Disabled Poissa käytöstä By JID JID:n perusteella By namespace uri Nimiavaruuden URI:n perusteella By all attributes Kaikkien attribuuttien perusteella Visible stanzas Näkyvät säkeistöt Information query Informaatiokysely Message Viesti Presence Läsnäolo Custom Mukautettu Close Sulje Save XMPP log to file Tallenna XMPP-loki tiedostoon OpenDocument Format (*.odf);;HTML file (*.html);;Plain text (*.txt) OpenDocument Format (*.odf);;HTML-tiedosto (*.html);;Muotoilematon teksti (*.txt) XmppConfigWidget Xmpp Configuration XMPP-asetukset Configure Asetukset Login Information Kirjautumistiedot Configure this Jabber/XMPP account Tämän Jabber/XMPP-tilin asetukset Enter your XMPP login to connect with your friends using Tomahawk! Ole yhteydessä Tomahawkia käyttäviin kavereihisi antamalla XMPP-kirjautumistietosi! XMPP ID: XMPP-tunnus: e.g. user@jabber.org esim. joku@jabber.org Password: Salasana: An account with this name already exists! Tämänniminen tili on jo olemassa! Advanced Xmpp Settings XMPP-lisäasetukset Server: Palvelin: Port: Portti: Lots of servers don't support this (e.g. GTalk, jabber.org) Useat palvelimet eivät tue tätä (esim. GTalk ja jabber.org) Display currently playing track Näytä parhaillaan soiva kappale Enforce secure connection Pakota salattu yhteys XmppSipPlugin User Interaction Käyttäjän toiminta Host is unknown Kone on tuntematon Item not found Kohdetta ei löydy Authorization Error Valtuutusvirhe Remote Stream Error Etävirran virhe Remote Connection failed Etäyhteys epäonnistui Internal Server Error Sisäinen palvelinvirhe System shutdown Järjestelmän sammutus Conflict Ristiriita Unknown Tuntematon Do you want to add <b>%1</b> to your friend list? Haluatko lisätä käyttäjän <b>%1</b> kaverilistallesi? No Compression Support Ei pakkaustukea Enter Jabber ID Anna Jabber-tunnus No Encryption Support Ei salaustukea No Authorization Support Ei valtuutustukea No Supported Feature Ei-tuettu ominaisuus Add Friend Lisää kaveri Enter Xmpp ID: Anna XMPP-tunnus: Add Friend... Lisää kaveri… XML Console... XML-konsoli… I'm sorry -- I'm just an automatic presence used by Tomahawk Player (http://gettomahawk.com). If you are getting this message, the person you are trying to reach is probably not signed on, so please try again later! Pahoittelen – olen pelkkä Tomahawk-soittimen (http://gettomahawk.com) automaattinen läsnäoloviesti. Jos näet tämän viestin, tavoittelemasi henkilö ei todennäköisesti ole kirjautuneena, joten yritä myöhemmin uudelleen! Authorize User Salli käyttäjä ZeroconfConfig Local Network configuration Lähiverkon asetukset This plugin will automatically find other users running Tomahawk on your local network Tämä liitännäinen löytää automaattisesti muut lähiverkossa olevat Tomahawk-käyttäjät Connect automatically when Tomahawk starts Yhdistä automaattisesti Tomahawkin käynnistymisen yhteydessä tomahawk-player/data/000775 001750 001750 00000000000 12661705042 015703 5ustar00stefanstefan000000 000000 tomahawk-player/src/libtomahawk/database/fuzzyindex/DatabaseFuzzyIndex.cpp000664 001750 001750 00000003014 12661705042 030314 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2014, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "DatabaseFuzzyIndex.h" #include "database/DatabaseImpl.h" #include "database/Database.h" #include "utils/TomahawkUtils.h" #include namespace Tomahawk { static QString s_indexPathName = "tomahawk.lucene"; DatabaseFuzzyIndex::DatabaseFuzzyIndex( QObject* parent, bool wipe ) : FuzzyIndex( parent, s_indexPathName, wipe ) { } void DatabaseFuzzyIndex::updateIndex() { Tomahawk::DatabaseCommand* cmd = new Tomahawk::DatabaseCommand_UpdateSearchIndex(); Tomahawk::Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) ); } void DatabaseFuzzyIndex::wipeIndex() { TomahawkUtils::removeDirectory( TomahawkUtils::appDataDir().absoluteFilePath( s_indexPathName ) ); } } // namespace Tomahawk tomahawk-player/CMakeModules/FindLibAttica.cmake000664 001750 001750 00000004573 12661705042 022753 0ustar00stefanstefan000000 000000 # Try to find the Attica library # Once done this will define # # LIBATTICA_FOUND Indicates that Attica was found # LIBATTICA_LIBRARIES Libraries needed to use Attica # LIBATTICA_LIBRARY_DIRS Paths needed for linking against Attica # LIBATTICA_INCLUDE_DIR Path needed for finding Attica include files # # The minimum required version of LibAttica can be specified using the # standard syntax, e.g. find_package(LibAttica 0.20) # Copyright (c) 2009 Frederik Gladhorn # # Redistribution and use is allowed according to the terms of the BSD license. # Support LIBATTICA_MIN_VERSION for compatibility: IF(NOT LibAttica_FIND_VERSION) SET(LibAttica_FIND_VERSION "${LIBATTICA_MIN_VERSION}") ENDIF(NOT LibAttica_FIND_VERSION) # the minimum version of LibAttica we require IF(NOT LibAttica_FIND_VERSION) SET(LibAttica_FIND_VERSION "0.1.0") ENDIF(NOT LibAttica_FIND_VERSION) IF (NOT WIN32) # use pkg-config to get the directories and then use these values # in the FIND_PATH() and FIND_LIBRARY() calls FIND_PACKAGE(PkgConfig) PKG_CHECK_MODULES(PC_LIBATTICA QUIET libattica) SET(LIBATTICA_DEFINITIONS ${PC_ATTICA_CFLAGS_OTHER}) ENDIF (NOT WIN32) FIND_PATH(LIBATTICA_INCLUDE_DIR attica/provider.h HINTS ${PC_LIBATTICA_INCLUDEDIR} ${PC_LIBATTICA_INCLUDE_DIRS} PATH_SUFFIXES attica ) # Store the version number in the cache, so we don't have to search everytime: IF(LIBATTICA_INCLUDE_DIR AND NOT LIBATTICA_VERSION) FILE(READ ${LIBATTICA_INCLUDE_DIR}/attica/version.h LIBATTICA_VERSION_CONTENT) STRING (REGEX MATCH "LIBATTICA_VERSION_STRING \".*\"\n" LIBATTICA_VERSION_MATCH "${LIBATTICA_VERSION_CONTENT}") IF(LIBATTICA_VERSION_MATCH) STRING(REGEX REPLACE "LIBATTICA_VERSION_STRING \"(.*)\"\n" "\\1" _LIBATTICA_VERSION ${LIBATTICA_VERSION_MATCH}) ENDIF(LIBATTICA_VERSION_MATCH) SET(LIBATTICA_VERSION "${_LIBATTICA_VERSION}" CACHE STRING "Version number of LibAttica" FORCE) ENDIF(LIBATTICA_INCLUDE_DIR AND NOT LIBATTICA_VERSION) FIND_LIBRARY(LIBATTICA_LIBRARIES NAMES attica libattica HINTS ${PC_LIBATTICA_LIBDIR} ${PC_LIBATTICA_LIBRARY_DIRS} ) INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibAttica REQUIRED_VARS LIBATTICA_LIBRARIES LIBATTICA_INCLUDE_DIR VERSION_VAR LIBATTICA_VERSION) MARK_AS_ADVANCED(LIBATTICA_INCLUDE_DIR LIBATTICA_LIBRARIES) tomahawk-player/src/libtomahawk/EchonestCatalogSynchronizer.h000664 001750 001750 00000004744 12661705042 025726 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef ECHONESTCATALOGSYNCHRONIZER_H #define ECHONESTCATALOGSYNCHRONIZER_H #include "DllMacro.h" #include "Query.h" #include #include #include namespace Tomahawk { class DLLEXPORT EchonestCatalogSynchronizer : public QObject { Q_OBJECT public: static EchonestCatalogSynchronizer* instance() { if ( !s_instance ) { s_instance = new EchonestCatalogSynchronizer; } return s_instance; } explicit EchonestCatalogSynchronizer(QObject *parent = 0); Echonest::Catalog songCatalog() const { return m_songCatalog; } Echonest::Catalog artistCatalog() const { return m_artistCatalog; } signals: void knownCatalogsChanged(); private slots: void checkSettingsChanged(); void tracksAdded( const QList& ); void tracksRemoved( const QList& ); void loadedResults( const QList& results ); // Echonest slots void songCreateFinished(); void artistCreateFinished(); void songUpdateFinished(); void catalogDeleted(); void checkTicket(); void rawTracksAdd( const QList< QStringList >& tracks ); private: void uploadDb(); QByteArray escape( const QString& in ) const; Echonest::CatalogUpdateEntry entryFromTrack( const QStringList&, Echonest::CatalogTypes::Action action ) const; void doUploadJob(); bool m_syncing; Echonest::Catalog m_songCatalog; Echonest::Catalog m_artistCatalog; QQueue< Echonest::CatalogUpdateEntries > m_queuedUpdates; static EchonestCatalogSynchronizer* s_instance; friend class DatabaseCommand_SetCollectionAttributes; }; } #endif // ECHONESTCATALOGSYNCHRONIZER_H tomahawk-player/src/libtomahawk/thirdparty/Qocoa/qtoolbartabdialog_mac.mm000664 001750 001750 00000034206 12661705042 030171 0ustar00stefanstefan000000 000000 /* Copyright (C) 2012 by Leo Franchi 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 "qtoolbartabdialog.h" #include "qocoa_mac.h" #import #include #include #include #include #include #include struct ItemData { QPixmap icon; QString text, tooltip; QMacNativeWidget* nativeWidget; QWidget* page; }; namespace { static const int TOOLBAR_ITEM_WIDTH = 32; CGFloat ToolbarHeightForWindow(NSWindow *window) { CGFloat toolbarHeight = 0.0f; NSToolbar *toolbar = toolbar = [window toolbar]; if(toolbar && [toolbar isVisible]) { NSRect windowFrame = [NSWindow contentRectForFrameRect:[window frame] styleMask:[window styleMask]]; toolbarHeight = NSHeight(windowFrame) - NSHeight([[window contentView] frame]); } return toolbarHeight; } }; @interface ToolbarDelegate : NSObject { QToolbarTabDialogPrivate *pimpl; } // Internal -(void)setPrivate:(QToolbarTabDialogPrivate*)withPimpl; // NSToolbarItem action -(void)changePanes:(id)sender; // NSToolbarDelegate -(NSToolbarItem *) toolbar: (NSToolbar *)toolbar itemForItemIdentifier: (NSString *) itemIdent willBeInsertedIntoToolbar:(BOOL) willBeInserted; -(NSArray*) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar; -(NSArray*) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar; -(NSArray*) toolbarSelectableItemIdentifiers: (NSToolbar*)toolbar; // NSWindowDelegate -(NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize; -(void)windowWillClose:(NSNotification *)notification; @end class QToolbarTabDialogPrivate { public: QToolbarTabDialogPrivate(QToolbarTabDialog* dialog) : q(dialog), currentPane(NULL), minimumWidth(0) { } ~QToolbarTabDialogPrivate() { // unset the delegate and toolbar from the window and manually release them // otherwise, for some reason the old delegate is laying around when we // create a new NSWindow [[prefsWindow toolbar] setDelegate:NULL]; [prefsWindow setToolbar:NULL]; [prefsWindow release]; [toolBar release]; [toolBarDelegate release]; } void calculateSize() { NSRect windowFrame = [prefsWindow frame]; while ([[toolBar visibleItems] count] < [[toolBar items] count]) { //Each toolbar item is 32x32; we expand by one toolbar item width repeatedly until they all fit windowFrame.origin.x -= TOOLBAR_ITEM_WIDTH / 2; windowFrame.size.width += TOOLBAR_ITEM_WIDTH / 2; [prefsWindow setFrame:windowFrame display:NO]; [prefsWindow setMinSize: windowFrame.size]; } minimumWidth = windowFrame.size.width; } void showPaneWithIdentifier(NSString* ident) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; const QString identStr = toQString(ident); Q_ASSERT(items.contains(identStr)); if (!items.contains(identStr)) return; QWidget* newWidget = items[identStr].nativeWidget; Q_ASSERT(newWidget); if (!newWidget) return; QWidget* newPage = items[identStr].page; Q_ASSERT(newPage); if (!newPage) return; const NSRect oldFrame = [[prefsWindow contentView] frame]; // Clear first responder on window and set a temporary NSView on the window // while we change the widget out underneath [prefsWindow makeFirstResponder:nil]; NSView *tempView = [[NSView alloc] initWithFrame:[[prefsWindow contentView] frame]]; [prefsWindow setContentView:tempView]; [tempView release]; QSize sizeToUse = newPage->sizeHint().isNull() ? newPage->size() : newPage->sizeHint(); sizeToUse.setWidth(qMax(sizeToUse.width(), newPage->minimumWidth())); sizeToUse.setHeight(qMax(sizeToUse.height(), newPage->minimumHeight())); static const int spacing = 4; [prefsWindow setMinSize:NSMakeSize(sizeToUse.width(), sizeToUse.height())]; // Make room for the new view NSRect newFrame = [prefsWindow frame]; newFrame.size.height = sizeToUse.height() + ([prefsWindow frame].size.height - [[prefsWindow contentView] frame].size.height) + spacing; newFrame.size.width = sizeToUse.width() + spacing; // Don't resize the width---only the height, so use the maximum width for any page // or the same width as before, if the user already resized it to be larger. newFrame.size.width < minimumWidth ? newFrame.size.width = minimumWidth : newFrame.size.width = qMax(newFrame.size.width, oldFrame.size.width); // Preserve upper left point of window during resize. newFrame.origin.y += ([[prefsWindow contentView] frame].size.height - sizeToUse.height()) - spacing; [prefsWindow setFrame:newFrame display:YES animate:YES]; [prefsWindow setContentView: [panes objectForKey:ident]]; currentPane = ident; // Resize the Qt widget immediately as well resizeCurrentPageToSize([[prefsWindow contentView] frame].size); NSSize minSize = [prefsWindow frame].size; minSize.height -= ToolbarHeightForWindow(prefsWindow); [prefsWindow setMinSize:minSize]; BOOL canResize = YES; NSSize maxSize = NSMakeSize(FLT_MAX, FLT_MAX); if (newPage->sizePolicy().horizontalPolicy() == QSizePolicy::Fixed) { canResize = NO; maxSize.width = sizeToUse.width(); } if (newPage->sizePolicy().verticalPolicy() == QSizePolicy::Fixed) { canResize = NO; maxSize.height = sizeToUse.height(); } [prefsWindow setMaxSize:maxSize]; [prefsWindow setShowsResizeIndicator:canResize]; [prefsWindow setTitle:ident]; [prefsWindow makeFirstResponder:[panes objectForKey:ident]]; [pool drain]; } void resizeCurrentPageToSize(NSSize frameSize) { const QString curPane = toQString(currentPane); if (items.contains(curPane) && items[curPane].nativeWidget) { items[curPane].nativeWidget->resize(frameSize.width, frameSize.height); } } void emitAccepted() { if (q.isNull()) return; q.data()->accepted(); } QWeakPointer q; NSWindow* prefsWindow; ToolbarDelegate *toolBarDelegate; QMap items; NSMutableDictionary *panes; NSToolbar *toolBar; NSString* currentPane; int minimumWidth; }; @implementation ToolbarDelegate -(id) init { if( self = [super init] ) { pimpl = nil; } return self; } -(void) setPrivate:(QToolbarTabDialogPrivate *)withPimpl { pimpl = withPimpl; } -(void)changePanes:(id)sender { Q_UNUSED(sender); if (!pimpl) return; pimpl->showPaneWithIdentifier([pimpl->toolBar selectedItemIdentifier]); } -(NSToolbarItem *) toolbar: (NSToolbar *)toolbar itemForItemIdentifier: (NSString *) itemIdent willBeInsertedIntoToolbar:(BOOL) willBeInserted { Q_UNUSED(toolbar); Q_UNUSED(willBeInserted); if (!pimpl) return nil; NSToolbarItem *toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdent] autorelease]; const QString identQStr = toQString(itemIdent); if (pimpl->items.contains(identQStr)) { const ItemData& data = pimpl->items[identQStr]; NSString* label = fromQString(data.text); [toolbarItem setLabel:label]; [toolbarItem setPaletteLabel:label]; [toolbarItem setToolTip:fromQString(data.tooltip)]; [toolbarItem setImage:fromQPixmap(data.icon)]; [toolbarItem setTarget: self]; [toolbarItem setAction: @selector(changePanes:)]; } else { toolbarItem = nil; } return toolbarItem; } -(NSArray*) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar { Q_UNUSED(toolbar); if (!pimpl) return [NSArray array]; NSMutableArray* allowedItems = [[[NSMutableArray alloc] init] autorelease]; Q_FOREACH( const QString& identQStr, pimpl->items.keys()) [allowedItems addObject:fromQString(identQStr)]; [allowedItems addObjectsFromArray:[NSArray arrayWithObjects:NSToolbarSeparatorItemIdentifier, NSToolbarSpaceItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier, NSToolbarCustomizeToolbarItemIdentifier, nil] ]; return allowedItems; } -(NSArray*) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar { Q_UNUSED(toolbar); if (!pimpl) return [NSArray array]; return [[[NSMutableArray alloc] initWithArray:[pimpl->panes allKeys]] autorelease]; } -(NSArray*) toolbarSelectableItemIdentifiers: (NSToolbar*)toolbar { Q_UNUSED(toolbar); if (!pimpl) return [NSArray array]; return [[[NSMutableArray alloc] initWithArray:[pimpl->panes allKeys]] autorelease]; } -(NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize { if (!pimpl) return frameSize; pimpl->resizeCurrentPageToSize([[sender contentView] frame].size); return frameSize; } -(void)windowWillClose:(NSNotification *)notification { Q_UNUSED(notification); pimpl->emitAccepted(); } @end QToolbarTabDialog::QToolbarTabDialog() : QObject(0), pimpl(new QToolbarTabDialogPrivate(this)) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; pimpl->panes = [[NSMutableDictionary alloc] init]; static const int defaultWidth = 350; static const int defaultHeight = 200; pimpl->prefsWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, defaultWidth, defaultHeight) styleMask:NSClosableWindowMask | NSResizableWindowMask | NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO]; [pimpl->prefsWindow setReleasedWhenClosed:NO]; [pimpl->prefsWindow setTitle:@"Preferences"]; // identifier is some app-unique string, since all toolbars in an app share state. make this unique to this app's preferences window pimpl->toolBar = [[NSToolbar alloc] initWithIdentifier:[NSString stringWithFormat:@"%@.prefspanel.toolbar", fromQString(QCoreApplication::instance()->applicationName())]]; [pimpl->toolBar setAllowsUserCustomization: NO]; [pimpl->toolBar setAutosavesConfiguration: NO]; [pimpl->toolBar setDisplayMode: NSToolbarDisplayModeIconAndLabel]; pimpl->toolBarDelegate = [[ToolbarDelegate alloc] init]; [pimpl->toolBarDelegate setPrivate:pimpl.data()]; [pimpl->prefsWindow setDelegate:pimpl->toolBarDelegate]; [pimpl->toolBar setDelegate:pimpl->toolBarDelegate]; [pimpl->prefsWindow setToolbar:pimpl->toolBar]; [pool drain]; } QToolbarTabDialog::~QToolbarTabDialog() { } void QToolbarTabDialog::addTab(QWidget* page, const QPixmap& icon, const QString& label, const QString& tooltip) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSString* identifier = fromQString(label); QMacNativeWidget* nativeWidget = new QMacNativeWidget; nativeWidget->move(0, 0); nativeWidget->setPalette(page->palette()); nativeWidget->setAutoFillBackground(true); QVBoxLayout* l = new QVBoxLayout; l->setContentsMargins(2, 2, 2, 2); l->setSpacing(0); page->setAttribute(Qt::WA_LayoutUsesWidgetRect); l->addWidget(page); nativeWidget->setLayout(l); NSView *nativeView = reinterpret_cast(nativeWidget->winId()); [nativeView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; [nativeView setAutoresizesSubviews:YES]; pimpl->minimumWidth = qMax(pimpl->minimumWidth, page->sizeHint().width()); nativeWidget->show(); ItemData data; data.icon = icon; data.text = label; data.tooltip = tooltip; data.nativeWidget = nativeWidget; data.page = page; pimpl->items.insert(label, data); [pimpl->panes setObject:nativeView forKey:identifier]; pimpl->showPaneWithIdentifier(identifier); [pimpl->toolBar insertItemWithItemIdentifier:identifier atIndex:[[pimpl->toolBar items] count]]; [pimpl->toolBar setSelectedItemIdentifier:identifier]; [[pimpl->prefsWindow standardWindowButton:NSWindowZoomButton] setEnabled:NO]; pimpl->calculateSize(); [pool drain]; } void QToolbarTabDialog::setCurrentIndex(int index) { Q_ASSERT(pimpl); if (!pimpl) return; [pimpl->toolBar setSelectedItemIdentifier:[[[pimpl->toolBar items] objectAtIndex:index] itemIdentifier]]; pimpl->showPaneWithIdentifier([[[pimpl->toolBar items] objectAtIndex:index] itemIdentifier]); } void QToolbarTabDialog::show() { Q_ASSERT(pimpl); if (!pimpl) return; [pimpl->prefsWindow center]; [pimpl->prefsWindow makeKeyAndOrderFront:nil]; } void QToolbarTabDialog::hide() { Q_ASSERT(pimpl); if (!pimpl) return; [pimpl->prefsWindow close]; emit accepted(); } #include "moc_qtoolbartabdialog.cpp" tomahawk-player/src/libtomahawk/database/DatabaseCommand_DirMtimes.cpp000664 001750 001750 00000005642 12661705042 027322 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2011, Jeff Mitchell * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "DatabaseCommand_DirMtimes.h" #include #include "DatabaseImpl.h" #include "utils/Logger.h" #include "Source.h" namespace Tomahawk { void DatabaseCommand_DirMtimes::exec( DatabaseImpl* dbi ) { if( m_update ) execUpdate( dbi ); else execSelect( dbi ); } void DatabaseCommand_DirMtimes::execSelect( DatabaseImpl* dbi ) { QMap mtimes; TomahawkSqlQuery query = dbi->newquery(); if( m_prefix.isEmpty() && m_prefixes.isEmpty() ) { query.exec( "SELECT name, mtime FROM dirs_scanned" ); while( query.next() ) mtimes.insert( query.value( 0 ).toString(), query.value( 1 ).toUInt() ); } else if( m_prefixes.isEmpty() ) execSelectPath( dbi, m_prefix, mtimes ); else { if( !m_prefix.isEmpty() ) execSelectPath( dbi, m_prefix, mtimes ); foreach( QString path, m_prefixes ) execSelectPath( dbi, path, mtimes ); } emit done( mtimes ); } void DatabaseCommand_DirMtimes::execSelectPath( DatabaseImpl *dbi, const QDir& path, QMap &mtimes ) { TomahawkSqlQuery query = dbi->newquery(); query.prepare( QString( "SELECT name, mtime " "FROM dirs_scanned " "WHERE name LIKE :prefix" ) ); query.bindValue( ":prefix", path.canonicalPath() + "%" ); query.exec(); while( query.next() ) mtimes.insert( query.value( 0 ).toString(), query.value( 1 ).toUInt() ); } void DatabaseCommand_DirMtimes::execUpdate( DatabaseImpl* dbi ) { qDebug() << "Saving mtimes..."; TomahawkSqlQuery query = dbi->newquery(); query.exec( "DELETE FROM dirs_scanned" ); query.prepare( "INSERT INTO dirs_scanned(name, mtime) VALUES(?, ?)" ); foreach( const QString& k, m_tosave.keys() ) { query.bindValue( 0, k ); query.bindValue( 1, m_tosave.value( k ) ); query.exec(); } qDebug() << "Saved mtimes for" << m_tosave.size() << "dirs."; emit done( QMap< QString, unsigned int >() ); } } tomahawk-player/src/libtomahawk/FuncTimeout.cpp000664 001750 001750 00000002366 12661705042 023040 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "FuncTimeout.h" #include using namespace Tomahawk; FuncTimeout::FuncTimeout( int ms, boost::function< void() > func, QObject* besafe ) : m_func( func ) , m_watch( QPointer< QObject >( besafe ) ) { //qDebug() << Q_FUNC_INFO; QTimer::singleShot( ms, this, SLOT( exec() ) ); } FuncTimeout::~FuncTimeout() { } void FuncTimeout::exec() { if( !m_watch.isNull() ) m_func(); this->deleteLater(); } tomahawk-player/src/libtomahawk/ActionCollection.h000664 001750 001750 00000006624 12661705042 023475 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser > * Copyright 2012, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef TOMAHAWKACTIONCOLLECTION_H #define TOMAHAWKACTIONCOLLECTION_H #include "DllMacro.h" #include #include class DLLEXPORT ActionCollection : public QObject { Q_OBJECT public: // Categories for custom-registered actions enum ActionDestination { // Tracks, TODO LocalPlaylists = 0 }; static ActionCollection* instance(); ActionCollection( QObject* parent ); ~ActionCollection(); void initActions(); /** * This method returns a main menu bar, suitable for Windows, Mac and X11. */ QMenuBar* createMenuBar( QWidget* parent ); /** * Returns a QMenu with all the entries that would normally be in the main menu, * arranged in a sensible way. The compressed menu makes absolutely no sense on Mac, * and fairly little sense on Unity and other X11 desktop configurations which pull * out the menu bar from the window. */ QMenu* createCompactMenu( QWidget* parent ); QAction* getAction( const QString& name ); QList< QAction* > getAction( ActionDestination category ); QObject* actionNotifier( QAction* ); /** * Add an action for a specific category. The action will show up * where the relevant category is displayed. * * e.g. if you register a Playlist action, it will be shown when * there is a context menu shown for a playlist. * * When the QAction* is shown, it will have a "payload" property that is set * to the that is being shown. * * Additionally you can pass a QObject* that will be notified before the given * action is shown. The slot "aboutToShow( QAction*, ) will be called, * * * corresponds to the category: playlist_ptr for Playlists, etc. * * The Action Collection takes ownership of the action. It's time to let go. */ void addAction( ActionDestination category, QAction* action, QObject* notify = 0 ); /** * Remove an action from one or all specific categories */ void removeAction( QAction* action ); void removeAction( QAction* action, ActionDestination category ); public slots: void togglePrivateListeningMode(); signals: void privacyModeChanged(); private: static ActionCollection* s_instance; QHash< QString, QAction* > m_actionCollection; QHash< ActionDestination, QList< QAction* > > m_categoryActions; QHash< QAction*, QObject* > m_actionNotifiers; }; #endif tomahawk-player/src/libtomahawk/thirdparty/Qocoa/000775 001750 001750 00000000000 12661705042 023317 5ustar00stefanstefan000000 000000 tomahawk-player/src/libtomahawk/utils/ItunesLoader.cpp000664 001750 001750 00000006456 12661705042 024340 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2013, Hugo Lindström * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "ItunesLoader.h" #include #include #include "Playlist.h" #include "SourceList.h" using namespace Tomahawk; ItunesLoader::ItunesLoader( const QString& input, QObject *parent ) : QObject(parent) , m_itunesLibFile( input ) { /** * Paths to ItunesLibFile * /Users/username/Music/iTunes/iTunes Library.xml * \Users\username\Music\iTunes\iTunes Library.xml * \Documents and Settings\username\My Documents\My Music\iTunes\iTunes Library.xml * http://support.apple.com/kb/HT1660 */ QSettings plist( QUrl::fromUserInput( QString( input.simplified() ) ).toLocalFile(), QSettings::NativeFormat); m_ignoreFields << "Library" << "Movies" << "TV Shows" << "Music Videos" << "Genius"; parseTracks( plist.value( "Tracks" ).toMap() ); parsePlaylists( plist.value( "Playlists" ).toList() ); foreach ( const QString& name, m_playlists.keys()) { Playlist::create( SourceList::instance()->getLocal(), uuid(), name, "iTunes imported playlist", "", false, m_playlists[ name ] ); } m_tracks.clear(); m_playlists.clear(); } void ItunesLoader::parseTracks( const QVariantMap& tracks ) { foreach ( const QVariant& track, tracks ) { QVariantMap trackMap = track.toMap(); if ( !trackMap.value( "Track ID" ).isValid() ) continue; const QString artist = trackMap.value( "Artist", "" ).toString(); const QString title = trackMap.value( "Name", "" ).toString(); const QString album = trackMap.value( "Album", "" ).toString(); if ( artist.isEmpty() || title.isEmpty() ) continue; m_tracks.insert( trackMap.value( "Track ID" ).toInt(), Tomahawk::Query::get( artist, title, album ) ); } } void ItunesLoader::parsePlaylists( const QVariantList& playlists ) { foreach ( const QVariant& playlist, playlists ) { const QString title = playlist.toMap().value( "Name" ).toString(); if ( m_ignoreFields.contains( title ) ) continue; foreach ( const QVariant& items, playlist.toMap() ) { if ( items.toMap().value( "Track ID" ).isValid() ) { int trackId = items.toMap().value( "Track ID" ).toInt(); if ( m_tracks.contains( trackId ) ) m_playlists[title] << m_tracks[ trackId ]; } } } } tomahawk-player/src/tools/database-reader/CMakeLists.txt000664 001750 001750 00000001127 12661705042 024466 0ustar00stefanstefan000000 000000 set( tomahawk_db_list_artists_src listartists.cpp ) include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_executable( tomahawk_db_list_artists_bin WIN32 MACOSX_BUNDLE ${tomahawk_db_list_artists_src} ) set_target_properties( tomahawk_db_list_artists_bin PROPERTIES AUTOMOC TRUE RUNTIME_OUTPUT_NAME tomahawk-db-list-artists ) target_link_libraries( tomahawk_db_list_artists_bin ${TOMAHAWK_LIBRARIES} ) qt5_use_modules(tomahawk_db_list_artists_bin Core) install( TARGETS tomahawk_db_list_artists_bin BUNDLE DESTINATION . RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) tomahawk-player/src/libtomahawk/utils/WidgetDragFilter.cpp000664 001750 001750 00000007046 12661705042 025125 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "WidgetDragFilter.h" #include "utils/Logger.h" #include #include #include WidgetDragFilter::WidgetDragFilter( QObject* parent ) : QObject( parent ) , m_dragStarted( false ) { Q_ASSERT( parent->isWidgetType() ); m_target = QPointer(static_cast(parent)); m_target.data()->installEventFilter( this ); } bool WidgetDragFilter::eventFilter( QObject* obj, QEvent* event ) { if ( m_target.isNull() || m_target.data() != obj ) return false; if ( event->type() == QEvent::MouseButtonPress ) { QMouseEvent *mouseEvent = static_cast( event ); if ( !canDrag( obj, mouseEvent ) ) return false; if ( !( mouseEvent->modifiers() == Qt::NoModifier && mouseEvent->button() == Qt::LeftButton ) ) return false; m_dragPoint = mouseEvent->pos(); m_dragStarted = true; return false; } else if ( event->type() == QEvent::MouseMove ) { if ( !m_dragStarted ) return false; QMouseEvent* e = static_cast(event); if ( !canDrag( obj, e ) ) { m_dragStarted = false; return false; } if ( e->buttons().testFlag( Qt::LeftButton ) ) { m_target.data()->window()->move( m_target.data()->window()->pos() + ( e->pos() - m_dragPoint ) ); return true; } } else if ( event->type() == QEvent::MouseButtonRelease ) m_dragStarted = false; return false; } /** * Make sure we can really drag this widget. Checks inspired by Oxygen's oxygenwindowmanager.cpp */ bool WidgetDragFilter::canDrag( QObject* obj, QMouseEvent* ev ) const { if ( !obj->isWidgetType() ) return false; QWidget* w = static_cast< QWidget* >( obj ); if ( QWidget::mouseGrabber() ) return false; if ( w->cursor().shape() != Qt::ArrowCursor ) return false; // Now we check various things about the child position and mouse QPoint position( ev->pos() ); QWidget* child = w->childAt( position ); if ( child && child->cursor().shape() != Qt::ArrowCursor ) return false; // Don't want to drag menubars when selecting an action if ( QMenuBar* menuBar = qobject_cast( w ) ) { // check if there is an active action if ( menuBar->activeAction() && menuBar->activeAction()->isEnabled() ) return false; // check if action at position exists and is enabled if ( QAction* action = menuBar->actionAt( position ) ) { if ( action->isSeparator() ) return true; if ( action->isEnabled() ) return false; } } return true; } tomahawk-player/src/libtomahawk/widgets/RecentPlaylistsModel.cpp000664 001750 001750 00000020665 12661705042 026354 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2011, Leo Franchi * Copyright 2013, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "RecentPlaylistsModel.h" #include "audio/AudioEngine.h" #include "collection/Collection.h" #include "database/Database.h" #include "database/DatabaseCommand_LoadAllSortedPlaylists.h" #include "network/Servent.h" #include "playlist/dynamic/DynamicPlaylist.h" #include "utils/Logger.h" #include "PlaylistEntry.h" #include "RecentlyPlayedPlaylistsModel.h" #include "SourceList.h" #include "TomahawkSettings.h" #include "Track.h" #define REFRESH_TIMEOUT 1000 using namespace Tomahawk; RecentPlaylistsModel::RecentPlaylistsModel( unsigned int maxPlaylists, QObject* parent ) : QAbstractListModel( parent ) , m_maxPlaylists( maxPlaylists ) { m_timer = new QTimer( this ); connect( m_timer, SIGNAL( timeout() ), SLOT( onRefresh() ) ); connect( SourceList::instance(), SIGNAL( ready() ), SLOT( onReady() ) ); // Load recent playlists initially if ( SourceList::instance()->isReady() ) onRefresh(); } void RecentPlaylistsModel::refresh() { if ( m_timer->isActive() ) m_timer->stop(); m_timer->start( REFRESH_TIMEOUT ); } void RecentPlaylistsModel::onRefresh() { if ( m_timer->isActive() ) m_timer->stop(); emit loadingStarted(); DatabaseCommand_LoadAllSortedPlaylists* cmd = new DatabaseCommand_LoadAllSortedPlaylists( source_ptr() ); cmd->setLimit( m_maxPlaylists ); cmd->setSortOrder( DatabaseCommand_LoadAllPlaylists::ModificationTime ); cmd->setSortAscDesc( DatabaseCommand_LoadAllPlaylists::Descending ); connect( cmd, SIGNAL( done( QList ) ), this, SLOT( playlistsLoaded( QList ) ) ); Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) ); } void RecentPlaylistsModel::onReady() { foreach ( const source_ptr& s, SourceList::instance()->sources() ) onSourceAdded( s ); connect( SourceList::instance(), SIGNAL( sourceAdded( Tomahawk::source_ptr ) ), this, SLOT( onSourceAdded( Tomahawk::source_ptr ) ), Qt::QueuedConnection ); onRefresh(); } void RecentPlaylistsModel::playlistsLoaded( const QList& playlistGuids ) { beginResetModel(); m_playlists.clear(); DatabaseCommand_LoadAllSortedPlaylists::SourcePlaylistPair plPair; foreach ( plPair, playlistGuids ) { const playlist_ptr& pl = Playlist::get( plPair.second ); if ( !pl ) { tDebug() << "ERROR: Found a playlist that is not associated with any source:" << plPair.first << plPair.second; continue; } connect( pl.data(), SIGNAL( changed() ), SLOT( updatePlaylist() ) ); m_playlists << pl; if ( !pl->loaded() ) pl->loadRevision(); } endResetModel(); emit emptinessChanged( m_playlists.isEmpty() ); emit loadingFinished(); } QVariant RecentPlaylistsModel::data( const QModelIndex& index, int role ) const { if ( !index.isValid() || !hasIndex( index.row(), index.column(), index.parent() ) ) return QVariant(); playlist_ptr pl = m_playlists[index.row()]; switch( role ) { case Qt::DisplayRole: return pl->title(); case RecentlyPlayedPlaylistsModel::PlaylistRole: return QVariant::fromValue< Tomahawk::playlist_ptr >( pl ); case RecentlyPlayedPlaylistsModel::ArtistRole: { if ( m_artists.value( pl ).isEmpty() ) { QStringList artists; foreach ( const Tomahawk::plentry_ptr& entry, pl->entries() ) { if ( !artists.contains( entry->query()->track()->artist() ) ) artists << entry->query()->track()->artist(); } m_artists[pl] = artists.join( ", " ); } return m_artists[pl]; } case RecentlyPlayedPlaylistsModel::PlaylistTypeRole: { if ( !pl.dynamicCast< Tomahawk::DynamicPlaylist >().isNull() ) { dynplaylist_ptr dynp = pl.dynamicCast< Tomahawk::DynamicPlaylist >(); if ( dynp->mode() == Static ) return RecentlyPlayedPlaylistsModel::AutoPlaylist; else if ( dynp->mode() == OnDemand ) return RecentlyPlayedPlaylistsModel::Station; } else { return RecentlyPlayedPlaylistsModel::StaticPlaylist; } } case RecentlyPlayedPlaylistsModel::DynamicPlaylistRole: { dynplaylist_ptr dynp = pl.dynamicCast< Tomahawk::DynamicPlaylist >(); return QVariant::fromValue< Tomahawk::dynplaylist_ptr >( dynp ); } case RecentlyPlayedPlaylistsModel::TrackCountRole: { if ( !pl.dynamicCast< Tomahawk::DynamicPlaylist >().isNull() && pl.dynamicCast< Tomahawk::DynamicPlaylist >()->mode() == OnDemand ) return QString( QChar( 0x221E ) ); else return pl->entries().count(); } default: return QVariant(); } } void RecentPlaylistsModel::updatePlaylist() { Playlist* p = qobject_cast< Playlist* >( sender() ); Q_ASSERT( p ); for ( int i = 0; i < m_playlists.size(); i++ ) { if ( m_playlists[ i ].isNull() ) continue; if ( m_playlists[ i ]->guid() == p->guid() ) { QModelIndex idx = index( i, 0, QModelIndex() ); emit dataChanged( idx, idx ); } } } void RecentPlaylistsModel::onSourceAdded( const Tomahawk::source_ptr& source ) { connect( source.data(), SIGNAL( online() ), this, SLOT( sourceOnline() ) ); connect( source->dbCollection().data(), SIGNAL( playlistsAdded( QList ) ), SLOT( refresh() ), Qt::QueuedConnection ); connect( source->dbCollection().data(), SIGNAL( autoPlaylistsAdded(QList)), SLOT( refresh() ), Qt::QueuedConnection ); connect( source->dbCollection().data(), SIGNAL( stationsAdded(QList)), SLOT( refresh() ), Qt::QueuedConnection ); connect( source->dbCollection().data(), SIGNAL( playlistsDeleted( QList ) ), SLOT( onPlaylistsRemoved( QList ) ) ); connect( source->dbCollection().data(), SIGNAL( autoPlaylistsDeleted(QList) ), SLOT( onDynPlaylistsRemoved( QList ) ) ); connect( source->dbCollection().data(), SIGNAL( stationsDeleted(QList) ), SLOT( onDynPlaylistsRemoved( QList ) ) ); } void RecentPlaylistsModel::sourceOnline() { Source* s = qobject_cast< Source* >( sender() ); Q_ASSERT( s ); for ( int i = 0; i < m_playlists.size(); i++ ) { if ( m_playlists[ i ]->author().data() == s ) { QModelIndex idx = index( i, 0, QModelIndex() ); emit dataChanged( idx, idx ); } } } void RecentPlaylistsModel::onDynPlaylistsRemoved( QList< dynplaylist_ptr > playlists ) { QList< playlist_ptr > pls; foreach ( const dynplaylist_ptr& p, playlists ) pls << p; onPlaylistsRemoved( pls ); } void RecentPlaylistsModel::onPlaylistsRemoved( QList< playlist_ptr > playlists ) { foreach ( const playlist_ptr& pl, playlists ) { if ( m_playlists.contains( pl ) ) { m_artists.remove( pl ); int idx = m_playlists.indexOf( pl ); beginRemoveRows( QModelIndex(), idx, idx ); m_playlists.removeAt( idx ); endRemoveRows(); } } emit emptinessChanged( m_playlists.isEmpty() ); } int RecentPlaylistsModel::rowCount( const QModelIndex& ) const { return m_playlists.count(); } tomahawk-player/src/tomahawk/dialogs/LoadPlaylistDialog.cpp000664 001750 001750 00000004545 12661705042 025253 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Leo Franchi * Copyright 2014, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "LoadPlaylistDialog.h" #include "ui_LoadPlaylistDialog.h" #include "TomahawkSettings.h" #include "Source.h" #include LoadPlaylistDialog::LoadPlaylistDialog( QWidget* parent, Qt::WindowFlags f ) : QDialog( parent, f ) , m_ui( new Ui_LoadPlaylist ) { m_ui->setupUi( this ); #ifdef Q_WS_MAC m_ui->horizontalLayout->setContentsMargins( 0, 0, 0, 0 ); m_ui->horizontalLayout->setSpacing( 5 ); #endif setMinimumSize( sizeHint() ); m_ui->autoUpdate->setEnabled( false ); connect( m_ui->navigateButton, SIGNAL( clicked( bool ) ), this, SLOT( getLocalFile() ) ); connect( m_ui->lineEdit, SIGNAL( textChanged( QString ) ), this, SLOT( onUrlChanged() ) ); } LoadPlaylistDialog::~LoadPlaylistDialog() { } void LoadPlaylistDialog::getLocalFile() { const QString path = TomahawkSettings::instance()->importPlaylistPath(); QString url = QFileDialog::getOpenFileName( this, tr( "Load Playlist" ), path, tr( "Playlists (*.xspf *.m3u *.jspf)" ) ); if ( !url.isEmpty() ) { const QFileInfo fi( url ); TomahawkSettings::instance()->setImportPlaylistPath( fi.absoluteDir().absolutePath() ); } m_ui->lineEdit->setText( url ); } void LoadPlaylistDialog::onUrlChanged() { m_ui->autoUpdate->setEnabled( m_ui->lineEdit->text().trimmed().startsWith( "http://" ) ); } QString LoadPlaylistDialog::url() const { return m_ui->lineEdit->text(); } bool LoadPlaylistDialog::autoUpdate() const { return m_ui->autoUpdate->isChecked(); } tomahawk-player/src/tomahawk/TomahawkWindow.cpp000664 001750 001750 00000136241 12661705042 023052 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2015, Christian Muehlhaeuser * Copyright 2010-2012, Leo Franchi * Copyright 2010-2012, Jeff Mitchell * Copyright 2012, Teo Mrnjavac * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "TomahawkWindow.h" #include "ui_TomahawkWindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "accounts/AccountManager.h" #include "sourcetree/SourceTreeView.h" #include "network/Servent.h" #include "utils/TomahawkStyle.h" #include "utils/TomahawkUtilsGui.h" #include "utils/ProxyStyle.h" #include "utils/WidgetDragFilter.h" #include "utils/NetworkAccessManager.h" #include "utils/M3uLoader.h" #include "utils/JspfLoader.h" #include "widgets/AccountsToolButton.h" #include "widgets/AnimatedSplitter.h" #include "widgets/ContainedMenuButton.h" #include "thirdparty/Qocoa/qsearchfield.h" #include "playlist/dynamic/GeneratorInterface.h" #include "playlist/PlaylistModel.h" #include "playlist/PlayableProxyModel.h" #include "playlist/ContextView.h" #include "playlist/TrackView.h" #include "playlist/QueueView.h" #include "jobview/JobStatusView.h" #include "jobview/JobStatusModel.h" #include "jobview/ErrorStatusMessage.h" #include "jobview/JobStatusModel.h" #include "sip/SipPlugin.h" #include "filemetadata/ScanManager.h" #include "viewpages/SearchViewPage.h" #include "viewpages/whatsnew_0_8/WhatsNew_0_8.h" #include "Playlist.h" #include "Query.h" #include "Artist.h" #include "ViewManager.h" #include "ActionCollection.h" #include "AudioControls.h" #include "dialogs/SettingsDialog.h" #include "dialogs/DiagnosticsDialog.h" #include "TomahawkSettings.h" #include "SourceList.h" #include "TomahawkTrayIcon.h" #include "TomahawkApp.h" #include "dialogs/LoadPlaylistDialog.h" #include "utils/ImageRegistry.h" #include "utils/Logger.h" #include "config.h" #if defined( Q_OS_WIN ) #if defined ( WITH_QtSparkle ) #include #endif #include #if QT_VERSION < QT_VERSION_CHECK(5,2,0) #include #ifndef THBN_CLICKED #define THBN_CLICKED 0x1800 #endif #endif #endif using namespace Tomahawk; using namespace Accounts; TomahawkWindow::TomahawkWindow( QWidget* parent ) : QMainWindow( parent ) , TomahawkUtils::DpiScaler( this ) #if defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK( 5, 2, 0 ) , m_buttonCreatedID( RegisterWindowMessage( L"TaskbarButtonCreated" ) ) , m_taskbarList( 0 ) #endif , ui( new Ui::TomahawkWindow ) , m_searchWidget( 0 ) , m_trayIcon( 0 ) , m_audioRetryCounter( 0 ) { #ifndef Q_OS_MAC setWindowIcon( QIcon( RESPATH "icons/tomahawk-icon-128x128.png" ) ); #endif new ViewManager( this ); QueueView* queueView = new QueueView(); ViewManager::instance()->setQueue( queueView ); AudioEngine::instance()->setQueue( queueView->view()->trackView()->proxyModel()->playlistInterface() ); m_audioControls = new AudioControls( this ); ui->setupUi( this ); applyPlatformTweaks(); ui->centralWidget->setContentsMargins( 0, 0, 0, 0 ); TomahawkUtils::unmarginLayout( ui->centralWidget->layout() ); if ( QSystemTrayIcon::isSystemTrayAvailable() ) { m_trayIcon = new TomahawkTrayIcon( this ); } setupMenuBar(); setupToolBar(); setupSideBar(); setupStatusBar(); setupUpdateCheck(); loadSettings(); setupSignals(); setupShortcuts(); #ifdef Q_OS_WIN connect( AudioEngine::instance(), SIGNAL( stateChanged( AudioState, AudioState ) ), SLOT( audioStateChanged( AudioState, AudioState ) ) ); #if QT_VERSION >= QT_VERSION_CHECK( 5, 2, 0 ) setupWindowsButtons(); #endif #endif if ( qApp->arguments().contains( "--debug" ) ) { connect( ActionCollection::instance()->getAction( "crashNow" ), SIGNAL( triggered() ), SLOT( crashNow() ) ); } // set initial state audioStopped(); if ( TomahawkSettings::instance()->fullscreenEnabled() ) { // Window must be fully constructed to toggle fullscreen mode. Queue it up. QTimer::singleShot( 0, this, SLOT( toggleFullscreen() ) ); } } TomahawkWindow::~TomahawkWindow() { saveSettings(); delete ui; } void TomahawkWindow::loadSettings() { TomahawkSettings* s = TomahawkSettings::instance(); // Workaround for broken window geometry restoring on Qt Cocoa when setUnifiedTitleAndToolBarOnMac is true. // See http://bugreports.qt.nokia.com/browse/QTBUG-3116 and // http://lists.qt.nokia.com/pipermail/qt-interest/2009-August/011491.html // for the 'fix' #ifdef QT_MAC_USE_COCOA bool workaround = isVisible(); if ( workaround ) { // make "invisible" setWindowOpacity( 0 ); // let Qt update its frameStruts show(); } #endif if ( !s->mainWindowGeometry().isEmpty() ) restoreGeometry( s->mainWindowGeometry() ); else { // Set default window geometry resize( QDesktopWidget().availableGeometry( this ).size() * 0.8 ); } if ( !s->mainWindowState().isEmpty() ) restoreState( s->mainWindowState() ); if ( !s->mainWindowSplitterState().isEmpty() ) ui->splitter->restoreState( s->mainWindowSplitterState() ); // Always set stretch factor. If user hasn't manually set splitter sizes, // this will ensure a sane default on all startups. If the user has, the manual // size will override the default stretching ui->splitter->setHandleWidth( 3 ); ui->splitter->setStretchFactor( 0, 0 ); ui->splitter->setStretchFactor( 1, 1 ); #ifdef QT_MAC_USE_COCOA if ( workaround ) { // Make it visible again setWindowOpacity( 1 ); } #endif #ifndef Q_OS_MAC bool mbVisible = s->menuBarVisible(); menuBar()->setVisible( mbVisible ); m_compactMenuAction->setVisible( !mbVisible ); ActionCollection::instance()->getAction( "toggleMenuBar" )->setText( mbVisible ? tr( "Hide Menu Bar" ) : tr( "Show Menu Bar" ) ); #endif ActionCollection::instance()->getAction( "showOfflineSources" )->setChecked( TomahawkSettings::instance()->showOfflineSources() ); } void TomahawkWindow::saveSettings() { TomahawkSettings* s = TomahawkSettings::instance(); s->setMainWindowGeometry( saveGeometry() ); s->setMainWindowState( saveState() ); s->setMainWindowSplitterState( ui->splitter->saveState() ); s->setMenuBarVisible( menuBar()->isVisible() ); } void TomahawkWindow::applyPlatformTweaks() { // HACK: QtCurve causes an infinite loop on startup. This is because // setStyle calls setPalette, which calls ensureBaseStyle, which loads // QtCurve. QtCurve calls setPalette, which creates an infinite loop. // We could simply not use ProxyStyle under QtCurve, but that would // make the whole UI look like crap. // Instead, we tell ProxyStyle that it's running under QtCurve, so it // can intercept QStyle::polish (which in the base implementation does // nothing and in QtCurve does evil things), and avoid forwarding it // to QtCurve. bool isQtCurve = false; if ( QString( qApp->style()->metaObject()->className() ).toLower().contains( "qtcurve" ) ) isQtCurve = true; qApp->setStyle( new ProxyStyle( isQtCurve ) ); #ifdef Q_OS_MAC setUnifiedTitleAndToolBarOnMac( true ); delete ui->hline1; delete ui->hline2; #else ui->hline1->setStyleSheet( "border: 1px solid gray;" ); ui->hline2->setStyleSheet( "border: 1px solid gray;" ); #endif } void TomahawkWindow::setupToolBar() { m_toolbar = addToolBar( "TomahawkToolbar" ); m_toolbar->setObjectName( "TomahawkToolbar" ); m_toolbar->setMovable( false ); m_toolbar->setFloatable( false ); #ifdef Q_OS_MAC m_toolbar->setIconSize( QSize( 22, 22 ) ); #else m_toolbar->setIconSize( scaled( 22, 22 ) ); #endif m_toolbar->setToolButtonStyle( Qt::ToolButtonIconOnly ); m_toolbar->setStyleSheet( "border-bottom: 0px" ); // If the toolbar is hidden accidentally it causes trouble on Unity because the user can't // easily bring it back (TWK-1046). So we just prevent the user from hiding the toolbar. // This should not affect Mac users. m_toolbar->setContextMenuPolicy( Qt::PreventContextMenu ); #ifdef Q_OS_MAC m_toolbar->installEventFilter( new WidgetDragFilter( m_toolbar ) ); #endif m_backAction = m_toolbar->addAction( ImageRegistry::instance()->pixmap( RESPATH "images/back.svg", m_toolbar->iconSize() ), tr( "Back" ), ViewManager::instance(), SLOT( historyBack() ) ); m_backAction->setToolTip( tr( "Go back one page" ) ); #ifdef Q_OS_MAC m_backAction->setShortcut( QKeySequence( "Ctrl+Left" ) ); #else m_backAction->setShortcut( QKeySequence( "Alt+Left" ) ); #endif m_forwardAction = m_toolbar->addAction( ImageRegistry::instance()->pixmap( RESPATH "images/forward.svg", m_toolbar->iconSize() ), tr( "Forward" ), ViewManager::instance(), SLOT( historyForward() ) ); m_forwardAction->setToolTip( tr( "Go forward one page" ) ); #ifdef Q_OS_MAC m_forwardAction->setShortcut( QKeySequence( "Ctrl+Right" ) ); #else m_forwardAction->setShortcut( QKeySequence( "Alt+Right" ) ); #endif m_toolbarLeftBalancer = new QWidget( this ); m_toolbarLeftBalancer->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred ); m_toolbarLeftBalancer->setFixedWidth( 0 ); m_toolbar->addWidget( m_toolbarLeftBalancer )->setProperty( "kind", QString( "spacer" ) ); QWidget* toolbarLeftSpacer = new QWidget( this ); toolbarLeftSpacer->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); m_toolbar->addWidget( toolbarLeftSpacer )->setProperty( "kind", QString( "spacer" ) ); m_searchWidget = new QSearchField( this ); m_searchWidget->setPlaceholderText( tr( "Search" ) ); m_searchWidget->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred ); m_searchWidget->setFixedWidth( scaledX( 340 ) ); connect( m_searchWidget, SIGNAL( returnPressed() ), SLOT( onFilterEdited() ) ); m_toolbar->addWidget( m_searchWidget )->setProperty( "kind", QString( "search" ) ); QWidget* rightSpacer = new QWidget( this ); rightSpacer->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); m_toolbar->addWidget( rightSpacer )->setProperty( "kind", QString( "spacer" ) ); m_toolbarRightBalancer = new QWidget( this ); m_toolbarRightBalancer->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred ); m_toolbarRightBalancer->setFixedWidth( 0 ); m_toolbar->addWidget( m_toolbarRightBalancer )->setProperty( "kind", QString( "spacer" ) ); m_accountsButton = new AccountsToolButton( m_toolbar ); m_toolbar->addWidget( m_accountsButton ); connect( m_accountsButton, SIGNAL( widthChanged() ), SLOT( balanceToolbar() ) ); #ifndef Q_OS_MAC ContainedMenuButton* compactMenuButton = new ContainedMenuButton( m_toolbar ); compactMenuButton->setIcon( ImageRegistry::instance()->pixmap( RESPATH "images/configure.svg", m_toolbar->iconSize() ) ); compactMenuButton->setText( tr( "&Main Menu" ) ); compactMenuButton->setMenu( m_compactMainMenu ); compactMenuButton->setToolButtonStyle( Qt::ToolButtonIconOnly ); m_compactMenuAction = m_toolbar->addWidget( compactMenuButton ); //HACK: adding the toggle action to the window, otherwise the shortcut keys // won't be picked up when the menu is hidden. // This must be done for all menu bar actions that have shortcut keys :( // Does not apply to Mac which always shows the menu bar. addAction( ActionCollection::instance()->getAction( "playPause" ) ); addAction( ActionCollection::instance()->getAction( "toggleMenuBar" ) ); addAction( ActionCollection::instance()->getAction( "quit" ) ); #endif onHistoryBackAvailable( false ); onHistoryForwardAvailable( false ); balanceToolbar(); } void TomahawkWindow::balanceToolbar() { int leftActionsWidth = 0; int rightActionsWidth = 0; bool flip = false; foreach ( QAction* action, m_toolbar->actions() ) { if ( action->property( "kind" ) == QString( "spacer" ) || !action->isVisible() ) { continue; } else if ( action->property( "kind" ) == QString( "search" ) ) { flip = true; continue; } QWidget* widget = m_toolbar->widgetForAction( action ); if ( !flip ) //we accumulate on the left { leftActionsWidth += widget->sizeHint().width() + m_toolbar->layout()->spacing(); } else //then, on the right { rightActionsWidth += widget->sizeHint().width() + m_toolbar->layout()->spacing(); } } if ( leftActionsWidth > rightActionsWidth ) { m_toolbarLeftBalancer->setFixedWidth( 0 ); m_toolbarRightBalancer->setFixedWidth( leftActionsWidth - rightActionsWidth ); } else { m_toolbarLeftBalancer->setFixedWidth( rightActionsWidth - leftActionsWidth ); m_toolbarRightBalancer->setFixedWidth( 0 ); } } void TomahawkWindow::toggleLoved() { if ( !AudioEngine::instance()->currentTrack().isNull() ) { AudioEngine::instance()->currentTrack()->track()->setLoved( !AudioEngine::instance()->currentTrack()->track()->loved() ); #ifdef Q_OS_WIN updateWindowsLoveButton(); #endif } } void TomahawkWindow::setupSideBar() { // Delete fake designer widgets delete ui->sidebarWidget; delete ui->playlistWidget; QWidget* sidebarWidget = new QWidget(); sidebarWidget->setLayout( new QVBoxLayout() ); m_sidebar = new AnimatedSplitter(); m_sidebar->setOrientation( Qt::Vertical ); m_sidebar->setChildrenCollapsible( false ); m_sourcetree = new SourceTreeView( this ); JobStatusView* jobsView = new JobStatusView( m_sidebar ); JobStatusModel* sourceModel = new JobStatusModel( jobsView ); m_jobsModel = new JobStatusSortModel( jobsView ); m_jobsModel->setJobModel( sourceModel ); jobsView->setModel( m_jobsModel ); m_sidebar->addWidget( m_sourcetree ); m_sidebar->addWidget( jobsView ); // m_sidebar->setGreedyWidget( 1 ); m_sidebar->hide( 1, false ); m_sidebar->hide( 2, false ); sidebarWidget->layout()->addWidget( m_sidebar ); sidebarWidget->setContentsMargins( 0, 0, 0, 0 ); sidebarWidget->layout()->setContentsMargins( 0, 0, 0, 0 ); sidebarWidget->layout()->setMargin( 0 ); #ifndef Q_OS_MAC sidebarWidget->layout()->setSpacing( 0 ); #endif ui->splitter->addWidget( sidebarWidget ); ui->splitter->addWidget( ViewManager::instance()->widget() ); ui->splitter->setCollapsible( 0, false ); ui->splitter->setCollapsible( 1, false ); } void TomahawkWindow::setupStatusBar() { statusBar()->hide(); setStatusBar( 0 ); ui->centralWidget->layout()->addWidget( m_audioControls ); } void TomahawkWindow::setupShortcuts() { { // Use Ctrl+F to focus the searchWidget QShortcut* shortcut = new QShortcut( QKeySequence( QKeySequence::Find ), this ); QObject::connect( shortcut, SIGNAL( activated() ), m_searchWidget, SLOT( setFocus() ) ); } { // Use Ctrl+W to close current page QShortcut* shortcut = new QShortcut( QKeySequence( QKeySequence::Close ), this ); QObject::connect( shortcut, SIGNAL( activated() ), ViewManager::instance(), SLOT( destroyCurrentPage() ) ); } { // Ctrl Up for raising the volume QShortcut* shortcut = new QShortcut( QKeySequence( QKeySequence( "Ctrl+Up" ) ), this ); QObject::connect( shortcut, SIGNAL( activated() ), AudioEngine::instance(), SLOT( raiseVolume() ) ); } { // Ctrl Down for lowering the volume QShortcut* shortcut = new QShortcut( QKeySequence( QKeySequence( "Ctrl+Down" ) ), this ); QObject::connect( shortcut, SIGNAL( activated() ), AudioEngine::instance(), SLOT( lowerVolume() ) ); } } void TomahawkWindow::setupUpdateCheck() { #if defined( Q_OS_MAC ) && defined( HAVE_SPARKLE ) connect( ActionCollection::instance()->getAction( "checkForUpdates" ), SIGNAL( triggered( bool ) ), SLOT( checkForUpdates() ) ); #elif defined( Q_OS_WIN ) && defined( WITH_QtSparkle ) QUrl updaterUrl; if ( qApp->arguments().contains( "--debug" ) ) updaterUrl.setUrl( "http://download.tomahawk-player.org/sparklewin-debug" ); else updaterUrl.setUrl( "http://download.tomahawk-player.org/sparklewin" ); qtsparkle::Updater* updater = new qtsparkle::Updater( updaterUrl, this ); Q_ASSERT( Tomahawk::Utils::nam() != 0 ); updater->SetNetworkAccessManager( Tomahawk::Utils::nam() ); updater->SetVersion( TomahawkUtils::appFriendlyVersion() ); connect( ActionCollection::instance()->getAction( "checkForUpdates" ), SIGNAL( triggered() ), updater, SLOT( CheckNow() ) ); #endif } #ifdef Q_OS_WIN bool TomahawkWindow::setupWindowsButtons() { #if QT_VERSION < QT_VERSION_CHECK( 5, 2, 0 ) const GUID IID_ITaskbarList3 = { 0xea1afb91,0x9e28,0x4b86, { 0x90,0xe9,0x9e,0x9f,0x8a,0x5e,0xef,0xaf } }; HRESULT hr = S_OK; THUMBBUTTONMASK dwMask = THUMBBUTTONMASK( THB_ICON | THB_TOOLTIP | THB_FLAGS ); m_thumbButtons[TP_PREVIOUS].dwMask = dwMask; m_thumbButtons[TP_PREVIOUS].iId = TP_PREVIOUS; m_thumbButtons[TP_PREVIOUS].hIcon = thumbIcon(TomahawkUtils::PrevButton); m_thumbButtons[TP_PREVIOUS].dwFlags = THBF_ENABLED; m_thumbButtons[TP_PREVIOUS].szTip[ tr( "Back" ).toWCharArray( m_thumbButtons[TP_PREVIOUS].szTip ) ] = 0; m_thumbButtons[TP_PLAY_PAUSE].dwMask = dwMask; m_thumbButtons[TP_PLAY_PAUSE].iId = TP_PLAY_PAUSE; m_thumbButtons[TP_PLAY_PAUSE].hIcon = thumbIcon(TomahawkUtils::PlayButton); m_thumbButtons[TP_PLAY_PAUSE].dwFlags = THBF_ENABLED; m_thumbButtons[TP_PLAY_PAUSE].szTip[ tr( "Play" ).toWCharArray( m_thumbButtons[TP_PLAY_PAUSE].szTip ) ] = 0; m_thumbButtons[TP_NEXT].dwMask = dwMask; m_thumbButtons[TP_NEXT].iId = TP_NEXT; m_thumbButtons[TP_NEXT].hIcon = thumbIcon(TomahawkUtils::NextButton); m_thumbButtons[TP_NEXT].dwFlags = THBF_ENABLED; m_thumbButtons[TP_NEXT].szTip[ tr( "Next" ).toWCharArray( m_thumbButtons[TP_NEXT].szTip ) ] = 0; m_thumbButtons[3].dwMask = dwMask; m_thumbButtons[3].iId = -1; m_thumbButtons[3].hIcon = 0; m_thumbButtons[3].dwFlags = THBF_NOBACKGROUND | THBF_DISABLED; m_thumbButtons[3].szTip[0] = 0; m_thumbButtons[TP_LOVE].dwMask = dwMask; m_thumbButtons[TP_LOVE].iId = TP_LOVE; m_thumbButtons[TP_LOVE].hIcon = thumbIcon(TomahawkUtils::NotLoved); m_thumbButtons[TP_LOVE].dwFlags = THBF_DISABLED; m_thumbButtons[TP_LOVE].szTip[ tr( "Love" ).toWCharArray( m_thumbButtons[TP_LOVE].szTip ) ] = 0; if ( S_OK == CoCreateInstance( CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_ITaskbarList3, (void **)&m_taskbarList ) ) { hr = m_taskbarList->HrInit(); if ( SUCCEEDED( hr ) ) { hr = m_taskbarList->ThumbBarAddButtons( (HWND)winId(), ARRAYSIZE( m_thumbButtons ), m_thumbButtons ); } else { m_taskbarList->Release(); m_taskbarList = 0; } } return SUCCEEDED( hr ); #else m_taskbarList = new QWinThumbnailToolBar( this ); m_taskbarList->setWindow( this->windowHandle() ); QWinThumbnailToolButton *back = new QWinThumbnailToolButton( m_taskbarList ); back->setToolTip( tr( "Back" ) ); back->setIcon( thumbIcon( TomahawkUtils::PrevButton ) ); connect( back, SIGNAL( clicked() ) , AudioEngine::instance() , SLOT( previous() ) ); m_taskbarList->addButton(back); QWinThumbnailToolButton *play = new QWinThumbnailToolButton( m_taskbarList ); play->setToolTip( tr( "Play" ) ); play->setIcon( thumbIcon( TomahawkUtils::PlayButton ) ); connect( play, SIGNAL( clicked() ) , AudioEngine::instance() , SLOT( playPause() ) ); m_taskbarList->addButton(play); QWinThumbnailToolButton *next = new QWinThumbnailToolButton( m_taskbarList ); next->setToolTip( tr( "Next" ) ); next->setIcon( thumbIcon( TomahawkUtils::NextButton ) ); connect( next, SIGNAL( clicked() ) , AudioEngine::instance() , SLOT( next() ) ); m_taskbarList->addButton(next); QWinThumbnailToolButton *space = new QWinThumbnailToolButton( m_taskbarList ); space->setVisible( true ); space->setFlat( true ); m_taskbarList->addButton(space); QWinThumbnailToolButton *love = new QWinThumbnailToolButton( m_taskbarList ); love->setToolTip( tr( "Love" ) ); love->setIcon( thumbIcon( TomahawkUtils::NotLoved ) ); love->setInteractive( false ); connect( love , SIGNAL( clicked() ) , this , SLOT( toggleLoved() ) ); m_taskbarList->addButton(love); return true; #endif//QT_VERSION < QT_VERSION_CHECK( 5, 2, 0 ) } #if QT_VERSION < QT_VERSION_CHECK( 5, 2, 0 ) HICON TomahawkWindow::thumbIcon( TomahawkUtils::ImageType type ) { static QMap thumbIcons; if ( !thumbIcons.contains( type ) ) { QPixmap pix ( TomahawkUtils::defaultPixmap(type , TomahawkUtils::Original, QSize( 40, 40 ) ) ); thumbIcons[type] = pix.toWinHICON(); } return thumbIcons[type]; } #else QIcon TomahawkWindow::thumbIcon(TomahawkUtils::ImageType type) { return TomahawkUtils::defaultPixmap( type , TomahawkUtils::Original, QSize( 40, 40 ) ); } #endif//QT_VERSION < QT_VERSION_CHECK( 5, 2, 0 ) #endif void TomahawkWindow::setupSignals() { // connect( AudioEngine::instance(), SIGNAL( error( AudioEngine::AudioErrorCode ) ), SLOT( onAudioEngineError( AudioEngine::AudioErrorCode ) ) ); connect( AudioEngine::instance(), SIGNAL( loading( const Tomahawk::result_ptr& ) ), SLOT( onPlaybackLoading( const Tomahawk::result_ptr& ) ) ); connect( AudioEngine::instance(), SIGNAL( started( Tomahawk::result_ptr ) ), SLOT( audioStarted() ) ); connect( AudioEngine::instance(), SIGNAL( finished( Tomahawk::result_ptr ) ), SLOT( audioFinished() ) ); connect( AudioEngine::instance(), SIGNAL( resumed() ), SLOT( audioStarted() ) ); connect( AudioEngine::instance(), SIGNAL( paused() ), SLOT( audioPaused() ) ); connect( AudioEngine::instance(), SIGNAL( stopped() ), SLOT( audioStopped() ) ); // ActionCollection *ac = ActionCollection::instance(); connect( ac->getAction( "preferences" ), SIGNAL( triggered() ), SLOT( showSettingsDialog() ) ); connect( ac->getAction( "diagnostics" ), SIGNAL( triggered() ), SLOT( showDiagnosticsDialog() ) ); connect( ac->getAction( "legalInfo" ), SIGNAL( triggered() ), SLOT( legalInfo() ) ); connect( ac->getAction( "reportBug" ), SIGNAL( triggered() ), SLOT( reportBug() ) ); connect( ac->getAction( "getSupport" ), SIGNAL( triggered() ), SLOT( getSupport() ) ); connect( ac->getAction( "helpTranslate" ), SIGNAL( triggered() ), SLOT( helpTranslate() ) ); connect( ac->getAction( "openLogfile" ), SIGNAL( triggered() ), SLOT( openLogfile() ) ); connect( ac->getAction( "updateCollection" ), SIGNAL( triggered() ), SLOT( updateCollectionManually() ) ); connect( ac->getAction( "rescanCollection" ), SIGNAL( triggered() ), SLOT( rescanCollectionManually() ) ); connect( ac->getAction( "importPlaylist" ), SIGNAL( triggered() ), SLOT( loadPlaylist() ) ); connect( ac->getAction( "whatsnew_0_8" ), SIGNAL( triggered() ), SLOT( showWhatsNew_0_8() ) ); connect( ac->getAction( "aboutTomahawk" ), SIGNAL( triggered() ), SLOT( showAboutTomahawk() ) ); connect( ac->getAction( "quit" ), SIGNAL( triggered() ), qApp, SLOT( quit() ) ); connect( ac->getAction( "showOfflineSources" ), SIGNAL( triggered() ), SLOT( showOfflineSources() ) ); #if defined( Q_OS_MAC ) connect( ac->getAction( "minimize" ), SIGNAL( triggered() ), SLOT( minimize() ) ); connect( ac->getAction( "zoom" ), SIGNAL( triggered() ), SLOT( maximize() ) ); connect( ac->getAction( "fullscreen" ), SIGNAL( triggered() ), SLOT( toggleFullscreen() ) ); #else connect( ac->getAction( "toggleMenuBar" ), SIGNAL( triggered() ), SLOT( toggleMenuBar() ) ); #endif connect( ViewManager::instance(), SIGNAL( historyBackAvailable( bool ) ), SLOT( onHistoryBackAvailable( bool ) ) ); connect( ViewManager::instance(), SIGNAL( historyForwardAvailable( bool ) ), SLOT( onHistoryForwardAvailable( bool ) ) ); } void TomahawkWindow::setupMenuBar() { // Always create a menubar, but only create a compactMenu on Windows and X11 m_menuBar = ActionCollection::instance()->createMenuBar( this ); m_menuBar->setFont( TomahawkUtils::systemFont() ); setMenuBar( m_menuBar ); #ifndef Q_OS_MAC m_compactMainMenu = ActionCollection::instance()->createCompactMenu( this ); #endif } bool TomahawkWindow::eventFilter( QObject* obj, QEvent* event ) { if ( event->type() == QEvent::MouseButtonPress ) { QMouseEvent* me = static_cast(event); switch ( me->button() ) { case Qt::XButton1: m_backAction->trigger(); break; case Qt::XButton2: m_forwardAction->trigger(); break; default: break; } } return QObject::eventFilter( obj, event ); } void TomahawkWindow::changeEvent( QEvent* e ) { QMainWindow::changeEvent( e ); switch ( e->type() ) { case QEvent::LanguageChange: ui->retranslateUi( this ); break; default: break; } } void TomahawkWindow::closeEvent( QCloseEvent* e ) { #ifndef Q_OS_MAC if ( e->spontaneous() && QSystemTrayIcon::isSystemTrayAvailable() ) { hide(); e->ignore(); return; } #endif QMainWindow::closeEvent( e ); } void TomahawkWindow::showEvent( QShowEvent* e ) { QMainWindow::showEvent( e ); #if defined( Q_OS_MAC ) ActionCollection::instance()->getAction( "minimize" )->setDisabled( false ); ActionCollection::instance()->getAction( "zoom" )->setDisabled( false ); #endif } void TomahawkWindow::hideEvent( QHideEvent* e ) { QMainWindow::hideEvent( e ); #if defined( Q_OS_MAC ) ActionCollection::instance()->getAction( "minimize" )->setDisabled( true ); ActionCollection::instance()->getAction( "zoom" )->setDisabled( true ); #endif } void TomahawkWindow::keyPressEvent( QKeyEvent* e ) { bool accept = true; #if ! defined ( Q_OS_MAC ) #define KEY_PRESSED Q_FUNC_INFO << "Multimedia Key Pressed:" switch ( e->key() ) { case Qt::Key_MediaPlay: tLog() << KEY_PRESSED << "Play"; AudioEngine::instance()->playPause(); break; case Qt::Key_MediaStop: tLog() << KEY_PRESSED << "Stop"; AudioEngine::instance()->stop(); break; case Qt::Key_MediaPrevious: tLog() << KEY_PRESSED << "Previous"; AudioEngine::instance()->previous(); break; case Qt::Key_MediaNext: tLog() << KEY_PRESSED << "Next"; AudioEngine::instance()->next(); break; case Qt::Key_MediaPause: tLog() << KEY_PRESSED << "Pause"; AudioEngine::instance()->pause(); break; case Qt::Key_MediaTogglePlayPause: tLog() << KEY_PRESSED << "PlayPause"; AudioEngine::instance()->playPause(); break; case Qt::Key_MediaRecord: default: accept = false; } #else accept = false; #endif if ( accept ) e->accept(); QMainWindow::keyPressEvent( e ); } #if defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK( 5, 2, 0 ) bool TomahawkWindow::winEvent( MSG* msg, long* result ) { Q_UNUSED(result); #define TB_PRESSED Q_FUNC_INFO << "Taskbar Button Pressed:" switch ( msg->message ) { case WM_COMMAND: if ( HIWORD( msg->wParam ) == THBN_CLICKED ) { switch ( TB_STATES( LOWORD( msg->wParam ) ) ) { case TP_PREVIOUS: tLog() << TB_PRESSED << "Previous"; AudioEngine::instance()->previous(); break; case TP_PLAY_PAUSE: tLog() << TB_PRESSED << "Play/Pause"; AudioEngine::instance()->playPause(); break; case TP_NEXT: tLog() << TB_PRESSED << "Next"; AudioEngine::instance()->next(); break; case TP_LOVE: tLog() << TB_PRESSED << "Love"; toggleLoved(); break; } return true; } break; } if ( msg->message == m_buttonCreatedID ) return setupWindowsButtons(); return false; } #endif//defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK( 5, 2, 0 ) void TomahawkWindow::audioStateChanged( AudioState newState, AudioState oldState ) { Q_UNUSED(oldState); #ifndef Q_OS_WIN Q_UNUSED(newState); #else #if QT_VERSION < QT_VERSION_CHECK( 5, 2, 0 ) if ( m_taskbarList == 0 ) return; switch ( newState ) { case AudioEngine::Playing: { m_thumbButtons[TP_PLAY_PAUSE].hIcon = thumbIcon(TomahawkUtils::PauseButton); m_thumbButtons[TP_PLAY_PAUSE].szTip[ tr( "Pause" ).toWCharArray( m_thumbButtons[TP_PLAY_PAUSE].szTip ) ] = 0; updateWindowsLoveButton(); } break; case AudioEngine::Paused: { m_thumbButtons[TP_PLAY_PAUSE].hIcon = thumbIcon(TomahawkUtils::PlayButton); m_thumbButtons[TP_PLAY_PAUSE].szTip[ tr( "Play" ).toWCharArray( m_thumbButtons[TP_PLAY_PAUSE].szTip ) ] = 0; } break; case AudioEngine::Stopped: { if ( !AudioEngine::instance()->currentTrack().isNull() ) { disconnect( AudioEngine::instance()->currentTrack()->track().data(), SIGNAL( socialActionsLoaded() ), this, SLOT( updateWindowsLoveButton() ) ); } m_thumbButtons[TP_PLAY_PAUSE].hIcon = thumbIcon(TomahawkUtils::PlayButton); m_thumbButtons[TP_PLAY_PAUSE].szTip[ tr( "Play" ).toWCharArray( m_thumbButtons[TP_PLAY_PAUSE].szTip ) ] = 0; m_thumbButtons[TP_LOVE].hIcon = thumbIcon(TomahawkUtils::NotLoved); m_thumbButtons[TP_LOVE].dwFlags = THBF_DISABLED; } break; default: return; } m_taskbarList->ThumbBarUpdateButtons( (HWND)winId(), ARRAYSIZE( m_thumbButtons ), m_thumbButtons ); #else QWinThumbnailToolButton *play = m_taskbarList->buttons()[ TP_PLAY_PAUSE ]; switch ( newState ) { case AudioEngine::Playing: { play->setIcon( thumbIcon(TomahawkUtils::PauseButton) ); play->setToolTip( tr( "Pause" ) ); updateWindowsLoveButton(); } break; case AudioEngine::Paused: { play->setIcon( thumbIcon(TomahawkUtils::PlayButton) ); play->setToolTip( tr( "Play" ) ); } break; case AudioEngine::Stopped: { if ( !AudioEngine::instance()->currentTrack().isNull() ) { disconnect( AudioEngine::instance()->currentTrack()->track().data(), SIGNAL( socialActionsLoaded() ), this, SLOT( updateWindowsLoveButton() ) ); } play->setIcon( thumbIcon(TomahawkUtils::PlayButton) ); play->setToolTip( tr( "Play" ) ); QWinThumbnailToolButton *love = m_taskbarList->buttons()[ TP_LOVE ]; love->setIcon( thumbIcon(TomahawkUtils::NotLoved) ); love->setInteractive( false ); } break; default: return; } #endif//QT_VERSION < QT_VERSION_CHECK( 5, 2, 0 ) #endif//Q_OS_WIN } void TomahawkWindow::updateWindowsLoveButton() { #if defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK( 5, 2, 0 ) if ( m_taskbarList == 0 ) return; if ( !AudioEngine::instance()->currentTrack().isNull() && AudioEngine::instance()->currentTrack()->track()->loved() ) { m_thumbButtons[TP_LOVE].hIcon = thumbIcon(TomahawkUtils::Loved); m_thumbButtons[TP_LOVE].szTip[ tr( "Unlove" ).toWCharArray( m_thumbButtons[TP_LOVE].szTip ) ] = 0; } else { m_thumbButtons[TP_LOVE].hIcon = thumbIcon(TomahawkUtils::NotLoved); m_thumbButtons[TP_LOVE].szTip[ tr( "Love" ).toWCharArray( m_thumbButtons[TP_LOVE].szTip ) ] = 0; } m_thumbButtons[TP_LOVE].dwFlags = THBF_ENABLED; m_taskbarList->ThumbBarUpdateButtons( (HWND)winId(), ARRAYSIZE( m_thumbButtons ), m_thumbButtons ); #elif defined(Q_OS_WIN) QWinThumbnailToolButton *love = m_taskbarList->buttons()[ TP_LOVE ]; if ( !AudioEngine::instance()->currentTrack().isNull() ) { love->setInteractive(true); if ( AudioEngine::instance()->currentTrack()->track()->loved() ) { love->setIcon(thumbIcon(TomahawkUtils::Loved)); love->setToolTip( tr( "Unlove" ) ); } else { love->setIcon( thumbIcon(TomahawkUtils::NotLoved) ); love->setToolTip( tr( "Love" ) ); } } else { love->setInteractive(false); love->setIcon( thumbIcon(TomahawkUtils::NotLoved) ); love->setToolTip( tr( "Love" ) ); } #endif//defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK( 5, 2, 0 ) } void TomahawkWindow::onHistoryBackAvailable( bool avail ) { m_backAction->setEnabled( avail ); } void TomahawkWindow::onHistoryForwardAvailable( bool avail ) { m_forwardAction->setEnabled( avail ); } void TomahawkWindow::showSettingsDialog() { if ( m_settingsDialog ) { m_settingsDialog->show(); return; } m_settingsDialog = new SettingsDialog; // This needs to be a QueuedConnection, so that deleteLater() actually works. connect( m_settingsDialog.data(), SIGNAL( finished( bool ) ), m_settingsDialog.data(), SLOT( deleteLater() ), Qt::QueuedConnection ); m_settingsDialog->show(); } void TomahawkWindow::showDiagnosticsDialog() { DiagnosticsDialog win; win.exec(); } void TomahawkWindow::legalInfo() { QDesktopServices::openUrl( QUrl( "http://www.tomahawk-player.org/legal.html" ) ); } void TomahawkWindow::getSupport() { QDesktopServices::openUrl( QUrl( "https://tomahawk.uservoice.com" ) ); } void TomahawkWindow::reportBug() { QDesktopServices::openUrl( QUrl( "https://bugs.tomahawk-player.org" ) ); } void TomahawkWindow::helpTranslate() { QDesktopServices::openUrl( QUrl( "https://www.transifex.com/projects/p/tomahawk/" ) ); } void TomahawkWindow::openLogfile() { #ifdef WIN32 ShellExecuteW( 0, 0, (LPCWSTR)TomahawkUtils::logFilePath().utf16(), 0, 0, SW_SHOWNORMAL ); #else QDesktopServices::openUrl( QUrl::fromLocalFile( TomahawkUtils::logFilePath() ) ); #endif } void TomahawkWindow::updateCollectionManually() { if ( TomahawkSettings::instance()->hasScannerPaths() ) ScanManager::instance()->runNormalScan(); } void TomahawkWindow::rescanCollectionManually() { if ( TomahawkSettings::instance()->hasScannerPaths() ) ScanManager::instance()->runFullRescan(); } void TomahawkWindow::showOfflineSources() { m_sourcetree->showOfflineSources( ActionCollection::instance()->getAction( "showOfflineSources" )->isChecked() ); TomahawkSettings::instance()->setShowOfflineSources( ActionCollection::instance()->getAction( "showOfflineSources" )->isChecked() ); } void TomahawkWindow::fullScreenEntered() { TomahawkSettings::instance()->setFullscreenEnabled( true ); // statusBar()->setSizeGripEnabled( false ); // Since we just disabled the size-grip the entire statusbar will shift a bit to the right // The volume bar would now have no margin to the right screen edge. Prevent that. // QMargins margins = statusBar()->contentsMargins(); // margins.setRight( 24 ); // statusBar()->setContentsMargins( margins ); #if defined( Q_WS_MAC ) ActionCollection::instance()->getAction( "fullscreen" )->setText( tr( "Exit Full Screen" ) ); #endif } void TomahawkWindow::fullScreenExited() { TomahawkSettings::instance()->setFullscreenEnabled( false ); // statusBar()->setSizeGripEnabled( true ); // Since we just enabled the size-grip the entire statusbar will shift a bit to the left // The volume bar would now have too big a margin to the right screen edge. Prevent that. // QMargins margins = statusBar()->contentsMargins(); // margins.setRight( 0 ); // statusBar()->setContentsMargins( margins ); #if defined( Q_WS_MAC ) ActionCollection::instance()->getAction( "fullscreen" )->setText( tr( "Enter Full Screen" ) ); #endif } void TomahawkWindow::loadPlaylist() { LoadPlaylistDialog* diag = new LoadPlaylistDialog( this, Qt::Sheet ); #ifdef Q_OS_MAC connect( diag, SIGNAL( finished( int ) ), this, SLOT( loadPlaylistFinished( int ) ) ); diag->show(); #else QPointer< LoadPlaylistDialog > safe( diag ); int ret = diag->exec(); if ( !safe.isNull() && ret == QDialog::Accepted ) { importPlaylist( safe->url(), safe->autoUpdate() ); } #endif } void TomahawkWindow::loadPlaylistFinished( int ret ) { LoadPlaylistDialog* d = qobject_cast< LoadPlaylistDialog* >( sender() ); Q_ASSERT( d ); if ( ret == QDialog::Accepted ) { importPlaylist( d->url(), d->autoUpdate() ); } d->deleteLater(); } void TomahawkWindow::importPlaylist( const QString& url, bool autoUpdate ) { const QUrl u = QUrl::fromUserInput( url ); const QString ext = u.toString().toLower(); if ( ext.endsWith( "m3u" ) ) { M3uLoader* loader = new M3uLoader( u.toString(), true ); loader->parse(); } else if ( ext.endsWith( "jspf" ) ) { JSPFLoader* loader = new JSPFLoader( true ); connect( loader, SIGNAL( failed() ), SLOT( onJSPFError() ) ); connect( loader, SIGNAL( ok( Tomahawk::playlist_ptr ) ), SLOT( onNewPlaylistOk( Tomahawk::playlist_ptr ) ) ); loader->load( u ); } else { XSPFLoader* loader = new XSPFLoader( true, autoUpdate ); connect( loader, SIGNAL( error( XSPFLoader::XSPFErrorCode ) ), SLOT( onXSPFError( XSPFLoader::XSPFErrorCode ) ) ); connect( loader, SIGNAL( ok( Tomahawk::playlist_ptr ) ), SLOT( onNewPlaylistOk( Tomahawk::playlist_ptr ) ) ); loader->load( u ); } } void TomahawkWindow::onNewPlaylistOk( const Tomahawk::playlist_ptr& pl ) { ViewManager::instance()->show( pl ); } void TomahawkWindow::onXSPFError( XSPFLoader::XSPFErrorCode error ) { QString msg; switch ( error ) { case XSPFLoader::ParseError: msg = tr( "This is not a valid XSPF playlist." ); break; case XSPFLoader::InvalidTrackError: msg = tr( "Some tracks in the playlist do not contain an artist and a title. They will be ignored." ); break; default: return; } JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( msg, 15 ) ); } void TomahawkWindow::onJSPFError() { JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( tr( "Failed to load JSPF playlist"), 15 ) ); } void TomahawkWindow::onAudioEngineError( AudioEngine::AudioErrorCode /* error */ ) { QString msg; #ifdef Q_OS_LINUX msg = tr( "Sorry, there is a problem accessing your audio device or the desired track, current track will be skipped. Make sure you have a suitable Phonon backend and required plugins installed." ); #else msg = tr( "Sorry, there is a problem accessing your audio device or the desired track, current track will be skipped." ); #endif tLog() << msg; JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( msg, 15 ) ); if ( m_audioRetryCounter < 3 ) AudioEngine::instance()->play(); m_audioRetryCounter++; } void TomahawkWindow::createStation() { QString title = tr( "Station" ); bool ok; QString playlistName = QInputDialog( this, Qt::Sheet ).getText( this, tr( "Create New Station" ), tr( "Name:" ), QLineEdit::Normal, title, &ok ); if ( !ok ) return; if ( playlistName.isEmpty() || playlistName == title ) { QList< dynplaylist_ptr > pls = SourceList::instance()->getLocal()->dbCollection()->stations(); QStringList titles; foreach ( const playlist_ptr& pl, pls ) titles << pl->title(); playlistName = title; int i = 2; while ( titles.contains( playlistName ) ) { playlistName = QString( "%1 (%2)" ).arg( title ).arg( i++ ); } } QString info = ""; // FIXME QString creator = ""; // FIXME dynplaylist_ptr playlist = DynamicPlaylist::create( SourceList::instance()->getLocal(), uuid(), playlistName, info, creator, OnDemand, false ); playlist->setMode( OnDemand ); playlist->createNewRevision( uuid(), playlist->currentrevision(), playlist->type(), playlist->generator()->controls() ); ViewManager::instance()->show( playlist ); } void TomahawkWindow::createPlaylist() { QString title = tr( "Playlist" ); bool ok; QString playlistName = QInputDialog( this, Qt::Sheet ).getText( this, tr( "Create New Playlist" ), tr( "Name:" ), QLineEdit::Normal, title, &ok ); if ( !ok ) return; if ( playlistName.isEmpty() || playlistName == title ) { QList< playlist_ptr > pls = SourceList::instance()->getLocal()->dbCollection()->playlists(); QStringList titles; foreach ( const playlist_ptr& pl, pls ) titles << pl->title(); playlistName = title; int i = 2; while ( titles.contains( playlistName ) ) { playlistName = QString( "%1 (%2)" ).arg( title ).arg( i++ ); } } QString info = ""; // FIXME? QString creator = ""; // FIXME? playlist_ptr playlist = Tomahawk::Playlist::create( SourceList::instance()->getLocal(), uuid(), playlistName, info, creator, false, QList< query_ptr>() ); ViewManager::instance()->show( playlist ); } void TomahawkWindow::audioStarted() { m_audioRetryCounter = 0; ActionCollection::instance()->getAction( "playPause" )->setIcon( ImageRegistry::instance()->icon( RESPATH "images/pause.svg" ) ); ActionCollection::instance()->getAction( "playPause" )->setText( tr( "Pause" ) ); ActionCollection::instance()->getAction( "stop" )->setEnabled( true ); #ifdef Q_OS_WIN connect( AudioEngine::instance()->currentTrack()->track().data(), SIGNAL( socialActionsLoaded() ), SLOT( updateWindowsLoveButton() ) ); #endif } void TomahawkWindow::audioFinished() { #ifdef Q_OS_WIN disconnect( AudioEngine::instance()->currentTrack()->track().data(), SIGNAL( socialActionsLoaded() ), this, SLOT( updateWindowsLoveButton() ) ); #endif } void TomahawkWindow::audioPaused() { ActionCollection::instance()->getAction( "playPause" )->setIcon( ImageRegistry::instance()->icon( RESPATH "images/play.svg" ) ); ActionCollection::instance()->getAction( "playPause" )->setText( tr( "&Play" ) ); } void TomahawkWindow::audioStopped() { audioPaused(); ActionCollection::instance()->getAction( "stop" )->setEnabled( false ); m_currentTrack = result_ptr(); setWindowTitle( m_windowTitle ); } void TomahawkWindow::onPlaybackLoading( const Tomahawk::result_ptr result ) { m_currentTrack = result; setWindowTitle( m_windowTitle ); } void TomahawkWindow::setWindowTitle( const QString& title ) { m_windowTitle = title; if ( m_currentTrack.isNull() ) QMainWindow::setWindowTitle( title ); else { QString s = tr( "%1 by %2", "track, artist name" ).arg( m_currentTrack->track()->track(), m_currentTrack->track()->artist() ); QMainWindow::setWindowTitle( tr( "%1 - %2", "current track, some window title" ).arg( s, title ) ); } } void TomahawkWindow::showAboutTomahawk() { QString head, desc; #ifdef QT_DEBUG head = tr( "

Tomahawk %1
(%2)

" ) .arg( TomahawkUtils::appFriendlyVersion() ) .arg( qApp->applicationVersion() ); #else head = tr( "

Tomahawk %1

" ) .arg( TomahawkUtils::appFriendlyVersion() ); #endif const QString copyright( tr( "Copyright 2010 - 2015" ) ); const QString thanksto( tr( "Thanks to:" ) ); desc = QString( "%1
Christian Muehlhaeuser <muesli@tomahawk-player.org>

" "%2 Leo Franchi, Jeff Mitchell, Dominik Schmidt, Jason Herskowitz, Alejandro Wainzinger, Hugo Lindström, Michael Zanetti, Teo Mrnjavac, Christopher Reichert, Uwe L. Korn, Patrick von Reth, Harald Sitter, Syd Lawrence, Jordi Verdú Orts" ) .arg( copyright ) .arg( thanksto ); QMessageBox::about( this, tr( "About Tomahawk" ), head + desc ); } void TomahawkWindow::showWhatsNew_0_8() { ViewManager::instance()->showDynamicPage( Tomahawk::Widgets::WHATSNEW_0_8_VIEWPAGE_NAME ); } void TomahawkWindow::checkForUpdates() { #ifdef Q_OS_MAC Tomahawk::checkForUpdates(); #endif } void TomahawkWindow::onSearch( const QString& search ) { if ( !search.trimmed().isEmpty() ) { if ( search.startsWith( "tomahawk:" ) ) { APP->loadUrl( search ); } else { ViewManager::instance()->show( new SearchWidget( search, this ) ); } } } void TomahawkWindow::onFilterEdited() { onSearch( m_searchWidget->text() ); m_searchWidget->clear(); } void TomahawkWindow::minimize() { if ( isMinimized() ) { showNormal(); } else { showMinimized(); } } void TomahawkWindow::maximize() { if ( isMaximized() ) { showNormal(); } else { showMaximized(); } } void TomahawkWindow::toggleFullscreen() { tDebug() << Q_FUNC_INFO; #if defined( Q_WS_MAC ) Tomahawk::toggleFullscreen(); #endif } void TomahawkWindow::crashNow() { TomahawkUtils::crash(); } void TomahawkWindow::toggleMenuBar() //SLOT { #ifndef Q_OS_MAC if ( menuBar()->isVisible() ) { menuBar()->setVisible( false ); ActionCollection::instance()->getAction( "toggleMenuBar" )->setText( tr( "Show Menu Bar" ) ); m_compactMenuAction->setVisible( true ); } else { m_compactMenuAction->setVisible( false ); ActionCollection::instance()->getAction( "toggleMenuBar" )->setText( tr( "Hide Menu Bar" ) ); menuBar()->setVisible( true ); } balanceToolbar(); saveSettings(); #endif } AudioControls* TomahawkWindow::audioControls() { return m_audioControls; } SourceTreeView* TomahawkWindow::sourceTreeView() const { return m_sourcetree; } tomahawk-player/src/libtomahawk/resolvers/JSResolverHelper.h000664 001750 001750 00000014022 12661705042 025455 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2014, Christian Muehlhaeuser * Copyright 2010-2011, Leo Franchi * Copyright 2013, Teo Mrnjavac * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef JSRESOLVERHELPER_H #define JSRESOLVERHELPER_H #include "DllMacro.h" #include "Typedefs.h" #include "UrlHandler.h" #include "database/fuzzyindex/FuzzyIndex.h" #include "utils/NetworkReply.h" #include #include #include class JSResolver; Q_DECLARE_METATYPE( boost::function< void( QSharedPointer< QIODevice >& ) > ) class DLLEXPORT JSResolverHelper : public QObject { Q_OBJECT public: JSResolverHelper( const QString& scriptPath, JSResolver* parent ); /** * INTERNAL USE ONLY! */ void setResolverConfig( const QVariantMap& config ); /** * Get the instance unique account id for this resolver. * * INTERNAL USE ONLY! */ Q_INVOKABLE QString acountId(); Q_INVOKABLE void addCustomUrlHandler( const QString& protocol, const QString& callbackFuncName, const QString& isAsynchronous = "false" ); Q_INVOKABLE void reportStreamUrl( const QString& qid, const QString& streamUrl ); Q_INVOKABLE void reportStreamUrl( const QString& qid, const QString& streamUrl, const QVariantMap& headers ); /** * Retrieve metadata for a media stream. * * Current suported transport protocols are: * * HTTP * * HTTPS * * This method is asynchronous and will call * Tomahawk.retrievedMetadata(metadataId, metadata, error) * on completion. This method is an internal variant, JavaScript resolvers * are advised to use Tomahawk.retrieveMetadata(url, options, callback). * * INTERNAL USE ONLY! */ Q_INVOKABLE void nativeRetrieveMetadata( int metadataId, const QString& url, const QString& mimetype, int sizehint, const QVariantMap& options ); /** * Native handler for asynchronous HTTP requests. * * This handler shall only be used if we cannot achieve the request with * XMLHttpRequest as that would be more efficient. * Use cases are: * * Referer header: Stripped on MacOS and the specification says it * should be stripped * * INTERNAL USE ONLY! */ Q_INVOKABLE void nativeAsyncRequest( int requestId, const QString& url, const QVariantMap& headers, const QVariantMap& options ); /** * Lucene++ indices for JS resolvers **/ Q_INVOKABLE bool hasFuzzyIndex(); Q_INVOKABLE void createFuzzyIndex( const QVariantList& list ); Q_INVOKABLE void addToFuzzyIndex( const QVariantList& list ); Q_INVOKABLE QVariantList searchFuzzyIndex( const QString& query ); Q_INVOKABLE QVariantList resolveFromFuzzyIndex( const QString& artist, const QString& album, const QString& tracks ); Q_INVOKABLE void deleteFuzzyIndex(); /** * INTERNAL USE ONLY! */ void customIODeviceFactory( const Tomahawk::result_ptr&, const QString& url, boost::function< void( const QString&, QSharedPointer< QIODevice >& ) > callback ); // async public slots: QByteArray readRaw( const QString& fileName ); QString readBase64( const QString& fileName ); QString readCompressed( const QString& fileName ); QString instanceUUID(); QString compress( const QString& data ); QVariantMap resolverData(); void log( const QString& message ); bool fakeEnv() { return false; } void addTrackResults( const QVariantMap& results ); void addArtistResults( const QVariantMap& results ); void addAlbumResults( const QVariantMap& results ); void addAlbumTrackResults( const QVariantMap& results ); void addUrlResult( const QString& url, const QVariantMap& result ); void reportCapabilities( const QVariant& capabilities ); private slots: void gotStreamUrl( IODeviceCallback callback, NetworkReply* reply ); void tracksAdded( const QList& tracks, const Tomahawk::ModelMode, const Tomahawk::collection_ptr& collection ); void pltemplateTracksLoadedForUrl( const QString& url, const Tomahawk::playlisttemplate_ptr& pltemplate ); void nativeAsyncRequestDone( int requestId, NetworkReply* reply ); private: Tomahawk::query_ptr parseTrack( const QVariantMap& track ); void returnStreamUrl( const QString& streamUrl, const QMap& headers, boost::function< void( const QString&, QSharedPointer< QIODevice >& ) > callback ); bool indexDataFromVariant( const QVariantMap& map, struct Tomahawk::IndexData& indexData ); QVariantList searchInFuzzyIndex( const Tomahawk::query_ptr& query ); QVariantMap m_resolverConfig; JSResolver* m_resolver; QString m_scriptPath, m_urlCallback, m_urlTranslator; QHash< QString, boost::function< void( const QString&, QSharedPointer< QIODevice >& ) > > m_streamCallbacks; QHash< QString, boost::function< void( const QString& ) > > m_translatorCallbacks; bool m_urlCallbackIsAsync; QString m_pendingUrl; Tomahawk::album_ptr m_pendingAlbum; }; #endif // JSRESOLVERHELPER_H tomahawk-player/src/libtomahawk/accounts/configstorage/telepathy/000775 001750 001750 00000000000 12661705042 026533 5ustar00stefanstefan000000 000000 tomahawk-player/src/libtomahawk/widgets/HoverControls.h000664 001750 001750 00000002661 12661705042 024516 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2014, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef HOVERCONTROLS_H #define HOVERCONTROLS_H #include #include "DllMacro.h" class QPaintEvent; class DLLEXPORT HoverControls : public QWidget { Q_OBJECT public: explicit HoverControls( QWidget* parent = 0 ); virtual ~HoverControls(); public slots: signals: void play(); protected: virtual void resizeEvent( QResizeEvent* event ); virtual void paintEvent( QPaintEvent* event ); virtual void mouseReleaseEvent( QMouseEvent* event ); virtual void mouseMoveEvent( QMouseEvent* event ); virtual void leaveEvent( QEvent* event ); private: bool m_hovering; }; #endif // HOVERCONTROLS_H tomahawk-player/src/libtomahawk/playlist/dynamic/database/DatabaseControl.h000664 001750 001750 00000004440 12661705042 030334 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef DATABASE_CONTROL_H #define DATABASE_CONTROL_H #include "playlist/dynamic/DynamicControl.h" #include namespace Tomahawk { class DatabaseControl : public DynamicControl { Q_OBJECT public: virtual QWidget* inputField(); virtual QWidget* matchSelector(); virtual QString input() const; virtual QString match() const; virtual QString matchString() const; virtual QString summary() const; virtual void setInput(const QString& input); virtual void setMatch(const QString& match); /// DO NOT USE IF YOU ARE NOT A DBCMD DatabaseControl( const QString& type, const QStringList& typeSelectors, QObject* parent = 0 ); DatabaseControl( const QString& sql, const QString& summary, const QStringList& typeSelectors, QObject* parent = 0 ); QString sql() const; public slots: virtual void setSelectedType ( const QString& type ); private slots: void updateData(); void editingFinished(); void editTimerFired(); private: void updateWidgets(); void updateWidgetsFromData(); // utility void calculateSummary(); QPointer< QWidget > m_input; QPointer< QWidget > m_match; QString m_matchData; QString m_matchString; QString m_summary; QTimer m_editingTimer; QTimer m_delayedEditTimer; // SQL control QString m_sql; QString m_sqlSummary; }; }; #endif tomahawk-player/thirdparty/qxt/qxtweb-standalone/core/qxtnull.cpp000664 001750 001750 00000003562 12661705042 026611 0ustar00stefanstefan000000 000000 #include "qxtnull.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. ** ** *****************************************************************************/ // nothing here tomahawk-player/data/fonts/Roboto-Italic.ttf000664 001750 001750 00000402530 12661705042 022226 0ustar00stefanstefan000000 000000 `GDEF % &nHGPOSLnXGSUB$'>OS/2)Oh`VDMXnvOcmap{lglyf/head@e6hhea a [$$hmtx[Floca,.p'`dmaxpPAH namewǺDopostGgXI %M_< .Ϯ Ls l  L11b33fP [ pyrsf O: "fJ:dmbcpL.5=fhfff/ffoeoffAf+ALp:D;t!;s;T;Sy;&IR ;7;;;`w;`o:+g9zHOpC3eCjK&CteP/ /RwBesI .CQ[n8!QiIRSH#bYV$a0&\nyo{K-(a 8\xDp:z5.[-8#j5o$pUG3I_gzBog[ !>|&Z#R  ![CRh x Dx_h3 lHoC9)o$usV?`o6NoJEgAyOnfQ+g"fExalCZB4%C[AZWBBE6EtITFy0K-!/;/0//`L/B{P/v$V0A064`0LP7N/p/oO?.J,/[N&)I.RhsBRhpBtRoOf0M@  ; ?.:qL4$C$6f-ddu:$B$q_=WPSP 5C-Co$gQ<:1k2L/ h3M;x#jL}Ej\f) "_Qwv9;9y^_^cP~i9fQK5CvMm$yAMi1LpADxpVX 5\"l9dGgL*=JEE3 mdEOz=t JJKp(kk/ 6 8u$hWKoC-CD%D%f^warDi9AS+?d beCzGs$AtFC&Ee5fpf|!;`fC0Z.?/?/n=ts;s;s;s;&I&I&I&I;`w`w`w`w`wggggC3C3C3C3C3C3C3C&C&C&C&C....RwBwBwBwBwBQ[Q[Q[Q[C3C3C3tCtCtCtC!;Ks;&Cs;&Cs;&Cs;&Cs;&CSyeSyeSyeSye;P&I&I&I.&}o&IxI/R  ; 7;/7;7;/7;/;R;R;RR`wwB`wwB`wwB:::+ .+ .+ .+ .+ .CCCgQ[gQ[gQ[gQ[gQ[gQ[Ka p:ggmllllllldG****JJJJJdEdEdEdEtllldGdGdGdGgLLLL*X*=JJJ333    mmdEdEdEdEdEdEtt   cqwtG ;s;;&I;;;`w;&IlH9)o$Eg[-wBongEgwBEgnfs;[C+&I&IR D;;[Cs;C;;`wD;tC3&C/wBeC&CK- ./.;/dCt ;C3`tdts;C&C/hyO-B w%!tC&I&IC3C3Ks;&CgQ!>!>%!C/C/`wwBRhsBRhsBEt64ZB{E0jKC3C3C3C3C3C3C3C3C3C3C3C3s;&Cs;&Cs;&Cs;&Cs;&Cs;&Cs;&Cs;&C&I.&`wwB`wwB`wwB`wwB`wwB`wwB`wwB_gzB_gzB_gzB_gzB_gzBgQ[gQ[og[og[og[og[og[KD;/;/`ZB{ZB{[CK- P++[KX 4C/;/;0]9)T  C   ! "!#"$#%$&%'&(')'*(+),*-+.,/-0.1/2031425364758697:8;9<:=:>;?<@=A>B?C@DAEBFCGDHEIFJGKHLIMJNKOLPMQMRNSOTPUQVRWSXTYUZV[W\X]Y^Z_[`\a]b^c_d`e`fagbhcidjekflgmhniojpkqlrmsntoupvqwrxsyszt{u|v}w~xyz{|}~& 0  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`   ! "$#%')(*+-,./0213547689lobcgnumi}shpetjy~alkzpbcijef;tyrsdmvgkq nwfl !~%'08@S_g~7Y #%/EOboy?M   " & 0 3 : < D t  !!!!"!&!.!^"""""""+"H"`"e% "&(19AT`h7Y #&0FPcpz>M   % 0 2 9 < D t  !!!!"!&!.!["""""""+"H"`"d%,&E@<:8602uxza@dEi) m VMLJA/9'0uhf[ߏP$߁ާutmj^B+( ...:\h`DLh*:0   !"#$%0123456789:ijklmnwxyz{|}~ &   !"#$%&'()+,*-n345678:o;<=>?@9ABCDEFGHIJKLMNOPpQRSTUVWqXrstuwv#$ %&'()*+xy,-./ 012345z{  |}'(  6789:;<~=>%&?@!"#$ABCDE)*F+bc-,wydlmfl !~%'08@S_g~7Y #%/EOboy?M   " & 0 3 : < D t  !!!!"!&!.!^"""""""+"H"`"e% "&(19AT`h7Y #&0FPcpz>M   % 0 2 9 < D t  !!!!"!&!.!["""""""+"H"`"d%,&E@<:8602uxza@dEi) m VMLJA/9'0uhf[ߏP$߁ާutmj^B+( ...:\h`DLh*:0   !"#$%0123456789:ijklmnwxyz{|}~ &   !"#$%&'()+,*-n345678:o;<=>?@9ABCDEFGHIJKLMNOPpQRSTUVWqXrstuwv#$ %&'()*+xy,-./ 012345z{  |}'(  6789:;<~=>%&?@!"#$ABCDE)*F+bc-,wydlmVF6Zv.j0jv.x*DR| < n  ` v  0 F T j ~ D  J :Zh*t@n2t(rDZ$$$\.ThDn2&t:|PttXn ( Z p ! !*!8!t!|!!"""0"D"d""#N#$$,$$%@%l%%&2&&'"'v''(&(x()))*.*j*++H+z+++,,,,,,-P-l---. .F.x./&/\/00$0V00011@1^1x112$2>2h22233>3344d44585r556&6d67 7,7P7~778@889T9::h::; ;z<<= =f==>>$>L>^>p?@??@@d@|@@@A"ALAnAAABBkFkNkkll$l\lhltlllllllllllmmmm(m4m@mLmXmdmpm|mmmmmmmmmmnn nn$n0npJpVpbpnpzppppq@qLqXqdqpq|qqqqqqqqqqrr rrFrrrrrrrrrrss ss$s0s{J{V{b{n{z{{{{{{{{{{{{|| |||"|*|2|:|B|J|V|b|n|z||||||||||||}}}}t}|}}}}}}}}}}}}}}~~ ~~~$~,~8~@~H~~~~~~~~~~~~ ,8DPX`lx$0n~y r "###73#7!3333#3##3#݁5fb``f#bJ0-C6.'.7>?3#6.'&#7.73>"@U-J\/AjT'(Vq@ ,O?8_G,!?W0J[.GsW"#Y~O 6YA:jT8~6S>.LfWWf? Y}O4lX9!?Z79T=-KeW\d; NvU;iN.=\0+AW[>'.7>?6.'&>'.7>?6.'&'4UpC?cC4TpB?dC! 3&'>.  3&'?.5TqB?dC 4TpB@cD  3&'>.  3%'?.cqc@pR.1Rl=M?oR.1Ql=!?1 2A$N!?3 2B$@pR-0Rl=N@oR.1Rl=!@23B$O!?32B$HhG:,=R>7.7>>73#''.>77>76.#"A?^t9*>gS@rS.(?O+-9 VRISjYo;(LGB. A6&=[3 n7,'8 +G4!JwaR$%MOT-R`3,NnC7^ODXCPy\qAE8h) 6?H)9\B$ <;7I+4>&!7'!9J!#73vL=yfm*l>7.675Mg_nrH 0O<-OsP. Kb/|RH6p7W)6k'>?>.'7#4Ng^mrH 0O;/OsO. Ib/sRG6p7Vc_%73% 'yB-A.ǘsZq[k[Y!aL4 !!#!7!3wPPvJ 4̯ 7'>?3vUh4LHmCK@Q!7!%5 74676.5>11@@0)^1A>11=(#33`}-h!;'.6?>'>.'&>7 &:Rq]Y~T. ! &;Sq]YT.  ,K:>`H4$* ,J:?`H4$nO{[19_}HOyX07]{H!*ab\I-%CZfk2(ce_L0(E^il1T!#7%3\} ̈$$)7>76.'&>!a)]R<4Q6FpS4 UfUh5B`q5B )[fp>3[D)+OoBfv?5bYNt3X/@2>76.'&>'.7>76./w=qZ;2R9;dM2 U[Zc/2K`43I. UcZk79W9BmR3@`<2 AaA7[B%#C]9]j7:i['.'3>76.'&x6z?dV Po[c67T:IrR04ZBIr8ӫr RZjJ=kX6[D(8`}B:oW71+o#:#&>'.?>3&>76.vg#B^bT  MmJv[>#  >^}2\OA 0H1GnN. /TGiBDSWfP0Qk{AWqӹn=p6K*F%WXR?(;a|?6s`> #!7!z>A(#7K'.7>7.7>6.'&>6.'&>!-F\4^[VaYk71Pg9]D''Ig0VC('Ga71VA''F_#:7>7'.7>+7>?6.'&}` IQX0bS MnPzX9 o93\PB 0G2HoN. -SC~q"9(UVfS3VsCCހ8J*I%XZTB)=d~?5ubA+F&sF'xs AO75!&]${zp!7!!7!+e+.:G 77 !$'5>7>76.'&>>76.A"6J0$J=,)C00T@- MvUSY*;Wi5=? :0.=>7>.'&67#.7>>747&'& '9MbxH)G7%/*CX^`*76&'%2>76.'%;Zp:-G[34J-[e[)@rZ;nv9lW9;R4t1'.?>#.'&>7is\kF' inq;?fLIzcM9& $@aDTbEq~C9`PoKiEwY5(Idt?5tqgP1+U{L; 3#2>?6.';Ҁ/ |ƹwY LpsVfQqZ`\; !!!!!!Z}yCQd,; !#!!!plPVQ}y1%'.?>#.'&67!7!y,|H`sM+ ehuC'EfCrsC*HhG]G<>R18aUrp@tf@jK+\bu9zujP1,>G;w !#!#3!3zu9umm_rI!#3 D3'.73>7Uiae/4U'>.'&>7g`mH' 7TqcamH% %DfHK{eM9& &DeGL}dM8$ u;eS\Ya5;eS8{ukQ2*Miy@_7zvkS3*LiyA;##%2>76.'%Zcbt: `lDF~a@ @c?:'>.'&>7 5VyQً%I&`mG& 7TqcanG& %DfHJ|eM9& %DeGL|dM8$ \2q76.'%fdu: 8YuCBzaA Ad?M7kjK|dK $GiEAdD$+;6.'.7>#6&'&'.7>p/Tk4KtCa\azA;p[>3Tg.Qs@d^bL+QrC:u`AwBY=)Fe\df2?rd<\@7eslt4 AjKOcA 'rDHm%GxX3.W}Ma 3#3>OPA7373#'#3Ds ?tƫ~*0rP-dt+ 3 # # 3SJC-%+72 3#3cs]`e* 7!!7!7!# y#3!!4px3#;z!!73#pHO #3# | )i!7!z+#3+&3P,=!&47'.7>376.'&>%>?'"B\DvV.aY .I0*RE2 YHR\+S ,.ULA'6zjK,A8=K-RrFgU%V/M8+A+QyQ'3^T 7n6,<$-QB)A//'.'#3>#>.'&>7$:OfLY2% m?_InN0 0I40RG<QpUSxR/ B}lP-FK~FH0Qkx<(XVM<$-@%)MZHtFCQ+%>?'.?>#4.'&1[J5 W~Pr]"  TvU`45N4S}W3 .^7O/Q`4\f+mǖW3#7'.73>7.'&S%;QiMS3h?_GkN3 0I3.OE;VoNTzT1 C}mP-?C5tEE1Rkw}:'XTN;%*<#GOHtGCP!-.?>!67&76.od) +D]sMpS > 9dI\2.'"3#w >eV!@ 00L8%rR`3 8L-rUR(Q+?>73'.'7>?'.7367.'&T $;PhMW2$Ui6j`Sg.TEqU9 @[GmN2 0I3[3UnNTzT0 C~mO-DHyex@/F-oDO)LlA`@B1Qkx|;'WUM<$ZGHOHuG>#6.'&#3qAbWsBvw %E6/RG= GR@kN;1T?%0@%/3#3>76&㴼:/';//<:/>'/<:G#'.'72>7>76. .NpI;)$6%90''/<&  #373 #@ oVjp`3/3#3 _P,>>#6.'&#6.'&#Bb-UI7FoXyG wv *J83]K5 {x+I6Z/;yFH*B-R^>iQ94V=""#6.'&#BdWsBvw %E6/RG=;HV@kN;1T?%/A$:BP1>'.73>?6.'&P -F_uPrb% Yvqb&  5M4S[7  6N4TZ6  I}iK)_fnɚY]f*ZVM;$GtH*[WO<%IvH`P-'.'#7>#>.'&>7$9OgLU6aAaInN0 2J3X3[oNS{V1 B}lP-;ByFH0Qkx<(XVM<$SDGKIvGI`%Q/>73#'.73>7.'&S&;QiNT3!b?XHnO3 1I4+LC;^ mKT|V1  D}lO,>Cn&<>1Qjx}<(XVO<&'6!CJIwGR&'"#7>2*+[03X*RK:~CR .O?6.'.7>'6.'&'.73>">M$<{b<NxHLd84G*'OB- +11>{b9Q|LOm?!9O0(TH3%.=) ,EfJPzR).W}P*D0)>++! /IiKU}Q&0ZU1K3&>C@3#3267#.7#73.q$,AC>S0n.@_-#1Pg7[:%'.73673#B^XuCtu >4a4kD=CoOB,UD,PPn:%3#3ߊԲ=:: 3##33YpzuB:2:&: 3 ## 3&Nųl,G: 3'.'73>?3CVk@7  1N>2@)5cL- /C*z#: 7!!7!7!a8?(.?6.'7>?>7QuH  &D5pyд=Y>& h`$0A8YwS1S>&~lϺ4uE[m=l3FOW+6j]K!#3j;(>?>7.?6.'7t=Z?& pf)4A8-PsF  %D5pyѴE[m=p1DOX-6j]JqYwR2R>%lλ4i%)'.'.#&>32>74X|O*KD>&*.-A-5Y}N*KD=&*/-E0 GjA&2#"9I&Gg=&3#%76:0';/.=O/>'0;:R &1%>?#7.?>?3#4.'&1[J5 IkG(+RqB  Guc')@fF%5N4S}W3 .^7O/I|]; gX+caFc|G1VB'DpH*@qJ+%!7>?#73>'6.'&! 3.C$0  ScW[+/L5#.Og7#7%'.''7.7>7'7>7>76.'&Ti^Em XHurQdZDo  QBs !TY`U SX`Vn76.'.7>7.7>#6.'&%.'>76.:%=P.8(bZey?$IjA5o^B+!'M&Kf );CG!&K%%B4#,=CF5WG78Ne[)8lkCgH&5T<(?0% A``4WF89MBlT=&:nf=fI*4U=@R7"A` `Q+B2$  #1?)(@3&R >76&%>76&://=;0/;:b)Ea'.?>'6&'&>7%>76.'&>'.:6Y{LQtG :`UHpK#D[7R:" "@4/F2/ 1QoSXz\= 0RnSXz]=| Ekhcf? Dkhcf?UJwQ*CkJtNk>-QsHUb,I^0v+YJ10E,~MvX3/VxSMuW2.VwScf7376.'&'>%26?#t,e=2S: Cg}`@1/S$]B9*'$)-9R3H\660$18 =Y:)He=/Y.z) /#" Y&rr:w #!7!{/'wa7Xe>'.7>76.'&##&6?6&/>76.'#k Ekhce@ Dkhde@{ 1QoTXy]< 1QoSXy]=5:iO+(6$0D=3#):!cf7'.732>76.#"+I`63S:*H_62U< { )2' )3'5bK-+FZ05aI,)DY3,"%2.$'3& !!#!7!3!7!aAAvAq+Wb\ !7>76&'&>!<0, 50BP6Sl>4_F(,@M#{t )0606K>>bE$9V90UK@n<>76.#"#>3'.5332>76./WN7/ *1M5Qe62aK,,7:::[p77aJ*"-6-%2We,% -09S85R9$9,# X??[::W:' ,) 3#Ξ`%:673#7#.'#g$:,\05W3 [Hfk1 \lDyeo|BPiK>76.=2*@1)1A)1?)K37>76.'7?@?_r5;5&,4.7U@AS1l ) !p#7%3hdU8qm+>'.7>?6.'&?'.7>7>7.5>76A!6J.#G<+,G11S@, LuUT],:Uh4<< ;/(<..=@hZR+!BIU3/N9!6M/V_14`VH}l`,3zL@/>'/;9x)!#!!!!!!/Rb?>G`a)x(c  7   ({yeROPrNrP #4C'.'#7.?>73.'&%>'>7gX6$/ 7Tqcn9>!nSK{eM9&  d!Y;L}dM8$ u1*4{F\Ya5J?1ov{<],`_\(q3@*Miy@FF$*LiyA8[#%#2>76.'3]p9 ^e7i_Aw^? >];:jakm7C%GhD<`E'A3#>'.'72>76.7>76.'&ӵ CndK|V*$*' 9F<&EqQJ?;0p;-TE/";F<&7B9$<*@\>$W[zE-TyN*HC?@E&/OIGLV4VX,"("*-F01QJGMY7;`\_:'F42Sk7WP@Q].''.7>376.'&'>>!67%>?'"&!76.k5h]OWHtQ)YT  'D2.XG2 UN.\RBHliX B :fNZJ5&\cf)OKF+0dT;-@EkQ9  /R+D/_W-QtI`O"V-R>&1I/U|P%'>-K\Q`vDg@.''7.'77>7.'&>7]: ,CZqMcf. NnL684;76.6!=2*?11>=2*@1)X71A)1><1A)1>(:y)#1@>73'.'#7.73&'&%>.'>7P -F_uP:a)h%3Yv6[&h'6  /FTZ6 PI3 S[7  I}iK)'bow=nɚY'bpz>$KKF IvH "IHD_ GtH`.>'.'#3>.'&>7?ZInN0$9OgLU6aH c 2J3X3[oNS{V1 ?#3a 8[R;1)<( ƻM`5  #9H#Trp$HP'>'"&'732>76.'&#?i\yE } 7[~Q :3);)}&K=U5;P`FtRL^4  6E#6_I+G@:U_0!!!'.7>3!!!67.'&~ZGG|q,0g؃JI1CQd6m62c2cnE0'B\ ds0{ڤ^,HHzZ4jaT?&GQ1KW>>!67'.''.73>?64.'&&76.UUx8k]K%Wcn>hX L 2]JXE=&Zdh38k^LNςq_# /]NS}X3 4L48\K:*RAfN90Q m̜\6Q5.Q;!Q_z=iE75$2 5N4cs_e>sMHuG)[WO=%!;P[c/7Xm45bK/3 3>2.'"3 :^T&H$!+0L7#Ob6!9N-TIG/+#'.'732>7#737>2.#"3ĝ 5Y~Q80);( 8]S%G#00G3L^4  $:G#rNa7 8K,rg:#A'.?>>?'>.'&>7g`mH' 7Tqcv9?X;# &%DfHK{eM9& &DeGL}dM8$ u;eS\Ya5UF+Hc9^m8{ukQ2*Miy@_7zvkS3*LiyAB!;>>?'.73>?6.'&P -F_uPe34D+Yvqb&  5M4S[7  6N4TZ6  I}iK)L?(@T0DOnɚY]f*ZVM;$GtH*[WO<%IvHg#>?'.73>7$73#B^XuCtu >4a44D,1V|TkD=CoOB,UD,PP6N5VzQ) G:'.'732>7 7[~R90)<):nL_4  $9H#>O+'.?!6.'&'>>7%9q`$  TmhX  1]JXE=%[dhL@fN90RN]e-dÚ]O`y=iF84~$3 47Xm45bL/d/53dqj&?#5/nΖV K'.767K3Rm?=dF&7FGR ?eF$'Ec=@TUA4676.:0';/'T/>'/<&Q'>32#".732>76.#"#7A:)A2DS)@,6Qb//8A&? y+0C(:^K:#".'>272>7!8P5&>;=$&z"9Q5%><=$'/YD) &(/ZF* %)3#3#Ƴ4/j5%4>32#".732>76.#"0?$":*.=$";-T%  $?0,;"#>.*:$/ !#3sK'U3#| .5s7>76.#73/# 04)`P4.76.:/';/'/>'/<&!A?3#t?!3#>76.%>76&:/('/<:iKvC!#!Xe3!%!5#2P&h=!7!'.?>'>.'&>7 g`mH' 7TqcamH% %DfHK{eM9& &DeGL}dM8$ u;eS\Ya5;eS8{ukQ2*Miy@_7zvkS3*LiyAq #3#yP  7!!!!!!(r#8y?Dp!#!#!sI/ !!7 7!!83T͝JG_j"1=3'##7.7>37 >76.'#&3qy7 6UrQ$$)UQKB6% kz '4;j[K9# EsRZoF DsRWmP~eF%*.XnynZyΕS6O^h4Ne=:jWNe=>73#.733Rc@ ZYe{DEnd& XY 5`J GkQ{ǑY lekElO 9%>?>.'&!?&?>7!{a[4 $>]Afj@  /L;"P< 5Ql[WjH)  6PkC)tWt4mi^G,RXt/oso^FqSxW/5ZyMqT5H$Q#=67'.''.?>7>7.'&$ 5=LI@iiM $9PhO[-/~ 0G21QE:L %3C+:^I7&:1." ZGNY`YCsU0PP(SQG7"3E'&C2$BXbg/E@'.'#>>76.'&3>76./7Sc1'=Q-3I-RfM$&D]6,,OoA1aN2`: 3#3PU$2C-E>."'.?>?.>?6.'&KIqHFAkGCmG6}oO 9QZ_+)N@">?'.7>7.7>'6.'"/_P7(CR&/_Q; [SGtE)AT04&VNImA$76./.7>7!7<k8lX? )Js+R?#(;G#\("$*R Igz;F^9vRKi'-F7.WNCY+/3!*W;<$aP>#6.'&#Bd]zE 'K<]1;JT@nTR6^F)\K:s%.?'.6?>!7>.'&!>7 &=Tq\X|S.  '=Tq[X}S/ /  ,J8=_H5&,I8=`I5% La6>eEL^4S0:-! 0Of8&%267".'#'.'&#7>Yq   #2E0 }c74   +XV6K,N#?wI.#"'"'>76./.7>7.7>5k71n`E +Of3RxO 6S4e*R@$(:G"d+$%,3Xa/?dJ*H3/Mdpv9{-L9>Q0$P}Y8^J6 -E4.VLCW*.5  HkbRdG8FT0BhO8"'`:#267'.7!##7!n$+ C">S0m|)p-! 0Of8_`P4'.'#'>'>.'&>7 $8Nd}JU6a'D^pEOyV6/I61QB3&3nMPxT2 AvfI):BCvcG'1So~C'\^WD* 8LTX(FK@lFNR7'6.'&'>76.'.?>rW]-1N65XI9) HkB,WD'(:F"\("$,bf-  UO3>?6.'&K9  ZnLx\?% Xt5Z4K4S~W4  1^MQ{W4 XshR-MhxCn’T)ZVN<$HuH?mHCoF:!3267'.7!7!p ,O]AR- knh.% 23Sk8g<!>76''.7m>97]L<,"&  *CZsRbL m:m+eX<&DZbf-}}IqR-J{YA"%=#3.767>#>76.tj*V[w ?lM{.F]5if+ j|U#RoI "4I0'bq ZKuKpO6U<`_ʐT 3c GlU#UWQ=!&O((<#>76'#.73YWpH%'hقTUz_RR 2eT:KKrW{{іY 9mrL|Zf<D>73>76''.''.6767#H=0  "<./J9*0/DA4O<*-/ 3Ib~N7^H1 EVg?KkH(mU9>~HU][K03ENQ%,/uhG*H]b`&}|@uW2#>W65Y@#7Yt~:wQi0?'.77>?.?>>76.'&c%I&Lzia`( 66/T?DeG+ `e/;^NPqD;"B n~;0-*=*r b{DI{]N8jR4/Sn<[bK^5?fI P%X"K?* 7F$g# >2.#&#.'&'63--1,B2# (F31!kH{+1C(fA:"H#'.''.67>77!6&'%>?3>$ .AZvK8^H1 DUh?Gd@!  A3u  2O 1).J9*''%;,/F4#\\;w[5#>X65YA#:]w~}5eX[[WdP]_N34FNP% JKG7"-L`b[r#!>7>76.'&#!7!VDHgu9 ]m Iy\9CfBKHzmI8=rkuo7'LpKChG&Yx4'.?>#.'&!%>7is\kF'  8Tqbnq;?gLnrH0 'BaBTbDq~C9aPY[2KiEwY5X\ 4tqgO1+UzM"/'!!'#73>7>76.'ebHao6 ^i]&:Txk6#MrS7& xdIEx^= <]=>qdms;WrC;c:-*NoE76.'njH]s; _ftIt[I@w^> ?^99w7gclm7d#EfD<[?!!>#6.'&#!7!QPSjj. KL6`HUSsHO:qm9EkJ&=Bn 3!3!#!?N>??Pg4!'!!2>76.'zXKG`q9 _ge`JCx]= >^;L8idnp8'JkE=^B#z#!#>7!3%!> ?YkPpP4O3JeF#eBbJ`¸Huu#### 33333 #sth nnRhh xxG %@2>76.'&>'.7>76./7i?w`A%Kf9:oZ? a^]{C 2Nb5g_4Sm|D`xA%Hd;AjI "Hi=4<`E?\< ?\<df32dg>dQ>-oK{aH.9le=bE&!DhGC_<Cn 3##3PVe#!'#732>7e k$6Nqg9$IjM3" PQvE>f5@ 3'.'732>?3=!I[rH6,'<0':dLB373"32>76.'x9 r'(y~8 r *d{N L\d{M K\]t۠Y]u۟ZCwaUsFiDw`UsFAm 3!33#!>j>_9#'.73679o]akj- LK7`Hb_}P\;rn8EkJ'B8 !3!3!PB8!3!33#!i=+ _x!'!!2>76.'HgG`q9 _gt`JCx]= >^;8idnp8'JkE=^B#E'!3 2>76.'#3G`q9 _g`JCx]= >^;^8idnp8'JkE=^B#A6y'!3 2>76.'G`q9 _g`JCx]= >^;^8idnp8'JkE=^B#t5>77!>.'&>'.'0 DlOHu^I7' ) (EeEQ_Aes^oJ)  6Rn`ow@JwU/*Ies{<8vpeN/0Y{InF7`SV\3G~lI!?'.7##33>'>.'&>7g\lI*nt8TqbamH% %DfHJ|eM9& &DeGL}dM8$ u7\}PdW^4;eS8{ukQ2*Miy@_7zvkS3*LiyA!!#.7>3'"cyrh bk?a?dF~cA7p:ro7P?gJ+>#HmFP.F'.?>7>73>&>?6.f]& Tsne(  +C]{`)XL9 AhNTiP%S\d JwV4 "5L3PzV2 8`S]l’TZeEXtQ!90ZoD%@`N&?-7_}D*WSI8"AmF?w^;0:%23' 2>76.'%2>76./0.`ZO9%:J((@+R~M9+XI36G%*_T<&BR$: *@V9/K:, .>K*ZyJ(A2,:# "?30: -:!#!g_:>:7>7!3#!#%!- I{6:####33333 #QRwNOs_H**:@@!O>2>76.#&>'.7>76./7*O@+2E%*QD1 QzLGc8$8F'KERNMh; 9M++XI32F(u&?/*;',A*RzP'$JsP.J%/7: 3##3|: :/W: ##333 #QPn[3:69:#!'#732>79B ,@]U8%7O8%`:C|]5.J^eb)0~: %3## #3漵,~ЎC,:/6: !#!#3!3zQQQR2:+/7:!#!#!{L_:`:!#!7!n\L`<,@T>36'&'#'".7%>.'&>767.#&U 3Ib}O&J#WYMQJiF'"5J`xIWLPQIGEfF,,*C374NrM,  LG4/9YD0"AvX2*#7[w7?ufJ*!T.Nfqv7"#Z\XF, DnC 5|lI  'CZbd-/7: 3!33#!뵡ᢵ~d8:]](A{;!#'.736739K76&'L]1LyT5[G1WC- WW.V~QY]/i5M3Wk0:'!3 >76&'#3_L]1LyT5[G1WC- WW.V~QY]/:i5M3Wk:0:'!3 >76&'_L]1LyT5[G1WC- WW.V~QY]/:i5M3Wk4P/&>'.7>7!7!6.71YI2 T}OJuX=#  TrT_01M3JpR8p1U9O/Qb4-LgvA+iřZ=iS/VB)>d~=6r]=0Q53>'.7##3>?6.'&Q]lrb% Yvjc-RN 5M4S[7  6N4TZ6 oaL_fnɚYS_(:*ZVM;$GtH*[WO<%IvH;#!#.7>3'I_UPQ|O*=&"?-WH1:[*]XS)'>*g/GG5!>3'.'732>?#6.'&##7373!0AbWsB+Z 7ZQ;3)<(w %E6/RG=ҡ!GR@kNL^5 $9H#Y1T?%0@%NQ-%>?'.?>#4.'&!!1[J5 W~Pr]"  TvU`45N4KtU8{1Z7O/Q`4\f+mǖW7>76.'HI`4NzP44 .C`V8%8S<'MM@.VE./B':d*PxOWV+F{Z4.Kagf*2J1*>*/K:#!3'!!#3>76.'YGHI`4NzP5[[4@.VD..C'c*PwOWW* :2J1*?)!!>#6.'&##7373!1AbWsBvw %E6/RG=Ӌ -GR@kN;1T?%0@%/7: !3!#!ᢵ?>:]d:o.'.''.73>73>7ٴ Lxa/ZM>CqZT" #B573>7z CjX)MD6($>R-.#!'!#733!>76.':O\-KxX5998ZM4YD+(A.:3]R[d4:EE!;T5+M9#J;3>#.'&!!>?'.?##3;Up_nq;?fLisH$@aDTbEis\kF' vAUxW.KiEwY5S[5tqgP1+U{Lq~C9`PW,Q33>#4.'&!!>?'.7##3LYnU`45N4KtU8[1ZH1[J5 W~Pk`)PhdM733q ih, <=3\FymrKwV6 <=Xr-hw9ojrChG&%x FlLqtl3{) :!%37>7!#76./#/&!)׼^L (QC5OT:JlL-  HsY=fL-P &ImG\GIZ+/732#6./#/&#67!#3!3 ih, <=3\FymrKwV6 <=busp=hw9ojrChG&%x FlLqjk|{.:%+!767!#3!!#76./#'#33! ZNS^L (QC5OT^i;:"HsY=fL-P \GH BK2>76.'%7#'.7>32>76./7?#5?xaA ?[4Ur>4Qe7kd[d5;1"$2Q-O; ?c~A-@u\="Hi?nΖ6&GhBDb? H2DM2>76.'%7#'.7>;2>76./7?#5+cW>%?L!CpC*?O(&?,XN/<0"$1Q,P:!?c}A)+^Q:,K[&=nΖh $B5,7 !GqP0K:,+9G*[~O$ -""7+"x?N\5Jf>(D24>!  h(5'.?>&!>.>7!g`mH' 7TqcamH% +muG&CelrG(Dbu;eS\Ya5;eSZ^8vrfM0pU]7snbK.BP"->'.7>7!&!6.P -F_uPrb% Yvqb& KvX;4[HsW<E5Z I}iK)_fnɚY]fx9a}A;xa?36]y?:t]<K7>3#&#3 <}:NhE3 "5) §nw":kR0(5x<P7>32.#"#3,2DU6 5$  % O2Wi-WE* ":hs5#A#3#3'.?>'>.'&>7gCԵE g`mH' 7TqcamH% %DfHK{eM9& &DeGL}dM8$ >u;eS\Ya5;eS8{ukQ2*Miy@_7zvkS3*LiyAB9#3#3>'.73>?6.'&@@ -F_uPrb% Yvqb&  5M4S[7  6N4TZ6 Fpo(I}iK)_fnɚY]f*ZVM;$GtH*[WO<%IvHtVBZf'.''.7>7>73>76.''.#"'7>>?<_P \ Frf/\QBBn`O [Iuh>\?& \B:Y>#\@9 ((KIGGI%6E } m3 "0:M|W]}G*@-VYM}W;dxD3Sj7-eW;(Ga8S4]H+5Vk4=/cT9Ł ;5$lr'/( F,`e#;2(RDZf'.''.7>7>?3>7>.''.#&'7>>?iUvE* =e]*QH;3 "/9DGrNTrA$8'JMGrM"Zm>.J]/995)">S/,P=&1M],$'WK3ʁ(/':5$mq'/(!E-_e";3)o67!!#7'.''.73>73>7 Lxa/ZM>CqZT" #B573>7z CjX)MD6($>R-f*#.?>#6.'&SEnW' 6SlYnv:DmL@m\I7% (&=Y<{nzeQvT.GmHxW2%B[hq80if\G-MQ&#.?>'6.'&F[wB  TrT^01L3OzV4  /G1nrlS+iřZ>iR/VB)GsC*'VTM<&@>'#'7'73',R%R TUrrutLaq'7!7* &#}l >'76&'&#7<@ust>eoz*2;stw>0 (/'of'06&/(W73%Mrr9;QW'?3GPHy#5GWgy>'6.#&>2'6.#">3'6.#">'6.#">'6.#&>'6.#&>3'6.#">'6.#" q[Xhk04  qZ,G3l$"7J-Xhl04 z"7J-Xhl04 2 rZXil/5 B r[Xhk04 "7I-Xil04 & qYXjm03 YefX%;*Xf2F,%$+F1eX%:*+G1eX%:*YeeX%<)YeeX%;*+F1eX%:*WfeX%:*c "'#'37%%77%'%'7: d~ e 7 @K=h>5h^C]D:`Q \E< [DMNO=>/.q#!'!#7373!>76.'aO\-KxX5""83M4YD+(A.3]R[d4E!;T5+M9#:7##%2>76.'%trcbt: `lDF~a@ @c?7^`e#>.'&>7tt\$9OgLU6aAaInN0 2J3X3[oNS{V1 `_B}lP-;ByFH0Qkx<(XVM<$SDGKIvG4 #!#!3~X;P$w!#!3d8_:=C!7>76./#!XQ~6 P}X}S. K^te/T~xɓSEqNXk>a$z:!3'>76.'##!^1des7 MxU!76.'##!#!j~6 P}X}S. K^tI/@T~xɓSEqNXk>b$>:3'>76.'##!#!fy< MvU#767&?>>?>.'&`XKR_vR.  +CYsU9[G6'.JiF#A"aI# %:NdzJGjL0&~4h6 $F6p'":,/J:+"% !9bTJy[6,H^gh.9zuiP0u>{nP-4Tnz}9d 3Bxj*OOTRB*$=QWV#_VR<P.''.?>767.?>>?>.'&MH>EN{[=" C  (*/"  "4UsC_W;!7!#!33#!bᢵ~d8](A9#3#'.7367ϒzo]akj- LK7`Hb_}>P\;rn8EkJ';%#3#'.736733cK#6.'&o]akk- LK7`Hb_}:rm9EkJ&I0A.?.77>!67%7>.'&m̓4Hd<1(9PfZ]f@   Lf^U"(`fg$?[>AjWD3& cgEd|E(H=0 MwW/8_Oq^ZN.%%W"3ibVA'&C[io6HQ&2.?.7>!67&76.Mod) aY(]bpS > 9dI\76.'%mx_rd& Q} W|T. J~]q blx˓UDpNVl>-V:'>76./#333lQ}R% MwR$9dM2"IlAPQP`KoRXrM2Ja=FfD"3:6CHm!3'"&'732>7!#rs 7\R;1*<)zKonM_5  #9H#$H+:!3'"&'732>7!#RR 7Z~Q:0*<)\P:+mM]4  #:G$'2:Q"0'.?76.'&'>>7ك/ :Zvav'TpaY#+ejmwEuaO>.Ks]Xa6m| c_/&&'D^lu:#YzI<u$!7!'.73>76&/7vR#[^* ^e^i5:W:?t\;$Ep[jo8?o_7_H+$GgAs/:$!7!'.73>76&/7&L'Z]* ^d^i5:Y:@v]= CnZip8?o^7aI,%HhBF&B&ʹ@F:&M&ʛ13%.7>3%"cao6 ^hcCx]< ;\>s=P>oemp:)8&IjE33>76&''%%"ap7 ^h`dK=aF,  MzbbCw]= ;\>=oenq9=-Ke9QPQQ_t@8&HkE3>7>'7'.''.7.'&67>7U#8OhPQ0m6+7[I8) Sx0[K8 AoFlN2iG:]I7&+XKW3CtV0@BN@$J=( ;Q[a.ddddnʛ[*E3Wb-Kdqz;B?C$?V`f/<~iESBD6./72>76.'%7>7>'3'.7-L8E{a? ?_:O^s< 0Md94B$ /#3SA1"  (>TkNKoH u3[F+>eK>Z:1bgBiT@HXf4F:/#=SZ\(ddddE|kN+/TwJh:<%>76''.?6./72>76.'%7",@aE) ( Jwd:]@ .?%+ZK57K(Ij<'=N*OA*+4Wn7NP]{G;Y>M(8$&B2.@(#JuT2K;+|QO1;72>76.'%7#.6?6.''>?3EcA ?_<_q9 /Kb84C$0P9vUh4Ly?fL?]= 2dfAhS@GZf4752:>>5]G*mCK@Qv:+572>76.'%7#.6?6&''>?3-ZL56K( Ii<(=O+R>   YPvUh4L&C3/@'#JuT2L;,!Ub*M*-.aTQzmCK@Q73!+73>7!>7>'7'.70[ %9Rwj8%KpR6%uI4*6YH6( SxSwJVqB;b9#I<'!;O[^-ddddm˜\8bP:3!'#732>7!>7>'3'.7Q4 .C`V<&8S<'L{5+GeC'  %:NdzHSuIE|\5-I_ee+#K=)El8_^^^@rbG(9bN;8%>7>'7'.7!#3!X4*QxR1  TxSvJ/Xosr#H;(IvCddddm˜\8bO'n#:'!#3!3>7>'3'.7PRR{5+GeD'  %:Nd{HRvI3:*#K=)El8^^^_@rbG(9bNj1.7>.'&>7>'3HUoP/ )m[NE@IbuK*-E_>EtX;   ]1TtO~b'0##MX4leZD)+NnBXYZYhv>LQ+%67>'3'.?>.#&Xt  FmOpc&  TrH=93q;PzW4  4aXX:v::u9Q|S*]e+jŘZ' GqE*?qK!!7!!>7>'7'.7g3_+4*QxR1 SySvJG#I<(IuCddddm˜\9bO}:!!7!!>76&''.7qa6+@`D*  JwdRwI$J>(5Wn7POOQ^}G8bOjD">?'.7>7.7>'6.'&FrO 5^y;AnN ng?saE#7WrA+I36VrDZL.Pj7@pO +Rl9>hPGb= BfEph0,C\vJIqV=>MY3M|_D*4ed=\<?eHCZ6"FL@"Fff@_l'AA 1 >7#yVd4JmCJAR{ '>?3vXd4KnCJAQ 7'>?3xVc3IOmCK@QjGf@1>&ee9&f f5 7'>?3'>?3tXh5JuXg4JOqHKEUqHKEUwQ !#!7!3!8y|;;_v``)#!7!!7!3!!!vAB~z~;;vy` v->3'.7!8J-+E0 7K-,E0+J64G*@,J55I*9&9S&'>!7Mcy}>>'.''.7>'.7>?6.'&>?6.'&>?6.'&'6UrCBp.tJ?cA5UqCBq.sI?cC 5TqB@dC4TqB?dC! 2&'>.  4&'?- 2&'?.  3&'?. 2&'?.  3&'?.'bqbd@pR.<<7B1Rl32#6.'&#08@&CX1SM-(:P`|2&5Uo:E9%@. 3!%!7>?737#737>'6.'&!!! 3.C$0!  ScW[+0L4#.Og7}9$6##'2>76./%3#3267#.7#73Qccdc* SkHrR2  /WC.q$,AC>S0n.5?qclt<*NoF=gL-(_-#1Pg7Q.!67'.7737#73>2.'&!!!. :mV8m6rwyn(dˁ#6&'"2>7>'.7>?6.'&'/Kd:>]>0OkA9]A"5@&:) ,#2&5TqB@cC 4TpC?dC  2&'?.  3&'?.bqb9`C%4Sk9M=oS0'D^8;N 4B!N?4#$1G@pR-0Rl=N@oR.1Rl=!?2 3B$O!@33B$HhHK$5.?7>7>>?>.'&U^O4g66g4^ -OtQ@Z6 S~ZE>m1YG/  '1L{X!DmC8Xo9*jƮ3c5dQ5_+gs~C*61$1GN5/9!7!>'.7>?6.'&##33IV>bNJrJ" !>7&!.TdI{cJ.3Ql~KIw]C);/yF4d`\,)NHD3 3-w]5=.NhzCGnO+-LfwA136!-+!-4:p'J't ''t'y'tw''tDM4)A6.'&'6'.?>&>?6.eV4'?X;KFla3  ,D]xXna%  UuKyY8  0]L>cN:* .JZKE.jhaK/#DOP;J_5YdgL7_~E>mG(G^im2A1M6$+F#!#!AL !!7 7!!OZc-BI҇D"!7!+A 3##7!i.PtMQ+Gc'.''.?>>>?6.'&%6.'&>7 YsNcF+e{XIv\A'  ZrNcE+g{Ylb&  4K31]TK<. (LmA7\L;+3K21]UK=/ "3AM,RY5 iʝ^.OhxAhɜ]=cHEg>b`))XVN<%";OY\+*7>.#" 4Y|O:&*@- 9]T%I$"+0J5!kL|X/  1D&Na6  9M,1-!C>3232>7'"&'.#">3632>7'"&'.#"3I 8531^:&F?:1zF;`1247 'GB=83H@f6014&E?:1yG;_2248'HB<3: )#.1=) #.3;, #.1=) #.p!'7#737!7!3!!ZqZAAáAfa@9)wLf s@9nvA 3 # =(r3x'C$pyw:#3#3NOy?_ %'>?3bMc*: a.'&3#!#3[ Gp\HC,5p;o֣8]Zc4$nm^U:rF(>PV\bhtx|'.?>6.'&>73#"&'7>733!7373!#%7!#73>76&'#7!!7!!7!7!!7!!7!7>76./#737#73#73%#737#73#73(@U20N6 (AT20M7 `"D6 7'&)$;J& , !6(  ,!!6'a; 3C'RfY&0,9 c7o$$m72-.2mo)")#Ono no XW-'$^o-oo,oo-om,mm-mm,m1R:!":P0p1R: ";O/%!6)09>),='K4)(7r5((7O'?,RU,28);qqtt !&&Ktttttt8qqqqqq ! ~~~\s)-15 4>7>54.#"3>32#33#3#AD"<-+QvJ@nS0$985(#4"KR11(!4?J)@gI'AeF' @44M+3B0[L s#+'.?>'6.'&>7_.PsMIc;/PsMIc:,'):%,(*9%FF}]48]xBE}]59]xBC8%"8F"D9&#9F#A7>76./3À~6 P}X}S. K^AT~xɓSEqNXk>" #'#3#3G|ѝX 3#'#3#쯈Xwy'.767#'3w>`yA>nS0*<%MohEfB "CcB&7%HM'= >7#^Eg$2 Y9W5l?x !#3#! #hs#03' 2>76./2>76./~Ik=&;M+*?(QSB/[K3/G,,XJ38L( IuU1O=/5ER+]R%.H4.E/)C20?&G2''.?>#.'&67[asb$ [z\g;mxX^9 0aS%z`e2]herʖV9g[rAoNgCrJ}z  3'2>?6.'Rvu2 l~^uJ  AsXVrA~͐N8i]@Ok@ !!!!!!1BY: !#!!!!W? L<+%'.?>'.'&6?!7!'n{7෌ JpSQ`2aa/O<'S\1.WUab6L- #373 #°@˴_|#7!!37˴3## #ʴGjLDssjy !##33ϭJ˭tuJD+'.?>'6.'&>72Z}wd% \{wc$  4fTYa:  4fT[`8 *tўZ`kCrНZ_kFrJEuODEuMFvP$##%2>76.'%LQj9VY5eQ78P/J/YXbY*4Q:3L3E7A/'#.?>'6.'&>7-<vd$ \{xd$  4gUY`:  4eTZ`8 %Qf`iEsН[`lFtKEuNDEvMDtP!##2>76./3NQi9.Jc9X3bP78P/?*TY@gQ<# X4P73J1?6.'.7>'6.'"'.72>'DT&AjA*CYci3Pm> ;T2+ZL51NY"Bb9XMSwF&E]5+\P802C, 6QsP:^I6"/ZV4M2)C10@+ 9TtOY|N"/[[9R4'BmB!#!7!&~~ EN'.73>7N Y`Zj3 ut=jQ6 ah58g^ u ?_= z73#7,z$[acs)7373#5#7kQSZ^U&RBw=\[s lm 3 ## 3(a"FU8te 3#3EKKBs 7!!7!7!^pJ/'.?>'64.'&>7DurHnO2DurlM !JBGeC& (>,HdC% eS*Iaq{?eSY^3r`@:_x<"LIC3 76.'&>!F"QH4-B'8_I0 TWH_3>Yg.dCLV0)A/7T7[^0+SxNAxl]'>2>76.'&>'.7>76./an/^M61F)*PB/ PxLIc6)?P+VORQLg:7K*/[I1=S-.H5,A**@,RyP'(PxQ3RA2![YW**R{R-B,0K34F, 3##!73!..Wq_"| 1J'!!62'.'3>76.'&_cmSW(Kz\Kb;lX9]E*5O1=d3FG1:fQ]g6.UzMYV%C]71R:!$ K0#&>'.?>3&>76.H ?;SRzP#Jx[`Y$ rQ, )L<6\E+-I8A=hM[l;L}YT[PB(1hW:'D\4/T@&p #!7!G`d(#7K'.7>7.7>6.'&>6.#"32>&(.B&*O?)c3RC5)YXT((PyQ9\I7(MT~T()Ot-F04M2.E-2L?(?,0F,(?,0Fk0767'.7>+76?6.'&>9PR|P#Ly\eS p P,  (J:7\E*,G69>hN[p=P[GYPAD0eT6*H_3.UC*s/'.5267#".'>327>7L7Tg53`K/?<=T+='412R,='323(/:R44P9<:;; #B3"C3 4#k!#7%3yhdU8qq# )7>76&'&>!/<0, 50BP6Sl>4_F(,@M#{t )0606K>>bE$9V90UK@v"<>76.#"#>3'.5332>76./N7/ *1M5Qe62aK,,7:::[p77aJ*"-6-%2W,% -09S85R9$9,# X??[::W:' ,) s 3##7!733 hg A:+p#%!!632'.'72>76&'"Fv;@B8Y=3So?5^H-=6"7)A<%?$A\7AgH&;T76.(7!2'.?>3&32>76&6 }-&\67T73Sm>DgB Qi0N .$7)<ku$)(F]4?gJ(3TqA7ir7.7>6.#"32>6.#"32>q'2377Wl72bL,,: +.2Pf50\F)!,4* -4+,&.$ %-%K"7-%[:>Z:6R:&>1'S3:X95Pi' ,( ,s$ ($ (6n!0767#".7>#'76?6.#"}u+$S08V93So?Fc= M~i ,G," 6( +w]n"'E]6?kN*5Yt@3jn9(!>9.-91%"!7!j ?q'3#4>32#".732>76&#"S/>$"9*-=#!;,R%!"" q#?0,;!#>/+:#1!4"p$7'>7'ވUmSN09 CT_.A.iBO#3sYub-A7#"&'77>76.'#?.'AV15a/)81L'A6BM+2Q9y .32+&F%'.'72>?$ 6Z~Q80);)'L_5  6E#x#'37>76.'7޶6,((+ #RF-'4 乸|  ]":.!/!=IEQ?Wm'"&'3'.7>7&7>7.?>2.'32>76.'>?6.'&+!  UR&K%*%Eg<:\t{y3(^]WA%$:J*&T;A; TT&F%q'8R -IW#LPM?,7G"8K+.[J36K-0\J2\aV]0 6$AmQ?fN7# (;P52SC7/?Ii(6TX\/\A0<" -;&+4 -H47N0.J68Q1"#'#%37#".'>327>7ڷO*9"/,.#/O+9#/-/#1䝝 9++;-*LQG+367.'#.'&7>:S:&BQ&x$0=:P8$U` ."$0F.K_1$-1,1Pa.Dx>5$KQ:%'.?>73#>?6.'&2!QcxFFlO3%9OiO>iS<P+XK0SF9, .B,:]I6&;cF%-Keqz<CsU0%C^;<jF#>RYZ'#"TUQA($BXcf/C-$='.?>763.'7>?6.'.#&-B ]-9cF# Vtob&  [d !IKK"y 0^LQ{X4 0$-P^; 8[.lFBmG0^XQ!:fJC&3##.6?6.'%2>76.'%mm_r9 1Ld94C$ 1P8%D|b@ ?^;t3dfAhS@HYf5762;=?5\E*?fJ=[=Dj ##333 ##qo]ar~5% ##333 #W r|27  DJ #333 #yyv !Dx2% ##33 #<[ s\'.'67'\:Wj43cN1E<=Y9q;Q12P9=79<E+'.5267'7>76.#7E6Se42`K.A<#.'&>?!7!`zv* 6Qm_jwE Et^K8%  C~eZ`<r˗Vp}V{Y0>th'G`py;S^>lP/<3'.?>#.'&>7lrbuQ.  ;[xfn|D&JnLMlU@) .KlISdGp~C#.'&>7!7!,Fe{U1  :Zxhk}J NmU@) 1QrM.^[S$<=R1;gZ^e8=rj.TrC>qW4 )FD 3# 2>?6.'D݌8zΛd Wut+iTv+f˜^X;'.?>'64.'&>7I ;\|eauO-  <^|catP- .JlHKnYB, /KkHMnW@* [f8>iT,Zf8>hT:~znU4-RpA-9{qW5.RpCY!?'#.?>'6.'&>7JBi[Ћ9asO-  <]|dauO- -KkIKnYB, /KkHMnW@) f2q>jS*Zg8>hT:~{oU4.RpA*9{qW5.RpC!#7%3L "9$)7>76.'&>!"C8'2G*=fO4 X\Id7.EU+^];DM.,D/%Fa:]l:+RzP;h]Q#$!7!'.'7>76./7 YDQ{Q$4SpSi^>HUVyO LxO?~pPuOT|aC"8-+.5cWQyQ*o0 %3##!73!p9:2p%Iw 7D&!!>'.'7>76.'&X:DfX! bwgWDBURiC 8^B[*vs#R[vŎO;69-Q+7 #!7!#ǿ.6??-%:>7'.7>'.'76?>.'&@iyVCUh_% Pqy_ &7TqfG?32e e56O6NuR01XMY56S`iU`m[c8\U,\VM:#BlC8xcAdp/'.?>'64.'&>7bZ΄}e" [ς|d" ":T9\d< #;T:]b9 Cxܧ`eoxڥ`do/a]R>&I{Q-/c^T@'K}QbJ #!7!6>}=-'.'#3>'6.'&>7 '=UkMZ2+ m?_IqR5 0]N0RG<QpUSY5 C}lO,GNFH0Pjw>>qK-@%)M[HtHCQ-%>?'.?>#4.'&2[J5 W~Pr_"  VuU`34N4S}X4  4L7O/Q`4]f+mǖW=hT0WA(DpH*+YUM9$G1>3#7'.73>7.'&P(?VmMS3i@`GoR85L3.OE;VnN8^N<-D}mO,?C5xFI0Qkw~<)YUL;$*;$GN9NZc1$R6P)?>73'.'7>?'.73>7.'&P'?UlNY1+Ru[KAqK+<$IP9NZc1A%P1>'.7>?6.'&O .HawPrf' [vpe) #7N4S_9 #7O4T]9  I~iK(^gnʙY]f+ZUM;#GsI+[WO<$IuH`P*'.'#7>'6.'&>7 &>TjMT6aBbJqR5 2aNX3[=S]8 C}lO,;B~HK/Pjx>?qKSDHuHF`5Q/>73#'.73>7.'&O (?VmOU3'b?XHqS7 1`M+MC;] lJ9`N>-F}lO+AEs&<>0Ojw=>rM'7!CI!:P[d1CQ-%>7'.?>.'&0VF2 UwKr^"  TvN^90I3S}W3  4L8L-&KwS,]e+mǖW4\M&.TA'CpH**ZVL:#EQ+.?>!67&76.tn/  UqjY >CmKNB,#RY]3FlR9 0SWl-hŘZQazEb<1*+ 5Xr;7fN05R(Q)=>73'.'7>?'.73>7.'&U%:PhMY1+SrQFA7~BNuS5 @[GlN2)WM.OE;UmOT{S0 C~mP-HKlG,)$&1WxG<@B1Qkw|;;rL+<$HOHuHp %:&'&6'.?>&>76.;6h21JJguRaW# OoLvW: 6RopY6 /G3KrP- 0WOVkRXhT/NjzBXi:oMC&XXR@'CkA9r[;|"7%>7'.7>'&'76?6.'&fbjF?QgY! KoxY 4^хv.-]a1*ZOLpM*/AGxT34U]eXcizۤ`8^P>tMEn@#MKD4!; 3#2>?6.';Ї8%{ƹnY& QhX܇O:qnatBf  1''.?>'6.'&>7 /LnKЋ%K&r('fᎉr' ?|ehpD& ?{cjoCZTt,qe|_d~W}NG}_UPF}a0P.;!&47'.7>376.'">%6?'"?_GxU-2Pht|;0I-*SE3 YHO_.W +V8,{7oO [ ? DQ(NsJEmR:$t0C+)@,QyQ')S|U7n6J@0UCYOK/#3#73#!2>?6./!YsoҀ/ |XwY LpTsVfQqZ`\/#3#73#!2>?6./!YsoҀ/ |XwY LpTsVfQqZ`\=!!>#6.'&##7373!5AbWsBvw %E6/RG=֦GR@kN;1T?%0@%җ ###73!7!!3ߎ9;E;977D@"#3267#.7#737#7333#3^8$,AC>S06..Z-#1Pg7M4&#Bi64&#s66&#6!&#:&#h36&#~A&#"tB&%w;@&'B7B;@&'sB;B&'B; &'hBI@&+BBI@&+swBIB&+~BI  &+hB;w!&05:w 6&1B8w 6&1s8w 8&18w #&1"<w &1hT8g4&7Bd6g4&7s6g6&76g&7h.624&;s63&CB3 &Cse3&Ck3!&Cs3&Ch3\&C 3^&CCBQ&Ew4C&GBC&GsUC&G[C&Gh.&B.&s%.&,.&hf&PjB&QBB&Qs`B&QfB&QnB&Qh[&WB[&WsW[&W][&WhG&[sG&[h^&#n>3&&Cnv&#.73&CO"!67#".7>7!#3 !eA:)A2DS)@,#8H'B y/8A&? y+0C(.OB6kP3OPDU!67#".7>7&67'.7>376.'&>%>?'"@A:)A2DS)@,$9I'B\DvV.aY .I0*RE2 YHR\+S ,.ULA'6zjK,A/8A&? y+0C(/OB6/=K-RrFgU%V/M8+A+QyQ'3^T 7n6,<$-QB)A/tU&%sWC&Es5tW&%WC&E;t&%WC&EtY&%XC&EP;D&&CK&Ff;&'nJC&Gnf;&'CC&G;&'BC&G2;O$!!#67#".7>7!!!!ZKA:)A2DS)@,0>"{yCQd/8A&? y+0C(*I>4,ChP4@%67#".7>7.?>!67&76.+pB=6%A2DS)@,A0f\% +D]sMpS > 9dI\V.6>$? y+0C(Be( [b+E~kN+]bS?~eAQA6Yq:5hS5;D&'CC&GpyW&)WR(&ISy0&)2XR(&Iy&)WR(&I*y&)XR(&I2X;wB&*!BA&JTAI4-&+F&4I9&+nJ&n7I&+C.&a}O!#67#".7>73%A:)A2DS)@,$9I'/8A&? y+0C(/OB7oO)3#67#".7>73>76&A:)A2DS)@,!5E%:/';//</8A&? y+0C(-LA5//>'/<:I7&+TBIj&+,&/G&KL  5&,5 G&2;XP&-Z E&M;/&.se1/&Nsi; &.% &N;&.f/g&Nf;&.L/&N;w4&0s'6&Ps\; w&0 P&P;w8&0B7&Pw&Pfw &1n%@B!&Qnqw &1O9B&QwT7&18B&Q:4&4s6a&Ts: &4 R&T:8&47X&T+6&5s8.&UsG+8&58.&UM+K&5w.CO&Uw[+&57.O&U+:&59.&Ub &6C@&VK &6wCK@&Vw 8&67Cw&Vfwg!&7:[&Weg&7n>[&Wnhg&7)7[&Wg&7yA[\&W g.5&7p6[&Wg{.67#".7>7.73>7 ?]zH6,A2DS)@,1'ls4 AjKOcA 'TuV+28 ? y+0C(9[%Im%GxX3.W}M[O:/!67#".7>?'.73673IA:)A2DS)@,"7G&B^XuCtu >4a4/8A&? y+0C(.MB5]D=CoOB,UD,PPA6&96&Y26&;6G&[$2&;h64&<s6&\s&<6&\8&<7&\3x@&sBW&ss ~&s):y)&s: & x & xmB&=&B&sj&p& &x"&hz&){& GH2&wh&B&s>&D&h~*&B*&s#*&**&hd &"JD&BJD&sJD&JD &"JD&hEN&BEN&sdEN&jEN&hte&s:+&n{&&O"!67#".7>7!#3 !A:)A2DS)@,$;K'5 sh/8A&? y+0C(0PB7sG2&soG2&uG2&LG2 &  &5&nO&&y&O$!!#67#".7>7!!!!1BY?A:)A2DS)@,0>":/8A&? y+0C(*I>4 &YL<&sL<&L<&JL<&& &2"&n5&*&_XO3#67#".7>73&A:)A2DS)@,&;K(ƶ/8A&? y+0C(0QC6w*&h&&#&s#&N&fn#&7&s&$ &JD&n&JD&J&&s/& &J&s9&?K&wS &TmB&mB &TEN &r"EN&nu&EN&ENz&)E&EtN,67#".7>7.73>7N 6Qi=6. A2DS)@,8,Tb/ ut=jQ6 Gx`E+29!? y+0C(=b&;fZ u ?_= )&7te&@te&hz&s.&  &I& ?&#/c?&'dBqA&*dPwe@&+dV?&1?&;d?& t&-#;$;'<;w*I+;P-;/;w0w 1;2 62;+:I  &+hB2&;h6H$:&h)9&!$a:&;e%&$g t&-W:BPQ`%:tn:X:Zg&hg&h{B:&Q9g%&f"&<; &'hBC@&sB+;6.'.7>#6&'&'.7>p/Tk4KtCa\azA;p[>3Tg.Qs@d^bL+QrC:u`AwBY=)Fe\df2?rd<\@?'.?>#4.'&1[J5 W~Pr]"  TvU`45N4S}W3 .^7O/Q`4\f+mǖWO>&hu &h1B&h%&hW!&hgCn&nAJ/7&n{Cn &hpB/7&hw &1hT8B&QhhBPh&ha=B&ht&hLX4&h@&nJG&[n/@ &hBG&[h^@A&]BG^&[9 &hDB{&hiE &'+hB0&' hjF+&:F:&ZKuFFe&$FJ:&;&#3P&C)&#F3&Cs&# 3Xi&C]&#3e&Cdg&#3&Ca&#3&Ca6&#'63&C&k)&#H3&C&##Z3 &C$@&#=3 &C&#E3&C&#'.73&C');&' CP&Gi;&'RC&Gc;-&'FC&Gc;&'CHi&GM;&'$Ce&GT;5&' C&GQ;&'C&GQ;B&''B C&G&[iI&+R.h&3&+S &K6 w &1BP&Q{w &1"HB&Qnw&1  BSi&QXw &1Be&Q_w&1B&Q\w &1B&Q\w 8&1'8B&Q&f{g/&s1B&sfg/&B1B&Bg&AB&tg&5B&tg:&B&vg&7[:&W0g&7F[&Weg@&s B[G&s`g@&BB[G&Bg&R[Go&ng-&F[G&ng&[G&62&;:&[c2&;FG&[,2!&;:G&[,K!7##7'.?>#73733>7.'&֥?_GkN3%;QiMS30 0I3.OE;VoNTzT1 .tEE1Rkw}:C}mP-?C'XTN;%*<#GOHtG&F'GAdDj&/W:&F;w&*e/6:&f &6-`:&+&::&Z9&${;&$9&{;&C&-:&u&0:&U&;XHQ&<J+y##'!#737332>76.'*G`q9 _g##`JCx]= >^;P8idnp8P'JkE=^B#+y##'!#737332>76.'*G`q9 _g##`JCx]= >^;P8idnp8P'JkE=^B# !##73!!!zvwleXQTm1: !##73!!!PSSO4!ߗęX~#33 ####73733.]arq('75r74.#73 ###73733hoV@p`3Cn&'kCP/E&'[;w&*Y/D:&Z;&/0:&e&FG:&]2###73333|[ZՐ  &]`:###73333FF k$R+## ##73333:J b7{-:####73333ųƧ&lv>)N&(D"Fff@C#3>76.1O:0'  S  _ $k  ,  @ / ; &O \u TFont data copyright Google 2014RobotoItalicGoogle:Roboto:2014Roboto ItalicVersion 2.000980; 2014Roboto-ItalicRoboto is a trademark of Google.GoogleGoogle.comChristian RobertsonLicensed under the Apache License, Version 2.0http://www.apache.org/licenses/LICENSE-2.0Roboto ItalicFont data copyright Google 2014RobotoItalicGoogle:Roboto:2014Roboto ItalicVersion 2.000980; 2014Roboto-ItalicRoboto is a trademark of Google.GoogleGoogle.comChristian RobertsonLicensed under the Apache License, Version 2.0http://www.apache.org/licenses/LICENSE-2.0jd1  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPbcQdefghjikmlnRoqprsutvwxzy{}|~STUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./012345678NULLmacronperiodcenteredHbar kgreenlandicEngenglongsOhornohornUhornuhornuni0237schwauni02F3 gravecomb acutecomb tildecombhookuni030Fdotbelowtonos dieresistonos anoteleiaGammaDeltaThetaLambdaXiPiSigmaPhiPsialphabetagammadeltaepsilonzetaetathetaiotalambdaxirhosigma1sigmatauupsilonphipsiomegauni03D1uni03D2uni03D6uni0402uni0404uni0409uni040Auni040Buni040Funi0411uni0414uni0416uni0417uni0418uni041Buni0423uni0424uni0426uni0427uni0428uni0429uni042Auni042Buni042Cuni042Duni042Euni042Funi0431uni0432uni0433uni0434uni0436uni0437uni0438uni043Auni043Buni043Cuni043Duni043Funi0442uni0444uni0446uni0447uni0448uni0449uni044Auni044Buni044Cuni044Duni044Euni044Funi0452uni0454uni0459uni045Auni045Buni045Funi0460uni0461uni0463uni0464uni0465uni0466uni0467uni0468uni0469uni046Auni046Buni046Cuni046Duni046Euni046Funi0472uni0473uni0474uni0475uni047Auni047Buni047Cuni047Duni047Euni047Funi0480uni0481uni0482uni0483uni0484uni0485uni0486uni0488uni0489uni048Duni048Euni048Funi0490uni0491uni0494uni0495uni049Cuni049Duni04A0uni04A1uni04A4uni04A5uni04A6uni04A7uni04A8uni04A9uni04B4uni04B5uni04B8uni04B9uni04BAuni04BCuni04BDuni04C3uni04C4uni04C7uni04C8uni04D8uni04E0uni04E1uni04FAuni04FBuni0500uni0502uni0503uni0504uni0505uni0506uni0507uni0508uni0509uni050Auni050Buni050Cuni050Duni050Euni050Funi0510uni2000uni2001uni2002uni2003uni2004uni2005uni2006uni2007uni2008uni2009uni200Auni200B underscoredbl quotereverseduni2025uni2074 nsuperiorlirapesetaEurouni2105uni2113uni2116 estimated oneeighth threeeighths fiveeighths seveneighths colon.lnum quotedblx commaaccentuniFEFFuniFFFCuniFFFDzero.suplargerighthookcircumflexgravecombcircumflexacutecombbrevegravecombcommaaccentrotateA.smcpB.smcpC.smcpD.smcpE.smcpF.smcpG.smcpH.smcpI.smcpJ.smcpK.smcpL.smcpM.smcpN.smcpO.smcpP.smcpQ.smcpR.smcpS.smcpT.smcpU.smcpV.smcpW.smcpX.smcpY.smcpZ.smcp zero.smcpone.smcptwo.smcp three.smcp four.smcp five.smcpsix.smcp seven.smcp eight.smcp nine.smcpbrevetildecombone.suptwo.sup three.supfour.supfive.supsix.sup seven.sup eight.supnine.supcrossbar ringacute dasiaoxia cyrillicticcyrillichookleft cyrillichookcircumflexhookcombg.altone.lnumcircumflextildecombchi.alt alpha.alt delta.altR.altK.altk.altK.alt2k.alt2breveacutecomb brevehookcomb seven.altG.altC.ss06G.ss06D.ss06O.ss06Q.ss06one.onumtwo.onum three.onum four.onum five.onum seven.onum nine.onum zero.onum seven.lnumb.ss06c.ss06d.ss06g.ss06o.ss06p.ss06q.ss06c.ss07e.ss07g.ss07six.altnine.altD.cnQ.cna.cn cyrillicbreveuni00ADDcroathbarTbartbar Aringacute aringacuteAmacronamacronAbreveabreveAogonekaogonek Ccircumflex ccircumflexuni010Auni010BDcarondcaronEmacronemacronEbreveebreve Edotaccent edotaccentEogonekeogonekEcaronecaron Gcircumflex gcircumflexuni0120uni0121 Gcommaaccent gcommaaccent Hcircumflex hcircumflexItildeitildeImacronimacronIbreveibreveIogonekiogonek IdotaccentIJij Jcircumflex jcircumflex Kcommaaccent kcommaaccentLacutelacute Lcommaaccent lcommaaccentLcaronlcaronLdotldotNacutenacute Ncommaaccent ncommaaccentNcaronncaron napostropheOmacronomacronObreveobreve Ohungarumlaut ohungarumlautRacuteracute Rcommaaccent rcommaaccentRcaronrcaronSacutesacute Scircumflex scircumflexuni0218uni0219uni021Auni021Buni0162uni0163TcarontcaronUtildeutildeUmacronumacronUbreveubreveUringuring Uhungarumlaut uhungarumlautUogonekuogonek Wcircumflex wcircumflex Ycircumflex ycircumflexZacutezacute Zdotaccent zdotaccentAEacuteaeacute Oslashacute oslashacute Dcroat.smcpEth.smcp Tbar.smcp Agrave.smcp Aacute.smcpAcircumflex.smcp Atilde.smcpAdieresis.smcp Aring.smcpAringacute.smcp Ccedilla.smcp Egrave.smcp Eacute.smcpEcircumflex.smcpEdieresis.smcp Igrave.smcp Iacute.smcpIcircumflex.smcpIdieresis.smcp Ntilde.smcp Ograve.smcp Oacute.smcpOcircumflex.smcp Otilde.smcpOdieresis.smcp Ugrave.smcp Uacute.smcpUcircumflex.smcpUdieresis.smcp Yacute.smcp Amacron.smcp Abreve.smcp Aogonek.smcp Cacute.smcpCcircumflex.smcp uni010A.smcp Ccaron.smcp Dcaron.smcp Emacron.smcp Ebreve.smcpEdotaccent.smcp Eogonek.smcp Ecaron.smcpGcircumflex.smcp Gbreve.smcp uni0120.smcpGcommaaccent.smcpHcircumflex.smcp Itilde.smcp Imacron.smcp Ibreve.smcp Iogonek.smcpIdotaccent.smcpJcircumflex.smcpKcommaaccent.smcp Lacute.smcpLcommaaccent.smcp Lcaron.smcp Ldot.smcp Nacute.smcpNcommaaccent.smcp Ncaron.smcp Omacron.smcp Obreve.smcpOhungarumlaut.smcp Racute.smcpRcommaaccent.smcp Rcaron.smcp Sacute.smcpScircumflex.smcp Scedilla.smcp Scaron.smcpTcommaaccent.smcp Tcaron.smcp Utilde.smcp Umacron.smcp Ubreve.smcp Uring.smcpUhungarumlaut.smcp Uogonek.smcpWcircumflex.smcpYcircumflex.smcpYdieresis.smcp Zacute.smcpZdotaccent.smcp Zcaron.smcpgermandbls.smcp Alphatonos EpsilontonosEtatonos Iotatonos Omicrontonos Upsilontonos OmegatonosiotadieresistonosAlphaBetaEpsilonZetaEtaIotaKappaMuNuOmicronRhoTauUpsilonChi IotadieresisUpsilondieresis alphatonos epsilontonosetatonos iotatonosupsilondieresistonoskappaomicronuni03BCnuchi iotadieresisupsilondieresis omicrontonos upsilontonos omegatonosuni0401uni0403uni0405uni0406uni0407uni0408uni041Auni040Cuni040Euni0410uni0412uni0413uni0415uni0419uni041Cuni041Duni041Euni041Funi0420uni0421uni0422uni0425uni0430uni0435uni0439uni043Euni0440uni0441uni0443uni0445uni0451uni0453uni0455uni0456uni0457uni0458uni045Cuni045EWgravewgraveWacutewacute Wdieresis wdieresisYgraveygraveminutesecond exclamdbluniFB02uni01F0uni02BCuni1E3Euni1E3Funi1E00uni1E01uni1F4DuniFB03uniFB04uni0400uni040Duni0450uni045Duni0470uni0471uni0476uni0477uni0479uni0478uni0498uni0499uni04AAuni04ABuni04AEuni04AFuni04C0uni04C1uni04C2uni04CFuni04D0uni04D1uni04D2uni04D3uni04D4uni04D5uni04D6uni04D7uni04DAuni04D9uni04DBuni04DCuni04DDuni04DEuni04DFuni04E2uni04E3uni04E4uni04E5uni04E6uni04E7uni04E8uni04E9uni04EAuni04EBuni04ECuni04EDuni04EEuni04EFuni04F0uni04F1uni04F2uni04F3uni04F4uni04F5uni04F8uni04F9uni04FCuni04FDuni0501uni0512uni0513uni1EA0uni1EA1uni1EA2uni1EA3uni1EA4uni1EA5uni1EA6uni1EA7uni1EA8uni1EA9uni1EAAuni1EABuni1EACuni1EADuni1EAEuni1EAFuni1EB0uni1EB1uni1EB2uni1EB3uni1EB4uni1EB5uni1EB6uni1EB7uni1EB8uni1EB9uni1EBAuni1EBBuni1EBCuni1EBDuni1EBEuni1EBFuni1EC0uni1EC1uni1EC2uni1EC3uni1EC4uni1EC5uni1EC6uni1EC7uni1EC8uni1EC9uni1ECAuni1ECBuni1ECCuni1ECDuni1ECEuni1ECFuni1ED0uni1ED1uni1ED2uni1ED3uni1ED4uni1ED5uni1ED6uni1ED7uni1ED8uni1ED9uni1EDAuni1EDBuni1EDCuni1EDDuni1EDEuni1EDFuni1EE0uni1EE1uni1EE2uni1EE3uni1EE4uni1EE5uni1EE6uni1EE7uni1EE8uni1EE9uni1EEAuni1EEBuni1EECuni1EEDuni1EEEuni1EEFuni1EF0uni1EF1uni1EF4uni1EF5uni1EF6uni1EF7uni1EF8uni1EF9dcroatuni20ABuni049Auni049Buni04A2uni04A3uni04ACuni04ADuni04B2uni04B3uni04B6uni04B7uni04CBuni04CCuni04F6uni04F7uni0496uni0497uni04BEuni04BFuni04BBuni048Cuni0462uni0492uni0493uni049Euni049Funi048Auni048Buni04C9uni04CAuni04CDuni04CEuni04C5uni04C6uni04B0uni04B1uni04FEuni04FFuni0511uni2015uni0002uni0009 $Vavv{|~~ ,DFLTkernNTt|\V\bh2<^$Fl6",:DNX  @ f  & P Z p * P v l 2T6<R .HNp&Pvpz &,28>dnx(NthL0 .Pr(Ntz<Zx"Lv02P(Ndn   < ^ !!F!t!!!!""("J"T"^""""#$#N#\#j#x$b%L&6&<&B&H&N&T&Z&''0''((((((())**(*>*`***++6+\+,l,-`-...H.f../h///00N01 1112202V2|23x3344,4F4d4j4t4~444555555556n6666677(7778\8b889999999:::@:f:::;;<;;;< >$>>?&?D??@F@d@@AfAABBBCC4CJCTCjCtCCCCCCCCD DD*D4DVDxDDDEE>EhEEEFF.FXF~FFFFGpGH H>HHIV?@VACEGIKMOQSUWY[]k VV$(*,/018   0 Zz,~rL=EFGIS5GHR'()*+CEGIKMOQSUWY[]$JNQpv{Q12345*0Lgkop,7k8 X[efhij:;,OX`abfu}=%)134Q BDFHVXZ\~CFkwz=%)134Q BDFHVXZ\~CFkwz6#:;   <>@  !#<H_i| '&#gkop   <>@ <iEFGIOPQRSWX[15@EGHR'()*+0123456789:;CEGIKMOQSUWY[]xz|}$&(*,/01JKLMNOQRWX`hpquv{}  "$q EFGISWX[5GHRefhij'()*+6789:;CEGIKMOQSUWY[]$(,/1JNOQX`abfpuv{}4 WX[efhij6789:;(,/1OX`abfu}=EFGIS5GHR'()*+CEGIKMOQSUWY[]$JNQpv{q EFGIQSX [  5GHRe f h i j '()*+12345: ; CEGIKMOQSUWY[] $*, 0JLNO QX ` a b f pu v{}       Z\P  Z\P XZ[\:;,OPX`u} y 68:;OPRXZ[.16@Eefhij0:;xz|} !#&,;GHKMOPRWX_`abfhqtu|}     "$'6X[.6:;,GOX`u} 468:;Z.6 !#;GHP_t|   '#%)1368;.46MQ    <>@BDFHVXZ\~  #<CFG_iktwz| %&'-68:;.6M !#;GH_t|  %'Y#68:; .6M   <>@  !#; <GH_it|     %&'.6:;.6M !#;GH_|  %&'!6:.6M!;GH  %&6;.6 #G_| '0OPRZ1@E0xz|}&KMPRWhq   "$ X[efhij:;,OX`abfu}  efhijabfTEFGIQS5GHR'()*+12345CEGIKMOQSUWY[]$*0JLNQpv{ CEFGIQSX [  5GHRe f h i j  !"#$%&'()*+12345: ; =?ACEGIKMOQSUWY[] $*, 0IJLNO QX ` a b f jpu v{}       &&EE F G I S    5 G H R ' ( ) * + C E G I K M O Q S U W Y [ ] $ J N Q p v {                  &XZ[:;,OPX`u} &  efhijabf  efhijabf!J M N QU12345*0LS^ EFGIQS5GHRe f h i j '()*+12345CEGIKMOQSUWY[]$*0JLNQa b f pv{ " V! |~&r DK>zDjb3,'{qQv8 - mxVy #(*3 6<CDGHJJORTT#XX$Z[%'().12469?@KLORWX[]`bcd')g,,j..kEElefmhjorstvwyz{|~&(+05:BDDFFHHJJLU^`bbddffhhkkmmooqqssuuw #&&**,, 00 34 6? ACEJLM OR"XY&[[(]])_b*fk.nn4pp5tu6zz8|9CEIKLSU~    !!##'' (34=H'MVY]fx #'*2 6<CEGGJJORTT"XX#Z\$'(,-./1236789:;<=>@BCD')G,,J..KEELekMopTVWY[+\05:JLU^`bbddffhhkkmmooqqssuuw #&&**,,00346?ACEJLRXY [[ ]] _bfknnpptuz(*.018:cdrstu{|  }  ~!!##'': $$%%&&''*+,, -- .. /01122 6677 88 99::;;<<CCDDEEGGJJOPQQRRTTXXZZ[[\\    ''(()),, .. EEefgghjkkop     &''(+0015:;==??AABBCCDDEEFFGGHHIIJJLLMMNNOOPPQQRRSSTTUU^^__``bbddffhhkk mm oo qq ss uu wwxxyyzz{{|}~~            !!""##&&**,,0033446788 9: ;;==>>??ABCCEE FFGGHHIIJJLLMMNNOOPPQQRRXXYY[[]]__``abffgghhjjkknnpptt uuzz{{||}}~~          !!##''*                               4DFLT  ligaJlnumPonumVpnum\smcpbss01hss02nss03tss04zss05ss06ss07   "*2:BJRZbjr` "  6DFJ     K n #<C\456E8HY{\hk|~HI4-M %&)13DEFIQRSGItomahawk-player/src/infoplugins/generic/hypem/000775 001750 001750 00000000000 12661705042 022654 5ustar00stefanstefan000000 000000 tomahawk-player/thirdparty/qt-certificate-addon/examples/examples.pro000664 001750 001750 00000000177 12661705042 027336 0ustar00stefanstefan000000 000000 TEMPLATE = subdirs SUBDIRS = create_certificate \ create_signed_certificate \ create_certificate_chain tomahawk-player/src/libtomahawk/network/ConnectionManager.h000664 001750 001750 00000007426 12661705042 025330 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef CONNECTIONMANAGER_H #define CONNECTIONMANAGER_H #include "DllMacro.h" #include "sip/PeerInfo.h" #include #include class ConnectionManagerPrivate; class QTcpSocketExtra; /** * Handle all (outgoing) connection attempts to a specific nodeid. */ class DLLEXPORT ConnectionManager : public QObject { Q_OBJECT public: /** * Get the ConnectionManager responsible for the given nodeid, if there is none yet, create a new instance. */ static QSharedPointer getManagerForNodeId( const QString& nodeid ); /** * Activate/Deactivate the ConnectionManager for a specific nodeid. * * A strong reference is held for every active ConnectionManagers so that they are not automatically deleted while in operation. */ static void setActive( bool active, const QString& nodeid, const QSharedPointer& manager ); virtual ~ConnectionManager(); /** * Receive incoming SipInfos and start a new thread to connect to this peer. */ void handleSipInfo( const Tomahawk::peerinfo_ptr& peerInfo ); QWeakPointer< ConnectionManager > weakRef() const; void setWeakRef( QWeakPointer< ConnectionManager > weakRef ); private slots: void authSuccessful(); void authFailed(); void socketConnected(); void socketError( QAbstractSocket::SocketError error ); private: Q_DECLARE_PRIVATE( ConnectionManager ) ConnectionManagerPrivate* d_ptr; /** * Create a new ConnectionManager. * * This should only be done internally so that we do not have more than one ConnectionManager for a nodeid. */ ConnectionManager( const QString& nodeid ); /** * Create a new ControlConnection for talking to a peer. */ void newControlConnection( const Tomahawk::peerinfo_ptr& peerInfo ); /** * Proxy handleSipInfoPrivate to hand over a strong reference to the connectionManager * so that the refcount is >0 while transferring the context of operation to another thread. */ static void handleSipInfoPrivateS( const Tomahawk::peerinfo_ptr& peerInfo, const QSharedPointer& connectionManager ); /** * Acquire the object lock and register this ConnectionManager as active. */ void activate(); /** * Release the object lock and register this ConnectionManager as inactive. */ void deactivate(); /** * Try to connect to a peer with a given SipInfo. */ void connectToPeer( const Tomahawk::peerinfo_ptr& peerInfo , bool lock ); /** * Look for existing connections and try to connect if there is none. */ void handleSipInfoPrivate( const Tomahawk::peerinfo_ptr& peerInfo ); /** * Transfers ownership of socket to the ControlConnection and inits the ControlConnection */ void handoverSocket( QTcpSocketExtra* sock ); /** * Attempt to connect to the peer using the current stored information. */ void tryConnect(); }; #endif // CONNECTIONMANAGER_H tomahawk-player/src/accounts/google/000775 001750 001750 00000000000 12661705042 020654 5ustar00stefanstefan000000 000000 tomahawk-player/src/tomahawk/widgets/AccountModelFactoryProxy.h000664 001750 001750 00000002712 12661705042 026164 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2012, Teo Mrnjavac * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef ACCOUNTMODELFACTORYPROXY_H #define ACCOUNTMODELFACTORYPROXY_H #include "accounts/Account.h" #include "accounts/AccountModel.h" #include class AccountModelFactoryProxy : public QSortFilterProxyModel { Q_OBJECT public: explicit AccountModelFactoryProxy( QObject* parent = 0 ); void setFilterEnabled( bool enabled ); void setFilterRowType( Tomahawk::Accounts::AccountModel::RowType rowType ); protected: virtual bool filterAcceptsRow ( int sourceRow, const QModelIndex& sourceParent ) const; private: bool m_filterEnabled; Tomahawk::Accounts::AccountModel::RowType m_filterRowType; }; #endif // ACCOUNTMODELFACTORYPROXY_H tomahawk-player/src/libtomahawk/utils/PixmapDelegateFader.cpp000664 001750 001750 00000021631 12661705042 025565 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2012, Leo Franchi * Copyright 2012, Jeff Mitchell * Copyright 2010-2012, Christian Muehlhaeuser * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "PixmapDelegateFader.h" #include #include #include #include #include "Logger.h" #include "Source.h" #include "TomahawkUtilsGui.h" #include "Track.h" using namespace Tomahawk; QPointer< TomahawkUtils::SharedTimeLine > PixmapDelegateFader::s_stlInstance = QPointer< TomahawkUtils::SharedTimeLine >(); QPointer< TomahawkUtils::SharedTimeLine > PixmapDelegateFader::stlInstance() { if ( s_stlInstance.isNull() ) s_stlInstance = QPointer< TomahawkUtils::SharedTimeLine> ( new TomahawkUtils::SharedTimeLine() ); return s_stlInstance; } PixmapDelegateFader::PixmapDelegateFader( const artist_ptr& artist, const QSize& size, TomahawkUtils::ImageMode mode, bool forceLoad ) : m_artist( artist ) , m_size( size ) , m_mode( mode ) { if ( !m_artist.isNull() ) { connect( m_artist.data(), SIGNAL( updated() ), SLOT( artistChanged() ) ); connect( m_artist.data(), SIGNAL( coverChanged() ), SLOT( artistChanged() ) ); m_currentReference = m_artist->cover( size, forceLoad ); } init(); } PixmapDelegateFader::PixmapDelegateFader( const album_ptr& album, const QSize& size, TomahawkUtils::ImageMode mode, bool forceLoad ) : m_album( album ) , m_size( size ) , m_mode( mode ) { if ( !m_album.isNull() ) { connect( m_album.data(), SIGNAL( updated() ), SLOT( albumChanged() ) ); connect( m_album.data(), SIGNAL( coverChanged() ), SLOT( albumChanged() ) ); m_currentReference = m_album->cover( size, forceLoad ); } init(); } PixmapDelegateFader::PixmapDelegateFader( const query_ptr& track, const QSize& size, TomahawkUtils::ImageMode mode, bool forceLoad ) : m_track( track ) , m_size( size ) , m_mode( mode ) { if ( !m_track.isNull() ) { connect( m_track.data(), SIGNAL( resultsChanged() ), SLOT( trackChanged() ) ); connect( m_track->track().data(), SIGNAL( updated() ), SLOT( trackChanged() ) ); connect( m_track->track().data(), SIGNAL( coverChanged() ), SLOT( trackChanged() ) ); m_currentReference = m_track->track()->cover( size, forceLoad ); } init(); } PixmapDelegateFader::~PixmapDelegateFader() { } void PixmapDelegateFader::init() { if ( m_currentReference.isNull() ) m_defaultImage = true; else m_defaultImage = false; m_startFrame = 0; m_fadePct = 100; m_connectedToStl = false; m_current = QPixmap( m_size ); m_current.fill( Qt::transparent ); setSize( m_size ); if ( m_defaultImage ) return; stlInstance().data()->setUpdateInterval( 20 ); m_startFrame = stlInstance().data()->currentFrame(); m_connectedToStl = true; m_fadePct = 0; connect( stlInstance().data(), SIGNAL( frameChanged( int ) ), SLOT( onAnimationStep( int ) ) ); } void PixmapDelegateFader::setSize( const QSize& size ) { m_size = size; if ( m_defaultImage ) { // No cover loaded yet, use default and don't fade in if ( !m_album.isNull() ) m_current = m_currentReference = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultAlbumCover, m_mode, m_size ); else if ( !m_artist.isNull() ) m_current = m_currentReference = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultArtistImage, m_mode, m_size ); else if ( !m_track.isNull() ) m_current = m_currentReference = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultTrackImage, m_mode, m_size ); } else { if ( !m_album.isNull() ) m_currentReference = m_album->cover( m_size ); else if ( !m_artist.isNull() ) m_currentReference = m_artist->cover( m_size ); else if ( !m_track.isNull() ) m_currentReference = m_track->track()->cover( m_size ); } emit repaintRequest(); } void PixmapDelegateFader::albumChanged() { if ( m_album.isNull() ) return; QMetaObject::invokeMethod( this, "setPixmap", Qt::QueuedConnection, Q_ARG( QPixmap, m_album->cover( m_size ) ) ); } void PixmapDelegateFader::artistChanged() { if ( m_artist.isNull() ) return; QMetaObject::invokeMethod( this, "setPixmap", Qt::QueuedConnection, Q_ARG( QPixmap, m_artist->cover( m_size ) ) ); } void PixmapDelegateFader::trackChanged() { if ( m_track.isNull() ) return; connect( m_track->track().data(), SIGNAL( updated() ), SLOT( trackChanged() ), Qt::UniqueConnection ); connect( m_track->track().data(), SIGNAL( coverChanged() ), SLOT( trackChanged() ), Qt::UniqueConnection ); QMetaObject::invokeMethod( this, "setPixmap", Qt::QueuedConnection, Q_ARG( QPixmap, m_track->track()->cover( m_size ) ) ); } void PixmapDelegateFader::setPixmap( const QPixmap& pixmap ) { if ( pixmap.isNull() ) return; m_defaultImage = false; const qint64 newImageMd5 = pixmap.cacheKey(); if ( m_oldImageMd5 == newImageMd5 ) return; m_oldImageMd5 = newImageMd5; if ( m_connectedToStl ) { m_pixmapQueue.enqueue( pixmap ); return; } m_oldReference = m_currentReference; m_currentReference = pixmap; stlInstance().data()->setUpdateInterval( 20 ); m_startFrame = stlInstance().data()->currentFrame(); m_connectedToStl = true; m_fadePct = 0; connect( stlInstance().data(), SIGNAL( frameChanged( int ) ), this, SLOT( onAnimationStep( int ) ) ); } void PixmapDelegateFader::onAnimationStep( int step ) { m_fadePct = (float)( step - m_startFrame ) / 10.0; if ( m_fadePct > 100.0 ) m_fadePct = 100.0; if ( m_fadePct == 100.0 ) QTimer::singleShot( 0, this, SLOT( onAnimationFinished() ) ); const qreal opacity = m_fadePct / 100.0; const qreal oldOpacity = ( 100.0 - m_fadePct ) / 100.0; m_current.fill( Qt::transparent ); // Update our pixmap with the new opacity QPainter p( &m_current ); if ( !m_oldReference.isNull() ) { p.setOpacity( oldOpacity ); p.drawPixmap( 0, 0, m_oldReference ); } Q_ASSERT( !m_currentReference.isNull() ); if ( !m_currentReference.isNull() ) // Should never be null... { p.setOpacity( opacity ); p.drawPixmap( 0, 0, m_currentReference ); } p.end(); emit repaintRequest(); /** * Avoids using setOpacity that is slow on X11 (turns off graphics-backed painting, forces fallback to * software rasterizer. * * but a bit buggy. */ /* const int opacity = ((float)step* / 1000.) * 255; const int oldOpacity = 255 - opacity; if ( !m_oldReference.isNull() ) { p.setCompositionMode( QPainter::CompositionMode_Source ); p.drawPixmap( 0, 0, m_oldReference ); // Reduce the source opacity by the value of the alpha channel p.setCompositionMode( QPainter::CompositionMode_DestinationIn ); qDebug() << Q_FUNC_INFO << "Drawing old pixmap w/ opacity;" << oldOpacity; p.fillRect( m_current.rect(), QColor( 0, 0, 0, oldOpacity ) ); } Q_ASSERT( !m_currentReference.isNull() ); if ( !m_currentReference.isNull() ) // Should never be null.. { QPixmap temp( m_size ); temp.fill( Qt::transparent ); QPainter p2( &temp ); p2.drawPixmap( 0, 0, m_currentReference ); p2.setCompositionMode( QPainter::CompositionMode_DestinationIn ); qDebug() << Q_FUNC_INFO << "Drawing NEW pixmap w/ opacity;" << opacity; p2.fillRect( temp.rect(), QColor( 0, 0, 0, opacity ) ); p2.end(); p.setCompositionMode( QPainter::CompositionMode_Source ); p.drawPixmap( 0, 0, temp ); } */ } void PixmapDelegateFader::onAnimationFinished() { m_oldReference = QPixmap(); m_connectedToStl = false; disconnect( stlInstance().data(), SIGNAL( frameChanged( int ) ), this, SLOT( onAnimationStep( int ) ) ); if ( !m_pixmapQueue.isEmpty() ) QMetaObject::invokeMethod( this, "setPixmap", Qt::QueuedConnection, Q_ARG( QPixmap, m_pixmapQueue.dequeue() ) ); } QPixmap PixmapDelegateFader::currentPixmap() const { return m_current; } tomahawk-player/src/libtomahawk/ActionCollection.cpp000664 001750 001750 00000036502 12661705042 024026 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2012, Jeff Mitchell * Copyright 2012, Leo Franchi * Copyright 2012, Teo Mrnjavac * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #include "ActionCollection.h" #include "TomahawkSettings.h" #include "Source.h" #include "audio/AudioEngine.h" #include "utils/ImageRegistry.h" #include "utils/TomahawkUtils.h" #include "utils/Logger.h" #include // Forward Declarations breaking QSharedPointer #if QT_VERSION < QT_VERSION_CHECK( 5, 0, 0 ) #include "Query.h" #endif ActionCollection* ActionCollection::s_instance = 0; ActionCollection* ActionCollection::instance() { return s_instance; } ActionCollection::ActionCollection( QObject* parent ) : QObject( parent ) { s_instance = this; initActions(); } ActionCollection::~ActionCollection() { s_instance = 0; foreach( QString key, m_actionCollection.keys() ) delete m_actionCollection[ key ]; } void ActionCollection::initActions() { QAction *latchOn = new QAction( tr( "&Listen Along" ), this ); latchOn->setIcon( ImageRegistry::instance()->icon( RESPATH "images/headphones.svg" ) ); m_actionCollection[ "latchOn" ] = latchOn; QAction *latchOff = new QAction( tr( "Stop &Listening Along" ), this ); latchOff->setIcon( ImageRegistry::instance()->icon( RESPATH "images/headphones-off.svg" ) ); m_actionCollection[ "latchOff" ] = latchOff; QAction *realtimeFollowingAlong = new QAction( tr( "&Follow in Real-Time" ), this ); realtimeFollowingAlong->setCheckable( true ); m_actionCollection[ "realtimeFollowingAlong" ] = realtimeFollowingAlong; bool isPublic = TomahawkSettings::instance()->privateListeningMode() == TomahawkSettings::PublicListening; m_actionCollection[ "togglePrivacy" ] = new QAction( tr( "&Listen Privately" ) , this ); m_actionCollection[ "togglePrivacy" ]->setCheckable( true ); m_actionCollection[ "togglePrivacy" ]->setChecked( !isPublic ); connect( m_actionCollection[ "togglePrivacy" ], SIGNAL( triggered() ), SLOT( togglePrivateListeningMode() ), Qt::UniqueConnection ); m_actionCollection[ "loadPlaylist" ] = new QAction( tr( "&Load Playlist" ), this ); m_actionCollection[ "loadStation" ] = new QAction( tr( "&Load Station" ), this ); m_actionCollection[ "renamePlaylist" ] = new QAction( tr( "&Rename Playlist" ), this ); m_actionCollection[ "renameStation" ] = new QAction( tr( "&Rename Station" ), this ); m_actionCollection[ "copyPlaylist" ] = new QAction( tr( "&Copy Playlist Link" ), this ); m_actionCollection[ "playPause" ] = new QAction( tr( "&Play" ), this ); m_actionCollection[ "playPause" ]->setIcon( ImageRegistry::instance()->icon( RESPATH "images/play.svg" ) ); m_actionCollection[ "playPause" ]->setShortcut( Qt::Key_Space ); m_actionCollection[ "playPause" ]->setShortcutContext( Qt::ApplicationShortcut ); m_actionCollection[ "stop" ] = new QAction( tr( "&Stop" ), this ); m_actionCollection[ "previousTrack" ] = new QAction( tr( "&Previous Track" ), this ); m_actionCollection[ "previousTrack" ]->setIcon( ImageRegistry::instance()->icon( RESPATH "images/back.svg" ) ); // m_actionCollection[ "previousTrack" ]->setShortcut( QKeySequence( "Left" ) ); m_actionCollection[ "nextTrack" ] = new QAction( tr( "&Next Track" ), this ); m_actionCollection[ "nextTrack" ]->setIcon( ImageRegistry::instance()->icon( RESPATH "images/forward.svg" ) ); // m_actionCollection[ "nextTrack" ]->setShortcut( QKeySequence( "Right" ) ); m_actionCollection[ "quit" ] = new QAction( tr( "&Quit" ), this ); m_actionCollection[ "quit" ]->setShortcut( QKeySequence::Quit ); m_actionCollection[ "quit" ]->setShortcutContext( Qt::ApplicationShortcut ); m_actionCollection[ "quit" ]->setMenuRole( QAction::QuitRole ); // connect actions to AudioEngine AudioEngine *ae = AudioEngine::instance(); connect( m_actionCollection[ "playPause" ], SIGNAL( triggered() ), ae, SLOT( playPause() ), Qt::UniqueConnection ); connect( m_actionCollection[ "stop" ], SIGNAL( triggered() ), ae, SLOT( stop() ), Qt::UniqueConnection ); connect( m_actionCollection[ "previousTrack" ], SIGNAL( triggered() ), ae, SLOT( previous() ), Qt::UniqueConnection ); connect( m_actionCollection[ "nextTrack" ], SIGNAL( triggered() ), ae, SLOT( next() ), Qt::UniqueConnection ); // main menu actions m_actionCollection[ "importPlaylist" ] = new QAction( tr( "Import Playlist..." ), this ); m_actionCollection[ "updateCollection" ] = new QAction( tr( "U&pdate Collection" ), this ); m_actionCollection[ "rescanCollection" ] = new QAction( tr( "Fully &Rescan Collection" ), this ); m_actionCollection[ "showOfflineSources" ] = new QAction( tr( "Show Offline Friends" ), this ); m_actionCollection[ "showOfflineSources" ]->setCheckable( true ); m_actionCollection[ "preferences" ] = new QAction( tr( "&Configure Tomahawk..." ), this ); m_actionCollection[ "preferences" ]->setMenuRole( QAction::PreferencesRole ); #ifdef Q_OS_MAC m_actionCollection[ "minimize" ] = new QAction( tr( "Minimize" ), this ); m_actionCollection[ "minimize" ]->setShortcut( QKeySequence( "Ctrl+M" ) ); m_actionCollection[ "zoom" ] = new QAction( tr( "Zoom" ), this ); m_actionCollection[ "zoom" ]->setShortcut( QKeySequence( "Meta+Ctrl+Z" ) ); m_actionCollection[ "fullscreen" ] = new QAction( tr( "Enter Full Screen" ), this ); m_actionCollection[ "fullscreen" ]->setShortcut( QKeySequence( "Meta+Ctrl+F" ) ); #else m_actionCollection[ "toggleMenuBar" ] = new QAction( tr( "Hide Menu Bar" ), this ); m_actionCollection[ "toggleMenuBar" ]->setShortcut( QKeySequence( "Ctrl+M" ) ); m_actionCollection[ "toggleMenuBar" ]->setShortcutContext( Qt::ApplicationShortcut ); #endif m_actionCollection[ "diagnostics" ] = new QAction( tr( "Diagnostics..." ), this ); m_actionCollection[ "diagnostics" ]->setMenuRole( QAction::ApplicationSpecificRole ); m_actionCollection[ "aboutTomahawk" ] = new QAction( tr( "About &Tomahawk..." ), this ); m_actionCollection[ "aboutTomahawk" ]->setMenuRole( QAction::AboutRole ); m_actionCollection[ "legalInfo" ] = new QAction( tr( "&Legal Information..." ), this ); m_actionCollection[ "legalInfo" ]->setMenuRole( QAction::ApplicationSpecificRole ); m_actionCollection[ "openLogfile" ] = new QAction( tr( "&View Logfile" ), this ); m_actionCollection[ "openLogfile" ]->setMenuRole( QAction::ApplicationSpecificRole ); #if defined( Q_OS_MAC ) && defined( HAVE_SPARKLE ) || defined( Q_OS_WIN ) m_actionCollection[ "checkForUpdates" ] = new QAction( tr( "Check For Updates..." ), this ); m_actionCollection[ "checkForUpdates" ]->setMenuRole( QAction::ApplicationSpecificRole ); #endif m_actionCollection[ "crashNow" ] = new QAction( "Crash now...", this ); m_actionCollection[ "whatsnew_0_8" ] = new QAction( tr( "0.8" ) , this ); m_actionCollection[ "whatsnew_0_8" ]->setMenuRole( QAction::ApplicationSpecificRole ); m_actionCollection[ "reportBug" ] = new QAction( tr( "Report a Bug" ) , this ); m_actionCollection[ "getSupport" ] = new QAction( tr( "Get Support" ) , this ); m_actionCollection[ "helpTranslate" ] = new QAction( tr( "Help Us Translate" ) , this ); } QMenuBar* ActionCollection::createMenuBar( QWidget *parent ) { QMenuBar* menuBar = new QMenuBar( parent ); menuBar->setFont( TomahawkUtils::systemFont() ); QMenu* controlsMenu = new QMenu( tr( "&Controls" ), menuBar ); controlsMenu->setFont( TomahawkUtils::systemFont() ); controlsMenu->addAction( m_actionCollection[ "playPause" ] ); controlsMenu->addAction( m_actionCollection[ "previousTrack" ] ); controlsMenu->addAction( m_actionCollection[ "nextTrack" ] ); controlsMenu->addSeparator(); controlsMenu->addAction( m_actionCollection[ "togglePrivacy" ] ); controlsMenu->addAction( m_actionCollection[ "showOfflineSources" ] ); controlsMenu->addSeparator(); controlsMenu->addAction( m_actionCollection[ "importPlaylist" ] ); controlsMenu->addAction( m_actionCollection[ "updateCollection" ] ); controlsMenu->addAction( m_actionCollection[ "rescanCollection" ] ); controlsMenu->addSeparator(); controlsMenu->addAction( m_actionCollection[ "quit" ] ); QMenu* settingsMenu = new QMenu( tr( "&Settings" ), menuBar ); settingsMenu->setFont( TomahawkUtils::systemFont() ); #ifndef Q_OS_MAC settingsMenu->addAction( m_actionCollection[ "toggleMenuBar" ] ); #endif settingsMenu->addAction( m_actionCollection[ "preferences" ] ); QMenu* helpMenu = new QMenu( tr( "&Help" ), menuBar ); helpMenu->setFont( TomahawkUtils::systemFont() ); helpMenu->addAction( m_actionCollection[ "diagnostics" ] ); helpMenu->addAction( m_actionCollection[ "openLogfile" ] ); helpMenu->addAction( m_actionCollection[ "legalInfo" ] ); helpMenu->addAction( m_actionCollection["getSupport"] ); helpMenu->addAction( m_actionCollection["reportBug"] ); helpMenu->addAction( m_actionCollection["helpTranslate"] ); helpMenu->addSeparator(); QMenu* whatsNew = helpMenu->addMenu( ImageRegistry::instance()->icon( RESPATH "images/whatsnew.svg" ), tr( "What's New in ..." ) ); whatsNew->setFont( TomahawkUtils::systemFont() ); whatsNew->addAction( m_actionCollection[ "whatsnew_0_8" ] ); helpMenu->addAction( m_actionCollection[ "aboutTomahawk" ] ); // Setup update check #ifndef Q_OS_MAC helpMenu->insertSeparator( m_actionCollection[ "legalInfo" ] ); #endif #if defined( Q_OS_MAC ) && defined( HAVE_SPARKLE ) helpMenu->addAction( m_actionCollection[ "checkForUpdates" ] ); #elif defined( Q_OS_WIN ) helpMenu->addSeparator(); helpMenu->addAction( m_actionCollection[ "checkForUpdates" ] ); #endif if ( qApp->arguments().contains( "--debug" ) ) { helpMenu->addSeparator(); helpMenu->addAction( m_actionCollection[ "crashNow" ] ); } menuBar->addMenu( controlsMenu ); menuBar->addMenu( settingsMenu ); #if defined( Q_OS_MAC ) QMenu* windowMenu = new QMenu( tr( "&Window" ), menuBar ); windowMenu->setFont( TomahawkUtils::systemFont() ); windowMenu->addAction( m_actionCollection[ "minimize" ] ); windowMenu->addAction( m_actionCollection[ "zoom" ] ); windowMenu->addAction( m_actionCollection[ "fullscreen" ] ); menuBar->addMenu( windowMenu ); #endif menuBar->addMenu( helpMenu ); return menuBar; } QMenu* ActionCollection::createCompactMenu( QWidget* parent ) { QMenu* compactMenu = new QMenu( tr( "Main Menu" ), parent ); compactMenu->setFont( TomahawkUtils::systemFont() ); compactMenu->addAction( m_actionCollection[ "playPause" ] ); compactMenu->addAction( m_actionCollection[ "previousTrack" ] ); compactMenu->addAction( m_actionCollection[ "nextTrack" ] ); compactMenu->addSeparator(); compactMenu->addAction( m_actionCollection[ "togglePrivacy" ] ); compactMenu->addAction( m_actionCollection[ "showOfflineSources" ] ); compactMenu->addSeparator(); compactMenu->addAction( m_actionCollection[ "importPlaylist" ] ); compactMenu->addAction( m_actionCollection[ "updateCollection" ] ); compactMenu->addAction( m_actionCollection[ "rescanCollection" ] ); compactMenu->addSeparator(); #ifdef Q_OS_MAC // This should never happen anyway compactMenu->addAction( m_actionCollection[ "minimize" ] ); compactMenu->addAction( m_actionCollection[ "zoom" ] ); #else compactMenu->addAction( m_actionCollection[ "toggleMenuBar" ] ); #endif compactMenu->addAction( m_actionCollection[ "preferences" ] ); compactMenu->addSeparator(); compactMenu->addAction( m_actionCollection[ "diagnostics" ] ); compactMenu->addAction( m_actionCollection[ "openLogfile" ] ); compactMenu->addAction( m_actionCollection[ "legalInfo" ] ); QMenu* whatsNew = compactMenu->addMenu( ImageRegistry::instance()->icon( RESPATH "images/whatsnew.svg" ), tr( "What's New in ..." ) ); whatsNew->addAction( m_actionCollection[ "whatsnew_0_8" ] ); compactMenu->addAction( m_actionCollection[ "aboutTomahawk" ] ); // Setup update check #ifndef Q_OS_MAC compactMenu->insertSeparator( m_actionCollection[ "legalInfo" ] ); #endif #if defined( Q_OS_MAC ) && defined( HAVE_SPARKLE ) compactMenu->addAction( m_actionCollection[ "checkForUpdates" ] ); #elif defined( Q_OS_WIN ) compactMenu->addSeparator(); compactMenu->addAction( m_actionCollection[ "checkForUpdates" ] ); #endif if ( qApp->arguments().contains( "--debug" ) ) { compactMenu->addSeparator(); compactMenu->addAction( m_actionCollection[ "crashNow" ] ); } compactMenu->addSeparator(); compactMenu->addAction( m_actionCollection["getSupport"] ); compactMenu->addAction( m_actionCollection["reportBug"] ); compactMenu->addAction( m_actionCollection["helpTranslate"] ); compactMenu->addSeparator(); compactMenu->addAction( m_actionCollection[ "quit" ] ); return compactMenu; } void ActionCollection::addAction( ActionCollection::ActionDestination category, QAction* action, QObject* notify ) { QList< QAction* > actions = m_categoryActions.value( category ); actions.append( action ); m_categoryActions[ category ] = actions; if ( notify ) m_actionNotifiers[ action ] = notify; } QAction* ActionCollection::getAction( const QString& name ) { return m_actionCollection.value( name, 0 ); } QObject* ActionCollection::actionNotifier( QAction* action ) { return m_actionNotifiers.value( action, 0 ); } QList< QAction* > ActionCollection::getAction( ActionCollection::ActionDestination category ) { return m_categoryActions.value( category ); } void ActionCollection::removeAction( QAction* action ) { removeAction( action, LocalPlaylists ); } void ActionCollection::removeAction( QAction* action, ActionCollection::ActionDestination category ) { QList< QAction* > actions = m_categoryActions.value( category ); actions.removeAll( action ); m_categoryActions[ category ] = actions; m_actionNotifiers.remove( action ); } void ActionCollection::togglePrivateListeningMode() { tDebug() << Q_FUNC_INFO; if ( TomahawkSettings::instance()->privateListeningMode() == TomahawkSettings::PublicListening ) TomahawkSettings::instance()->setPrivateListeningMode( TomahawkSettings::FullyPrivate ); else TomahawkSettings::instance()->setPrivateListeningMode( TomahawkSettings::PublicListening ); bool isPublic = TomahawkSettings::instance()->privateListeningMode() == TomahawkSettings::PublicListening; m_actionCollection[ "togglePrivacy" ]->setChecked( !isPublic ); emit privacyModeChanged(); } tomahawk-player/src/tomahawk/sourcetree/SourcesProxyModel.h000664 001750 001750 00000003537 12661705042 025403 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Leo Franchi * Copyright 2010-2012, Jeff Mitchell * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef SOURCESPROXYMODEL_H #define SOURCESPROXYMODEL_H #include class SourcesModel; class SourcesProxyModel : public QSortFilterProxyModel { Q_OBJECT public: explicit SourcesProxyModel( SourcesModel* model, QObject* parent = 0 ); public slots: void showOfflineSources( bool offlineSourcesShown ); void selectRequested( const QPersistentModelIndex& ); void expandRequested( const QPersistentModelIndex& ); void toggleExpandRequested( const QPersistentModelIndex& ); signals: void selectRequest( const QPersistentModelIndex& idx ); void expandRequest( const QPersistentModelIndex& idx ); void toggleExpandRequest( const QPersistentModelIndex& idx ); protected: bool filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const; bool lessThan( const QModelIndex& left, const QModelIndex& right ) const; private slots: void onModelChanged(); private: SourcesModel* m_model; bool m_filtered; }; #endif // SOURCESPROXYMODEL_H tomahawk-player/src/libtomahawk/DllMacro.h000664 001750 001750 00000002115 12661705042 021730 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2011, Jeff Mitchell * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef DLLMACRO_H #define DLLMACRO_H #include #ifndef DLLEXPORT # if defined (DLLEXPORT_PRO) # define DLLEXPORT Q_DECL_EXPORT # else # define DLLEXPORT Q_DECL_IMPORT # endif #endif #endif tomahawk-player/src/libtomahawk-playdarapi/CMakeLists.txt000664 001750 001750 00000001331 12661705042 024745 0ustar00stefanstefan000000 000000 set(TOMAHAWK_PLAYDARAPI_LIBRARY_TARGET tomahawk-playdarapi) list(APPEND ${TOMAHAWK_PLAYDARAPI_LIBRARY_TARGET}_SOURCES Api_v1.cpp Api_v1_5.cpp PlaydarApi.cpp StatResponseHandler.cpp ) list(APPEND ${TOMAHAWK_PLAYDARAPI_LIBRARY_TARGET}_UI ) include_directories( ${QXTWEB_INCLUDE_DIRS} ${THIRDPARTY_DIR}/qt-certificate-addon/src/ ) tomahawk_add_library(${TOMAHAWK_PLAYDARAPI_LIBRARY_TARGET} SOURCES ${${TOMAHAWK_PLAYDARAPI_LIBRARY_TARGET}_SOURCES} UI ${${TOMAHAWK_PLAYDARAPI_LIBRARY_TARGET}_UI} LINK_PRIVATE_LIBRARIES ${QXTWEB_LIBRARIES} qtcertificateaddon ${GNUTLS_LIBRARIES} EXPORT TomahawkLibraryDepends VERSION ${TOMAHAWK_VERSION_SHORT} ) tomahawk-player/src/libtomahawk/widgets/FilterHeader.h000664 001750 001750 00000002604 12661705042 024242 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2013, Teo Mrnjavac * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef FILTERHEADER_H #define FILTERHEADER_H #include "widgets/BasicHeader.h" #include "DllMacro.h" #include class QSearchField; class DLLEXPORT FilterHeader : public BasicHeader { Q_OBJECT public: explicit FilterHeader( QWidget *parent = 0 ); virtual ~FilterHeader(); public slots: void setFilter( const QString& filter ); signals: void filterTextChanged( const QString& filter ); private slots: void onFilterEdited(); void applyFilter(); protected: QSearchField* m_filterField; private: QString m_filter; QTimer m_filterTimer; }; #endif // FILTERHEADER_H tomahawk-player/src/libtomahawk-playdarapi/Api_v1_5.h000664 001750 001750 00000002701 12661705042 023723 0ustar00stefanstefan000000 000000 /* === This file is part of Tomahawk Player - === * * Copyright 2014, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tomahawk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tomahawk. If not, see . */ #ifndef API_V1_5_H #define API_V1_5_H #include class Api_v1; class QxtWebRequestEvent; class Api_v1_5 : public QObject { Q_OBJECT public: Api_v1_5( Api_v1* parent = 0 ); signals: public slots: /** * Simple test to check for API 1.5 support. * * This call needs no authentication. */ void ping( QxtWebRequestEvent* event ); /** * Control playback. */ void playback( QxtWebRequestEvent* event, const QString& command ); protected: void jsonReply( QxtWebRequestEvent* event, const char* funcInfo, const QString& errorMessage, bool isError ); private: Api_v1* m_service; }; #endif // API_V1_5_H tomahawk-player/src/libtomahawk/widgets/searchlineedit/LineEdit.cpp000664 001750 001750 00000015711 12661705042 026722 0ustar00stefanstefan000000 000000 /** * Copyright (c) 2008 - 2009, Benjamin C. Meyer * * 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 Benjamin Meyer 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 REGENTS 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 REGENTS 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 "LineEdit.h" #include "LineEdit_p.h" #include #include #include #include SideWidget::SideWidget(QWidget *parent) : QWidget(parent) { } bool SideWidget::event(QEvent *event) { if (event->type() == QEvent::LayoutRequest) emit sizeHintChanged(); return QWidget::event(event); } LineEdit::LineEdit(QWidget *parent) : QLineEdit(parent) , m_leftLayout(0) , m_rightLayout(0) { init(); } LineEdit::LineEdit(const QString &contents, QWidget *parent) : QLineEdit(contents, parent) , m_leftWidget(0) , m_rightWidget(0) , m_leftLayout(0) , m_rightLayout(0) { init(); } void LineEdit::init() { m_leftWidget = new SideWidget(this); m_leftWidget->resize(0, 0); m_leftLayout = new QHBoxLayout(m_leftWidget); m_leftLayout->setContentsMargins(0, 0, 0, 0); if (isRightToLeft()) m_leftLayout->setDirection(QBoxLayout::RightToLeft); else m_leftLayout->setDirection(QBoxLayout::LeftToRight); m_leftLayout->setSizeConstraint(QLayout::SetFixedSize); m_rightWidget = new SideWidget(this); m_rightWidget->resize(0, 0); m_rightLayout = new QHBoxLayout(m_rightWidget); if (isRightToLeft()) m_rightLayout->setDirection(QBoxLayout::RightToLeft); else m_rightLayout->setDirection(QBoxLayout::LeftToRight); m_rightLayout->setContentsMargins(0, 0, 0, 0); QSpacerItem *horizontalSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum); m_rightLayout->addItem(horizontalSpacer); setWidgetSpacing(3); connect(m_leftWidget, SIGNAL(sizeHintChanged()), this, SLOT(updateTextMargins())); connect(m_rightWidget, SIGNAL(sizeHintChanged()), this, SLOT(updateTextMargins())); } bool LineEdit::event(QEvent *event) { if (event->type() == QEvent::LayoutDirectionChange) { if (isRightToLeft()) { m_leftLayout->setDirection(QBoxLayout::RightToLeft); m_rightLayout->setDirection(QBoxLayout::RightToLeft); } else { m_leftLayout->setDirection(QBoxLayout::LeftToRight); m_rightLayout->setDirection(QBoxLayout::LeftToRight); } } return QLineEdit::event(event); } void LineEdit::addWidget(QWidget *widget, WidgetPosition position) { if (!widget) return; bool rtl = isRightToLeft(); if (rtl) position = (position == LeftSide) ? RightSide : LeftSide; if (position == LeftSide) { m_leftLayout->addWidget(widget); } else { m_rightLayout->insertWidget(1, widget); } } void LineEdit::removeWidget(QWidget *widget) { if (!widget) return; m_leftLayout->removeWidget(widget); m_rightLayout->removeWidget(widget); widget->hide(); } void LineEdit::setWidgetSpacing(int spacing) { m_leftLayout->setSpacing(spacing); m_rightLayout->setSpacing(spacing); updateTextMargins(); } int LineEdit::widgetSpacing() const { return m_leftLayout->spacing(); } int LineEdit::textMargin(WidgetPosition position) const { int spacing = m_rightLayout->spacing(); int w = 0; if (position == LeftSide) w = m_leftWidget->sizeHint().width(); else w = m_rightWidget->sizeHint().width(); if (w == 0) return 0; return w + spacing * 2; } void LineEdit::updateTextMargins() { int left = textMargin(LineEdit::LeftSide); int right = textMargin(LineEdit::RightSide); int top = 0; int bottom = 0; setTextMargins(left, top, right, bottom); updateSideWidgetLocations(); } void LineEdit::updateSideWidgetLocations() { QStyleOptionFrameV2 opt; initStyleOption(&opt); QRect textRect = style()->subElementRect(QStyle::SE_LineEditContents, &opt, this); int spacing = m_rightLayout->spacing(); textRect.adjust(spacing, 0, -spacing, 0); int left = textMargin(LineEdit::LeftSide); int midHeight = textRect.center().y() + 1; if (m_leftLayout->count() > 0) { int leftHeight = midHeight - m_leftWidget->height() / 2; int leftWidth = m_leftWidget->width(); if (leftWidth == 0) leftHeight = midHeight - m_leftWidget->sizeHint().height() / 2; m_leftWidget->move(textRect.x(), leftHeight); } textRect.setX(left); textRect.setY(midHeight - m_rightWidget->sizeHint().height() / 2); textRect.setHeight(m_rightWidget->sizeHint().height()); m_rightWidget->setGeometry(textRect); } void LineEdit::resizeEvent(QResizeEvent *event) { updateSideWidgetLocations(); QLineEdit::resizeEvent(event); } QString LineEdit::inactiveText() const { return m_inactiveText; } void LineEdit::setInactiveText(const QString &text) { m_inactiveText = text; } void LineEdit::paintEvent(QPaintEvent *event) { QLineEdit::paintEvent(event); if (text().isEmpty() && !m_inactiveText.isEmpty() && !hasFocus()) { QStyleOptionFrameV2 panel; initStyleOption(&panel); QRect textRect = style()->subElementRect(QStyle::SE_LineEditContents, &panel, this); int horizontalMargin = 2; textRect.adjust(horizontalMargin, 0, 0, 0); int left = textMargin(LineEdit::LeftSide); int right = textMargin(LineEdit::RightSide); textRect.adjust(left, 0, -right, 0); QPainter painter(this); painter.setPen(palette().brush(QPalette::Disabled, QPalette::Text).color()); painter.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, m_inactiveText); } } tomahawk-player/src/libtomahawk/utils/ImageRegistry.cpp000664 001750 001750 00000010622 12661705042 024503 0ustar00stefanstefan000000 000000 /* * Copyright 2012, Christian Muehlhaeuser This program is free software: you can redistribute it and/or modify it under the terms of the 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 . */ #include "ImageRegistry.h" #include #include #include #include "utils/Logger.h" static QHash< QString, QHash< int, QHash< qint64, QPixmap > > > s_cache; ImageRegistry* ImageRegistry::s_instance = 0; ImageRegistry* ImageRegistry::instance() { return s_instance; } ImageRegistry::ImageRegistry() { s_instance = this; } QIcon ImageRegistry::icon( const QString& image, TomahawkUtils::ImageMode mode ) { return pixmap( image, TomahawkUtils::defaultIconSize(), mode ); } qint64 ImageRegistry::cacheKey( const QSize& size, float opacity, QColor tint ) { return size.width() * 100 + size.height() * 10 + ( opacity * 100.0 ) + tint.value(); } QPixmap ImageRegistry::pixmap( const QString& image, const QSize& size, TomahawkUtils::ImageMode mode, float opacity, QColor tint ) { if ( size.width() < 0 || size.height() < 0 ) { Q_ASSERT( false ); return QPixmap(); } QHash< qint64, QPixmap > subsubcache; QHash< int, QHash< qint64, QPixmap > > subcache; if ( s_cache.contains( image ) ) { subcache = s_cache.value( image ); if ( subcache.contains( mode ) ) { subsubcache = subcache.value( mode ); const qint64 ck = cacheKey( size, opacity, tint ); if ( subsubcache.contains( ck ) ) { return subsubcache.value( ck ); } } } // Image not found in cache. Let's load it. QPixmap pixmap; if ( image.toLower().endsWith( ".svg" ) ) { QSvgRenderer svgRenderer( image ); QPixmap p( size.isNull() || size.height() == 0 || size.width() == 0 ? svgRenderer.defaultSize() : size ); p.fill( Qt::transparent ); QPainter pixPainter( &p ); pixPainter.setOpacity( opacity ); svgRenderer.render( &pixPainter ); pixPainter.end(); if ( tint.alpha() > 0 ) p = TomahawkUtils::tinted( p, tint ); pixmap = p; } else pixmap = QPixmap( image ); if ( !pixmap.isNull() ) { switch ( mode ) { case TomahawkUtils::RoundedCorners: pixmap = TomahawkUtils::createRoundedImage( pixmap, size ); break; default: break; } if ( !size.isNull() && pixmap.size() != size ) { if ( size.width() == 0 ) { pixmap = pixmap.scaledToHeight( size.height(), Qt::SmoothTransformation ); } else if ( size.height() == 0 ) { pixmap = pixmap.scaledToWidth( size.width(), Qt::SmoothTransformation ); } else pixmap = pixmap.scaled( size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); } putInCache( image, size, mode, opacity, pixmap, tint ); } return pixmap; } void ImageRegistry::putInCache( const QString& image, const QSize& size, TomahawkUtils::ImageMode mode, float opacity, const QPixmap& pixmap, QColor tint ) { tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Adding to image cache:" << image << size << mode; QHash< qint64, QPixmap > subsubcache; QHash< int, QHash< qint64, QPixmap > > subcache; if ( s_cache.contains( image ) ) { subcache = s_cache.value( image ); if ( subcache.contains( mode ) ) { subsubcache = subcache.value( mode ); /* if ( subsubcache.contains( size.width() * size.height() ) ) { Q_ASSERT( false ); }*/ } } subsubcache.insert( cacheKey( size, opacity, tint ), pixmap ); subcache.insert( mode, subsubcache ); s_cache.insert( image, subcache ); } tomahawk-player/src/libtomahawk/accounts/configstorage/telepathy/resources.qrc000664 001750 001750 00000000137 12661705042 031255 0ustar00stefanstefan000000 000000 kde.png tomahawk-player/data/images/stations_background_small.png000664 001750 001750 00001765472 12661705042 025137 0ustar00stefanstefan000000 000000 PNG  IHDRvsRGB, pHYs   iTXtXML:com.adobe.xmpuQN0K ,*u@TLnR lGnt{&-Q?硥d:@ɕ-}}:u<V.#4t/ݼ^0#uoPu EUGF 7,:[{T֐ԋĖҙ3xv=VC@ i'ՃGl.mL~oPHDyZ)iUS0e ܾ˾Hk5%IDATxڄ۲,9$  }ES=23H@%ͣ&DR2.{n Ћv3ww31\T]̆lYkِ1DEDEUEDDĆ^K\\"-""f3>.Q3^.sYk#w!""{{~~s/QUcZ[朢<"?QQ3QQ?D4W|x^D>##>ןw͟7TYlL]LD6#~_}L޲EDUg绻dΙϘo:߳v1M\㿨NQSyv>v,b3T|35kt1kwԆ1SSe&zz{x]lZ+ג~̑jKC{1f!1W3]֊etص@Td~5%\<7=ϲ3ĺ[}T]DMsbdm6E{b?r0(NjMķ!*ng.XGvOv8{1ZS,z.^QkVo[r=x_aYYO-"cX,ߝȦsD5<;lOG\>bbLs ϳG|{}guv -.C\ڲ3\)K 1,=Y2ǏoyK**vƷ'ڛOzGaq YOid"۟lu=֬a|q"q4Ya.Z.q^=*}J%n<<ߝ~vw1y]gt7G3k[q;{+jܷkqE4ֲ5sׯoZ_kj#t16R 9gUg^?Q;[QպT]κ{.nj:{5ge ^"ϳ=3is3y-qhyfęr<vIꚽw|"=[ ;ξ؈uʳ+3?9Ӯvc:e-.2~d=KdY+8*w yr8}>'z57}ԠV-/UѲFw"8W# p5Z.sLy߼DėPY[w=:8Lj8[YG= wGD+?x#e=`gƽXo^FZkwl]*sΣN6wݥjx c Y{˦~z9GV{߽VL3Q/|856j/,V71˻RkY`w,23ͅv`; 4VR>?x{eDV=a2lڿ26wzzٗc-WO%e/fu{-Cg}Zj8JGj_~|Ϲ ;sXyGfg j9b7_9j< <]Kv/|3o⿻qtY)|jn< y4?<Caq!HXq~D!bTPa&>hķ9.=39Gb_TpհhX6#;k3YTŭ2>g{3e~z޺NٓS<Ţ7'/ːSy@_*pj,% ;7.^LPEь/"385iao <|>3V_8XSf,hGA <1~f\B@DJ&|DB T- ztCߵYSV1Xh}[ؘbA ?ֶRύ THpȍ9d=KDE;:VEGԆ(G 08@>x}l|'Δ&SDT>cڔ`ٶU0344h 9|wPPyawղH=ЋB. lT ((U-'X1R6SfUhdAzvaD=3 ]qιX6r@3*IrRň +u!KE@@?䑸TԢ1TϬZ|#I[X`yϐ_DF'Px.} Exy ]Z**sPm$hkL54 P⾠54M |Q4ޑt[h y/Ebb&8hLswq׹luؗcݯ ؘ$p!%&?uA ^mê9Z$8F /@3{ pjt=`g%-:T$l&815s= (1-j"}ΉK  p΢t5In6a9Z`h ts)w4+ t;mg#yԯ 8b"byj6{]?x9o1<w Xn6oo)+2FAY8\fbbYD3*t.Y57(!"+5ϟ9y>  Q@!ټbHp^nNys.7Z*N-Pa'7r-u<.BB`F=WՌ4 FWAhCTPR\Ԉ˳12?1|Ѭ>/ד p.b콣Ⱥ MM9Dm C b.zoJVmY{y 2ChlTs̽ZSXbOhuPO V5jb6/k~wRkϰ6]W *Dd1{EM,WW̨Q_"=}0|1͇ lY}yb vM~»'<{ wm /IUs2tup>aJ}rx~7 qGH֏JriFp`a&YdXwL]wQs֬x44w U]0)4법Vw?czEMow/'17ƧXadYu X%m5 Q,`=Y\":ᏼ@E{\"`Y烋YK|pʇηpOqk㸴 'J ,$3ߜ#K}cVS:?8ޗD^Nϳ)tb SU@ ߙ3.~WbTIFDOPVXK;rp>*} e|7B]P_`r bf7҈v\bvQ|Z&E!_`e1)4Qb*ƻ5r<䷳n@0<1T!T,-@e*G[[2߿\ MZsϧdSh:陣 |^=vp9S! `H ATuF=XMCK09*/B&H`sJ,]+by{ G.^Z:F_kŽdJ ZJ2OG9읞d†!^{+O <'@6C`DQ߱>OҺzƷūƋh@r;V=R/ZchF/k9) A4 ' B((JVomϱG`%CcI0J=N0޾~'  1 2.wo :3m5(={URxPc LD7Қ5}M-mw"2Ajc=utr0Tytyyf'd3g^/u 'wx$}%rx]M߷ q2f+_=_j&UY uA@ DGv,F_r8gP\ Q6,+ǺЃ+u*qZl^z`2'/> >)ͣ)G3eZ߲72-!RB; TfdW"Q XɜB1I;N^y1?riN<b)H%ҠZO* Xt|i`&_;/:fu5 =xBfOC0r=.)=$Y4;Iy^ӽ}sZ{sJx'Y3e}\ʎ=ߩXn`D T ٤'Y͸,M>-;Y:YzO1 B4j׌;9)AuˊbKb^l䲄c،$qi-Wbp"޵5[} u{`E[`ģb/ZN`}*k?% * h7Mi|`c+i}HiT3ڥRiv Qy]`z. lVrJKгpl%j>uܵ-Plt,ƥI@bYVCIM4ܻ#w4%*~]G^lgz:LcL&b,'l&Ię3ŋm gK Q*ӕ{j|8? 3P^lُIƦg6~'˼?l3!/`̂If )3{L^4RؠݦCl!b94%S0 Ξ\OVzi)C1zQ@;ǗCNtEg$TP?YʢVpܺ+Fxm*di1Y`6gP' ͢)?C^,Ti :- lZ~tF>jό&]ƴ.`)+{NdoyG4gŅɒ^9[r~5N1YJ! ,FȽ|&Dou8bL1F^|^I9mHŘ1YAJdڋ~ ~ '^tyydqP&}!{MHX S %;o!F2MQ9) ɦy%) DC wYY6H՛ņ k, ?}>;"v-Yͺf0oKazfOER3lvJU}S6`oJrx+z ˴!,?aigc38}mc2h@$ˊqMWS?hYDBgeZJ{h2I i`kh8,kI)oYxG~ӏmg$Uj1JEW'E\{$UWkHcMy1>[ӾPަVĄ-EL0V!}|BVxF% wRI^l裎R6lวj{ۅߘG=>!65G:i=\46#uJ#!\;j/}cao1c`57f`(YbDB:zC'H=s&92 vi @6Qs Zo8[}Q(60M=yq 9[+.[a9hۓ Y{~pUQm;^6:֓-,_n_TL}'~ !h3˵l346ĀqF1g?sZv4#Mi aPs [9}YX )@XWd6+7l ] W/EsOfI:NT99mo{kYlZdۂ/yc9<8 +)0{UO*??=ho`<)wI iNuн,O T^ML=aRUjznEC?PƕRR2Ǭhd#j^lI\$z7Ɗ?,o5Ra^Z%M95.+uEiI\A ɦrSY`n?c_q8ȑlmpobd_ϯXvzJԶoy~ڏ0EyW /7dzג/Qٲ~_< x;1˂n i?:Qlڹ.JBdZ~c ܘ,D$`z f)zy~Sr2J],#VpDTgh: w'H5ۘjf(ʬf| * =O)2FXy<|=sa:2)zxB\TWj{d0p-% 6+,\T9Oʡdl(fDlXH3Q)> NI yzV^"91]ǢCE5b=IڙPBu(@n()U#V5aII>7fGJ92fd_0qSkiؠ`mJk9 :)16l_4N "m|\rJhT !w7לUu>y6̱} byW̐S$NN~6~TXVVfhهډ'MJܷoȱ-U)^Y]*xeviQdދ=j̜ P Q嘳_Ӿ==BVLLIOeҟ QNiwc\Zz7MW /1# ۋ㘰_)b]\@`gmW)MIDO6VIH\NQcyڟ8쒫WZQ AގJm$wx)1];.S=f"Jhz3j0$xX2> FFIIl'!%L[eT98f2,G15zdglp>aU+8u/4u"[Vy|{xQ ֋6%Ujr=) ]i#ePWezX[F:{U{/h$*S:up/Ug[4 te`G$.@dE=O)/zd?;%2`=\oֲ: ) '5G@LDž,g*0KKO}GCٓP%d5Jrk)+xתlYJ!ϲè &*Uf&[Lz/b +8H:w^++%x.83ol"$Yp͠0=w kSc3T4#ըoq_b$^+$& c~LCC9T0;зW`Ї#Z½{2lK q&#YsL8ezˑ+m l5wQyPz&,+yw>;dYA+;Rbҳ}ݟ5!r:t=KJ$Cg pw*27mHٶͪOP״%h% l{k~փm/of?kڹ7nŝ\wg>#SȽǘ1$] Ϫ_k%I,6NN #/±N& d2M}+D2IR#<[Zj}7v]l>S'r#zT\3:I<ۿh0TM!6 C6>MIXw{[ < q1NN?@2[>Do: $i) V5^aڪ/:w2D*92ݟ/}^L5i{_(uJ/J[#߰(Mo[m Ą{bRɥr<*W-42ͩMɜXg{& @^LիH(z_$sASF0'ϏL]^TKHƂTo<ȳgO!72IM^yVi'BTN fFd}ķDO 3٨s9 ֐JLl4C_ %AM-=gFyye_gn߾zV%zA)AZLi* {>Y>P`VɃ Y\Q" %6 [C\`;}Y`'yx@'f p[]I]B>l܁UB!OgG QFpcJJ`93Rf3̑~3 7;~4`-hu.s,5ܺAQC.v~r<;5|+D kqe P.`/v4ֽDr8!єgds'SC[i*{*Y[}.04CU‡ *Ϸ6,u gC!ML3DϬ9T)CO$whHԔux^ Ubmoɽ ؉!IE"zyLshW{Ub .bQR1pCH;/|.ep ֵw*+ܮ@E \ PS+12Eߔ<IY M@t|?"z3^~=PW bbm楔x{{~/ U)C3l9.fDk7E\6_`~ Ps*1x8L&e|cLv R}p?>oG`t (_8J\,g09fw4#wo}3a@d<Nlj ]W^ $=9 4 !՗5t|} 5a ~Չg:&;pCæ2dXc6waILJ~NM;X)X#>? //6r6كOl~Hz4 rڿ"fI-<|d\aX? $#D^V#fXӸ0;?3 x>CB2sfl=2# ͊YSE3#$ܠ1G>?ds Bs&kS ұCM䐝d„7[x*2'+G1Gq4'ޓPZ^iTC 0OYäAv3MhLuBt{rk`@ܨ SQ@Ѡ 7K4SqKP {Mo+H OO^n!VdbY^->?SzҴ*J{ _$Ѭi0śQo Yz[1p<[;ngpʹʴ6!Şb<*["*.YRˈˠ͔ɬ/l&D+da?Pw3 YUmC<*σ. ĽV#`c2<Xl82]5^iQwtӦ<,b KG꼍:_na9 SN-W"8Ш YiFޕ@X B6L&}y׶Ęfn@VђvJITbߡzۏA%0kkRkw~&y]ٴx_\vz:Hல nꁧ_WFZX84K͖{fLT<ϣUCWk)N?ƐUHNKyG?Os}* )@J 0(&gne;ۃEEd;l쟻S5>9?+%#uSp\ݕPkuRX$2t0a߼dSC~lt$e+e=!xN;͚ͰoM pmcPɉEKL~kM*< O=!zg!& i} $>^r6q.&BT#,* c3Z3[L}EwVM\+2Y.G]sUz-f /`cb|d"%Y^ U,H@Д)<-P`$[#<`|,!^DwUoW]BI)M* A> jxrݶVq~., ZRTt̀Kx._Ws^|24p[ b5#=xg}800ጉbfJuq`п8|ۉ^l.!=vNi7Ap%[o6(زfx1}814~~OoDD Z8 ICa$)I4M$wkҍCx2HY:7fOc}ǀu3IjZb̗"St1 H`0f>d-)pa{B]&z!f~ (_Ho~NmuFAXRFCQ@3JNO`}i9~q?O"R3(ҼGn9,lwP"0P@=EzUAlFfGF}b-oCVj_d%"k]m:}#8_& X] ǻx' =tz!bj}~Ko=׆PdOzysEtofz&)aPV)Y/PxmyvSޜl5y5"ͺQzbA5S^T^$*a/GyuwՀR|jx"z8aQwYԒcq(yEL$0)n]?ց thbqX9(>"=d]OȲ32P9 \DfX1 t;혜e:AOOQ<@ʪ"1\c*`R ܹ<8-K2vzC}kW3\Sěl?vѽҼ:vz!$OɆN/9⌑w"˄q=kYd50s@_YH:U|\d7 5%LߌPٳ2vvfPԻ;kPMHtdb9秥@ @trY+ىJ^&G[ ZOx68$UOEQOC&0^PwSGNTA9`L=f$I0ԾRߒGUU[ANоOAǝfX۞#4ޥ!ó54ys1ggO?6+{]ZFC9df2hG/cm?PiaiJY2QLG^QQ4d9H:EPfApPOSy&؜ r}gO@h;n>k(DCgP]L4v ZF4TϰlbXoO$-0SZA< 8Z 0arDJIxWb)]NVh+x ~ C^3Qe.caG^zB}NFY-.c.~, XX^`xG? oIMr2-(u]>~3i$30wJR`R計,iKKQs/!7keE*1Cϳ*ҷs A x-J ^'^:qn]æMG1Mc& 1glQJU~:G  >EjoV>"ի úI/V6R7역`x ׍DMƦ^P*KpXM?otmgP#{?{\sz1M-(i@4 SBKPˤIVBC"U g<ccfM0/ɃAn5̘[^S4CLht]I4gf=ic=|CrЛmjggX_ao OB}PW)JM ѻ=;bS!!Er}%>zsL < J0@E%^rB:f SȲ[V-X;4TTY@+➉??QZN[d:2!v^yCӿH0T+=,%0xy.t L$Yw6RGڕŐFkJ5.oOPֶ |`c-rHzgRbdl l 6:`Ƣy#Xle]6Zx߁<~ d\߁r=2CHۙVf::;K2:D+_ oɾܨgVA\LE)wT ;2;. 6nInY98NYp<ԁfϔٷ<د?,d="%U URq|+,>Ǩbg׮T&@^yW৅KS!uڵG0$,'EPZ^x'xa%oݳo}G8r$ ? aBiGPa< {Q; {ba-7EU?\<ϖ3T2q˕fiz&2 P0NLSբJ|8F6Jh05þ:x2Xt:=Ac<ϓgݧ}Kw6&%f|&ӀbT3"T, 5P1NriήrJ@4M7_ 6j\/`Zk@ weR/zf@p@/O;avӶ!Mc|},%qO!3&1 ".ń}Zr2HBL[gL9'()}{S"Ya4_{xW\t|dWȀ5P7 4ÿCV=m.0 m^逴g*jl$EXAy4 J-toGlZ`0@k/ ̘cGBU(2^b`%/((fiRNLqz74;ŝj"*!/Hה9Y4NדLݾ5d):oˠܵ!$<^Fz&Y6ӘY b9KJU\TdIXdTz,KOx.nCE7d4!OcvOO (Cgbo)Yڇ 9L<"|( =?H[2$=;c[SLh-)g(iv$r_,QW5dwZ>nZI!XS)y6`_,ߢNCntәMStFg%ݣ-a <PdžF*wCcr .]۶`"-CQ)2g2Fah |xi'T?w MocV6ڸn!kɬjx)YKڙ[ YxqSo-*-kS tMi YVޝFPR R 93_T4^|DN5,[RM\M`$WRDw?¶&~;JQ>kv}t o^%ABи:Cpo" $W__b`7;ѳ~j@}eݤܽ2Jj`;i{$^P $֋Ly蠺f@l Ю=dëW[i8]N vj& J("92Aw4`&JJ_pː7GX+ֳ3F|D:~bop9R9+/1Z"?;WڹCGE˃(e1aHF7Zޝi1ɴtT&Z)9ɽijvI y1c'% v *:#Xd<6 I@oI7cOfRZa XH\ N̮NIewM!jJtz?wӬONK$]!  ʜFvd :.昞0cG@Fhܘ9j/2 ;ā(M,]H{>RaElkclEf;eo beBB 3gsxgR|d%SoL^~\٬L)w[NR.v vzrp"_ԖB2Ĥ ՚L*=*ȁ%BfCuj:ցdUAro=Iczmz))3`i{(M*6͘^a}ϐl9<&O-bjo_6˔CR?GaȬu~Ia) ֶ ìe.\Z=KӅDx *xY~S' oHT]Yy̥Tjt6p3vcgaKFN=FŪFT(Hz^>F ?Skàgv -4`#w9F"ɻ {gZin3,ϵs`*ohYd,}:j) Ryd{[vӳӬ;f>З$Ho,LGۺt iۣ98aقmD {kH _X' )cx!װt_UWEi;hʯ4١r&vkBc#F րj Ѣ&;v4Ԅ+PeXn@@@%tV=ʹmhVq$*|ntwjEY`=tĘ__byᯣk#?X$İHۓ;ɞ_uGfVdCn_0Y81X'c԰ioS3ӻxl[ ƹxy aG!c";dX|~XmϐހTor'-+-%?CG/@*,cocw k}A!33Ԡ7ەcgPԱg\(5 :SI6~ke ͊԰@hm?i߫bNB Z*4ݸ*`z@I8#:gÚlFK2ITwuCtf#Txw2=AP@N}Vp14Vs[f}:`WNY[0aYl/^ޝ!,KaRi0T YGz5nA2?j]2p.ρ.&p.VvDsGp!–1ۻna3çGr2eSzJ;?זkRfߜ(+yzم[Z/U`ʸgv CDc QdTPP e\%R-- _ǜ5d &l_C5B34Z@dz0pW6Hd>^MR զ Lqx3z'ZY]RG<;Ade?נ/`~N^!j'C `lyt͏7 1'b ēgaqKar<4@hHV2< rTi~xET҅s:Y.W%H{)\Ջyrxx%_*#Hk">H_ݾ(bME.ȗ8X6' L?/Ѓ9MvL?)J`".R\Ni_FZ"uoIp%5u$7fg~VNP,h1l1Eh bCE1`ҭdrԅk~>,JuT7 \p;$$ɛ-yxEUV[Rt'n}>GA9|[no Y5A՞gis1yHfVS^a_.)ߒy'W&{mH6.o-46 &twp1]23P|L2]!X6b1^";D`&䒮kMwi;/vLݹG?01z/YAHJ IC$7~ʶa0L wTb#j3i`!<%08W0n<<39SZ~"a>(jPFu6AҊu5] !IKV}hU=^{A 6pҤ?"2ǟ :t0ef䮮iA?gy4󸜭#SfWӻ%%뙦J0F= }լ*&_W k%:O4s=9.Z)%:)p=`9~ɦl]>XV ƧM.dJI,S)^T}OvHF@vɓQqb2&*IcLalOpxNLWJ,i>4qGqAd]`w,@*`A.ٛ YWhr.%gKلt)ɜ?䛱` O[l_R)&1iB*֪]($7{dI&C+1i,^{JH6>h?y N`S {tNm6`> 婷۟p_`)-]6@Vmctgf\Z-e b ̒UCyGe&"4؍bfg겗e~T>?0D)45b/3I"w5QTЏ2az(*CE98Y u#5y6§,M֕Brpe6V_f1䳫q"ްfz Aث|K4l7bI6]\LNGm*fKBvMl<2Jb3pk!Oi_'a<2H`gJ]|@JI+Fyƌw,!)ހC]dy܃D,Z*_w5M30mT)jj LhXX6AB#ƞ!5-~甬a=Po"cA!\#Cf{_]g4[?/~2? p )ˊb 3x1|Jݨ[\RS~Õ7ԲOE+P2h:.#}j x1eCXD<R-=+{8AM^aC!!Bú<"/qY,dtqPGGC[%`w*UZ)oI9kvy=TgۿL)j=}e}QTLW~@ЭE*lrY,-roWSςFw4b6׭ i|.c ˋ5JkIjz_l,3b\L9}:+̞B 9ĵb̜.jK# #^m!Ѵ `~ nJ*(N QyִYwkl{j=^o'.<lbw5_~hq{K@eDA'dKqMkXQ@I3=9Kl~D蒜@Iٜ28l ϱNȿg6s5hbiJͧue,n9-As.~]k^ﰂcl!A+_@WN=X+p֬'/v ۵ D=L.df(>j_^iJx`kv O|nS|y0ƨ]( ׾Zi=2f6C>x(:rǦI_>B~'FyݩxC4}J5CwBfd?T m3+F.1,[Izܕ=Ņ V55¾>WX1,Ւt4=wu$ }&dJj39;&.^P1|6_I ]A )-Qaz;>L1ֽA3\{`9WŐ!&KD\#=VQ'lz4JiK|*oXeFlΛK.ǾfğqzrJ {Brl00 Ih}|lXӨ~Fx] WN[)V:^I~ڙ$ԛB꾝vYLp@–Ȟ{+)fa 4=@s__&bb0s΁5j ݒ|pi^>x $A=M*4Zrd ՝,Юas 2vxA ޶}ȐסH@Hkۮfy†=Υ_x(ω 0֌ȁ9qvSУ|(=ǯ@Ea"0Mf^R/_ / )Рw}QcT" 9hdpHQw$Z]b"c!QAHv鐑 YLk+*AAƚ0I#t'jVad ''`0eIs/bYT;66}yG?=G}%_9z>y#<oK_mHm9g9Qp! Kj6@+qv.a`=/A^'WpJLK;fTԻELI3NrI08g[x*q|oF]$9xIﶸЙ@󫩪 IǚzJIo^n_CÄ̕׮T}J!nX^u?v}Ce-'4f1u˭Jt4'E—/CfNA`ru͊vIZFS;X;Sms*yFn#%݀8&㡬gzLߚ?ioU/@#B%fڝ6p]s}֣́4km Y+ï~#P>$CƉԵ, [3R暨yyJ&+Sb{%VPj>NJ 0ž!cgwFYbi}_XkÊ-Y-P{ [ e IS?U+fꗝIx[+mZjP9NDw⨹Uux! v;@<>}br RO*eЫ+.}>hRkvS zٍ|ܧ%{(Z@10 {͵P}Ω߬?@L^  @ K I͈)EA,OU|7{u)iDig#5̊SU˔c ⺀ûIrA}M_bU* <>|ָ;kGD 5}[r%BC\ FTaLFwUI*8JpI궒ߥdqr>ghhv٨"~m&7,2PM$v1=~ʔU{|jYTg<&`Lnݸ$9uONcmb !U"ߞ4P5(SI#AZ[~ 64`hNh`*ʻI,$ʋ`H,})jɋ[_4ѳyIP.LI =4ZIB^v؅ tgE^vY])o~\v@Ul3@v&cDW~>9ēb)}u&nSmt8YGhV BCSd/^km 14X;`֨ܙ`>޿o-/oKwʉ6g.d9?O_ _V } @؞REG?D (2fTf߸s&#TIRNK[t4^/[ܡq0V[Ԝz®*@/YxR{nnѣ OlTm G̃0FHwg)ʼ>+%C[9[N1ƤM.b޲l?'#S>6% e}tJsǰ663.6%O(\:\0e(RSjz08F_~/|Ig-|\G\;TMfLMgoY*ݴ?"8gh|Y<4imcf'$ećK}ʏkMם`Iߦ6] cf~G/~~~tx@L̛$O22RÎ!ua #p- "=-qPZGᨕ;ƇXiH̴J y®?*2),9眎Q~u^ #L飌u̪dW+}![}u ygk=N_ؔQ aPҗok{Ȃr> \2]{mgg eAg,m]`@byL*vY!I137=:w!3E "(%<,crW*Ŵ}xMX3uߧ>kg(N&X{9fyUS0J$ >שdb=,SN臹_ogVRBl/77ے\7 Ln&QGG1"o lMLR2p0n齝i$nج00#e-A{˼XFRK$5%#md̏Zp(YRӼaĎfw[L`1mr3@F&)`s\@62Ђ0fkۈ @z}^:etP} oUj/F{glTf1<ěf''hVZً\#ua v`@7,\y=LA$!Ct\Ų\9asvnq'IpWf} lG$p#DE8J` [:$C^SGvʫ G [b ?;_{0 uSgbQUV ;]v*DD+n,Kq#я^ .* [uW{J硰?zywydR5!B!y#RiƬ+0aGqScw^O =;9Ô !p+l`0u q9cG vb:!X+tj #Eg^0Y&$h~Ծ&Xx?G*pPa@NisGa?TYWUA^JE(K)PW5F_>~Ɔ(rzR,EIt34zho&x݄Ǩ1_lO4' 6I(DwJ -4^Mc^O& -~^f. JE>U4њ1%D?R-GdK2sXY&0>$􁘕̴/I-Nyt@`\Ϝ|WpF]d~~Vo";2Sc2WSLP,? iKV|Cx@o/^ri^7`jb) ݶ8תᩩJG57I٩5Cg>mIȥ |ۿt@˷ݯļ&C䠙`%ˎW:AcX+JwH *YòfA)$݁d)=ҽKi`;fzϝV*k7#t˿|@Ij(9fݯ3 `}aݭdpiVԂX_]g!vż΋ٚ`~j(AM~d@8i^c 힪9\8~d1EUֹ~jU+=o`i`wv̕'߀T @kPJ&/_5NC叽r!óDN+_NTq=7I McLe>SENHu&7 ;B<l!Ih:m)VX %sq03A4%1|c}L'/:װ4S*DS;-;Ї@=uN=s&k?ՀkBt;m0FZ?$szl D׫HPÄSI(`? + W>WzoaSߑBE6.˔BNJG:|hRcϢn_Rsl8r|~~xgBipa^wS:*qƜ5)CI484;uXlbv!.q^M"$wr 0vW3??3/B&YDi+SE12mE:u|TyXJ;U|]S߼*;h3@5Vȓ@^^߿4QزfKL1 J$ÞJ"K!,+]޲!#-qfq:#6SQ¥\iP[O.`ڜYhy%f==]=Q.nY$-?'g`tYwrhDoi Ċ.i%YOTi ߙ 6q-Y4GiLxV62-ZLNIfug~? ?Lj\ޚ ,tx )jfj'i r;q Y=`Zھ/&.[,nG}e\0NvCR.%=M;}yN5rlb}|;I~`-e@N) -;>J fvCrR5^M-|ҡ.[pp"Lam`bG.#kMU|!g3+q"`f&%d=JoHy ï {kK+Iٷ|"aP!>Sޤfǐ_NFB "ϜhPi~ny|13F}kX[N{3GE&mW ,c2F O$?TF)@ cbNV 'j|MořDjໆmwcGeiD VJ&2k]w}}.-dhZRw=M~*Lq,pyLw <^Sd#z7ڀlK: SدZtEkGa#LRO}1Q\ fڙ4$c(v( a!%S:p(D(r%kVA)= tw͢$=L˧hCj[ 9fDU;ҙG,>Nٺل8ƜqLk/;Ѯ)sjG~ȘO.q/@]1Ĕx~~w5#?XvjӳL½KD%٫0L0ho:_ |eHd"YKZA؝b{BG#!4HpuLIPfȘSFN{7pu@~j=|'v&N;mSUD߾3DŽMWf d6# }Ȁ|qx$Hd4 W {`{9lM4*ϑʌ@#֪W!#E6;,斻2 /9}@Ȓ@l2,nbGAtaS|o<KvL< GsLqĔu )ben@,dmrj)Ž-aַ1ܻ} %G# U(T0@8KBqLNwq"G@ ;|AiJ]j fs_ 7vJRXj 9djR@o|oy0o%÷T}1--b|Ja#%2&cw%`f73^=x~<+=(%o>S:>-e*_(uGS)Oܥ(/SMZ+WYC \'Gm>s|:wΞRnRP]Y\ mF^۫; #)[Qg"p (1*)ɽf7-4V=wtVMu߱eou]zָ\0o-QݫCYVZCx,<~= yʵvdN}Mtudrmb9Ő&;} CyVj,f~HU]dNYye]Ƙ!-|T:i!yɒ=݇dNd?zCF*WvD`Q0\/|l1߸D>a L(t q &:wEL`y+`oq{Z_JͶ~*K$,N]^ﶆBRd.a4J q^U)Խc}BG^؋i4/_3+杁;<M i[e럪mB-]&4⤴6c\jk6œg:74S_2, ѹ !Oa(Kj?N:8*!{_$h/44NN}mQ4ҡ̉&/֔`u"}2 /&Q ״SXk*s5 iy:kKʋ+7 '1! : ܊XG,eǃ}N=#EO!gR4_L3?hӼ}NE.]Ka*i"JC>A,V{rm_ qw6`NٵQbfv|+py4## F`\#ps}˰O4Q2!#ٺ{H$̩~?7GY dpYTҊa7Hfzv^F~N:DQ62{&%Q iIdmW|?F tC#Y D2bmCo;4hϦdL&p4wF@%8_'1#KȖŽ ~ M8uѫqȊ18BJҐV oJ}Q@))-s^ors-Hl}!ًѻ?G oٰ}`, <_~HPo=> H?PAeU*c)iX{uo }s&v%eQ PR1)C W>\-/&j 9yzP?L`'5bb :r@5s9pnpTmqEWGleIPv5LP_S^5rR3ExB[0Jo^QFB&cg&`pAGj ]Nou?aM.K_jAV 4deV j-C0Jvk'jzKڷ)”N ^]b x|p$U_\arLmrG%רA<,FߠQYǢZ O+CX#Ac2i1^qs$;' }ب&fX"uTZ`fdט9qyܒbN2 nS´e-+&@'t'scd\ȲZ9M(R 1 `3O1-lS>sjXBEX :F&cf#L8X gXw" D"zRFڃlci`Ŧ^xHnNyd)kILdAz E[|k!+H2YP8浶ت%.~KG-Dɿ3di,.*⸩'e7`ê͐s_rYg.3EYCw4IJyxfh=V&XދJU{qJ`g|NF[&H!%i%?) %BNDo_,';Dhz5 WJ^2,:}qN" (PXW`q8\d{YQF#B=ѿ{N>glYCHkX@s_uP m+J|G>'GD&Dza$ k 2dud% -i֌zzdC`Ay"i}:M&̲-{y5]*5NH,c6Ye߆t - Fh+Pe ,E}oJNύ)y+[4kSPWB1ұ)%Y+(xzmߠd3nnfϺ% H%U1"g%6#ۦؘ<3|F~T5j43w!P>~dx,w+[\`N<.X>Ph@{Wm#O0wJ{]ۋtQj- {$U,LfG_FaFyu\l?{a|*יr#RxwtDUY+j#ih% oZleK~ef5n3Q7,|Vw1%k/Qu]1TDa)y䱔qK^xUxvEpᜃ\.Z 4d\UTzgOT?'ᷣ*vsN!Fzy0Rr&kAh -$](hUc"4p3 =;Io HCڦ #ᕽn+TĺWU@3h|>*IY+⸚J|5iߘJ }TEl^Oͧtd]Q^D#A1i7*qK[&/9dSwH=U o)B,sQ@p~>2tȦ 7svcH!ɐ٘&U{Fh][f m4 -H`R  9UY)UЖ 3dpH]S>bcgL'|y5 nĢk V !L&}ƃ`П6w=M]l4jXhP:0hlS B#ҡ &1;iaJ{T؜Qr՜jW&r܃\0䛙7 [;܃McB*r߱13آ;0{%chy+i`xIh{)jST7HxWD[R[R'09l. Ⱥ"hght >^)VsɧSDD=?ڇ|~>皚gγ-tR&!pG1^ڕ*3m3Hr|c9 #}RѰ\Wx0B}0*}o7P^Q@߇ kojy63֋;.@1pvf}oLy#6Baa8.>5$VҠ({vྃw+(dF11dqE-궱࠰N*/{yϧ(a!r2NmhdWT58ɧKPRV_2KkN+ ׅbб{yGw 3hStnL7i m@LWcj1<`9زm"w"r>u5OF"ASBm^~H^i:{Jy!wHO;U 8͎!ᄥ1'6 {c1{)Y|p")ZM`fMPgzpO#?qS,أ?nKC `sx7 p yF,1^ <;'s0FB/6`fnfilhf}Qulq ҥ˗+o P}{|h=y=^sp/Gb[,$D"Ug_5;gIYJ=aѬԤ.Yk45eP,ǘ|uh#'z0+Mg1彺 Xr<6<=7"*-k/Yk^K1=t}y=D4AJk~A9d̏|>g{q{/F..sZ5H_/A$4L jBM@t`bm ^VdQ,@ 7#uN1C}r^u.rBTr"χ'F"yM5*|-g)Tb^؋ÿEb ^"'/{Or>G|'Ch=1r05̊  W f|7tN'&~Gf׀ g(1tjG3%3JębGTGaجa7E֪$ 8OvI5;.؏Plh=o2Zs%J5 KEâ"=R$h3Z4W6 °q0 \-T il4\u7c9\oZ,\aP.w=@/v)jYgYjms )TX_u>'J%Uj 5C$Fl<4罼w@ BT٩3A*6;8Xt9tϡf4zbjaسl~{yqb7~#"p07{  ^oyÍ2ѣgdnɍHb&{mȜvkQ[幙g.s D.V*&gdVHHzzI{(7%ՓAqlKU|q>$c )K}_؄vX^Gv,IA18Uvx=L2?2nq]@M tY*QZJ/H$*n' yyo_)3kCtK\}D߼J¿XPyP%6#.~)ΗB$<#."5E`X*D?'fᤗh`^i_eA;|T;.ii*,C'$ULi&}(;D>̂14Lz3@Æ^ 0ˡ4>Va|\pKd8H!d1pRo)j`0Ӿtx>}^)3ktd,` ̦`^hBy}zn+EdR߼n[^GSWBgu8RJb&zm1Gtl[VcJb@ijgޛoB)"*tlT%W )c=mJ=/<,vJZ @ |QA~>??=0Twg3:h^hfKlYqe>dmG7X0w k078,e/)ՙ"`!ע$sâ`Ig,YD)@EGLyVP OoU7+oFL[ewiMTx̔"'Q\=-[[1^rcHw'ЬdKb l!۷ Qw@8k+bz9;<^%tWcs; G(3Bsйw5%-%*zs_duv`[!]n(#{d/HKoDD?yM/0NqcvW:pSi6iRv)Y X%:|AtuSXh3ܽL,+':$ߒHڨvh˃GF>3At߆k`i"ek!hQ`L/󇸦EcȌFfkHН˫o!L_/vY[ mCb<Š%LZv=^} Qo-Ɍ$rxԕTU_(b\l&i9DcYF\'ML|$$Bk?Y{rnR[.{G"aB&A :̄]S44>yrD%:FL;8]=0'9W/ዔsؼ&&`^ Ix*:ݻ_h~/x,诣[N3&gQ,,6T:4,gBYĠ 2HdAofwr݈Vޯ~ }\v2Q%za m4HU(R) %& sR^ X  ? ,\i)kZSΖTAτ=)Mf08Dstb!v/MLy3TSrzOOLu-C4Lx3թ@Pn^y ߒ Io9F:{=F'O*?URz7 ~/פp`z~sX`_*i )ncc<JI_ ( qgЇɵQR#6fQ`Ҁګuf#H lyJ:ePSvS{ra J-Ӻf) `i%SkoǺ+Y-qu>5ffV ldҧB'J"- ׈[H>-5h?Y*51{7?|/&04KE&Q O˴)+}W <7 +ytarXXzCΌ*xQ#1*-N`5ǒzK򱹎 # Ed' l4);*bV5e4qi ǎ;+{"'WJ MFz7GܓI4P-`vd-@cf)j[2d-@a@{ۯ2LGU kU~1L5;N<b|?\beJIfNhJbq3{^6R{ɩZY4^G\Mt|Z znư+(X\;;MaIcJ0tuG}깯 )D{gN;ܟRq+v@fx`+8:Y2UdM`K? ѹl H8Hd90BFk=n*l}<_%^I GళD;Kijv{&_>гV-.߻Ysi3U  E4{?Zn[EFzD|=JR(m 9 *]ޞ|v)8` idulc晲wWLJث"L6;Cy<%:d)#*qU@5?7aRڻB^H =, c}JD!`S5$OB(Hw Emcgwk@]{/P=3TR0sԪ˺UN. ղ9و6+H+saA3jb%q9 ߽8yf˞CG/"4a"ɤTNR " 6TéHM~8F✬xu[NE!'L I g%ː6NCMVto)='\Ͷftu&WnEw W&1j5Y"H*V{;r~} M`ǡ`.B^W\{qKaH)vv&kp|gw&%x>IT` (b{JƔ(@ = aU䝊;fo fU%7E"#r> ^rVsL6D-bvWidE[MȜ`z Ԉ,k4t0j`hXsF;hi\1h TRYڏx%{_ipqኬǫɫrهYqМ2O)aB;@_rz0I0=<H3N=Dš2ZHCsX $SeT2"oqv*1OI<̛5]#/Gg=5鵔huZ%+, R$$5a25}C)SHARz`=VxLu#UQ*iˮl،@9>5sʖ QXKyQ(ɧ~kpWw |Mѕ~>ޒHfI)ɝŪ!jJf~^=wpзDX@xsT;Ȝb~ VwbbbnsիIF_ƐfUx*j`( _!}>ouu^AV'.FιdeIc8$L$CxpC*\!ّ~/ofwjcgaog'؜c$C y^[QK(ѡzHz]Fjd͈3#3DPGhgGSrxڻT=7p{]Tk=k$[qEĊ8Tsکڵs/SUBUb$̊ <@f+V<!mv$_ 1:rXͮ=d}&K:TNk=%H!M8Smc3Dnh%`w.mqMޑdug0;p$5Mj" $aIX])-U9ԗ@c&$wI0]V8!P>Rbڗ`OnVGdd)yFT;z8ȧRrUt6JHG|ҝDo16@&mzdZ/j>~*Iè \SV,!($V `&N0Dט*%XM[ӌMt33Q {U@Vukgps H#rd3H>Dž -uSl5SQٷ_}  @YtnZrNsݢfבNkO]@?ӼU$3{R:yQ9XwI|aC) .FV :^K潤\RE+Ō,rwۋ}k]-4*NIz'@33[| 4@] v0cp10lXfԠc$(&`!8g-̜i8/";& uW20U sr8 oJabsA +_ tr06HcMIߵ*.s48]0tQa45Lȝ\j TfdaRH~`"\R_wˈ |? >ΠVwyTyWr V7EX9S|>Oh`yFC>L5@z5o_z^82ddo: ?*Gƥnhw"nne | ^C-_2B=A/ֿӊDNdnWx4cL@4nڔ(8&R>،upuXI5z,%ClQa$Ǎf3-I_-; $uTi_ :j+Pn5~ xa"'69L-kGxqѮ@XFvP gjԷavB-eD {^16+6<лwf3 PP8d>OJw:ң54SLwL[mR@c{G| {@'18y~|0Xؿ gК,@W)ҾqWJ=+c]Teb8Y{@fٍ۝I GQH|u>LFdΩna)τa&Fl]i:76,s{JKh`GCM?,q( Z/I{>k[cKJx~=&3 Il`\>U6ا*tE.v+ nCqӴf dw(FJ69*dDeSSƯ]ĚD^y)($д"+O"$J &EL25p|1u0fi˱R$NoJT5?+hyL;߫; آ=a3 L:u <i$[ Xe R-۫~9ev]5MG7*yܬo#ԫ ZoU3sf.RœA7,2JI2!2f,k1e#;QEkyL3tK3y釜(i۶CΚ3|Ώdn,`3M &8\́})'+oBN$hsH!>>{09tاyԴa \Ladyzål`7URmjYsK-֣Ty o0)_{`J*~3j#xZ;Q]u.<ֈ/QzVG ) 8 x5a10zZ?ϷWoн'{sn!s~:Pz$" @z@38ǣ&pkŘAW]6UZ!lߴGklAMrJ4Kd9;^;fyؾ3 F`VPϘMSl3+4%k8ȓLct=3YTј0a1e h0*˚;(+C3gݻ)ΗRo] $ }^=.{Rx6 ùjNW۟eny.բ Zs}?abF* 9:WZ^/O32`LF)ECM8/!i`L\ .U| % `4lfJ\1eq G;lX{DHRe[ic~FYԭ]kLgn`|8[ v1Byz`F `tlR?RRWw7 BKpnsR9^ 4oMfg&.!z(_UWF&!9xNL@g*N<-$yʔ{=Ԑdt2`̺N'c7-yb9/I-' @%M&GzиҰ"( ~=ʑnF ǭp:}uy8̹9I5)tFX&."UY*,iЙ%<9Nj'St|?C>c$Yx0!˼$sX6Hhk glRR:ܾ/ƲRo'JyxDQJߢ]a7w~aEAJUZ"{?K~w݋Wkݏ_; /j8J|G!d/^+ympk ;}G1ơl."2q`v2<~ݩb[k6oJ 1756;#_4&m/{Zox˘P䘖jx&{,Zuj3j{2V61lF58SnE)*=\y sج0C7Rf4-v78 N]FKʤNUMX@N,0󶗷hܱi#q/ªgP  6`m ulBL30k|@c}R=w[]!^:)&IO6?֭J/ QsM#y!ma03? ҍABxyC`%竦az^>/5N6`apm6V 0j `7k]4`).SRLﺈ9?v{}|=L-k%6*hɪB {2Fkfp%uDK=dwX=V1͟NbLa& wՄ7i xƱvZNZu3A3w>N1^{:qd;F/~$< 82`I:̂d nV F$  & ! ZYXJS!eC@-9{adua%K"4&^̲´GWJ&м kи(`0dlgQkà4u.aՠd8m@8y%:SY%6`)T 3R˃ArWJ#]$Gԝ5d|t!a^Oi1'd3-G)v \_1o=Td|![iz).oI8 wBu_~rfm q{Z{7p$!~yJh !0"j04 '?ѝ؏&kAi ftSi;X!- u1P=!Rܹ  (Ko)fM 8UT< :yT2[;J$HJx )ٜlZاYB}| L>{p? $BSIE# HRzgfZܝRbg{ U3Y[lDobyi=ic,}d(lΩNͤb# ;}91IygMFydu@8ڱN=9<ِfF`ȌJtIi( J#3⚬rJBxŗ+H?so0J'T5lQ)h^]<'O. uP =o/NecX2悂Yd=nd j1]:ywk*g{~xWus`Ȥc!|N L{qVJyLzdl߿d~>(><G=Jyr4e;@-CC~o"f :ͺJ5[id B@b^?F>6km,=~0;o;=O2q䡥0kޝ۫B`]W.^[ +M6crcԺȼ++qK̥|[@vq0q. &z6#h  em{O Yp +as|\648a; Xӗ\>|z$]\ac NƝ%kng3w  l>MxxFRͰ`[}4p#yﭩ~Tf:`ĖZs7Ѧje6}ײZrV]r01=8l9x߹륖- 83@Nlb1eă:o H%&/[llP~%g1/]:Ba b,cH\.YZi] `ZDn+:-E%rJDmlXb. #JXXjK$j0 @t'hНwFZVUۃنD5)u^ӝRZߑ1FlOq;4ِn WZ.g 5ԺS50R_8qQ/*s C/mYtW6j" A2eGr9Q}~&HDMZ!tr1Jˬ"b\'1+GoOϲ#0ix%+ɤ o댒jD#j"ãj0TgP3xA2llyȦMHkbI6eؚŃy?rhėtۓ26'OeEk-U~m_ I{IPjb'>')?.&E9\8'"m!=w `>0D6իYR09%`sŔm̟x|(sb6Cso}V-gAM& { #}!d֧u'X yn]M[ =,s͵.&WJ&wqs1YA'=ekCt$IN1[y>};/+W":cͫn顚ÝgHI4 uu_`k>63a*b\?Aڵ8|Cjk}i7Z!hn=C9㞛Y s3J&kO%co#0Qi5#b>fNw[=Wsz:>1F?u-ҹUg.ҀFa53="/"^3+" HrX>[&6wX˖ Rb‰ =G.?n=vkU eU>r31qOLς 4%up?,73XDYg#O5[QGX;T,7Єb`MFZm~ڲT6O$$?zJm,=a?o-#-[Wp%0sTasZy]a.w(*;QuTnHS=2mۉw*yPa9VVg}eup0F2YRK ʇfQ ͜6.FS~-#.q.W mu|fQ# rXp KwC Ows[&[fYY 3 G={_-o=2#y}ݡ"r:8 Cw;;>gϺV5{&ߛ8H(-5ޥQ%6'X+U)7bұeԘ+1#["i-[Ou'=NkeM&gR"hsh*2-sxr\!oQb=S]π(24&S1Liz:` F F@M`Db;;岇m2cK.=*˒zSlHV9o@[*ų^NBO z9R܌3g;ްKdg>i3h-Teu/t9l|jneu"2 9~=R9Z/+;;P7&-lOcw½qGsp$`mX:C/blUv,<J橛X*\!7./Ҫ^!NFKGcX?_sk`q֌|p:ޠy0/!4SH lc 5P'hYcáx3B͛mGzMy˟f'87YF#jKbos+ T* 0`Q_U!IXfxBCK}6?46f0l$bRp7+><*zFmoX;'7:Yfnŝ| |nKH^mH7Xrn+yգt! P2`6^R?b NJZ"3a T=Q@v%tmIKF,/3ڏϘ噼E WZf՚+\$sHYe,VLd?VQ2d"^8Ϋ8)QB̤lgR-3x&C\;v|j)~0DTZ-4A]Pb.vLD7$c\ ĻurJCu9S4 >lC^EAGmOh7Td v`;OE LPv6ڲB%;ALwDlC?Rjn\Ayjn;Y=p++;;8\nMo[l)qJqBKyA H >ժF=^dՠ֗/r?妤'Uax/.yL^!7pz \gl,3#D&V,= za=n?xw1 EuH,)AQW¿hj \Z Zz&.4{g;gkMX5E7V氠-2+oR;6ËgRL^"uޣH1rMȸ=yZ)1wChڏ?_m _<|u`(KSasz$,:~$y--:(w.ؾ+->kko-^,EfsU4Fl 9j 11$Tug8MZ¥@Jf<G ָ+Q?lFs*3aߡͤLe#Lly@F<,©f6;Xiae CR +`W1Yu$^pb,cfTqq*t@RcχĵkaflF\rm;|[XCwS^ΛjPJ>~_UbH &P?h .!@cF$Nf)^=x*Eԟ${!Ykܶ"xjFȬrX [$-#"aF֯ ,+4m"z55?iiqZ9⳴8%@Xy"sS 93wTS ~:܍:Dz+t9S|>?9\D#hu$A3ȂQɳw0;̍b,MYtW Vk(cQ >s݇#8٦ĂW;;u/ z`pYEɩί{veTsw15KPw(k{&sUE_Yczy]svBJEcJPAY?ܐ KT~TESl-+"Vĉn^wmL-T*cÍXKKd}&ς7/Os&ո8*k{ T G&Clڋ]OX " %.SAC1xmKq0@qn ߽5j][S폁zklAɞWPH'~,RpkZeRZSB{&ܿY`n)YzfBsW\)/i"Q6/<̑=HoiO[~kRn4PpÄg%`%2˷BF1aJwV3G3!p{ v4BExN@AKϿR}sLV~ X9rBg86ip"G١gX F+xp ТGɐJjv"/a0HE.XxXBD.+;hpz*C", RM]]~dPC #uX?'7UM>y'8ԕ)J%0KuI)p$k,bhsvG=XcJ6-%2`l92*r"cVK&` r@:Qi%C,tσ2դiYXpRb8>3JwoL(2;dYvKPX'7٘PEU( 2QW9ͽmGz >\-8~.>UD&2aǽ6SUXFLp+"ut1Ȃʎ^Kp:zٔf;L--F vdɛ$)i^ 'F($L}p=ޞwr}ս!n` Y:2XOP.P WF,uR:c[H%T6ʂ"t-)q/לe]hu7/ueU ͹dzѸ 1 ]v/.qpՠO/ָG b;g s1Nj?/끓>x$X>؛<78PcmvYQKzv*!t M=8wYks8ś#B `}-qxf񑂴zx",JD; ;J*ͮڄnHmӶ9\"XM6zlHv_B9|il l%W_ h Xod+2R$ieuFϓ7K=.e+e-g6dZsict-p) E`hrc"H2c5v c3%,h7v r" TR>[J Tǖpٟ65 @d#lPQ<nSr#8,`a)[F_ P.NZ+GzَKmo{iKglu ke7"p=Uay\&iI-ܒ9G27˞3cK:}iTԸkJ郿i vW7Ljrdh)%(4C&gU#,ܸBsRޠjH e&J(v$ =N|S᳝G#L[~㴼+XT,vr\pI^Ԩ;dmT M}B}6 >-NVu=4[ɸz,,>^g-ZC:(+ϴWЈ6n63lX&wYH~Ӿ,fV)姻hNzhXhn5U"E%OuG_ sMEHX׹7G$M& c@ E,d`^oɗ]Xjq(騵NJq7_cl#\Ԝ#j)ЏHyd-(y' 3qW0Π#l׀t1Bl >B0 S !LS FOq,a.>-npdݴE<` }X;ݭH=Df \գ.as񕉰j.qg>0T1=2)n(z7kRvS`}]Z$;u̬aeλƻ Pdt¦X*z1&:aqnk|o@8.鞜.Nr .:[n),HE!f]gZ+éլ=DV+xXЊF}rGmva:jW"r˖ϐKpbl.ɯrZ|8 YYڹUO Ys}..?jgλe&madF}{3Of"ɭ˚_S tEZm-[kc} V`m3ޫ(c'n[&os*7 ;,3:Pk=;. )i_u~k(jrk1^֓ƲkxN`w#  iX 6̜Ch;.&$x(4sF(8,A5(lD<,z QXH+ݪz=/w̝"Zw}S5\ˏ8E538A0Y5ޞˡsCŔ7B|=&kHX{XJ\P:%OPpR%  x{@R#ί;ag]v߷kAGi}.DN6Υِ/KMKߡpQǠO瓋sAG!%u- Hw:(T41Jwlq5i!;lM&ְռ:JgŠb~Se-@u L/cgͤ[UqgmͲ)>{]VZsdhTyX})X3h-|mo8s ퟏH2d52njFljE9 uy IK[4i[L%]"`@e4GͳƺXآN"knJ{P')/+<+iFF[_PR9 [e~^危w2lOO}:8XX`1u5jrwـUZ^(R`駫!Vޥ\.!.M]]r/| xIO[?Ďu ,64ES3B*/h`дwfv.ϣVA}E+ ebimg"3c*mlcR;6́W9ޣz!AɌ!(^pʁh<ENy _v"bs/5E F!,̳G?O[;xW '%$]{ >N&5=1IrNB3c"*`fJ_RW[ƒz·C UQxgE6(F$sK]R3 SepBp%I Lxy5C,X&) X&}n4h]/9nzlWTE_T 'o[S6{qw}Sa>,J(ߥ%c~ r E=FB}X3=5[PqSBߓ? Akm2Iؠ&R+F7PIEQbso6 V VP% T8s"(+$ƻ/b={-sra $]͠@H!xYPe چ\Yϕ#L7qJQbКI#LU#m`1l-Kq} Czo9S3/ ` Zl| ®H+DvY?"Xf1F\,#՛fr,P.7>r}~c*-9c/nJ6ٱ6W*9t"}iyS0Ԙas̔[2;g'T,'A#5|>˲ _  6Ϫ2o&#-Lw|>G( X¿H\l#oVԔ9?#>{6#byf[.z.[* ,qҝY3wS.&a)x(~&xm{n8T7BZ82ДM[ecUÜ}lF2hu}c᝚eo )ڴvx%Uf`P.Ruwx6Ky6<ޛ s(1~k9Y!s\`e|n WV:or'-`!v{-E%4ؒ,SmbhT۩ef<ԽzĄ.*#J7`OT{k"^D4]M*r>~hFX-kZ`FA<SI#T2x8XkX^H Υܩ<1 B1H11Q'?1V_d#9J gK@B䰂rc<'s9sJQݙ8ܥ߳wYi B.t֦&d Ld=oyEȌZ  ΠL?l} |,>Scc.)d;]*'+tN@tpX\S@`dd;Y1JT\ɜ|Y&`E<Z9-z/ShFW| @ }ʢDD j*A7m[ٱ5yDM/o}& ~گ`g=1DISRȋoC09HpJMn&}̄o7bK1ǘltcP]?]Zj5p'_co KqUض<G|4C-''V)|l+ ̦ftc46ꭉmq{ڥQ[Ƃz/]L{-J#Dk]k[qE?6¸+Hf3N˺%щcٞiybG2$[3G((T6E? ] LUj[V+dOlr]#x3k }ہ$^)v5sDuKĈ0lG1/Kۛs{P=R)"E3'(Pek6"o! c;)x@wH+&`D B 4pVHȸӐ~ms'K&qƉ(旐F]F.ASUYCd9c<1{Ǐςɷ.*^ɂlhHnSir =!i^8;%}i[d]jfA|Fj68յ]V=)X5M+b|fBq26V|OEk䉰Q8O! ª1\i3߂aH=f @|>WjmPΡ9ls<:}k\XMqk%^BȂ"+~IV)00aeTi)"  a.ccZlrF viMP6zp6.lrrΆl#k%pS M 6]͂wgXycA馩0_m; $xhZ{k2uC.9RXͭ &~cZ8(lK\ 3%٠ 碵pVfu/tyn5Y@W@O.'3  A]3 J#EnZeW#8f G9!lKgPwfHSZ\1IC a>,_xsY'r6؏S4]J YqyWBx(|;tZxi=œJ(%LIe9:^ v!i ܧH]lT|Br٧&MVr)\//[𰀂jĎ݊ 'QVHNgst(sЪo_ n:r`sr7>3OV1s6CAG yR)9Iΰ2;hv.?78l0BӒF|hfFVwܬNx,Xf6mZReք{Dj{?V_^gFg8IX!dbX#bK3CؕuDpwHP`a,Ul e#z]2D$)E3$)}Z7(QpAhM6fjKV٨o^Gɞ]a8| ;ZS%:j" fuDX"CwI7 I0}33`Z a4~'F{â JӃL3ͥ (% D:OEVpǰBql\;8c=ӝ1Ζ_PS"Spp9k?gx8[•6ո:!1\D?H`*j/Vhmq~1s*H1S0 ay'-hs woIuP/Sb/{L|Eʹ̛^n!3de}m]+laqU_Ah;pCS|G* 4ח |K:G% t[.']yrPUq;AsÈ''<|P{M;͊[Ț_TaNR,OCJ]ܞi{1x**KVLs(,N4aϭ9 DQL27, v'\u/a%M6IsŶtX6#YbuV eofMs|uQdq*BŖKn@ȯH͡H\Q y%o12Mg >Ў Z;Jd1")ٵ+1?I\$VKî^mSlYpERJ5!SV^k]tJٚs 2ro[ub`,:{{3ߚ7M)xL!8趩80`_K*T!k~OnD1KƬBiևr>~sgfEx*W(cW|U1Ųidg^ѶTF:ic۶s 'g6KeoCOc2G0mh)1-86UmXǒا6(jSaРN۔œho橤B ?KMZ1`+ʩtEWHYd9us܁0up&0b&k1qk !JWzbԹRR}60S5IKaz0 $׀5,J@^K],y-U_SR#/C;C##SR̀J-%hhJʎdh-<:H s#*Գx6>-3iPĴV7҂PX]ro7T, ;C]YڴIiq=79凞xi@|c%qܧ #5Gr4">70Abޒn RVO.0w঍Bgdo@]nRj⛲T%tVzU:%?'Њ{x_TG qiD!*(tO /;@I)d9CQ7*Q{NS4$~ {-TﯯI?y uءjrfJQGBt|X{Ng9h܋ώ=Igϴժ=%HÚv=ůKS܈BNu;WK"6Ɍ&܊ؾMyja igqEN#뎑< ܼ'@aגnRѻ' [']~֤ɸKF鏳wԘr|;sRxCGn%_Rj.@"wLOݗ%V8N|9tgY7 {Xn۰3 rIiA#{/+N˶An,).Gu-ƅMR져GtKfmn5!1ٹM0Qt٠ɘa@殝.- P<^+.2l>39RIN@[x;3UyC2h2UAE, K󋭯4S<a#»4*- eI\bҮ.W3ir&s%$MMt0v|w*yDl<}#%OqlmdNF'#I˖{i=UTɵ۬"Ȼ+#N+88GH]*)g)AW$XU`6 ܀ 7p2cwf9U+픬η+J HoQⵞ)Ũح9B<)hjX ZpRvʑDٲ .\C!ωɫj-JRv`&亿NDŇTWf`ԝ>-jhWS KiFEq/GH՘;oGd\M*iNW qv#DF3O %Ӯtt䷄#Lƙג:5kJ*UJi^ūKQl+؇i9>Y5[*cp83]v-<B}Ӽ-{3W]ڿ?)Y%iof%^*)K[ ^?ȸ5z2'8\ p#,"`3<cϨsFD] kܾ߬1ty𶔇g 3탘\+9_|Dp N 9VF b_,%?7>6&aHZ}51Y床ȍM7POTVa?x|S2DƭB)g|ea#vk򾭄 mIsOzFqci] ^  *;)W|o̳Pu ulMn@U `JKplP!Mz$*OٽkEm+a` t-`Z6 F yy {Kd:ABRԊiP9llVFx7lQ}S N$Ƽl0Db!z'pKW}N2Ͱ+͍9_mOVYޔBULZfOm~|{C\f3!DV s<}8t*k#)yŋ]V Dqᾖ$@bIEu sQ۲ޮQ_}zM_R*{xԞ2$9dnѸ#&r_-Y<)+~61$2Y5+V+Qs若=\ɾ^ZXeFXaEDzi2%UmXϿ@;ƀP0k8g˸l[K=\9e;4B*W*vJ5̉fh['-]aK. ēK~;-CZ-4B|g"x| Tyjpy RlK yWO`;wP>=^mIJ)svMbC4!-jSkx̤;ȸA6'\0i$P $2&k7@;qw;T7kpyJp#r ʦZ\b{yݵyĪEku *8=r]y7%B2:@1C<E`a5~T:T~z YK}ܼ{jr]aDf Xj [3mOy*P e|ά [w^2 U<0{ߙ2fU*i;Jqx݅E 'C4bo8 xҀ2ۖ'mWJMЀ77"cx+y;8ܨ;c+鍆,lXүR qMXsԪ]+!Mݕ Yedb7#'UBMr/wlhbNM|.!Y$[n6R^"*T8BRgF՟Hw%K7!/p7]oS`Z}WtH+&t>7ffֶwRh@LRZ)Hɳ(9@D I6[(7P̸m[NUP@0zJ\ƾqϝ(s`fG;yA2+h>|ïqp]{`Lt<=9S^Lc zR{|PScɈ}ƃQ倫%tZD=Kj15ֺzl_2({"J؉y 4DÏWænB6#X} jɂ\?eZ_.9ԥΧl"Y%8VoSbh%JK;X4-"] ߴ)ﶷFNPΰZpLRʠ R: |%Yh9ɵ A )Y5a3.!`DeXC0D(B-W HW Nl ȆN\Ish=Ux4i]iZ1hmQ0Ƞ"T`h]E&HC5J K)v#8 43;˿۠8hlYݶBp_#ޙ5^3 .δKZ3RlGJ%Q0y@1<=,W L[a51@>ń^D20FRCNkT1+Ҽ{ǝ~} X3 u Q|+vjnzK}>,MiQf{"FbWC=p\Mmp-zRPu6ѝ/Y][-a_3JCY뵰hjsJg\͵lG/"%P‚h-c[_bAM՘gqWŀ;38k)үoL\8Ȇ{ݵ"TsIC\⟩\H B*غ:Z7]*cV,uEd.baH Js|r'>[`XB9Uf{{#h/Wmq٦Ƚe|s`QR*JC>IKM!#‚]g0ҮS#D_{,,&l^w% NJB]N-闈[0j0{5 7xZC"ę-8 b6Rdzo$ZGJ$\ Mevs 깚Ǽc9׿g_OV̠dr d~&ȔjBz!+axc-y ςPP Jjf "5yHc;q?NK>q!P $3 \RVA\zC2eRNlC5oȒYyki<3B/2gKC0"O~ʅ|1ȡJL[MdRS/řt[q8-E8%nҹA{rN^<(D8Bαz|< QؽI5J,FJefj+E`@vϥ\$;7Riһ/' `fm0*YqчIqL'.T>%H(qḄ*a)˄&&H^[X70(HǙ k"Λ&azx%Y!0g"2Ҁ2E{\?.fңI?Y6ZT~^B~z3c{<;}a2Hߍ$smfâ`.v/LJ F{, Ƙ@̽N$^_vxqlKp*hT10II*1YI)#ΜN^ (_gS^8ʒ9G_c܁oJKYقe 锌6~%l5i7?LC>zdɏfeSJ9ƪ5L5K4g!DhRy 4dֽ1z!'V%g(,ϱR1H F.pFlIw镪5**5|"ģ]ǽT&+?{n=U Z*#0-) .u]uV456r>=A [23֙U1to41ByrQaPIROĀ(&F+3{cغЛJU8٬OH?#R}Nɟ^72ޛ\iJΨ=x>Nf&3Z'Nmr _@VCz_g\npޛHd)RA9O-“l.fLs)Xdܟ87E%T4d\CZo6FhZk2א"B9hc*((8C)dX]p*N>|V{f-{8*w]+<|nBg}o_`dnJV`(~gez_hpQֿPw,5)cbxlX}P1jf2Gny1t+wA.w3M],59q:r6}|ɧMC+ B¦ z$yeBm;v~ )AqpA*W+ 䪏Y 3?m ږ<871d @A[碛k<bRlMWmnu BjDw(e- Şm }m+ASZ<OE0v BaA͹L;of+_DSP!'f:圉XZ-ߏFk"X u`R.-6d0v˖՗MmlʕBgs1 fͦl kh8|>|whr *2]U 4`DܙZyNz(g1a$1``y]b us=T9<Zr]=#YS8:=[i3y. h1lOB1f)z~z@'~ E6Z~;JyM+#,iZj'˅h(Еf=*UH2VTLdMKץh>eNM5766=}Ʒ=/4óNK+X؆1dp.Цwx r$| 8B# bO.LG;x؅\%XsU߃Ҷ,8P΀ ]6.MPTt O * x].a߿?;Ƽo]&: σ(S ,|c˙!] ٟ6~M[lS P]IcY"=w-CW)+H ^lu^Փm,c,wng{ݫ$ާ)- 9sNk`QgM+m/KFc"dCKg=(\hB&|v[xR)Uvŕi[6k;%EnMi%pZ}e%,+T?[gW۶>XM^?{v–/% ި~}K!-lmVkMZSwT9\N쥦H͎w}:|=q;˽)esY{Y'#:FbEBl(\P 泧X-}'>g1/H87k*D[.$0_U}(79Z[5$gFHh7J~ڐ5N9gͷPw*Xi X6{ v_4&j]vp1eοW.r_3fySy<͒< $1WsĴONFOaɞ((իX'YTT{KJ~p1ւLxtLs0KkЂĚ]5&^zkYȦRp'_ ֯Upfl\ `l/H13|yJjskXl-9'HlSPi6Wnj'dn6u7P4.kP X%t[o dqrI4iw^h\L)NEEZW)MuLL^dhaY )80%K%͊{V^#ss"%AN44 ضcF ُ\R6Sk(sEWH(Z354Хf-x²&TD# )l~"n⭋|>?7g6UA/>kpJv~"lU0@q b]4"@pF*+q c1hNTpyP)\R 5V-R *w-kvSZЬpʓ;*:|?7"9³E gA+x DX7ITj?C9Gbw7RyV,-v5^1F9Q_-i0UE3l!jD*xCd5҉I?=RY -Cm\j8M= ~LX2"eͭx `1 XiZw/+!Jkc-#⏻þYXyz3qbG?PK$,4hq#cڲ@r!6D38̀3 ~w }Lxs)F PtITc@j]=Kޫ"g xƈa#t\]yIgޠ\gJ2^׭,l,J LX,TP/&7lEP ԇ3"~CJM|ݞzN(!BC~,r(uRA"/^|];Cz*|Lkk_:Ϸ:J\M=+GY j%{[p{h:RϠ4 .k&ޭYqr,$~97[8u2NcG3<7T"V|&c6֗l;UySŖDd'sThg M`@u$H1HX0|YC|L_Z}%v" AM^?lN0j[J%No)jUhh^s Yd$`@¼]˅Qgl,ֽ}֚z"`UPr9o)KZ۳7WvLbN/v+?>Y4U>:mv@R|Ue^?5t Jr8m#<7kd=LM'ɯ;K-w[x. s _o C< 6x>f״H馰`+ =uJՅ_URbH5P*w]0;dW7ʋXr8 >@ga34"wJ(Sd[:v߃zB)F*0J́lޝ dG艦jyjӪ ӧ"KHV$/b@{{(V7VQ~__ۖ3ov`DulqS 3ɿ_7U.)gݷ !x2P r6{K>񤘁2DKɸ} 5DZBCMxR}F=* N/;MZ S*\K1נ]+n-v~q\%H"Ca24ȍ RXa͡ODm.g})JḄժ03^jػz#T#GqadVYcLnՊh0"a5:=P /=7by"4e ch)~m؍X(DKuUm pFBݷ"4vU %&qN#h QMh-mLA} *~T~%Ʋ1{wյHG=%R z't㧗 ^.b0F~ .+1X ݁};h9#ĝOQ}\u@{֖) lO ZK@rKZ[wG=j& F fq } c/9<( 1I6uȻb$d3]}r-e@[X;ƝDP֯֒r=?9ϖI7lS2pg׀=RؖN+)Pa@9,>7-՘oE1W*R!! Prp(x(1PUΛIBn%F%7ckf$tF|N(aˉ5ȯsC7h!Z%CW3ل"\5&Saji=͏aqbapu8"'q_, ]Kո@MN௷q_ԠcYyz ,Ŗܡ]O db ا]6sC:+88<q?4B9Br860/á_HkM8()JδBb! ?3v@.5N^k_0 R{&əi/0t 9Z;#I[}NYD#"6?l7u4дb#W U_rVÜy'B:TpjUl=(VZB/ATJW:1|`8 2h6. }| E6xH,Ͱcp )bo#n*1dxزc_ C/eWX̋4'p?DUdl"v k"D aaX$3'wj}Sv0tkGqIdaDki}m Pw25K;7b1)+oMPl<3 j$Ra0{)*D̦\~]kq<ƌt<?O\-ܸKc{i-#nH%xײQP|HP<15lS*"O;&x.G9]3MzBSߐIl24(-xD?8H֎ͬIHd}ӽHge#fX{EɵcЖSѶgBjFkT8~S+E{qOjP~Z* =Rr*Sk蒩(Uh+|%o\8J5ݬ~f;W[|֟L4Xf BJxPnQ8w*a/8v*~Lg`$fiɭ_KNMM#cM{B8{ |sCƃyu.MD~!)H]7kpL x:S޵DS1~xw4z\=cTqA xŰduD{-2 5qbij!2" <Pq5`"|PPPfVc<1tt]uV6y$aFx<`nTUvDVq`J$Ҿ7 4Uۜrp !l;/69@mHy3 ;^á >>TJ62 ?~cYL T}l23봀y1[CZ֒=DZp#*2U$5ᶄC9\f0+t=3vFY5ބsk~?"Q;6- b4F M3nZzӌx LAlM@S*ł|sпF簪``UʼnDv|l ~z6IU5J"L&lVYK+pbH5hX$@H[B`'X BIZbW~.OH=ې\K ™<6yEo&0KZ1;Z ORslrR3nĮOٜb@F `j[݂ Lg:5xg HYVkTJSf){pjM f>8%̯f9H ,]mRIؼ+tf,%c ϶@-[˪9QXR'rSQ%îeIjTܚKw“!L\M%P΄l a#d%&'Dž&@콸9h[(a<qμxPD\S10]=PsG. q_j-)!* s3*āb*C~ 'T}U^{Yvq>#DJ(5:XyS]W~X8,Py4,nu*܌x)gQH+>p!PdaVsڮ?A5b:b}%.FFNe^zY235-١16Ou8moRYMjA4s<'g\jBg⩊ST%XүJ[qtd4=]~ɶ^@cߢyX:>X]3S}.~u}bv^a {HzEEExwZgc"0j *%j!CB)aWWcԶavWp9A=#˩躮}3SUC US9"TS۠/;gXPlgJޭ-[`✛ \5])P|sD(;S9{ތT h=dpVKCL&jEԻ#,W_z2R΍}w2x/hud=%##|S”Zw0B~QapPogeRkVLYݓXkBAr )O`Y+l0p(Bt&R*f,൸,Z9bf& îdS7eB7u]A=Yv|~fб ڊV(H=3*$!k0έ&XiY J{]nj֨CuLJD [qѥO'؝/I 6Ila0Hy /N[YR%U{:Kip;.$qQƳl/oNUiΝ*+yW(7 r_9ԗM-ĄPgfM3螩Fod%I&J|i&U⹅g|x|_U=5CU6BVV\\xg T5eEJ^UrKUP-JDF_dY3EmLRoYpeZIi?zj Zl(AVc XHùWlKo]sո!( VM%=')Փɡ:vCϿG.t-՝rΡޕV~zϭΈL-EY¤|G g.4x@:EzàbfQ.9LAJDr)7Z&k-A*[KcR,lWB:)RB5)G:Xs܎޲QkY"E}vŇ&F UgQdsYX|3{*,t{ l_oOh$-BnEspl*?(xiFJkNZNJ" LkS۔ά&]&SfeXp-=Jn йGݕqsA9ZʠE>Ecw,kR$x<YAc*GK(Eߎ:}1wdh3wǐn&׏.iZd8 ̹aqWf3lh{&Xk9md4O4ec_PucJcU90##c}7 s&\DkA.R[3]!=}㾡ȅ5en=xb0k/иe>]qf}?C%dF.UBxoK_*cɅHt\&SZ]g6|Rܕ׵#~7zh,baZ5$4P8~Z9fp@5P-L^~F_kl]Oj:H m5ZX7MДAN NhLUbUUo5ik{'|{W+yb9s99urc!" L18#USc9+T"jߥ UlkWwIck bAo5~XPTl3Y\lʖh뺖e/ /`)61-'U!5% Rv3Jpn/ a;3b N] mϭn[|N`1wA$ZEvkW8"QU]9#YSJGޚ|﹒TSSylSMcx<3# OK/ɊS)Bp90?xMOvճE7<-Ni`+ L3Ķ,c $]'aS٢hLĥ]B\ו,gIMyUMHF_ya^'ד59w2&@…c X.nfğmh?G U=PC'TJ qGrOķr#L-9<ҚA5ߛ|7"k,"H!󑫉c)'ow"͊w6@Q4Tk z ݒo~ܠk jיKdz(8vm".Rԭ&s0 )3\ZM>7w((Z;r x^,8t%5f3 vS؈0- %BoV}gn[Ys3sW$-ZrQ ^u.ejEL%KRM!&3Q] ~D9ʦ@"Ra)2CiҌ:& 5m V*Fp՜?SppjI=lB^N8ؒ}qVF+]DDr&_D-VͶz&=D2< IpP}0Yɯ~pӰ<&bɔ!BVPܬ$:S"iCHDGh{ne`R_S%b0{G\Lֈsix-$:coMdqNQ^| e#W%r! R[~\`Dc.v{))ɼRAjN5KM\J땹=+f15w̞sIJs9Ţc7jkrl /X&B)).U8SYvɠ4,d2 ZBvGK~K7 (`Nv#y0~m#*Cp&,-<`U g\pV Y cK>,l$$Z`c ggPP:z=ed@5Cs!r,O\#~.X9@`NܡH9e޷Lm~/~jcϗYxI} ah&R[qeŕ>4$ݦ&C$mve)SL 5߷XfOgpDQ]P Ʈ"_:a Ebopx/[)23Cw7GF-`a)lcD`G=aK)><,eLmcy]2],jjg}țVSlZ(PӅ5ܼ?h#+ÝUc*P`5r8Ŧ_=[ $h,/oluK*/=lyd[d4C,bCbqN^%Y#9NS&b.K_%à9O3l#wxNjwd-9]=jP;ISZ+.VCz)}wN*)?4;ſ)k5o).c䞞v~3F=j>AE׻}ع|”ԏZ뙼B΂/8b?~b1r"5vJZҖxX{Pc`1,ރ8FLxmK+xf\T swȥg׺KH_rͶڮC߇\ȯS(k~kM)M+a QwH9*3Phk*{ Ÿﲂa@0 +d)SYG- AtM{`e`̞H/} `lb1+zS!rJZQ-AspU@l98zĎ}3ՇXN, JSwod0@lKQ`wjbR+41S$ĿX.?aQpAGGm5 +uc9)US٨yabu]06]غ.o#8 = L7+oW18(FhiM0wf#m\1]_fru&?L~(`sT$' g/- |ɑEX'A5MiZwObs Dm@M&9h۞b[?Լx;6bֿc5+_sC:YGPY T,{* AR*Am؟u RM ETo5ܼ b75E;1[eie}.E̤5 ZWjěIIYVz뗈Z~}D"4E 78"PXIMwAiVse$biφxSk*W`ʦɊU;c*f$}%DWBe. If^tL3l-lEkBSTg3N,0GC8 )ْ|m-.$zC\L}B9乘շtgyMaCML*q.XA8U+_P,szJzZWMDk\餶u';A霚czن ~v#°x\Zo#8W/NB,1np7F*P0XKYO[)c'7*hHNQ_2[.9sSo$D.u"x&Ƹ]q1m΄`P P t.Uoi]7c@45 JNZTlѭ*Y LEb\CկTAJ~Q~uNG=ٔoKТSq/"Ê/brq.)cҳYFB%/yY;t._+ցYKCl'[<yE-A N%n<v·8x[bm-=Lt8@ǪR|c_c-(?͠%OcF|{5\sI$z<[=P[6TL-P`zrl4sJ{ݙI2ymdx3Ve Z Mk~81]f$XVeѭKEoֹ5]ՄK¦aj(䥅in18Ӿeɗ<,T@ H'd=^5ݙ5eWv%o/-vɔ=x#a79sggNq[h%@L2 VX{[X.*S7 ,0/s Vk'.8g s{YY}Φc2|=\V|f- +nfүkY'q$ ;_9TX}瞰7ٽnIAUE?5ēT'YLbRdwSNne)/>?G7B9 E16{̰jT5l-P_n[h6F&g&\CU[(2縣!Zk7 v# uǭtk&.\Z˜67 A /c)% >Rem[zuiv1{80`d-A+Y@b:jir<7?{hp-b?k-K K> dIYmKjWM_ [Tjue*̡vK9s4nz>~\`\c* 7y~fU&[Ԏ/wIUK|~=U$ԒR*Tr{ ir םyϦJPYIY[>!@H+mzY.k=x&>8`x e+b-蘹lER]8B8SWNZ.f4".ՋIx5\6 _sxlXp 6f)>kLyCāgi=ЯII^LDfe™(G)z T¦pS?)8yP{$qk̸>z,#A[r1 r0ʉ2yv.nN_o>f HFfN?'^ixrΌzYBm)l[OJS&Fy*LHa3b Eox3 ⇜,Îݺؖ i138JQ<\dTj{tQ$lqlv}NI7ɛe+_ɖTO.uNpi oРқ/RR!pI"vUczWOVQO52whMsZ]z(m6<'Rd]dTRZmME}gBre?`2[zH‘n-: ڤ8FTj,S4C"f{cs[ x4G]#_.h6R*B.Ct9ey 1y8? =TL-R,Yl}*/Kvf& sje(+یSgXckb(NTǯUL.G> !?~!V6x; fem{ |5 l7XX|@AT*>д-ձ^mZVO]--W\gIHf=7PskͶbҍiVPVs~r9`lCYӮh0s͊ e>` ?M])YS4Tm0o,ީ3XhH 4;+p_;hIP8"1"3b*+}w10!#A(yg{dj >;JuXqAF[ ElizVZQ(X-i_٥gm0EHEMG"$xW*kzA˺$'85'+J@Nna[fc'Znp(a T89XM\h $ߡRi2J,Y7bUbP^Ͼ9ea2Dg.]fXcE²B>m+I[B@(ţ]*,RP>4b1(;=0O Ź:Z`AOqNJCj s< O[ΉϾN ^K]ނi7k)A<.z @$RQUef)+˟1o Cze(+[3w u2v]f-|`sޘ3‰6Y&s\K, %v ^ԬWfN%%C>+`W*I$$+$u  ;`GE}w'`^fXUk\>ތd2fI& UxFg$Q<&\,jV(+.ފAO֔b(1x@āR*:<ě-y76MigEJL9?C`Q x?BfY#U@hlX [0;nvN3woGy+RcmwPD6Ve҄[rCԩs0TluUj\ImZ*?"MgO|4ؽ^W*֐sz(Z6=,?YmJ<9UTx\˲X@g!іօ ^~z0<-GQiV,?si{R3cˡlv@ᣰE1 CxqWj)T=7n M.'٠[ Ցf2w,+ƫ]]W95<@JSLĒɢ<S{0[cY᧍k Ӊ?[L]֥~\c:Jk@yqsc)Bj;\|g0wAo+`kzA(7ViUsptK߆j>h*DDOTص(0jhB4TaբR9JlESѬ,C4߰|A=n1V [{L{y()peg*)x9U$qHjKu2tjxCjc;nvsiׇ,sg-> Sn(pib.b?A~MWtR=jonTCCwٞV*|TE窉FpH0k-T\hӱu̥עG\'MFRݭAPlwCgL8<Io #9 V3P2Dz}`bl9F`yq|~9S:h"|F 'b$2n/CӓlO\*ȭʔn]2R]Oc 9x?X\EǪ(x^,cDijr؟Hwj{xcֵ};y) \fZPaӚX{xؒܚ'Ek?Ak]- Xzz_%8a R ?\kQ#eTQ#'kyjt5P%@T`t??o}>+ü(-?7eJAh 䌈b2R*JJxGRVgzVS}*"1Q%Xu(X#bS[ב,KJFK|Ni2.mHɷT#uJ>VGșrSq0> emG/&ɔD U+XNqOf3:aeU-1Df~\O[tޢwiZgՂ V58,QTXߚAoܧ9=ג6C!RYHm7R!G>Ⱦ3g+Rig}<ѠNe1lb*`1-hN C+P?EZʼ|}amaE2.XnϤuY)ADNUahxEٴ) ZYY".XKD@C&f؂ݞN"$yԩԠhiRYsdD#5%BF=\IX2˜VO-v3z!.2oYb;xٟSjPoTrBq_Ͷ{5l-_g Y<"8rpf\iەznY=]m~3 |X$'Aa4kPtz !BHRpDwfSX5H?K[ߒ[頇]"dA\W Ғ(˗yO=&_a;u-U1:LED{dSBVO זr@K=]vTLm%۝!gCx j"礻ElL9TPrQ d"9A H^>grzS^}so6"[>[qD`XYx.o (lbs: |ūC d\( ~hEBXrfr8Q[_`cȝ쵕Dۊ2ҬzW# F|1Mqnud3Τe4|6bC[K&QlK[VN!J lQ4,fxQZR`|)ֻ85Qӥϧ˥t>꜌k@<-cggx:$Pp'[}N릈߿D/Yk>?:G*ۀH PkYw!H>7 ^7^`F__x(SHW.L@$uPpL= cBsqU$x71q=\B9^tVT#.Kmj.̘XEl}ORyngv7L u}+Q8&q6lOkL߻BKK+(OKu@ $z:[7Rdi!qU 'i]g`\UA˘ gp時arΛTৃHZ=Tg Q]iX(Eu`2\6%w1LpZ/LJzw!PqڦG!5iW)C_ik c]eqNth9Q_P̑/`TZi}, P0v Mm&fbe@\ ބQW=TuƝek-x洐]i7yZh砦E 'Hg:p #;mPgÿ%/%]ɐӵ/$b2hB\2U]^{Sgsia)J>ĬW{vkF 0\, ,KbiHuˁ 5AB3 q/w|ҝm(.='cȀT!SlDtsҎ|_xJ-(w5y$p&hf̰4HGH*1>U2P;owͪ1-z'upWLJuaJSkwj0a m힪m!M38Ͷ ދA߸vkK.8ثULi?/K0hD#Gd=P32cGN2@@mG(&$-*@̀y\_q>?r"-6 s =tXfpYL PaȠ{^Hܳ(F"'2`e9̷TﲀnP!7 -.e/,qHB ,fy vW1fˮg퓶CR&{PbS LP sd:Y 1TimSJ!7?~ȧH\[ZQ;NR0ac)Γz&:4(Z6cyleJ69uNv$~q*8F ;b(vᜋ?ae)1"rz47P6 G"DńE鶨# R Gʦ-oYA̖@iNoE*χQ4(J4x?빜TP#B3' b -Zޗ1ÜTm@̣feLA=3DF+dѝ e!*;gz~9.m>)bC|Yۡ^;JiULV=:+mK)* P(R:lLVӳ&R^Bs U7#8ĩClef&x FY)w,6 ׏K.SG NaX<}=mW簰^%sC  f]Dg P vpn~FBP,mEby(.v͞SE%7{#/jil=ݎ"[`?1P79}8+}.i}y/[,fͶf)4anIQpHC"FJJjlT IDh +|iߪ d3Y8rW=)\3ԣϺL~~=T_j1C#ÉjiV X}( 8C7z9cFg%S tŰH~2}N^t!rV\ڸu>ŭsK5`R=U3<,Rvy}XcQŋAAd9u%'A)O1PWjs+*,έwzqXAT?r-ukqb IcPLs̄sewb^E j$) QY`d>3S,#Cm \(qEa|;Qpό1_W"g8b˳_,s[xq{kY?T+6A/=CA9Ocp!ުŅVbiЗy(mه4ŶX2TM2xbXk2 ESh;ާʽGU[cH3A+f塽R TAMEɈϥmW~PZ 9ŕЦC/XmVp9/TzZRqݚ*P=2贖N*?~ìm*OA ?k"ޙX5]n[bZ/Һ4׻K{ըH;u:5{e5sDoXήMM`*s@z-lV' 'R27[#1 !Sh-ހ a%7Ţ[,(HIsjh%g6(-,֖MnFxF)T}a3b֌{ko?ǏK1@n b(u)߭(5lŒ68륊x=z_j"Q[R. P+(<[9t&ɦgGɗeMi<(h/ӓM<Ճ3fk _"Ki;]z(ޚZX5Kg{ io pjr___YXذWC3~F ~hjmܜHO&ŒJyY AtbQd YA(X滒|qk!!pNаZ8emk꾹P]W%. r =bA@cn{n=RaԠ"kyjMK_1ĞٹP3FXW>X{-%,k36ؓa]k%kT +ƂѲaKe$#TT+4ITU\J0}Y ~¬,_e!2C,;&˥=m2y?J.swOCsjC^6C:EN5,sLzٖ#Q!:q)Upe8 VrY:-m3Œ40Vջʍ|s*YǬNf,d2[tcȹ-'w!EREDR4 b@j!zzDIč"e;&}L0bl\De|7"-M %cڃ?^\Wr+l^c\B,*DR`X tmw\.&lat9A 9gkG@ /eoԇ݅01VeA#+а%lJ7~Fb ur3EQ$5"%oH-JweB 9<v:RCAUQ|qHNRFUbW!Q>:o$OFfLjւo14pce,ATX:K][XV`ZsC)8K'CwLl(&.Ow⃠&PŲJq32ukkPq?5486޻\:1:ws\Vޡmm,-dvkҮg@Vjdͱ?w[J0 Cc%ݛzLD?g'Ywز7?k;-ќKecdRv,w2aQKTaKk1gq "{ ܶګk\hެBW0~>FThvpxXiM2G̅\[B&Ҫs{3Pr\TWVՓmBDJM9f.1d h䥦ٴ|HOJZ|mB\=?~'ceN9=,}ن҅vw\PO%eѽ!sԨ-{k59s7psԜO)Ϗ.~ݢL]|KPMϏMZ/5GNjF5Y,س)`K4``5֢ m) 5]3Kp%C&RsTXëH\g= fci-k8iaнO 71~X%{WAk;n K@Ia~vg.)k'a3y/#ĐRYBGO뻟+}Yzg\>t 0;=\]6ιk`\81 /QQLRe|J$w GN!Xq1ɏ$c R+p*%F(7C4z-8QY|}LVs1G;~vnmk\zsn\.rg>(Zɽ3D&;EB5_<,}jfO|3Й=0_DZ6l-GSѲ u`Re53T(O}Rs#[ N>LR9,Hi݁ZC61EajBwUٙ44ɷWjNRІ-xtI0R.n{BNgGZ䠁ӵH~&x^ɚvJ..ʮrֳUҶf}jb؈5Ч3tnyaoNY!{c%Er,M`j>w(Q0qe=wҋ\aՍzؠ6k{rGXNvIs-*UW ^6R4ĥ^k.ﻬZ\A Û> ϵcޥh2RĖwR :~VuS%Wo}H#eCԋ)&fCR_>YMG!TY&@=7Y<43w+0HB/2sġ<<o NMfw{)Zzߔy<@d^Ե INR>H34]d2 %I$ID"fQ3̬nY:vf_*y*Thɬ=]GF0Ӂ E3=)D})(͕>kqZ]]*X49[^Yg?. mWќeTy_*դ\K P=Њ49?R9ǿU]'8/'g8LՁUZiZKrqh|:Sc(+X @¹S]\hYxZL>`vTUE]=]lW gy. ep,=~qu̥nw]43܂7zXYb}Ao3m/8]z3(S.^h^Dx<s /|@)o vP]ˈ&aٓ>' _92;Pw`UuL*!f ֻX"+<+G2؃c庩gfjl#kN*qJƐCx\XKĔ9b3Hj|y "dygLSf:>h%gDi=;@[?VC2\8Ǜ,z:0'a\>Ss"Tjbp`yé={vah\Þҟ|78ɉ!P U6E^O]nHbZZP0";YSR7 oWZH>E1 9Lg[I[*u ƍ\:]Erg /ښp骐tK}ߕv:fE MOI?8T^g:)bH[ˢ!w3V [xf0E ^Um9|(7Ƴ@":13v؟mE7EFSa!o^YoQ@hcpKa(VKp2YRED[( M bn!9oJdEBv($DxpSo[hun1ѷL@ f1 !F)6AeSR~H$^W 4K1wb$*O??Ŋ aA[ 0oxo![ZqRX +y60[ /c {6q{w/E&ЌJ ӃUg- ًw+_ XUCKl:y g pn{pHglwMS%Dg^}?ZZ?*% K{岰T]3>#S C={ՕKS"=}}aٙxh;mG[b.>aĹ IKl=te(@xsT!@vhS>OA_@m8w 3%l54M[;WG .l{H]Ig TKƔ<ٟ;hlMc7XLzcN[)(Uڙy}NtC~ɫeZ0>?qӄ< QCYqzj#_d KsNhe Kݢ ` Z>νVrb OT (a!׎` [r=`ivqj׈!:;)gX[ITJKВҖ/0f4dP5P/CEB+ k!5Xu=,a'';g{@10yIKj.>-pH)wv tQQ! rߛE>f=; ٲVks6Ľnsvǚy=uU3 )n`vq#@_Um)cJ n^`gܟU?ӑR7yÞdž+AC]H3(t%/\GS3dsY>q|,7%O5wO>$,j#&4R(XhL[r4(|y*FNV=5p?r!Oo+B:2:Q@|qYc <]}Cwl~V줭MK}y"p3byM۞h"z|]6eYMy;ZіgsERKEH/J;b[QH,h*x+!Ûj)o݂"!*ը^# fwl qe X~@¢Y7_9K)j O=L^>gbot2Lj$' 2|Z?/S1^𠖢h %`Y+ԇ|30T)8!(^k;"mkLH͔d{0ӡ->z`l89lúy\.aƘP3Z(Cw| 6ÇVy\I{YƷ;_Ax; %W0n>ؕ=3igxFo¶dϖ*&XO|!;Us\XH > G\n,#.ņ3лwwh 3Xh"w%&š:=l$Y:ݲV}8ލ͠ T$4]*p~QsZ.P3T:zRm!"wL^)(暈T^h4^'˯hkY47[2ڊPN,-lmM =߫ 6eɣ&]MfWt6 mB.tXS򋚣cϤK섓 }{H7YO4 аq;U.YĬxoݭZyl[͆@13·4|^a405f&`ʱ ]HkcMְr-h,{f#{WbQ@j?m3