Initial commit.
This commit is contained in:
171
src/core/SBSystemPreferences.h
Normal file
171
src/core/SBSystemPreferences.h
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* SBSystemPreferences.h
|
||||
*
|
||||
* Generated with:
|
||||
* sdef "/Applications/System Preferences.app" | sdp -fh --basename
|
||||
*SBSystemPreferences -o SBSystemPreferences.h
|
||||
*/
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <ScriptingBridge/ScriptingBridge.h>
|
||||
|
||||
@class SBSystemPreferencesApplication, SBSystemPreferencesDocument,
|
||||
SBSystemPreferencesWindow, SBSystemPreferencesPane,
|
||||
SBSystemPreferencesAnchor;
|
||||
|
||||
enum SBSystemPreferencesSaveOptions {
|
||||
SBSystemPreferencesSaveOptionsYes = 'yes ' /* Save the file. */,
|
||||
SBSystemPreferencesSaveOptionsNo = 'no ' /* Do not save the file. */,
|
||||
SBSystemPreferencesSaveOptionsAsk =
|
||||
'ask ' /* Ask the user whether or not to save the file. */
|
||||
};
|
||||
typedef enum SBSystemPreferencesSaveOptions SBSystemPreferencesSaveOptions;
|
||||
|
||||
enum SBSystemPreferencesPrintingErrorHandling {
|
||||
SBSystemPreferencesPrintingErrorHandlingStandard =
|
||||
'lwst' /* Standard PostScript error handling */,
|
||||
SBSystemPreferencesPrintingErrorHandlingDetailed =
|
||||
'lwdt' /* print a detailed report of PostScript errors */
|
||||
};
|
||||
typedef enum SBSystemPreferencesPrintingErrorHandling
|
||||
SBSystemPreferencesPrintingErrorHandling;
|
||||
|
||||
/*
|
||||
* Standard Suite
|
||||
*/
|
||||
|
||||
// The application's top-level scripting object.
|
||||
@interface SBSystemPreferencesApplication : SBApplication
|
||||
|
||||
- (SBElementArray*)documents;
|
||||
- (SBElementArray*)windows;
|
||||
|
||||
@property(copy, readonly) NSString* name; // The name of the application.
|
||||
@property(readonly) BOOL frontmost; // Is this the active application?
|
||||
@property(copy, readonly)
|
||||
NSString* version; // The version number of the application.
|
||||
|
||||
- (id)open:(id)x; // Open a document.
|
||||
- (void)print:(id)x
|
||||
withProperties:(NSDictionary*)withProperties
|
||||
printDialog:(BOOL)printDialog; // Print a document.
|
||||
- (void)quitSaving:
|
||||
(SBSystemPreferencesSaveOptions)saving; // Quit the application.
|
||||
- (BOOL)exists:(id)x; // Verify that an object exists.
|
||||
|
||||
@end
|
||||
|
||||
// A document.
|
||||
@interface SBSystemPreferencesDocument : SBObject
|
||||
|
||||
@property(copy, readonly) NSString* name; // Its name.
|
||||
@property(readonly) BOOL modified; // Has it been modified since the last save?
|
||||
@property(copy, readonly) NSURL* file; // Its location on disk, if it has one.
|
||||
|
||||
- (void)closeSaving:(SBSystemPreferencesSaveOptions)saving
|
||||
savingIn:(NSURL*)savingIn; // Close a document.
|
||||
- (void)saveIn:(NSURL*)in_ as:(id)as; // Save a document.
|
||||
- (void)printWithProperties:(NSDictionary*)withProperties
|
||||
printDialog:(BOOL)printDialog; // Print a document.
|
||||
- (void) delete; // Delete an object.
|
||||
- (void)duplicateTo:(SBObject*)to
|
||||
withProperties:(NSDictionary*)withProperties; // Copy an object.
|
||||
- (void)moveTo:(SBObject*)to; // Move an object to a new location.
|
||||
|
||||
@end
|
||||
|
||||
// A window.
|
||||
@interface SBSystemPreferencesWindow : SBObject
|
||||
|
||||
@property(copy, readonly) NSString* name; // The title of the window.
|
||||
- (NSInteger)id; // The unique identifier of the window.
|
||||
@property NSInteger index; // The index of the window, ordered front to back.
|
||||
@property NSRect bounds; // The bounding rectangle of the window.
|
||||
@property(readonly) BOOL closeable; // Does the window have a close button?
|
||||
@property(readonly)
|
||||
BOOL miniaturizable; // Does the window have a minimize button?
|
||||
@property BOOL miniaturized; // Is the window minimized right now?
|
||||
@property(readonly) BOOL resizable; // Can the window be resized?
|
||||
@property BOOL visible; // Is the window visible right now?
|
||||
@property(readonly) BOOL zoomable; // Does the window have a zoom button?
|
||||
@property BOOL zoomed; // Is the window zoomed right now?
|
||||
@property(copy, readonly) SBSystemPreferencesDocument*
|
||||
document; // The document whose contents are displayed in the window.
|
||||
|
||||
- (void)closeSaving:(SBSystemPreferencesSaveOptions)saving
|
||||
savingIn:(NSURL*)savingIn; // Close a document.
|
||||
- (void)saveIn:(NSURL*)in_ as:(id)as; // Save a document.
|
||||
- (void)printWithProperties:(NSDictionary*)withProperties
|
||||
printDialog:(BOOL)printDialog; // Print a document.
|
||||
- (void) delete; // Delete an object.
|
||||
- (void)duplicateTo:(SBObject*)to
|
||||
withProperties:(NSDictionary*)withProperties; // Copy an object.
|
||||
- (void)moveTo:(SBObject*)to; // Move an object to a new location.
|
||||
|
||||
@end
|
||||
|
||||
/*
|
||||
* System Preferences
|
||||
*/
|
||||
|
||||
// System Preferences top level scripting object
|
||||
@interface SBSystemPreferencesApplication (SystemPreferences)
|
||||
|
||||
- (SBElementArray*)panes;
|
||||
|
||||
@property(copy)
|
||||
SBSystemPreferencesPane* currentPane; // the currently selected pane
|
||||
@property(copy, readonly) SBSystemPreferencesWindow*
|
||||
preferencesWindow; // the main preferences window
|
||||
@property BOOL showAll; // Is SystemPrefs in show all view. (Setting to false
|
||||
// will do nothing)
|
||||
|
||||
@end
|
||||
|
||||
// a preference pane
|
||||
@interface SBSystemPreferencesPane : SBObject
|
||||
|
||||
- (SBElementArray*)anchors;
|
||||
|
||||
- (NSString*)id; // locale independent name of the preference pane; can refer
|
||||
// to a pane using the expression: pane id "<name>"
|
||||
@property(copy, readonly)
|
||||
NSString* localizedName; // localized name of the preference pane
|
||||
@property(copy, readonly) NSString* name; // name of the preference pane as it
|
||||
// appears in the title bar; can
|
||||
// refer to a pane using the
|
||||
// expression: pane "<name>"
|
||||
|
||||
- (void)closeSaving:(SBSystemPreferencesSaveOptions)saving
|
||||
savingIn:(NSURL*)savingIn; // Close a document.
|
||||
- (void)saveIn:(NSURL*)in_ as:(id)as; // Save a document.
|
||||
- (void)printWithProperties:(NSDictionary*)withProperties
|
||||
printDialog:(BOOL)printDialog; // Print a document.
|
||||
- (void) delete; // Delete an object.
|
||||
- (void)duplicateTo:(SBObject*)to
|
||||
withProperties:(NSDictionary*)withProperties; // Copy an object.
|
||||
- (void)moveTo:(SBObject*)to; // Move an object to a new location.
|
||||
- (id)reveal; // Reveals an anchor within a preference pane or preference pane
|
||||
// itself
|
||||
|
||||
@end
|
||||
|
||||
// an anchor within a preference pane
|
||||
@interface SBSystemPreferencesAnchor : SBObject
|
||||
|
||||
@property(copy, readonly)
|
||||
NSString* name; // name of the anchor within a preference pane
|
||||
|
||||
- (void)closeSaving:(SBSystemPreferencesSaveOptions)saving
|
||||
savingIn:(NSURL*)savingIn; // Close a document.
|
||||
- (void)saveIn:(NSURL*)in_ as:(id)as; // Save a document.
|
||||
- (void)printWithProperties:(NSDictionary*)withProperties
|
||||
printDialog:(BOOL)printDialog; // Print a document.
|
||||
- (void) delete; // Delete an object.
|
||||
- (void)duplicateTo:(SBObject*)to
|
||||
withProperties:(NSDictionary*)withProperties; // Copy an object.
|
||||
- (void)moveTo:(SBObject*)to; // Move an object to a new location.
|
||||
- (id)reveal; // Reveals an anchor within a preference pane or preference pane
|
||||
// itself
|
||||
|
||||
@end
|
||||
91
src/core/appearance.cpp
Normal file
91
src/core/appearance.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "appearance.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QSettings>
|
||||
|
||||
#include "settings/appearancesettingspage.h"
|
||||
|
||||
const char *Appearance::kUseCustomColorSet = "use-custom-set";
|
||||
const char *Appearance::kForegroundColor = "foreground-color";
|
||||
const char *Appearance::kBackgroundColor = "background-color";
|
||||
|
||||
const QPalette Appearance::kDefaultPalette = QPalette();
|
||||
|
||||
Appearance::Appearance(QObject *parent) : QObject(parent) {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
|
||||
QPalette p = QApplication::palette();
|
||||
background_color_ = s.value(kBackgroundColor, p.color(QPalette::WindowText)).value<QColor>();
|
||||
foreground_color_ = s.value(kForegroundColor, p.color(QPalette::Window)).value<QColor>();
|
||||
|
||||
}
|
||||
|
||||
void Appearance::LoadUserTheme() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
|
||||
bool use_a_custom_color_set = s.value(kUseCustomColorSet).toBool();
|
||||
if (!use_a_custom_color_set) return;
|
||||
|
||||
ChangeForegroundColor(foreground_color_);
|
||||
ChangeBackgroundColor(background_color_);
|
||||
|
||||
}
|
||||
|
||||
void Appearance::ResetToSystemDefaultTheme() {
|
||||
QApplication::setPalette(kDefaultPalette);
|
||||
}
|
||||
|
||||
void Appearance::ChangeForegroundColor(const QColor &color) {
|
||||
|
||||
// Get the application palette
|
||||
QPalette p = QApplication::palette();
|
||||
|
||||
// Modify the palette
|
||||
p.setColor(QPalette::WindowText, color);
|
||||
p.setColor(QPalette::Text, color);
|
||||
|
||||
// Make the modified palette the new application's palette
|
||||
QApplication::setPalette(p);
|
||||
foreground_color_ = color;
|
||||
|
||||
}
|
||||
|
||||
void Appearance::ChangeBackgroundColor(const QColor &color) {
|
||||
|
||||
// Get the application palette
|
||||
QPalette p = QApplication::palette();
|
||||
|
||||
// Modify the palette
|
||||
p.setColor(QPalette::Window, color);
|
||||
p.setColor(QPalette::Base, color);
|
||||
|
||||
// Make the modified palette the new application's palette
|
||||
QApplication::setPalette(p);
|
||||
background_color_ = color;
|
||||
|
||||
}
|
||||
|
||||
51
src/core/appearance.h
Normal file
51
src/core/appearance.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef APPEARANCE_H
|
||||
#define APPEARANCE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QPalette>
|
||||
|
||||
class Appearance : public QObject {
|
||||
public:
|
||||
explicit Appearance(QObject* parent = nullptr);
|
||||
// Load the user preferred theme, which could the default system theme or a
|
||||
// custom set of colors that user has chosen
|
||||
void LoadUserTheme();
|
||||
void ResetToSystemDefaultTheme();
|
||||
void ChangeForegroundColor(const QColor& color);
|
||||
void ChangeBackgroundColor(const QColor& color);
|
||||
|
||||
static const char* kSettingsGroup;
|
||||
static const char* kUseCustomColorSet;
|
||||
static const char* kForegroundColor;
|
||||
static const char* kBackgroundColor;
|
||||
static const QPalette kDefaultPalette;
|
||||
|
||||
private:
|
||||
QColor foreground_color_;
|
||||
QColor background_color_;
|
||||
};
|
||||
|
||||
#endif // APPEARANCE_H
|
||||
|
||||
217
src/core/application.cpp
Normal file
217
src/core/application.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "application.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "core/appearance.h"
|
||||
#include "core/database.h"
|
||||
#include "core/lazy.h"
|
||||
#include "core/player.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "engine/enginetype.h"
|
||||
#include "engine/enginedevice.h"
|
||||
#include "device/devicemanager.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collection.h"
|
||||
#include "playlist/playlistbackend.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "covermanager/coverproviders.h"
|
||||
#include "covermanager/currentartloader.h"
|
||||
#ifdef HAVE_LIBLASTFM
|
||||
#include "covermanager/lastfmcoverprovider.h"
|
||||
#endif // HAVE_LIBLASTFM
|
||||
#include "covermanager/amazoncoverprovider.h"
|
||||
#include "covermanager/discogscoverprovider.h"
|
||||
#include "covermanager/musicbrainzcoverprovider.h"
|
||||
|
||||
bool Application::kIsPortable = false;
|
||||
|
||||
class ApplicationImpl {
|
||||
public:
|
||||
ApplicationImpl(Application *app) :
|
||||
tag_reader_client_([=]() {
|
||||
TagReaderClient *client = new TagReaderClient(app);
|
||||
app->MoveToNewThread(client);
|
||||
client->Start();
|
||||
return client;
|
||||
}),
|
||||
database_([=]() {
|
||||
Database *db = new Database(app, app);
|
||||
app->MoveToNewThread(db);
|
||||
DoInAMinuteOrSo(db, SLOT(DoBackup()));
|
||||
return db;
|
||||
}),
|
||||
appearance_([=]() { return new Appearance(app); }),
|
||||
task_manager_([=]() { return new TaskManager(app); }),
|
||||
player_([=]() { return new Player(app, app); }),
|
||||
enginedevice_([=]() { return new EngineDevice(app); }),
|
||||
device_manager_([=]() { return new DeviceManager(app, app); }),
|
||||
collection_([=]() { return new Collection(app, app); }),
|
||||
playlist_backend_([=]() {
|
||||
PlaylistBackend *backend = new PlaylistBackend(app, app);
|
||||
app->MoveToThread(backend, database_->thread());
|
||||
return backend;
|
||||
}),
|
||||
playlist_manager_([=]() { return new PlaylistManager(app); }),
|
||||
cover_providers_([=]() {
|
||||
CoverProviders *cover_providers = new CoverProviders(app);
|
||||
// Initialize the repository of cover providers.
|
||||
#ifdef HAVE_LIBLASTFM
|
||||
cover_providers->AddProvider(new LastFmCoverProvider(app));
|
||||
#endif
|
||||
cover_providers->AddProvider(new AmazonCoverProvider(app));
|
||||
cover_providers->AddProvider(new DiscogsCoverProvider(app));
|
||||
cover_providers->AddProvider(new MusicbrainzCoverProvider(app));
|
||||
return cover_providers;
|
||||
}),
|
||||
album_cover_loader_([=]() {
|
||||
AlbumCoverLoader *loader = new AlbumCoverLoader(app);
|
||||
app->MoveToNewThread(loader);
|
||||
return loader;
|
||||
}),
|
||||
current_art_loader_([=]() { return new CurrentArtLoader(app, app); })
|
||||
{ }
|
||||
|
||||
Lazy<TagReaderClient> tag_reader_client_;
|
||||
Lazy<Database> database_;
|
||||
Lazy<Appearance> appearance_;
|
||||
Lazy<TaskManager> task_manager_;
|
||||
Lazy<Player> player_;
|
||||
Lazy<EngineDevice> enginedevice_;
|
||||
Lazy<DeviceManager> device_manager_;
|
||||
Lazy<Collection> collection_;
|
||||
Lazy<PlaylistBackend> playlist_backend_;
|
||||
Lazy<PlaylistManager> playlist_manager_;
|
||||
Lazy<CoverProviders> cover_providers_;
|
||||
Lazy<AlbumCoverLoader> album_cover_loader_;
|
||||
Lazy<CurrentArtLoader> current_art_loader_;
|
||||
|
||||
};
|
||||
|
||||
Application::Application(QObject *parent)
|
||||
: QObject(parent), p_(new ApplicationImpl(this)) {
|
||||
|
||||
enginedevice()->Init();
|
||||
collection()->Init();
|
||||
tag_reader_client();
|
||||
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
|
||||
// It's important that the device manager is deleted before the database.
|
||||
// Deleting the database deletes all objects that have been created in its
|
||||
// thread, including some device collection backends.
|
||||
p_->device_manager_.reset();
|
||||
|
||||
for (QThread *thread : threads_) {
|
||||
thread->quit();
|
||||
}
|
||||
|
||||
for (QThread *thread : threads_) {
|
||||
thread->wait();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::MoveToNewThread(QObject *object) {
|
||||
|
||||
QThread *thread = new QThread(this);
|
||||
|
||||
MoveToThread(object, thread);
|
||||
|
||||
thread->start();
|
||||
threads_ << thread;
|
||||
}
|
||||
|
||||
void Application::MoveToThread(QObject *object, QThread *thread) {
|
||||
object->setParent(nullptr);
|
||||
object->moveToThread(thread);
|
||||
}
|
||||
|
||||
void Application::AddError(const QString& message) { emit ErrorAdded(message); }
|
||||
|
||||
QString Application::language_without_region() const {
|
||||
const int underscore = language_name_.indexOf('_');
|
||||
if (underscore != -1) {
|
||||
return language_name_.left(underscore);
|
||||
}
|
||||
return language_name_;
|
||||
}
|
||||
|
||||
void Application::ReloadSettings() { emit SettingsChanged(); }
|
||||
|
||||
void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) {
|
||||
emit SettingsDialogRequested(page);
|
||||
}
|
||||
|
||||
AlbumCoverLoader *Application::album_cover_loader() const {
|
||||
return p_->album_cover_loader_.get();
|
||||
}
|
||||
|
||||
Appearance *Application::appearance() const { return p_->appearance_.get(); }
|
||||
|
||||
CoverProviders *Application::cover_providers() const {
|
||||
return p_->cover_providers_.get();
|
||||
}
|
||||
|
||||
CurrentArtLoader *Application::current_art_loader() const {
|
||||
return p_->current_art_loader_.get();
|
||||
}
|
||||
|
||||
Database *Application::database() const { return p_->database_.get(); }
|
||||
|
||||
DeviceManager *Application::device_manager() const {
|
||||
return p_->device_manager_.get();
|
||||
}
|
||||
|
||||
Collection *Application::collection() const { return p_->collection_.get(); }
|
||||
|
||||
CollectionBackend *Application::collection_backend() const {
|
||||
return collection()->backend();
|
||||
}
|
||||
|
||||
CollectionModel *Application::collection_model() const { return collection()->model(); }
|
||||
|
||||
Player *Application::player() const { return p_->player_.get(); }
|
||||
|
||||
PlaylistBackend *Application::playlist_backend() const {
|
||||
return p_->playlist_backend_.get();
|
||||
}
|
||||
|
||||
PlaylistManager *Application::playlist_manager() const {
|
||||
return p_->playlist_manager_.get();
|
||||
}
|
||||
|
||||
TagReaderClient *Application::tag_reader_client() const {
|
||||
return p_->tag_reader_client_.get();
|
||||
}
|
||||
|
||||
TaskManager *Application::task_manager() const {
|
||||
return p_->task_manager_.get();
|
||||
}
|
||||
|
||||
EngineDevice *Application::enginedevice() const {
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
return p_->enginedevice_.get();
|
||||
}
|
||||
103
src/core/application.h
Normal file
103
src/core/application.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef APPLICATION_H_
|
||||
#define APPLICATION_H_
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "settings/settingsdialog.h"
|
||||
|
||||
class ApplicationImpl;
|
||||
class TagReaderClient;
|
||||
class Database;
|
||||
class Appearance;
|
||||
class TaskManager;
|
||||
class Player;
|
||||
class DeviceManager;
|
||||
class Collection;
|
||||
class PlaylistBackend;
|
||||
class PlaylistManager;
|
||||
class AlbumCoverLoader;
|
||||
class CoverProviders;
|
||||
class CurrentArtLoader;
|
||||
class CollectionBackend;
|
||||
class CollectionModel;
|
||||
class EngineDevice;
|
||||
|
||||
class Application : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static bool kIsPortable;
|
||||
|
||||
explicit Application(QObject *parent = nullptr);
|
||||
~Application();
|
||||
|
||||
const QString &language_name() const { return language_name_; }
|
||||
// Same as language_name, but remove the region code at the end if there is one
|
||||
QString language_without_region() const;
|
||||
void set_language_name(const QString &name) { language_name_ = name; }
|
||||
|
||||
TagReaderClient *tag_reader_client() const;
|
||||
Database *database() const;
|
||||
Appearance *appearance() const;
|
||||
TaskManager *task_manager() const;
|
||||
Player *player() const;
|
||||
EngineDevice *enginedevice() const;
|
||||
DeviceManager *device_manager() const;
|
||||
|
||||
Collection *collection() const;
|
||||
|
||||
PlaylistBackend *playlist_backend() const;
|
||||
PlaylistManager *playlist_manager() const;
|
||||
|
||||
CoverProviders *cover_providers() const;
|
||||
AlbumCoverLoader *album_cover_loader() const;
|
||||
CurrentArtLoader *current_art_loader() const;
|
||||
|
||||
CollectionBackend *collection_backend() const;
|
||||
CollectionModel *collection_model() const;
|
||||
|
||||
void MoveToNewThread(QObject *object);
|
||||
void MoveToThread(QObject *object, QThread *thread);
|
||||
|
||||
public slots:
|
||||
void AddError(const QString &message);
|
||||
void ReloadSettings();
|
||||
void OpenSettingsDialogAtPage(SettingsDialog::Page page);
|
||||
|
||||
signals:
|
||||
void ErrorAdded(const QString &message);
|
||||
void SettingsChanged();
|
||||
void SettingsDialogRequested(SettingsDialog::Page page);
|
||||
|
||||
private:
|
||||
QString language_name_;
|
||||
std::unique_ptr<ApplicationImpl> p_;
|
||||
QList<QThread*> threads_;
|
||||
|
||||
};
|
||||
|
||||
#endif // APPLICATION_H_
|
||||
103
src/core/cachedlist.h
Normal file
103
src/core/cachedlist.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CACHEDLIST_H
|
||||
#define CACHEDLIST_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QSettings>
|
||||
|
||||
template <typename T>
|
||||
class CachedList {
|
||||
public:
|
||||
// Use a CachedList when you want to download and save a list of things from a
|
||||
// remote service, updating it only periodically.
|
||||
// T must be a registered metatype and must support being stored in
|
||||
// QSettings. This usually means you have to implement QDataStream streaming
|
||||
// operators, and use qRegisterMetaTypeStreamOperators.
|
||||
|
||||
typedef QList<T> ListType;
|
||||
|
||||
CachedList(const QString &settings_group, const QString &name, int cache_duration_secs)
|
||||
: settings_group_(settings_group), name_(name), cache_duration_secs_(cache_duration_secs) {
|
||||
}
|
||||
|
||||
void Load() {
|
||||
QSettings s;
|
||||
s.beginGroup(settings_group_);
|
||||
|
||||
last_updated_ = s.value("last_refreshed_" + name_).toDateTime();
|
||||
data_.clear();
|
||||
|
||||
const int count = s.beginReadArray(name_ + "_data");
|
||||
for (int i = 0; i < count; ++i) {
|
||||
s.setArrayIndex(i);
|
||||
data_ << s.value("value").value<T>();
|
||||
}
|
||||
s.endArray();
|
||||
}
|
||||
|
||||
void Save() const {
|
||||
QSettings s;
|
||||
s.beginGroup(settings_group_);
|
||||
|
||||
s.setValue("last_refreshed_" + name_, last_updated_);
|
||||
|
||||
s.beginWriteArray(name_ + "_data", data_.size());
|
||||
for (int i = 0; i < data_.size(); ++i) {
|
||||
s.setArrayIndex(i);
|
||||
s.setValue("value", QVariant::fromValue(data_[i]));
|
||||
}
|
||||
s.endArray();
|
||||
}
|
||||
|
||||
void Update(const ListType &data) {
|
||||
data_ = data;
|
||||
last_updated_ = QDateTime::currentDateTime();
|
||||
Save();
|
||||
}
|
||||
|
||||
bool IsStale() const {
|
||||
return last_updated_.isNull() || last_updated_.secsTo(QDateTime::currentDateTime()) > cache_duration_secs_;
|
||||
}
|
||||
|
||||
void Sort() { qSort(data_); }
|
||||
|
||||
const ListType &Data() const { return data_; }
|
||||
operator ListType() const { return data_; }
|
||||
|
||||
// Q_FOREACH support
|
||||
typedef typename ListType::const_iterator const_iterator;
|
||||
const_iterator begin() const { return data_.begin(); }
|
||||
const_iterator end() const { return data_.end(); }
|
||||
|
||||
private:
|
||||
const QString settings_group_;
|
||||
const QString name_;
|
||||
const int cache_duration_secs_;
|
||||
|
||||
QDateTime last_updated_;
|
||||
ListType data_;
|
||||
};
|
||||
|
||||
#endif // CACHEDLIST_H
|
||||
|
||||
388
src/core/commandlineoptions.cpp
Normal file
388
src/core/commandlineoptions.cpp
Normal file
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "commandlineoptions.h"
|
||||
#include "version.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <getopt.h>
|
||||
#include <iostream>
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QCoreApplication>
|
||||
#include <QFileInfo>
|
||||
|
||||
|
||||
const char *CommandlineOptions::kHelpText =
|
||||
"%1: strawberry [%2] [%3]\n"
|
||||
"\n"
|
||||
"%4:\n"
|
||||
" -p, --play %5\n"
|
||||
" -t, --play-pause %6\n"
|
||||
" -u, --pause %7\n"
|
||||
" -s, --stop %8\n"
|
||||
" -q, --stop-after-current %9\n"
|
||||
" -r, --previous %10\n"
|
||||
" -f, --next %11\n"
|
||||
" -v, --volume <value> %12\n"
|
||||
" --volume-up %13\n"
|
||||
" --volume-down %14\n"
|
||||
" --volume-increase-by %15\n"
|
||||
" --volume-decrease-by %16\n"
|
||||
" --seek-to <seconds> %17\n"
|
||||
" --seek-by <seconds> %18\n"
|
||||
" --restart-or-previous %19\n"
|
||||
"\n"
|
||||
"%20:\n"
|
||||
" -c, --create <name> %21\n"
|
||||
" -a, --append %22\n"
|
||||
" -l, --load %23\n"
|
||||
" -k, --play-track <n> %24\n"
|
||||
"\n"
|
||||
"%25:\n"
|
||||
" -o, --show-osd %26\n"
|
||||
" -y, --toggle-pretty-osd %27\n"
|
||||
" -g, --language <lang> %28\n"
|
||||
" --quiet %29\n"
|
||||
" --verbose %30\n"
|
||||
" --log-levels <levels> %31\n"
|
||||
" --version %32\n";
|
||||
|
||||
const char *CommandlineOptions::kVersionText = "Strawberry %1";
|
||||
|
||||
CommandlineOptions::CommandlineOptions(int argc, char* *argv)
|
||||
: argc_(argc),
|
||||
argv_(argv),
|
||||
url_list_action_(UrlList_None),
|
||||
player_action_(Player_None),
|
||||
set_volume_(-1),
|
||||
volume_modifier_(0),
|
||||
seek_to_(-1),
|
||||
seek_by_(0),
|
||||
play_track_at_(-1),
|
||||
show_osd_(false),
|
||||
toggle_pretty_osd_(false),
|
||||
log_levels_(logging::kDefaultLogLevels) {
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
// Remove -psn_xxx option that Mac passes when opened from Finder.
|
||||
RemoveArg("-psn", 1);
|
||||
#endif
|
||||
|
||||
// Remove the -session option that KDE passes
|
||||
RemoveArg("-session", 2);
|
||||
}
|
||||
|
||||
void CommandlineOptions::RemoveArg(const QString& starts_with, int count) {
|
||||
|
||||
for (int i = 0; i < argc_; ++i) {
|
||||
QString opt(argv_[i]);
|
||||
if (opt.startsWith(starts_with)) {
|
||||
for (int j = i; j < argc_ - count + 1; ++j) {
|
||||
argv_[j] = argv_[j + count];
|
||||
}
|
||||
argc_ -= count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool CommandlineOptions::Parse() {
|
||||
|
||||
static const struct option kOptions[] = {
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"play", no_argument, 0, 'p'},
|
||||
{"play-pause", no_argument, 0, 't'},
|
||||
{"pause", no_argument, 0, 'u'},
|
||||
{"stop", no_argument, 0, 's'},
|
||||
{"stop-after-current", no_argument, 0, 'q'},
|
||||
{"previous", no_argument, 0, 'r'},
|
||||
{"next", no_argument, 0, 'f'},
|
||||
{"volume", required_argument, 0, 'v'},
|
||||
{"volume-up", no_argument, 0, VolumeUp},
|
||||
{"volume-down", no_argument, 0, VolumeDown},
|
||||
{"volume-increase-by", required_argument, 0, VolumeIncreaseBy},
|
||||
{"volume-decrease-by", required_argument, 0, VolumeDecreaseBy},
|
||||
{"seek-to", required_argument, 0, SeekTo},
|
||||
{"seek-by", required_argument, 0, SeekBy},
|
||||
{"restart-or-previous", no_argument, 0, RestartOrPrevious},
|
||||
{"create", required_argument, 0, 'c'},
|
||||
{"append", no_argument, 0, 'a'},
|
||||
{"load", no_argument, 0, 'l'},
|
||||
{"play-track", required_argument, 0, 'k'},
|
||||
{"show-osd", no_argument, 0, 'o'},
|
||||
{"toggle-pretty-osd", no_argument, 0, 'y'},
|
||||
{"language", required_argument, 0, 'g'},
|
||||
{"quiet", no_argument, 0, Quiet},
|
||||
{"verbose", no_argument, 0, Verbose},
|
||||
{"log-levels", required_argument, 0, LogLevels},
|
||||
{"version", no_argument, 0, Version},
|
||||
{0, 0, 0, 0}};
|
||||
|
||||
// Parse the arguments
|
||||
bool ok = false;
|
||||
forever {
|
||||
int c = getopt_long(argc_, argv_, "hptusqrfv:c:alk:oyg:", kOptions, nullptr);
|
||||
|
||||
// End of the options
|
||||
if (c == -1) break;
|
||||
|
||||
switch (c) {
|
||||
case 'h': {
|
||||
QString translated_help_text =
|
||||
QString(kHelpText)
|
||||
.arg(tr("Usage"), tr("options"), tr("URL(s)"),
|
||||
tr("Player options"),
|
||||
tr("Start the playlist currently playing"),
|
||||
tr("Play if stopped, pause if playing"),
|
||||
tr("Pause playback"), tr("Stop playback"),
|
||||
tr("Stop playback after current track"))
|
||||
.arg(tr("Skip backwards in playlist"),
|
||||
tr("Skip forwards in playlist"),
|
||||
tr("Set the volume to <value> percent"),
|
||||
tr("Increase the volume by 4%"),
|
||||
tr("Decrease the volume by 4%"),
|
||||
tr("Increase the volume by <value> percent"),
|
||||
tr("Decrease the volume by <value> percent"))
|
||||
.arg(tr("Seek the currently playing track to an absolute "
|
||||
"position"),
|
||||
tr("Seek the currently playing track by a relative "
|
||||
"amount"),
|
||||
tr("Restart the track, or play the previous track if "
|
||||
"within 8 seconds of start."),
|
||||
tr("Playlist options"),
|
||||
tr("Create a new playlist with files"),
|
||||
tr("Append files/URLs to the playlist"),
|
||||
tr("Loads files/URLs, replacing current playlist"),
|
||||
tr("Play the <n>th track in the playlist"))
|
||||
.arg(tr("Other options"), tr("Display the on-screen-display"),
|
||||
tr("Toggle visibility for the pretty on-screen-display"),
|
||||
tr("Change the language"),
|
||||
tr("Equivalent to --log-levels *:1"),
|
||||
tr("Equivalent to --log-levels *:3"),
|
||||
tr("Comma separated list of class:level, level is 0-3"))
|
||||
.arg(tr("Print out version information"));
|
||||
|
||||
std::cout << translated_help_text.toLocal8Bit().constData();
|
||||
return false;
|
||||
}
|
||||
|
||||
case 'p':
|
||||
player_action_ = Player_Play;
|
||||
break;
|
||||
case 't':
|
||||
player_action_ = Player_PlayPause;
|
||||
break;
|
||||
case 'u':
|
||||
player_action_ = Player_Pause;
|
||||
break;
|
||||
case 's':
|
||||
player_action_ = Player_Stop;
|
||||
break;
|
||||
case 'q':
|
||||
player_action_ = Player_StopAfterCurrent;
|
||||
break;
|
||||
case 'r':
|
||||
player_action_ = Player_Previous;
|
||||
break;
|
||||
case 'f':
|
||||
player_action_ = Player_Next;
|
||||
break;
|
||||
case 'c':
|
||||
url_list_action_ = UrlList_CreateNew;
|
||||
playlist_name_ = QString(optarg);
|
||||
break;
|
||||
case 'a':
|
||||
url_list_action_ = UrlList_Append;
|
||||
break;
|
||||
case 'l':
|
||||
url_list_action_ = UrlList_Load;
|
||||
break;
|
||||
case 'o':
|
||||
show_osd_ = true;
|
||||
break;
|
||||
case 'y':
|
||||
toggle_pretty_osd_ = true;
|
||||
break;
|
||||
case 'g':
|
||||
language_ = QString(optarg);
|
||||
break;
|
||||
case VolumeUp:
|
||||
volume_modifier_ = +4;
|
||||
break;
|
||||
case VolumeDown:
|
||||
volume_modifier_ = -4;
|
||||
break;
|
||||
case Quiet:
|
||||
log_levels_ = "1";
|
||||
break;
|
||||
case Verbose:
|
||||
log_levels_ = "3";
|
||||
break;
|
||||
case LogLevels:
|
||||
log_levels_ = QString(optarg);
|
||||
break;
|
||||
case Version: {
|
||||
QString version_text = QString(kVersionText).arg(STRAWBERRY_VERSION_DISPLAY);
|
||||
std::cout << version_text.toLocal8Bit().constData() << std::endl;
|
||||
std::exit(0);
|
||||
}
|
||||
case 'v':
|
||||
set_volume_ = QString(optarg).toInt(&ok);
|
||||
if (!ok) set_volume_ = -1;
|
||||
break;
|
||||
|
||||
case VolumeIncreaseBy:
|
||||
volume_modifier_ = QString(optarg).toInt(&ok);
|
||||
if (!ok) volume_modifier_ = 0;
|
||||
break;
|
||||
|
||||
case VolumeDecreaseBy:
|
||||
volume_modifier_ = -QString(optarg).toInt(&ok);
|
||||
if (!ok) volume_modifier_ = 0;
|
||||
break;
|
||||
|
||||
case SeekTo:
|
||||
seek_to_ = QString(optarg).toInt(&ok);
|
||||
if (!ok) seek_to_ = -1;
|
||||
break;
|
||||
|
||||
case SeekBy:
|
||||
seek_by_ = QString(optarg).toInt(&ok);
|
||||
if (!ok) seek_by_ = 0;
|
||||
break;
|
||||
|
||||
case RestartOrPrevious:
|
||||
player_action_ = Player_RestartOrPrevious;
|
||||
break;
|
||||
|
||||
case 'k':
|
||||
play_track_at_ = QString(optarg).toInt(&ok);
|
||||
if (!ok) play_track_at_ = -1;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get any filenames or URLs following the arguments
|
||||
for (int i = optind; i < argc_; ++i) {
|
||||
QString value = QFile::decodeName(argv_[i]);
|
||||
QFileInfo file_info(value);
|
||||
if (file_info.exists())
|
||||
urls_ << QUrl::fromLocalFile(file_info.canonicalFilePath());
|
||||
else
|
||||
urls_ << QUrl::fromUserInput(value);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool CommandlineOptions::is_empty() const {
|
||||
return player_action_ == Player_None &&
|
||||
set_volume_ == -1 &&
|
||||
volume_modifier_ == 0 &&
|
||||
seek_to_ == -1 &&
|
||||
seek_by_ == 0 &&
|
||||
play_track_at_ == -1 &&
|
||||
show_osd_ == false &&
|
||||
toggle_pretty_osd_ == false &&
|
||||
urls_.isEmpty();
|
||||
}
|
||||
|
||||
bool CommandlineOptions::contains_play_options() const {
|
||||
return player_action_ != Player_None || play_track_at_ != -1 || !urls_.isEmpty();
|
||||
}
|
||||
|
||||
QByteArray CommandlineOptions::Serialize() const {
|
||||
|
||||
QBuffer buf;
|
||||
buf.open(QIODevice::WriteOnly);
|
||||
|
||||
QDataStream s(&buf);
|
||||
s << *this;
|
||||
buf.close();
|
||||
|
||||
return buf.data().toBase64();
|
||||
|
||||
}
|
||||
|
||||
void CommandlineOptions::Load(const QByteArray &serialized) {
|
||||
|
||||
QByteArray copy = QByteArray::fromBase64(serialized);
|
||||
QBuffer buf(©);
|
||||
buf.open(QIODevice::ReadOnly);
|
||||
|
||||
QDataStream s(&buf);
|
||||
s >> *this;
|
||||
|
||||
}
|
||||
|
||||
QString CommandlineOptions::tr(const char *source_text) {
|
||||
return QObject::tr(source_text);
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream &s, const CommandlineOptions &a) {
|
||||
|
||||
s << qint32(a.player_action_)
|
||||
<< qint32(a.url_list_action_)
|
||||
<< a.set_volume_
|
||||
<< a.volume_modifier_
|
||||
<< a.seek_to_
|
||||
<< a.seek_by_
|
||||
<< a.play_track_at_
|
||||
<< a.show_osd_
|
||||
<< a.urls_
|
||||
<< a.log_levels_
|
||||
<< a.toggle_pretty_osd_;
|
||||
|
||||
return s;
|
||||
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream &s, CommandlineOptions &a) {
|
||||
|
||||
quint32 player_action = 0;
|
||||
quint32 url_list_action = 0;
|
||||
s >> player_action
|
||||
>> url_list_action
|
||||
>> a.set_volume_
|
||||
>> a.volume_modifier_
|
||||
>> a.seek_to_
|
||||
>> a.seek_by_
|
||||
>> a.play_track_at_
|
||||
>> a.show_osd_
|
||||
>> a.urls_
|
||||
>> a.log_levels_
|
||||
>> a.toggle_pretty_osd_;
|
||||
a.player_action_ = CommandlineOptions::PlayerAction(player_action);
|
||||
a.url_list_action_ = CommandlineOptions::UrlListAction(url_list_action);
|
||||
|
||||
return s;
|
||||
|
||||
}
|
||||
|
||||
128
src/core/commandlineoptions.h
Normal file
128
src/core/commandlineoptions.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef COMMANDLINEOPTIONS_H
|
||||
#define COMMANDLINEOPTIONS_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QUrl>
|
||||
#include <QDataStream>
|
||||
|
||||
class CommandlineOptions {
|
||||
friend QDataStream &operator<<(QDataStream &s, const CommandlineOptions &a);
|
||||
friend QDataStream &operator>>(QDataStream &s, CommandlineOptions &a);
|
||||
|
||||
public:
|
||||
explicit CommandlineOptions(int argc = 0, char **argv = nullptr);
|
||||
|
||||
static const char *kHelpText;
|
||||
static const char *kVersionText;
|
||||
|
||||
// Don't change the values or order, these get serialised and sent to
|
||||
// possibly a different version of Strawberry
|
||||
enum UrlListAction {
|
||||
UrlList_Append = 0,
|
||||
UrlList_Load = 1,
|
||||
UrlList_None = 2,
|
||||
UrlList_CreateNew = 3,
|
||||
};
|
||||
enum PlayerAction {
|
||||
Player_None = 0,
|
||||
Player_Play = 1,
|
||||
Player_PlayPause = 2,
|
||||
Player_Pause = 3,
|
||||
Player_Stop = 4,
|
||||
Player_Previous = 5,
|
||||
Player_Next = 6,
|
||||
Player_RestartOrPrevious = 7,
|
||||
Player_StopAfterCurrent = 8,
|
||||
};
|
||||
|
||||
bool Parse();
|
||||
|
||||
bool is_empty() const;
|
||||
bool contains_play_options() const;
|
||||
|
||||
UrlListAction url_list_action() const { return url_list_action_; }
|
||||
PlayerAction player_action() const { return player_action_; }
|
||||
int set_volume() const { return set_volume_; }
|
||||
int volume_modifier() const { return volume_modifier_; }
|
||||
int seek_to() const { return seek_to_; }
|
||||
int seek_by() const { return seek_by_; }
|
||||
int play_track_at() const { return play_track_at_; }
|
||||
bool show_osd() const { return show_osd_; }
|
||||
bool toggle_pretty_osd() const { return toggle_pretty_osd_; }
|
||||
QList<QUrl> urls() const { return urls_; }
|
||||
QString language() const { return language_; }
|
||||
QString log_levels() const { return log_levels_; }
|
||||
QString playlist_name() const { return playlist_name_; }
|
||||
|
||||
QByteArray Serialize() const;
|
||||
void Load(const QByteArray &serialized);
|
||||
|
||||
private:
|
||||
// These are "invalid" characters to pass to getopt_long for options that
|
||||
// shouldn't have a short (single character) option.
|
||||
enum LongOptions {
|
||||
VolumeUp = 256,
|
||||
VolumeDown,
|
||||
SeekTo,
|
||||
SeekBy,
|
||||
Quiet,
|
||||
Verbose,
|
||||
LogLevels,
|
||||
Version,
|
||||
VolumeIncreaseBy,
|
||||
VolumeDecreaseBy,
|
||||
RestartOrPrevious
|
||||
};
|
||||
|
||||
QString tr(const char *source_text);
|
||||
void RemoveArg(const QString &starts_with, int count);
|
||||
|
||||
private:
|
||||
int argc_;
|
||||
char **argv_;
|
||||
|
||||
UrlListAction url_list_action_;
|
||||
PlayerAction player_action_;
|
||||
|
||||
// Don't change the type of these.
|
||||
int set_volume_;
|
||||
int volume_modifier_;
|
||||
int seek_to_;
|
||||
int seek_by_;
|
||||
int play_track_at_;
|
||||
bool show_osd_;
|
||||
bool toggle_pretty_osd_;
|
||||
QString language_;
|
||||
QString log_levels_;
|
||||
QString playlist_name_;
|
||||
|
||||
QList<QUrl> urls_;
|
||||
};
|
||||
|
||||
QDataStream &operator<<(QDataStream &s, const CommandlineOptions &a);
|
||||
QDataStream &operator>>(QDataStream &s, CommandlineOptions &a);
|
||||
|
||||
#endif // COMMANDLINEOPTIONS_H
|
||||
|
||||
684
src/core/database.cpp
Normal file
684
src/core/database.cpp
Normal file
@@ -0,0 +1,684 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "database.h"
|
||||
#include "scopedtransaction.h"
|
||||
#include "utilities.h"
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/taskmanager.h"
|
||||
|
||||
#include <boost/scope_exit.hpp>
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QLibrary>
|
||||
#include <QLibraryInfo>
|
||||
#include <QSqlDriver>
|
||||
#include <QSqlQuery>
|
||||
#include <QtDebug>
|
||||
#include <QThread>
|
||||
#include <QUrl>
|
||||
#include <QVariant>
|
||||
|
||||
const char *Database::kDatabaseFilename = "strawberry.db";
|
||||
const int Database::kSchemaVersion = 0;
|
||||
const char *Database::kMagicAllSongsTables = "%allsongstables";
|
||||
|
||||
int Database::sNextConnectionId = 1;
|
||||
QMutex Database::sNextConnectionIdMutex;
|
||||
|
||||
Database::Token::Token(const QString &token, int start, int end)
|
||||
: token(token), start_offset(start), end_offset(end) {}
|
||||
|
||||
struct sqlite3_tokenizer_module {
|
||||
|
||||
int iVersion;
|
||||
int (*xCreate)(int argc, /* Size of argv array */
|
||||
const char *const *argv, /* Tokenizer argument strings */
|
||||
sqlite3_tokenizer** ppTokenizer); /* OUT: Created tokenizer */
|
||||
|
||||
int (*xDestroy)(sqlite3_tokenizer *pTokenizer);
|
||||
|
||||
int (*xOpen)(
|
||||
sqlite3_tokenizer *pTokenizer, /* Tokenizer object */
|
||||
const char *pInput, int nBytes, /* Input buffer */
|
||||
sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */
|
||||
);
|
||||
|
||||
int (*xClose)(sqlite3_tokenizer_cursor *pCursor);
|
||||
|
||||
int (*xNext)(
|
||||
sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */
|
||||
const char* *ppToken, int *pnBytes, /* OUT: Normalized text for token */
|
||||
int *piStartOffset, /* OUT: Byte offset of token in input buffer */
|
||||
int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */
|
||||
int *piPosition); /* OUT: Number of tokens returned before this one */
|
||||
};
|
||||
|
||||
struct sqlite3_tokenizer {
|
||||
const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */
|
||||
/* Tokenizer implementations will typically add additional fields */
|
||||
};
|
||||
|
||||
struct sqlite3_tokenizer_cursor {
|
||||
sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */
|
||||
/* Tokenizer implementations will typically add additional fields */
|
||||
};
|
||||
|
||||
sqlite3_tokenizer_module *Database::sFTSTokenizer = nullptr;
|
||||
|
||||
int Database::FTSCreate(int argc, const char *const *argv, sqlite3_tokenizer **tokenizer) {
|
||||
|
||||
*tokenizer = reinterpret_cast<sqlite3_tokenizer*>(new UnicodeTokenizer);
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
int Database::FTSDestroy(sqlite3_tokenizer *tokenizer) {
|
||||
|
||||
UnicodeTokenizer *real_tokenizer = reinterpret_cast<UnicodeTokenizer*>(tokenizer);
|
||||
delete real_tokenizer;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
int Database::FTSOpen(sqlite3_tokenizer *pTokenizer, const char *input, int bytes, sqlite3_tokenizer_cursor **cursor) {
|
||||
|
||||
UnicodeTokenizerCursor *new_cursor = new UnicodeTokenizerCursor;
|
||||
new_cursor->pTokenizer = pTokenizer;
|
||||
new_cursor->position = 0;
|
||||
|
||||
QString str = QString::fromUtf8(input, bytes).toLower();
|
||||
QChar *data = str.data();
|
||||
// Decompose and strip punctuation.
|
||||
QList<Token> tokens;
|
||||
QString token;
|
||||
int start_offset = 0;
|
||||
int offset = 0;
|
||||
for (int i = 0; i < str.length(); ++i) {
|
||||
QChar c = data[i];
|
||||
ushort unicode = c.unicode();
|
||||
if (unicode <= 0x007f) {
|
||||
offset += 1;
|
||||
}
|
||||
else if (unicode >= 0x0080 && unicode <= 0x07ff) {
|
||||
offset += 2;
|
||||
}
|
||||
else if (unicode >= 0x0800) {
|
||||
offset += 3;
|
||||
}
|
||||
// Unicode astral planes unsupported in Qt?
|
||||
/*else if (unicode >= 0x010000 && unicode <= 0x10ffff) {
|
||||
offset += 4;
|
||||
}*/
|
||||
|
||||
if (!data[i].isLetterOrNumber()) {
|
||||
// Token finished.
|
||||
if (token.length() != 0) {
|
||||
tokens << Token(token, start_offset, offset - 1);
|
||||
start_offset = offset;
|
||||
token.clear();
|
||||
}
|
||||
else {
|
||||
++start_offset;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (data[i].decompositionTag() != QChar::NoDecomposition) {
|
||||
token.push_back(data[i].decomposition()[0]);
|
||||
} else {
|
||||
token.push_back(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (i == str.length() - 1) {
|
||||
if (token.length() != 0) {
|
||||
tokens << Token(token, start_offset, offset);
|
||||
token.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_cursor->tokens = tokens;
|
||||
*cursor = reinterpret_cast<sqlite3_tokenizer_cursor*>(new_cursor);
|
||||
|
||||
return SQLITE_OK;
|
||||
|
||||
}
|
||||
|
||||
int Database::FTSClose(sqlite3_tokenizer_cursor *cursor) {
|
||||
|
||||
UnicodeTokenizerCursor *real_cursor = reinterpret_cast<UnicodeTokenizerCursor*>(cursor);
|
||||
delete real_cursor;
|
||||
|
||||
return SQLITE_OK;
|
||||
|
||||
}
|
||||
|
||||
int Database::FTSNext(sqlite3_tokenizer_cursor *cursor, const char* *token, int *bytes, int *start_offset, int *end_offset, int *position) {
|
||||
|
||||
UnicodeTokenizerCursor *real_cursor = reinterpret_cast<UnicodeTokenizerCursor*>(cursor);
|
||||
|
||||
QList<Token> tokens = real_cursor->tokens;
|
||||
if (real_cursor->position >= tokens.size()) {
|
||||
return SQLITE_DONE;
|
||||
}
|
||||
|
||||
Token t = tokens[real_cursor->position];
|
||||
QByteArray utf8 = t.token.toUtf8();
|
||||
*token = utf8.constData();
|
||||
*bytes = utf8.size();
|
||||
*start_offset = t.start_offset;
|
||||
*end_offset = t.end_offset;
|
||||
*position = real_cursor->position++;
|
||||
|
||||
real_cursor->current_utf8 = utf8;
|
||||
|
||||
return SQLITE_OK;
|
||||
|
||||
}
|
||||
|
||||
void Database::StaticInit() {
|
||||
|
||||
sFTSTokenizer = new sqlite3_tokenizer_module;
|
||||
sFTSTokenizer->iVersion = 0;
|
||||
sFTSTokenizer->xCreate = &Database::FTSCreate;
|
||||
sFTSTokenizer->xDestroy = &Database::FTSDestroy;
|
||||
sFTSTokenizer->xOpen = &Database::FTSOpen;
|
||||
sFTSTokenizer->xNext = &Database::FTSNext;
|
||||
sFTSTokenizer->xClose = &Database::FTSClose;
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
Database::Database(Application *app, QObject *parent, const QString &database_name) :
|
||||
QObject(parent),
|
||||
app_(app),
|
||||
mutex_(QMutex::Recursive),
|
||||
injected_database_name_(database_name),
|
||||
query_hash_(0),
|
||||
startup_schema_version_(-1) {
|
||||
|
||||
{
|
||||
QMutexLocker l(&sNextConnectionIdMutex);
|
||||
connection_id_ = sNextConnectionId++;
|
||||
}
|
||||
|
||||
directory_ = QDir::toNativeSeparators(Utilities::GetConfigPath(Utilities::Path_Root));
|
||||
|
||||
QMutexLocker l(&mutex_);
|
||||
Connect();
|
||||
|
||||
}
|
||||
|
||||
QSqlDatabase Database::Connect() {
|
||||
|
||||
QMutexLocker l(&connect_mutex_);
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
if (!QFile::exists(directory_)) {
|
||||
QDir dir;
|
||||
if (!dir.mkpath(directory_)) {
|
||||
}
|
||||
}
|
||||
|
||||
const QString connection_id = QString("%1_thread_%2").arg(connection_id_).arg(reinterpret_cast<quint64>(QThread::currentThread()));
|
||||
|
||||
// Try to find an existing connection for this thread
|
||||
QSqlDatabase db = QSqlDatabase::database(connection_id);
|
||||
if (db.isOpen()) {
|
||||
return db;
|
||||
}
|
||||
|
||||
db = QSqlDatabase::addDatabase("QSQLITE", connection_id);
|
||||
|
||||
if (!injected_database_name_.isNull())
|
||||
db.setDatabaseName(injected_database_name_);
|
||||
else
|
||||
db.setDatabaseName(directory_ + "/" + kDatabaseFilename);
|
||||
|
||||
if (!db.open()) {
|
||||
app_->AddError("Database: " + db.lastError().text());
|
||||
return db;
|
||||
}
|
||||
|
||||
// Find Sqlite3 functions in the Qt plugin.
|
||||
StaticInit();
|
||||
|
||||
{
|
||||
|
||||
#ifdef SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER
|
||||
// In case sqlite>=3.12 is compiled without -DSQLITE_ENABLE_FTS3_TOKENIZER (generally a good idea due to security reasons) the fts3 support should be enabled explicitly.
|
||||
QVariant v = db.driver()->handle();
|
||||
if (v.isValid() && qstrcmp(v.typeName(), "sqlite3*") == 0) {
|
||||
sqlite3 *handle = *static_cast<sqlite3**>(v.data());
|
||||
if (handle) sqlite3_db_config(handle, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, NULL);
|
||||
}
|
||||
#endif
|
||||
QSqlQuery set_fts_tokenizer(db);
|
||||
set_fts_tokenizer.prepare("SELECT fts3_tokenizer(:name, :pointer)");
|
||||
set_fts_tokenizer.bindValue(":name", "unicode");
|
||||
set_fts_tokenizer.bindValue(":pointer", QByteArray(reinterpret_cast<const char*>(&sFTSTokenizer), sizeof(&sFTSTokenizer)));
|
||||
if (!set_fts_tokenizer.exec()) {
|
||||
qLog(Warning) << "Couldn't register FTS3 tokenizer : " << set_fts_tokenizer.lastError();
|
||||
}
|
||||
// Implicit invocation of ~QSqlQuery() when leaving the scope
|
||||
// to release any remaining database locks!
|
||||
}
|
||||
|
||||
if (db.tables().count() == 0) {
|
||||
// Set up initial schema
|
||||
qLog(Info) << "Creating initial database schema";
|
||||
UpdateDatabaseSchema(0, db);
|
||||
}
|
||||
|
||||
// Attach external databases
|
||||
for (const QString &key : attached_databases_.keys()) {
|
||||
QString filename = attached_databases_[key].filename_;
|
||||
|
||||
if (!injected_database_name_.isNull()) filename = injected_database_name_;
|
||||
|
||||
// Attach the db
|
||||
QSqlQuery q(db);
|
||||
q.prepare("ATTACH DATABASE :filename AS :alias");
|
||||
q.bindValue(":filename", filename);
|
||||
q.bindValue(":alias", key);
|
||||
if (!q.exec()) {
|
||||
qFatal("Couldn't attach external database '%s'", key.toLatin1().constData());
|
||||
}
|
||||
}
|
||||
|
||||
if (startup_schema_version_ == -1) {
|
||||
UpdateMainSchema(&db);
|
||||
}
|
||||
|
||||
// We might have to initialise the schema in some attached databases now, if
|
||||
// they were deleted and don't match up with the main schema version.
|
||||
for (const QString &key : attached_databases_.keys()) {
|
||||
if (attached_databases_[key].is_temporary_ && attached_databases_[key].schema_.isEmpty())
|
||||
continue;
|
||||
// Find out if there are any tables in this database
|
||||
QSqlQuery q(db);
|
||||
q.prepare(QString("SELECT ROWID FROM %1.sqlite_master WHERE type='table'").arg(key));
|
||||
if (!q.exec() || !q.next()) {
|
||||
q.finish();
|
||||
ExecSchemaCommandsFromFile(db, attached_databases_[key].schema_, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return db;
|
||||
|
||||
}
|
||||
|
||||
void Database::UpdateMainSchema(QSqlDatabase *db) {
|
||||
|
||||
// Get the database's schema version
|
||||
int schema_version = 0;
|
||||
{
|
||||
QSqlQuery q("SELECT version FROM schema_version", *db);
|
||||
if (q.next()) schema_version = q.value(0).toInt();
|
||||
// Implicit invocation of ~QSqlQuery() when leaving the scope
|
||||
// to release any remaining database locks!
|
||||
}
|
||||
|
||||
startup_schema_version_ = schema_version;
|
||||
|
||||
if (schema_version > kSchemaVersion) {
|
||||
qLog(Warning) << "The database schema (version" << schema_version << ") is newer than I was expecting";
|
||||
return;
|
||||
}
|
||||
if (schema_version < kSchemaVersion) {
|
||||
// Update the schema
|
||||
for (int v = schema_version + 1; v <= kSchemaVersion; ++v) {
|
||||
UpdateDatabaseSchema(v, *db);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Database::RecreateAttachedDb(const QString &database_name) {
|
||||
|
||||
if (!attached_databases_.contains(database_name)) {
|
||||
qLog(Warning) << "Attached database does not exist:" << database_name;
|
||||
return;
|
||||
}
|
||||
|
||||
const QString filename = attached_databases_[database_name].filename_;
|
||||
|
||||
QMutexLocker l(&mutex_);
|
||||
{
|
||||
QSqlDatabase db(Connect());
|
||||
|
||||
QSqlQuery q(db);
|
||||
q.prepare("DETACH DATABASE :alias");
|
||||
q.bindValue(":alias", database_name);
|
||||
if (!q.exec()) {
|
||||
qLog(Warning) << "Failed to detach database" << database_name;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!QFile::remove(filename)) {
|
||||
qLog(Warning) << "Failed to remove file" << filename;
|
||||
}
|
||||
}
|
||||
|
||||
// We can't just re-attach the database now because it needs to be done for
|
||||
// each thread. Close all the database connections, so each thread will
|
||||
// re-attach it when they next connect.
|
||||
for (const QString &name : QSqlDatabase::connectionNames()) {
|
||||
QSqlDatabase::removeDatabase(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Database::AttachDatabase(const QString &database_name, const AttachedDatabase &database) {
|
||||
attached_databases_[database_name] = database;
|
||||
}
|
||||
|
||||
void Database::AttachDatabaseOnDbConnection(const QString &database_name, const AttachedDatabase &database, QSqlDatabase &db) {
|
||||
|
||||
AttachDatabase(database_name, database);
|
||||
|
||||
// Attach the db
|
||||
QSqlQuery q(db);
|
||||
q.prepare("ATTACH DATABASE :filename AS :alias");
|
||||
q.bindValue(":filename", database.filename_);
|
||||
q.bindValue(":alias", database_name);
|
||||
if (!q.exec()) {
|
||||
qFatal("Couldn't attach external database '%s'", database_name.toLatin1().constData());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Database::DetachDatabase(const QString &database_name) {
|
||||
|
||||
QMutexLocker l(&mutex_);
|
||||
{
|
||||
QSqlDatabase db(Connect());
|
||||
|
||||
QSqlQuery q(db);
|
||||
q.prepare("DETACH DATABASE :alias");
|
||||
q.bindValue(":alias", database_name);
|
||||
if (!q.exec()) {
|
||||
qLog(Warning) << "Failed to detach database" << database_name;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
attached_databases_.remove(database_name);
|
||||
|
||||
}
|
||||
|
||||
void Database::UpdateDatabaseSchema(int version, QSqlDatabase &db) {
|
||||
|
||||
QString filename;
|
||||
if (version == 0) filename = ":/schema/schema.sql";
|
||||
else filename = QString(":/schema/schema-%1.sql").arg(version);
|
||||
|
||||
qLog(Debug) << "Applying database schema update" << version << "from" << filename;
|
||||
ExecSchemaCommandsFromFile(db, filename, version - 1);
|
||||
|
||||
}
|
||||
|
||||
void Database::UrlEncodeFilenameColumn(const QString &table, QSqlDatabase &db) {
|
||||
|
||||
QSqlQuery select(db);
|
||||
select.prepare(QString("SELECT ROWID, filename FROM %1").arg(table));
|
||||
QSqlQuery update(db);
|
||||
update.prepare(QString("UPDATE %1 SET filename=:filename WHERE ROWID=:id").arg(table));
|
||||
select.exec();
|
||||
if (CheckErrors(select)) return;
|
||||
|
||||
while (select.next()) {
|
||||
const int rowid = select.value(0).toInt();
|
||||
const QString filename = select.value(1).toString();
|
||||
|
||||
if (filename.isEmpty() || filename.contains("://")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QUrl url = QUrl::fromLocalFile(filename);
|
||||
|
||||
update.bindValue(":filename", url.toEncoded());
|
||||
update.bindValue(":id", rowid);
|
||||
update.exec();
|
||||
CheckErrors(update);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Database::ExecSchemaCommandsFromFile(QSqlDatabase &db, const QString &filename, int schema_version, bool in_transaction) {
|
||||
|
||||
// Open and read the database schema
|
||||
QFile schema_file(filename);
|
||||
if (!schema_file.open(QIODevice::ReadOnly))
|
||||
qFatal("Couldn't open schema file %s", filename.toUtf8().constData());
|
||||
ExecSchemaCommands(db, QString::fromUtf8(schema_file.readAll()), schema_version, in_transaction);
|
||||
|
||||
}
|
||||
|
||||
void Database::ExecSchemaCommands(QSqlDatabase &db, const QString &schema, int schema_version, bool in_transaction) {
|
||||
|
||||
// Run each command
|
||||
const QStringList commands(schema.split(QRegExp("; *\n\n")));
|
||||
|
||||
// We don't want this list to reflect possible DB schema changes
|
||||
// so we initialize it before executing any statements.
|
||||
// If no outer transaction is provided the song tables need to
|
||||
// be queried before beginning an inner transaction! Otherwise
|
||||
// DROP TABLE commands on song tables may fail due to database
|
||||
// locks.
|
||||
const QStringList song_tables(SongsTables(db, schema_version));
|
||||
|
||||
if (!in_transaction) {
|
||||
ScopedTransaction inner_transaction(&db);
|
||||
ExecSongTablesCommands(db, song_tables, commands);
|
||||
inner_transaction.Commit();
|
||||
}
|
||||
else {
|
||||
ExecSongTablesCommands(db, song_tables, commands);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Database::ExecSongTablesCommands(QSqlDatabase &db, const QStringList &song_tables, const QStringList &commands) {
|
||||
|
||||
for (const QString &command : commands) {
|
||||
// There are now lots of "songs" tables that need to have the same schema:
|
||||
// songs, magnatune_songs, and device_*_songs. We allow a magic value
|
||||
// in the schema files to update all songs tables at once.
|
||||
if (command.contains(kMagicAllSongsTables)) {
|
||||
for (const QString &table : song_tables) {
|
||||
// Another horrible hack: device songs tables don't have matching _fts
|
||||
// tables, so if this command tries to touch one, ignore it.
|
||||
if (table.startsWith("device_") &&
|
||||
command.contains(QString(kMagicAllSongsTables) + "_fts")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
qLog(Info) << "Updating" << table << "for" << kMagicAllSongsTables;
|
||||
QString new_command(command);
|
||||
new_command.replace(kMagicAllSongsTables, table);
|
||||
QSqlQuery query(db.exec(new_command));
|
||||
if (CheckErrors(query))
|
||||
qFatal("Unable to update music collection database");
|
||||
}
|
||||
} else {
|
||||
QSqlQuery query(db.exec(command));
|
||||
if (CheckErrors(query)) qFatal("Unable to update music collection database");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QStringList Database::SongsTables(QSqlDatabase &db, int schema_version) const {
|
||||
|
||||
QStringList ret;
|
||||
|
||||
// look for the tables in the main db
|
||||
for (const QString &table : db.tables()) {
|
||||
if (table == "songs" || table.endsWith("_songs")) ret << table;
|
||||
}
|
||||
|
||||
// look for the tables in attached dbs
|
||||
for (const QString &key : attached_databases_.keys()) {
|
||||
QSqlQuery q(db);
|
||||
q.prepare(QString("SELECT NAME FROM %1.sqlite_master WHERE type='table' AND name='songs' OR name LIKE '%songs'").arg(key));
|
||||
if (q.exec()) {
|
||||
while (q.next()) {
|
||||
QString tab_name = key + "." + q.value(0).toString();
|
||||
ret << tab_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret << "playlist_items";
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
bool Database::CheckErrors(const QSqlQuery &query) {
|
||||
|
||||
QSqlError last_error = query.lastError();
|
||||
if (last_error.isValid()) {
|
||||
qLog(Error) << "db error: " << last_error;
|
||||
qLog(Error) << "faulty query: " << query.lastQuery();
|
||||
qLog(Error) << "bound values: " << query.boundValues();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
bool Database::IntegrityCheck(QSqlDatabase db) {
|
||||
|
||||
qLog(Debug) << "Starting database integrity check";
|
||||
int task_id = app_->task_manager()->StartTask(tr("Integrity check"));
|
||||
|
||||
bool ok = false;
|
||||
bool error_reported = false;
|
||||
// Ask for 10 error messages at most.
|
||||
QSqlQuery q(QString("PRAGMA integrity_check(10)"), db);
|
||||
while (q.next()) {
|
||||
QString message = q.value(0).toString();
|
||||
|
||||
// If no errors are found, a single row with the value "ok" is returned
|
||||
if (message == "ok") {
|
||||
ok = true;
|
||||
break;
|
||||
} else {
|
||||
if (!error_reported) { app_->AddError(tr("Database corruption detected.")); }
|
||||
app_->AddError("Database: " + message);
|
||||
error_reported = true;
|
||||
}
|
||||
}
|
||||
|
||||
app_->task_manager()->SetTaskFinished(task_id);
|
||||
|
||||
return ok;
|
||||
|
||||
}
|
||||
|
||||
void Database::DoBackup() {
|
||||
|
||||
QSqlDatabase db(this->Connect());
|
||||
|
||||
// Before we overwrite anything, make sure the database is not corrupt
|
||||
QMutexLocker l(&mutex_);
|
||||
const bool ok = IntegrityCheck(db);
|
||||
|
||||
if (ok) {
|
||||
BackupFile(db.databaseName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool Database::OpenDatabase(const QString &filename, sqlite3 **connection) const {
|
||||
|
||||
int ret = sqlite3_open(filename.toUtf8(), connection);
|
||||
if (ret != 0) {
|
||||
if (*connection) {
|
||||
const char *error_message = sqlite3_errmsg(*connection);
|
||||
qLog(Error) << "Failed to open database for backup:" << filename << error_message;
|
||||
}
|
||||
else {
|
||||
qLog(Error) << "Failed to open database for backup:" << filename;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
void Database::BackupFile(const QString &filename) {
|
||||
|
||||
qLog(Debug) << "Starting database backup";
|
||||
QString dest_filename = QString("%1.bak").arg(filename);
|
||||
const int task_id = app_->task_manager()->StartTask(tr("Backing up database"));
|
||||
|
||||
sqlite3 *source_connection = nullptr;
|
||||
sqlite3 *dest_connection = nullptr;
|
||||
|
||||
BOOST_SCOPE_EXIT((source_connection)(dest_connection)(task_id)(app_)) {
|
||||
// Harmless to call sqlite3_close() with a nullptr pointer.
|
||||
sqlite3_close(source_connection);
|
||||
sqlite3_close(dest_connection);
|
||||
app_->task_manager()->SetTaskFinished(task_id);
|
||||
}
|
||||
BOOST_SCOPE_EXIT_END
|
||||
|
||||
bool success = OpenDatabase(filename, &source_connection);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
success = OpenDatabase(dest_filename, &dest_connection);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
sqlite3_backup *backup = sqlite3_backup_init(dest_connection, "main", source_connection, "main");
|
||||
if (!backup) {
|
||||
const char *error_message = sqlite3_errmsg(dest_connection);
|
||||
qLog(Error) << "Failed to start database backup:" << error_message;
|
||||
return;
|
||||
}
|
||||
|
||||
int ret = SQLITE_OK;
|
||||
do {
|
||||
ret = sqlite3_backup_step(backup, 16);
|
||||
const int page_count = sqlite3_backup_pagecount(backup);
|
||||
app_->task_manager()->SetTaskProgress(
|
||||
task_id, page_count - sqlite3_backup_remaining(backup), page_count);
|
||||
} while (ret == SQLITE_OK);
|
||||
|
||||
if (ret != SQLITE_DONE) {
|
||||
qLog(Error) << "Database backup failed";
|
||||
}
|
||||
|
||||
sqlite3_backup_finish(backup);
|
||||
|
||||
}
|
||||
|
||||
175
src/core/database.h
Normal file
175
src/core/database.h
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DATABASE_H
|
||||
#define DATABASE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlError>
|
||||
#include <QStringList>
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include "gtest/gtest_prod.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
struct sqlite3_tokenizer;
|
||||
struct sqlite3_tokenizer_cursor;
|
||||
struct sqlite3_tokenizer_module;
|
||||
}
|
||||
|
||||
class Application;
|
||||
|
||||
class Database : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Database(Application *app, QObject *parent = nullptr, const QString &database_name = QString());
|
||||
|
||||
struct AttachedDatabase {
|
||||
AttachedDatabase() {}
|
||||
AttachedDatabase(const QString &filename, const QString &schema, bool is_temporary)
|
||||
: filename_(filename), schema_(schema), is_temporary_(is_temporary) {}
|
||||
|
||||
QString filename_;
|
||||
QString schema_;
|
||||
bool is_temporary_;
|
||||
};
|
||||
|
||||
static const int kSchemaVersion;
|
||||
static const char *kDatabaseFilename;
|
||||
static const char *kMagicAllSongsTables;
|
||||
|
||||
QSqlDatabase Connect();
|
||||
bool CheckErrors(const QSqlQuery &query);
|
||||
QMutex *Mutex() { return &mutex_; }
|
||||
|
||||
void RecreateAttachedDb(const QString &database_name);
|
||||
void ExecSchemaCommands(QSqlDatabase &db, const QString &schema, int schema_version, bool in_transaction = false);
|
||||
|
||||
int startup_schema_version() const { return startup_schema_version_; }
|
||||
int current_schema_version() const { return kSchemaVersion; }
|
||||
|
||||
void AttachDatabase(const QString &database_name, const AttachedDatabase &database);
|
||||
void AttachDatabaseOnDbConnection(const QString &database_name, const AttachedDatabase &database, QSqlDatabase &db);
|
||||
void DetachDatabase(const QString &database_name);
|
||||
|
||||
signals:
|
||||
void Error(const QString &message);
|
||||
|
||||
public slots:
|
||||
void DoBackup();
|
||||
|
||||
private:
|
||||
void UpdateMainSchema(QSqlDatabase *db);
|
||||
|
||||
void ExecSchemaCommandsFromFile(QSqlDatabase &db, const QString &filename, int schema_version, bool in_transaction = false);
|
||||
void ExecSongTablesCommands(QSqlDatabase &db, const QStringList &song_tables, const QStringList &commands);
|
||||
|
||||
void UpdateDatabaseSchema(int version, QSqlDatabase &db);
|
||||
void UrlEncodeFilenameColumn(const QString &table, QSqlDatabase &db);
|
||||
QStringList SongsTables(QSqlDatabase &db, int schema_version) const;
|
||||
bool IntegrityCheck(QSqlDatabase db);
|
||||
void BackupFile(const QString &filename);
|
||||
bool OpenDatabase(const QString &filename, sqlite3 **connection) const;
|
||||
|
||||
Application *app_;
|
||||
|
||||
// Alias -> filename
|
||||
QMap<QString, AttachedDatabase> attached_databases_;
|
||||
|
||||
QString directory_;
|
||||
QMutex connect_mutex_;
|
||||
QMutex mutex_;
|
||||
|
||||
// This ID makes the QSqlDatabase name unique to the object as well as the
|
||||
// thread
|
||||
int connection_id_;
|
||||
|
||||
static QMutex sNextConnectionIdMutex;
|
||||
static int sNextConnectionId;
|
||||
|
||||
// Used by tests
|
||||
QString injected_database_name_;
|
||||
|
||||
uint query_hash_;
|
||||
QStringList query_cache_;
|
||||
|
||||
// This is the schema version of Strawberry's DB from the app's last run.
|
||||
int startup_schema_version_;
|
||||
|
||||
FRIEND_TEST(DatabaseTest, FTSOpenParsesSimpleInput);
|
||||
FRIEND_TEST(DatabaseTest, FTSOpenParsesUTF8Input);
|
||||
FRIEND_TEST(DatabaseTest, FTSOpenParsesMultipleTokens);
|
||||
FRIEND_TEST(DatabaseTest, FTSCursorWorks);
|
||||
FRIEND_TEST(DatabaseTest, FTSOpenLeavesCyrillicQueries);
|
||||
|
||||
// Do static initialisation like loading sqlite functions.
|
||||
static void StaticInit();
|
||||
|
||||
typedef int (*Sqlite3CreateFunc)(sqlite3*, const char*, int, int, void*, void (*)(sqlite3_context*, int, sqlite3_value**), void (*)(sqlite3_context*, int, sqlite3_value**), void (*)(sqlite3_context*));
|
||||
|
||||
static sqlite3_tokenizer_module *sFTSTokenizer;
|
||||
|
||||
static int FTSCreate(int argc, const char *const *argv, sqlite3_tokenizer **tokenizer);
|
||||
static int FTSDestroy(sqlite3_tokenizer *tokenizer);
|
||||
static int FTSOpen(sqlite3_tokenizer *tokenizer, const char *input, int bytes, sqlite3_tokenizer_cursor **cursor);
|
||||
static int FTSClose(sqlite3_tokenizer_cursor *cursor);
|
||||
static int FTSNext(sqlite3_tokenizer_cursor *cursor, const char **token, int *bytes, int *start_offset, int *end_offset, int *position);
|
||||
|
||||
struct Token {
|
||||
Token(const QString &token, int start, int end);
|
||||
QString token;
|
||||
int start_offset;
|
||||
int end_offset;
|
||||
};
|
||||
|
||||
// Based on sqlite3_tokenizer.
|
||||
struct UnicodeTokenizer {
|
||||
const sqlite3_tokenizer_module *pModule;
|
||||
};
|
||||
|
||||
struct UnicodeTokenizerCursor {
|
||||
const sqlite3_tokenizer *pTokenizer;
|
||||
|
||||
QList<Token> tokens;
|
||||
int position;
|
||||
QByteArray current_utf8;
|
||||
};
|
||||
};
|
||||
|
||||
class MemoryDatabase : public Database {
|
||||
public:
|
||||
MemoryDatabase(Application *app, QObject *parent = nullptr)
|
||||
: Database(app, parent, ":memory:") {}
|
||||
~MemoryDatabase() {
|
||||
// Make sure Qt doesn't reuse the same database
|
||||
QSqlDatabase::removeDatabase(Connect().connectionName());
|
||||
}
|
||||
};
|
||||
|
||||
#endif // DATABASE_H
|
||||
|
||||
47
src/core/dbusscreensaver.cpp
Normal file
47
src/core/dbusscreensaver.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "dbusscreensaver.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDBusInterface>
|
||||
#include <QDBusReply>
|
||||
|
||||
DBusScreensaver::DBusScreensaver(const QString &service, const QString &path, const QString &interface)
|
||||
: service_(service), path_(path), interface_(interface) {}
|
||||
|
||||
void DBusScreensaver::Inhibit() {
|
||||
|
||||
QDBusInterface gnome_screensaver("org.gnome.ScreenSaver", "/", "org.gnome.ScreenSaver");
|
||||
QDBusReply<quint32> reply = gnome_screensaver.call("Inhibit", QCoreApplication::applicationName(), QObject::tr("Visualizations"));
|
||||
if (reply.isValid()) {
|
||||
cookie_ = reply.value();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DBusScreensaver::Uninhibit() {
|
||||
|
||||
QDBusInterface gnome_screensaver("org.gnome.ScreenSaver", "/", "org.gnome.ScreenSaver");
|
||||
gnome_screensaver.call("UnInhibit", cookie_);
|
||||
|
||||
}
|
||||
45
src/core/dbusscreensaver.h
Normal file
45
src/core/dbusscreensaver.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DBUSSCREENSAVER_H
|
||||
#define DBUSSCREENSAVER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "screensaver.h"
|
||||
|
||||
class DBusScreensaver : public Screensaver {
|
||||
public:
|
||||
DBusScreensaver(const QString &service, const QString &path, const QString &interface);
|
||||
|
||||
void Inhibit();
|
||||
void Uninhibit();
|
||||
|
||||
private:
|
||||
QString service_;
|
||||
QString path_;
|
||||
QString interface_;
|
||||
|
||||
quint32 cookie_;
|
||||
};
|
||||
|
||||
#endif
|
||||
123
src/core/deletefiles.cpp
Normal file
123
src/core/deletefiles.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "deletefiles.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <QTimer>
|
||||
#include <QThread>
|
||||
#include <QUrl>
|
||||
|
||||
#include "musicstorage.h"
|
||||
#include "taskmanager.h"
|
||||
|
||||
const int DeleteFiles::kBatchSize = 50;
|
||||
|
||||
DeleteFiles::DeleteFiles(TaskManager* task_manager, std::shared_ptr<MusicStorage> storage)
|
||||
: thread_(nullptr),
|
||||
task_manager_(task_manager),
|
||||
storage_(storage),
|
||||
started_(false),
|
||||
task_id_(0),
|
||||
progress_(0) {
|
||||
original_thread_ = thread();
|
||||
}
|
||||
|
||||
DeleteFiles::~DeleteFiles() {}
|
||||
|
||||
void DeleteFiles::Start(const SongList &songs) {
|
||||
|
||||
if (thread_) return;
|
||||
|
||||
songs_ = songs;
|
||||
|
||||
task_id_ = task_manager_->StartTask(tr("Deleting files"));
|
||||
task_manager_->SetTaskBlocksCollectionScans(true);
|
||||
|
||||
thread_ = new QThread;
|
||||
connect(thread_, SIGNAL(started()), SLOT(ProcessSomeFiles()));
|
||||
|
||||
moveToThread(thread_);
|
||||
thread_->start();
|
||||
|
||||
}
|
||||
|
||||
void DeleteFiles::Start(const QStringList &filenames) {
|
||||
|
||||
SongList songs;
|
||||
for (const QString &filename : filenames) {
|
||||
Song song;
|
||||
song.set_url(QUrl::fromLocalFile(filename));
|
||||
songs << song;
|
||||
}
|
||||
|
||||
Start(songs);
|
||||
|
||||
}
|
||||
|
||||
void DeleteFiles::ProcessSomeFiles() {
|
||||
|
||||
if (!started_) {
|
||||
storage_->StartDelete();
|
||||
started_ = true;
|
||||
}
|
||||
|
||||
// None left?
|
||||
if (progress_ >= songs_.count()) {
|
||||
task_manager_->SetTaskProgress(task_id_, progress_, songs_.count());
|
||||
|
||||
storage_->FinishCopy(songs_with_errors_.isEmpty());
|
||||
|
||||
task_manager_->SetTaskFinished(task_id_);
|
||||
|
||||
emit Finished(songs_with_errors_);
|
||||
|
||||
// Move back to the original thread so deleteLater() can get called in
|
||||
// the main thread's event loop
|
||||
moveToThread(original_thread_);
|
||||
deleteLater();
|
||||
|
||||
// Stop this thread
|
||||
thread_->quit();
|
||||
return;
|
||||
}
|
||||
|
||||
// We process files in batches so we can be cancelled part-way through.
|
||||
|
||||
const int n = qMin(songs_.count(), progress_ + kBatchSize);
|
||||
for (; progress_ < n; ++progress_) {
|
||||
task_manager_->SetTaskProgress(task_id_, progress_, songs_.count());
|
||||
|
||||
const Song &song = songs_[progress_];
|
||||
|
||||
MusicStorage::DeleteJob job;
|
||||
job.metadata_ = song;
|
||||
|
||||
if (!storage_->DeleteFromStorage(job)) {
|
||||
songs_with_errors_ << song;
|
||||
}
|
||||
}
|
||||
|
||||
QTimer::singleShot(0, this, SLOT(ProcessSomeFiles()));
|
||||
|
||||
}
|
||||
|
||||
70
src/core/deletefiles.h
Normal file
70
src/core/deletefiles.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DELETEFILES_H
|
||||
#define DELETEFILES_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "song.h"
|
||||
|
||||
class MusicStorage;
|
||||
class TaskManager;
|
||||
|
||||
class DeleteFiles : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DeleteFiles(TaskManager *task_manager, std::shared_ptr<MusicStorage> storage);
|
||||
~DeleteFiles();
|
||||
|
||||
static const int kBatchSize;
|
||||
|
||||
void Start(const SongList& songs);
|
||||
void Start(const QStringList& filenames);
|
||||
|
||||
signals:
|
||||
void Finished(const SongList& songs_with_errors);
|
||||
|
||||
private slots:
|
||||
void ProcessSomeFiles();
|
||||
|
||||
private:
|
||||
QThread *thread_;
|
||||
QThread *original_thread_;
|
||||
TaskManager *task_manager_;
|
||||
std::shared_ptr<MusicStorage> storage_;
|
||||
|
||||
SongList songs_;
|
||||
|
||||
bool started_;
|
||||
|
||||
int task_id_;
|
||||
int progress_;
|
||||
|
||||
SongList songs_with_errors_;
|
||||
};
|
||||
|
||||
#endif // DELETEFILES_H
|
||||
|
||||
71
src/core/filesystemmusicstorage.cpp
Normal file
71
src/core/filesystemmusicstorage.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "filesystemmusicstorage.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/utilities.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QUrl>
|
||||
|
||||
FilesystemMusicStorage::FilesystemMusicStorage(const QString &root)
|
||||
: root_(root) {}
|
||||
|
||||
bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) {
|
||||
|
||||
const QFileInfo src = QFileInfo(job.source_);
|
||||
const QFileInfo dest = QFileInfo(root_ + "/" + job.destination_);
|
||||
|
||||
// Don't do anything if the destination is the same as the source
|
||||
if (src == dest) return true;
|
||||
|
||||
// Create directories as required
|
||||
QDir dir;
|
||||
if (!dir.mkpath(dest.absolutePath())) {
|
||||
qLog(Warning) << "Failed to create directory" << dest.dir().absolutePath();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove the destination file if it exists and we want to overwrite
|
||||
if (job.overwrite_ && dest.exists()) QFile::remove(dest.absoluteFilePath());
|
||||
|
||||
// Copy or move
|
||||
if (job.remove_original_)
|
||||
return QFile::rename(src.absoluteFilePath(), dest.absoluteFilePath());
|
||||
else
|
||||
return QFile::copy(src.absoluteFilePath(), dest.absoluteFilePath());
|
||||
|
||||
}
|
||||
|
||||
bool FilesystemMusicStorage::DeleteFromStorage(const DeleteJob &job) {
|
||||
|
||||
QString path = job.metadata_.url().toLocalFile();
|
||||
QFileInfo fileInfo(path);
|
||||
|
||||
if (fileInfo.isDir())
|
||||
return Utilities::RemoveRecursive(path);
|
||||
else
|
||||
return QFile::remove(path);
|
||||
|
||||
}
|
||||
|
||||
43
src/core/filesystemmusicstorage.h
Normal file
43
src/core/filesystemmusicstorage.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FILESYSTEMMUSICSTORAGE_H
|
||||
#define FILESYSTEMMUSICSTORAGE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "musicstorage.h"
|
||||
|
||||
class FilesystemMusicStorage : public virtual MusicStorage {
|
||||
public:
|
||||
explicit FilesystemMusicStorage(const QString &root);
|
||||
~FilesystemMusicStorage() {}
|
||||
|
||||
QString LocalPath() const { return root_; }
|
||||
|
||||
bool CopyToStorage(const CopyJob &job);
|
||||
bool DeleteFromStorage(const DeleteJob &job);
|
||||
|
||||
private:
|
||||
QString root_;
|
||||
};
|
||||
|
||||
#endif // FILESYSTEMMUSICSTORAGE_H
|
||||
|
||||
46
src/core/filesystemwatcherinterface.cpp
Normal file
46
src/core/filesystemwatcherinterface.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "filesystemwatcherinterface.h"
|
||||
|
||||
#include "qtfslistener.h"
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
#include "macfslistener.h"
|
||||
#endif
|
||||
|
||||
FileSystemWatcherInterface::FileSystemWatcherInterface(QObject *parent)
|
||||
: QObject(parent) {}
|
||||
|
||||
FileSystemWatcherInterface *FileSystemWatcherInterface::Create(QObject *parent) {
|
||||
FileSystemWatcherInterface *ret;
|
||||
#ifdef Q_OS_DARWIN
|
||||
ret = new MacFSListener(parent);
|
||||
#else
|
||||
ret = new QtFSListener(parent);
|
||||
#endif
|
||||
|
||||
ret->Init();
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
44
src/core/filesystemwatcherinterface.h
Normal file
44
src/core/filesystemwatcherinterface.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FILESYSTEMWATCHERINTERFACE_H
|
||||
#define FILESYSTEMWATCHERINTERFACE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class FileSystemWatcherInterface : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FileSystemWatcherInterface(QObject *parent = nullptr);
|
||||
virtual void Init() {}
|
||||
virtual void AddPath(const QString& path) = 0;
|
||||
virtual void RemovePath(const QString& path) = 0;
|
||||
virtual void Clear() = 0;
|
||||
|
||||
static FileSystemWatcherInterface* Create(QObject *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void PathChanged(const QString &path);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
183
src/core/flowlayout.cpp
Normal file
183
src/core/flowlayout.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "flowlayout.h"
|
||||
//! [1]
|
||||
FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing)
|
||||
: QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing)
|
||||
{
|
||||
setContentsMargins(margin, margin, margin, margin);
|
||||
}
|
||||
|
||||
FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing)
|
||||
: m_hSpace(hSpacing), m_vSpace(vSpacing) {
|
||||
setContentsMargins(margin, margin, margin, margin);
|
||||
}
|
||||
//! [1]
|
||||
|
||||
//! [2]
|
||||
FlowLayout::~FlowLayout() {
|
||||
QLayoutItem* item;
|
||||
while ((item = takeAt(0))) delete item;
|
||||
}
|
||||
//! [2]
|
||||
|
||||
//! [3]
|
||||
void FlowLayout::addItem(QLayoutItem* item) { itemList.append(item); }
|
||||
//! [3]
|
||||
|
||||
//! [4]
|
||||
int FlowLayout::horizontalSpacing() const {
|
||||
if (m_hSpace >= 0) {
|
||||
return m_hSpace;
|
||||
} else {
|
||||
return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
|
||||
}
|
||||
}
|
||||
|
||||
int FlowLayout::verticalSpacing() const {
|
||||
if (m_vSpace >= 0) {
|
||||
return m_vSpace;
|
||||
} else {
|
||||
return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
|
||||
}
|
||||
}
|
||||
//! [4]
|
||||
|
||||
//! [5]
|
||||
int FlowLayout::count() const { return itemList.size(); }
|
||||
|
||||
QLayoutItem* FlowLayout::itemAt(int index) const {
|
||||
return itemList.value(index);
|
||||
}
|
||||
|
||||
QLayoutItem* FlowLayout::takeAt(int index) {
|
||||
if (index >= 0 && index < itemList.size())
|
||||
return itemList.takeAt(index);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
//! [5]
|
||||
|
||||
//! [6]
|
||||
Qt::Orientations FlowLayout::expandingDirections() const { return 0; }
|
||||
//! [6]
|
||||
|
||||
//! [7]
|
||||
bool FlowLayout::hasHeightForWidth() const { return true; }
|
||||
|
||||
int FlowLayout::heightForWidth(int width) const {
|
||||
int height = doLayout(QRect(0, 0, width, 0), true);
|
||||
return height;
|
||||
}
|
||||
//! [7]
|
||||
|
||||
//! [8]
|
||||
void FlowLayout::setGeometry(const QRect& rect) {
|
||||
QLayout::setGeometry(rect);
|
||||
doLayout(rect, false);
|
||||
}
|
||||
|
||||
QSize FlowLayout::sizeHint() const { return minimumSize(); }
|
||||
|
||||
QSize FlowLayout::minimumSize() const {
|
||||
QSize size;
|
||||
for (QLayoutItem* item : itemList)
|
||||
size = size.expandedTo(item->minimumSize());
|
||||
|
||||
size += QSize(2 * margin(), 2 * margin());
|
||||
return size;
|
||||
}
|
||||
//! [8]
|
||||
|
||||
//! [9]
|
||||
int FlowLayout::doLayout(const QRect& rect, bool testOnly) const {
|
||||
int left, top, right, bottom;
|
||||
getContentsMargins(&left, &top, &right, &bottom);
|
||||
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
|
||||
int x = effectiveRect.x();
|
||||
int y = effectiveRect.y();
|
||||
int lineHeight = 0;
|
||||
//! [9]
|
||||
|
||||
//! [10]
|
||||
for (QLayoutItem* item : itemList) {
|
||||
QWidget* wid = item->widget();
|
||||
int spaceX = horizontalSpacing();
|
||||
if (spaceX == -1)
|
||||
spaceX = wid->style()->layoutSpacing(
|
||||
QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
|
||||
int spaceY = verticalSpacing();
|
||||
if (spaceY == -1)
|
||||
spaceY = wid->style()->layoutSpacing(
|
||||
QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
|
||||
//! [10]
|
||||
//! [11]
|
||||
int nextX = x + item->sizeHint().width() + spaceX;
|
||||
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
|
||||
x = effectiveRect.x();
|
||||
y = y + lineHeight + spaceY;
|
||||
nextX = x + item->sizeHint().width() + spaceX;
|
||||
lineHeight = 0;
|
||||
}
|
||||
|
||||
if (!testOnly) item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
|
||||
|
||||
x = nextX;
|
||||
lineHeight = qMax(lineHeight, item->sizeHint().height());
|
||||
}
|
||||
return y + lineHeight - rect.y() + bottom;
|
||||
}
|
||||
//! [11]
|
||||
//! [12]
|
||||
int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const {
|
||||
QObject* parent = this->parent();
|
||||
if (!parent) {
|
||||
return -1;
|
||||
} else if (parent->isWidgetType()) {
|
||||
QWidget *pw = static_cast<QWidget *>(parent);
|
||||
return pw->style()->pixelMetric(pm, 0, pw);
|
||||
} else {
|
||||
return static_cast<QLayout *>(parent)->spacing();
|
||||
}
|
||||
}
|
||||
//! [12]
|
||||
79
src/core/flowlayout.h
Normal file
79
src/core/flowlayout.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef FLOWLAYOUT_H
|
||||
#define FLOWLAYOUT_H
|
||||
|
||||
#include <QLayout>
|
||||
#include <QRect>
|
||||
#include <QStyle>
|
||||
#include <QWidgetItem>
|
||||
//! [0]
|
||||
class FlowLayout : public QLayout {
|
||||
public:
|
||||
FlowLayout(QWidget* parent, int margin = -1, int hSpacing = -1,
|
||||
int vSpacing = -1);
|
||||
FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1);
|
||||
~FlowLayout();
|
||||
|
||||
void addItem(QLayoutItem *item);
|
||||
int horizontalSpacing() const;
|
||||
int verticalSpacing() const;
|
||||
Qt::Orientations expandingDirections() const;
|
||||
bool hasHeightForWidth() const;
|
||||
int heightForWidth(int) const;
|
||||
int count() const;
|
||||
QLayoutItem *itemAt(int index) const;
|
||||
QSize minimumSize() const;
|
||||
void setGeometry(const QRect &rect);
|
||||
QSize sizeHint() const;
|
||||
QLayoutItem *takeAt(int index);
|
||||
|
||||
private:
|
||||
int doLayout(const QRect &rect, bool testOnly) const;
|
||||
int smartSpacing(QStyle::PixelMetric pm) const;
|
||||
|
||||
QList<QLayoutItem *> itemList;
|
||||
int m_hSpace;
|
||||
int m_vSpace;
|
||||
};
|
||||
//! [0]
|
||||
|
||||
#endif
|
||||
80
src/core/iconloader.cpp
Normal file
80
src/core/iconloader.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QtDebug>
|
||||
#include <QSettings>
|
||||
|
||||
#include "iconloader.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/appearance.h"
|
||||
|
||||
QList<int> IconLoader::sizes_;
|
||||
QString IconDefault(":/icons/64x64/strawberry.png");
|
||||
|
||||
void IconLoader::Init() {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
sizes_.clear();
|
||||
sizes_ << 22 << 32 << 48 << 64;
|
||||
|
||||
if (!QFile::exists(IconDefault)) {
|
||||
qLog(Error) << "Default icon does not exist" << IconDefault;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QIcon IconLoader::Load(const QString &name) {
|
||||
|
||||
QIcon ret;
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__ << name;
|
||||
|
||||
if (name.isEmpty()) {
|
||||
qLog(Warning) << "Icon name is empty!";
|
||||
ret.addFile(IconDefault, QSize(64, 64));
|
||||
return ret;
|
||||
}
|
||||
|
||||
const QString path(":icons/%1x%2/%3.png");
|
||||
for (int size : sizes_) {
|
||||
QString filename(path.arg(size).arg(size).arg(name));
|
||||
if (QFile::exists(filename)) ret.addFile(filename, QSize(size, size));
|
||||
}
|
||||
|
||||
// Load icon from system theme only if it hasn't been found
|
||||
if (ret.isNull()) {
|
||||
ret = QIcon::fromTheme(name);
|
||||
if (!ret.isNull()) return ret;
|
||||
qLog(Warning) << "Couldn't load icon" << name;
|
||||
}
|
||||
|
||||
if (ret.isNull()) {
|
||||
ret.addFile(IconDefault, QSize(64, 64));
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
38
src/core/iconloader.h
Normal file
38
src/core/iconloader.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ICONLOADER_H
|
||||
#define ICONLOADER_H
|
||||
|
||||
#include <QIcon>
|
||||
|
||||
class IconLoader {
|
||||
public:
|
||||
|
||||
static void Init();
|
||||
static QIcon Load(const QString &name);
|
||||
|
||||
private:
|
||||
IconLoader() {}
|
||||
|
||||
static QList<int> sizes_;
|
||||
};
|
||||
|
||||
#endif // ICONLOADER_H
|
||||
33
src/core/mac_delegate.h
Normal file
33
src/core/mac_delegate.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#import <AppKit/NSApplication.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "macglobalshortcutbackend.h"
|
||||
|
||||
class PlatformInterface;
|
||||
@class SPMediaKeyTap;
|
||||
|
||||
@interface AppDelegate : NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate> {
|
||||
PlatformInterface* application_handler_;
|
||||
NSMenu* dock_menu_;
|
||||
MacGlobalShortcutBackend* shortcut_handler_;
|
||||
SPMediaKeyTap* key_tap_;
|
||||
|
||||
}
|
||||
|
||||
- (id) initWithHandler: (PlatformInterface*)handler;
|
||||
|
||||
// NSApplicationDelegate
|
||||
- (BOOL) applicationShouldHandleReopen: (NSApplication*)app hasVisibleWindows:(BOOL)flag;
|
||||
- (NSMenu*) applicationDockMenu: (NSApplication*)sender;
|
||||
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification;
|
||||
- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication*)sender;
|
||||
|
||||
// NSUserNotificationCenterDelegate
|
||||
- (BOOL) userNotificationCenter: (id)center
|
||||
shouldPresentNotification: (id)notification;
|
||||
|
||||
- (void) setDockMenu: (NSMenu*)menu;
|
||||
- (MacGlobalShortcutBackend*) shortcut_handler;
|
||||
- (void) setShortcutHandler: (MacGlobalShortcutBackend*)backend;
|
||||
- (void) mediaKeyTap: (SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event;
|
||||
@end
|
||||
37
src/core/mac_startup.h
Normal file
37
src/core/mac_startup.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef MAC_STARTUP_H
|
||||
#define MAC_STARTUP_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QKeySequence>
|
||||
|
||||
class MacGlobalShortcutBackend;
|
||||
class QObject;
|
||||
class QWidget;
|
||||
|
||||
class PlatformInterface {
|
||||
public:
|
||||
// Called when the application should show itself.
|
||||
virtual void Activate() = 0;
|
||||
virtual bool LoadUrl(const QString& url) = 0;
|
||||
|
||||
virtual ~PlatformInterface() {}
|
||||
};
|
||||
|
||||
namespace mac {
|
||||
|
||||
void MacMain();
|
||||
void SetShortcutHandler(MacGlobalShortcutBackend* handler);
|
||||
void SetApplicationHandler(PlatformInterface* handler);
|
||||
void CheckForUpdates();
|
||||
|
||||
QString GetBundlePath();
|
||||
QString GetResourcesPath();
|
||||
QString GetApplicationSupportPath();
|
||||
QString GetMusicDirectory();
|
||||
|
||||
void EnableFullScreen(const QWidget& main_window);
|
||||
|
||||
} // namespace mac
|
||||
|
||||
#endif
|
||||
457
src/core/mac_startup.mm
Normal file
457
src/core/mac_startup.mm
Normal file
@@ -0,0 +1,457 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <AppKit/NSApplication.h>
|
||||
#import <AppKit/NSEvent.h>
|
||||
#import <AppKit/NSGraphics.h>
|
||||
#import <AppKit/NSNibDeclarations.h>
|
||||
#import <AppKit/NSViewController.h>
|
||||
|
||||
#import <Foundation/NSBundle.h>
|
||||
#import <Foundation/NSError.h>
|
||||
#import <Foundation/NSFileManager.h>
|
||||
#import <Foundation/NSPathUtilities.h>
|
||||
#import <Foundation/NSProcessInfo.h>
|
||||
#import <Foundation/NSThread.h>
|
||||
#import <Foundation/NSTimer.h>
|
||||
#import <Foundation/NSURL.h>
|
||||
|
||||
#import <IOKit/hidsystem/ev_keymap.h>
|
||||
|
||||
#import <Kernel/AvailabilityMacros.h>
|
||||
|
||||
#import <QuartzCore/CALayer.h>
|
||||
|
||||
#import "3rdparty/SPMediaKeyTap/SPMediaKeyTap.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "globalshortcuts.h"
|
||||
#include "mac_delegate.h"
|
||||
#include "mac_startup.h"
|
||||
#include "mac_utilities.h"
|
||||
#include "macglobalshortcutbackend.h"
|
||||
#include "utilities.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/scoped_cftyperef.h"
|
||||
#include "core/scoped_nsautorelease_pool.h"
|
||||
|
||||
#ifdef HAVE_SPARKLE
|
||||
#import <Sparkle/SUUpdater.h>
|
||||
#endif
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QEvent>
|
||||
#include <QFile>
|
||||
#include <QSettings>
|
||||
#include <QWidget>
|
||||
|
||||
#include <QtDebug>
|
||||
|
||||
QDebug operator<<(QDebug dbg, NSObject* object) {
|
||||
QString ns_format = [[NSString stringWithFormat:@"%@", object] UTF8String];
|
||||
dbg.nospace() << ns_format;
|
||||
return dbg.space();
|
||||
}
|
||||
|
||||
// Capture global media keys on Mac (Cocoa only!)
|
||||
// See: http://www.rogueamoeba.com/utm/2007/09/29/apple-keyboard-media-key-event-handling/
|
||||
|
||||
@interface MacApplication : NSApplication {
|
||||
PlatformInterface* application_handler_;
|
||||
AppDelegate* delegate_;
|
||||
// shortcut_handler_ only used to temporarily save it
|
||||
// AppDelegate does all the heavy-shortcut-lifting
|
||||
MacGlobalShortcutBackend* shortcut_handler_;
|
||||
}
|
||||
|
||||
- (MacGlobalShortcutBackend*)shortcut_handler;
|
||||
- (void)SetShortcutHandler:(MacGlobalShortcutBackend*)handler;
|
||||
|
||||
- (PlatformInterface*)application_handler;
|
||||
- (void)SetApplicationHandler:(PlatformInterface*)handler;
|
||||
|
||||
@end
|
||||
|
||||
#ifdef HAVE_BREAKPAD
|
||||
static bool BreakpadCallback(int, int, mach_port_t, void*) { return true; }
|
||||
|
||||
static BreakpadRef InitBreakpad() {
|
||||
ScopedNSAutoreleasePool pool;
|
||||
BreakpadRef breakpad = nil;
|
||||
NSDictionary* plist = [[NSBundle mainBundle] infoDictionary];
|
||||
if (plist) {
|
||||
breakpad = BreakpadCreate(plist);
|
||||
BreakpadSetFilterCallback(breakpad, &BreakpadCallback, nullptr);
|
||||
}
|
||||
[pool release];
|
||||
return breakpad;
|
||||
}
|
||||
#endif // HAVE_BREAKPAD
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (id)init {
|
||||
if ((self = [super init])) {
|
||||
application_handler_ = nil;
|
||||
shortcut_handler_ = nil;
|
||||
dock_menu_ = nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithHandler:(PlatformInterface*)handler {
|
||||
application_handler_ = handler;
|
||||
|
||||
#ifdef HAVE_BREAKPAD
|
||||
breakpad_ = InitBreakpad();
|
||||
#endif
|
||||
|
||||
// Register defaults for the whitelist of apps that want to use media keys
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[SPMediaKeyTap defaultMediaKeyUserBundleIdentifiers], kMediaKeyUsingBundleIdentifiersDefaultsKey,
|
||||
nil]];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL) applicationShouldHandleReopen: (NSApplication*)app hasVisibleWindows:(BOOL)flag {
|
||||
if (application_handler_) {
|
||||
application_handler_->Activate();
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)setDockMenu:(NSMenu*)menu {
|
||||
dock_menu_ = menu;
|
||||
}
|
||||
|
||||
- (NSMenu*)applicationDockMenu:(NSApplication*)sender {
|
||||
return dock_menu_;
|
||||
}
|
||||
|
||||
- (void)setShortcutHandler:(MacGlobalShortcutBackend*)backend {
|
||||
shortcut_handler_ = backend;
|
||||
}
|
||||
|
||||
- (MacGlobalShortcutBackend*)shortcut_handler {
|
||||
return shortcut_handler_;
|
||||
}
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification {
|
||||
key_tap_ = [[SPMediaKeyTap alloc] initWithDelegate:self];
|
||||
if ([SPMediaKeyTap usesGlobalMediaKeyTap] &&
|
||||
![[NSProcessInfo processInfo]
|
||||
isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){
|
||||
.majorVersion = 10,
|
||||
.minorVersion = 12,
|
||||
.patchVersion = 0}]) {
|
||||
[key_tap_ startWatchingMediaKeys];
|
||||
}
|
||||
else {
|
||||
qLog(Warning) << "Media key monitoring disabled";
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)application:(NSApplication*)app openFile:(NSString*)filename {
|
||||
qLog(Debug) << "Wants to open:" << [filename UTF8String];
|
||||
|
||||
if (application_handler_->LoadUrl(QString::fromUtf8([filename UTF8String]))) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)application:(NSApplication*)app openFiles:(NSArray*)filenames {
|
||||
qLog(Debug) << "Wants to open:" << filenames;
|
||||
[filenames enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL* stop) {
|
||||
[self application:app openFile:(NSString*)object];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void) mediaKeyTap: (SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event {
|
||||
NSAssert([event type] == NSSystemDefined && [event subtype] == SPSystemDefinedEventMediaKeys, @"Unexpected NSEvent in mediaKeyTap:receivedMediaKeyEvent:");
|
||||
|
||||
int key_code = (([event data1] & 0xFFFF0000) >> 16);
|
||||
int key_flags = ([event data1] & 0x0000FFFF);
|
||||
BOOL key_is_released = (((key_flags & 0xFF00) >> 8)) == 0xB;
|
||||
// not used. keep just in case
|
||||
// int key_repeat = (key_flags & 0x1);
|
||||
|
||||
if (!shortcut_handler_) {
|
||||
return;
|
||||
}
|
||||
if (key_is_released) {
|
||||
shortcut_handler_->MacMediaKeyPressed(key_code);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication*) sender {
|
||||
#ifdef HAVE_BREAKPAD
|
||||
BreakpadRelease(breakpad_);
|
||||
#endif
|
||||
return NSTerminateNow;
|
||||
}
|
||||
|
||||
- (BOOL) userNotificationCenter: (id)center shouldPresentNotification: (id)notification {
|
||||
// Always show notifications, even if Strawberry is in the foreground.
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MacApplication
|
||||
|
||||
- (id)init {
|
||||
if ((self = [super init])) {
|
||||
[self SetShortcutHandler:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (MacGlobalShortcutBackend*)shortcut_handler {
|
||||
// should be the same as delegate_'s shortcut handler
|
||||
return shortcut_handler_;
|
||||
}
|
||||
|
||||
- (void)SetShortcutHandler:(MacGlobalShortcutBackend*)handler {
|
||||
shortcut_handler_ = handler;
|
||||
if (delegate_) [delegate_ setShortcutHandler:handler];
|
||||
}
|
||||
|
||||
- (PlatformInterface*)application_handler {
|
||||
return application_handler_;
|
||||
}
|
||||
|
||||
- (void)SetApplicationHandler:(PlatformInterface*)handler {
|
||||
delegate_ = [[AppDelegate alloc] initWithHandler:handler];
|
||||
// App-shortcut-handler set before delegate is set.
|
||||
// this makes sure the delegate's shortcut_handler is set
|
||||
[delegate_ setShortcutHandler:shortcut_handler_];
|
||||
[self setDelegate:delegate_];
|
||||
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter]
|
||||
setDelegate:delegate_];
|
||||
}
|
||||
|
||||
- (void)sendEvent:(NSEvent*)event {
|
||||
// If event tap is not installed, handle events that reach the app instead
|
||||
BOOL shouldHandleMediaKeyEventLocally = ![SPMediaKeyTap usesGlobalMediaKeyTap];
|
||||
|
||||
if(shouldHandleMediaKeyEventLocally && [event type] == NSSystemDefined && [event subtype] == SPSystemDefinedEventMediaKeys) {
|
||||
[(id)[self delegate] mediaKeyTap:nil receivedMediaKeyEvent:event];
|
||||
}
|
||||
|
||||
[super sendEvent:event];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace mac {
|
||||
|
||||
void MacMain() {
|
||||
ScopedNSAutoreleasePool pool;
|
||||
// Creates and sets the magic global variable so QApplication will find it.
|
||||
[MacApplication sharedApplication];
|
||||
#ifdef HAVE_SPARKLE
|
||||
// Creates and sets the magic global variable for Sparkle.
|
||||
[[SUUpdater sharedUpdater] setDelegate:NSApp];
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetShortcutHandler(MacGlobalShortcutBackend* handler) {
|
||||
[NSApp SetShortcutHandler:handler];
|
||||
}
|
||||
|
||||
void SetApplicationHandler(PlatformInterface* handler) {
|
||||
[NSApp SetApplicationHandler:handler];
|
||||
}
|
||||
|
||||
void CheckForUpdates() {
|
||||
#ifdef HAVE_SPARKLE
|
||||
[[SUUpdater sharedUpdater] checkForUpdates:NSApp];
|
||||
#endif
|
||||
}
|
||||
|
||||
QString GetBundlePath() {
|
||||
ScopedCFTypeRef<CFURLRef> app_url(CFBundleCopyBundleURL(CFBundleGetMainBundle()));
|
||||
ScopedCFTypeRef<CFStringRef> mac_path(CFURLCopyFileSystemPath(app_url.get(), kCFURLPOSIXPathStyle));
|
||||
const char* path = CFStringGetCStringPtr(mac_path.get(), CFStringGetSystemEncoding());
|
||||
QString bundle_path = QString::fromUtf8(path);
|
||||
return bundle_path;
|
||||
}
|
||||
|
||||
QString GetResourcesPath() {
|
||||
QString bundle_path = GetBundlePath();
|
||||
return bundle_path + "/Contents/Resources";
|
||||
}
|
||||
|
||||
QString GetApplicationSupportPath() {
|
||||
ScopedNSAutoreleasePool pool;
|
||||
NSArray* paths = NSSearchPathForDirectoriesInDomains(
|
||||
NSApplicationSupportDirectory, NSUserDomainMask, YES);
|
||||
QString ret;
|
||||
if ([paths count] > 0) {
|
||||
NSString* user_path = [paths objectAtIndex:0];
|
||||
ret = QString::fromUtf8([user_path UTF8String]);
|
||||
} else {
|
||||
ret = "~/Collection/Application Support";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString GetMusicDirectory() {
|
||||
ScopedNSAutoreleasePool pool;
|
||||
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSMusicDirectory,
|
||||
NSUserDomainMask, YES);
|
||||
QString ret;
|
||||
if ([paths count] > 0) {
|
||||
NSString* user_path = [paths objectAtIndex:0];
|
||||
ret = QString::fromUtf8([user_path UTF8String]);
|
||||
} else {
|
||||
ret = "~/Music";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int MapFunctionKey(int keycode) {
|
||||
switch (keycode) {
|
||||
// Function keys
|
||||
case NSInsertFunctionKey: return Qt::Key_Insert;
|
||||
case NSDeleteFunctionKey: return Qt::Key_Delete;
|
||||
case NSPauseFunctionKey: return Qt::Key_Pause;
|
||||
case NSPrintFunctionKey: return Qt::Key_Print;
|
||||
case NSSysReqFunctionKey: return Qt::Key_SysReq;
|
||||
case NSHomeFunctionKey: return Qt::Key_Home;
|
||||
case NSEndFunctionKey: return Qt::Key_End;
|
||||
case NSLeftArrowFunctionKey: return Qt::Key_Left;
|
||||
case NSUpArrowFunctionKey: return Qt::Key_Up;
|
||||
case NSRightArrowFunctionKey: return Qt::Key_Right;
|
||||
case NSDownArrowFunctionKey: return Qt::Key_Down;
|
||||
case NSPageUpFunctionKey: return Qt::Key_PageUp;
|
||||
case NSPageDownFunctionKey: return Qt::Key_PageDown;
|
||||
case NSScrollLockFunctionKey: return Qt::Key_ScrollLock;
|
||||
case NSF1FunctionKey: return Qt::Key_F1;
|
||||
case NSF2FunctionKey: return Qt::Key_F2;
|
||||
case NSF3FunctionKey: return Qt::Key_F3;
|
||||
case NSF4FunctionKey: return Qt::Key_F4;
|
||||
case NSF5FunctionKey: return Qt::Key_F5;
|
||||
case NSF6FunctionKey: return Qt::Key_F6;
|
||||
case NSF7FunctionKey: return Qt::Key_F7;
|
||||
case NSF8FunctionKey: return Qt::Key_F8;
|
||||
case NSF9FunctionKey: return Qt::Key_F9;
|
||||
case NSF10FunctionKey: return Qt::Key_F10;
|
||||
case NSF11FunctionKey: return Qt::Key_F11;
|
||||
case NSF12FunctionKey: return Qt::Key_F12;
|
||||
case NSF13FunctionKey: return Qt::Key_F13;
|
||||
case NSF14FunctionKey: return Qt::Key_F14;
|
||||
case NSF15FunctionKey: return Qt::Key_F15;
|
||||
case NSF16FunctionKey: return Qt::Key_F16;
|
||||
case NSF17FunctionKey: return Qt::Key_F17;
|
||||
case NSF18FunctionKey: return Qt::Key_F18;
|
||||
case NSF19FunctionKey: return Qt::Key_F19;
|
||||
case NSF20FunctionKey: return Qt::Key_F20;
|
||||
case NSF21FunctionKey: return Qt::Key_F21;
|
||||
case NSF22FunctionKey: return Qt::Key_F22;
|
||||
case NSF23FunctionKey: return Qt::Key_F23;
|
||||
case NSF24FunctionKey: return Qt::Key_F24;
|
||||
case NSF25FunctionKey: return Qt::Key_F25;
|
||||
case NSF26FunctionKey: return Qt::Key_F26;
|
||||
case NSF27FunctionKey: return Qt::Key_F27;
|
||||
case NSF28FunctionKey: return Qt::Key_F28;
|
||||
case NSF29FunctionKey: return Qt::Key_F29;
|
||||
case NSF30FunctionKey: return Qt::Key_F30;
|
||||
case NSF31FunctionKey: return Qt::Key_F31;
|
||||
case NSF32FunctionKey: return Qt::Key_F32;
|
||||
case NSF33FunctionKey: return Qt::Key_F33;
|
||||
case NSF34FunctionKey: return Qt::Key_F34;
|
||||
case NSF35FunctionKey: return Qt::Key_F35;
|
||||
case NSMenuFunctionKey: return Qt::Key_Menu;
|
||||
case NSHelpFunctionKey: return Qt::Key_Help;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
QKeySequence KeySequenceFromNSEvent(NSEvent* event) {
|
||||
NSString* str = [event charactersIgnoringModifiers];
|
||||
NSString* upper = [str uppercaseString];
|
||||
const char* chars = [upper UTF8String];
|
||||
NSUInteger modifiers = [event modifierFlags];
|
||||
int key = 0;
|
||||
unsigned char c = chars[0];
|
||||
switch (c) {
|
||||
case 0x1b: key = Qt::Key_Escape; break;
|
||||
case 0x09: key = Qt::Key_Tab; break;
|
||||
case 0x0d: key = Qt::Key_Return; break;
|
||||
case 0x08: key = Qt::Key_Backspace; break;
|
||||
case 0x03: key = Qt::Key_Enter; break;
|
||||
}
|
||||
|
||||
if (key == 0) {
|
||||
if (c >= 0x20 && c <= 0x7e) { // ASCII from space to ~
|
||||
key = c;
|
||||
} else {
|
||||
key = MapFunctionKey([event keyCode]);
|
||||
if (key == 0) {
|
||||
return QKeySequence();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (modifiers & NSShiftKeyMask) {
|
||||
key += Qt::SHIFT;
|
||||
}
|
||||
if (modifiers & NSControlKeyMask) {
|
||||
key += Qt::META;
|
||||
}
|
||||
if (modifiers & NSAlternateKeyMask) {
|
||||
key += Qt::ALT;
|
||||
}
|
||||
if (modifiers & NSCommandKeyMask) {
|
||||
key += Qt::CTRL;
|
||||
}
|
||||
|
||||
return QKeySequence(key);
|
||||
}
|
||||
|
||||
void DumpDictionary(CFDictionaryRef dict) {
|
||||
NSDictionary* d = (NSDictionary*)dict;
|
||||
NSLog(@"%@", d);
|
||||
}
|
||||
|
||||
// NSWindowCollectionBehaviorFullScreenPrimary
|
||||
static const NSUInteger kFullScreenPrimary = 1 << 7;
|
||||
|
||||
void EnableFullScreen(const QWidget& main_window) {
|
||||
|
||||
NSView* view = reinterpret_cast<NSView*>(main_window.winId());
|
||||
NSWindow* window = [view window];
|
||||
[window setCollectionBehavior:kFullScreenPrimary];
|
||||
}
|
||||
|
||||
float GetDevicePixelRatio(QWidget* widget) {
|
||||
NSView* view = reinterpret_cast<NSView*>(widget->winId());
|
||||
return [[view window] backingScaleFactor];
|
||||
}
|
||||
|
||||
} // namespace mac
|
||||
|
||||
38
src/core/mac_utilities.h
Normal file
38
src/core/mac_utilities.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QKeySequence>
|
||||
|
||||
#include <CoreFoundation/CFDictionary.h>
|
||||
|
||||
#ifdef __OBJC__
|
||||
@class NSEvent;
|
||||
#else
|
||||
class NSEvent;
|
||||
#endif
|
||||
|
||||
namespace mac {
|
||||
|
||||
QKeySequence KeySequenceFromNSEvent(NSEvent* event);
|
||||
void DumpDictionary(CFDictionaryRef dict);
|
||||
float GetDevicePixelRatio(QWidget* widget);
|
||||
}
|
||||
63
src/core/macfslistener.h
Normal file
63
src/core/macfslistener.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MACFSLISTENER_H
|
||||
#define MACFSLISTENER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <CoreServices/CoreServices.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
#include <QTimer>
|
||||
|
||||
#include "filesystemwatcherinterface.h"
|
||||
|
||||
class MacFSListener : public FileSystemWatcherInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MacFSListener(QObject *parent = nullptr);
|
||||
void Init();
|
||||
void AddPath(const QString &path);
|
||||
void RemovePath(const QString &path);
|
||||
void Clear();
|
||||
|
||||
signals:
|
||||
void PathChanged(const QString &path);
|
||||
|
||||
private slots:
|
||||
void UpdateStream();
|
||||
|
||||
private:
|
||||
void UpdateStreamAsync();
|
||||
|
||||
static void EventStreamCallback(ConstFSEventStreamRef stream, void *user_data, size_t num_events, void *event_paths, const FSEventStreamEventFlags event_flags[], const FSEventStreamEventId event_ids[]);
|
||||
|
||||
CFRunLoopRef run_loop_;
|
||||
FSEventStreamRef stream_;
|
||||
|
||||
QSet<QString> paths_;
|
||||
QTimer update_timer_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
112
src/core/macfslistener.mm
Normal file
112
src/core/macfslistener.mm
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "macfslistener.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <CoreFoundation/CFArray.h>
|
||||
#include <Foundation/NSArray.h>
|
||||
#include <Foundation/NSString.h>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/scoped_nsobject.h"
|
||||
|
||||
MacFSListener::MacFSListener(QObject* parent)
|
||||
: FileSystemWatcherInterface(parent), run_loop_(nullptr), stream_(nullptr) {
|
||||
update_timer_.setSingleShot(true);
|
||||
update_timer_.setInterval(2000);
|
||||
connect(&update_timer_, SIGNAL(timeout()), SLOT(UpdateStream()));
|
||||
}
|
||||
|
||||
void MacFSListener::Init() { run_loop_ = CFRunLoopGetCurrent(); }
|
||||
|
||||
void MacFSListener::EventStreamCallback(
|
||||
ConstFSEventStreamRef stream,
|
||||
void* user_data,
|
||||
size_t num_events,
|
||||
void* event_paths,
|
||||
const FSEventStreamEventFlags event_flags[],
|
||||
const FSEventStreamEventId event_ids[]) {
|
||||
MacFSListener* me = reinterpret_cast<MacFSListener*>(user_data);
|
||||
char** paths = reinterpret_cast<char**>(event_paths);
|
||||
for (int i = 0; i < num_events; ++i) {
|
||||
QString path = QString::fromUtf8(paths[i]);
|
||||
qLog(Debug) << "Something changed at:" << path;
|
||||
while (path.endsWith('/')) {
|
||||
path.chop(1);
|
||||
}
|
||||
emit me->PathChanged(path);
|
||||
}
|
||||
}
|
||||
|
||||
void MacFSListener::AddPath(const QString& path) {
|
||||
Q_ASSERT(run_loop_);
|
||||
paths_.insert(path);
|
||||
UpdateStreamAsync();
|
||||
}
|
||||
|
||||
void MacFSListener::RemovePath(const QString& path) {
|
||||
Q_ASSERT(run_loop_);
|
||||
paths_.remove(path);
|
||||
UpdateStreamAsync();
|
||||
}
|
||||
|
||||
void MacFSListener::Clear() {
|
||||
paths_.clear();
|
||||
UpdateStreamAsync();
|
||||
}
|
||||
|
||||
void MacFSListener::UpdateStreamAsync() { update_timer_.start(); }
|
||||
|
||||
void MacFSListener::UpdateStream() {
|
||||
if (stream_) {
|
||||
FSEventStreamStop(stream_);
|
||||
FSEventStreamInvalidate(stream_);
|
||||
FSEventStreamRelease(stream_);
|
||||
stream_ = nullptr;
|
||||
}
|
||||
|
||||
if (paths_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
scoped_nsobject<NSMutableArray> array([[NSMutableArray alloc] init]);
|
||||
|
||||
for (const QString& path : paths_) {
|
||||
scoped_nsobject<NSString> string(
|
||||
[[NSString alloc] initWithUTF8String:path.toUtf8().constData()]);
|
||||
[array addObject:string.get()];
|
||||
}
|
||||
|
||||
FSEventStreamContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
context.info = this;
|
||||
CFAbsoluteTime latency = 1.0;
|
||||
|
||||
stream_ = FSEventStreamCreate(nullptr, &EventStreamCallback, &context, // Copied
|
||||
reinterpret_cast<CFArrayRef>(array.get()),
|
||||
kFSEventStreamEventIdSinceNow, latency,
|
||||
kFSEventStreamCreateFlagNone);
|
||||
|
||||
FSEventStreamScheduleWithRunLoop(stream_, run_loop_, kCFRunLoopDefaultMode);
|
||||
FSEventStreamStart(stream_);
|
||||
}
|
||||
|
||||
46
src/core/macscreensaver.cpp
Normal file
46
src/core/macscreensaver.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "macscreensaver.h"
|
||||
|
||||
#include <QtDebug>
|
||||
|
||||
#include "core/utilities.h"
|
||||
|
||||
// kIOPMAssertionTypePreventUserIdleDisplaySleep from Lion.
|
||||
#define kLionDisplayAssertion CFSTR("PreventUserIdleDisplaySleep")
|
||||
|
||||
MacScreensaver::MacScreensaver() : assertion_id_(0) {}
|
||||
|
||||
void MacScreensaver::Inhibit() {
|
||||
CFStringRef assertion_type = (Utilities::GetMacVersion() >= 7) ? kLionDisplayAssertion : kIOPMAssertionTypeNoDisplaySleep;
|
||||
|
||||
IOPMAssertionCreateWithName(
|
||||
assertion_type,
|
||||
kIOPMAssertionLevelOn,
|
||||
CFSTR("Showing full-screen Strawberry visualisations"),
|
||||
&assertion_id_);
|
||||
}
|
||||
|
||||
void MacScreensaver::Uninhibit() {
|
||||
IOPMAssertionRelease(assertion_id_);
|
||||
}
|
||||
41
src/core/macscreensaver.h
Normal file
41
src/core/macscreensaver.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MACSCREENSAVER_H
|
||||
#define MACSCREENSAVER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "screensaver.h"
|
||||
|
||||
#include <IOKit/pwr_mgt/IOPMLib.h>
|
||||
|
||||
class MacScreensaver : public Screensaver {
|
||||
public:
|
||||
MacScreensaver();
|
||||
|
||||
void Inhibit();
|
||||
void Uninhibit();
|
||||
|
||||
private:
|
||||
IOPMAssertionID assertion_id_;
|
||||
};
|
||||
|
||||
#endif
|
||||
61
src/core/macsystemtrayicon.h
Normal file
61
src/core/macsystemtrayicon.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
*Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MACSYSTEMTRAYICON_H
|
||||
#define MACSYSTEMTRAYICON_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "systemtrayicon.h"
|
||||
|
||||
class MacSystemTrayIconPrivate;
|
||||
|
||||
class MacSystemTrayIcon : public SystemTrayIcon {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacSystemTrayIcon(QObject *parent = nullptr);
|
||||
~MacSystemTrayIcon();
|
||||
|
||||
void SetupMenu(QAction *previous, QAction *play, QAction *stop, QAction *stop_after, QAction *next, QAction *mute, QAction *quit);
|
||||
|
||||
void SetNowPlaying(const Song& song, const QString& image_path);
|
||||
void ClearNowPlaying();
|
||||
|
||||
private:
|
||||
void SetupMenuItem(QAction *action);
|
||||
|
||||
private slots:
|
||||
void ActionChanged();
|
||||
|
||||
protected:
|
||||
// SystemTrayIcon
|
||||
void UpdateIcon();
|
||||
|
||||
private:
|
||||
QPixmap orange_icon_;
|
||||
QPixmap grey_icon_;
|
||||
std::unique_ptr<MacSystemTrayIconPrivate> p_;
|
||||
Q_DISABLE_COPY(MacSystemTrayIcon);
|
||||
};
|
||||
|
||||
#endif // MACSYSTEMTRAYICON_H
|
||||
210
src/core/macsystemtrayicon.mm
Normal file
210
src/core/macsystemtrayicon.mm
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "macsystemtrayicon.h"
|
||||
|
||||
#include "core/mac_delegate.h"
|
||||
#include "core/song.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QIcon>
|
||||
|
||||
#include <QtDebug>
|
||||
|
||||
#include <AppKit/NSMenu.h>
|
||||
#include <AppKit/NSMenuItem.h>
|
||||
|
||||
@interface Target :NSObject {
|
||||
QAction* action_;
|
||||
}
|
||||
- (id) initWithQAction: (QAction*)action;
|
||||
- (void) clicked;
|
||||
@end
|
||||
|
||||
@implementation Target // <NSMenuValidation>
|
||||
- (id) init {
|
||||
return [super init];
|
||||
}
|
||||
|
||||
- (id) initWithQAction: (QAction*)action {
|
||||
action_ = action;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL) validateMenuItem: (NSMenuItem*)menuItem {
|
||||
// This is called when the menu is shown.
|
||||
return action_->isEnabled();
|
||||
}
|
||||
|
||||
- (void) clicked {
|
||||
action_->trigger();
|
||||
}
|
||||
@end
|
||||
|
||||
class MacSystemTrayIconPrivate {
|
||||
public:
|
||||
MacSystemTrayIconPrivate() {
|
||||
dock_menu_ = [[NSMenu alloc] initWithTitle:@"DockMenu"];
|
||||
|
||||
QString title = QT_TR_NOOP("Now Playing");
|
||||
NSString* t = [[NSString alloc] initWithUTF8String:title.toUtf8().constData()];
|
||||
now_playing_ = [[NSMenuItem alloc]
|
||||
initWithTitle:t
|
||||
action:nullptr
|
||||
keyEquivalent:@""];
|
||||
|
||||
now_playing_artist_ = [[NSMenuItem alloc]
|
||||
initWithTitle:@"Nothing to see here"
|
||||
action:nullptr
|
||||
keyEquivalent:@""];
|
||||
|
||||
now_playing_title_ = [[NSMenuItem alloc]
|
||||
initWithTitle:@"Nothing to see here"
|
||||
action:nullptr
|
||||
keyEquivalent:@""];
|
||||
|
||||
[dock_menu_ insertItem:now_playing_title_ atIndex:0];
|
||||
[dock_menu_ insertItem:now_playing_artist_ atIndex:0];
|
||||
[dock_menu_ insertItem:now_playing_ atIndex:0];
|
||||
|
||||
// Don't look now.
|
||||
// This must be called after our custom NSApplicationDelegate has been set.
|
||||
[(AppDelegate*)([NSApp delegate]) setDockMenu:dock_menu_];
|
||||
|
||||
ClearPlaying();
|
||||
}
|
||||
|
||||
void AddMenuItem(QAction* action) {
|
||||
// Strip accelarators from name.
|
||||
QString text = action->text().remove("&");
|
||||
NSString* title = [[NSString alloc] initWithUTF8String: text.toUtf8().constData()];
|
||||
// Create an object that can receive user clicks and pass them on to the QAction.
|
||||
Target* target = [[Target alloc] initWithQAction:action];
|
||||
NSMenuItem* item = [[[NSMenuItem alloc]
|
||||
initWithTitle:title
|
||||
action:@selector(clicked)
|
||||
keyEquivalent:@""] autorelease];
|
||||
[item setEnabled:action->isEnabled()];
|
||||
[item setTarget:target];
|
||||
[dock_menu_ addItem:item];
|
||||
actions_[action] = item;
|
||||
}
|
||||
|
||||
void ActionChanged(QAction* action) {
|
||||
NSMenuItem* item = actions_[action];
|
||||
NSString* title = [[NSString alloc] initWithUTF8String: action->text().toUtf8().constData()];
|
||||
[item setTitle:title];
|
||||
}
|
||||
|
||||
void AddSeparator() {
|
||||
NSMenuItem* separator = [NSMenuItem separatorItem];
|
||||
[dock_menu_ addItem:separator];
|
||||
}
|
||||
|
||||
void ShowPlaying(const QString& artist, const QString& title) {
|
||||
ClearPlaying(); // Makes sure the order is consistent.
|
||||
[now_playing_artist_ setTitle:
|
||||
[[NSString alloc] initWithUTF8String: artist.toUtf8().constData()]];
|
||||
[now_playing_title_ setTitle:
|
||||
[[NSString alloc] initWithUTF8String: title.toUtf8().constData()]];
|
||||
title.isEmpty() ? HideItem(now_playing_title_) : ShowItem(now_playing_title_);
|
||||
artist.isEmpty() ? HideItem(now_playing_artist_) : ShowItem(now_playing_artist_);
|
||||
artist.isEmpty() && title.isEmpty() ? HideItem(now_playing_) : ShowItem(now_playing_);
|
||||
}
|
||||
|
||||
void ClearPlaying() {
|
||||
// Hiding doesn't seem to work in the dock menu.
|
||||
HideItem(now_playing_);
|
||||
HideItem(now_playing_artist_);
|
||||
HideItem(now_playing_title_);
|
||||
}
|
||||
|
||||
private:
|
||||
void HideItem(NSMenuItem* item) {
|
||||
if ([dock_menu_ indexOfItem:item] != -1) {
|
||||
[dock_menu_ removeItem:item];
|
||||
}
|
||||
}
|
||||
|
||||
void ShowItem(NSMenuItem* item, int index = 0) {
|
||||
if ([dock_menu_ indexOfItem:item] == -1) {
|
||||
[dock_menu_ insertItem:item atIndex:index];
|
||||
}
|
||||
}
|
||||
|
||||
QMap<QAction*, NSMenuItem*> actions_;
|
||||
|
||||
NSMenu* dock_menu_;
|
||||
NSMenuItem* now_playing_;
|
||||
NSMenuItem* now_playing_artist_;
|
||||
NSMenuItem* now_playing_title_;
|
||||
|
||||
Q_DISABLE_COPY(MacSystemTrayIconPrivate);
|
||||
};
|
||||
|
||||
MacSystemTrayIcon::MacSystemTrayIcon(QObject* parent)
|
||||
: SystemTrayIcon(parent),
|
||||
orange_icon_(QPixmap(":/icons/64x64/strawberry.png").scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)),
|
||||
grey_icon_(QPixmap(":icon_large_grey.png").scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)) {
|
||||
QApplication::setWindowIcon(orange_icon_);
|
||||
}
|
||||
|
||||
MacSystemTrayIcon::~MacSystemTrayIcon() {
|
||||
}
|
||||
|
||||
void MacSystemTrayIcon::SetupMenu(QAction* previous, QAction* play, QAction* stop, QAction* stop_after, QAction* next, QAction* mute, QAction* quit) {
|
||||
|
||||
p_.reset(new MacSystemTrayIconPrivate());
|
||||
SetupMenuItem(previous);
|
||||
SetupMenuItem(play);
|
||||
SetupMenuItem(stop);
|
||||
SetupMenuItem(stop_after);
|
||||
SetupMenuItem(next);
|
||||
p_->AddSeparator();
|
||||
SetupMenuItem(mute);
|
||||
p_->AddSeparator();
|
||||
Q_UNUSED(quit); // Mac already has a Quit item.
|
||||
|
||||
}
|
||||
|
||||
void MacSystemTrayIcon::SetupMenuItem(QAction* action) {
|
||||
p_->AddMenuItem(action);
|
||||
connect(action, SIGNAL(changed()), SLOT(ActionChanged()));
|
||||
}
|
||||
|
||||
void MacSystemTrayIcon::UpdateIcon() {
|
||||
QApplication::setWindowIcon(CreateIcon(orange_icon_, grey_icon_));
|
||||
}
|
||||
|
||||
void MacSystemTrayIcon::ActionChanged() {
|
||||
QAction* action = qobject_cast<QAction*>(sender());
|
||||
p_->ActionChanged(action);
|
||||
}
|
||||
|
||||
void MacSystemTrayIcon::ClearPlaying() {
|
||||
p_->ClearPlaying();
|
||||
}
|
||||
|
||||
void MacSystemTrayIcon::SetPlaying(const Song& song, const QString& image_path) {
|
||||
p_->ShowPlaying(song.artist(), song.PrettyTitle());
|
||||
}
|
||||
305
src/core/main.cpp
Normal file
305
src/core/main.cpp
Normal file
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <glib.h>
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
#include <gst/gst.h>
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
#include <unistd.h>
|
||||
#endif // Q_OS_UNIX
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
#include "core/mpris.h"
|
||||
#include "core/mpris2.h"
|
||||
#include <QDBusArgument>
|
||||
#include <QImage>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
#include <sys/resource.h>
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#define _WIN32_WINNT 0x0600
|
||||
#include <windows.h>
|
||||
#include <iostream>
|
||||
#include <qtsparkle/Updater>
|
||||
#endif // Q_OS_WIN32
|
||||
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
#include <QFont>
|
||||
#include <QLibraryInfo>
|
||||
#include <QNetworkProxyFactory>
|
||||
#include <QSslSocket>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlQuery>
|
||||
#include <QSysInfo>
|
||||
#include <QTextCodec>
|
||||
#include <QTranslator>
|
||||
#include <QtConcurrentRun>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/mainwindow.h"
|
||||
#include "core/commandlineoptions.h"
|
||||
#include "core/database.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/mac_startup.h"
|
||||
#include "core/metatypes.h"
|
||||
#include "core/network.h"
|
||||
#include "core/networkproxyfactory.h"
|
||||
#include "core/song.h"
|
||||
#include "core/utilities.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/systemtrayicon.h"
|
||||
#include "core/scangiomodulepath.h"
|
||||
#include "engine/enginebase.h"
|
||||
#ifdef HAVE_GSTREAMER
|
||||
#include "engine/gstengine.h"
|
||||
#endif
|
||||
#include "version.h"
|
||||
#include "widgets/osd.h"
|
||||
#if 0
|
||||
#ifdef HAVE_LIBLASTFM
|
||||
#include "covermanager/lastfmcoverprovider.h"
|
||||
#endif
|
||||
#include "covermanager/amazoncoverprovider.h"
|
||||
#include "covermanager/coverproviders.h"
|
||||
#include "covermanager/musicbrainzcoverprovider.h"
|
||||
#include "covermanager/discogscoverprovider.h"
|
||||
#endif
|
||||
|
||||
#include "tagreadermessages.pb.h"
|
||||
|
||||
#include "qtsingleapplication.h"
|
||||
#include "qtsinglecoreapplication.h"
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
QDBusArgument &operator<<(QDBusArgument &arg, const QImage &image);
|
||||
const QDBusArgument &operator>>(const QDBusArgument &arg, QImage &image);
|
||||
#endif
|
||||
|
||||
// Load sqlite plugin on windows and mac.
|
||||
#include <QtPlugin>
|
||||
Q_IMPORT_PLUGIN(QSQLiteDriverPlugin)
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
// Do Mac specific startup to get media keys working.
|
||||
// This must go before QApplication initialisation.
|
||||
mac::MacMain();
|
||||
|
||||
if (QSysInfo::MacintoshVersion > QSysInfo::MV_10_8) {
|
||||
// Work around 10.9 issue.
|
||||
// https://bugreports.qt-project.org/browse/QTBUG-32789
|
||||
QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN)
|
||||
QCoreApplication::setApplicationName("Strawberry");
|
||||
QCoreApplication::setOrganizationName("Strawberry");
|
||||
#else
|
||||
QCoreApplication::setApplicationName("strawberry");
|
||||
QCoreApplication::setOrganizationName("strawberry");
|
||||
#endif
|
||||
QCoreApplication::setApplicationVersion(STRAWBERRY_VERSION_DISPLAY);
|
||||
QCoreApplication::setOrganizationDomain("strawbs.org");
|
||||
|
||||
// This makes us show up nicely in gnome-volume-control
|
||||
#if !GLIB_CHECK_VERSION(2, 36, 0)
|
||||
g_type_init(); // Deprecated in glib 2.36.0
|
||||
#endif
|
||||
g_set_application_name(QCoreApplication::applicationName().toLocal8Bit());
|
||||
|
||||
RegisterMetaTypes();
|
||||
|
||||
// Initialise logging. Log levels are set after the commandline options are
|
||||
// parsed below.
|
||||
logging::Init();
|
||||
g_log_set_default_handler(reinterpret_cast<GLogFunc>(&logging::GLog), nullptr);
|
||||
|
||||
CommandlineOptions options(argc, argv);
|
||||
|
||||
{
|
||||
// Only start a core application now so we can check if there's another
|
||||
// Strawberry running without needing an X server.
|
||||
// This MUST be done before parsing the commandline options so QTextCodec
|
||||
// gets the right system locale for filenames.
|
||||
QtSingleCoreApplication a(argc, argv);
|
||||
Utilities::CheckPortable();
|
||||
//crash_reporting.SetApplicationPath(a.applicationFilePath());
|
||||
|
||||
// Parse commandline options - need to do this before starting the
|
||||
// full QApplication so it works without an X server
|
||||
if (!options.Parse()) return 1;
|
||||
logging::SetLevels(options.log_levels());
|
||||
|
||||
if (a.isRunning()) {
|
||||
if (options.is_empty()) {
|
||||
qLog(Info) << "Strawberry is already running - activating existing window";
|
||||
}
|
||||
if (a.sendMessage(options.Serialize(), 5000)) {
|
||||
return 0;
|
||||
}
|
||||
// Couldn't send the message so start anyway
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
// Must happen after QCoreApplication::setOrganizationName().
|
||||
setenv("XDG_CONFIG_HOME", Utilities::GetConfigPath(Utilities::Path_Root).toLocal8Bit().constData(), 1);
|
||||
#endif
|
||||
|
||||
// Output the version, so when people attach log output to bug reports they
|
||||
// don't have to tell us which version they're using.
|
||||
qLog(Info) << "Strawberry" << STRAWBERRY_VERSION_DISPLAY;
|
||||
|
||||
// Seed the random number generators.
|
||||
time_t t = time(nullptr);
|
||||
srand(t);
|
||||
qsrand(t);
|
||||
|
||||
Utilities::IncreaseFDLimit();
|
||||
|
||||
QtSingleApplication a(argc, argv);
|
||||
|
||||
// A bug in Qt means the wheel_scroll_lines setting gets ignored and replaced
|
||||
// with the default value of 3 in QApplicationPrivate::initialize.
|
||||
{
|
||||
QSettings qt_settings(QSettings::UserScope, "Trolltech");
|
||||
qt_settings.beginGroup("Qt");
|
||||
QApplication::setWheelScrollLines(qt_settings.value("wheelScrollLines", QApplication::wheelScrollLines()).toInt());
|
||||
}
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
QCoreApplication::setCollectionPaths(
|
||||
QStringList() << QCoreApplication::applicationDirPath() + "/../PlugIns");
|
||||
#endif
|
||||
|
||||
a.setQuitOnLastWindowClosed(false);
|
||||
|
||||
// Do this check again because another instance might have started by now
|
||||
if (a.isRunning() && a.sendMessage(options.Serialize(), 5000)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef Q_OS_DARWIN
|
||||
// Gnome on Ubuntu has menu icons disabled by default. I think that's a bad
|
||||
// idea, and makes some menus in Strawberry look confusing.
|
||||
QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus, false);
|
||||
#else
|
||||
QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus, true);
|
||||
// Fixes focus issue with NSSearchField, see QTBUG-11401
|
||||
QCoreApplication::setAttribute(Qt::AA_NativeWindows, true);
|
||||
#endif
|
||||
|
||||
// Set the permissions on the config file on Unix - it can contain passwords
|
||||
// for internet services so it's important that other users can't read it.
|
||||
// On Windows these are stored in the registry instead.
|
||||
#ifdef Q_OS_UNIX
|
||||
{
|
||||
QSettings s;
|
||||
|
||||
// Create the file if it doesn't exist already
|
||||
if (!QFile::exists(s.fileName())) {
|
||||
QFile file(s.fileName());
|
||||
file.open(QIODevice::WriteOnly);
|
||||
}
|
||||
|
||||
// Set -rw-------
|
||||
QFile::setPermissions(s.fileName(), QFile::ReadOwner | QFile::WriteOwner);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Resources
|
||||
Q_INIT_RESOURCE(data);
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// Set the language for qtsparkle
|
||||
qtsparkle::LoadTranslations(language);
|
||||
#endif
|
||||
|
||||
// Icons
|
||||
IconLoader::Init();
|
||||
|
||||
// This is a nasty hack to ensure that everything in libprotobuf is
|
||||
// initialised in the main thread. It fixes issue 3265 but nobody knows why.
|
||||
// Don't remove this unless you can reproduce the error that it fixes.
|
||||
//ParseAProto();
|
||||
//QtConcurrent::run(&ParseAProto);
|
||||
|
||||
Application app;
|
||||
|
||||
// Network proxy
|
||||
QNetworkProxyFactory::setApplicationProxyFactory(NetworkProxyFactory::Instance());
|
||||
|
||||
#if 0
|
||||
//#ifdef HAVE_LIBLASTFM
|
||||
app.cover_providers()->AddProvider(new LastFmCoverProvider);
|
||||
app.cover_providers()->AddProvider(new AmazonCoverProvider);
|
||||
app.cover_providers()->AddProvider(new DiscogsCoverProvider);
|
||||
app.cover_providers()->AddProvider(new MusicbrainzCoverProvider);
|
||||
#endif
|
||||
|
||||
// Create the tray icon and OSD
|
||||
std::unique_ptr<SystemTrayIcon> tray_icon(SystemTrayIcon::CreateSystemTrayIcon());
|
||||
OSD osd(tray_icon.get(), &app);
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
mpris::Mpris mpris(&app);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
gst_init(nullptr, nullptr);
|
||||
gst_pb_utils_init();
|
||||
#endif
|
||||
|
||||
// Window
|
||||
MainWindow w(&app, tray_icon.get(), &osd, options);
|
||||
#ifdef Q_OS_DARWIN
|
||||
mac::EnableFullScreen(w);
|
||||
#endif // Q_OS_DARWIN
|
||||
#ifdef HAVE_GIO
|
||||
ScanGIOModulePath();
|
||||
#endif
|
||||
#ifdef HAVE_DBUS
|
||||
QObject::connect(&mpris, SIGNAL(RaiseMainWindow()), &w, SLOT(Raise()));
|
||||
#endif
|
||||
QObject::connect(&a, SIGNAL(messageReceived(QString)), &w, SLOT(CommandlineOptionsReceived(QString)));
|
||||
|
||||
int ret = a.exec();
|
||||
|
||||
return ret;
|
||||
}
|
||||
2300
src/core/mainwindow.cpp
Normal file
2300
src/core/mainwindow.cpp
Normal file
File diff suppressed because it is too large
Load Diff
353
src/core/mainwindow.h
Normal file
353
src/core/mainwindow.h
Normal file
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QSettings>
|
||||
#include <QSystemTrayIcon>
|
||||
|
||||
#include "core/lazy.h"
|
||||
#include "core/mac_startup.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "engine/engine_fwd.h"
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "playlist/playlistitem.h"
|
||||
#ifdef HAVE_GSTREAMER
|
||||
#include "dialogs/organisedialog.h"
|
||||
#endif
|
||||
#include "settings/settingsdialog.h"
|
||||
|
||||
class StatusView;
|
||||
class About;
|
||||
class AlbumCoverManager;
|
||||
class Appearance;
|
||||
class Application;
|
||||
class ArtistInfoView;
|
||||
class CommandlineOptions;
|
||||
class CoverProviders;
|
||||
class Database;
|
||||
class DeviceManager;
|
||||
class DeviceView;
|
||||
class DeviceViewContainer;
|
||||
class EditTagDialog;
|
||||
class Equalizer;
|
||||
class ErrorDialog;
|
||||
class FileView;
|
||||
class GlobalShortcuts;
|
||||
class GroupByDialog;
|
||||
class Collection;
|
||||
class CollectionViewContainer;
|
||||
class MimeData;
|
||||
class MultiLoadingIndicator;
|
||||
class OSD;
|
||||
class Player;
|
||||
class PlaylistBackend;
|
||||
class PlaylistListContainer;
|
||||
class PlaylistManager;
|
||||
class QueueManager;
|
||||
class Song;
|
||||
class SystemTrayIcon;
|
||||
class TagFetcher;
|
||||
class TaskManager;
|
||||
class TrackSelectionDialog;
|
||||
#ifdef HAVE_GSTREAMER
|
||||
class TranscodeDialog;
|
||||
#endif
|
||||
class Windows7ThumbBar;
|
||||
class Ui_MainWindow;
|
||||
|
||||
class QSortFilterProxyModel;
|
||||
|
||||
class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MainWindow(Application *app, SystemTrayIcon* tray_icon, OSD* osd, const CommandlineOptions& options, QWidget* parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
static const char *kSettingsGroup;
|
||||
static const char *kAllFilesFilterSpec;
|
||||
|
||||
// Don't change the values
|
||||
enum StartupBehaviour {
|
||||
Startup_Remember = 1,
|
||||
Startup_AlwaysShow = 2,
|
||||
Startup_AlwaysHide = 3,
|
||||
};
|
||||
|
||||
// Don't change the values
|
||||
enum AddBehaviour {
|
||||
AddBehaviour_Append = 1,
|
||||
AddBehaviour_Enqueue = 2,
|
||||
AddBehaviour_Load = 3,
|
||||
AddBehaviour_OpenInNew = 4
|
||||
};
|
||||
|
||||
// Don't change the values
|
||||
enum PlayBehaviour {
|
||||
PlayBehaviour_Never = 1,
|
||||
PlayBehaviour_IfStopped = 2,
|
||||
PlayBehaviour_Always = 3,
|
||||
};
|
||||
|
||||
// Don't change the values
|
||||
enum PlaylistAddBehaviour {
|
||||
PlaylistAddBehaviour_Play = 1,
|
||||
PlaylistAddBehaviour_Enqueue = 2,
|
||||
};
|
||||
|
||||
void SetHiddenInTray(bool hidden);
|
||||
void CommandlineOptionsReceived(const CommandlineOptions& options);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent* event);
|
||||
void resizeEvent(QResizeEvent* event);
|
||||
void closeEvent(QCloseEvent* event);
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
bool winEvent(MSG* message, long* result);
|
||||
#endif
|
||||
|
||||
// PlatformInterface
|
||||
void Activate();
|
||||
bool LoadUrl(const QString& url);
|
||||
|
||||
signals:
|
||||
// Signals that stop playing after track was toggled.
|
||||
void StopAfterToggled(bool stop);
|
||||
|
||||
void IntroPointReached();
|
||||
|
||||
private slots:
|
||||
void FilePathChanged(const QString& path);
|
||||
|
||||
void MediaStopped();
|
||||
void MediaPaused();
|
||||
void MediaPlaying();
|
||||
void TrackSkipped(PlaylistItemPtr item);
|
||||
void ForceShowOSD(const Song& song, const bool toggle);
|
||||
|
||||
void PlaylistRightClick(const QPoint& global_pos, const QModelIndex& index);
|
||||
void PlaylistCurrentChanged(const QModelIndex& current);
|
||||
void PlaylistViewSelectionModelChanged();
|
||||
void PlaylistPlay();
|
||||
void PlaylistStopAfter();
|
||||
void PlaylistQueue();
|
||||
void PlaylistSkip();
|
||||
void PlaylistRemoveCurrent();
|
||||
void PlaylistEditFinished(const QModelIndex& index);
|
||||
void EditTracks();
|
||||
void EditTagDialogAccepted();
|
||||
void RenumberTracks();
|
||||
void SelectionSetValue();
|
||||
void EditValue();
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void AutoCompleteTags();
|
||||
void AutoCompleteTagsAccepted();
|
||||
#endif
|
||||
void PlaylistUndoRedoChanged(QAction* undo, QAction* redo);
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void AddFilesToTranscoder();
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void PlaylistCopyToCollection();
|
||||
void PlaylistMoveToCollection();
|
||||
void PlaylistCopyToDevice();
|
||||
void PlaylistOrganiseSelected(bool copy);
|
||||
#endif
|
||||
//void PlaylistDelete();
|
||||
void PlaylistOpenInBrowser();
|
||||
void ShowInCollection();
|
||||
|
||||
void ChangeCollectionQueryMode(QAction* action);
|
||||
|
||||
void PlayIndex(const QModelIndex& index);
|
||||
void PlaylistDoubleClick(const QModelIndex& index);
|
||||
void StopAfterCurrent();
|
||||
|
||||
void SongChanged(const Song& song);
|
||||
void VolumeChanged(int volume);
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void CopyFilesToCollection(const QList<QUrl>& urls);
|
||||
void MoveFilesToCollection(const QList<QUrl>& urls);
|
||||
void CopyFilesToDevice(const QList<QUrl>& urls);
|
||||
#endif
|
||||
void EditFileTags(const QList<QUrl>& urls);
|
||||
|
||||
void AddToPlaylist(QMimeData* data);
|
||||
void AddToPlaylist(QAction* action);
|
||||
|
||||
void VolumeWheelEvent(int delta);
|
||||
void ToggleShowHide();
|
||||
|
||||
void Seeked(qlonglong microseconds);
|
||||
void UpdateTrackPosition();
|
||||
void UpdateTrackSliderPosition();
|
||||
|
||||
void TaskCountChanged(int count);
|
||||
|
||||
void ShowCollectionConfig();
|
||||
void ReloadSettings();
|
||||
void ReloadAllSettings();
|
||||
void RefreshStyleSheet();
|
||||
void SetHiddenInTray() { SetHiddenInTray(true); }
|
||||
|
||||
void AddFile();
|
||||
void AddFolder();
|
||||
void AddCDTracks();
|
||||
|
||||
void CommandlineOptionsReceived(const QString& string_options);
|
||||
|
||||
void CheckForUpdates();
|
||||
|
||||
void PlayingWidgetPositionChanged();
|
||||
|
||||
void SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex& index);
|
||||
|
||||
void ShowCoverManager();
|
||||
|
||||
void ShowAboutDialog();
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void ShowTranscodeDialog();
|
||||
#endif
|
||||
void ShowErrorDialog(const QString& message);
|
||||
void ShowQueueManager();
|
||||
void EnsureSettingsDialogCreated();
|
||||
void EnsureEditTagDialogCreated();
|
||||
SettingsDialog* CreateSettingsDialog();
|
||||
EditTagDialog* CreateEditTagDialog();
|
||||
void OpenSettingsDialog();
|
||||
void OpenSettingsDialogAtPage(SettingsDialog::Page page);
|
||||
|
||||
void TabSwitched();
|
||||
void SaveGeometry();
|
||||
void SavePlaybackStatus();
|
||||
void LoadPlaybackStatus();
|
||||
void ResumePlayback();
|
||||
|
||||
void Raise();
|
||||
|
||||
void Exit();
|
||||
|
||||
void HandleNotificationPreview(OSD::Behaviour type, QString line1, QString line2);
|
||||
void FocusCollectionTab();
|
||||
|
||||
void ShowConsole();
|
||||
|
||||
private:
|
||||
void ConnectStatusView(StatusView *statusview);
|
||||
|
||||
void ApplyAddBehaviour(AddBehaviour b, MimeData *data) const;
|
||||
void ApplyPlayBehaviour(PlayBehaviour b, MimeData *data) const;
|
||||
|
||||
void CheckFullRescanRevisions();
|
||||
|
||||
// creates the icon by painting the full one depending on the current position
|
||||
QPixmap CreateOverlayedIcon(int position, int scrobble_point);
|
||||
|
||||
private:
|
||||
Ui_MainWindow *ui_;
|
||||
Windows7ThumbBar *thumbbar_;
|
||||
|
||||
Application *app_;
|
||||
SystemTrayIcon * tray_icon_;
|
||||
OSD* osd_;
|
||||
Lazy<EditTagDialog> edit_tag_dialog_;
|
||||
Lazy<About> about_dialog_;
|
||||
|
||||
GlobalShortcuts* global_shortcuts_;
|
||||
|
||||
CollectionViewContainer *collection_view_;
|
||||
StatusView *status_view_;
|
||||
FileView *file_view_;
|
||||
PlaylistListContainer *playlist_list_;
|
||||
DeviceViewContainer *device_view_container_;
|
||||
DeviceView *device_view_;
|
||||
|
||||
Lazy<SettingsDialog> settings_dialog_;
|
||||
Lazy<AlbumCoverManager> cover_manager_;
|
||||
std::unique_ptr<Equalizer> equalizer_;
|
||||
#ifdef HAVE_GSTREAMER
|
||||
Lazy<TranscodeDialog> transcode_dialog_;
|
||||
#endif
|
||||
Lazy<ErrorDialog> error_dialog_;
|
||||
#ifdef HAVE_GSTREAMER
|
||||
Lazy<OrganiseDialog> organise_dialog_;
|
||||
#endif
|
||||
Lazy<QueueManager> queue_manager_;
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
std::unique_ptr<TagFetcher> tag_fetcher_;
|
||||
#endif
|
||||
std::unique_ptr<TrackSelectionDialog> track_selection_dialog_;
|
||||
#ifdef HAVE_GSTREAMER
|
||||
PlaylistItemList autocomplete_tag_items_;
|
||||
#endif
|
||||
|
||||
QAction *collection_show_all_;
|
||||
QAction *collection_show_duplicates_;
|
||||
QAction *collection_show_untagged_;
|
||||
|
||||
QMenu *playlist_menu_;
|
||||
QAction *playlist_play_pause_;
|
||||
QAction *playlist_stop_after_;
|
||||
QAction *playlist_undoredo_;
|
||||
//QAction *playlist_organise_;
|
||||
QAction *playlist_show_in_collection_;
|
||||
#ifdef HAVE_GSTREAMER
|
||||
QAction *playlist_copy_to_collection_;
|
||||
QAction *playlist_move_to_collection_;
|
||||
QAction *playlist_copy_to_device_;
|
||||
//QAction *playlist_delete_;
|
||||
#endif
|
||||
QAction *playlist_open_in_browser_;
|
||||
QAction *playlist_queue_;
|
||||
QAction *playlist_skip_;
|
||||
QAction *playlist_add_to_another_;
|
||||
QList<QAction*> playlistitem_actions_;
|
||||
QAction *playlistitem_actions_separator_;
|
||||
QModelIndex playlist_menu_index_;
|
||||
|
||||
QSortFilterProxyModel *collection_sort_model_;
|
||||
|
||||
QTimer *track_position_timer_;
|
||||
QTimer *track_slider_timer_;
|
||||
QSettings settings_;
|
||||
|
||||
bool was_maximized_;
|
||||
int saved_playback_position_;
|
||||
Engine::State saved_playback_state_;
|
||||
AddBehaviour doubleclick_addmode_;
|
||||
PlayBehaviour doubleclick_playmode_;
|
||||
PlaylistAddBehaviour doubleclick_playlist_addmode_;
|
||||
PlayBehaviour menu_playmode_;
|
||||
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
|
||||
784
src/core/mainwindow.ui
Normal file
784
src/core/mainwindow.ui
Normal file
@@ -0,0 +1,784 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1131</width>
|
||||
<height>685</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Strawberry Music Player</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="sidebar_layout">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="FancyTabWidget" name="tabs" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="PlayingWidget" name="now_playing" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="playlist_layout">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="PlaylistContainer" name="playlist" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<addaction name="action_edit_track"/>
|
||||
<addaction name="action_edit_value"/>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="Line" name="line_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="player_controls_container" native="true">
|
||||
<layout class="QVBoxLayout" name="player_controls_layout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QFrame" name="player_controls">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QToolButton" name="back_button">
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="pause_play_button">
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="stop_button">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::MenuButtonPopup</enum>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="forward_button">
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_buttons">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="AnalyzerContainer" name="analyzer" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>100</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>36</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_volume">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Amarok::VolumeSlider" name="volume">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="status_bar" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="status_bar_layout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="Line" name="status_bar_line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="status_bar_internal" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="status_bar_stack">
|
||||
<widget class="MultiLoadingIndicator" name="multi_loading_indicator"/>
|
||||
<widget class="QWidget" name="playlist_summary_page">
|
||||
<layout class="QVBoxLayout" name="playlist_summary_layout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="playlist_summary">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="PlaylistSequence" name="playlist_sequence" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="TrackSlider" name="track_slider" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>10</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menuBar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1131</width>
|
||||
<height>24</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menu_music">
|
||||
<property name="title">
|
||||
<string>&Music</string>
|
||||
</property>
|
||||
<addaction name="action_open_file"/>
|
||||
<addaction name="action_open_cd"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_previous_track"/>
|
||||
<addaction name="action_play_pause"/>
|
||||
<addaction name="action_stop"/>
|
||||
<addaction name="action_next_track"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_mute"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_quit"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_playlist">
|
||||
<property name="title">
|
||||
<string>&Playlist</string>
|
||||
</property>
|
||||
<addaction name="action_add_file"/>
|
||||
<addaction name="action_add_folder"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_shuffle_mode"/>
|
||||
<addaction name="action_repeat_mode"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_new_playlist"/>
|
||||
<addaction name="action_save_playlist"/>
|
||||
<addaction name="action_load_playlist"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_jump"/>
|
||||
<addaction name="action_clear_playlist"/>
|
||||
<addaction name="action_shuffle"/>
|
||||
<addaction name="action_remove_duplicates"/>
|
||||
<addaction name="action_remove_unavailable"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_help">
|
||||
<property name="title">
|
||||
<string>&Help</string>
|
||||
</property>
|
||||
<addaction name="action_about_strawberry"/>
|
||||
<addaction name="action_about_qt"/>
|
||||
<addaction name="separator"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_tools">
|
||||
<property name="title">
|
||||
<string>&Tools</string>
|
||||
</property>
|
||||
<addaction name="action_cover_manager"/>
|
||||
<addaction name="action_queue_manager"/>
|
||||
<addaction name="action_equalizer"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_update_collection"/>
|
||||
<addaction name="action_full_collection_scan"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_settings"/>
|
||||
<addaction name="action_console"/>
|
||||
</widget>
|
||||
<addaction name="menu_music"/>
|
||||
<addaction name="menu_playlist"/>
|
||||
<addaction name="menu_tools"/>
|
||||
<addaction name="menu_help"/>
|
||||
</widget>
|
||||
<action name="action_previous_track">
|
||||
<property name="text">
|
||||
<string>&Previous track</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>F5</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_play_pause">
|
||||
<property name="text">
|
||||
<string>P&lay</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>F6</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_stop">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Stop</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>F7</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_next_track">
|
||||
<property name="text">
|
||||
<string>&Next track</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>F8</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_quit">
|
||||
<property name="text">
|
||||
<string>&Quit</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Q</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::QuitRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_stop_after_this_track">
|
||||
<property name="text">
|
||||
<string>Stop after this track</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Alt+V</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_clear_playlist">
|
||||
<property name="text">
|
||||
<string>&Clear playlist</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Clear playlist</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+K</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_edit_track">
|
||||
<property name="text">
|
||||
<string>Edit track information...</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+E</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_renumber_tracks">
|
||||
<property name="text">
|
||||
<string>Renumber tracks in this order...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_selection_set_value">
|
||||
<property name="text">
|
||||
<string>Set value for all selected tracks...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_edit_value">
|
||||
<property name="text">
|
||||
<string>Edit tag...</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>F2</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_settings">
|
||||
<property name="text">
|
||||
<string>&Settings...</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+P</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::PreferencesRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_about_strawberry">
|
||||
<property name="icon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&About Strawberry</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>F1</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::AboutRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_shuffle">
|
||||
<property name="text">
|
||||
<string>S&huffle playlist</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+H</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_add_file">
|
||||
<property name="text">
|
||||
<string>&Add file...</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Shift+A</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_open_file">
|
||||
<property name="text">
|
||||
<string>&Open file...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_open_cd">
|
||||
<property name="text">
|
||||
<string>Open &audio CD...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_cover_manager">
|
||||
<property name="text">
|
||||
<string>&Cover Manager</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_console">
|
||||
<property name="text">
|
||||
<string>C&onsole</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_shuffle_mode">
|
||||
<property name="text">
|
||||
<string>&Shuffle mode</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_repeat_mode">
|
||||
<property name="text">
|
||||
<string>&Repeat mode</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_remove_from_playlist">
|
||||
<property name="text">
|
||||
<string>Remove from playlist</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_equalizer">
|
||||
<property name="text">
|
||||
<string>&Equalizer</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_add_folder">
|
||||
<property name="text">
|
||||
<string>Add &folder...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_jump">
|
||||
<property name="text">
|
||||
<string>&Jump to the currently playing track</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+J</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_new_playlist">
|
||||
<property name="text">
|
||||
<string>&New playlist</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+N</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_save_playlist">
|
||||
<property name="text">
|
||||
<string>Save &playlist...</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+S</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_load_playlist">
|
||||
<property name="text">
|
||||
<string>&Load playlist...</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Shift+O</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_next_playlist">
|
||||
<property name="text">
|
||||
<string>Go to next playlist tab</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_previous_playlist">
|
||||
<property name="text">
|
||||
<string>Go to previous playlist tab</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_update_collection">
|
||||
<property name="text">
|
||||
<string>&Update changed collection folders</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_queue_manager">
|
||||
<property name="text">
|
||||
<string>&Queue Manager</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_about_qt">
|
||||
<property name="text">
|
||||
<string>About &Qt</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::AboutQtRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_mute">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Mute</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+M</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_full_collection_scan">
|
||||
<property name="text">
|
||||
<string>&Do a full collection rescan</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_auto_complete_tags">
|
||||
<property name="icon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<normaloff>:/pictures/musicbrainz.png</normaloff>:/pictures/musicbrainz.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Complete tags automatically...</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+T</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_toggle_scrobbling">
|
||||
<property name="text">
|
||||
<string>Toggle scrobbling</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_remove_duplicates">
|
||||
<property name="text">
|
||||
<string>Remove &duplicates from playlist</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_remove_unavailable">
|
||||
<property name="text">
|
||||
<string>Remove &unavailable tracks from playlist</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Amarok::VolumeSlider</class>
|
||||
<extends>QSlider</extends>
|
||||
<header>widgets/sliderwidget.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>AnalyzerContainer</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>analyzer/analyzercontainer.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>PlaylistContainer</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>playlist/playlistcontainer.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>TrackSlider</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/trackslider.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>PlaylistSequence</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>playlist/playlistsequence.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>MultiLoadingIndicator</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/multiloadingindicator.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>PlayingWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/playingwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>FancyTabWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/fancytabwidget.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
545
src/core/mergedproxymodel.cpp
Normal file
545
src/core/mergedproxymodel.cpp
Normal file
@@ -0,0 +1,545 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "mergedproxymodel.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
// boost::multi_index still relies on these being in the global namespace.
|
||||
using std::placeholders::_1;
|
||||
using std::placeholders::_2;
|
||||
|
||||
#include <boost/multi_index_container.hpp>
|
||||
#include <boost/multi_index/member.hpp>
|
||||
#include <boost/multi_index/hashed_index.hpp>
|
||||
#include <boost/multi_index/ordered_index.hpp>
|
||||
|
||||
using boost::multi_index::hashed_unique;
|
||||
using boost::multi_index::identity;
|
||||
using boost::multi_index::indexed_by;
|
||||
using boost::multi_index::member;
|
||||
using boost::multi_index::multi_index_container;
|
||||
using boost::multi_index::ordered_unique;
|
||||
using boost::multi_index::tag;
|
||||
|
||||
std::size_t hash_value(const QModelIndex& index) { return qHash(index); }
|
||||
|
||||
namespace {
|
||||
|
||||
struct Mapping {
|
||||
explicit Mapping(const QModelIndex& _source_index) : source_index(_source_index) {}
|
||||
|
||||
QModelIndex source_index;
|
||||
};
|
||||
|
||||
struct tag_by_source {};
|
||||
struct tag_by_pointer {};
|
||||
|
||||
} // namespace
|
||||
|
||||
class MergedProxyModelPrivate {
|
||||
private:
|
||||
typedef multi_index_container<
|
||||
Mapping*,
|
||||
indexed_by<
|
||||
hashed_unique<tag<tag_by_source>,
|
||||
member<Mapping, QModelIndex, &Mapping::source_index> >,
|
||||
ordered_unique<tag<tag_by_pointer>, identity<Mapping*> > > >
|
||||
MappingContainer;
|
||||
|
||||
public:
|
||||
MappingContainer mappings_;
|
||||
};
|
||||
|
||||
MergedProxyModel::MergedProxyModel(QObject* parent)
|
||||
: QAbstractProxyModel(parent),
|
||||
resetting_model_(nullptr),
|
||||
p_(new MergedProxyModelPrivate) {}
|
||||
|
||||
MergedProxyModel::~MergedProxyModel() { DeleteAllMappings(); }
|
||||
|
||||
void MergedProxyModel::DeleteAllMappings() {
|
||||
const auto& begin = p_->mappings_.get<tag_by_pointer>().begin();
|
||||
const auto& end = p_->mappings_.get<tag_by_pointer>().end();
|
||||
qDeleteAll(begin, end);
|
||||
}
|
||||
|
||||
void MergedProxyModel::AddSubModel(const QModelIndex& source_parent, QAbstractItemModel* submodel) {
|
||||
|
||||
connect(submodel, SIGNAL(modelReset()), this, SLOT(SubModelReset()));
|
||||
connect(submodel, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(RowsAboutToBeInserted(QModelIndex, int, int)));
|
||||
connect(submodel, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(RowsAboutToBeRemoved(QModelIndex, int, int)));
|
||||
connect(submodel, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(RowsInserted(QModelIndex, int, int)));
|
||||
connect(submodel, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(RowsRemoved(QModelIndex, int, int)));
|
||||
connect(submodel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(DataChanged(QModelIndex, QModelIndex)));
|
||||
|
||||
QModelIndex proxy_parent = mapFromSource(source_parent);
|
||||
const int rows = submodel->rowCount();
|
||||
|
||||
if (rows) beginInsertRows(proxy_parent, 0, rows - 1);
|
||||
|
||||
merge_points_.insert(submodel, source_parent);
|
||||
|
||||
if (rows) endInsertRows();
|
||||
}
|
||||
|
||||
void MergedProxyModel::RemoveSubModel(const QModelIndex& source_parent) {
|
||||
// Find the submodel that the parent corresponded to
|
||||
QAbstractItemModel* submodel = merge_points_.key(source_parent);
|
||||
merge_points_.remove(submodel);
|
||||
|
||||
// The submodel might have been deleted already so we must be careful not
|
||||
// to dereference it.
|
||||
|
||||
// Remove all the children of the item that got deleted
|
||||
QModelIndex proxy_parent = mapFromSource(source_parent);
|
||||
|
||||
// We can't know how many children it had, since we can't dereference it
|
||||
resetting_model_ = submodel;
|
||||
beginRemoveRows(proxy_parent, 0, std::numeric_limits<int>::max() - 1);
|
||||
endRemoveRows();
|
||||
resetting_model_ = nullptr;
|
||||
|
||||
// Delete all the mappings that reference the submodel
|
||||
auto it = p_->mappings_.get<tag_by_pointer>().begin();
|
||||
auto end = p_->mappings_.get<tag_by_pointer>().end();
|
||||
while (it != end) {
|
||||
if ((*it)->source_index.model() == submodel) {
|
||||
delete *it;
|
||||
it = p_->mappings_.get<tag_by_pointer>().erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MergedProxyModel::setSourceModel(QAbstractItemModel* source_model) {
|
||||
|
||||
if (sourceModel()) {
|
||||
disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(SourceModelReset()));
|
||||
disconnect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(RowsAboutToBeInserted(QModelIndex,int,int)));
|
||||
disconnect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(RowsAboutToBeRemoved(QModelIndex,int,int)));
|
||||
disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(RowsInserted(QModelIndex,int,int)));
|
||||
disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(RowsRemoved(QModelIndex,int,int)));
|
||||
disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(DataChanged(QModelIndex, QModelIndex)));
|
||||
disconnect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), this, SLOT(LayoutAboutToBeChanged()));
|
||||
disconnect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(LayoutChanged()));
|
||||
}
|
||||
|
||||
QAbstractProxyModel::setSourceModel(source_model);
|
||||
|
||||
connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(SourceModelReset()));
|
||||
connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(RowsAboutToBeInserted(QModelIndex, int, int)));
|
||||
connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(RowsAboutToBeRemoved(QModelIndex, int, int)));
|
||||
connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(RowsInserted(QModelIndex,int,int)));
|
||||
connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(RowsRemoved(QModelIndex,int,int)));
|
||||
connect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(DataChanged(QModelIndex,QModelIndex)));
|
||||
connect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), this, SLOT(LayoutAboutToBeChanged()));
|
||||
connect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(LayoutChanged()));
|
||||
|
||||
}
|
||||
|
||||
void MergedProxyModel::SourceModelReset() {
|
||||
|
||||
// Delete all mappings
|
||||
DeleteAllMappings();
|
||||
|
||||
// Reset the proxy
|
||||
beginResetModel();
|
||||
|
||||
// Clear the containers
|
||||
p_->mappings_.clear();
|
||||
merge_points_.clear();
|
||||
|
||||
endResetModel();
|
||||
|
||||
}
|
||||
|
||||
void MergedProxyModel::SubModelReset() {
|
||||
|
||||
QAbstractItemModel* submodel = static_cast<QAbstractItemModel*>(sender());
|
||||
|
||||
// TODO: When we require Qt 4.6, use beginResetModel() and endResetModel()
|
||||
// in CollectionModel and catch those here - that will let us do away with this
|
||||
// std::numeric_limits<int>::max() hack.
|
||||
|
||||
// Remove all the children of the item that got deleted
|
||||
QModelIndex source_parent = merge_points_.value(submodel);
|
||||
QModelIndex proxy_parent = mapFromSource(source_parent);
|
||||
|
||||
// We can't know how many children it had, since it's already disappeared...
|
||||
resetting_model_ = submodel;
|
||||
beginRemoveRows(proxy_parent, 0, std::numeric_limits<int>::max() - 1);
|
||||
endRemoveRows();
|
||||
resetting_model_ = nullptr;
|
||||
|
||||
// Delete all the mappings that reference the submodel
|
||||
auto it = p_->mappings_.get<tag_by_pointer>().begin();
|
||||
auto end = p_->mappings_.get<tag_by_pointer>().end();
|
||||
while (it != end) {
|
||||
if ((*it)->source_index.model() == submodel) {
|
||||
delete *it;
|
||||
it = p_->mappings_.get<tag_by_pointer>().erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// "Insert" items from the newly reset submodel
|
||||
int count = submodel->rowCount();
|
||||
if (count) {
|
||||
beginInsertRows(proxy_parent, 0, count - 1);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
emit SubModelReset(proxy_parent, submodel);
|
||||
|
||||
}
|
||||
|
||||
QModelIndex MergedProxyModel::GetActualSourceParent(const QModelIndex& source_parent, QAbstractItemModel* model) const {
|
||||
|
||||
if (!source_parent.isValid() && model != sourceModel())
|
||||
return merge_points_.value(model);
|
||||
return source_parent;
|
||||
|
||||
}
|
||||
|
||||
void MergedProxyModel::RowsAboutToBeInserted(const QModelIndex& source_parent, int start, int end) {
|
||||
beginInsertRows(mapFromSource(GetActualSourceParent(source_parent, static_cast<QAbstractItemModel*>(sender()))), start, end);
|
||||
}
|
||||
|
||||
void MergedProxyModel::RowsInserted(const QModelIndex&, int, int) {
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void MergedProxyModel::RowsAboutToBeRemoved(const QModelIndex& source_parent, int start, int end) {
|
||||
beginRemoveRows(mapFromSource(GetActualSourceParent(source_parent, static_cast<QAbstractItemModel*>(sender()))), start, end);
|
||||
}
|
||||
|
||||
void MergedProxyModel::RowsRemoved(const QModelIndex&, int, int) {
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
QModelIndex MergedProxyModel::mapToSource(const QModelIndex& proxy_index) const {
|
||||
|
||||
if (!proxy_index.isValid()) return QModelIndex();
|
||||
|
||||
Mapping* mapping = static_cast<Mapping*>(proxy_index.internalPointer());
|
||||
if (p_->mappings_.get<tag_by_pointer>().find(mapping) ==
|
||||
p_->mappings_.get<tag_by_pointer>().end())
|
||||
return QModelIndex();
|
||||
if (mapping->source_index.model() == resetting_model_) return QModelIndex();
|
||||
|
||||
return mapping->source_index;
|
||||
|
||||
}
|
||||
|
||||
QModelIndex MergedProxyModel::mapFromSource(const QModelIndex& source_index) const {
|
||||
|
||||
if (!source_index.isValid()) return QModelIndex();
|
||||
if (source_index.model() == resetting_model_) return QModelIndex();
|
||||
|
||||
// Add a mapping if we don't have one already
|
||||
const auto& it = p_->mappings_.get<tag_by_source>().find(source_index);
|
||||
Mapping* mapping;
|
||||
if (it != p_->mappings_.get<tag_by_source>().end()) {
|
||||
mapping = *it;
|
||||
} else {
|
||||
mapping = new Mapping(source_index);
|
||||
const_cast<MergedProxyModel*>(this)->p_->mappings_.insert(mapping);
|
||||
}
|
||||
|
||||
return createIndex(source_index.row(), source_index.column(), mapping);
|
||||
|
||||
}
|
||||
|
||||
QModelIndex MergedProxyModel::index(int row, int column, const QModelIndex &parent) const {
|
||||
|
||||
QModelIndex source_index;
|
||||
|
||||
if (!parent.isValid()) {
|
||||
source_index = sourceModel()->index(row, column, QModelIndex());
|
||||
}
|
||||
else {
|
||||
QModelIndex source_parent = mapToSource(parent);
|
||||
const QAbstractItemModel* child_model = merge_points_.key(source_parent);
|
||||
|
||||
if (child_model)
|
||||
source_index = child_model->index(row, column, QModelIndex());
|
||||
else
|
||||
source_index = source_parent.model()->index(row, column, source_parent);
|
||||
}
|
||||
|
||||
return mapFromSource(source_index);
|
||||
|
||||
}
|
||||
|
||||
QModelIndex MergedProxyModel::parent(const QModelIndex& child) const {
|
||||
|
||||
QModelIndex source_child = mapToSource(child);
|
||||
if (source_child.model() == sourceModel())
|
||||
return mapFromSource(source_child.parent());
|
||||
|
||||
if (!IsKnownModel(source_child.model())) return QModelIndex();
|
||||
|
||||
if (!source_child.parent().isValid())
|
||||
return mapFromSource(merge_points_.value(GetModel(source_child)));
|
||||
return mapFromSource(source_child.parent());
|
||||
|
||||
}
|
||||
|
||||
int MergedProxyModel::rowCount(const QModelIndex& parent) const {
|
||||
|
||||
if (!parent.isValid()) return sourceModel()->rowCount(QModelIndex());
|
||||
|
||||
QModelIndex source_parent = mapToSource(parent);
|
||||
if (!IsKnownModel(source_parent.model())) return 0;
|
||||
|
||||
const QAbstractItemModel* child_model = merge_points_.key(source_parent);
|
||||
if (child_model) {
|
||||
// Query the source model but disregard what it says, so it gets a chance
|
||||
// to lazy load
|
||||
source_parent.model()->rowCount(source_parent);
|
||||
|
||||
return child_model->rowCount(QModelIndex());
|
||||
}
|
||||
|
||||
return source_parent.model()->rowCount(source_parent);
|
||||
|
||||
}
|
||||
|
||||
int MergedProxyModel::columnCount(const QModelIndex& parent) const {
|
||||
|
||||
if (!parent.isValid()) return sourceModel()->columnCount(QModelIndex());
|
||||
|
||||
QModelIndex source_parent = mapToSource(parent);
|
||||
if (!IsKnownModel(source_parent.model())) return 0;
|
||||
|
||||
const QAbstractItemModel* child_model = merge_points_.key(source_parent);
|
||||
if (child_model) return child_model->columnCount(QModelIndex());
|
||||
return source_parent.model()->columnCount(source_parent);
|
||||
|
||||
}
|
||||
|
||||
bool MergedProxyModel::hasChildren(const QModelIndex& parent) const {
|
||||
|
||||
if (!parent.isValid()) return sourceModel()->hasChildren(QModelIndex());
|
||||
|
||||
QModelIndex source_parent = mapToSource(parent);
|
||||
if (!IsKnownModel(source_parent.model())) return false;
|
||||
|
||||
const QAbstractItemModel* child_model = merge_points_.key(source_parent);
|
||||
|
||||
if (child_model) return child_model->hasChildren(QModelIndex()) || source_parent.model()->hasChildren(source_parent);
|
||||
return source_parent.model()->hasChildren(source_parent);
|
||||
|
||||
}
|
||||
|
||||
QVariant MergedProxyModel::data(const QModelIndex& proxyIndex, int role) const {
|
||||
|
||||
QModelIndex source_index = mapToSource(proxyIndex);
|
||||
if (!IsKnownModel(source_index.model())) return QVariant();
|
||||
|
||||
return source_index.model()->data(source_index, role);
|
||||
|
||||
}
|
||||
|
||||
QMap<int, QVariant> MergedProxyModel::itemData(const QModelIndex& proxy_index) const {
|
||||
|
||||
QModelIndex source_index = mapToSource(proxy_index);
|
||||
|
||||
if (!source_index.isValid()) return sourceModel()->itemData(QModelIndex());
|
||||
return source_index.model()->itemData(source_index);
|
||||
|
||||
}
|
||||
|
||||
Qt::ItemFlags MergedProxyModel::flags(const QModelIndex& index) const {
|
||||
|
||||
QModelIndex source_index = mapToSource(index);
|
||||
|
||||
if (!source_index.isValid()) return sourceModel()->flags(QModelIndex());
|
||||
return source_index.model()->flags(source_index);
|
||||
|
||||
}
|
||||
|
||||
bool MergedProxyModel::setData(const QModelIndex& index, const QVariant& value, int role) {
|
||||
|
||||
QModelIndex source_index = mapToSource(index);
|
||||
|
||||
if (!source_index.isValid())
|
||||
return sourceModel()->setData(index, value, role);
|
||||
return GetModel(index)->setData(index, value, role);
|
||||
|
||||
}
|
||||
|
||||
QStringList MergedProxyModel::mimeTypes() const {
|
||||
|
||||
QStringList ret;
|
||||
ret << sourceModel()->mimeTypes();
|
||||
|
||||
for (const QAbstractItemModel* model : merge_points_.keys()) {
|
||||
ret << model->mimeTypes();
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
QMimeData* MergedProxyModel::mimeData(const QModelIndexList& indexes) const {
|
||||
|
||||
if (indexes.isEmpty()) return 0;
|
||||
|
||||
// Only ask the first index's model
|
||||
const QAbstractItemModel* model = mapToSource(indexes[0]).model();
|
||||
if (!model) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Only ask about the indexes that are actually in that model
|
||||
QModelIndexList indexes_in_model;
|
||||
|
||||
for (const QModelIndex& proxy_index : indexes) {
|
||||
QModelIndex source_index = mapToSource(proxy_index);
|
||||
if (source_index.model() != model) continue;
|
||||
indexes_in_model << source_index;
|
||||
}
|
||||
|
||||
return model->mimeData(indexes_in_model);
|
||||
|
||||
}
|
||||
|
||||
bool MergedProxyModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) {
|
||||
|
||||
if (!parent.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return sourceModel()->dropMimeData(data, action, row, column, parent);
|
||||
|
||||
}
|
||||
|
||||
QModelIndex MergedProxyModel::FindSourceParent(const QModelIndex& proxy_index) const {
|
||||
|
||||
if (!proxy_index.isValid()) return QModelIndex();
|
||||
|
||||
QModelIndex source_index = mapToSource(proxy_index);
|
||||
if (source_index.model() == sourceModel()) return source_index;
|
||||
return merge_points_.value(GetModel(source_index));
|
||||
|
||||
}
|
||||
|
||||
bool MergedProxyModel::canFetchMore(const QModelIndex& parent) const {
|
||||
|
||||
QModelIndex source_index = mapToSource(parent);
|
||||
|
||||
if (!source_index.isValid())
|
||||
return sourceModel()->canFetchMore(QModelIndex());
|
||||
return source_index.model()->canFetchMore(source_index);
|
||||
|
||||
}
|
||||
|
||||
void MergedProxyModel::fetchMore(const QModelIndex& parent) {
|
||||
|
||||
QModelIndex source_index = mapToSource(parent);
|
||||
|
||||
if (!source_index.isValid())
|
||||
sourceModel()->fetchMore(QModelIndex());
|
||||
else
|
||||
GetModel(source_index)->fetchMore(source_index);
|
||||
|
||||
}
|
||||
|
||||
QAbstractItemModel* MergedProxyModel::GetModel(const QModelIndex& source_index) const {
|
||||
|
||||
// This is essentially const_cast<QAbstractItemModel*>(source_index.model()),
|
||||
// but without the const_cast
|
||||
const QAbstractItemModel* const_model = source_index.model();
|
||||
if (const_model == sourceModel()) return sourceModel();
|
||||
for (QAbstractItemModel* submodel : merge_points_.keys()) {
|
||||
if (submodel == const_model) return submodel;
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
}
|
||||
|
||||
void MergedProxyModel::DataChanged(const QModelIndex& top_left, const QModelIndex& bottom_right) {
|
||||
emit dataChanged(mapFromSource(top_left), mapFromSource(bottom_right));
|
||||
}
|
||||
|
||||
void MergedProxyModel::LayoutAboutToBeChanged() {
|
||||
|
||||
old_merge_points_.clear();
|
||||
for (QAbstractItemModel* key : merge_points_.keys()) {
|
||||
old_merge_points_[key] = merge_points_.value(key);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MergedProxyModel::LayoutChanged() {
|
||||
|
||||
for (QAbstractItemModel* key : merge_points_.keys()) {
|
||||
if (!old_merge_points_.contains(key)) continue;
|
||||
|
||||
const int old_row = old_merge_points_[key].row();
|
||||
const int new_row = merge_points_[key].row();
|
||||
|
||||
if (old_row != new_row) {
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool MergedProxyModel::IsKnownModel(const QAbstractItemModel* model) const {
|
||||
|
||||
if (model == this || model == sourceModel() ||
|
||||
merge_points_.contains(const_cast<QAbstractItemModel*>(model)))
|
||||
return true;
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
QModelIndexList MergedProxyModel::mapFromSource(const QModelIndexList& source_indexes) const {
|
||||
|
||||
QModelIndexList ret;
|
||||
for (const QModelIndex& index : source_indexes) {
|
||||
ret << mapFromSource(index);
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
QModelIndexList MergedProxyModel::mapToSource(const QModelIndexList& proxy_indexes) const {
|
||||
|
||||
QModelIndexList ret;
|
||||
for (const QModelIndex& index : proxy_indexes) {
|
||||
ret << mapToSource(index);
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
110
src/core/mergedproxymodel.h
Normal file
110
src/core/mergedproxymodel.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MERGEDPROXYMODEL_H
|
||||
#define MERGEDPROXYMODEL_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QAbstractProxyModel>
|
||||
|
||||
std::size_t hash_value(const QModelIndex& index);
|
||||
|
||||
class MergedProxyModelPrivate;
|
||||
|
||||
class MergedProxyModel : public QAbstractProxyModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MergedProxyModel(QObject* parent = nullptr);
|
||||
~MergedProxyModel();
|
||||
|
||||
// Make another model appear as a child of the given item in the source model.
|
||||
void AddSubModel(const QModelIndex& source_parent, QAbstractItemModel* submodel);
|
||||
void RemoveSubModel(const QModelIndex& source_parent);
|
||||
|
||||
// Find the item in the source model that is the parent of the model
|
||||
// containing proxy_index. If proxy_index is in the source model, then
|
||||
// this just returns mapToSource(proxy_index).
|
||||
QModelIndex FindSourceParent(const QModelIndex& proxy_index) const;
|
||||
|
||||
// QAbstractItemModel
|
||||
QModelIndex index(int row, int column, const QModelIndex& parent) const;
|
||||
QModelIndex parent(const QModelIndex& child) const;
|
||||
int rowCount(const QModelIndex& parent) const;
|
||||
int columnCount(const QModelIndex& parent) const;
|
||||
QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const;
|
||||
bool hasChildren(const QModelIndex& parent) const;
|
||||
QMap<int, QVariant> itemData(const QModelIndex& proxyIndex) const;
|
||||
Qt::ItemFlags flags(const QModelIndex& index) const;
|
||||
bool setData(const QModelIndex& index, const QVariant& value, int role);
|
||||
QStringList mimeTypes() const;
|
||||
QMimeData* mimeData(const QModelIndexList& indexes) const;
|
||||
bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent);
|
||||
bool canFetchMore(const QModelIndex& parent) const;
|
||||
void fetchMore(const QModelIndex& parent);
|
||||
|
||||
// QAbstractProxyModel
|
||||
// Note that these implementations of map{To,From}Source will not always
|
||||
// give you an index in sourceModel(), you might get an index in one of the
|
||||
// child models instead.
|
||||
QModelIndex mapFromSource(const QModelIndex& sourceIndex) const;
|
||||
QModelIndex mapToSource(const QModelIndex& proxyIndex) const;
|
||||
void setSourceModel(QAbstractItemModel* sourceModel);
|
||||
|
||||
// Convenience functions that call map{To,From}Source multiple times.
|
||||
QModelIndexList mapFromSource(const QModelIndexList& source_indexes) const;
|
||||
QModelIndexList mapToSource(const QModelIndexList& proxy_indexes) const;
|
||||
|
||||
signals:
|
||||
void SubModelReset(const QModelIndex& root, QAbstractItemModel* model);
|
||||
|
||||
private slots:
|
||||
void SourceModelReset();
|
||||
void SubModelReset();
|
||||
|
||||
void RowsAboutToBeInserted(const QModelIndex& source_parent, int start, int end);
|
||||
void RowsInserted(const QModelIndex& source_parent, int start, int end);
|
||||
void RowsAboutToBeRemoved(const QModelIndex& source_parent, int start, int end);
|
||||
void RowsRemoved(const QModelIndex& source_parent, int start, int end);
|
||||
void DataChanged(const QModelIndex& top_left, const QModelIndex& bottom_right);
|
||||
|
||||
void LayoutAboutToBeChanged();
|
||||
void LayoutChanged();
|
||||
|
||||
private:
|
||||
QModelIndex GetActualSourceParent(const QModelIndex& source_parent,
|
||||
QAbstractItemModel* model) const;
|
||||
QAbstractItemModel* GetModel(const QModelIndex& source_index) const;
|
||||
void DeleteAllMappings();
|
||||
bool IsKnownModel(const QAbstractItemModel* model) const;
|
||||
|
||||
QMap<QAbstractItemModel*, QPersistentModelIndex> merge_points_;
|
||||
QAbstractItemModel* resetting_model_;
|
||||
|
||||
QMap<QAbstractItemModel*, QModelIndex> old_merge_points_;
|
||||
|
||||
std::unique_ptr<MergedProxyModelPrivate> p_;
|
||||
};
|
||||
|
||||
#endif // MERGEDPROXYMODEL_H
|
||||
|
||||
107
src/core/metatypes.cpp
Normal file
107
src/core/metatypes.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "metatypes.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <QMetaType>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkCookie>
|
||||
|
||||
#include "metatypes.h"
|
||||
|
||||
#include "engine/enginebase.h"
|
||||
#include "engine/enginetype.h"
|
||||
#ifdef HAVE_GSTREAMER
|
||||
# include "engine/gstengine.h"
|
||||
# include "engine/gstenginepipeline.h"
|
||||
#endif
|
||||
#include "collection/directory.h"
|
||||
#include "playlist/playlist.h"
|
||||
#include "equalizer/equalizer.h"
|
||||
#include "covermanager/albumcoverfetcher.h"
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
#include <QDBusMetaType>
|
||||
#include "core/mpris2.h"
|
||||
#include "dbus/metatypes.h"
|
||||
#endif
|
||||
|
||||
class QNetworkReply;
|
||||
#ifdef HAVE_GSTREAMER
|
||||
class GstEnginePipeline;
|
||||
#endif
|
||||
|
||||
void RegisterMetaTypes() {
|
||||
//qRegisterMetaType<CollapsibleInfoPane::Data>("CollapsibleInfoPane::Data");
|
||||
qRegisterMetaType<const char*>("const char*");
|
||||
qRegisterMetaType<CoverSearchResult>("CoverSearchResult");
|
||||
qRegisterMetaType<CoverSearchResults>("CoverSearchResults");
|
||||
qRegisterMetaType<Directory>("Directory");
|
||||
qRegisterMetaType<DirectoryList>("DirectoryList");
|
||||
qRegisterMetaType<Engine::SimpleMetaBundle>("Engine::SimpleMetaBundle");
|
||||
qRegisterMetaType<Engine::State>("Engine::State");
|
||||
qRegisterMetaType<Engine::TrackChangeFlags>("Engine::TrackChangeFlags");
|
||||
qRegisterMetaType<Equalizer::Params>("Equalizer::Params");
|
||||
qRegisterMetaType<EngineBase::PluginDetails>("EngineBase::PluginDetails");
|
||||
qRegisterMetaType<EngineBase::OutputDetails>("EngineBase::OutputDetails");
|
||||
#ifdef HAVE_GSTREAMER
|
||||
qRegisterMetaType<GstBuffer*>("GstBuffer*");
|
||||
qRegisterMetaType<GstElement*>("GstElement*");
|
||||
qRegisterMetaType<GstEnginePipeline*>("GstEnginePipeline*");
|
||||
#endif
|
||||
qRegisterMetaType<PlaylistItemList>("PlaylistItemList");
|
||||
qRegisterMetaType<PlaylistItemPtr>("PlaylistItemPtr");
|
||||
qRegisterMetaType<QList<CoverSearchResult> >("QList<CoverSearchResult>");
|
||||
qRegisterMetaType<QList<int>>("QList<int>");
|
||||
qRegisterMetaType<QList<PlaylistItemPtr> >("QList<PlaylistItemPtr>");
|
||||
qRegisterMetaType<PlaylistSequence::RepeatMode>("PlaylistSequence::RepeatMode");
|
||||
qRegisterMetaType<PlaylistSequence::ShuffleMode>("PlaylistSequence::ShuffleMode");
|
||||
qRegisterMetaType<QAbstractSocket::SocketState>("QAbstractSocket::SocketState");
|
||||
qRegisterMetaType<QList<QNetworkCookie> >("QList<QNetworkCookie>");
|
||||
qRegisterMetaType<QList<Song> >("QList<Song>");
|
||||
qRegisterMetaType<QNetworkCookie>("QNetworkCookie");
|
||||
qRegisterMetaType<QNetworkReply*>("QNetworkReply*");
|
||||
qRegisterMetaType<QNetworkReply**>("QNetworkReply**");
|
||||
qRegisterMetaType<SongList>("SongList");
|
||||
qRegisterMetaType<Song>("Song");
|
||||
qRegisterMetaTypeStreamOperators<Equalizer::Params>("Equalizer::Params");
|
||||
qRegisterMetaTypeStreamOperators<QMap<int, int> >("ColumnAlignmentMap");
|
||||
qRegisterMetaType<SubdirectoryList>("SubdirectoryList");
|
||||
qRegisterMetaType<Subdirectory>("Subdirectory");
|
||||
qRegisterMetaType<QList<QUrl>>("QList<QUrl>");
|
||||
qRegisterMetaType<QAbstractSocket::SocketState>();
|
||||
|
||||
qRegisterMetaType<Engine::EngineType>("EngineType");
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
qDBusRegisterMetaType<QImage>();
|
||||
qDBusRegisterMetaType<TrackMetadata>();
|
||||
qDBusRegisterMetaType<TrackIds>();
|
||||
qDBusRegisterMetaType<QList<QByteArray>>();
|
||||
qDBusRegisterMetaType<MprisPlaylist>();
|
||||
qDBusRegisterMetaType<MaybePlaylist>();
|
||||
qDBusRegisterMetaType<MprisPlaylistList>();
|
||||
qDBusRegisterMetaType<InterfacesAndProperties>();
|
||||
qDBusRegisterMetaType<ManagedObjectList>();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
6
src/core/metatypes.h
Normal file
6
src/core/metatypes.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef METATYPES_H
|
||||
#define METATYPES_H
|
||||
|
||||
void RegisterMetaTypes();
|
||||
|
||||
#endif
|
||||
76
src/core/mimedata.h
Normal file
76
src/core/mimedata.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MIMEDATA_H
|
||||
#define MIMEDATA_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QMimeData>
|
||||
|
||||
class MimeData : public QMimeData {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MimeData(bool clear = false, bool play_now = false, bool enqueue = false, bool open_in_new_playlist = false)
|
||||
: override_user_settings_(false),
|
||||
clear_first_(clear),
|
||||
play_now_(play_now),
|
||||
enqueue_now_(enqueue),
|
||||
open_in_new_playlist_(open_in_new_playlist),
|
||||
name_for_new_playlist_(QString()),
|
||||
from_doubleclick_(false) {}
|
||||
|
||||
// If this is set then MainWindow will not touch any of the other flags.
|
||||
bool override_user_settings_;
|
||||
|
||||
// If this is set then the playlist will be cleared before these songs
|
||||
// are inserted.
|
||||
bool clear_first_;
|
||||
|
||||
// If this is set then the first item that is inserted will start playing
|
||||
// immediately. Note: this is always overridden with the user's preference
|
||||
// if the MimeData goes via MainWindow, unless you set override_user_settings_.
|
||||
bool play_now_;
|
||||
|
||||
// If this is set then the items are added to the queue after being inserted.
|
||||
bool enqueue_now_;
|
||||
|
||||
// If this is set then the items are inserted into a newly created playlist.
|
||||
bool open_in_new_playlist_;
|
||||
|
||||
// This serves as a name for the new playlist in 'open_in_new_playlist_' mode.
|
||||
QString name_for_new_playlist_;
|
||||
|
||||
// This can be set if this MimeData goes via MainWindow (ie. it is created
|
||||
// manually in a double-click). The MainWindow will set the above flags to
|
||||
// the defaults set by the user.
|
||||
bool from_doubleclick_;
|
||||
|
||||
// Returns a pretty name for a playlist containing songs described by this MimeData
|
||||
// object. By pretty name we mean the value of 'name_for_new_playlist_' or generic
|
||||
// "Playlist" string if the 'name_for_new_playlist_' attribute is empty.
|
||||
QString get_name_for_new_playlist() {
|
||||
return name_for_new_playlist_.isEmpty() ? tr("Playlist") : name_for_new_playlist_;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // MIMEDATA_H
|
||||
|
||||
33
src/core/mpris.cpp
Normal file
33
src/core/mpris.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "mpris.h"
|
||||
#include "mpris2.h"
|
||||
|
||||
namespace mpris {
|
||||
|
||||
Mpris::Mpris(Application* app, QObject* parent) : QObject(parent), mpris2_(new mpris::Mpris2(app, this)) {
|
||||
connect(mpris2_, SIGNAL(RaiseMainWindow()), SIGNAL(RaiseMainWindow()));
|
||||
}
|
||||
|
||||
} // namespace mpris
|
||||
52
src/core/mpris.h
Normal file
52
src/core/mpris.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MPRIS_H
|
||||
#define MPRIS_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class Application;
|
||||
|
||||
namespace mpris {
|
||||
|
||||
class Mpris1;
|
||||
class Mpris2;
|
||||
|
||||
class Mpris : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Mpris(Application *app, QObject *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void RaiseMainWindow();
|
||||
|
||||
private:
|
||||
Mpris1 *mpris1_;
|
||||
Mpris2 *mpris2_;
|
||||
};
|
||||
|
||||
} // namespace mpris
|
||||
|
||||
#endif // MPRIS_H
|
||||
|
||||
543
src/core/mpris2.cpp
Normal file
543
src/core/mpris2.cpp
Normal file
@@ -0,0 +1,543 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDBusConnection>
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
#include "mpris2.h"
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/mainwindow.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/mpris_common.h"
|
||||
#include "core/mpris2_player.h"
|
||||
#include "core/mpris2_playlists.h"
|
||||
#include "core/mpris2_root.h"
|
||||
#include "core/mpris2_tracklist.h"
|
||||
#include "core/player.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "engine/enginebase.h"
|
||||
#include "playlist/playlist.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "playlist/playlistsequence.h"
|
||||
#include "covermanager/currentartloader.h"
|
||||
|
||||
QDBusArgument &operator<<(QDBusArgument &arg, const MprisPlaylist &playlist) {
|
||||
arg.beginStructure();
|
||||
arg << playlist.id << playlist.name << playlist.icon;
|
||||
arg.endStructure();
|
||||
return arg;
|
||||
}
|
||||
|
||||
const QDBusArgument &operator>> (const QDBusArgument &arg, MprisPlaylist &playlist) {
|
||||
arg.beginStructure();
|
||||
arg >> playlist.id >> playlist.name >> playlist.icon;
|
||||
arg.endStructure();
|
||||
return arg;
|
||||
}
|
||||
|
||||
QDBusArgument &operator<<(QDBusArgument &arg, const MaybePlaylist &playlist) {
|
||||
arg.beginStructure();
|
||||
arg << playlist.valid;
|
||||
arg << playlist.playlist;
|
||||
arg.endStructure();
|
||||
return arg;
|
||||
}
|
||||
|
||||
const QDBusArgument &operator>> (const QDBusArgument &arg, MaybePlaylist &playlist) {
|
||||
arg.beginStructure();
|
||||
arg >> playlist.valid >> playlist.playlist;
|
||||
arg.endStructure();
|
||||
return arg;
|
||||
}
|
||||
|
||||
namespace mpris {
|
||||
|
||||
const char* Mpris2::kMprisObjectPath = "/org/mpris/MediaPlayer2";
|
||||
const char* Mpris2::kServiceName = "org.mpris.MediaPlayer2.strawberry";
|
||||
const char* Mpris2::kFreedesktopPath = "org.freedesktop.DBus.Properties";
|
||||
|
||||
Mpris2::Mpris2(Application* app, QObject* parent) : QObject(parent), app_(app) {
|
||||
|
||||
new Mpris2Root(this);
|
||||
new Mpris2TrackList(this);
|
||||
new Mpris2Player(this);
|
||||
new Mpris2Playlists(this);
|
||||
|
||||
if (!QDBusConnection::sessionBus().registerService(kServiceName)) {
|
||||
qLog(Warning) << "Failed to register" << QString(kServiceName) << "on the session bus";
|
||||
return;
|
||||
}
|
||||
|
||||
QDBusConnection::sessionBus().registerObject(kMprisObjectPath, this);
|
||||
|
||||
connect(app_->current_art_loader(), SIGNAL(ArtLoaded(Song,QString,QImage)), SLOT(ArtLoaded(Song,QString)));
|
||||
|
||||
connect(app_->player()->engine(), SIGNAL(StateChanged(Engine::State)), SLOT(EngineStateChanged(Engine::State)));
|
||||
connect(app_->player(), SIGNAL(VolumeChanged(int)), SLOT(VolumeChanged()));
|
||||
connect(app_->player(), SIGNAL(Seeked(qlonglong)), SIGNAL(Seeked(qlonglong)));
|
||||
|
||||
connect(app_->playlist_manager(), SIGNAL(PlaylistManagerInitialized()), SLOT(PlaylistManagerInitialized()));
|
||||
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(CurrentSongChanged(Song)));
|
||||
connect(app_->playlist_manager(), SIGNAL(PlaylistChanged(Playlist*)), SLOT(PlaylistChanged(Playlist*)));
|
||||
connect(app_->playlist_manager(), SIGNAL(CurrentChanged(Playlist*)), SLOT(PlaylistCollectionChanged(Playlist*)));
|
||||
|
||||
}
|
||||
|
||||
// when PlaylistManager gets it ready, we connect PlaylistSequence with this
|
||||
void Mpris2::PlaylistManagerInitialized() {
|
||||
connect(app_->playlist_manager()->sequence(), SIGNAL(ShuffleModeChanged(PlaylistSequence::ShuffleMode)), SLOT(ShuffleModeChanged()));
|
||||
connect(app_->playlist_manager()->sequence(), SIGNAL(RepeatModeChanged(PlaylistSequence::RepeatMode)), SLOT(RepeatModeChanged()));
|
||||
}
|
||||
|
||||
void Mpris2::EngineStateChanged(Engine::State newState) {
|
||||
|
||||
if (newState != Engine::Playing && newState != Engine::Paused) {
|
||||
last_metadata_ = QVariantMap();
|
||||
EmitNotification("Metadata");
|
||||
}
|
||||
|
||||
EmitNotification("PlaybackStatus", PlaybackStatus(newState));
|
||||
if (newState == Engine::Playing) EmitNotification("CanSeek", CanSeek(newState));
|
||||
|
||||
}
|
||||
|
||||
void Mpris2::VolumeChanged() { EmitNotification("Volume"); }
|
||||
|
||||
void Mpris2::ShuffleModeChanged() { EmitNotification("Shuffle"); }
|
||||
|
||||
void Mpris2::RepeatModeChanged() {
|
||||
|
||||
EmitNotification("LoopStatus");
|
||||
EmitNotification("CanGoNext", CanGoNext());
|
||||
EmitNotification("CanGoPrevious", CanGoPrevious());
|
||||
|
||||
}
|
||||
|
||||
void Mpris2::EmitNotification(const QString &name, const QVariant &val) {
|
||||
EmitNotification(name, val, "org.mpris.MediaPlayer2.Player");
|
||||
}
|
||||
|
||||
void Mpris2::EmitNotification(const QString &name, const QVariant &val, const QString &mprisEntity) {
|
||||
|
||||
QDBusMessage msg = QDBusMessage::createSignal(kMprisObjectPath, kFreedesktopPath, "PropertiesChanged");
|
||||
QVariantMap map;
|
||||
map.insert(name, val);
|
||||
QVariantList args = QVariantList() << mprisEntity << map << QStringList();
|
||||
msg.setArguments(args);
|
||||
QDBusConnection::sessionBus().send(msg);
|
||||
|
||||
}
|
||||
|
||||
void Mpris2::EmitNotification(const QString &name) {
|
||||
|
||||
QVariant value;
|
||||
if (name == "PlaybackStatus") value = PlaybackStatus();
|
||||
else if (name == "LoopStatus") value = LoopStatus();
|
||||
else if (name == "Shuffle") value = Shuffle();
|
||||
else if (name == "Metadata") value = Metadata();
|
||||
else if (name == "Volume") value = Volume();
|
||||
else if (name == "Position") value = Position();
|
||||
else if (name == "CanGoNext") value = CanGoNext();
|
||||
else if (name == "CanGoPrevious") value = CanGoPrevious();
|
||||
else if (name == "CanSeek") value = CanSeek();
|
||||
|
||||
if (value.isValid()) EmitNotification(name, value);
|
||||
|
||||
}
|
||||
|
||||
//------------------Root Interface--------------------------//
|
||||
|
||||
bool Mpris2::CanQuit() const { return true; }
|
||||
|
||||
bool Mpris2::CanRaise() const { return true; }
|
||||
|
||||
bool Mpris2::HasTrackList() const { return true; }
|
||||
|
||||
QString Mpris2::Identity() const { return QCoreApplication::applicationName(); }
|
||||
|
||||
QString Mpris2::DesktopEntryAbsolutePath() const {
|
||||
QStringList xdg_data_dirs = QString(getenv("XDG_DATA_DIRS")).split(":");
|
||||
xdg_data_dirs.append("/usr/local/share/");
|
||||
xdg_data_dirs.append("/usr/share/");
|
||||
|
||||
for (const QString &directory : xdg_data_dirs) {
|
||||
QString path = QString("%1/applications/%2.desktop").arg(
|
||||
directory, QApplication::applicationName().toLower());
|
||||
if (QFile::exists(path)) return path;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString Mpris2::DesktopEntry() const {
|
||||
return QApplication::applicationName().toLower();
|
||||
}
|
||||
|
||||
QStringList Mpris2::SupportedUriSchemes() const {
|
||||
|
||||
static QStringList res = QStringList() << "file"
|
||||
<< "http"
|
||||
<< "cdda"
|
||||
<< "smb"
|
||||
<< "sftp";
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
QStringList Mpris2::SupportedMimeTypes() const {
|
||||
|
||||
static QStringList res = QStringList() << "application/ogg"
|
||||
<< "application/x-ogg"
|
||||
<< "application/x-ogm-audio"
|
||||
<< "audio/aac"
|
||||
<< "audio/mp4"
|
||||
<< "audio/mpeg"
|
||||
<< "audio/mpegurl"
|
||||
<< "audio/ogg"
|
||||
<< "audio/vnd.rn-realaudio"
|
||||
<< "audio/vorbis"
|
||||
<< "audio/x-flac"
|
||||
<< "audio/x-mp3"
|
||||
<< "audio/x-mpeg"
|
||||
<< "audio/x-mpegurl"
|
||||
<< "audio/x-ms-wma"
|
||||
<< "audio/x-musepack"
|
||||
<< "audio/x-oggflac"
|
||||
<< "audio/x-pn-realaudio"
|
||||
<< "audio/x-scpls"
|
||||
<< "audio/x-speex"
|
||||
<< "audio/x-vorbis"
|
||||
<< "audio/x-vorbis+ogg"
|
||||
<< "audio/x-wav"
|
||||
<< "video/x-ms-asf"
|
||||
<< "x-content/audio-player";
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
void Mpris2::Raise() { emit RaiseMainWindow(); }
|
||||
|
||||
void Mpris2::Quit() { qApp->quit(); }
|
||||
|
||||
QString Mpris2::PlaybackStatus() const {
|
||||
return PlaybackStatus(app_->player()->GetState());
|
||||
}
|
||||
|
||||
QString Mpris2::PlaybackStatus(Engine::State state) const {
|
||||
|
||||
switch (state) {
|
||||
case Engine::Playing: return "Playing";
|
||||
case Engine::Paused: return "Paused";
|
||||
default: return "Stopped";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString Mpris2::LoopStatus() const {
|
||||
|
||||
if (!app_->playlist_manager()->sequence()) {
|
||||
return "None";
|
||||
}
|
||||
|
||||
switch (app_->playlist_manager()->sequence()->repeat_mode()) {
|
||||
case PlaylistSequence::Repeat_Album:
|
||||
case PlaylistSequence::Repeat_Playlist: return "Playlist";
|
||||
case PlaylistSequence::Repeat_Track: return "Track";
|
||||
default: return "None";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Mpris2::SetLoopStatus(const QString &value) {
|
||||
|
||||
PlaylistSequence::RepeatMode mode = PlaylistSequence::Repeat_Off;
|
||||
|
||||
if (value == "None") {
|
||||
mode = PlaylistSequence::Repeat_Off;
|
||||
} else if (value == "Track") {
|
||||
mode = PlaylistSequence::Repeat_Track;
|
||||
} else if (value == "Playlist") {
|
||||
mode = PlaylistSequence::Repeat_Playlist;
|
||||
}
|
||||
|
||||
app_->playlist_manager()->active()->sequence()->SetRepeatMode(mode);
|
||||
|
||||
}
|
||||
|
||||
double Mpris2::Rate() const { return 1.0; }
|
||||
|
||||
void Mpris2::SetRate(double rate) {
|
||||
|
||||
if (rate == 0) {
|
||||
app_->player()->Pause();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool Mpris2::Shuffle() const {
|
||||
|
||||
return app_->playlist_manager()->sequence()->shuffle_mode() != PlaylistSequence::Shuffle_Off;
|
||||
|
||||
}
|
||||
|
||||
void Mpris2::SetShuffle(bool enable) {
|
||||
app_->playlist_manager()->active()->sequence()->SetShuffleMode(enable ? PlaylistSequence::Shuffle_All : PlaylistSequence::Shuffle_Off);
|
||||
}
|
||||
|
||||
QVariantMap Mpris2::Metadata() const { return last_metadata_; }
|
||||
|
||||
QString Mpris2::current_track_id() const {
|
||||
return QString("/org/strawberry/strawberrymusicplayer/Track/%1").arg(QString::number(app_->playlist_manager()->active()->current_row()));
|
||||
}
|
||||
|
||||
// We send Metadata change notification as soon as the process of
|
||||
// changing song starts...
|
||||
void Mpris2::CurrentSongChanged(const Song &song) {
|
||||
|
||||
ArtLoaded(song, "");
|
||||
EmitNotification("CanGoNext", CanGoNext());
|
||||
EmitNotification("CanGoPrevious", CanGoPrevious());
|
||||
EmitNotification("CanSeek", CanSeek());
|
||||
|
||||
}
|
||||
|
||||
// ... and we add the cover information later, when it's available.
|
||||
void Mpris2::ArtLoaded(const Song &song, const QString &art_uri) {
|
||||
|
||||
last_metadata_ = QVariantMap();
|
||||
song.ToXesam(&last_metadata_);
|
||||
|
||||
using mpris::AddMetadata;
|
||||
AddMetadata("mpris:trackid", current_track_id(), &last_metadata_);
|
||||
|
||||
if (!art_uri.isEmpty()) {
|
||||
AddMetadata("mpris:artUrl", art_uri, &last_metadata_);
|
||||
}
|
||||
|
||||
AddMetadata("year", song.year(), &last_metadata_);
|
||||
AddMetadata("bitrate", song.bitrate(), &last_metadata_);
|
||||
|
||||
EmitNotification("Metadata", last_metadata_);
|
||||
|
||||
}
|
||||
|
||||
double Mpris2::Volume() const { return app_->player()->GetVolume() / 100.0; }
|
||||
|
||||
void Mpris2::SetVolume(double value) { app_->player()->SetVolume(value * 100); }
|
||||
|
||||
qlonglong Mpris2::Position() const {
|
||||
return app_->player()->engine()->position_nanosec() / kNsecPerUsec;
|
||||
}
|
||||
|
||||
double Mpris2::MaximumRate() const { return 1.0; }
|
||||
|
||||
double Mpris2::MinimumRate() const { return 1.0; }
|
||||
|
||||
bool Mpris2::CanGoNext() const {
|
||||
return app_->playlist_manager()->active() && app_->playlist_manager()->active()->next_row() != -1;
|
||||
}
|
||||
|
||||
bool Mpris2::CanGoPrevious() const {
|
||||
return app_->playlist_manager()->active() && (app_->playlist_manager()->active()->previous_row() != -1 || app_->player()->PreviousWouldRestartTrack());
|
||||
}
|
||||
|
||||
bool Mpris2::CanPlay() const {
|
||||
return app_->playlist_manager()->active() && app_->playlist_manager()->active()->rowCount() != 0 && !(app_->player()->GetState() == Engine::Playing);
|
||||
}
|
||||
|
||||
// This one's a bit different than MPRIS 1 - we want this to be true even when
|
||||
// the song is already paused or stopped.
|
||||
bool Mpris2::CanPause() const {
|
||||
return (app_->player()->GetCurrentItem() && app_->player()->GetState() == Engine::Playing && !(app_->player()->GetCurrentItem()->options() & PlaylistItem::PauseDisabled)) || PlaybackStatus() == "Paused" || PlaybackStatus() == "Stopped";
|
||||
}
|
||||
|
||||
bool Mpris2::CanSeek() const { return CanSeek(app_->player()->GetState()); }
|
||||
|
||||
bool Mpris2::CanSeek(Engine::State state) const {
|
||||
return app_->player()->GetCurrentItem() && state != Engine::Empty;
|
||||
}
|
||||
|
||||
bool Mpris2::CanControl() const { return true; }
|
||||
|
||||
void Mpris2::Next() {
|
||||
if (CanGoNext()) {
|
||||
app_->player()->Next();
|
||||
}
|
||||
}
|
||||
|
||||
void Mpris2::Previous() {
|
||||
if (CanGoPrevious()) {
|
||||
app_->player()->Previous();
|
||||
}
|
||||
}
|
||||
|
||||
void Mpris2::Pause() {
|
||||
if (CanPause() && app_->player()->GetState() != Engine::Paused) {
|
||||
app_->player()->Pause();
|
||||
}
|
||||
}
|
||||
|
||||
void Mpris2::PlayPause() {
|
||||
if (CanPause()) {
|
||||
app_->player()->PlayPause();
|
||||
}
|
||||
}
|
||||
|
||||
void Mpris2::Stop() { app_->player()->Stop(); }
|
||||
|
||||
void Mpris2::Play() {
|
||||
if (CanPlay()) {
|
||||
app_->player()->Play();
|
||||
}
|
||||
}
|
||||
|
||||
void Mpris2::Seek(qlonglong offset) {
|
||||
if (CanSeek()) {
|
||||
app_->player()->SeekTo(app_->player()->engine()->position_nanosec() / kNsecPerSec + offset / kUsecPerSec);
|
||||
}
|
||||
}
|
||||
|
||||
void Mpris2::SetPosition(const QDBusObjectPath &trackId, qlonglong offset) {
|
||||
if (CanSeek() && trackId.path() == current_track_id() && offset >= 0) {
|
||||
offset *= kNsecPerUsec;
|
||||
|
||||
if(offset < app_->player()->GetCurrentItem()->Metadata().length_nanosec()) {
|
||||
app_->player()->SeekTo(offset / kNsecPerSec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Mpris2::OpenUri(const QString &uri) {
|
||||
app_->playlist_manager()->active()->InsertUrls(QList<QUrl>() << QUrl(uri), -1, true);
|
||||
}
|
||||
|
||||
TrackIds Mpris2::Tracks() const {
|
||||
// TODO
|
||||
return TrackIds();
|
||||
}
|
||||
|
||||
bool Mpris2::CanEditTracks() const { return false; }
|
||||
|
||||
TrackMetadata Mpris2::GetTracksMetadata(const TrackIds &tracks) const {
|
||||
// TODO
|
||||
return TrackMetadata();
|
||||
}
|
||||
|
||||
void Mpris2::AddTrack(const QString &uri, const QDBusObjectPath &afterTrack, bool setAsCurrent) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void Mpris2::RemoveTrack(const QDBusObjectPath &trackId) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void Mpris2::GoTo(const QDBusObjectPath &trackId) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
quint32 Mpris2::PlaylistCount() const {
|
||||
return app_->playlist_manager()->GetAllPlaylists().size();
|
||||
}
|
||||
|
||||
QStringList Mpris2::Orderings() const { return QStringList() << "User"; }
|
||||
|
||||
namespace {
|
||||
|
||||
QDBusObjectPath MakePlaylistPath(int id) {
|
||||
return QDBusObjectPath(QString("/org/strawberry/strawberrymusicplayer/PlaylistId/%1").arg(id));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MaybePlaylist Mpris2::ActivePlaylist() const {
|
||||
|
||||
MaybePlaylist maybe_playlist;
|
||||
Playlist* current_playlist = app_->playlist_manager()->current();
|
||||
maybe_playlist.valid = current_playlist;
|
||||
if (!current_playlist) {
|
||||
return maybe_playlist;
|
||||
}
|
||||
|
||||
maybe_playlist.playlist.id = MakePlaylistPath(current_playlist->id());
|
||||
maybe_playlist.playlist.name = app_->playlist_manager()->GetPlaylistName(current_playlist->id());
|
||||
return maybe_playlist;
|
||||
|
||||
}
|
||||
|
||||
void Mpris2::ActivatePlaylist(const QDBusObjectPath &playlist_id) {
|
||||
|
||||
QStringList split_path = playlist_id.path().split('/');
|
||||
qLog(Debug) << Q_FUNC_INFO << playlist_id.path() << split_path;
|
||||
if (split_path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
bool ok = false;
|
||||
int p = split_path.last().toInt(&ok);
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
if (!app_->playlist_manager()->IsPlaylistOpen(p)) {
|
||||
qLog(Error) << "Playlist isn't opened!";
|
||||
return;
|
||||
}
|
||||
app_->playlist_manager()->SetActivePlaylist(p);
|
||||
app_->player()->Next();
|
||||
|
||||
}
|
||||
|
||||
// TODO: Support sort orders.
|
||||
MprisPlaylistList Mpris2::GetPlaylists(quint32 index, quint32 max_count, const QString &order, bool reverse_order) {
|
||||
|
||||
MprisPlaylistList ret;
|
||||
for (Playlist* p : app_->playlist_manager()->GetAllPlaylists()) {
|
||||
MprisPlaylist mpris_playlist;
|
||||
mpris_playlist.id = MakePlaylistPath(p->id());
|
||||
mpris_playlist.name = app_->playlist_manager()->GetPlaylistName(p->id());
|
||||
ret << mpris_playlist;
|
||||
}
|
||||
|
||||
if (reverse_order) {
|
||||
std::reverse(ret.begin(), ret.end());
|
||||
}
|
||||
|
||||
return ret.mid(index, max_count);
|
||||
|
||||
}
|
||||
|
||||
void Mpris2::PlaylistChanged(Playlist* playlist) {
|
||||
|
||||
MprisPlaylist mpris_playlist;
|
||||
mpris_playlist.id = MakePlaylistPath(playlist->id());
|
||||
mpris_playlist.name = app_->playlist_manager()->GetPlaylistName(playlist->id());
|
||||
emit PlaylistChanged(mpris_playlist);
|
||||
|
||||
}
|
||||
|
||||
void Mpris2::PlaylistCollectionChanged(Playlist* playlist) {
|
||||
EmitNotification("PlaylistCount", "", "org.mpris.MediaPlayer2.Playlists");
|
||||
}
|
||||
|
||||
} // namespace mpris
|
||||
|
||||
234
src/core/mpris2.h
Normal file
234
src/core/mpris2.h
Normal file
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MPRIS2_H
|
||||
#define MPRIS2_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "playlist/playlistitem.h"
|
||||
|
||||
#include <QMetaObject>
|
||||
#include <QObject>
|
||||
#include <QtDBus>
|
||||
|
||||
class Application;
|
||||
class MainWindow;
|
||||
class Playlist;
|
||||
|
||||
typedef QList<QVariantMap> TrackMetadata;
|
||||
typedef QList<QDBusObjectPath> TrackIds;
|
||||
Q_DECLARE_METATYPE(TrackMetadata);
|
||||
|
||||
struct MprisPlaylist {
|
||||
QDBusObjectPath id;
|
||||
QString name;
|
||||
QString icon; // Uri
|
||||
};
|
||||
typedef QList<MprisPlaylist> MprisPlaylistList;
|
||||
Q_DECLARE_METATYPE(MprisPlaylist);
|
||||
Q_DECLARE_METATYPE(MprisPlaylistList);
|
||||
|
||||
struct MaybePlaylist {
|
||||
bool valid;
|
||||
MprisPlaylist playlist;
|
||||
};
|
||||
Q_DECLARE_METATYPE(MaybePlaylist);
|
||||
|
||||
QDBusArgument &operator<<(QDBusArgument &arg, const MprisPlaylist &playlist);
|
||||
const QDBusArgument &operator>> (const QDBusArgument &arg, MprisPlaylist &playlist);
|
||||
|
||||
QDBusArgument &operator<<(QDBusArgument &arg, const MaybePlaylist &playlist);
|
||||
const QDBusArgument &operator>> (const QDBusArgument &arg, MaybePlaylist &playlist);
|
||||
|
||||
QDBusArgument &operator<< (QDBusArgument &arg, const QImage &image);
|
||||
const QDBusArgument &operator>> (const QDBusArgument &arg, QImage &image);
|
||||
|
||||
namespace mpris {
|
||||
|
||||
class Mpris2 : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Mpris2(Application *app, QObject *parent = nullptr);
|
||||
|
||||
// org.mpris.MediaPlayer2 MPRIS 2.0 Root interface
|
||||
Q_PROPERTY(bool CanQuit READ CanQuit)
|
||||
Q_PROPERTY(bool CanRaise READ CanRaise)
|
||||
Q_PROPERTY(bool HasTrackList READ HasTrackList)
|
||||
Q_PROPERTY(QString Identity READ Identity)
|
||||
Q_PROPERTY(QString DesktopEntry READ DesktopEntry)
|
||||
Q_PROPERTY(QStringList SupportedUriSchemes READ SupportedUriSchemes)
|
||||
Q_PROPERTY(QStringList SupportedMimeTypes READ SupportedMimeTypes)
|
||||
|
||||
// org.mpris.MediaPlayer2 MPRIS 2.2 Root interface
|
||||
Q_PROPERTY(bool CanSetFullscreen READ CanSetFullscreen)
|
||||
Q_PROPERTY(bool Fullscreen READ Fullscreen WRITE SetFullscreen)
|
||||
|
||||
// org.mpris.MediaPlayer2.Player MPRIS 2.0 Player interface
|
||||
Q_PROPERTY(QString PlaybackStatus READ PlaybackStatus)
|
||||
Q_PROPERTY(QString LoopStatus READ LoopStatus WRITE SetLoopStatus)
|
||||
Q_PROPERTY(double Rate READ Rate WRITE SetRate)
|
||||
Q_PROPERTY(bool Shuffle READ Shuffle WRITE SetShuffle)
|
||||
Q_PROPERTY(QVariantMap Metadata READ Metadata)
|
||||
Q_PROPERTY(double Volume READ Volume WRITE SetVolume)
|
||||
Q_PROPERTY(qlonglong Position READ Position)
|
||||
Q_PROPERTY(double MinimumRate READ MinimumRate)
|
||||
Q_PROPERTY(double MaximumRate READ MaximumRate)
|
||||
Q_PROPERTY(bool CanGoNext READ CanGoNext)
|
||||
Q_PROPERTY(bool CanGoPrevious READ CanGoPrevious)
|
||||
Q_PROPERTY(bool CanPlay READ CanPlay)
|
||||
Q_PROPERTY(bool CanPause READ CanPause)
|
||||
Q_PROPERTY(bool CanSeek READ CanSeek)
|
||||
Q_PROPERTY(bool CanControl READ CanControl)
|
||||
|
||||
// org.mpris.MediaPlayer2.TrackList MPRIS 2.0 Player interface
|
||||
Q_PROPERTY(TrackIds Tracks READ Tracks)
|
||||
Q_PROPERTY(bool CanEditTracks READ CanEditTracks)
|
||||
|
||||
// org.mpris.MediaPlayer2.Playlists MPRIS 2.1 Playlists interface
|
||||
Q_PROPERTY(quint32 PlaylistCount READ PlaylistCount)
|
||||
Q_PROPERTY(QStringList Orderings READ Orderings)
|
||||
Q_PROPERTY(MaybePlaylist ActivePlaylist READ ActivePlaylist)
|
||||
|
||||
// Root Properties
|
||||
bool CanQuit() const;
|
||||
bool CanRaise() const;
|
||||
bool HasTrackList() const;
|
||||
QString Identity() const;
|
||||
QString DesktopEntry() const;
|
||||
QStringList SupportedUriSchemes() const;
|
||||
QStringList SupportedMimeTypes() const;
|
||||
|
||||
// Root Properties added in MPRIS 2.2
|
||||
bool CanSetFullscreen() const { return false; }
|
||||
bool Fullscreen() const { return false; }
|
||||
void SetFullscreen(bool) {}
|
||||
|
||||
// Methods
|
||||
void Raise();
|
||||
void Quit();
|
||||
|
||||
// Player Properties
|
||||
QString PlaybackStatus() const;
|
||||
QString LoopStatus() const;
|
||||
void SetLoopStatus(const QString &value);
|
||||
double Rate() const;
|
||||
void SetRate(double value);
|
||||
bool Shuffle() const;
|
||||
void SetShuffle(bool value);
|
||||
QVariantMap Metadata() const;
|
||||
double Volume() const;
|
||||
void SetVolume(double value);
|
||||
qlonglong Position() const;
|
||||
double MaximumRate() const;
|
||||
double MinimumRate() const;
|
||||
bool CanGoNext() const;
|
||||
bool CanGoPrevious() const;
|
||||
bool CanPlay() const;
|
||||
bool CanPause() const;
|
||||
bool CanSeek() const;
|
||||
bool CanControl() const;
|
||||
|
||||
// Methods
|
||||
void Next();
|
||||
void Previous();
|
||||
void Pause();
|
||||
void PlayPause();
|
||||
void Stop();
|
||||
void Play();
|
||||
void Seek(qlonglong offset);
|
||||
void SetPosition(const QDBusObjectPath &trackId, qlonglong offset);
|
||||
void OpenUri(const QString &uri);
|
||||
|
||||
// TrackList Properties
|
||||
TrackIds Tracks() const;
|
||||
bool CanEditTracks() const;
|
||||
|
||||
// Methods
|
||||
TrackMetadata GetTracksMetadata(const TrackIds &tracks) const;
|
||||
void AddTrack(const QString &uri, const QDBusObjectPath &afterTrack, bool setAsCurrent);
|
||||
void RemoveTrack(const QDBusObjectPath &trackId);
|
||||
void GoTo(const QDBusObjectPath &trackId);
|
||||
|
||||
// Playlist Properties
|
||||
quint32 PlaylistCount() const;
|
||||
QStringList Orderings() const;
|
||||
MaybePlaylist ActivePlaylist() const;
|
||||
|
||||
// Methods
|
||||
void ActivatePlaylist(const QDBusObjectPath &playlist_id);
|
||||
QList<MprisPlaylist> GetPlaylists(quint32 index, quint32 max_count, const QString &order, bool reverse_order);
|
||||
|
||||
signals:
|
||||
// Player
|
||||
void Seeked(qlonglong position);
|
||||
|
||||
// TrackList
|
||||
void TrackListReplaced(const TrackIds &Tracks, QDBusObjectPath CurrentTrack);
|
||||
void TrackAdded(const TrackMetadata &Metadata, QDBusObjectPath AfterTrack);
|
||||
void TrackRemoved(const QDBusObjectPath &trackId);
|
||||
void TrackMetadataChanged(const QDBusObjectPath &trackId, const TrackMetadata &metadata);
|
||||
|
||||
void RaiseMainWindow();
|
||||
|
||||
// Playlist
|
||||
void PlaylistChanged(const MprisPlaylist &playlist);
|
||||
|
||||
private slots:
|
||||
void ArtLoaded(const Song &song, const QString &art_uri);
|
||||
void EngineStateChanged(Engine::State newState);
|
||||
void VolumeChanged();
|
||||
|
||||
void PlaylistManagerInitialized();
|
||||
void CurrentSongChanged(const Song &song);
|
||||
void ShuffleModeChanged();
|
||||
void RepeatModeChanged();
|
||||
void PlaylistChanged(Playlist *playlist);
|
||||
void PlaylistCollectionChanged(Playlist *playlist);
|
||||
|
||||
private:
|
||||
void EmitNotification(const QString &name);
|
||||
void EmitNotification(const QString &name, const QVariant &val);
|
||||
void EmitNotification(const QString &name, const QVariant &val, const QString &mprisEntity);
|
||||
|
||||
QString PlaybackStatus(Engine::State state) const;
|
||||
|
||||
QString current_track_id() const;
|
||||
|
||||
bool CanSeek(Engine::State state) const;
|
||||
|
||||
QString DesktopEntryAbsolutePath() const;
|
||||
|
||||
private:
|
||||
static const char *kMprisObjectPath;
|
||||
static const char *kServiceName;
|
||||
static const char *kFreedesktopPath;
|
||||
|
||||
QVariantMap last_metadata_;
|
||||
|
||||
Application *app_;
|
||||
|
||||
};
|
||||
|
||||
} // namespace mpris
|
||||
|
||||
|
||||
#endif
|
||||
64
src/core/mpris_common.h
Normal file
64
src/core/mpris_common.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MPRIS_COMMON_H
|
||||
#define MPRIS_COMMON_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
#include <QVariantMap>
|
||||
|
||||
namespace mpris {
|
||||
|
||||
inline void AddMetadata(const QString &key, const QString &metadata, QVariantMap *map) {
|
||||
if (!metadata.isEmpty()) (*map)[key] = metadata;
|
||||
}
|
||||
|
||||
inline void AddMetadataAsList(const QString &key, const QString &metadata, QVariantMap *map) {
|
||||
if (!metadata.isEmpty()) (*map)[key] = QStringList() << metadata;
|
||||
}
|
||||
|
||||
inline void AddMetadata(const QString &key, int metadata, QVariantMap *map) {
|
||||
if (metadata > 0) (*map)[key] = metadata;
|
||||
}
|
||||
|
||||
inline void AddMetadata(const QString &key, qint64 metadata, QVariantMap *map) {
|
||||
if (metadata > 0) (*map)[key] = metadata;
|
||||
}
|
||||
|
||||
inline void AddMetadata(const QString &key, double metadata, QVariantMap *map) {
|
||||
if (metadata != 0.0) (*map)[key] = metadata;
|
||||
}
|
||||
|
||||
inline void AddMetadata(const QString &key, const QDateTime &metadata, QVariantMap *map) {
|
||||
if (metadata.isValid()) (*map)[key] = metadata;
|
||||
}
|
||||
|
||||
inline QString AsMPRISDateTimeType(uint time) {
|
||||
return time != -1 ? QDateTime::fromTime_t(time).toString(Qt::ISODate) : "";
|
||||
}
|
||||
|
||||
} // namespace mpris
|
||||
|
||||
#endif // MPRIS_COMMON_H
|
||||
|
||||
90
src/core/multisortfilterproxy.cpp
Normal file
90
src/core/multisortfilterproxy.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "multisortfilterproxy.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
#include <QDate>
|
||||
#include <QDateTime>
|
||||
#include <QTime>
|
||||
|
||||
MultiSortFilterProxy::MultiSortFilterProxy(QObject *parent)
|
||||
: QSortFilterProxyModel(parent) {}
|
||||
|
||||
void MultiSortFilterProxy::AddSortSpec(int role, Qt::SortOrder order) {
|
||||
sorting_ << SortSpec(role, order);
|
||||
}
|
||||
|
||||
bool MultiSortFilterProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const {
|
||||
|
||||
for (const SortSpec &spec : sorting_) {
|
||||
const int ret = Compare(left.data(spec.first), right.data(spec.first));
|
||||
|
||||
if (ret < 0) {
|
||||
return spec.second == Qt::AscendingOrder;
|
||||
}
|
||||
else if (ret > 0) {
|
||||
return spec.second != Qt::AscendingOrder;
|
||||
}
|
||||
}
|
||||
|
||||
return left.row() < right.row();
|
||||
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline int DoCompare(T left, T right) {
|
||||
|
||||
if (left < right) return -1;
|
||||
if (left > right) return 1;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int MultiSortFilterProxy::Compare(const QVariant &left, const QVariant &right) const {
|
||||
|
||||
// Copied from the QSortFilterProxyModel::lessThan implementation, but returns
|
||||
// -1, 0 or 1 instead of true or false.
|
||||
switch (left.userType()) {
|
||||
case QVariant::Invalid: return (right.type() != QVariant::Invalid) ? -1 : 0;
|
||||
case QVariant::Int: return DoCompare(left.toInt(), right.toInt());
|
||||
case QVariant::UInt: return DoCompare(left.toUInt(), right.toUInt());
|
||||
case QVariant::LongLong: return DoCompare(left.toLongLong(), right.toLongLong());
|
||||
case QVariant::ULongLong: return DoCompare(left.toULongLong(), right.toULongLong());
|
||||
case QMetaType::Float: return DoCompare(left.toFloat(), right.toFloat());
|
||||
case QVariant::Double: return DoCompare(left.toDouble(), right.toDouble());
|
||||
case QVariant::Char: return DoCompare(left.toChar(), right.toChar());
|
||||
case QVariant::Date: return DoCompare(left.toDate(), right.toDate());
|
||||
case QVariant::Time: return DoCompare(left.toTime(), right.toTime());
|
||||
case QVariant::DateTime: return DoCompare(left.toDateTime(), right.toDateTime());
|
||||
case QVariant::String:
|
||||
default:
|
||||
if (isSortLocaleAware())
|
||||
return left.toString().localeAwareCompare(right.toString());
|
||||
else
|
||||
return left.toString().compare(right.toString(), sortCaseSensitivity());
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
45
src/core/multisortfilterproxy.h
Normal file
45
src/core/multisortfilterproxy.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MULTISORTFILTERPROXY_H
|
||||
#define MULTISORTFILTERPROXY_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class MultiSortFilterProxy : public QSortFilterProxyModel {
|
||||
public:
|
||||
MultiSortFilterProxy(QObject *parent = nullptr);
|
||||
|
||||
void AddSortSpec(int role, Qt::SortOrder order = Qt::AscendingOrder);
|
||||
|
||||
protected:
|
||||
bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
|
||||
|
||||
private:
|
||||
int Compare(const QVariant &left, const QVariant &right) const;
|
||||
|
||||
typedef QPair<int, Qt::SortOrder> SortSpec;
|
||||
QList<SortSpec> sorting_;
|
||||
};
|
||||
|
||||
#endif // MULTISORTFILTERPROXY_H
|
||||
|
||||
28
src/core/musicstorage.cpp
Normal file
28
src/core/musicstorage.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "musicstorage.h"
|
||||
|
||||
MusicStorage::MusicStorage()
|
||||
{
|
||||
}
|
||||
|
||||
89
src/core/musicstorage.h
Normal file
89
src/core/musicstorage.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MUSICSTORAGE_H
|
||||
#define MUSICSTORAGE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "song.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <QMetaType>
|
||||
|
||||
class MusicStorage {
|
||||
public:
|
||||
MusicStorage();
|
||||
virtual ~MusicStorage() {}
|
||||
|
||||
enum Role {
|
||||
Role_Storage = Qt::UserRole + 100,
|
||||
Role_StorageForceConnect,
|
||||
Role_Capacity,
|
||||
Role_FreeSpace,
|
||||
};
|
||||
|
||||
// Values are saved in the database - don't change
|
||||
enum TranscodeMode {
|
||||
Transcode_Always = 1,
|
||||
Transcode_Never = 2,
|
||||
Transcode_Unsupported = 3,
|
||||
};
|
||||
|
||||
typedef std::function<void(float progress)> ProgressFunction;
|
||||
|
||||
struct CopyJob {
|
||||
QString source_;
|
||||
QString destination_;
|
||||
Song metadata_;
|
||||
bool overwrite_;
|
||||
bool mark_as_listened_;
|
||||
bool remove_original_;
|
||||
ProgressFunction progress_;
|
||||
};
|
||||
|
||||
struct DeleteJob {
|
||||
Song metadata_;
|
||||
};
|
||||
|
||||
virtual QString LocalPath() const { return QString(); }
|
||||
|
||||
virtual TranscodeMode GetTranscodeMode() const { return Transcode_Never; }
|
||||
virtual Song::FileType GetTranscodeFormat() const { return Song::Type_Unknown; }
|
||||
virtual bool GetSupportedFiletypes(QList<Song::FileType>* ret) { return true; }
|
||||
|
||||
virtual bool StartCopy(QList<Song::FileType>* supported_types) { return true;}
|
||||
virtual bool CopyToStorage(const CopyJob& job) = 0;
|
||||
virtual void FinishCopy(bool success) {}
|
||||
|
||||
virtual void StartDelete() {}
|
||||
virtual bool DeleteFromStorage(const DeleteJob& job) = 0;
|
||||
virtual void FinishDelete(bool success) {}
|
||||
|
||||
virtual void Eject() {}
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(MusicStorage*);
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<MusicStorage>);
|
||||
|
||||
#endif // MUSICSTORAGE_H
|
||||
|
||||
224
src/core/network.cpp
Normal file
224
src/core/network.cpp
Normal file
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "network.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkDiskCache>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "utilities.h"
|
||||
|
||||
QMutex ThreadSafeNetworkDiskCache::sMutex;
|
||||
QNetworkDiskCache *ThreadSafeNetworkDiskCache::sCache = nullptr;
|
||||
|
||||
ThreadSafeNetworkDiskCache::ThreadSafeNetworkDiskCache(QObject *parent)
|
||||
: QAbstractNetworkCache(parent) {
|
||||
QMutexLocker l(&sMutex);
|
||||
if (!sCache) {
|
||||
sCache = new QNetworkDiskCache;
|
||||
sCache->setCacheDirectory(Utilities::GetConfigPath(Utilities::Path_NetworkCache));
|
||||
}
|
||||
}
|
||||
|
||||
qint64 ThreadSafeNetworkDiskCache::cacheSize() const {
|
||||
QMutexLocker l(&sMutex);
|
||||
return sCache->cacheSize();
|
||||
}
|
||||
|
||||
QIODevice *ThreadSafeNetworkDiskCache::data(const QUrl &url) {
|
||||
QMutexLocker l(&sMutex);
|
||||
return sCache->data(url);
|
||||
}
|
||||
|
||||
void ThreadSafeNetworkDiskCache::insert(QIODevice *device) {
|
||||
QMutexLocker l(&sMutex);
|
||||
sCache->insert(device);
|
||||
}
|
||||
|
||||
QNetworkCacheMetaData ThreadSafeNetworkDiskCache::metaData(const QUrl &url) {
|
||||
QMutexLocker l(&sMutex);
|
||||
return sCache->metaData(url);
|
||||
}
|
||||
|
||||
QIODevice *ThreadSafeNetworkDiskCache::prepare(const QNetworkCacheMetaData &metaData) {
|
||||
QMutexLocker l(&sMutex);
|
||||
return sCache->prepare(metaData);
|
||||
}
|
||||
|
||||
bool ThreadSafeNetworkDiskCache::remove(const QUrl &url) {
|
||||
QMutexLocker l(&sMutex);
|
||||
return sCache->remove(url);
|
||||
}
|
||||
|
||||
void ThreadSafeNetworkDiskCache::updateMetaData(const QNetworkCacheMetaData &metaData) {
|
||||
QMutexLocker l(&sMutex);
|
||||
sCache->updateMetaData(metaData);
|
||||
}
|
||||
|
||||
void ThreadSafeNetworkDiskCache::clear() {
|
||||
QMutexLocker l(&sMutex);
|
||||
sCache->clear();
|
||||
}
|
||||
|
||||
NetworkAccessManager::NetworkAccessManager(QObject *parent)
|
||||
: QNetworkAccessManager(parent) {
|
||||
setCache(new ThreadSafeNetworkDiskCache(this));
|
||||
}
|
||||
|
||||
QNetworkReply *NetworkAccessManager::createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) {
|
||||
|
||||
QByteArray user_agent = QString("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion()).toUtf8();
|
||||
|
||||
if (request.hasRawHeader("User-Agent")) {
|
||||
// Append the existing user-agent set by a client collection (such as
|
||||
// libmygpo-qt).
|
||||
user_agent += " " + request.rawHeader("User-Agent");
|
||||
}
|
||||
|
||||
QNetworkRequest new_request(request);
|
||||
new_request.setRawHeader("User-Agent", user_agent);
|
||||
|
||||
if (op == QNetworkAccessManager::PostOperation &&
|
||||
!new_request.header(QNetworkRequest::ContentTypeHeader).isValid()) {
|
||||
new_request.setHeader(QNetworkRequest::ContentTypeHeader,
|
||||
"application/x-www-form-urlencoded");
|
||||
}
|
||||
|
||||
// Prefer the cache unless the caller has changed the setting already
|
||||
if (request.attribute(QNetworkRequest::CacheLoadControlAttribute).toInt() == QNetworkRequest::PreferNetwork) {
|
||||
new_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
||||
}
|
||||
|
||||
return QNetworkAccessManager::createRequest(op, new_request, outgoingData);
|
||||
}
|
||||
|
||||
|
||||
NetworkTimeouts::NetworkTimeouts(int timeout_msec, QObject *parent)
|
||||
: QObject(parent), timeout_msec_(timeout_msec) {}
|
||||
|
||||
void NetworkTimeouts::AddReply(QNetworkReply *reply) {
|
||||
|
||||
if (timers_.contains(reply)) return;
|
||||
|
||||
connect(reply, SIGNAL(destroyed()), SLOT(ReplyFinished()));
|
||||
connect(reply, SIGNAL(finished()), SLOT(ReplyFinished()));
|
||||
timers_[reply] = startTimer(timeout_msec_);
|
||||
|
||||
}
|
||||
|
||||
void NetworkTimeouts::AddReply(RedirectFollower *reply) {
|
||||
|
||||
if (redirect_timers_.contains(reply)) {
|
||||
return;
|
||||
}
|
||||
|
||||
NewClosure(reply, SIGNAL(destroyed()), this, SLOT(RedirectFinished(RedirectFollower*)), reply);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(RedirectFinished(RedirectFollower*)), reply);
|
||||
redirect_timers_[reply] = startTimer(timeout_msec_);
|
||||
|
||||
}
|
||||
|
||||
void NetworkTimeouts::ReplyFinished() {
|
||||
|
||||
QNetworkReply *reply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
if (timers_.contains(reply)) {
|
||||
killTimer(timers_.take(reply));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void NetworkTimeouts::RedirectFinished(RedirectFollower *reply) {
|
||||
|
||||
if (redirect_timers_.contains(reply)) {
|
||||
killTimer(redirect_timers_.take(reply));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void NetworkTimeouts::timerEvent(QTimerEvent *e) {
|
||||
|
||||
QNetworkReply *reply = timers_.key(e->timerId());
|
||||
if (reply) {
|
||||
reply->abort();
|
||||
}
|
||||
|
||||
RedirectFollower *redirect = redirect_timers_.key(e->timerId());
|
||||
if (redirect) {
|
||||
redirect->abort();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
RedirectFollower::RedirectFollower(QNetworkReply *first_reply, int max_redirects) : QObject(nullptr), current_reply_(first_reply), redirects_remaining_(max_redirects) {
|
||||
ConnectReply(first_reply);
|
||||
}
|
||||
|
||||
void RedirectFollower::ConnectReply(QNetworkReply *reply) {
|
||||
|
||||
connect(reply, SIGNAL(readyRead()), SLOT(ReadyRead()));
|
||||
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), SIGNAL(error(QNetworkReply::NetworkError)));
|
||||
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), SIGNAL(downloadProgress(qint64,qint64)));
|
||||
connect(reply, SIGNAL(uploadProgress(qint64,qint64)), SIGNAL(uploadProgress(qint64,qint64)));
|
||||
connect(reply, SIGNAL(finished()), SLOT(ReplyFinished()));
|
||||
|
||||
}
|
||||
|
||||
void RedirectFollower::ReadyRead() {
|
||||
|
||||
// Don't re-emit this signal for redirect replies.
|
||||
if (current_reply_->attribute(QNetworkRequest::RedirectionTargetAttribute).isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit readyRead();
|
||||
|
||||
}
|
||||
|
||||
void RedirectFollower::ReplyFinished() {
|
||||
|
||||
current_reply_->deleteLater();
|
||||
|
||||
if (current_reply_->attribute(QNetworkRequest::RedirectionTargetAttribute).isValid()) {
|
||||
if (redirects_remaining_-- == 0) {
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
const QUrl next_url = current_reply_->url().resolved(current_reply_->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl());
|
||||
|
||||
QNetworkRequest req(current_reply_->request());
|
||||
req.setUrl(next_url);
|
||||
|
||||
current_reply_ = current_reply_->manager()->get(req);
|
||||
ConnectReply(current_reply_);
|
||||
return;
|
||||
}
|
||||
|
||||
emit finished();
|
||||
|
||||
}
|
||||
|
||||
128
src/core/network.h
Normal file
128
src/core/network.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NETWORK_H
|
||||
#define NETWORK_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QAbstractNetworkCache>
|
||||
#include <QMutex>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
class QNetworkDiskCache;
|
||||
|
||||
class ThreadSafeNetworkDiskCache : public QAbstractNetworkCache {
|
||||
public:
|
||||
explicit ThreadSafeNetworkDiskCache(QObject *parent);
|
||||
|
||||
qint64 cacheSize() const;
|
||||
QIODevice *data(const QUrl &url);
|
||||
void insert(QIODevice *device);
|
||||
QNetworkCacheMetaData metaData(const QUrl &url);
|
||||
QIODevice *prepare(const QNetworkCacheMetaData &metaData);
|
||||
bool remove(const QUrl &url);
|
||||
void updateMetaData(const QNetworkCacheMetaData &metaData);
|
||||
|
||||
void clear();
|
||||
|
||||
private:
|
||||
static QMutex sMutex;
|
||||
static QNetworkDiskCache *sCache;
|
||||
};
|
||||
|
||||
class NetworkAccessManager : public QNetworkAccessManager {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NetworkAccessManager(QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData);
|
||||
};
|
||||
|
||||
class RedirectFollower : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RedirectFollower(QNetworkReply *first_reply, int max_redirects = 5);
|
||||
|
||||
bool hit_redirect_limit() const { return redirects_remaining_ < 0; }
|
||||
QNetworkReply *reply() const { return current_reply_; }
|
||||
|
||||
// These are all forwarded to the current reply.
|
||||
QNetworkReply::NetworkError error() const { return current_reply_->error(); }
|
||||
QString errorString() const { return current_reply_->errorString(); }
|
||||
QVariant attribute(QNetworkRequest::Attribute code) const { return current_reply_->attribute(code); }
|
||||
QVariant header(QNetworkRequest::KnownHeaders header) const { return current_reply_->header(header); }
|
||||
qint64 bytesAvailable() const { return current_reply_->bytesAvailable(); }
|
||||
QUrl url() const { return current_reply_->url(); }
|
||||
QByteArray readAll() { return current_reply_->readAll(); }
|
||||
void abort() { current_reply_->abort(); }
|
||||
|
||||
signals:
|
||||
// These are all forwarded from the current reply.
|
||||
void readyRead();
|
||||
void error(QNetworkReply::NetworkError);
|
||||
void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
|
||||
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
|
||||
// This is NOT emitted when a request that has a redirect finishes.
|
||||
void finished();
|
||||
|
||||
private slots:
|
||||
void ReadyRead();
|
||||
void ReplyFinished();
|
||||
|
||||
private:
|
||||
void ConnectReply(QNetworkReply *reply);
|
||||
|
||||
private:
|
||||
QNetworkReply *current_reply_;
|
||||
int redirects_remaining_;
|
||||
};
|
||||
|
||||
class NetworkTimeouts : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NetworkTimeouts(int timeout_msec, QObject *parent = nullptr);
|
||||
|
||||
// TODO: Template this to avoid code duplication.
|
||||
void AddReply(QNetworkReply *reply);
|
||||
void AddReply(RedirectFollower *reply);
|
||||
void SetTimeout(int msec) { timeout_msec_ = msec; }
|
||||
|
||||
protected:
|
||||
void timerEvent(QTimerEvent *e);
|
||||
|
||||
private slots:
|
||||
void ReplyFinished();
|
||||
void RedirectFinished(RedirectFollower *redirect);
|
||||
|
||||
private:
|
||||
int timeout_msec_;
|
||||
QMap<QNetworkReply*, int> timers_;
|
||||
QMap<RedirectFollower*, int> redirect_timers_;
|
||||
};
|
||||
|
||||
#endif // NETWORK_H
|
||||
|
||||
142
src/core/networkproxyfactory.cpp
Normal file
142
src/core/networkproxyfactory.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "networkproxyfactory.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <QMutexLocker>
|
||||
#include <QSettings>
|
||||
#include <QStringList>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
NetworkProxyFactory *NetworkProxyFactory::sInstance = nullptr;
|
||||
const char *NetworkProxyFactory::kSettingsGroup = "NetworkProxy";
|
||||
|
||||
NetworkProxyFactory::NetworkProxyFactory()
|
||||
: mode_(Mode_System),
|
||||
type_(QNetworkProxy::HttpProxy),
|
||||
port_(8080),
|
||||
use_authentication_(false) {
|
||||
#ifdef Q_OS_LINUX
|
||||
// Linux uses environment variables to pass proxy configuration information,
|
||||
// which systemProxyForQuery doesn't support for some reason.
|
||||
|
||||
QStringList urls;
|
||||
urls << QString::fromLocal8Bit(getenv("HTTP_PROXY"));
|
||||
urls << QString::fromLocal8Bit(getenv("http_proxy"));
|
||||
urls << QString::fromLocal8Bit(getenv("ALL_PROXY"));
|
||||
urls << QString::fromLocal8Bit(getenv("all_proxy"));
|
||||
|
||||
qLog(Debug) << "Detected system proxy URLs:" << urls;
|
||||
|
||||
for (const QString &url_str : urls) {
|
||||
|
||||
if (url_str.isEmpty()) continue;
|
||||
env_url_ = QUrl(url_str);
|
||||
break;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
NetworkProxyFactory *NetworkProxyFactory::Instance() {
|
||||
|
||||
if (!sInstance) {
|
||||
sInstance = new NetworkProxyFactory;
|
||||
}
|
||||
|
||||
return sInstance;
|
||||
|
||||
}
|
||||
|
||||
void NetworkProxyFactory::ReloadSettings() {
|
||||
|
||||
QMutexLocker l(&mutex_);
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
|
||||
mode_ = Mode(s.value("mode", Mode_System).toInt());
|
||||
type_ = QNetworkProxy::ProxyType(s.value("type", QNetworkProxy::HttpProxy).toInt());
|
||||
hostname_ = s.value("hostname").toString();
|
||||
port_ = s.value("port", 8080).toInt();
|
||||
use_authentication_ = s.value("use_authentication", false).toBool();
|
||||
username_ = s.value("username").toString();
|
||||
password_ = s.value("password").toString();
|
||||
|
||||
}
|
||||
|
||||
QList<QNetworkProxy> NetworkProxyFactory::queryProxy(const QNetworkProxyQuery &query) {
|
||||
|
||||
QMutexLocker l(&mutex_);
|
||||
|
||||
QNetworkProxy ret;
|
||||
|
||||
switch (mode_) {
|
||||
case Mode_System:
|
||||
#ifdef Q_OS_LINUX
|
||||
Q_UNUSED(query);
|
||||
|
||||
if (env_url_.isEmpty()) {
|
||||
ret.setType(QNetworkProxy::NoProxy);
|
||||
}
|
||||
else {
|
||||
ret.setHostName(env_url_.host());
|
||||
ret.setPort(env_url_.port());
|
||||
ret.setUser(env_url_.userName());
|
||||
ret.setPassword(env_url_.password());
|
||||
if (env_url_.scheme().startsWith("http"))
|
||||
ret.setType(QNetworkProxy::HttpProxy);
|
||||
else
|
||||
ret.setType(QNetworkProxy::Socks5Proxy);
|
||||
qLog(Debug) << "Using proxy URL:" << env_url_;
|
||||
}
|
||||
break;
|
||||
#else
|
||||
return systemProxyForQuery(query);
|
||||
#endif
|
||||
|
||||
case Mode_Direct:
|
||||
ret.setType(QNetworkProxy::NoProxy);
|
||||
break;
|
||||
|
||||
case Mode_Manual:
|
||||
ret.setType(type_);
|
||||
ret.setHostName(hostname_);
|
||||
ret.setPort(port_);
|
||||
if (use_authentication_) {
|
||||
ret.setUser(username_);
|
||||
ret.setPassword(password_);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return QList<QNetworkProxy>() << ret;
|
||||
|
||||
}
|
||||
|
||||
62
src/core/networkproxyfactory.h
Normal file
62
src/core/networkproxyfactory.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NETWORKPROXYFACTORY_H
|
||||
#define NETWORKPROXYFACTORY_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QMutex>
|
||||
#include <QNetworkProxyFactory>
|
||||
#include <QUrl>
|
||||
|
||||
class NetworkProxyFactory : public QNetworkProxyFactory {
|
||||
public:
|
||||
// These values are persisted
|
||||
enum Mode { Mode_System = 0, Mode_Direct = 1, Mode_Manual = 2, };
|
||||
|
||||
static NetworkProxyFactory *Instance();
|
||||
static const char *kSettingsGroup;
|
||||
|
||||
// These methods are thread-safe
|
||||
void ReloadSettings();
|
||||
QList<QNetworkProxy> queryProxy(const QNetworkProxyQuery &query);
|
||||
|
||||
private:
|
||||
NetworkProxyFactory();
|
||||
|
||||
static NetworkProxyFactory *sInstance;
|
||||
|
||||
QMutex mutex_;
|
||||
|
||||
Mode mode_;
|
||||
QNetworkProxy::ProxyType type_;
|
||||
QString hostname_;
|
||||
int port_;
|
||||
bool use_authentication_;
|
||||
QString username_;
|
||||
QString password_;
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
QUrl env_url_;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // NETWORKPROXYFACTORY_H
|
||||
298
src/core/organise.cpp
Normal file
298
src/core/organise.cpp
Normal file
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "organise.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QTimer>
|
||||
#include <QThread>
|
||||
#include <QUrl>
|
||||
|
||||
#include "musicstorage.h"
|
||||
#include "taskmanager.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/utilities.h"
|
||||
|
||||
using std::placeholders::_1;
|
||||
|
||||
const int Organise::kBatchSize = 10;
|
||||
const int Organise::kTranscodeProgressInterval = 500;
|
||||
|
||||
Organise::Organise(TaskManager *task_manager, std::shared_ptr<MusicStorage> destination, const OrganiseFormat &format, bool copy, bool overwrite, bool mark_as_listened, const NewSongInfoList &songs_info, bool eject_after)
|
||||
: thread_(nullptr),
|
||||
task_manager_(task_manager),
|
||||
transcoder_(new Transcoder(this)),
|
||||
destination_(destination),
|
||||
format_(format),
|
||||
copy_(copy),
|
||||
overwrite_(overwrite),
|
||||
mark_as_listened_(mark_as_listened),
|
||||
eject_after_(eject_after),
|
||||
task_count_(songs_info.count()),
|
||||
transcode_suffix_(1),
|
||||
tasks_complete_(0),
|
||||
started_(false),
|
||||
task_id_(0),
|
||||
current_copy_progress_(0) {
|
||||
|
||||
original_thread_ = thread();
|
||||
|
||||
for (const NewSongInfo &song_info : songs_info) {
|
||||
tasks_pending_ << Task(song_info);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Organise::Start() {
|
||||
|
||||
if (thread_) return;
|
||||
|
||||
task_id_ = task_manager_->StartTask(tr("Organising files"));
|
||||
task_manager_->SetTaskBlocksCollectionScans(true);
|
||||
|
||||
thread_ = new QThread;
|
||||
connect(thread_, SIGNAL(started()), SLOT(ProcessSomeFiles()));
|
||||
connect(transcoder_, SIGNAL(JobComplete(QString, QString, bool)), SLOT(FileTranscoded(QString, QString, bool)));
|
||||
|
||||
moveToThread(thread_);
|
||||
thread_->start();
|
||||
}
|
||||
|
||||
void Organise::ProcessSomeFiles() {
|
||||
|
||||
if (!started_) {
|
||||
transcode_temp_name_.open();
|
||||
|
||||
if (!destination_->StartCopy(&supported_filetypes_)) {
|
||||
// Failed to start - mark everything as failed :(
|
||||
for (const Task &task : tasks_pending_) files_with_errors_ << task.song_info_.song_.url().toLocalFile();
|
||||
tasks_pending_.clear();
|
||||
}
|
||||
started_ = true;
|
||||
}
|
||||
|
||||
// None left?
|
||||
if (tasks_pending_.isEmpty()) {
|
||||
if (!tasks_transcoding_.isEmpty()) {
|
||||
// Just wait - FileTranscoded will start us off again in a little while
|
||||
qLog(Debug) << "Waiting for transcoding jobs";
|
||||
transcode_progress_timer_.start(kTranscodeProgressInterval, this);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateProgress();
|
||||
|
||||
destination_->FinishCopy(files_with_errors_.isEmpty());
|
||||
if (eject_after_) destination_->Eject();
|
||||
|
||||
task_manager_->SetTaskFinished(task_id_);
|
||||
|
||||
emit Finished(files_with_errors_);
|
||||
|
||||
// Move back to the original thread so deleteLater() can get called in
|
||||
// the main thread's event loop
|
||||
moveToThread(original_thread_);
|
||||
deleteLater();
|
||||
|
||||
// Stop this thread
|
||||
thread_->quit();
|
||||
return;
|
||||
}
|
||||
|
||||
// We process files in batches so we can be cancelled part-way through.
|
||||
for (int i = 0; i < kBatchSize; ++i) {
|
||||
SetSongProgress(0);
|
||||
|
||||
if (tasks_pending_.isEmpty()) break;
|
||||
|
||||
Task task = tasks_pending_.takeFirst();
|
||||
qLog(Info) << "Processing" << task.song_info_.song_.url().toLocalFile();
|
||||
|
||||
// Use a Song instead of a tag reader
|
||||
Song song = task.song_info_.song_;
|
||||
if (!song.is_valid()) continue;
|
||||
|
||||
// Maybe this file is one that's been transcoded already?
|
||||
if (!task.transcoded_filename_.isEmpty()) {
|
||||
qLog(Debug) << "This file has already been transcoded";
|
||||
|
||||
// Set the new filetype on the song so the formatter gets it right
|
||||
song.set_filetype(task.new_filetype_);
|
||||
|
||||
// Fiddle the filename extension as well to match the new type
|
||||
song.set_url(QUrl::fromLocalFile(Utilities::FiddleFileExtension(song.basefilename(), task.new_extension_)));
|
||||
song.set_basefilename(Utilities::FiddleFileExtension(song.basefilename(), task.new_extension_));
|
||||
|
||||
// Have to set this to the size of the new file or else funny stuff happens
|
||||
song.set_filesize(QFileInfo(task.transcoded_filename_).size());
|
||||
}
|
||||
else {
|
||||
// Figure out if we need to transcode it
|
||||
Song::FileType dest_type = CheckTranscode(song.filetype());
|
||||
if (dest_type != Song::Type_Unknown) {
|
||||
// Get the preset
|
||||
TranscoderPreset preset = Transcoder::PresetForFileType(dest_type);
|
||||
qLog(Debug) << "Transcoding with" << preset.name_;
|
||||
|
||||
// Get a temporary name for the transcoded file
|
||||
task.transcoded_filename_ = transcode_temp_name_.fileName() + "-" + QString::number(transcode_suffix_++);
|
||||
task.new_extension_ = preset.extension_;
|
||||
task.new_filetype_ = dest_type;
|
||||
tasks_transcoding_[task.song_info_.song_.url().toLocalFile()] = task;
|
||||
|
||||
qLog(Debug) << "Transcoding to" << task.transcoded_filename_;
|
||||
|
||||
// Start the transcoding - this will happen in the background and
|
||||
// FileTranscoded() will get called when it's done. At that point the
|
||||
// task will get re-added to the pending queue with the new filename.
|
||||
transcoder_->AddJob(task.song_info_.song_.url().toLocalFile(), preset, task.transcoded_filename_);
|
||||
transcoder_->Start();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
MusicStorage::CopyJob job;
|
||||
job.source_ = task.transcoded_filename_.isEmpty() ? task.song_info_.song_.url().toLocalFile() : task.transcoded_filename_;
|
||||
job.destination_ = task.song_info_.new_filename_;
|
||||
job.metadata_ = song;
|
||||
job.overwrite_ = overwrite_;
|
||||
job.mark_as_listened_ = mark_as_listened_;
|
||||
job.remove_original_ = !copy_;
|
||||
job.progress_ = std::bind(&Organise::SetSongProgress, this, _1, !task.transcoded_filename_.isEmpty());
|
||||
|
||||
if (!destination_->CopyToStorage(job)) {
|
||||
files_with_errors_ << task.song_info_.song_.basefilename();
|
||||
} else {
|
||||
if (job.mark_as_listened_) {
|
||||
emit FileCopied(job.metadata_.id());
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up the temporary transcoded file
|
||||
if (!task.transcoded_filename_.isEmpty())
|
||||
QFile::remove(task.transcoded_filename_);
|
||||
|
||||
tasks_complete_++;
|
||||
}
|
||||
SetSongProgress(0);
|
||||
|
||||
QTimer::singleShot(0, this, SLOT(ProcessSomeFiles()));
|
||||
|
||||
}
|
||||
|
||||
Song::FileType Organise::CheckTranscode(Song::FileType original_type) const {
|
||||
|
||||
//if (original_type == Song::Type_Stream) return Song::Type_Unknown;
|
||||
|
||||
const MusicStorage::TranscodeMode mode = destination_->GetTranscodeMode();
|
||||
const Song::FileType format = destination_->GetTranscodeFormat();
|
||||
|
||||
switch (mode) {
|
||||
case MusicStorage::Transcode_Never:
|
||||
return Song::Type_Unknown;
|
||||
|
||||
case MusicStorage::Transcode_Always:
|
||||
if (original_type == format) return Song::Type_Unknown;
|
||||
return format;
|
||||
|
||||
case MusicStorage::Transcode_Unsupported:
|
||||
if (supported_filetypes_.isEmpty() || supported_filetypes_.contains(original_type)) return Song::Type_Unknown;
|
||||
|
||||
if (format != Song::Type_Unknown) return format;
|
||||
|
||||
// The user hasn't visited the device properties page yet to set a
|
||||
// preferred format for the device, so we have to pick the best
|
||||
// available one.
|
||||
return Transcoder::PickBestFormat(supported_filetypes_);
|
||||
}
|
||||
return Song::Type_Unknown;
|
||||
|
||||
}
|
||||
|
||||
void Organise::SetSongProgress(float progress, bool transcoded) {
|
||||
|
||||
const int max = transcoded ? 50 : 100;
|
||||
current_copy_progress_ = (transcoded ? 50 : 0) + qBound(0, static_cast<int>(progress * max), max - 1);
|
||||
UpdateProgress();
|
||||
|
||||
}
|
||||
|
||||
void Organise::UpdateProgress() {
|
||||
|
||||
const int total = task_count_ * 100;
|
||||
|
||||
// Update transcoding progress
|
||||
QMap<QString, float> transcode_progress = transcoder_->GetProgress();
|
||||
for (const QString &filename : transcode_progress.keys()) {
|
||||
if (!tasks_transcoding_.contains(filename)) continue;
|
||||
tasks_transcoding_[filename].transcode_progress_ = transcode_progress[filename];
|
||||
}
|
||||
|
||||
// Count the progress of all tasks that are in the queue. Files that need
|
||||
// transcoding total 50 for the transcode and 50 for the copy, files that
|
||||
// only need to be copied total 100.
|
||||
int progress = tasks_complete_ * 100;
|
||||
|
||||
for (const Task &task : tasks_pending_) {
|
||||
progress += qBound(0, static_cast<int>(task.transcode_progress_ * 50), 50);
|
||||
}
|
||||
for (const Task &task : tasks_transcoding_.values()) {
|
||||
progress += qBound(0, static_cast<int>(task.transcode_progress_ * 50), 50);
|
||||
}
|
||||
|
||||
// Add the progress of the track that's currently copying
|
||||
progress += current_copy_progress_;
|
||||
|
||||
task_manager_->SetTaskProgress(task_id_, progress, total);
|
||||
|
||||
}
|
||||
|
||||
void Organise::FileTranscoded(const QString &input, const QString &output, bool success) {
|
||||
|
||||
qLog(Info) << "File finished" << input << success;
|
||||
transcode_progress_timer_.stop();
|
||||
|
||||
Task task = tasks_transcoding_.take(input);
|
||||
if (!success) {
|
||||
files_with_errors_ << input;
|
||||
}
|
||||
else {
|
||||
tasks_pending_ << task;
|
||||
}
|
||||
QTimer::singleShot(0, this, SLOT(ProcessSomeFiles()));
|
||||
|
||||
}
|
||||
|
||||
void Organise::timerEvent(QTimerEvent *e) {
|
||||
|
||||
QObject::timerEvent(e);
|
||||
|
||||
if (e->timerId() == transcode_progress_timer_.timerId()) {
|
||||
UpdateProgress();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
114
src/core/organise.h
Normal file
114
src/core/organise.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ORGANISE_H
|
||||
#define ORGANISE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QBasicTimer>
|
||||
#include <QObject>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
#include "organiseformat.h"
|
||||
#include "transcoder/transcoder.h"
|
||||
|
||||
class MusicStorage;
|
||||
class TaskManager;
|
||||
|
||||
class Organise : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct NewSongInfo {
|
||||
NewSongInfo(const Song &song = Song(), const QString &new_filename = QString()) : song_(song), new_filename_(new_filename) {}
|
||||
Song song_;
|
||||
QString new_filename_;
|
||||
};
|
||||
typedef QList<NewSongInfo> NewSongInfoList;
|
||||
|
||||
Organise(TaskManager *task_manager, std::shared_ptr<MusicStorage> destination, const OrganiseFormat &format, bool copy, bool overwrite, bool mark_as_listened, const NewSongInfoList &songs, bool eject_after);
|
||||
|
||||
static const int kBatchSize;
|
||||
static const int kTranscodeProgressInterval;
|
||||
|
||||
void Start();
|
||||
|
||||
signals:
|
||||
void Finished(const QStringList &files_with_errors);
|
||||
void FileCopied(int database_id);
|
||||
|
||||
protected:
|
||||
void timerEvent(QTimerEvent *e);
|
||||
|
||||
private slots:
|
||||
void ProcessSomeFiles();
|
||||
void FileTranscoded(const QString &input, const QString &output, bool success);
|
||||
|
||||
private:
|
||||
void SetSongProgress(float progress, bool transcoded = false);
|
||||
void UpdateProgress();
|
||||
Song::FileType CheckTranscode(Song::FileType original_type) const;
|
||||
|
||||
private:
|
||||
struct Task {
|
||||
explicit Task(const NewSongInfo &song_info = NewSongInfo()) : song_info_(song_info), transcode_progress_(0.0) {}
|
||||
|
||||
NewSongInfo song_info_;
|
||||
|
||||
float transcode_progress_;
|
||||
QString transcoded_filename_;
|
||||
QString new_extension_;
|
||||
Song::FileType new_filetype_;
|
||||
};
|
||||
|
||||
QThread *thread_;
|
||||
QThread *original_thread_;
|
||||
TaskManager *task_manager_;
|
||||
Transcoder *transcoder_;
|
||||
std::shared_ptr<MusicStorage> destination_;
|
||||
QList<Song::FileType> supported_filetypes_;
|
||||
|
||||
const OrganiseFormat format_;
|
||||
const bool copy_;
|
||||
const bool overwrite_;
|
||||
const bool mark_as_listened_;
|
||||
const bool eject_after_;
|
||||
int task_count_;
|
||||
|
||||
QBasicTimer transcode_progress_timer_;
|
||||
QTemporaryFile transcode_temp_name_;
|
||||
int transcode_suffix_;
|
||||
|
||||
QList<Task> tasks_pending_;
|
||||
QMap<QString, Task> tasks_transcoding_;
|
||||
int tasks_complete_;
|
||||
|
||||
bool started_;
|
||||
|
||||
int task_id_;
|
||||
int current_copy_progress_;
|
||||
|
||||
QStringList files_with_errors_;
|
||||
};
|
||||
|
||||
#endif // ORGANISE_H
|
||||
312
src/core/organiseformat.cpp
Normal file
312
src/core/organiseformat.cpp
Normal file
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "organiseformat.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QPalette>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/arraysize.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/utilities.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
const char *OrganiseFormat::kTagPattern = "\\%([a-zA-Z]*)";
|
||||
const char *OrganiseFormat::kBlockPattern = "\\{([^{}]+)\\}";
|
||||
const QStringList OrganiseFormat::kKnownTags = QStringList() << "title"
|
||||
<< "album"
|
||||
<< "artist"
|
||||
<< "artistinitial"
|
||||
<< "albumartist"
|
||||
<< "composer"
|
||||
<< "track"
|
||||
<< "disc"
|
||||
<< "year"
|
||||
<< "genre"
|
||||
<< "comment"
|
||||
<< "length"
|
||||
<< "bitrate"
|
||||
<< "samplerate"
|
||||
<< "bitdepth"
|
||||
<< "extension"
|
||||
<< "performer"
|
||||
<< "grouping"
|
||||
<< "originalyear";
|
||||
|
||||
// From http://en.wikipedia.org/wiki/8.3_filename#Directory_table
|
||||
const char OrganiseFormat::kInvalidFatCharacters[] = "\"*/\\:<>?|";
|
||||
const int OrganiseFormat::kInvalidFatCharactersCount = arraysize(OrganiseFormat::kInvalidFatCharacters) - 1;
|
||||
|
||||
const char OrganiseFormat::kInvalidPrefixCharacters[] = ".";
|
||||
const int OrganiseFormat::kInvalidPrefixCharactersCount = arraysize(OrganiseFormat::kInvalidPrefixCharacters) - 1;
|
||||
|
||||
const QRgb OrganiseFormat::SyntaxHighlighter::kValidTagColorLight = qRgb(64, 64, 255);
|
||||
const QRgb OrganiseFormat::SyntaxHighlighter::kInvalidTagColorLight = qRgb(255, 64, 64);
|
||||
const QRgb OrganiseFormat::SyntaxHighlighter::kBlockColorLight = qRgb(230, 230, 230);
|
||||
|
||||
const QRgb OrganiseFormat::SyntaxHighlighter::kValidTagColorDark = qRgb(128, 128, 255);
|
||||
const QRgb OrganiseFormat::SyntaxHighlighter::kInvalidTagColorDark = qRgb(255, 128, 128);
|
||||
const QRgb OrganiseFormat::SyntaxHighlighter::kBlockColorDark = qRgb(64, 64, 64);
|
||||
|
||||
OrganiseFormat::OrganiseFormat(const QString &format)
|
||||
: format_(format),
|
||||
replace_non_ascii_(false),
|
||||
replace_spaces_(false),
|
||||
replace_the_(false) {}
|
||||
|
||||
void OrganiseFormat::set_format(const QString &v) {
|
||||
format_ = v;
|
||||
format_.replace('\\', '/');
|
||||
}
|
||||
|
||||
bool OrganiseFormat::IsValid() const {
|
||||
|
||||
int pos = 0;
|
||||
QString format_copy(format_);
|
||||
|
||||
Validator v;
|
||||
return v.validate(format_copy, pos) == QValidator::Acceptable;
|
||||
|
||||
}
|
||||
|
||||
QString OrganiseFormat::GetFilenameForSong(const Song &song) const {
|
||||
|
||||
QString filename = ParseBlock(format_, song);
|
||||
|
||||
if (QFileInfo(filename).completeBaseName().isEmpty()) {
|
||||
// Avoid having empty filenames, or filenames with extension only: in this
|
||||
// case, keep the original filename.
|
||||
// We remove the extension from "filename" if it exists, as
|
||||
// song.basefilename()
|
||||
// also contains the extension.
|
||||
filename =
|
||||
Utilities::PathWithoutFilenameExtension(filename) + song.basefilename();
|
||||
}
|
||||
|
||||
if (replace_spaces_) filename.replace(QRegExp("\\s"), "_");
|
||||
|
||||
if (replace_non_ascii_) {
|
||||
QString stripped;
|
||||
for (int i = 0; i < filename.length(); ++i) {
|
||||
const QCharRef c = filename[i];
|
||||
if (c < 128) {
|
||||
stripped.append(c);
|
||||
} else {
|
||||
const QString decomposition = c.decomposition();
|
||||
if (!decomposition.isEmpty() && decomposition[0] < 128)
|
||||
stripped.append(decomposition[0]);
|
||||
else
|
||||
stripped.append("_");
|
||||
}
|
||||
}
|
||||
filename = stripped;
|
||||
}
|
||||
|
||||
// Fix any parts of the path that start with dots.
|
||||
QStringList parts = filename.split("/");
|
||||
for (int i = 0; i < parts.count(); ++i) {
|
||||
QString *part = &parts[i];
|
||||
for (int j = 0; j < kInvalidPrefixCharactersCount; ++j) {
|
||||
if (part->startsWith(kInvalidPrefixCharacters[j])) {
|
||||
part->replace(0, 1, '_');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parts.join("/");
|
||||
|
||||
}
|
||||
|
||||
QString OrganiseFormat::ParseBlock(QString block, const Song &song, bool *any_empty) const {
|
||||
|
||||
QRegExp tag_regexp(kTagPattern);
|
||||
QRegExp block_regexp(kBlockPattern);
|
||||
|
||||
// Find any blocks first
|
||||
int pos = 0;
|
||||
while ((pos = block_regexp.indexIn(block, pos)) != -1) {
|
||||
// Recursively parse the block
|
||||
bool empty = false;
|
||||
QString value = ParseBlock(block_regexp.cap(1), song, &empty);
|
||||
if (empty) value = "";
|
||||
|
||||
// Replace the block's value
|
||||
block.replace(pos, block_regexp.matchedLength(), value);
|
||||
pos += value.length();
|
||||
}
|
||||
|
||||
// Now look for tags
|
||||
bool empty = false;
|
||||
pos = 0;
|
||||
while ((pos = tag_regexp.indexIn(block, pos)) != -1) {
|
||||
QString value = TagValue(tag_regexp.cap(1), song);
|
||||
if (value.isEmpty()) empty = true;
|
||||
|
||||
block.replace(pos, tag_regexp.matchedLength(), value);
|
||||
pos += value.length();
|
||||
}
|
||||
|
||||
if (any_empty) *any_empty = empty;
|
||||
return block;
|
||||
|
||||
}
|
||||
|
||||
QString OrganiseFormat::TagValue(const QString &tag, const Song &song) const {
|
||||
|
||||
QString value;
|
||||
|
||||
if (tag == "title")
|
||||
value = song.title();
|
||||
else if (tag == "album")
|
||||
value = song.album();
|
||||
else if (tag == "artist")
|
||||
value = song.artist();
|
||||
else if (tag == "composer")
|
||||
value = song.composer();
|
||||
else if (tag == "performer")
|
||||
value = song.performer();
|
||||
else if (tag == "grouping")
|
||||
value = song.grouping();
|
||||
else if (tag == "genre")
|
||||
value = song.genre();
|
||||
else if (tag == "comment")
|
||||
value = song.comment();
|
||||
else if (tag == "year")
|
||||
value = QString::number(song.year());
|
||||
else if (tag == "originalyear")
|
||||
value = QString::number(song.effective_originalyear());
|
||||
else if (tag == "track")
|
||||
value = QString::number(song.track());
|
||||
else if (tag == "disc")
|
||||
value = QString::number(song.disc());
|
||||
else if (tag == "length")
|
||||
value = QString::number(song.length_nanosec() / kNsecPerSec);
|
||||
else if (tag == "bitrate")
|
||||
value = QString::number(song.bitrate());
|
||||
else if (tag == "samplerate") value = QString::number(song.samplerate());
|
||||
else if (tag == "bitdepth") value = QString::number(song.bitdepth());
|
||||
else if (tag == "extension")
|
||||
value = QFileInfo(song.url().toLocalFile()).suffix();
|
||||
else if (tag == "artistinitial") {
|
||||
value = song.effective_albumartist().trimmed();
|
||||
if (replace_the_ && !value.isEmpty()) value.replace(QRegExp("^the\\s+", Qt::CaseInsensitive), "");
|
||||
if (!value.isEmpty()) value = value[0].toUpper();
|
||||
}
|
||||
else if (tag == "albumartist") {
|
||||
value = song.is_compilation() ? "Various Artists" : song.effective_albumartist();
|
||||
}
|
||||
|
||||
if (replace_the_ && (tag == "artist" || tag == "albumartist"))
|
||||
value.replace(QRegExp("^the\\s+", Qt::CaseInsensitive), "");
|
||||
|
||||
if (value == "0" || value == "-1") value = "";
|
||||
|
||||
// Prepend a 0 to single-digit track numbers
|
||||
if (tag == "track" && value.length() == 1) value.prepend('0');
|
||||
|
||||
// Replace characters that really shouldn't be in paths
|
||||
for (int i = 0; i < kInvalidFatCharactersCount; ++i) {
|
||||
value.replace(kInvalidFatCharacters[i], '_');
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
OrganiseFormat::Validator::Validator(QObject *parent) : QValidator(parent) {}
|
||||
|
||||
QValidator::State OrganiseFormat::Validator::validate(QString &input, int&) const {
|
||||
|
||||
QRegExp tag_regexp(kTagPattern);
|
||||
|
||||
// Make sure all the blocks match up
|
||||
int block_level = 0;
|
||||
for (int i = 0; i < input.length(); ++i) {
|
||||
if (input[i] == '{')
|
||||
block_level++;
|
||||
else if (input[i] == '}')
|
||||
block_level--;
|
||||
|
||||
if (block_level < 0 || block_level > 1) return QValidator::Invalid;
|
||||
}
|
||||
|
||||
if (block_level != 0) return QValidator::Invalid;
|
||||
|
||||
// Make sure the tags are valid
|
||||
int pos = 0;
|
||||
while ((pos = tag_regexp.indexIn(input, pos)) != -1) {
|
||||
if (!OrganiseFormat::kKnownTags.contains(tag_regexp.cap(1)))
|
||||
return QValidator::Invalid;
|
||||
|
||||
pos += tag_regexp.matchedLength();
|
||||
}
|
||||
|
||||
return QValidator::Acceptable;
|
||||
|
||||
}
|
||||
|
||||
OrganiseFormat::SyntaxHighlighter::SyntaxHighlighter(QObject *parent)
|
||||
: QSyntaxHighlighter(parent) {}
|
||||
|
||||
OrganiseFormat::SyntaxHighlighter::SyntaxHighlighter(QTextEdit *parent)
|
||||
: QSyntaxHighlighter(parent) {}
|
||||
|
||||
OrganiseFormat::SyntaxHighlighter::SyntaxHighlighter(QTextDocument *parent)
|
||||
: QSyntaxHighlighter(parent) {}
|
||||
|
||||
void OrganiseFormat::SyntaxHighlighter::highlightBlock(const QString &text) {
|
||||
|
||||
const bool light = QApplication::palette().color(QPalette::Base).value() > 128;
|
||||
const QRgb block_color = light ? kBlockColorLight : kBlockColorDark;
|
||||
const QRgb valid_tag_color = light ? kValidTagColorLight : kValidTagColorDark;
|
||||
const QRgb invalid_tag_color = light ? kInvalidTagColorLight : kInvalidTagColorDark;
|
||||
|
||||
QRegExp tag_regexp(kTagPattern);
|
||||
QRegExp block_regexp(kBlockPattern);
|
||||
|
||||
QTextCharFormat block_format;
|
||||
block_format.setBackground(QColor(block_color));
|
||||
|
||||
// Reset formatting
|
||||
setFormat(0, text.length(), QTextCharFormat());
|
||||
|
||||
// Blocks
|
||||
int pos = 0;
|
||||
while ((pos = block_regexp.indexIn(text, pos)) != -1) {
|
||||
setFormat(pos, block_regexp.matchedLength(), block_format);
|
||||
pos += block_regexp.matchedLength();
|
||||
}
|
||||
|
||||
// Tags
|
||||
pos = 0;
|
||||
while ((pos = tag_regexp.indexIn(text, pos)) != -1) {
|
||||
QTextCharFormat f = format(pos);
|
||||
f.setForeground(QColor(OrganiseFormat::kKnownTags.contains(tag_regexp.cap(1)) ? valid_tag_color : invalid_tag_color));
|
||||
|
||||
setFormat(pos, tag_regexp.matchedLength(), f);
|
||||
pos += tag_regexp.matchedLength();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
91
src/core/organiseformat.h
Normal file
91
src/core/organiseformat.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ORGANISEFORMAT_H
|
||||
#define ORGANISEFORMAT_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QSyntaxHighlighter>
|
||||
#include <QValidator>
|
||||
#include <QTextEdit>
|
||||
|
||||
#include "core/song.h"
|
||||
|
||||
class OrganiseFormat {
|
||||
|
||||
public:
|
||||
explicit OrganiseFormat(const QString &format = QString());
|
||||
|
||||
static const char* kTagPattern;
|
||||
static const char* kBlockPattern;
|
||||
static const QStringList kKnownTags;
|
||||
static const char kInvalidFatCharacters[];
|
||||
static const int kInvalidFatCharactersCount;
|
||||
static const char kInvalidPrefixCharacters[];
|
||||
static const int kInvalidPrefixCharactersCount;
|
||||
|
||||
QString format() const { return format_; }
|
||||
bool replace_non_ascii() const { return replace_non_ascii_; }
|
||||
bool replace_spaces() const { return replace_spaces_; }
|
||||
bool replace_the() const { return replace_the_; }
|
||||
|
||||
void set_format(const QString &v);
|
||||
void set_replace_non_ascii(bool v) { replace_non_ascii_ = v; }
|
||||
void set_replace_spaces(bool v) { replace_spaces_ = v; }
|
||||
void set_replace_the(bool v) { replace_the_ = v; }
|
||||
|
||||
bool IsValid() const;
|
||||
QString GetFilenameForSong(const Song &song) const;
|
||||
|
||||
class Validator : public QValidator {
|
||||
public:
|
||||
explicit Validator(QObject* parent = nullptr);
|
||||
QValidator::State validate(QString &format, int &pos) const;
|
||||
};
|
||||
|
||||
class SyntaxHighlighter : public QSyntaxHighlighter {
|
||||
public:
|
||||
static const QRgb kValidTagColorLight;
|
||||
static const QRgb kInvalidTagColorLight;
|
||||
static const QRgb kBlockColorLight;
|
||||
static const QRgb kValidTagColorDark;
|
||||
static const QRgb kInvalidTagColorDark;
|
||||
static const QRgb kBlockColorDark;
|
||||
|
||||
explicit SyntaxHighlighter(QObject* parent = nullptr);
|
||||
explicit SyntaxHighlighter(QTextEdit* parent);
|
||||
explicit SyntaxHighlighter(QTextDocument* parent);
|
||||
void highlightBlock(const QString &format);
|
||||
};
|
||||
|
||||
private:
|
||||
QString ParseBlock(QString block, const Song &song, bool* any_empty = nullptr) const;
|
||||
QString TagValue(const QString &tag, const Song &song) const;
|
||||
|
||||
QString format_;
|
||||
bool replace_non_ascii_;
|
||||
bool replace_spaces_;
|
||||
bool replace_the_;
|
||||
|
||||
};
|
||||
|
||||
#endif // ORGANISEFORMAT_H
|
||||
|
||||
784
src/core/player.cpp
Normal file
784
src/core/player.cpp
Normal file
@@ -0,0 +1,784 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "player.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QtDebug>
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/urlhandler.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "engine/enginetype.h"
|
||||
#include "engine/enginebase.h"
|
||||
#include "engine/enginedevice.h"
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
# include "engine/gstengine.h"
|
||||
#endif
|
||||
#ifdef HAVE_XINE
|
||||
# include "engine/xineengine.h"
|
||||
#endif
|
||||
#ifdef HAVE_PHONON
|
||||
# include "engine/phononengine.h"
|
||||
#endif
|
||||
#ifdef HAVE_VLC
|
||||
# include "engine/vlcengine.h"
|
||||
#endif
|
||||
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "playlist/playlist.h"
|
||||
#include "playlist/playlistitem.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
|
||||
#include "analyzer/analyzercontainer.h"
|
||||
#include "equalizer/equalizer.h"
|
||||
|
||||
#include "settings/backendsettingspage.h"
|
||||
#include "settings/behavioursettingspage.h"
|
||||
#include "settings/playlistsettingspage.h"
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
Player::Player(Application *app, QObject *parent)
|
||||
: PlayerInterface(parent),
|
||||
app_(app),
|
||||
//engine_(new GstEngine(app_->task_manager())),
|
||||
//engine_(CreateEngine()),
|
||||
stream_change_type_(Engine::First),
|
||||
last_state_(Engine::Empty),
|
||||
nb_errors_received_(0),
|
||||
volume_before_mute_(50),
|
||||
last_pressed_previous_(QDateTime::currentDateTime()),
|
||||
menu_previousmode_(PreviousBehaviour_DontRestart),
|
||||
seek_step_sec_(10)
|
||||
{
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(BackendSettingsPage::kSettingsGroup);
|
||||
Engine::EngineType enginetype = Engine::EngineTypeFromName(s.value("engine", BackendSettingsPage::EngineText_Xine).toString().toLower());
|
||||
s.endGroup();
|
||||
|
||||
CreateEngine(enginetype);
|
||||
|
||||
settings_.beginGroup("Player");
|
||||
|
||||
SetVolume(settings_.value("volume", 100).toInt());
|
||||
|
||||
#if 0
|
||||
connect(engine_.get(), SIGNAL(Error(QString)), SIGNAL(Error(QString)));
|
||||
connect(engine_.get(), SIGNAL(ValidSongRequested(QUrl)), SLOT(ValidSongRequested(QUrl)));
|
||||
connect(engine_.get(), SIGNAL(InvalidSongRequested(QUrl)), SLOT(InvalidSongRequested(QUrl)));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
Player::~Player() {}
|
||||
|
||||
EngineBase *Player::CreateEngine(Engine::EngineType enginetype) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
bool engine = false;
|
||||
EngineBase *enginebase = nullptr;
|
||||
|
||||
for (int i = 1 ; !engine ; i++) {
|
||||
switch(enginetype) {
|
||||
case Engine::None:
|
||||
#ifdef HAVE_XINE
|
||||
case Engine::Xine:
|
||||
engine=true;
|
||||
enginetype=Engine::Xine;
|
||||
enginebase = new XineEngine(app_->task_manager());
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_GSTREAMER
|
||||
case Engine::GStreamer:
|
||||
engine=true;
|
||||
enginetype=Engine::GStreamer;
|
||||
enginebase = new GstEngine(app_->task_manager());
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_PHONON
|
||||
case Engine::Phonon:
|
||||
engine=true;
|
||||
enginetype=Engine::Phonon;
|
||||
enginebase = new PhononEngine(app_->task_manager());
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_VLC
|
||||
case Engine::VLC:
|
||||
engine=true;
|
||||
enginetype=Engine::VLC;
|
||||
enginebase = new VLCEngine(app_->task_manager());
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
if (i > 1) { qFatal("No engine available!"); return nullptr; }
|
||||
QSettings s;
|
||||
s.beginGroup(BackendSettingsPage::kSettingsGroup);
|
||||
s.setValue("engine", "");
|
||||
s.setValue("output", "");
|
||||
s.setValue("device", "");
|
||||
s.endGroup();
|
||||
enginetype = Engine::None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(BackendSettingsPage::kSettingsGroup);
|
||||
s.setValue("engine", Engine::EngineNameFromType(enginetype));
|
||||
s.endGroup();
|
||||
|
||||
if (enginebase == nullptr) {
|
||||
qFatal("Failed to create engine!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
engine_.reset(enginebase);
|
||||
|
||||
return enginebase;
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Player::Init() {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
connect(engine_.get(), SIGNAL(Error(QString)), SIGNAL(Error(QString)));
|
||||
connect(engine_.get(), SIGNAL(ValidSongRequested(QUrl)), SLOT(ValidSongRequested(QUrl)));
|
||||
connect(engine_.get(), SIGNAL(InvalidSongRequested(QUrl)), SLOT(InvalidSongRequested(QUrl)));
|
||||
|
||||
if (!engine_->Init()) qFatal("Error initialising audio engine");
|
||||
|
||||
connect(engine_.get(), SIGNAL(StateChanged(Engine::State)), SLOT(EngineStateChanged(Engine::State)));
|
||||
connect(engine_.get(), SIGNAL(TrackAboutToEnd()), SLOT(TrackAboutToEnd()));
|
||||
connect(engine_.get(), SIGNAL(TrackEnded()), SLOT(TrackEnded()));
|
||||
connect(engine_.get(), SIGNAL(MetaData(Engine::SimpleMetaBundle)), SLOT(EngineMetadataReceived(Engine::SimpleMetaBundle)));
|
||||
|
||||
engine_->SetVolume(settings_.value("volume", 50).toInt());
|
||||
|
||||
analyzer_->SetEngine(engine_.get());
|
||||
|
||||
// Equalizer
|
||||
qLog(Debug) << "Creating equalizer";
|
||||
connect(equalizer_, SIGNAL(ParametersChanged(int,QList<int>)), app_->player()->engine(), SLOT(SetEqualizerParameters(int,QList<int>)));
|
||||
connect(equalizer_, SIGNAL(EnabledChanged(bool)), app_->player()->engine(), SLOT(SetEqualizerEnabled(bool)));
|
||||
connect(equalizer_, SIGNAL(StereoBalanceChanged(float)), app_->player()->engine(), SLOT(SetStereoBalance(float)));
|
||||
|
||||
engine_->SetEqualizerEnabled(equalizer_->is_enabled());
|
||||
|
||||
engine_->SetEqualizerParameters(equalizer_->preamp_value(), equalizer_->gain_values());
|
||||
engine_->SetStereoBalance(equalizer_->stereo_balance());
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
void Player::SetAnalyzer(AnalyzerContainer *analyzer) {
|
||||
|
||||
analyzer_ = analyzer;
|
||||
|
||||
}
|
||||
void Player::SetEqualizer(Equalizer *equalizer) {
|
||||
equalizer_ = equalizer;
|
||||
}
|
||||
|
||||
void Player::ReloadSettings() {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
QSettings s;
|
||||
|
||||
s.beginGroup(PlaylistSettingsPage::kSettingsGroup);
|
||||
menu_previousmode_ = PreviousBehaviour(s.value("menu_previousmode", PreviousBehaviour_DontRestart).toInt());
|
||||
s.endGroup();
|
||||
|
||||
s.beginGroup(BehaviourSettingsPage::kSettingsGroup);
|
||||
seek_step_sec_ = s.value("seek_step_sec", 10).toInt();
|
||||
s.endGroup();
|
||||
|
||||
engine_->ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
// Might've been an async load, so check we're still on the same item
|
||||
shared_ptr<PlaylistItem> item = app_->playlist_manager()->active()->current_item();
|
||||
if (!item) {
|
||||
loading_async_ = QUrl();
|
||||
return;
|
||||
}
|
||||
|
||||
if (item->Url() != result.original_url_) return;
|
||||
|
||||
switch (result.type_) {
|
||||
case UrlHandler::LoadResult::NoMoreTracks:
|
||||
qLog(Debug) << "URL handler for" << result.original_url_ << "said no more tracks";
|
||||
|
||||
loading_async_ = QUrl();
|
||||
NextItem(stream_change_type_);
|
||||
break;
|
||||
|
||||
case UrlHandler::LoadResult::TrackAvailable: {
|
||||
|
||||
qLog(Debug) << "URL handler for" << result.original_url_ << "returned" << result.media_url_;
|
||||
|
||||
// If there was no length info in song's metadata, use the one provided by
|
||||
// URL handler, if there is one
|
||||
if (item->Metadata().length_nanosec() <= 0 && result.length_nanosec_ != -1) {
|
||||
Song song = item->Metadata();
|
||||
song.set_length_nanosec(result.length_nanosec_);
|
||||
item->SetTemporaryMetadata(song);
|
||||
app_->playlist_manager()->active()->InformOfCurrentSongChange();
|
||||
}
|
||||
engine_->Play(result.media_url_, stream_change_type_, item->Metadata().has_cue(), item->Metadata().beginning_nanosec(), item->Metadata().end_nanosec());
|
||||
|
||||
current_item_ = item;
|
||||
loading_async_ = QUrl();
|
||||
break;
|
||||
}
|
||||
|
||||
case UrlHandler::LoadResult::WillLoadAsynchronously:
|
||||
qLog(Debug) << "URL handler for" << result.original_url_ << "is loading asynchronously";
|
||||
|
||||
// We'll get called again later with either NoMoreTracks or TrackAvailable
|
||||
loading_async_ = result.original_url_;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Player::Next() { NextInternal(Engine::Manual); }
|
||||
|
||||
void Player::NextInternal(Engine::TrackChangeFlags change) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
if (HandleStopAfter()) return;
|
||||
|
||||
if (app_->playlist_manager()->active()->current_item()) {
|
||||
const QUrl url = app_->playlist_manager()->active()->current_item()->Url();
|
||||
|
||||
if (url_handlers_.contains(url.scheme())) {
|
||||
// The next track is already being loaded
|
||||
if (url == loading_async_) return;
|
||||
|
||||
stream_change_type_ = change;
|
||||
HandleLoadResult(url_handlers_[url.scheme()]->LoadNext(url));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NextItem(change);
|
||||
}
|
||||
|
||||
void Player::NextItem(Engine::TrackChangeFlags change) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
Playlist *active_playlist = app_->playlist_manager()->active();
|
||||
|
||||
// If we received too many errors in auto change, with repeat enabled, we stop
|
||||
if (change == Engine::Auto) {
|
||||
const PlaylistSequence::RepeatMode repeat_mode = active_playlist->sequence()->repeat_mode();
|
||||
if (repeat_mode != PlaylistSequence::Repeat_Off) {
|
||||
if ((repeat_mode == PlaylistSequence::Repeat_Track && nb_errors_received_ >= 3) || (nb_errors_received_ >= app_->playlist_manager()->active()->proxy()->rowCount())) {
|
||||
// We received too many "Error" state changes: probably looping over a
|
||||
// playlist which contains only unavailable elements: stop now.
|
||||
nb_errors_received_ = 0;
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Manual track changes override "Repeat track"
|
||||
const bool ignore_repeat_track = change & Engine::Manual;
|
||||
|
||||
int i = active_playlist->next_row(ignore_repeat_track);
|
||||
if (i == -1) {
|
||||
app_->playlist_manager()->active()->set_current_row(i);
|
||||
emit PlaylistFinished();
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
PlayAt(i, change, false);
|
||||
|
||||
}
|
||||
|
||||
bool Player::HandleStopAfter() {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
if (app_->playlist_manager()->active()->stop_after_current()) {
|
||||
// Find what the next track would've been, and mark that one as current
|
||||
// so it plays next time the user presses Play.
|
||||
const int next_row = app_->playlist_manager()->active()->next_row();
|
||||
if (next_row != -1) {
|
||||
app_->playlist_manager()->active()->set_current_row(next_row, true);
|
||||
}
|
||||
|
||||
app_->playlist_manager()->active()->StopAfter(-1);
|
||||
|
||||
Stop(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Player::TrackEnded() {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
if (HandleStopAfter()) return;
|
||||
|
||||
if (current_item_ && current_item_->IsLocalCollectionItem() && current_item_->Metadata().id() != -1) {
|
||||
app_->playlist_manager()->collection_backend()->IncrementPlayCountAsync( current_item_->Metadata().id());
|
||||
}
|
||||
|
||||
NextInternal(Engine::Auto);
|
||||
}
|
||||
|
||||
void Player::PlayPause() {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
switch (engine_->state()) {
|
||||
case Engine::Paused:
|
||||
engine_->Unpause();
|
||||
break;
|
||||
|
||||
case Engine::Playing: {
|
||||
if (current_item_->options() & PlaylistItem::PauseDisabled) {
|
||||
Stop();
|
||||
}
|
||||
else {
|
||||
engine_->Pause();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Engine::Empty:
|
||||
case Engine::Error:
|
||||
case Engine::Idle: {
|
||||
app_->playlist_manager()->SetActivePlaylist(app_->playlist_manager()->current_id());
|
||||
if (app_->playlist_manager()->active()->rowCount() == 0) break;
|
||||
|
||||
int i = app_->playlist_manager()->active()->current_row();
|
||||
if (i == -1) i = app_->playlist_manager()->active()->last_played_row();
|
||||
if (i == -1) i = 0;
|
||||
|
||||
PlayAt(i, Engine::First, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Player::RestartOrPrevious() {
|
||||
|
||||
if (engine_->position_nanosec() < 8 * kNsecPerSec) return Previous();
|
||||
|
||||
SeekTo(0);
|
||||
|
||||
}
|
||||
|
||||
void Player::Stop(bool stop_after) {
|
||||
engine_->Stop(stop_after);
|
||||
app_->playlist_manager()->active()->set_current_row(-1);
|
||||
current_item_.reset();
|
||||
}
|
||||
|
||||
void Player::StopAfterCurrent() {
|
||||
app_->playlist_manager()->active()->StopAfter(app_->playlist_manager()->active()->current_row());
|
||||
}
|
||||
|
||||
bool Player::PreviousWouldRestartTrack() const {
|
||||
// Check if it has been over two seconds since previous button was pressed
|
||||
return menu_previousmode_ == PreviousBehaviour_Restart && last_pressed_previous_.isValid() && last_pressed_previous_.secsTo(QDateTime::currentDateTime()) >= 2;
|
||||
}
|
||||
|
||||
void Player::Previous() { PreviousItem(Engine::Manual); }
|
||||
|
||||
void Player::PreviousItem(Engine::TrackChangeFlags change) {
|
||||
|
||||
const bool ignore_repeat_track = change & Engine::Manual;
|
||||
|
||||
if (menu_previousmode_ == PreviousBehaviour_Restart) {
|
||||
// Check if it has been over two seconds since previous button was pressed
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
if (last_pressed_previous_.isValid() && last_pressed_previous_.secsTo(now) >= 2) {
|
||||
last_pressed_previous_ = now;
|
||||
PlayAt(app_->playlist_manager()->active()->current_row(), change, false);
|
||||
return;
|
||||
}
|
||||
last_pressed_previous_ = now;
|
||||
}
|
||||
|
||||
int i = app_->playlist_manager()->active()->previous_row(ignore_repeat_track);
|
||||
app_->playlist_manager()->active()->set_current_row(i);
|
||||
if (i == -1) {
|
||||
Stop();
|
||||
PlayAt(i, change, true);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayAt(i, change, false);
|
||||
|
||||
}
|
||||
|
||||
void Player::EngineStateChanged(Engine::State state) {
|
||||
|
||||
if (Engine::Error == state) {
|
||||
nb_errors_received_++;
|
||||
}
|
||||
else {
|
||||
nb_errors_received_ = 0;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case Engine::Paused:
|
||||
emit Paused();
|
||||
break;
|
||||
case Engine::Playing:
|
||||
emit Playing();
|
||||
break;
|
||||
case Engine::Error:
|
||||
case Engine::Empty:
|
||||
case Engine::Idle:
|
||||
emit Stopped();
|
||||
break;
|
||||
}
|
||||
last_state_ = state;
|
||||
|
||||
}
|
||||
|
||||
void Player::SetVolume(int value) {
|
||||
|
||||
int old_volume = engine_->volume();
|
||||
|
||||
int volume = qBound(0, value, 100);
|
||||
settings_.setValue("volume", volume);
|
||||
engine_->SetVolume(volume);
|
||||
|
||||
if (volume != old_volume) {
|
||||
emit VolumeChanged(volume);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int Player::GetVolume() const { return engine_->volume(); }
|
||||
|
||||
void Player::PlayAt(int index, Engine::TrackChangeFlags change, bool reshuffle) {
|
||||
|
||||
if (change == Engine::Manual && engine_->position_nanosec() != engine_->length_nanosec()) {
|
||||
emit TrackSkipped(current_item_);
|
||||
const QUrl& url = current_item_->Url();
|
||||
if (url_handlers_.contains(url.scheme())) {
|
||||
url_handlers_[url.scheme()]->TrackSkipped();
|
||||
}
|
||||
}
|
||||
|
||||
if (current_item_ && app_->playlist_manager()->active()->has_item_at(index) && current_item_->Metadata().IsOnSameAlbum(app_->playlist_manager()->active()->item_at(index)->Metadata())) {
|
||||
change |= Engine::SameAlbum;
|
||||
}
|
||||
|
||||
if (reshuffle) app_->playlist_manager()->active()->ReshuffleIndices();
|
||||
app_->playlist_manager()->active()->set_current_row(index);
|
||||
|
||||
if (app_->playlist_manager()->active()->current_row() == -1) {
|
||||
// Maybe index didn't exist in the playlist.
|
||||
return;
|
||||
}
|
||||
|
||||
current_item_ = app_->playlist_manager()->active()->current_item();
|
||||
const QUrl url = current_item_->Url();
|
||||
|
||||
if (url_handlers_.contains(url.scheme())) {
|
||||
// It's already loading
|
||||
if (url == loading_async_) return;
|
||||
|
||||
stream_change_type_ = change;
|
||||
HandleLoadResult(url_handlers_[url.scheme()]->StartLoading(url));
|
||||
}
|
||||
else {
|
||||
loading_async_ = QUrl();
|
||||
engine_->Play(current_item_->Url(), change, current_item_->Metadata().has_cue(), current_item_->Metadata().beginning_nanosec(), current_item_->Metadata().end_nanosec());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Player::CurrentMetadataChanged(const Song &metadata) {
|
||||
// those things might have changed (especially when a previously invalid
|
||||
// song was reloaded) so we push the latest version into Engine
|
||||
engine_->RefreshMarkers(metadata.beginning_nanosec(), metadata.end_nanosec());
|
||||
|
||||
}
|
||||
|
||||
void Player::SeekTo(int seconds) {
|
||||
|
||||
const qint64 length_nanosec = engine_->length_nanosec();
|
||||
|
||||
// If the length is 0 then either there is no song playing, or the song isn't
|
||||
// seekable.
|
||||
if (length_nanosec <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const qint64 nanosec = qBound(0ll, qint64(seconds) * kNsecPerSec, length_nanosec);
|
||||
engine_->Seek(nanosec);
|
||||
|
||||
emit Seeked(nanosec / 1000);
|
||||
|
||||
}
|
||||
|
||||
void Player::SeekForward() {
|
||||
SeekTo(engine()->position_nanosec() / kNsecPerSec + seek_step_sec_);
|
||||
//SeekTo(engine()->position_nanosec() / kNsecPerSec + 10);
|
||||
}
|
||||
|
||||
void Player::SeekBackward() {
|
||||
//SeekTo(engine()->position_nanosec() / kNsecPerSec - 10);
|
||||
SeekTo(engine()->position_nanosec() / kNsecPerSec - seek_step_sec_);
|
||||
}
|
||||
|
||||
void Player::EngineMetadataReceived(const Engine::SimpleMetaBundle &bundle) {
|
||||
|
||||
PlaylistItemPtr item = app_->playlist_manager()->active()->current_item();
|
||||
if (!item) return;
|
||||
|
||||
Engine::SimpleMetaBundle bundle_copy = bundle;
|
||||
|
||||
// Maybe the metadata is from icycast and has "Artist - Title" shoved
|
||||
// together in the title field.
|
||||
const int dash_pos = bundle_copy.title.indexOf('-');
|
||||
if (dash_pos != -1 && bundle_copy.artist.isEmpty()) {
|
||||
// Split on " - " if it exists, otherwise split on "-".
|
||||
const int space_dash_pos = bundle_copy.title.indexOf(" - ");
|
||||
if (space_dash_pos != -1) {
|
||||
bundle_copy.artist = bundle_copy.title.left(space_dash_pos).trimmed();
|
||||
bundle_copy.title = bundle_copy.title.mid(space_dash_pos + 3).trimmed();
|
||||
} else {
|
||||
bundle_copy.artist = bundle_copy.title.left(dash_pos).trimmed();
|
||||
bundle_copy.title = bundle_copy.title.mid(dash_pos + 1).trimmed();
|
||||
}
|
||||
}
|
||||
|
||||
Song song = item->Metadata();
|
||||
song.MergeFromSimpleMetaBundle(bundle_copy);
|
||||
|
||||
// Ignore useless metadata
|
||||
if (song.title().isEmpty() && song.artist().isEmpty()) return;
|
||||
|
||||
app_->playlist_manager()->active()->SetStreamMetadata(item->Url(), song);
|
||||
|
||||
}
|
||||
|
||||
PlaylistItemPtr Player::GetItemAt(int pos) const {
|
||||
|
||||
if (pos < 0 || pos >= app_->playlist_manager()->active()->rowCount())
|
||||
return PlaylistItemPtr();
|
||||
return app_->playlist_manager()->active()->item_at(pos);
|
||||
|
||||
}
|
||||
|
||||
void Player::Mute() {
|
||||
|
||||
const int current_volume = engine_->volume();
|
||||
|
||||
if (current_volume == 0) {
|
||||
SetVolume(volume_before_mute_);
|
||||
}
|
||||
else {
|
||||
volume_before_mute_ = current_volume;
|
||||
SetVolume(0);
|
||||
}
|
||||
}
|
||||
|
||||
void Player::Pause() { engine_->Pause(); }
|
||||
|
||||
void Player::Play() {
|
||||
switch (GetState()) {
|
||||
case Engine::Playing:
|
||||
SeekTo(0);
|
||||
break;
|
||||
case Engine::Paused:
|
||||
engine_->Unpause();
|
||||
break;
|
||||
default:
|
||||
PlayPause();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Player::ShowOSD() {
|
||||
if (current_item_) emit ForceShowOSD(current_item_->Metadata(), false);
|
||||
}
|
||||
|
||||
void Player::TogglePrettyOSD() {
|
||||
if (current_item_) emit ForceShowOSD(current_item_->Metadata(), true);
|
||||
}
|
||||
|
||||
void Player::TrackAboutToEnd() {
|
||||
|
||||
// If the current track was from a URL handler then it might have special
|
||||
// behaviour to queue up a subsequent track. We don't want to preload (and
|
||||
// scrobble) the next item in the playlist if it's just going to be stopped
|
||||
// again immediately after.
|
||||
if (app_->playlist_manager()->active()->current_item()) {
|
||||
const QUrl url = app_->playlist_manager()->active()->current_item()->Url();
|
||||
if (url_handlers_.contains(url.scheme())) {
|
||||
url_handlers_[url.scheme()]->TrackAboutToEnd();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const bool has_next_row = app_->playlist_manager()->active()->next_row() != -1;
|
||||
PlaylistItemPtr next_item;
|
||||
|
||||
if (has_next_row) {
|
||||
next_item = app_->playlist_manager()->active()->item_at(app_->playlist_manager()->active()->next_row());
|
||||
}
|
||||
|
||||
if (engine_->is_autocrossfade_enabled()) {
|
||||
// Crossfade is on, so just start playing the next track. The current one
|
||||
// will fade out, and the new one will fade in
|
||||
|
||||
// But, if there's no next track and we don't want to fade out, then do
|
||||
// nothing and just let the track finish to completion.
|
||||
if (!engine_->is_fadeout_enabled() && !has_next_row) return;
|
||||
|
||||
// If the next track is on the same album (or same cue file), and the
|
||||
// user doesn't want to crossfade between tracks on the same album, then
|
||||
// don't do this automatic crossfading.
|
||||
if (engine_->crossfade_same_album() || !has_next_row || !next_item || !current_item_->Metadata().IsOnSameAlbum(next_item->Metadata())) {
|
||||
TrackEnded();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Crossfade is off, so start preloading the next track so we don't get a
|
||||
// gap between songs.
|
||||
if (!has_next_row || !next_item) return;
|
||||
|
||||
QUrl url = next_item->Url();
|
||||
|
||||
// Get the actual track URL rather than the stream URL.
|
||||
if (url_handlers_.contains(url.scheme())) {
|
||||
UrlHandler::LoadResult result = url_handlers_[url.scheme()]->LoadNext(url);
|
||||
switch (result.type_) {
|
||||
case UrlHandler::LoadResult::NoMoreTracks:
|
||||
return;
|
||||
|
||||
case UrlHandler::LoadResult::WillLoadAsynchronously:
|
||||
loading_async_ = url;
|
||||
return;
|
||||
|
||||
case UrlHandler::LoadResult::TrackAvailable:
|
||||
url = result.media_url_;
|
||||
break;
|
||||
}
|
||||
}
|
||||
engine_->StartPreloading(url, next_item->Metadata().has_cue(), next_item->Metadata().beginning_nanosec(), next_item->Metadata().end_nanosec());
|
||||
|
||||
}
|
||||
|
||||
void Player::IntroPointReached() { NextInternal(Engine::Intro); }
|
||||
|
||||
void Player::ValidSongRequested(const QUrl &url) {
|
||||
emit SongChangeRequestProcessed(url, true);
|
||||
}
|
||||
|
||||
void Player::InvalidSongRequested(const QUrl &url) {
|
||||
|
||||
// first send the notification to others...
|
||||
emit SongChangeRequestProcessed(url, false);
|
||||
// ... and now when our listeners have completed their processing of the
|
||||
// current item we can change the current item by skipping to the next song
|
||||
//NextItem(Engine::Auto);
|
||||
|
||||
}
|
||||
|
||||
void Player::RegisterUrlHandler(UrlHandler *handler) {
|
||||
|
||||
const QString scheme = handler->scheme();
|
||||
|
||||
if (url_handlers_.contains(scheme)) {
|
||||
qLog(Warning) << "Tried to register a URL handler for" << scheme << "but one was already registered";
|
||||
return;
|
||||
}
|
||||
|
||||
qLog(Info) << "Registered URL handler for" << scheme;
|
||||
url_handlers_.insert(scheme, handler);
|
||||
connect(handler, SIGNAL(destroyed(QObject*)), SLOT(UrlHandlerDestroyed(QObject*)));
|
||||
connect(handler, SIGNAL(AsyncLoadComplete(UrlHandler::LoadResult)), SLOT(HandleLoadResult(UrlHandler::LoadResult)));
|
||||
|
||||
}
|
||||
|
||||
void Player::UnregisterUrlHandler(UrlHandler *handler) {
|
||||
|
||||
const QString scheme = url_handlers_.key(handler);
|
||||
if (scheme.isEmpty()) {
|
||||
qLog(Warning) << "Tried to unregister a URL handler for" << handler->scheme() << "that wasn't registered";
|
||||
return;
|
||||
}
|
||||
|
||||
qLog(Info) << "Unregistered URL handler for" << scheme;
|
||||
url_handlers_.remove(scheme);
|
||||
disconnect(handler, SIGNAL(destroyed(QObject*)), this, SLOT(UrlHandlerDestroyed(QObject*)));
|
||||
disconnect(handler, SIGNAL(AsyncLoadComplete(UrlHandler::LoadResult)), this, SLOT(HandleLoadResult(UrlHandler::LoadResult)));
|
||||
|
||||
}
|
||||
|
||||
const UrlHandler *Player::HandlerForUrl(const QUrl &url) const {
|
||||
|
||||
QMap<QString, UrlHandler*>::const_iterator it = url_handlers_.constFind(url.scheme());
|
||||
if (it == url_handlers_.constEnd()) {
|
||||
return nullptr;
|
||||
}
|
||||
return *it;
|
||||
|
||||
}
|
||||
|
||||
void Player::UrlHandlerDestroyed(QObject *object) {
|
||||
|
||||
UrlHandler *handler = static_cast<UrlHandler*>(object);
|
||||
const QString scheme = url_handlers_.key(handler);
|
||||
if (!scheme.isEmpty()) {
|
||||
url_handlers_.remove(scheme);
|
||||
}
|
||||
|
||||
}
|
||||
221
src/core/player.h
Normal file
221
src/core/player.h
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PLAYER_H
|
||||
#define PLAYER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QSettings>
|
||||
|
||||
#include "config.h"
|
||||
#include "core/song.h"
|
||||
#include "core/urlhandler.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "engine/engine_fwd.h"
|
||||
#include "engine/enginetype.h"
|
||||
#include "engine/enginedevice.h"
|
||||
#include "playlist/playlistitem.h"
|
||||
#include "analyzer/analyzercontainer.h"
|
||||
#include "equalizer/equalizer.h"
|
||||
|
||||
class Application;
|
||||
class AnalyzerContainer;
|
||||
class Equalizer;
|
||||
|
||||
class PlayerInterface : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PlayerInterface(QObject *parent = nullptr) : QObject(parent) {}
|
||||
|
||||
virtual EngineBase *engine() const = 0;
|
||||
virtual Engine::State GetState() const = 0;
|
||||
virtual int GetVolume() const = 0;
|
||||
|
||||
virtual PlaylistItemPtr GetCurrentItem() const = 0;
|
||||
virtual PlaylistItemPtr GetItemAt(int pos) const = 0;
|
||||
|
||||
virtual void RegisterUrlHandler(UrlHandler *handler) = 0;
|
||||
virtual void UnregisterUrlHandler(UrlHandler *handler) = 0;
|
||||
|
||||
public slots:
|
||||
virtual void ReloadSettings() = 0;
|
||||
|
||||
// Manual track change to the specified track
|
||||
virtual void PlayAt(int i, Engine::TrackChangeFlags change, bool reshuffle) = 0;
|
||||
|
||||
// If there's currently a song playing, pause it, otherwise play the track
|
||||
// that was playing last, or the first one on the playlist
|
||||
virtual void PlayPause() = 0;
|
||||
virtual void RestartOrPrevious() = 0;
|
||||
|
||||
// Skips this track. Might load more of the current radio station.
|
||||
virtual void Next() = 0;
|
||||
|
||||
virtual void Previous() = 0;
|
||||
virtual void SetVolume(int value) = 0;
|
||||
virtual void VolumeUp() = 0;
|
||||
virtual void VolumeDown() = 0;
|
||||
virtual void SeekTo(int seconds) = 0;
|
||||
// Moves the position of the currently playing song five seconds forward.
|
||||
virtual void SeekForward() = 0;
|
||||
// Moves the position of the currently playing song five seconds backwards.
|
||||
virtual void SeekBackward() = 0;
|
||||
|
||||
virtual void CurrentMetadataChanged(const Song &metadata) = 0;
|
||||
|
||||
virtual void Mute() = 0;
|
||||
virtual void Pause() = 0;
|
||||
virtual void Stop(bool stop_after = false) = 0;
|
||||
virtual void Play() = 0;
|
||||
virtual void ShowOSD() = 0;
|
||||
|
||||
signals:
|
||||
void Playing();
|
||||
void Paused();
|
||||
void Stopped();
|
||||
void PlaylistFinished();
|
||||
void VolumeChanged(int volume);
|
||||
void Error(const QString &message);
|
||||
void TrackSkipped(PlaylistItemPtr old_track);
|
||||
// Emitted when there's a manual change to the current's track position.
|
||||
void Seeked(qlonglong microseconds);
|
||||
|
||||
// Emitted when Player has processed a request to play another song. This contains
|
||||
// the URL of the song and a flag saying whether it was able to play the song.
|
||||
void SongChangeRequestProcessed(const QUrl &url, bool valid);
|
||||
|
||||
// The toggle parameter is true when user requests to toggle visibility for Pretty OSD
|
||||
void ForceShowOSD(Song, bool toogle);
|
||||
};
|
||||
|
||||
class Player : public PlayerInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Player(Application *app, QObject *parent);
|
||||
~Player();
|
||||
|
||||
static const char *kSettingsGroup;
|
||||
|
||||
// Don't change the values: they are saved in preferences
|
||||
enum PreviousBehaviour {
|
||||
PreviousBehaviour_DontRestart = 1,
|
||||
PreviousBehaviour_Restart = 2
|
||||
};
|
||||
|
||||
EngineBase *CreateEngine(Engine::EngineType enginetype);
|
||||
void Init();
|
||||
|
||||
EngineBase *engine() const { return engine_.get(); }
|
||||
Engine::State GetState() const { return last_state_; }
|
||||
int GetVolume() const;
|
||||
|
||||
PlaylistItemPtr GetCurrentItem() const { return current_item_; }
|
||||
PlaylistItemPtr GetItemAt(int pos) const;
|
||||
|
||||
void RegisterUrlHandler(UrlHandler *handler);
|
||||
void UnregisterUrlHandler(UrlHandler *handler);
|
||||
|
||||
const UrlHandler *HandlerForUrl(const QUrl &url) const;
|
||||
|
||||
bool PreviousWouldRestartTrack() const;
|
||||
|
||||
void SetAnalyzer(AnalyzerContainer *analyzer);
|
||||
void SetEqualizer(Equalizer *equalizer);
|
||||
|
||||
public slots:
|
||||
void ReloadSettings();
|
||||
|
||||
void PlayAt(int i, Engine::TrackChangeFlags change, bool reshuffle);
|
||||
void PlayPause();
|
||||
void RestartOrPrevious();
|
||||
void Next();
|
||||
void Previous();
|
||||
void SetVolume(int value);
|
||||
void VolumeUp() { SetVolume(GetVolume() + 5); }
|
||||
void VolumeDown() { SetVolume(GetVolume() - 5); }
|
||||
void SeekTo(int seconds);
|
||||
void SeekForward();
|
||||
void SeekBackward();
|
||||
|
||||
void CurrentMetadataChanged(const Song &metadata);
|
||||
|
||||
void Mute();
|
||||
void Pause();
|
||||
void Stop(bool stop_after = false);
|
||||
void StopAfterCurrent();
|
||||
void IntroPointReached();
|
||||
void Play();
|
||||
void ShowOSD();
|
||||
void TogglePrettyOSD();
|
||||
|
||||
private slots:
|
||||
void EngineStateChanged(Engine::State);
|
||||
void EngineMetadataReceived(const Engine::SimpleMetaBundle &bundle);
|
||||
void TrackAboutToEnd();
|
||||
void TrackEnded();
|
||||
// Play the next item on the playlist - disregarding radio stations like last.fm that might have more tracks.
|
||||
void NextItem(Engine::TrackChangeFlags change);
|
||||
void PreviousItem(Engine::TrackChangeFlags change);
|
||||
|
||||
void NextInternal(Engine::TrackChangeFlags);
|
||||
|
||||
void ValidSongRequested(const QUrl&);
|
||||
void InvalidSongRequested(const QUrl&);
|
||||
|
||||
void UrlHandlerDestroyed(QObject *object);
|
||||
void HandleLoadResult(const UrlHandler::LoadResult &result);
|
||||
|
||||
private:
|
||||
// Returns true if we were supposed to stop after this track.
|
||||
bool HandleStopAfter();
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
QSettings settings_;
|
||||
AnalyzerContainer *analyzer_;
|
||||
Equalizer *equalizer_;
|
||||
|
||||
PlaylistItemPtr current_item_;
|
||||
|
||||
std::unique_ptr<EngineBase> engine_;
|
||||
Engine::TrackChangeFlags stream_change_type_;
|
||||
Engine::State last_state_;
|
||||
int nb_errors_received_;
|
||||
|
||||
QMap<QString, UrlHandler*> url_handlers_;
|
||||
|
||||
QUrl loading_async_;
|
||||
|
||||
int volume_before_mute_;
|
||||
|
||||
QDateTime last_pressed_previous_;
|
||||
PreviousBehaviour menu_previousmode_;
|
||||
int seek_step_sec_;
|
||||
|
||||
};
|
||||
|
||||
#endif // PLAYER_H
|
||||
31
src/core/qhash_qurl.h
Normal file
31
src/core/qhash_qurl.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QHASH_QURL_H
|
||||
#define QHASH_QURL_H
|
||||
|
||||
#include <QUrl>
|
||||
|
||||
#if QT_VERSION < 0x040700
|
||||
inline uint qHash(const QUrl& url) { return qHash(url.toEncoded()); }
|
||||
#endif
|
||||
|
||||
#endif // QHASH_QURL_H
|
||||
|
||||
29
src/core/qt_blurimage.h
Normal file
29
src/core/qt_blurimage.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QT_BLURIMAGE_H
|
||||
#define QT_BLURIMAGE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
// Exported by QtGui
|
||||
void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0);
|
||||
|
||||
#endif // QT_BLURIMAGE_H
|
||||
43
src/core/qtfslistener.cpp
Normal file
43
src/core/qtfslistener.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "qtfslistener.h"
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
QtFSListener::QtFSListener(QObject *parent) : FileSystemWatcherInterface(parent), watcher_(this) {
|
||||
|
||||
connect(&watcher_, SIGNAL(directoryChanged(const QString&)), SIGNAL(PathChanged(const QString&)));
|
||||
|
||||
}
|
||||
|
||||
void QtFSListener::AddPath(const QString &path) { watcher_.addPath(path); }
|
||||
|
||||
void QtFSListener::RemovePath(const QString &path) {
|
||||
watcher_.removePath(path);
|
||||
}
|
||||
|
||||
void QtFSListener::Clear() {
|
||||
watcher_.removePaths(watcher_.directories());
|
||||
watcher_.removePaths(watcher_.files());
|
||||
}
|
||||
|
||||
43
src/core/qtfslistener.h
Normal file
43
src/core/qtfslistener.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QTFSLISTENER_H
|
||||
#define QTFSLISTENER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "filesystemwatcherinterface.h"
|
||||
|
||||
#include <QFileSystemWatcher>
|
||||
|
||||
class QtFSListener : public FileSystemWatcherInterface {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit QtFSListener(QObject *parent);
|
||||
virtual void AddPath(const QString &path);
|
||||
virtual void RemovePath(const QString &path);
|
||||
virtual void Clear();
|
||||
|
||||
private:
|
||||
QFileSystemWatcher watcher_;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
263
src/core/qtsystemtrayicon.cpp
Normal file
263
src/core/qtsystemtrayicon.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "iconloader.h"
|
||||
#include "qtsystemtrayicon.h"
|
||||
|
||||
#include "core/song.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QTextDocument>
|
||||
#include <QStringBuilder>
|
||||
#include <QMenu>
|
||||
#include <QFile>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QWheelEvent>
|
||||
|
||||
QtSystemTrayIcon::QtSystemTrayIcon(QObject *parent)
|
||||
: SystemTrayIcon(parent),
|
||||
tray_(new QSystemTrayIcon(this)),
|
||||
menu_(new QMenu),
|
||||
action_play_pause_(nullptr),
|
||||
action_stop_(nullptr),
|
||||
action_stop_after_this_track_(nullptr),
|
||||
action_mute_(nullptr)
|
||||
{
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
QIcon theme_icon = IconLoader::Load("strawberry-panel");
|
||||
QIcon theme_icon_grey = IconLoader::Load("strawberry-panel-grey");
|
||||
|
||||
if (theme_icon.isNull() || theme_icon_grey.isNull()) {
|
||||
// Load the default icon
|
||||
QIcon icon(":/icons/64x64/strawberry-panel.png");
|
||||
normal_icon_ = icon.pixmap(48, QIcon::Normal);
|
||||
grey_icon_ = icon.pixmap(48, QIcon::Disabled);
|
||||
}
|
||||
else {
|
||||
// Use the icons from the theme
|
||||
normal_icon_ = theme_icon.pixmap(48);
|
||||
grey_icon_ = theme_icon_grey.pixmap(48);
|
||||
}
|
||||
|
||||
tray_->setIcon(normal_icon_);
|
||||
tray_->installEventFilter(this);
|
||||
ClearNowPlaying();
|
||||
|
||||
QFile pattern_file(":/misc/playing_tooltip.txt");
|
||||
pattern_file.open(QIODevice::ReadOnly);
|
||||
pattern_ = QString::fromLatin1(pattern_file.readAll());
|
||||
|
||||
connect(tray_, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(Clicked(QSystemTrayIcon::ActivationReason)));
|
||||
|
||||
}
|
||||
|
||||
QtSystemTrayIcon::~QtSystemTrayIcon() {
|
||||
delete menu_;
|
||||
}
|
||||
|
||||
bool QtSystemTrayIcon::eventFilter(QObject *object, QEvent *event) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
if (QObject::eventFilter(object, event)) return true;
|
||||
|
||||
if (object != tray_) return false;
|
||||
|
||||
if (event->type() == QEvent::Wheel) {
|
||||
QWheelEvent *e = static_cast<QWheelEvent*>(event);
|
||||
if (e->modifiers() == Qt::ShiftModifier) {
|
||||
if (e->delta() > 0) {
|
||||
emit SeekForward();
|
||||
} else {
|
||||
emit SeekBackward();
|
||||
}
|
||||
}
|
||||
else if (e->modifiers() == Qt::ControlModifier) {
|
||||
if (e->delta() < 0) {
|
||||
emit NextTrack();
|
||||
}
|
||||
else {
|
||||
emit PreviousTrack();
|
||||
}
|
||||
}
|
||||
else {
|
||||
emit ChangeVolume(e->delta());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
void QtSystemTrayIcon::SetupMenu(QAction *previous, QAction *play, QAction *stop, QAction *stop_after, QAction *next, QAction *mute, QAction *quit) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
// Creating new actions and connecting them to old ones. This allows us to
|
||||
// use old actions without displaying shortcuts that can not be used when
|
||||
// Strawberry's window is hidden
|
||||
menu_->addAction(previous->icon(), previous->text(), previous, SLOT(trigger()));
|
||||
action_play_pause_ = menu_->addAction(play->icon(), play->text(), play, SLOT(trigger()));
|
||||
action_stop_ = menu_->addAction(stop->icon(), stop->text(), stop, SLOT(trigger()));
|
||||
action_stop_after_this_track_ = menu_->addAction(stop_after->icon(), stop_after->text(), stop_after, SLOT(trigger()));
|
||||
menu_->addAction(next->icon(), next->text(), next, SLOT(trigger()));
|
||||
|
||||
menu_->addSeparator();
|
||||
action_mute_ = menu_->addAction(mute->icon(), mute->text(), mute, SLOT(trigger()));
|
||||
action_mute_->setCheckable(true);
|
||||
action_mute_->setChecked(mute->isChecked());
|
||||
|
||||
menu_->addSeparator();
|
||||
menu_->addSeparator();
|
||||
menu_->addAction(quit->icon(), quit->text(), quit, SLOT(trigger()));
|
||||
|
||||
tray_->setContextMenu(menu_);
|
||||
|
||||
}
|
||||
|
||||
void QtSystemTrayIcon::Clicked(QSystemTrayIcon::ActivationReason reason) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
switch (reason) {
|
||||
case QSystemTrayIcon::DoubleClick:
|
||||
case QSystemTrayIcon::Trigger:
|
||||
emit ShowHide();
|
||||
break;
|
||||
|
||||
case QSystemTrayIcon::MiddleClick:
|
||||
emit PlayPause();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void QtSystemTrayIcon::ShowPopup(const QString &summary, const QString &message, int timeout) {
|
||||
tray_->showMessage(summary, message, QSystemTrayIcon::NoIcon, timeout);
|
||||
}
|
||||
|
||||
void QtSystemTrayIcon::UpdateIcon() {
|
||||
tray_->setIcon(CreateIcon(normal_icon_, grey_icon_));
|
||||
}
|
||||
|
||||
void QtSystemTrayIcon::SetPaused() {
|
||||
|
||||
SystemTrayIcon::SetPaused();
|
||||
|
||||
action_stop_->setEnabled(true);
|
||||
action_stop_after_this_track_->setEnabled(true);
|
||||
action_play_pause_->setIcon(IconLoader::Load("media-play"));
|
||||
action_play_pause_->setText(tr("Play"));
|
||||
|
||||
action_play_pause_->setEnabled(true);
|
||||
|
||||
}
|
||||
|
||||
void QtSystemTrayIcon::SetPlaying(bool enable_play_pause) {
|
||||
|
||||
SystemTrayIcon::SetPlaying();
|
||||
|
||||
action_stop_->setEnabled(true);
|
||||
action_stop_after_this_track_->setEnabled(true);
|
||||
action_play_pause_->setIcon(IconLoader::Load("media-pause"));
|
||||
action_play_pause_->setText(tr("Pause"));
|
||||
action_play_pause_->setEnabled(enable_play_pause);
|
||||
|
||||
}
|
||||
|
||||
void QtSystemTrayIcon::SetStopped() {
|
||||
|
||||
SystemTrayIcon::SetStopped();
|
||||
|
||||
action_stop_->setEnabled(false);
|
||||
action_stop_after_this_track_->setEnabled(false);
|
||||
action_play_pause_->setIcon(IconLoader::Load("media-play"));
|
||||
action_play_pause_->setText(tr("Play"));
|
||||
|
||||
action_play_pause_->setEnabled(true);
|
||||
|
||||
}
|
||||
|
||||
void QtSystemTrayIcon::LastFMButtonVisibilityChanged(bool value) {
|
||||
|
||||
}
|
||||
|
||||
void QtSystemTrayIcon::MuteButtonStateChanged(bool value) {
|
||||
if (action_mute_) action_mute_->setChecked(value);
|
||||
}
|
||||
|
||||
bool QtSystemTrayIcon::IsVisible() const {
|
||||
return tray_->isVisible();
|
||||
}
|
||||
|
||||
void QtSystemTrayIcon::SetVisible(bool visible) {
|
||||
tray_->setVisible(visible);
|
||||
}
|
||||
|
||||
void QtSystemTrayIcon::SetNowPlaying(const Song &song, const QString &image_path) {
|
||||
|
||||
#ifdef Q_WS_WIN
|
||||
// Windows doesn't support HTML in tooltips, so just show something basic
|
||||
tray_->setToolTip(song.PrettyTitleWithArtist());
|
||||
return;
|
||||
#endif
|
||||
|
||||
int columns = image_path == nullptr ? 1 : 2;
|
||||
|
||||
QString clone = pattern_;
|
||||
|
||||
clone.replace("%columns" , QString::number(columns));
|
||||
clone.replace("%appName" , QCoreApplication::applicationName());
|
||||
|
||||
clone.replace("%titleKey" , tr("Title") % ":");
|
||||
clone.replace("%titleValue" , song.PrettyTitle().toHtmlEscaped());
|
||||
clone.replace("%artistKey" , tr("Artist") % ":");
|
||||
clone.replace("%artistValue", song.artist().toHtmlEscaped());
|
||||
clone.replace("%albumKey" , tr("Album") % ":");
|
||||
clone.replace("%albumValue" , song.album().toHtmlEscaped());
|
||||
|
||||
clone.replace("%lengthKey" , tr("Length") % ":");
|
||||
clone.replace("%lengthValue", song.PrettyLength().toHtmlEscaped());
|
||||
|
||||
if(columns == 2) {
|
||||
QString final_path = image_path.startsWith("file://") ? image_path.mid(7) : image_path;
|
||||
clone.replace("%image", " <td> <img src=\"" % final_path % "\" /> </td>");
|
||||
}
|
||||
else {
|
||||
clone.replace("%image", "");
|
||||
}
|
||||
|
||||
// TODO: we should also repaint this
|
||||
tray_->setToolTip(clone);
|
||||
|
||||
}
|
||||
|
||||
void QtSystemTrayIcon::ClearNowPlaying() {
|
||||
tray_->setToolTip(QCoreApplication::applicationName());
|
||||
}
|
||||
75
src/core/qtsystemtrayicon.h
Normal file
75
src/core/qtsystemtrayicon.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QTSYSTEMTRAYICON_H
|
||||
#define QTSYSTEMTRAYICON_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "systemtrayicon.h"
|
||||
|
||||
#include <QSystemTrayIcon>
|
||||
|
||||
class QtSystemTrayIcon : public SystemTrayIcon {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QtSystemTrayIcon(QObject* parent = nullptr);
|
||||
~QtSystemTrayIcon();
|
||||
|
||||
void SetupMenu(QAction* previous, QAction *play, QAction *stop, QAction *stop_after, QAction *next, QAction *mute, QAction *quit);
|
||||
bool IsVisible() const;
|
||||
void SetVisible(bool visible);
|
||||
|
||||
void ShowPopup(const QString &summary, const QString &message, int timeout);
|
||||
|
||||
void SetNowPlaying(const Song& song, const QString& image_path);
|
||||
void ClearNowPlaying();
|
||||
|
||||
protected:
|
||||
// SystemTrayIcon
|
||||
void UpdateIcon();
|
||||
void SetPaused();
|
||||
void SetPlaying(bool enable_play_pause = false);
|
||||
void SetStopped();
|
||||
void LastFMButtonVisibilityChanged(bool value);
|
||||
void MuteButtonStateChanged(bool value);
|
||||
|
||||
// QObject
|
||||
bool eventFilter(QObject *, QEvent *);
|
||||
|
||||
private slots:
|
||||
void Clicked(QSystemTrayIcon::ActivationReason);
|
||||
|
||||
private:
|
||||
QSystemTrayIcon *tray_;
|
||||
QMenu *menu_;
|
||||
QAction *action_play_pause_;
|
||||
QAction *action_stop_;
|
||||
QAction *action_stop_after_this_track_;
|
||||
QAction *action_mute_;
|
||||
|
||||
QString pattern_;
|
||||
|
||||
QPixmap normal_icon_;
|
||||
QPixmap grey_icon_;
|
||||
};
|
||||
|
||||
#endif // QTSYSTEMTRAYICON_H
|
||||
44
src/core/scangiomodulepath.cpp
Normal file
44
src/core/scangiomodulepath.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//#ifdef HAVE_GIO
|
||||
//#undef signals // Clashes with GIO, and not needed in this file
|
||||
#include <gio/gio.h>
|
||||
#include <QString>
|
||||
#include "core/logging.h"
|
||||
|
||||
//namespace {
|
||||
|
||||
void ScanGIOModulePath() {
|
||||
QString gio_module_path;
|
||||
|
||||
#if defined(Q_OS_WIN32)
|
||||
gio_module_path = QCoreApplication::applicationDirPath() + "/gio-modules";
|
||||
#endif
|
||||
|
||||
if (!gio_module_path.isEmpty()) {
|
||||
qLog(Debug) << "Adding GIO module path:" << gio_module_path;
|
||||
QByteArray bytes = gio_module_path.toLocal8Bit();
|
||||
g_io_modules_scan_all_in_directory(bytes.data());
|
||||
}
|
||||
}
|
||||
|
||||
//} // namespace
|
||||
//#endif // HAVE_GIO
|
||||
21
src/core/scangiomodulepath.h
Normal file
21
src/core/scangiomodulepath.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This code was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
void ScanGIOModulePath();
|
||||
65
src/core/scoped_cftyperef.h
Normal file
65
src/core/scoped_cftyperef.h
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_MAC_SCOPED_CFTYPEREF_H_
|
||||
#define BASE_MAC_SCOPED_CFTYPEREF_H_
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
// ScopedCFTypeRef<> is patterned after scoped_ptr<>, but maintains ownership
|
||||
// of a CoreFoundation object: any object that can be represented as a
|
||||
// CFTypeRef. Style deviations here are solely for compatibility with
|
||||
// scoped_ptr<>'s interface, with which everyone is already familiar.
|
||||
//
|
||||
// When ScopedCFTypeRef<> takes ownership of an object (in the constructor or
|
||||
// in reset()), it takes over the caller's existing ownership claim. The
|
||||
// caller must own the object it gives to ScopedCFTypeRef<>, and relinquishes
|
||||
// an ownership claim to that object. ScopedCFTypeRef<> does not call
|
||||
// CFRetain().
|
||||
template <typename CFT>
|
||||
class ScopedCFTypeRef {
|
||||
public:
|
||||
typedef CFT element_type;
|
||||
|
||||
explicit ScopedCFTypeRef(CFT object = nullptr) : object_(object) {}
|
||||
|
||||
~ScopedCFTypeRef() {
|
||||
if (object_) CFRelease(object_);
|
||||
}
|
||||
|
||||
void reset(CFT object = nullptr) {
|
||||
if (object_) CFRelease(object_);
|
||||
object_ = object;
|
||||
}
|
||||
|
||||
bool operator==(CFT that) const { return object_ == that; }
|
||||
|
||||
bool operator!=(CFT that) const { return object_ != that; }
|
||||
|
||||
operator CFT() const { return object_; }
|
||||
|
||||
CFT get() const { return object_; }
|
||||
|
||||
void swap(ScopedCFTypeRef& that) {
|
||||
CFT temp = that.object_;
|
||||
that.object_ = object_;
|
||||
object_ = temp;
|
||||
}
|
||||
|
||||
// ScopedCFTypeRef<>::release() is like scoped_ptr<>::release. It is NOT
|
||||
// a wrapper for CFRelease(). To force a ScopedCFTypeRef<> object to call
|
||||
// CFRelease(), use ScopedCFTypeRef<>::reset().
|
||||
CFT release() __attribute__((warn_unused_result)) {
|
||||
CFT temp = object_;
|
||||
object_ = nullptr;
|
||||
return temp;
|
||||
}
|
||||
|
||||
private:
|
||||
CFT object_;
|
||||
|
||||
Q_DISABLE_COPY(ScopedCFTypeRef);
|
||||
};
|
||||
|
||||
#endif // BASE_MAC_SCOPED_CFTYPEREF_H_
|
||||
38
src/core/scoped_nsautorelease_pool.h
Normal file
38
src/core/scoped_nsautorelease_pool.h
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_MAC_SCOPED_NSAUTORELEASE_POOL_H_
|
||||
#define BASE_MAC_SCOPED_NSAUTORELEASE_POOL_H_
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#if defined(__OBJC__)
|
||||
@class NSAutoreleasePool;
|
||||
#else // __OBJC__
|
||||
class NSAutoreleasePool;
|
||||
#endif // __OBJC__
|
||||
|
||||
// ScopedNSAutoreleasePool allocates an NSAutoreleasePool when instantiated and
|
||||
// sends it a -drain message when destroyed. This allows an autorelease pool to
|
||||
// be maintained in ordinary C++ code without bringing in any direct Objective-C
|
||||
// dependency.
|
||||
|
||||
class ScopedNSAutoreleasePool {
|
||||
public:
|
||||
ScopedNSAutoreleasePool();
|
||||
~ScopedNSAutoreleasePool();
|
||||
|
||||
// Clear out the pool in case its position on the stack causes it to be
|
||||
// alive for long periods of time (such as the entire length of the app).
|
||||
// Only use then when you're certain the items currently in the pool are
|
||||
// no longer needed.
|
||||
void Recycle();
|
||||
private:
|
||||
NSAutoreleasePool* autorelease_pool_;
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(ScopedNSAutoreleasePool);
|
||||
};
|
||||
|
||||
#endif // BASE_MAC_SCOPED_NSAUTORELEASE_POOL_H_
|
||||
24
src/core/scoped_nsautorelease_pool.mm
Normal file
24
src/core/scoped_nsautorelease_pool.mm
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "scoped_nsautorelease_pool.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
ScopedNSAutoreleasePool::ScopedNSAutoreleasePool()
|
||||
: autorelease_pool_([[NSAutoreleasePool alloc] init]) {
|
||||
Q_ASSERT(autorelease_pool_);
|
||||
}
|
||||
|
||||
ScopedNSAutoreleasePool::~ScopedNSAutoreleasePool() {
|
||||
[autorelease_pool_ drain];
|
||||
}
|
||||
|
||||
// Cycle the internal pool, allowing everything there to get cleaned up and
|
||||
// start anew.
|
||||
void ScopedNSAutoreleasePool::Recycle() {
|
||||
[autorelease_pool_ drain];
|
||||
autorelease_pool_ = [[NSAutoreleasePool alloc] init];
|
||||
Q_ASSERT(autorelease_pool_);
|
||||
}
|
||||
143
src/core/scoped_nsobject.h
Normal file
143
src/core/scoped_nsobject.h
Normal file
@@ -0,0 +1,143 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_MEMORY_SCOPED_NSOBJECT_H_
|
||||
#define BASE_MEMORY_SCOPED_NSOBJECT_H_
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// scoped_nsobject<> is patterned after scoped_ptr<>, but maintains ownership
|
||||
// of an NSObject subclass object. Style deviations here are solely for
|
||||
// compatibility with scoped_ptr<>'s interface, with which everyone is already
|
||||
// familiar.
|
||||
//
|
||||
// When scoped_nsobject<> takes ownership of an object (in the constructor or
|
||||
// in reset()), it takes over the caller's existing ownership claim. The
|
||||
// caller must own the object it gives to scoped_nsobject<>, and relinquishes
|
||||
// an ownership claim to that object. scoped_nsobject<> does not call
|
||||
// -retain.
|
||||
//
|
||||
// scoped_nsobject<> is not to be used for NSAutoreleasePools. For
|
||||
// NSAutoreleasePools use ScopedNSAutoreleasePool from
|
||||
// scoped_nsautorelease_pool.h instead.
|
||||
// We check for bad uses of scoped_nsobject and NSAutoreleasePool at compile
|
||||
// time with a template specialization (see below).
|
||||
template <typename NST>
|
||||
class scoped_nsobject {
|
||||
public:
|
||||
explicit scoped_nsobject(NST* object = nil) : object_(object) {}
|
||||
|
||||
~scoped_nsobject() { [object_ release]; }
|
||||
|
||||
void reset(NST* object = nil) {
|
||||
// We intentionally do not check that object != object_ as the caller must
|
||||
// already have an ownership claim over whatever it gives to
|
||||
// scoped_nsobject and ScopedCFTypeRef, whether it's in the constructor or
|
||||
// in a call to reset(). In either case, it relinquishes that claim and
|
||||
// the scoper assumes it.
|
||||
[object_ release];
|
||||
object_ = object;
|
||||
}
|
||||
|
||||
bool operator==(NST* that) const { return object_ == that; }
|
||||
bool operator!=(NST* that) const { return object_ != that; }
|
||||
|
||||
operator NST*() const { return object_; }
|
||||
|
||||
NST* get() const { return object_; }
|
||||
|
||||
void swap(scoped_nsobject& that) {
|
||||
NST* temp = that.object_;
|
||||
that.object_ = object_;
|
||||
object_ = temp;
|
||||
}
|
||||
|
||||
// scoped_nsobject<>::release() is like scoped_ptr<>::release. It is NOT
|
||||
// a wrapper for [object_ release]. To force a scoped_nsobject<> object to
|
||||
// call [object_ release], use scoped_nsobject<>::reset().
|
||||
NST* release() __attribute__((warn_unused_result)) {
|
||||
NST* temp = object_;
|
||||
object_ = nil;
|
||||
return temp;
|
||||
}
|
||||
|
||||
private:
|
||||
NST* object_;
|
||||
|
||||
Q_DISABLE_COPY(scoped_nsobject);
|
||||
};
|
||||
|
||||
// Free functions
|
||||
template <class C>
|
||||
void swap(scoped_nsobject<C>& p1, scoped_nsobject<C>& p2) {
|
||||
p1.swap(p2);
|
||||
}
|
||||
|
||||
template <class C>
|
||||
bool operator==(C* p1, const scoped_nsobject<C>& p2) {
|
||||
return p1 == p2.get();
|
||||
}
|
||||
|
||||
template <class C>
|
||||
bool operator!=(C* p1, const scoped_nsobject<C>& p2) {
|
||||
return p1 != p2.get();
|
||||
}
|
||||
|
||||
// Specialization to make scoped_nsobject<id> work.
|
||||
template <>
|
||||
class scoped_nsobject<id> {
|
||||
public:
|
||||
explicit scoped_nsobject(id object = nil) : object_(object) {}
|
||||
|
||||
~scoped_nsobject() { [object_ release]; }
|
||||
|
||||
void reset(id object = nil) {
|
||||
// We intentionally do not check that object != object_ as the caller must
|
||||
// already have an ownership claim over whatever it gives to
|
||||
// scoped_nsobject and ScopedCFTypeRef, whether it's in the constructor or
|
||||
// in a call to reset(). In either case, it relinquishes that claim and
|
||||
// the scoper assumes it.
|
||||
[object_ release];
|
||||
object_ = object;
|
||||
}
|
||||
|
||||
bool operator==(id that) const { return object_ == that; }
|
||||
bool operator!=(id that) const { return object_ != that; }
|
||||
|
||||
operator id() const { return object_; }
|
||||
|
||||
id get() const { return object_; }
|
||||
|
||||
void swap(scoped_nsobject& that) {
|
||||
id temp = that.object_;
|
||||
that.object_ = object_;
|
||||
object_ = temp;
|
||||
}
|
||||
|
||||
// scoped_nsobject<>::release() is like scoped_ptr<>::release. It is NOT
|
||||
// a wrapper for [object_ release]. To force a scoped_nsobject<> object to
|
||||
// call [object_ release], use scoped_nsobject<>::reset().
|
||||
id release() __attribute__((warn_unused_result)) {
|
||||
id temp = object_;
|
||||
object_ = nil;
|
||||
return temp;
|
||||
}
|
||||
|
||||
private:
|
||||
id object_;
|
||||
|
||||
Q_DISABLE_COPY(scoped_nsobject);
|
||||
};
|
||||
|
||||
// Do not use scoped_nsobject for NSAutoreleasePools, use
|
||||
// ScopedNSAutoreleasePool instead. This is a compile time check. See details
|
||||
// at top of header.
|
||||
template <>
|
||||
class scoped_nsobject<NSAutoreleasePool> {
|
||||
private:
|
||||
explicit scoped_nsobject(NSAutoreleasePool* object = nil);
|
||||
Q_DISABLE_COPY(scoped_nsobject);
|
||||
};
|
||||
|
||||
#endif // BASE_MEMORY_SCOPED_NSOBJECT_H_
|
||||
71
src/core/scopedgobject.h
Normal file
71
src/core/scopedgobject.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCOPEDGOBJECT_H
|
||||
#define SCOPEDGOBJECT_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
#include <QtDebug>
|
||||
|
||||
template <typename T>
|
||||
class ScopedGObject {
|
||||
public:
|
||||
ScopedGObject() : object_(nullptr) {}
|
||||
|
||||
explicit ScopedGObject(const ScopedGObject& other) : object_(nullptr) {
|
||||
reset(other.object_);
|
||||
}
|
||||
|
||||
~ScopedGObject() { reset(); }
|
||||
|
||||
ScopedGObject& operator=(const ScopedGObject& other) {
|
||||
reset(other.object_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reset(T* new_object = nullptr) {
|
||||
if (new_object) g_object_ref(new_object);
|
||||
reset_without_add(new_object);
|
||||
}
|
||||
|
||||
void reset_without_add(T* new_object = nullptr) {
|
||||
if (object_) g_object_unref(object_);
|
||||
|
||||
object_ = new_object;
|
||||
}
|
||||
|
||||
T* get() const { return object_; }
|
||||
operator T*() const { return get(); }
|
||||
T* operator*() const { return get(); }
|
||||
operator bool() const { return get(); }
|
||||
|
||||
bool operator==(const ScopedGObject& other) const {
|
||||
return object_ == other.object_;
|
||||
}
|
||||
|
||||
private:
|
||||
T* object_;
|
||||
};
|
||||
|
||||
#endif // SCOPEDGOBJECT_H
|
||||
|
||||
50
src/core/scopedtransaction.cpp
Normal file
50
src/core/scopedtransaction.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "scopedtransaction.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
#include <QSqlDatabase>
|
||||
#include <QtDebug>
|
||||
|
||||
ScopedTransaction::ScopedTransaction(QSqlDatabase* db)
|
||||
: db_(db), pending_(true) {
|
||||
db->transaction();
|
||||
}
|
||||
|
||||
ScopedTransaction::~ScopedTransaction() {
|
||||
if (pending_) {
|
||||
qLog(Warning) << "Rolling back transaction";
|
||||
db_->rollback();
|
||||
}
|
||||
}
|
||||
|
||||
void ScopedTransaction::Commit() {
|
||||
if (!pending_) {
|
||||
qLog(Warning) << "Tried to commit a ScopedTransaction twice";
|
||||
return;
|
||||
}
|
||||
|
||||
db_->commit();
|
||||
pending_ = false;
|
||||
}
|
||||
|
||||
45
src/core/scopedtransaction.h
Normal file
45
src/core/scopedtransaction.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCOPEDTRANSACTION_H
|
||||
#define SCOPEDTRANSACTION_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
class QSqlDatabase;
|
||||
|
||||
// Opens a transaction on a database.
|
||||
// Rolls back the transaction if the object goes out of scope before Commit()
|
||||
// is called.
|
||||
class ScopedTransaction : boost::noncopyable {
|
||||
public:
|
||||
ScopedTransaction(QSqlDatabase* db);
|
||||
~ScopedTransaction();
|
||||
|
||||
void Commit();
|
||||
|
||||
private:
|
||||
QSqlDatabase* db_;
|
||||
bool pending_;
|
||||
};
|
||||
|
||||
#endif // SCOPEDTRANSACTION_H
|
||||
62
src/core/screensaver.cpp
Normal file
62
src/core/screensaver.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "screensaver.h"
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
#include "dbusscreensaver.h"
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusConnectionInterface>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
#include "macscreensaver.h"
|
||||
#endif
|
||||
|
||||
#include <QtDebug>
|
||||
|
||||
const char *Screensaver::kGnomeService = "org.gnome.ScreenSaver";
|
||||
const char *Screensaver::kGnomePath = "/";
|
||||
const char *Screensaver::kGnomeInterface = "org.gnome.ScreenSaver";
|
||||
const char *Screensaver::kKdeService = "org.kde.ScreenSaver";
|
||||
const char *Screensaver::kKdePath = "/ScreenSaver/";
|
||||
const char *Screensaver::kKdeInterface = "org.freedesktop.ScreenSaver";
|
||||
|
||||
Screensaver *Screensaver::screensaver_ = 0;
|
||||
|
||||
Screensaver *Screensaver::GetScreensaver() {
|
||||
if (!screensaver_) {
|
||||
#if defined(HAVE_DBUS)
|
||||
if (QDBusConnection::sessionBus().interface()->isServiceRegistered(kGnomeService)) {
|
||||
screensaver_ = new DBusScreensaver(kGnomeService, kGnomePath, kGnomeInterface);
|
||||
}
|
||||
else if (QDBusConnection::sessionBus().interface()->isServiceRegistered(kKdeService)) {
|
||||
screensaver_ = new DBusScreensaver(kKdeService, kKdePath, kKdeInterface);
|
||||
}
|
||||
#elif defined(Q_OS_DARWIN)
|
||||
screensaver_ = new MacScreensaver();
|
||||
#endif
|
||||
}
|
||||
return screensaver_;
|
||||
}
|
||||
47
src/core/screensaver.h
Normal file
47
src/core/screensaver.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCREENSAVER_H
|
||||
#define SCREENSAVER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
class Screensaver {
|
||||
public:
|
||||
virtual ~Screensaver() {}
|
||||
|
||||
static const char *kGnomeService;
|
||||
static const char *kGnomePath;
|
||||
static const char *kGnomeInterface;
|
||||
|
||||
static const char *kKdeService;
|
||||
static const char *kKdePath;
|
||||
static const char *kKdeInterface;
|
||||
|
||||
virtual void Inhibit() = 0;
|
||||
virtual void Uninhibit() = 0;
|
||||
|
||||
static Screensaver *GetScreensaver();
|
||||
|
||||
private:
|
||||
static Screensaver *screensaver_;
|
||||
};
|
||||
|
||||
#endif
|
||||
56
src/core/settingsprovider.cpp
Normal file
56
src/core/settingsprovider.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "settingsprovider.h"
|
||||
|
||||
SettingsProvider::SettingsProvider() {}
|
||||
|
||||
DefaultSettingsProvider::DefaultSettingsProvider() {}
|
||||
|
||||
void DefaultSettingsProvider::set_group(const char *group) {
|
||||
while (!backend_.group().isEmpty()) backend_.endGroup();
|
||||
|
||||
backend_.beginGroup(group);
|
||||
}
|
||||
|
||||
QVariant DefaultSettingsProvider::value(const QString &key, const QVariant &default_value) const {
|
||||
return backend_.value(key, default_value);
|
||||
}
|
||||
|
||||
void DefaultSettingsProvider::setValue(const QString &key, const QVariant &value) {
|
||||
backend_.setValue(key, value);
|
||||
}
|
||||
|
||||
int DefaultSettingsProvider::beginReadArray(const QString &prefix) {
|
||||
return backend_.beginReadArray(prefix);
|
||||
}
|
||||
|
||||
void DefaultSettingsProvider::beginWriteArray(const QString &prefix, int size) {
|
||||
backend_.beginWriteArray(prefix, size);
|
||||
}
|
||||
|
||||
void DefaultSettingsProvider::setArrayIndex(int i) {
|
||||
backend_.setArrayIndex(i);
|
||||
}
|
||||
|
||||
void DefaultSettingsProvider::endArray() { backend_.endArray(); }
|
||||
|
||||
62
src/core/settingsprovider.h
Normal file
62
src/core/settingsprovider.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SETTINGSPROVIDER_H
|
||||
#define SETTINGSPROVIDER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QVariant>
|
||||
#include <QSettings>
|
||||
|
||||
class SettingsProvider {
|
||||
public:
|
||||
SettingsProvider();
|
||||
virtual ~SettingsProvider() {}
|
||||
|
||||
virtual void set_group(const char *group) = 0;
|
||||
|
||||
virtual QVariant value(const QString &key, const QVariant &default_value = QVariant()) const = 0;
|
||||
virtual void setValue(const QString &key, const QVariant &value) = 0;
|
||||
virtual int beginReadArray(const QString &prefix) = 0;
|
||||
virtual void beginWriteArray(const QString &prefix, int size = -1) = 0;
|
||||
virtual void setArrayIndex(int i) = 0;
|
||||
virtual void endArray() = 0;
|
||||
};
|
||||
|
||||
class DefaultSettingsProvider : public SettingsProvider {
|
||||
public:
|
||||
DefaultSettingsProvider();
|
||||
|
||||
void set_group(const char* group);
|
||||
|
||||
QVariant value(const QString &key, const QVariant &default_value = QVariant()) const;
|
||||
void setValue(const QString &key, const QVariant &value);
|
||||
int beginReadArray(const QString &prefix);
|
||||
void beginWriteArray(const QString &prefix, int size = -1);
|
||||
void setArrayIndex(int i);
|
||||
void endArray();
|
||||
|
||||
private:
|
||||
QSettings backend_;
|
||||
};
|
||||
|
||||
#endif // SETTINGSPROVIDER_H
|
||||
|
||||
52
src/core/signalchecker.cpp
Normal file
52
src/core/signalchecker.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2012, 2014, John Maguire <john.maguire@gmail.com>
|
||||
* Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "signalchecker.h"
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
gulong CheckedGConnect(gpointer source, const char *signal, GCallback callback, gpointer data, const int callback_param_count) {
|
||||
|
||||
guint signal_id = 0;
|
||||
GQuark detail = 0;
|
||||
|
||||
if (!g_signal_parse_name(signal, G_OBJECT_TYPE(source), &signal_id, &detail, false)) {
|
||||
qFatal("Connecting to invalid signal: %s", signal);
|
||||
return 0;
|
||||
}
|
||||
|
||||
GSignalQuery query;
|
||||
g_signal_query(signal_id, &query);
|
||||
// The signature for a signal callback is always:
|
||||
// return_type callback(gpointer data1, params..., gpointer data2);
|
||||
int signal_params = query.n_params + 2;
|
||||
if (signal_params != callback_param_count) {
|
||||
qFatal("Connecting callback to signal with different parameters counts");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return g_signal_connect(source, signal, G_CALLBACK(callback), data);
|
||||
|
||||
}
|
||||
|
||||
38
src/core/signalchecker.h
Normal file
38
src/core/signalchecker.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SIGNALCHECKER_H
|
||||
#define SIGNALCHECKER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
#include <boost/function_types/function_arity.hpp>
|
||||
#include <boost/typeof/typeof.hpp>
|
||||
|
||||
// Do not call this directly, use CHECKED_GCONNECT instead.
|
||||
gulong CheckedGConnect(gpointer source, const char *signal, GCallback callback, gpointer data, const int callback_param_count);
|
||||
|
||||
#define FUNCTION_ARITY(callback) boost::function_types::function_arity<BOOST_TYPEOF(callback)>::value
|
||||
|
||||
#define CHECKED_GCONNECT(source, signal, callback, data) CheckedGConnect(source, signal, G_CALLBACK(callback), data, FUNCTION_ARITY(callback))
|
||||
|
||||
#endif // SIGNALCHECKER_H
|
||||
161
src/core/simpletreeitem.h
Normal file
161
src/core/simpletreeitem.h
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SIMPLETREEITEM_H
|
||||
#define SIMPLETREEITEM_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "simpletreemodel.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
|
||||
template <typename T>
|
||||
class SimpleTreeItem {
|
||||
public:
|
||||
SimpleTreeItem(int _type, SimpleTreeModel<T>* _model); // For the root item
|
||||
SimpleTreeItem(int _type, const QString& _key, T* _parent = nullptr);
|
||||
explicit SimpleTreeItem(int _type, T* _parent = nullptr);
|
||||
virtual ~SimpleTreeItem();
|
||||
|
||||
void InsertNotify(T* _parent);
|
||||
void DeleteNotify(int child_row);
|
||||
void ClearNotify();
|
||||
void ChangedNotify();
|
||||
|
||||
void Delete(int child_row);
|
||||
T* ChildByKey(const QString& key) const;
|
||||
|
||||
QString DisplayText() const { return display_text.isNull() ? key : display_text; }
|
||||
QString SortText() const { return sort_text.isNull() ? key : sort_text; }
|
||||
|
||||
int type;
|
||||
QString key;
|
||||
QString sort_text;
|
||||
QString display_text;
|
||||
|
||||
int row;
|
||||
bool lazy_loaded;
|
||||
|
||||
T* parent;
|
||||
QList<T*> children;
|
||||
QAbstractItemModel* child_model;
|
||||
|
||||
SimpleTreeModel<T>* model;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
SimpleTreeItem<T>::SimpleTreeItem(int _type, SimpleTreeModel<T>* _model)
|
||||
: type(_type),
|
||||
row(0),
|
||||
lazy_loaded(true),
|
||||
parent(nullptr),
|
||||
child_model(nullptr),
|
||||
model(_model) {}
|
||||
|
||||
template <typename T>
|
||||
SimpleTreeItem<T>::SimpleTreeItem(int _type, const QString& _key, T* _parent)
|
||||
: type(_type),
|
||||
key(_key),
|
||||
lazy_loaded(false),
|
||||
parent(_parent),
|
||||
child_model(nullptr),
|
||||
model(_parent ? _parent->model : nullptr) {
|
||||
if (parent) {
|
||||
row = parent->children.count();
|
||||
parent->children << static_cast<T*>(this);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
SimpleTreeItem<T>::SimpleTreeItem(int _type, T* _parent)
|
||||
: type(_type),
|
||||
lazy_loaded(false),
|
||||
parent(_parent),
|
||||
child_model(nullptr),
|
||||
model(_parent ? _parent->model : nullptr) {
|
||||
if (parent) {
|
||||
row = parent->children.count();
|
||||
parent->children << static_cast<T*>(this);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeItem<T>::InsertNotify(T* _parent) {
|
||||
parent = _parent;
|
||||
model = parent->model;
|
||||
row = parent->children.count();
|
||||
|
||||
model->BeginInsert(parent, row);
|
||||
parent->children << static_cast<T*>(this);
|
||||
model->EndInsert();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeItem<T>::DeleteNotify(int child_row) {
|
||||
model->BeginDelete(static_cast<T*>(this), child_row);
|
||||
delete children.takeAt(child_row);
|
||||
|
||||
// Adjust row numbers of those below it :(
|
||||
for (int i = child_row; i < children.count(); ++i) children[i]->row--;
|
||||
model->EndDelete();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeItem<T>::ClearNotify() {
|
||||
if (children.count()) {
|
||||
model->BeginDelete(static_cast<T*>(this), 0, children.count() - 1);
|
||||
|
||||
qDeleteAll(children);
|
||||
children.clear();
|
||||
|
||||
model->EndDelete();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeItem<T>::ChangedNotify() {
|
||||
model->EmitDataChanged(static_cast<T*>(this));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
SimpleTreeItem<T>::~SimpleTreeItem() {
|
||||
qDeleteAll(children);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeItem<T>::Delete(int child_row) {
|
||||
delete children.takeAt(child_row);
|
||||
|
||||
// Adjust row numbers of those below it :(
|
||||
for (int i = child_row; i < children.count(); ++i) children[i]->row--;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* SimpleTreeItem<T>::ChildByKey(const QString& key) const {
|
||||
for (T* child : children) {
|
||||
if (child->key == key) return child;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif // SIMPLETREEITEM_H
|
||||
|
||||
154
src/core/simpletreemodel.h
Normal file
154
src/core/simpletreemodel.h
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SIMPLETREEMODEL_H
|
||||
#define SIMPLETREEMODEL_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
class QModelIndex;
|
||||
|
||||
template <typename T>
|
||||
class SimpleTreeModel : public QAbstractItemModel {
|
||||
public:
|
||||
explicit SimpleTreeModel(T *root = 0, QObject *parent = nullptr);
|
||||
virtual ~SimpleTreeModel() {}
|
||||
|
||||
// QAbstractItemModel
|
||||
int columnCount(const QModelIndex &parent) const;
|
||||
QModelIndex index(int row, int, const QModelIndex &parent = QModelIndex()) const;
|
||||
QModelIndex parent(const QModelIndex &index) const;
|
||||
int rowCount(const QModelIndex &parent) const;
|
||||
bool hasChildren(const QModelIndex &parent) const;
|
||||
bool canFetchMore(const QModelIndex &parent) const;
|
||||
void fetchMore(const QModelIndex &parent);
|
||||
|
||||
T *IndexToItem(const QModelIndex &index) const;
|
||||
QModelIndex ItemToIndex(T *item) const;
|
||||
|
||||
// Called by items
|
||||
void BeginInsert(T *parent, int start, int end = -1);
|
||||
void EndInsert();
|
||||
void BeginDelete(T *parent, int start, int end = -1);
|
||||
void EndDelete();
|
||||
void EmitDataChanged(T *item);
|
||||
|
||||
protected:
|
||||
virtual void LazyPopulate(T *item) = 0;
|
||||
|
||||
protected:
|
||||
T *root_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
SimpleTreeModel<T>::SimpleTreeModel(T *root, QObject *parent)
|
||||
: QAbstractItemModel(parent), root_(root) {}
|
||||
|
||||
template <typename T>
|
||||
T *SimpleTreeModel<T>::IndexToItem(const QModelIndex &index) const {
|
||||
if (!index.isValid()) return root_;
|
||||
return reinterpret_cast<T*>(index.internalPointer());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QModelIndex SimpleTreeModel<T>::ItemToIndex(T *item) const {
|
||||
if (!item || !item->parent) return QModelIndex();
|
||||
return createIndex(item->row, 0, item);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int SimpleTreeModel<T>::columnCount(const QModelIndex&) const {
|
||||
return 1;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QModelIndex SimpleTreeModel<T>::index(int row, int, const QModelIndex &parent) const {
|
||||
T *parent_item = IndexToItem(parent);
|
||||
if (!parent_item || row < 0 || parent_item->children.count() <= row)
|
||||
return QModelIndex();
|
||||
|
||||
return ItemToIndex(parent_item->children[row]);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QModelIndex SimpleTreeModel<T>::parent(const QModelIndex &index) const {
|
||||
return ItemToIndex(IndexToItem(index)->parent);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int SimpleTreeModel<T>::rowCount(const QModelIndex &parent) const {
|
||||
T *item = IndexToItem(parent);
|
||||
return item->children.count();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool SimpleTreeModel<T>::hasChildren(const QModelIndex &parent) const {
|
||||
T *item = IndexToItem(parent);
|
||||
if (item->lazy_loaded)
|
||||
return !item->children.isEmpty();
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool SimpleTreeModel<T>::canFetchMore(const QModelIndex &parent) const {
|
||||
T *item = IndexToItem(parent);
|
||||
return !item->lazy_loaded;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeModel<T>::fetchMore(const QModelIndex &parent) {
|
||||
T *item = IndexToItem(parent);
|
||||
if (!item->lazy_loaded) {
|
||||
LazyPopulate(item);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeModel<T>::BeginInsert(T *parent, int start, int end) {
|
||||
if (end == -1) end = start;
|
||||
beginInsertRows(ItemToIndex(parent), start, end);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeModel<T>::EndInsert() {
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeModel<T>::BeginDelete(T *parent, int start, int end) {
|
||||
if (end == -1) end = start;
|
||||
beginRemoveRows(ItemToIndex(parent), start, end);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeModel<T>::EndDelete() {
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeModel<T>::EmitDataChanged(T *item) {
|
||||
QModelIndex index(ItemToIndex(item));
|
||||
emit dataChanged(index, index);
|
||||
}
|
||||
|
||||
#endif // SIMPLETREEMODEL_H
|
||||
1152
src/core/song.cpp
Normal file
1152
src/core/song.cpp
Normal file
File diff suppressed because it is too large
Load Diff
313
src/core/song.h
Normal file
313
src/core/song.h
Normal file
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SONG_H
|
||||
#define SONG_H
|
||||
|
||||
#include <QFuture>
|
||||
#include <QImage>
|
||||
#include <QMetaType>
|
||||
#include <QSharedDataPointer>
|
||||
#include <QVariantMap>
|
||||
|
||||
#include "config.h"
|
||||
#include "engine/engine_fwd.h"
|
||||
|
||||
namespace pb {
|
||||
namespace tagreader {
|
||||
class SongMetadata;
|
||||
} // namespace tagreader
|
||||
} // namespace pb
|
||||
|
||||
class QSqlQuery;
|
||||
class QUrl;
|
||||
|
||||
#ifdef HAVE_LIBGPOD
|
||||
struct _Itdb_Track;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBMTP
|
||||
struct LIBMTP_track_struct;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBLASTFM
|
||||
namespace lastfm {
|
||||
class Track;
|
||||
}
|
||||
#endif
|
||||
|
||||
class SqlRow;
|
||||
|
||||
class Song {
|
||||
|
||||
public:
|
||||
|
||||
Song();
|
||||
Song(const Song &other);
|
||||
~Song();
|
||||
|
||||
static const QStringList kColumns;
|
||||
static const QString kColumnSpec;
|
||||
static const QString kBindSpec;
|
||||
static const QString kUpdateSpec;
|
||||
|
||||
static const QStringList kFtsColumns;
|
||||
static const QString kFtsColumnSpec;
|
||||
static const QString kFtsBindSpec;
|
||||
static const QString kFtsUpdateSpec;
|
||||
|
||||
static const QString kManuallyUnsetCover;
|
||||
static const QString kEmbeddedCover;
|
||||
|
||||
static QString JoinSpec(const QString &table);
|
||||
|
||||
// Don't change these values - they're stored in the database, and defined
|
||||
// in the tag reader protobuf.
|
||||
// If a new lossless file is added, also add it to IsFileLossless().
|
||||
enum FileType {
|
||||
Type_Unknown = 0,
|
||||
Type_Asf = 1,
|
||||
Type_Flac = 2,
|
||||
Type_Mp4 = 3,
|
||||
Type_Mpc = 4,
|
||||
Type_Mpeg = 5,
|
||||
Type_OggFlac = 6,
|
||||
Type_OggSpeex = 7,
|
||||
Type_OggVorbis = 8,
|
||||
Type_Aiff = 9,
|
||||
Type_Wav = 10,
|
||||
Type_TrueAudio = 11,
|
||||
Type_Cdda = 12,
|
||||
Type_OggOpus = 13,
|
||||
};
|
||||
|
||||
static QString TextForFiletype(FileType type);
|
||||
QString TextForFiletype() const { return TextForFiletype(filetype()); }
|
||||
|
||||
bool IsFileLossless() const;
|
||||
|
||||
// Sort songs alphabetically using their pretty title
|
||||
static void SortSongsListAlphabetically(QList<Song> *songs);
|
||||
|
||||
// Constructors
|
||||
void Init(const QString &title, const QString &artist, const QString &album, qint64 length_nanosec);
|
||||
void Init(const QString &title, const QString &artist, const QString &album, qint64 beginning, qint64 end);
|
||||
void InitFromProtobuf(const pb::tagreader::SongMetadata &pb);
|
||||
void InitFromQuery(const SqlRow &query, bool reliable_metadata, int col = 0);
|
||||
void InitFromFilePartial(const QString &filename); // Just store the filename: incomplete but fast
|
||||
void InitArtManual(); // Check if there is already a art in the cache and
|
||||
// store the filename in art_manual
|
||||
#ifdef HAVE_LIBLASTFM
|
||||
void InitFromLastFM(const lastfm::Track &track);
|
||||
#endif
|
||||
|
||||
void MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle);
|
||||
|
||||
#ifdef HAVE_LIBGPOD
|
||||
void InitFromItdb(const _Itdb_Track *track, const QString &prefix);
|
||||
void ToItdb(_Itdb_Track *track) const;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBMTP
|
||||
void InitFromMTP(const LIBMTP_track_struct *track, const QString &host);
|
||||
void ToMTP(LIBMTP_track_struct *track) const;
|
||||
#endif
|
||||
|
||||
// Copies important statistics from the other song to this one, overwriting
|
||||
// any data that already exists. Useful when you want updated tags from disk
|
||||
// but you want to keep user stats.
|
||||
void MergeUserSetData(const Song &other);
|
||||
|
||||
static QString Decode(const QString &tag, const QTextCodec *codec = nullptr);
|
||||
|
||||
// Save
|
||||
void BindToQuery(QSqlQuery *query) const;
|
||||
void BindToFtsQuery(QSqlQuery *query) const;
|
||||
#ifdef HAVE_LIBLASTFM
|
||||
void ToLastFM(lastfm::Track *track, bool prefer_album_artist) const;
|
||||
#endif
|
||||
void ToXesam(QVariantMap *map) const;
|
||||
void ToProtobuf(pb::tagreader::SongMetadata *pb) const;
|
||||
|
||||
// Simple accessors
|
||||
bool is_valid() const;
|
||||
bool is_unavailable() const;
|
||||
int id() const;
|
||||
|
||||
const QString &title() const;
|
||||
const QString &album() const;
|
||||
const QString &artist() const;
|
||||
const QString &albumartist() const;
|
||||
int track() const;
|
||||
int disc() const;
|
||||
int year() const;
|
||||
int originalyear() const;
|
||||
const QString &genre() const;
|
||||
bool is_compilation() const;
|
||||
const QString &composer() const;
|
||||
const QString &performer() const;
|
||||
const QString &grouping() const;
|
||||
const QString &comment() const;
|
||||
|
||||
int playcount() const;
|
||||
int skipcount() const;
|
||||
int lastplayed() const;
|
||||
int album_id() const;
|
||||
|
||||
qint64 beginning_nanosec() const;
|
||||
qint64 end_nanosec() const;
|
||||
qint64 length_nanosec() const;
|
||||
|
||||
int bitrate() const;
|
||||
int samplerate() const;
|
||||
int bitdepth() const;
|
||||
|
||||
int directory_id() const;
|
||||
const QUrl &url() const;
|
||||
const QString &basefilename() const;
|
||||
FileType filetype() const;
|
||||
int filesize() const;
|
||||
uint mtime() const;
|
||||
uint ctime() const;
|
||||
|
||||
const QString &art_automatic() const;
|
||||
const QString &art_manual() const;
|
||||
|
||||
const QString &cue_path() const;
|
||||
bool has_cue() const;
|
||||
|
||||
const QString &effective_album() const;
|
||||
int effective_originalyear() const;
|
||||
const QString &effective_albumartist() const;
|
||||
|
||||
bool is_collection_song() const;
|
||||
bool is_cdda() const;
|
||||
|
||||
// Playlist views are special because you don't want to fill in album artists automatically for compilations, but you do for normal albums:
|
||||
const QString &playlist_albumartist() const;
|
||||
|
||||
// Returns true if this Song had it's cover manually unset by user.
|
||||
bool has_manually_unset_cover() const;
|
||||
// This method represents an explicit request to unset this song's
|
||||
// cover.
|
||||
void manually_unset_cover();
|
||||
|
||||
// Returns true if this song (it's media file) has an embedded cover.
|
||||
bool has_embedded_cover() const;
|
||||
// Sets a flag saying that this song (it's media file) has an embedded cover.
|
||||
void set_embedded_cover();
|
||||
|
||||
const QImage &image() const;
|
||||
|
||||
// Pretty accessors
|
||||
QString PrettyTitle() const;
|
||||
QString PrettyTitleWithArtist() const;
|
||||
QString PrettyLength() const;
|
||||
QString PrettyYear() const;
|
||||
|
||||
QString TitleWithCompilationArtist() const;
|
||||
|
||||
QString SampleRateBitDepthToText() const;
|
||||
|
||||
// Setters
|
||||
bool IsEditable() const;
|
||||
|
||||
void set_id(int id);
|
||||
void set_album_id(int v);
|
||||
void set_valid(bool v);
|
||||
|
||||
void set_title(const QString &v);
|
||||
void set_album(const QString &v);
|
||||
void set_artist(const QString &v);
|
||||
void set_albumartist(const QString &v);
|
||||
void set_track(int v);
|
||||
void set_disc(int v);
|
||||
void set_year(int v);
|
||||
void set_originalyear(int v);
|
||||
void set_genre(const QString &v);
|
||||
void set_genre_id3(int id);
|
||||
void set_compilation(bool v);
|
||||
void set_composer(const QString &v);
|
||||
void set_performer(const QString &v);
|
||||
void set_grouping(const QString &v);
|
||||
void set_comment(const QString &v);
|
||||
|
||||
void set_beginning_nanosec(qint64 v);
|
||||
void set_end_nanosec(qint64 v);
|
||||
void set_length_nanosec(qint64 v);
|
||||
|
||||
void set_bitrate(int v);
|
||||
void set_samplerate(int v);
|
||||
void set_bitdepth(int v);
|
||||
|
||||
void set_directory_id(int v);
|
||||
void set_url(const QUrl &v);
|
||||
void set_basefilename(const QString &v);
|
||||
void set_filetype(FileType v);
|
||||
void set_filesize(int v);
|
||||
void set_mtime(int v);
|
||||
void set_ctime(int v);
|
||||
void set_unavailable(bool v);
|
||||
|
||||
void set_playcount(int v);
|
||||
void set_skipcount(int v);
|
||||
void set_lastplayed(int v);
|
||||
|
||||
void set_compilation_detected(bool v);
|
||||
void set_compilation_on(bool v);
|
||||
void set_compilation_off(bool v);
|
||||
|
||||
void set_art_automatic(const QString &v);
|
||||
void set_art_manual(const QString &v);
|
||||
|
||||
void set_cue_path(const QString &v);
|
||||
|
||||
void set_image(const QImage &i);
|
||||
|
||||
|
||||
// Comparison functions
|
||||
bool IsMetadataEqual(const Song &other) const;
|
||||
bool IsOnSameAlbum(const Song &other) const;
|
||||
bool IsSimilar(const Song &other) const;
|
||||
|
||||
bool operator==(const Song &other) const;
|
||||
|
||||
// Two songs that are on the same album will have the same AlbumKey. It is
|
||||
// more efficient to use IsOnSameAlbum, but this function can be used when
|
||||
// you need to hash the key to do fast lookups.
|
||||
QString AlbumKey() const;
|
||||
|
||||
Song &operator=(const Song &other);
|
||||
|
||||
private:
|
||||
struct Private;
|
||||
QSharedDataPointer<Private> d;
|
||||
};
|
||||
Q_DECLARE_METATYPE(Song);
|
||||
|
||||
typedef QList<Song> SongList;
|
||||
Q_DECLARE_METATYPE(QList<Song>);
|
||||
|
||||
uint qHash(const Song &song);
|
||||
// Hash function using field checked in IsSimilar function
|
||||
uint HashSimilar(const Song &song);
|
||||
|
||||
#endif // SONG_H
|
||||
|
||||
623
src/core/songloader.cpp
Normal file
623
src/core/songloader.cpp
Normal file
@@ -0,0 +1,623 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "songloader.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#if defined(HAVE_GSTREAMER) && defined(HAVE_AUDIOCD)
|
||||
#include <gst/audio/gstaudiocdsrc.h>
|
||||
#endif
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QDirIterator>
|
||||
#include <QFileInfo>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "config.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/player.h"
|
||||
#include "core/signalchecker.h"
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/sqlrow.h"
|
||||
#include "playlistparsers/cueparser.h"
|
||||
#include "playlistparsers/parserbase.h"
|
||||
#include "playlistparsers/playlistparser.h"
|
||||
|
||||
#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER)
|
||||
#include "device/cddasongloader.h"
|
||||
#endif
|
||||
|
||||
using std::placeholders::_1;
|
||||
|
||||
QSet<QString> SongLoader::sRawUriSchemes;
|
||||
const int SongLoader::kDefaultTimeout = 5000;
|
||||
|
||||
SongLoader::SongLoader(CollectionBackendInterface *collection, const Player *player, QObject *parent) :
|
||||
QObject(parent),
|
||||
timeout_timer_(new QTimer(this)),
|
||||
playlist_parser_(new PlaylistParser(collection, this)),
|
||||
cue_parser_(new CueParser(collection, this)),
|
||||
timeout_(kDefaultTimeout),
|
||||
state_(WaitingForType),
|
||||
success_(false),
|
||||
parser_(nullptr),
|
||||
collection_(collection),
|
||||
player_(player) {
|
||||
if (sRawUriSchemes.isEmpty()) {
|
||||
sRawUriSchemes << "udp"
|
||||
<< "mms"
|
||||
<< "mmsh"
|
||||
<< "mmst"
|
||||
<< "mmsu"
|
||||
<< "rtsp"
|
||||
<< "rtspu"
|
||||
<< "rtspt"
|
||||
<< "rtsph";
|
||||
}
|
||||
|
||||
timeout_timer_->setSingleShot(true);
|
||||
|
||||
connect(timeout_timer_, SIGNAL(timeout()), SLOT(Timeout()));
|
||||
|
||||
}
|
||||
|
||||
SongLoader::~SongLoader() {
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
if (pipeline_) {
|
||||
state_ = Finished;
|
||||
gst_element_set_state(pipeline_.get(), GST_STATE_NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
SongLoader::Result SongLoader::Load(const QUrl &url) {
|
||||
|
||||
url_ = url;
|
||||
|
||||
if (url_.scheme() == "file") {
|
||||
return LoadLocal(url_.toLocalFile());
|
||||
}
|
||||
|
||||
if (sRawUriSchemes.contains(url_.scheme()) || player_->HandlerForUrl(url) != nullptr) {
|
||||
// The URI scheme indicates that it can't possibly be a playlist, or we have
|
||||
// a custom handler for the URL, so add it as a raw stream.
|
||||
//AddAsRawStream();
|
||||
return Success;
|
||||
}
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
preload_func_ = std::bind(&SongLoader::LoadRemote, this);
|
||||
#endif
|
||||
|
||||
return BlockingLoadRequired;
|
||||
}
|
||||
|
||||
void SongLoader::LoadFilenamesBlocking() {
|
||||
|
||||
if (preload_func_) {
|
||||
preload_func_();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SongLoader::Result SongLoader::LoadLocalPartial(const QString &filename) {
|
||||
|
||||
qLog(Debug) << "Fast Loading local file" << filename;
|
||||
// First check to see if it's a directory - if so we can load all the songs
|
||||
// inside right away.
|
||||
if (QFileInfo(filename).isDir()) {
|
||||
LoadLocalDirectory(filename);
|
||||
return Success;
|
||||
}
|
||||
Song song;
|
||||
song.InitFromFilePartial(filename);
|
||||
if (song.is_valid()) songs_ << song;
|
||||
return Success;
|
||||
|
||||
}
|
||||
|
||||
SongLoader::Result SongLoader::LoadAudioCD() {
|
||||
|
||||
#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER)
|
||||
CddaSongLoader *cdda_song_loader = new CddaSongLoader;
|
||||
connect(cdda_song_loader, SIGNAL(SongsDurationLoaded(SongList)), this, SLOT(AudioCDTracksLoadedSlot(SongList)));
|
||||
connect(cdda_song_loader, SIGNAL(SongsMetadataLoaded(SongList)), this, SLOT(AudioCDTracksTagsLoaded(SongList)));
|
||||
cdda_song_loader->LoadSongs();
|
||||
return Success;
|
||||
#else
|
||||
return Error;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER)
|
||||
void SongLoader::AudioCDTracksLoadedSlot(const SongList &songs) {
|
||||
songs_ = songs;
|
||||
emit AudioCDTracksLoaded();
|
||||
}
|
||||
|
||||
void SongLoader::AudioCDTracksTagsLoaded(const SongList &songs) {
|
||||
CddaSongLoader *cdda_song_loader = qobject_cast<CddaSongLoader*>(sender());
|
||||
cdda_song_loader->deleteLater();
|
||||
songs_ = songs;
|
||||
emit LoadAudioCDFinished(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
SongLoader::Result SongLoader::LoadLocal(const QString &filename) {
|
||||
|
||||
qLog(Debug) << "Loading local file" << filename;
|
||||
|
||||
// Search in the database.
|
||||
QUrl url = QUrl::fromLocalFile(filename);
|
||||
|
||||
CollectionQuery query;
|
||||
query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
|
||||
query.AddWhere("filename", url.toEncoded());
|
||||
|
||||
if (collection_->ExecQuery(&query) && query.Next()) {
|
||||
// we may have many results when the file has many sections
|
||||
do {
|
||||
Song song;
|
||||
song.InitFromQuery(query, true);
|
||||
|
||||
if (song.is_valid()) {
|
||||
songs_ << song;
|
||||
}
|
||||
} while (query.Next());
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
// It's not in the database, load it asynchronously.
|
||||
preload_func_ =
|
||||
std::bind(&SongLoader::LoadLocalAsync, this, filename);
|
||||
return BlockingLoadRequired;
|
||||
}
|
||||
|
||||
void SongLoader::LoadLocalAsync(const QString &filename) {
|
||||
|
||||
// First check to see if it's a directory - if so we will load all the songs
|
||||
// inside right away.
|
||||
if (QFileInfo(filename).isDir()) {
|
||||
LoadLocalDirectory(filename);
|
||||
return;
|
||||
}
|
||||
|
||||
// It's a local file, so check if it looks like a playlist.
|
||||
// Read the first few bytes.
|
||||
QFile file(filename);
|
||||
if (!file.open(QIODevice::ReadOnly)) return;
|
||||
QByteArray data(file.read(PlaylistParser::kMagicSize));
|
||||
|
||||
ParserBase *parser = playlist_parser_->ParserForMagic(data);
|
||||
if (!parser) {
|
||||
// Check the file extension as well, maybe the magic failed, or it was a
|
||||
// basic M3U file which is just a plain list of filenames.
|
||||
parser = playlist_parser_->ParserForExtension(QFileInfo(filename).suffix().toLower());
|
||||
}
|
||||
|
||||
if (parser) {
|
||||
qLog(Debug) << "Parsing using" << parser->name();
|
||||
|
||||
// It's a playlist!
|
||||
LoadPlaylist(parser, filename);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if it's a cue file
|
||||
QString matching_cue = filename.section('.', 0, -2) + ".cue";
|
||||
if (QFile::exists(matching_cue)) {
|
||||
// it's a cue - create virtual tracks
|
||||
QFile cue(matching_cue);
|
||||
cue.open(QIODevice::ReadOnly);
|
||||
|
||||
SongList song_list = cue_parser_->Load(&cue, matching_cue,
|
||||
QDir(filename.section('/', 0, -2)));
|
||||
for (Song song: song_list){
|
||||
if (song.is_valid()) songs_ << song;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Assume it's just a normal file
|
||||
Song song;
|
||||
song.InitFromFilePartial(filename);
|
||||
if (song.is_valid()) songs_ << song;
|
||||
}
|
||||
|
||||
void SongLoader::LoadMetadataBlocking() {
|
||||
for (int i = 0; i < songs_.size(); i++) {
|
||||
EffectiveSongLoad(&songs_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void SongLoader::EffectiveSongLoad(Song *song) {
|
||||
|
||||
if (!song) return;
|
||||
|
||||
if (song->filetype() != Song::Type_Unknown) {
|
||||
// Maybe we loaded the metadata already, for example from a cuesheet.
|
||||
return;
|
||||
}
|
||||
|
||||
// First, try to get the song from the collection
|
||||
Song collection_song = collection_->GetSongByUrl(song->url());
|
||||
if (collection_song.is_valid()) {
|
||||
*song = collection_song;
|
||||
} else {
|
||||
// it's a normal media file
|
||||
QString filename = song->url().toLocalFile();
|
||||
TagReaderClient::Instance()->ReadFileBlocking(filename, song);
|
||||
}
|
||||
}
|
||||
|
||||
void SongLoader::LoadPlaylist(ParserBase *parser, const QString &filename) {
|
||||
QFile file(filename);
|
||||
file.open(QIODevice::ReadOnly);
|
||||
songs_ = parser->Load(&file, filename, QFileInfo(filename).path());
|
||||
}
|
||||
|
||||
static bool CompareSongs(const Song &left, const Song &right) {
|
||||
|
||||
// Order by artist, album, disc, track
|
||||
if (left.artist() < right.artist()) return true;
|
||||
if (left.artist() > right.artist()) return false;
|
||||
if (left.album() < right.album()) return true;
|
||||
if (left.album() > right.album()) return false;
|
||||
if (left.disc() < right.disc()) return true;
|
||||
if (left.disc() > right.disc()) return false;
|
||||
if (left.track() < right.track()) return true;
|
||||
if (left.track() > right.track()) return false;
|
||||
return left.url() < right.url();
|
||||
}
|
||||
|
||||
void SongLoader::LoadLocalDirectory(const QString &filename) {
|
||||
|
||||
QDirIterator it(filename, QDir::Files | QDir::NoDotAndDotDot | QDir::Readable, QDirIterator::Subdirectories);
|
||||
|
||||
while (it.hasNext()) {
|
||||
LoadLocalPartial(it.next());
|
||||
}
|
||||
|
||||
qStableSort(songs_.begin(), songs_.end(), CompareSongs);
|
||||
|
||||
// Load the first song: all songs will be loaded async, but we want the first
|
||||
// one in our list to be fully loaded, so if the user has the "Start playing
|
||||
// when adding to playlist" preference behaviour set, it can enjoy the first
|
||||
// song being played (seek it, have moodbar, etc.)
|
||||
if (!songs_.isEmpty()) EffectiveSongLoad(&(*songs_.begin()));
|
||||
}
|
||||
|
||||
//void SongLoader::AddAsRawStream() {
|
||||
// Song song;
|
||||
// song.set_valid(true);
|
||||
// song.set_filetype(Song::Type_Stream);
|
||||
// song.set_url(url_);
|
||||
// song.set_title(url_.toString());
|
||||
// songs_ << song;
|
||||
//}
|
||||
|
||||
void SongLoader::Timeout() {
|
||||
state_ = Finished;
|
||||
success_ = false;
|
||||
StopTypefind();
|
||||
}
|
||||
|
||||
void SongLoader::StopTypefind() {
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
// Destroy the pipeline
|
||||
if (pipeline_) {
|
||||
gst_element_set_state(pipeline_.get(), GST_STATE_NULL);
|
||||
pipeline_.reset();
|
||||
}
|
||||
#endif
|
||||
timeout_timer_->stop();
|
||||
|
||||
if (success_ && parser_) {
|
||||
qLog(Debug) << "Parsing" << url_ << "with" << parser_->name();
|
||||
|
||||
// Parse the playlist
|
||||
QBuffer buf(&buffer_);
|
||||
buf.open(QIODevice::ReadOnly);
|
||||
songs_ = parser_->Load(&buf);
|
||||
|
||||
}
|
||||
else if (success_) {
|
||||
//qLog(Debug) << "Loading" << url_ << "as raw stream";
|
||||
|
||||
// It wasn't a playlist - just put the URL in as a stream
|
||||
//AddAsRawStream();
|
||||
}
|
||||
|
||||
emit LoadRemoteFinished();
|
||||
}
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void SongLoader::LoadRemote() {
|
||||
|
||||
qLog(Debug) << "Loading remote file" << url_;
|
||||
|
||||
// It's not a local file so we have to fetch it to see what it is. We use
|
||||
// gstreamer to do this since it handles funky URLs for us (http://, ssh://,
|
||||
// etc) and also has typefinder plugins.
|
||||
// First we wait for typefinder to tell us what it is. If it's not text/plain
|
||||
// or text/uri-list assume it's a song and return success.
|
||||
// Otherwise wait to get 512 bytes of data and do magic on it - if the magic
|
||||
// fails then we don't know what it is so return failure.
|
||||
// If the magic succeeds then we know for sure it's a playlist - so read the
|
||||
// rest of the file, parse the playlist and return success.
|
||||
|
||||
timeout_timer_->start(timeout_);
|
||||
|
||||
// Create the pipeline - it gets unreffed if it goes out of scope
|
||||
std::shared_ptr<GstElement> pipeline(gst_pipeline_new(nullptr), std::bind(&gst_object_unref, _1));
|
||||
|
||||
// Create the source element automatically based on the URL
|
||||
GstElement *source = gst_element_make_from_uri(GST_URI_SRC, url_.toEncoded().constData(), nullptr, nullptr);
|
||||
if (!source) {
|
||||
qLog(Warning) << "Couldn't create gstreamer source element for" << url_.toString();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the other elements and link them up
|
||||
GstElement *typefind = gst_element_factory_make("typefind", nullptr);
|
||||
GstElement *fakesink = gst_element_factory_make("fakesink", nullptr);
|
||||
|
||||
gst_bin_add_many(GST_BIN(pipeline.get()), source, typefind, fakesink, nullptr);
|
||||
gst_element_link_many(source, typefind, fakesink, nullptr);
|
||||
|
||||
// Connect callbacks
|
||||
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline.get()));
|
||||
CHECKED_GCONNECT(typefind, "have-type", &TypeFound, this);
|
||||
gst_bus_set_sync_handler(bus, BusCallbackSync, this, NULL);
|
||||
gst_bus_add_watch(bus, BusCallback, this);
|
||||
|
||||
// Add a probe to the sink so we can capture the data if it's a playlist
|
||||
GstPad *pad = gst_element_get_static_pad(fakesink, "sink");
|
||||
gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, &DataReady, this, NULL);
|
||||
gst_object_unref(pad);
|
||||
|
||||
QEventLoop loop;
|
||||
loop.connect(this, SIGNAL(LoadRemoteFinished()), SLOT(quit()));
|
||||
|
||||
// Start "playing"
|
||||
gst_element_set_state(pipeline.get(), GST_STATE_PLAYING);
|
||||
pipeline_ = pipeline;
|
||||
|
||||
// Wait until loading is finished
|
||||
loop.exec();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void SongLoader::TypeFound(GstElement *, uint, GstCaps *caps, void *self) {
|
||||
|
||||
SongLoader *instance = static_cast<SongLoader*>(self);
|
||||
|
||||
if (instance->state_ != WaitingForType) return;
|
||||
|
||||
// Check the mimetype
|
||||
instance->mime_type_ = gst_structure_get_name(gst_caps_get_structure(caps, 0));
|
||||
qLog(Debug) << "Mime type is" << instance->mime_type_;
|
||||
if (instance->mime_type_ == "text/plain" || instance->mime_type_ == "text/uri-list") {
|
||||
// Yeah it might be a playlist, let's get some data and have a better look
|
||||
instance->state_ = WaitingForMagic;
|
||||
return;
|
||||
}
|
||||
|
||||
// Nope, not a playlist - we're done
|
||||
instance->StopTypefindAsync(true);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
GstPadProbeReturn SongLoader::DataReady(GstPad*, GstPadProbeInfo *info, gpointer self) {
|
||||
|
||||
SongLoader *instance = reinterpret_cast<SongLoader*>(self);
|
||||
|
||||
if (instance->state_ == Finished)
|
||||
return GST_PAD_PROBE_OK;
|
||||
|
||||
GstBuffer *buffer = gst_pad_probe_info_get_buffer(info);
|
||||
GstMapInfo map;
|
||||
gst_buffer_map(buffer, &map, GST_MAP_READ);
|
||||
|
||||
// Append the data to the buffer
|
||||
instance->buffer_.append(reinterpret_cast<const char*>(map.data), map.size);
|
||||
qLog(Debug) << "Received total" << instance->buffer_.size() << "bytes";
|
||||
gst_buffer_unmap(buffer, &map);
|
||||
|
||||
if (instance->state_ == WaitingForMagic && (instance->buffer_.size() >= PlaylistParser::kMagicSize || !instance->IsPipelinePlaying())) {
|
||||
// Got enough that we can test the magic
|
||||
instance->MagicReady();
|
||||
}
|
||||
|
||||
return GST_PAD_PROBE_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
gboolean SongLoader::BusCallback(GstBus *, GstMessage *msg, gpointer self) {
|
||||
|
||||
SongLoader *instance = reinterpret_cast<SongLoader*>(self);
|
||||
|
||||
switch (GST_MESSAGE_TYPE(msg)) {
|
||||
case GST_MESSAGE_ERROR:
|
||||
instance->ErrorMessageReceived(msg);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
GstBusSyncReply SongLoader::BusCallbackSync(GstBus *, GstMessage *msg, gpointer self) {
|
||||
|
||||
SongLoader *instance = reinterpret_cast<SongLoader*>(self);
|
||||
|
||||
switch (GST_MESSAGE_TYPE(msg)) {
|
||||
case GST_MESSAGE_EOS:
|
||||
instance->EndOfStreamReached();
|
||||
break;
|
||||
|
||||
case GST_MESSAGE_ERROR:
|
||||
instance->ErrorMessageReceived(msg);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return GST_BUS_PASS;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void SongLoader::ErrorMessageReceived(GstMessage *msg) {
|
||||
|
||||
if (state_ == Finished) return;
|
||||
|
||||
GError *error;
|
||||
gchar *debugs;
|
||||
|
||||
gst_message_parse_error(msg, &error, &debugs);
|
||||
qLog(Error) << __PRETTY_FUNCTION__ << error->message;
|
||||
qLog(Error) << __PRETTY_FUNCTION__ << debugs;
|
||||
|
||||
QString message_str = error->message;
|
||||
|
||||
g_error_free(error);
|
||||
free(debugs);
|
||||
|
||||
if (state_ == WaitingForType && message_str == gst_error_get_message(GST_STREAM_ERROR, GST_STREAM_ERROR_TYPE_NOT_FOUND)) {
|
||||
// Don't give up - assume it's a playlist and see if one of our parsers can
|
||||
// read it.
|
||||
state_ = WaitingForMagic;
|
||||
return;
|
||||
}
|
||||
|
||||
StopTypefindAsync(false);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void SongLoader::EndOfStreamReached() {
|
||||
|
||||
qLog(Debug) << Q_FUNC_INFO << state_;
|
||||
switch (state_) {
|
||||
case Finished:
|
||||
break;
|
||||
|
||||
case WaitingForMagic:
|
||||
// Do the magic on the data we have already
|
||||
MagicReady();
|
||||
if (state_ == Finished) break;
|
||||
// It looks like a playlist, so parse it
|
||||
|
||||
// fallthrough
|
||||
case WaitingForData:
|
||||
// It's a playlist and we've got all the data - finish and parse it
|
||||
StopTypefindAsync(true);
|
||||
break;
|
||||
|
||||
case WaitingForType:
|
||||
StopTypefindAsync(false);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void SongLoader::MagicReady() {
|
||||
|
||||
qLog(Debug) << Q_FUNC_INFO;
|
||||
parser_ = playlist_parser_->ParserForMagic(buffer_, mime_type_);
|
||||
|
||||
if (!parser_) {
|
||||
qLog(Warning) << url_.toString() << "is text, but not a recognised playlist";
|
||||
// It doesn't look like a playlist, so just finish
|
||||
StopTypefindAsync(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// We'll get more data and parse the whole thing in EndOfStreamReached
|
||||
|
||||
qLog(Debug) << "Magic says" << parser_->name();
|
||||
if (parser_->name() == "ASX/INI" && url_.scheme() == "http") {
|
||||
// This is actually a weird MS-WMSP stream. Changing the protocol to MMS from
|
||||
// HTTP makes it playable.
|
||||
parser_ = nullptr;
|
||||
url_.setScheme("mms");
|
||||
StopTypefindAsync(true);
|
||||
}
|
||||
|
||||
state_ = WaitingForData;
|
||||
|
||||
if (!IsPipelinePlaying()) {
|
||||
EndOfStreamReached();
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
bool SongLoader::IsPipelinePlaying() {
|
||||
|
||||
GstState state = GST_STATE_NULL;
|
||||
GstState pending_state = GST_STATE_NULL;
|
||||
GstStateChangeReturn ret = gst_element_get_state(pipeline_.get(), &state, &pending_state, GST_SECOND);
|
||||
|
||||
if (ret == GST_STATE_CHANGE_ASYNC && pending_state == GST_STATE_PLAYING) {
|
||||
// We're still on the way to playing
|
||||
return true;
|
||||
}
|
||||
return state == GST_STATE_PLAYING;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void SongLoader::StopTypefindAsync(bool success) {
|
||||
|
||||
state_ = Finished;
|
||||
success_ = success;
|
||||
|
||||
metaObject()->invokeMethod(this, "StopTypefind", Qt::QueuedConnection);
|
||||
|
||||
}
|
||||
#endif
|
||||
152
src/core/songloader.h
Normal file
152
src/core/songloader.h
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SONGLOADER_H
|
||||
#define SONGLOADER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
#include <gst/gst.h>
|
||||
#endif
|
||||
|
||||
#include <QObject>
|
||||
#include <QThreadPool>
|
||||
#include <QUrl>
|
||||
|
||||
#include "song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "musicbrainz/musicbrainzclient.h"
|
||||
|
||||
class CueParser;
|
||||
class CollectionBackendInterface;
|
||||
class ParserBase;
|
||||
class Player;
|
||||
class PlaylistParser;
|
||||
#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER)
|
||||
class CddaSongLoader;
|
||||
#endif
|
||||
|
||||
class SongLoader : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SongLoader(CollectionBackendInterface *collection, const Player *player, QObject *parent = nullptr);
|
||||
~SongLoader();
|
||||
|
||||
enum Result {
|
||||
Success,
|
||||
Error,
|
||||
BlockingLoadRequired,
|
||||
};
|
||||
|
||||
static const int kDefaultTimeout;
|
||||
|
||||
const QUrl &url() const { return url_; }
|
||||
const SongList &songs() const { return songs_; }
|
||||
|
||||
int timeout() const { return timeout_; }
|
||||
void set_timeout(int msec) { timeout_ = msec; }
|
||||
|
||||
// If Success is returned the songs are fully loaded. If BlockingLoadRequired
|
||||
// is returned LoadFilenamesBlocking() needs to be called next.
|
||||
Result Load(const QUrl &url);
|
||||
// Loads the files with only filenames. When finished, songs() contains a
|
||||
// complete list of all Song objects, but without metadata. This method is
|
||||
// blocking, do not call it from the UI thread.
|
||||
void LoadFilenamesBlocking();
|
||||
// Completely load songs previously loaded with LoadFilenamesBlocking(). When
|
||||
// finished, the Song objects in songs() contain metadata now. This method is
|
||||
// blocking, do not call it from the UI thread.
|
||||
void LoadMetadataBlocking();
|
||||
Result LoadAudioCD();
|
||||
|
||||
signals:
|
||||
void AudioCDTracksLoaded();
|
||||
void LoadAudioCDFinished(bool success);
|
||||
void LoadRemoteFinished();
|
||||
|
||||
private slots:
|
||||
void Timeout();
|
||||
void StopTypefind();
|
||||
#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER)
|
||||
void AudioCDTracksLoadedSlot(const SongList &songs);
|
||||
void AudioCDTracksTagsLoaded(const SongList &songs);
|
||||
#endif // HAVE_AUDIOCD
|
||||
|
||||
private:
|
||||
enum State { WaitingForType, WaitingForMagic, WaitingForData, Finished };
|
||||
|
||||
Result LoadLocal(const QString &filename);
|
||||
void LoadLocalAsync(const QString &filename);
|
||||
void EffectiveSongLoad(Song *song);
|
||||
Result LoadLocalPartial(const QString &filename);
|
||||
void LoadLocalDirectory(const QString &filename);
|
||||
void LoadPlaylist(ParserBase *parser, const QString &filename);
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
void LoadRemote();
|
||||
|
||||
// GStreamer callbacks
|
||||
static void TypeFound(GstElement *typefind, uint probability, GstCaps *caps, void *self);
|
||||
static GstPadProbeReturn DataReady(GstPad*, GstPadProbeInfo *buf, gpointer self);
|
||||
static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage*, gpointer);
|
||||
static gboolean BusCallback(GstBus*, GstMessage*, gpointer);
|
||||
|
||||
void StopTypefindAsync(bool success);
|
||||
void ErrorMessageReceived(GstMessage *msg);
|
||||
void EndOfStreamReached();
|
||||
void MagicReady();
|
||||
bool IsPipelinePlaying();
|
||||
|
||||
#endif
|
||||
|
||||
private:
|
||||
static QSet<QString> sRawUriSchemes;
|
||||
|
||||
QUrl url_;
|
||||
SongList songs_;
|
||||
|
||||
QTimer *timeout_timer_;
|
||||
PlaylistParser *playlist_parser_;
|
||||
CueParser *cue_parser_;
|
||||
|
||||
// For async loads
|
||||
std::function<void()> preload_func_;
|
||||
int timeout_;
|
||||
State state_;
|
||||
bool success_;
|
||||
ParserBase *parser_;
|
||||
QString mime_type_;
|
||||
QByteArray buffer_;
|
||||
CollectionBackendInterface *collection_;
|
||||
const Player *player_;
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
std::shared_ptr<GstElement> pipeline_;
|
||||
#endif
|
||||
|
||||
QThreadPool thread_pool_;
|
||||
};
|
||||
|
||||
#endif // SONGLOADER_H
|
||||
|
||||
98
src/core/standarditemiconloader.cpp
Normal file
98
src/core/standarditemiconloader.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QSet>
|
||||
#include <QStandardItem>
|
||||
|
||||
#include "standarditemiconloader.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
|
||||
StandardItemIconLoader::StandardItemIconLoader(AlbumCoverLoader *cover_loader, QObject *parent)
|
||||
: QObject(parent),
|
||||
cover_loader_(cover_loader),
|
||||
model_(nullptr)
|
||||
{
|
||||
cover_options_.desired_height_ = 16;
|
||||
|
||||
connect(cover_loader_, SIGNAL(ImageLoaded(quint64,QImage)), SLOT(ImageLoaded(quint64,QImage)));
|
||||
}
|
||||
|
||||
void StandardItemIconLoader::SetModel(QAbstractItemModel *model) {
|
||||
|
||||
if (model_) {
|
||||
disconnect(model_, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(RowsAboutToBeRemoved(QModelIndex,int,int)));
|
||||
}
|
||||
|
||||
model_ = model;
|
||||
|
||||
connect(model_, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), SLOT(RowsAboutToBeRemoved(QModelIndex,int,int)));
|
||||
connect(model_, SIGNAL(modelAboutToBeReset()), SLOT(ModelReset()));
|
||||
|
||||
}
|
||||
|
||||
void StandardItemIconLoader::LoadIcon(const QString& art_automatic, const QString& art_manual, QStandardItem *for_item) {
|
||||
|
||||
const quint64 id = cover_loader_->LoadImageAsync(cover_options_, art_automatic, art_manual);
|
||||
pending_covers_[id] = for_item;
|
||||
|
||||
}
|
||||
|
||||
void StandardItemIconLoader::LoadIcon(const Song& song, QStandardItem *for_item) {
|
||||
const quint64 id = cover_loader_->LoadImageAsync(cover_options_, song);
|
||||
pending_covers_[id] = for_item;
|
||||
}
|
||||
|
||||
void StandardItemIconLoader::RowsAboutToBeRemoved(const QModelIndex& parent, int begin, int end) {
|
||||
|
||||
for (QMap<quint64, QStandardItem*>::iterator it = pending_covers_.begin() ; it != pending_covers_.end() ; ) {
|
||||
const QStandardItem *item = it.value();
|
||||
const QStandardItem *item_parent = item->parent();
|
||||
|
||||
if (item_parent && item_parent->index() == parent && item->index().row() >= begin && item->index().row() <= end) {
|
||||
cover_loader_->CancelTask(it.key());
|
||||
it = pending_covers_.erase(it);
|
||||
}
|
||||
else {
|
||||
++ it;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StandardItemIconLoader::ModelReset() {
|
||||
|
||||
cover_loader_->CancelTasks(QSet<quint64>::fromList(pending_covers_.keys()));
|
||||
pending_covers_.clear();
|
||||
|
||||
}
|
||||
|
||||
void StandardItemIconLoader::ImageLoaded(quint64 id, const QImage& image) {
|
||||
|
||||
QStandardItem *item = pending_covers_.take(id);
|
||||
if (!item) return;
|
||||
|
||||
if (!image.isNull()) {
|
||||
item->setIcon(QIcon(QPixmap::fromImage(image)));
|
||||
}
|
||||
|
||||
}
|
||||
67
src/core/standarditemiconloader.h
Normal file
67
src/core/standarditemiconloader.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef STANDARDITEMICONLOADER_H
|
||||
#define STANDARDITEMICONLOADER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
|
||||
class AlbumCoverLoader;
|
||||
class Song;
|
||||
|
||||
class QAbstractItemModel;
|
||||
class QModelIndex;
|
||||
class QStandardItem;
|
||||
|
||||
// Uses an AlbumCoverLoader to asynchronously load and set an icon on a QStandardItem.
|
||||
class StandardItemIconLoader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
StandardItemIconLoader(AlbumCoverLoader *cover_loader, QObject *parent = nullptr);
|
||||
|
||||
AlbumCoverLoaderOptions *options() { return &cover_options_; }
|
||||
|
||||
void SetModel(QAbstractItemModel *model);
|
||||
|
||||
void LoadIcon(const QString &art_automatic, const QString &art_manual,
|
||||
QStandardItem *for_item);
|
||||
void LoadIcon(const Song &song, QStandardItem *for_item);
|
||||
|
||||
private slots:
|
||||
void ImageLoaded(quint64 id, const QImage &image);
|
||||
void RowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end);
|
||||
void ModelReset();
|
||||
|
||||
private:
|
||||
AlbumCoverLoader *cover_loader_;
|
||||
AlbumCoverLoaderOptions cover_options_;
|
||||
|
||||
QAbstractItemModel *model_;
|
||||
|
||||
QMap<quint64, QStandardItem*> pending_covers_;
|
||||
};
|
||||
|
||||
#endif // STANDARDITEMICONLOADER_H
|
||||
110
src/core/stylesheetloader.cpp
Normal file
110
src/core/stylesheetloader.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "stylesheetloader.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QEvent>
|
||||
|
||||
StyleSheetLoader::StyleSheetLoader(QObject *parent) : QObject(parent) {}
|
||||
|
||||
void StyleSheetLoader::SetStyleSheet(QWidget *widget, const QString &filename) {
|
||||
|
||||
filenames_[widget] = filename;
|
||||
widget->installEventFilter(this);
|
||||
UpdateStyleSheet(widget);
|
||||
|
||||
}
|
||||
|
||||
void StyleSheetLoader::UpdateStyleSheet(QWidget *widget) {
|
||||
|
||||
QString filename(filenames_[widget]);
|
||||
|
||||
// Load the file
|
||||
QFile file(filename);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qLog(Warning) << "error opening" << filename;
|
||||
return;
|
||||
}
|
||||
QString contents(file.readAll());
|
||||
|
||||
// Replace %palette-role with actual colours
|
||||
QPalette p(widget->palette());
|
||||
|
||||
QColor alt = p.color(QPalette::AlternateBase);
|
||||
alt.setAlpha(50);
|
||||
contents.replace("%palette-alternate-base", QString("rgba(%1,%2,%3,%4%)")
|
||||
.arg(alt.red())
|
||||
.arg(alt.green())
|
||||
.arg(alt.blue())
|
||||
.arg(alt.alpha()));
|
||||
|
||||
ReplaceColor(&contents, "Window", p, QPalette::Window);
|
||||
ReplaceColor(&contents, "Background", p, QPalette::Background);
|
||||
ReplaceColor(&contents, "WindowText", p, QPalette::WindowText);
|
||||
ReplaceColor(&contents, "Foreground", p, QPalette::Foreground);
|
||||
ReplaceColor(&contents, "Base", p, QPalette::Base);
|
||||
ReplaceColor(&contents, "AlternateBase", p, QPalette::AlternateBase);
|
||||
ReplaceColor(&contents, "ToolTipBase", p, QPalette::ToolTipBase);
|
||||
ReplaceColor(&contents, "ToolTipText", p, QPalette::ToolTipText);
|
||||
ReplaceColor(&contents, "Text", p, QPalette::Text);
|
||||
ReplaceColor(&contents, "Button", p, QPalette::Button);
|
||||
ReplaceColor(&contents, "ButtonText", p, QPalette::ButtonText);
|
||||
ReplaceColor(&contents, "BrightText", p, QPalette::BrightText);
|
||||
ReplaceColor(&contents, "Light", p, QPalette::Light);
|
||||
ReplaceColor(&contents, "Midlight", p, QPalette::Midlight);
|
||||
ReplaceColor(&contents, "Dark", p, QPalette::Dark);
|
||||
ReplaceColor(&contents, "Mid", p, QPalette::Mid);
|
||||
ReplaceColor(&contents, "Shadow", p, QPalette::Shadow);
|
||||
ReplaceColor(&contents, "Highlight", p, QPalette::Highlight);
|
||||
ReplaceColor(&contents, "HighlightedText", p, QPalette::HighlightedText);
|
||||
ReplaceColor(&contents, "Link", p, QPalette::Link);
|
||||
ReplaceColor(&contents, "LinkVisited", p, QPalette::LinkVisited);
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
contents.replace("darwin", "*");
|
||||
#endif
|
||||
|
||||
widget->setStyleSheet(contents);
|
||||
|
||||
}
|
||||
|
||||
void StyleSheetLoader::ReplaceColor(QString *css, const QString &name, const QPalette &palette, QPalette::ColorRole role) const {
|
||||
|
||||
css->replace("%palette-" + name + "-lighter", palette.color(role).lighter().name(), Qt::CaseInsensitive);
|
||||
css->replace("%palette-" + name + "-darker", palette.color(role).darker().name(), Qt::CaseInsensitive);
|
||||
css->replace("%palette-" + name, palette.color(role).name(), Qt::CaseInsensitive);
|
||||
|
||||
}
|
||||
|
||||
bool StyleSheetLoader::eventFilter(QObject *obj, QEvent *event) {
|
||||
|
||||
if (event->type() != QEvent::PaletteChange) return false;
|
||||
|
||||
QWidget *widget = qobject_cast<QWidget*>(obj);
|
||||
if (!widget || !filenames_.contains(widget)) return false;
|
||||
|
||||
UpdateStyleSheet(widget);
|
||||
return false;
|
||||
|
||||
}
|
||||
53
src/core/stylesheetloader.h
Normal file
53
src/core/stylesheetloader.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef STYLESHEETLOADER_H
|
||||
#define STYLESHEETLOADER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QPalette>
|
||||
#include <QWidget>
|
||||
#include <QMap>
|
||||
|
||||
class StyleSheetLoader : public QObject {
|
||||
public:
|
||||
explicit StyleSheetLoader(QObject *parent = nullptr);
|
||||
|
||||
// Sets the given stylesheet on the given widget.
|
||||
// If the stylesheet contains strings like %palette-[role], these get replaced
|
||||
// with actual palette colours.
|
||||
// The stylesheet is reloaded when the widget's palette changes.
|
||||
void SetStyleSheet(QWidget *widget, const QString& filename);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
|
||||
private:
|
||||
void UpdateStyleSheet(QWidget *widget);
|
||||
void ReplaceColor(QString *css, const QString& name, const QPalette& palette, QPalette::ColorRole role) const;
|
||||
|
||||
private:
|
||||
QMap<QWidget *, QString> filenames_;
|
||||
};
|
||||
|
||||
#endif // STYLESHEETLOADER_H
|
||||
|
||||
111
src/core/systemtrayicon.cpp
Normal file
111
src/core/systemtrayicon.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QEvent>
|
||||
#include <QWheelEvent>
|
||||
#include <QPainter>
|
||||
#include <QWidget>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "macsystemtrayicon.h"
|
||||
#include "qtsystemtrayicon.h"
|
||||
#include "systemtrayicon.h"
|
||||
|
||||
SystemTrayIcon::SystemTrayIcon(QObject *parent)
|
||||
: QObject(parent),
|
||||
percentage_(0),
|
||||
playing_icon_(":/pictures/tiny-play.png"),
|
||||
paused_icon_(":/pictures/tiny-pause.png")
|
||||
{
|
||||
}
|
||||
|
||||
QPixmap SystemTrayIcon::CreateIcon(const QPixmap &icon, const QPixmap &grey_icon) {
|
||||
|
||||
QRect rect(icon.rect());
|
||||
|
||||
// The angle of the line that's used to cover the icon.
|
||||
// Centered on rect.topRight()
|
||||
double angle = double(100 - song_progress()) / 100.0 * M_PI_2 + M_PI;
|
||||
double length = sqrt(pow(rect.width(), 2.0) + pow(rect.height(), 2.0));
|
||||
|
||||
QPolygon mask;
|
||||
mask << rect.topRight();
|
||||
mask << rect.topRight() + QPoint(length * sin(angle), -length * cos(angle));
|
||||
|
||||
if (song_progress() > 50) mask << rect.bottomLeft();
|
||||
|
||||
mask << rect.topLeft();
|
||||
mask << rect.topRight();
|
||||
|
||||
QPixmap ret(icon);
|
||||
QPainter p(&ret);
|
||||
|
||||
// Draw the grey bit
|
||||
//p.setClipRegion(mask);
|
||||
//p.drawPixmap(0, 0, grey_icon);
|
||||
//p.setClipping(false);
|
||||
|
||||
// Draw the playing or paused icon in the top-right
|
||||
if (!current_state_icon().isNull()) {
|
||||
int height = rect.height() / 2;
|
||||
QPixmap scaled(current_state_icon().scaledToHeight(height, Qt::SmoothTransformation));
|
||||
|
||||
QRect state_rect(rect.width() - scaled.width(), 0, scaled.width(), scaled.height());
|
||||
p.drawPixmap(state_rect, scaled);
|
||||
}
|
||||
|
||||
p.end();
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void SystemTrayIcon::SetProgress(int percentage) {
|
||||
percentage_ = percentage;
|
||||
UpdateIcon();
|
||||
}
|
||||
|
||||
void SystemTrayIcon::SetPaused() {
|
||||
current_state_icon_ = paused_icon_;
|
||||
UpdateIcon();
|
||||
}
|
||||
|
||||
void SystemTrayIcon::SetPlaying(bool enable_play_pause) {
|
||||
current_state_icon_ = playing_icon_;
|
||||
UpdateIcon();
|
||||
}
|
||||
|
||||
void SystemTrayIcon::SetStopped() {
|
||||
current_state_icon_ = QPixmap();
|
||||
UpdateIcon();
|
||||
}
|
||||
|
||||
SystemTrayIcon* SystemTrayIcon::CreateSystemTrayIcon(QObject *parent) {
|
||||
#ifdef Q_OS_DARWIN
|
||||
return new MacSystemTrayIcon(parent);
|
||||
#else
|
||||
return new QtSystemTrayIcon(parent);
|
||||
#endif
|
||||
}
|
||||
85
src/core/systemtrayicon.h
Normal file
85
src/core/systemtrayicon.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SYSTEMTRAYICON_H
|
||||
#define SYSTEMTRAYICON_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QPixmap>
|
||||
|
||||
class QAction;
|
||||
class Song;
|
||||
|
||||
class SystemTrayIcon : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SystemTrayIcon(QObject *parent = nullptr);
|
||||
|
||||
// Called once to create the icon's context menu
|
||||
virtual void SetupMenu(QAction *previous, QAction *play, QAction *stop, QAction *stop_after, QAction *next, QAction *mute, QAction *quit) = 0;
|
||||
|
||||
virtual bool IsVisible() const { return true; }
|
||||
virtual void SetVisible(bool visible) {}
|
||||
|
||||
// Called by the OSD
|
||||
virtual void ShowPopup(const QString &summary, const QString &message, int timeout) {}
|
||||
/**
|
||||
* If this get's invoked with image_path equal to nullptr, the tooltip should
|
||||
* still be shown - just without the cover art.
|
||||
*/
|
||||
virtual void SetNowPlaying(const Song &song, const QString &image_path) {}
|
||||
virtual void ClearNowPlaying() {}
|
||||
|
||||
static SystemTrayIcon *CreateSystemTrayIcon(QObject *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void SetProgress(int percentage);
|
||||
virtual void SetPaused();
|
||||
virtual void SetPlaying(bool enable_play_pause = false);
|
||||
virtual void SetStopped();
|
||||
virtual void MuteButtonStateChanged(bool value) {}
|
||||
|
||||
signals:
|
||||
void ChangeVolume(int delta);
|
||||
void SeekForward();
|
||||
void SeekBackward();
|
||||
void NextTrack();
|
||||
void PreviousTrack();
|
||||
void ShowHide();
|
||||
void PlayPause();
|
||||
|
||||
protected:
|
||||
virtual void UpdateIcon() = 0;
|
||||
QPixmap CreateIcon(const QPixmap &icon, const QPixmap &grey_icon);
|
||||
|
||||
int song_progress() const { return percentage_; }
|
||||
QPixmap current_state_icon() const { return current_state_icon_; }
|
||||
|
||||
private:
|
||||
int percentage_;
|
||||
QPixmap playing_icon_;
|
||||
QPixmap paused_icon_;
|
||||
QPixmap current_state_icon_;
|
||||
};
|
||||
|
||||
#endif // SYSTEMTRAYICON_H
|
||||
155
src/core/tagreaderclient.cpp
Normal file
155
src/core/tagreaderclient.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "tagreaderclient.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QFile>
|
||||
#include <QProcess>
|
||||
#include <QTcpServer>
|
||||
#include <QThread>
|
||||
#include <QUrl>
|
||||
|
||||
const char *TagReaderClient::kWorkerExecutableName = "strawberry-tagreader";
|
||||
TagReaderClient *TagReaderClient::sInstance = nullptr;
|
||||
|
||||
TagReaderClient::TagReaderClient(QObject *parent) : QObject(parent), worker_pool_(new WorkerPool<HandlerType>(this)) {
|
||||
|
||||
sInstance = this;
|
||||
|
||||
worker_pool_->SetExecutableName(kWorkerExecutableName);
|
||||
worker_pool_->SetWorkerCount(QThread::idealThreadCount());
|
||||
connect(worker_pool_, SIGNAL(WorkerFailedToStart()), SLOT(WorkerFailedToStart()));
|
||||
}
|
||||
|
||||
void TagReaderClient::Start() { worker_pool_->Start(); }
|
||||
|
||||
void TagReaderClient::WorkerFailedToStart() {
|
||||
qLog(Error) << "The" << kWorkerExecutableName << "executable was not found in the current directory or on the PATH. Strawberry will not be able to read music file tags without it.";
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::ReadFile(const QString &filename) {
|
||||
|
||||
pb::tagreader::Message message;
|
||||
pb::tagreader::ReadFileRequest *req = message.mutable_read_file_request();
|
||||
|
||||
req->set_filename(DataCommaSizeFromQString(filename));
|
||||
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::SaveFile(const QString &filename, const Song &metadata) {
|
||||
|
||||
pb::tagreader::Message message;
|
||||
pb::tagreader::SaveFileRequest *req = message.mutable_save_file_request();
|
||||
|
||||
req->set_filename(DataCommaSizeFromQString(filename));
|
||||
metadata.ToProtobuf(req->mutable_metadata());
|
||||
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::IsMediaFile(const QString &filename) {
|
||||
|
||||
pb::tagreader::Message message;
|
||||
pb::tagreader::IsMediaFileRequest *req = message.mutable_is_media_file_request();
|
||||
|
||||
req->set_filename(DataCommaSizeFromQString(filename));
|
||||
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::LoadEmbeddedArt(const QString &filename) {
|
||||
|
||||
pb::tagreader::Message message;
|
||||
pb::tagreader::LoadEmbeddedArtRequest *req = message.mutable_load_embedded_art_request();
|
||||
|
||||
req->set_filename(DataCommaSizeFromQString(filename));
|
||||
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::ReadFileBlocking(const QString &filename, Song *song) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
TagReaderReply *reply = ReadFile(filename);
|
||||
if (reply->WaitForFinished()) {
|
||||
song->InitFromProtobuf(reply->message().read_file_response().metadata());
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
}
|
||||
|
||||
bool TagReaderClient::SaveFileBlocking(const QString &filename, const Song &metadata) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
bool ret = false;
|
||||
|
||||
TagReaderReply *reply = SaveFile(filename, metadata);
|
||||
if (reply->WaitForFinished()) {
|
||||
ret = reply->message().save_file_response().success();
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
bool TagReaderClient::IsMediaFileBlocking(const QString &filename) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
bool ret = false;
|
||||
|
||||
TagReaderReply *reply = IsMediaFile(filename);
|
||||
if (reply->WaitForFinished()) {
|
||||
ret = reply->message().is_media_file_response().success();
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
QImage TagReaderClient::LoadEmbeddedArtBlocking(const QString &filename) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
QImage ret;
|
||||
|
||||
TagReaderReply *reply = LoadEmbeddedArt(filename);
|
||||
if (reply->WaitForFinished()) {
|
||||
const std::string &data_str = reply->message().load_embedded_art_response().data();
|
||||
ret.loadFromData(QByteArray(data_str.data(), data_str.size()));
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
79
src/core/tagreaderclient.h
Normal file
79
src/core/tagreaderclient.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERCLIENT_H
|
||||
#define TAGREADERCLIENT_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "song.h"
|
||||
#include "tagreadermessages.pb.h"
|
||||
#include "core/messagehandler.h"
|
||||
#include "core/workerpool.h"
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
class QLocalServer;
|
||||
class QProcess;
|
||||
|
||||
class TagReaderClient : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TagReaderClient(QObject *parent = nullptr);
|
||||
|
||||
typedef AbstractMessageHandler<pb::tagreader::Message> HandlerType;
|
||||
typedef HandlerType::ReplyType ReplyType;
|
||||
|
||||
static const char *kWorkerExecutableName;
|
||||
|
||||
void Start();
|
||||
|
||||
ReplyType *ReadFile(const QString &filename);
|
||||
ReplyType *SaveFile(const QString &filename, const Song &metadata);
|
||||
ReplyType *IsMediaFile(const QString &filename);
|
||||
ReplyType *LoadEmbeddedArt(const QString &filename);
|
||||
|
||||
// Convenience functions that call the above functions and wait for a
|
||||
// response. These block the calling thread with a semaphore, and must NOT
|
||||
// be called from the TagReaderClient's thread.
|
||||
void ReadFileBlocking(const QString &filename, Song *song);
|
||||
bool SaveFileBlocking(const QString &filename, const Song &metadata);
|
||||
bool IsMediaFileBlocking(const QString &filename);
|
||||
QImage LoadEmbeddedArtBlocking(const QString &filename);
|
||||
|
||||
// TODO: Make this not a singleton
|
||||
static TagReaderClient *Instance() { return sInstance; }
|
||||
|
||||
public slots:
|
||||
|
||||
private slots:
|
||||
void WorkerFailedToStart();
|
||||
|
||||
private:
|
||||
static TagReaderClient *sInstance;
|
||||
|
||||
WorkerPool<HandlerType> *worker_pool_;
|
||||
QList<pb::tagreader::Message> message_queue_;
|
||||
};
|
||||
|
||||
typedef TagReaderClient::ReplyType TagReaderReply;
|
||||
|
||||
#endif // TAGREADERCLIENT_H
|
||||
138
src/core/taskmanager.cpp
Normal file
138
src/core/taskmanager.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "taskmanager.h"
|
||||
|
||||
TaskManager::TaskManager(QObject *parent) : QObject(parent), next_task_id_(1) {}
|
||||
|
||||
int TaskManager::StartTask(const QString &name) {
|
||||
|
||||
Task t;
|
||||
t.name = name;
|
||||
t.progress = 0;
|
||||
t.progress_max = 0;
|
||||
t.blocks_collection_scans = false;
|
||||
|
||||
{
|
||||
QMutexLocker l(&mutex_);
|
||||
t.id = next_task_id_++;
|
||||
tasks_[t.id] = t;
|
||||
}
|
||||
|
||||
emit TasksChanged();
|
||||
return t.id;
|
||||
|
||||
}
|
||||
|
||||
QList<TaskManager::Task> TaskManager::GetTasks() {
|
||||
|
||||
QList<TaskManager::Task> ret;
|
||||
|
||||
{
|
||||
QMutexLocker l(&mutex_);
|
||||
ret = tasks_.values();
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void TaskManager::SetTaskBlocksCollectionScans(int id) {
|
||||
|
||||
{
|
||||
QMutexLocker l(&mutex_);
|
||||
if (!tasks_.contains(id)) return;
|
||||
|
||||
Task &t = tasks_[id];
|
||||
t.blocks_collection_scans = true;
|
||||
}
|
||||
|
||||
emit TasksChanged();
|
||||
emit PauseCollectionWatchers();
|
||||
|
||||
}
|
||||
|
||||
void TaskManager::SetTaskProgress(int id, int progress, int max) {
|
||||
|
||||
{
|
||||
QMutexLocker l(&mutex_);
|
||||
if (!tasks_.contains(id)) return;
|
||||
|
||||
Task &t = tasks_[id];
|
||||
t.progress = progress;
|
||||
if (max) t.progress_max = max;
|
||||
}
|
||||
|
||||
emit TasksChanged();
|
||||
}
|
||||
|
||||
void TaskManager::IncreaseTaskProgress(int id, int progress, int max) {
|
||||
|
||||
{
|
||||
QMutexLocker l(&mutex_);
|
||||
if (!tasks_.contains(id)) return;
|
||||
|
||||
Task &t = tasks_[id];
|
||||
t.progress += progress;
|
||||
if (max) t.progress_max = max;
|
||||
}
|
||||
|
||||
emit TasksChanged();
|
||||
|
||||
}
|
||||
|
||||
void TaskManager::SetTaskFinished(int id) {
|
||||
|
||||
bool resume_collection_watchers = false;
|
||||
|
||||
{
|
||||
QMutexLocker l(&mutex_);
|
||||
if (!tasks_.contains(id)) return;
|
||||
|
||||
if (tasks_[id].blocks_collection_scans) {
|
||||
resume_collection_watchers = true;
|
||||
for (const Task &task : tasks_.values()) {
|
||||
if (task.id != id && task.blocks_collection_scans) {
|
||||
resume_collection_watchers = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks_.remove(id);
|
||||
}
|
||||
|
||||
emit TasksChanged();
|
||||
if (resume_collection_watchers) emit ResumeCollectionWatchers();
|
||||
|
||||
}
|
||||
|
||||
int TaskManager::GetTaskProgress(int id) {
|
||||
|
||||
{
|
||||
QMutexLocker l(&mutex_);
|
||||
if (!tasks_.contains(id)) return 0;
|
||||
return tasks_[id].progress;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
82
src/core/taskmanager.h
Normal file
82
src/core/taskmanager.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TASKMANAGER_H
|
||||
#define TASKMANAGER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
|
||||
class TaskManager : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TaskManager(QObject *parent = nullptr);
|
||||
|
||||
struct Task {
|
||||
int id;
|
||||
QString name;
|
||||
int progress;
|
||||
int progress_max;
|
||||
bool blocks_collection_scans;
|
||||
};
|
||||
|
||||
class ScopedTask {
|
||||
public:
|
||||
ScopedTask(const int task_id, TaskManager *task_manager) : task_id_(task_id), task_manager_(task_manager) {}
|
||||
|
||||
~ScopedTask() { task_manager_->SetTaskFinished(task_id_); }
|
||||
|
||||
private:
|
||||
const int task_id_;
|
||||
TaskManager *task_manager_;
|
||||
|
||||
Q_DISABLE_COPY(ScopedTask);
|
||||
};
|
||||
|
||||
// Everything here is thread safe
|
||||
QList<Task> GetTasks();
|
||||
|
||||
int StartTask(const QString &name);
|
||||
void SetTaskBlocksCollectionScans(int id);
|
||||
void SetTaskProgress(int id, int progress, int max = 0);
|
||||
void IncreaseTaskProgress(int id, int progress, int max = 0);
|
||||
void SetTaskFinished(int id);
|
||||
int GetTaskProgress(int id);
|
||||
|
||||
signals:
|
||||
void TasksChanged();
|
||||
|
||||
void PauseCollectionWatchers();
|
||||
void ResumeCollectionWatchers();
|
||||
|
||||
private:
|
||||
QMutex mutex_;
|
||||
QMap<int, Task> tasks_;
|
||||
int next_task_id_;
|
||||
|
||||
Q_DISABLE_COPY(TaskManager);
|
||||
};
|
||||
|
||||
#endif // TASKMANAGER_H
|
||||
|
||||
25
src/core/thread.cpp
Normal file
25
src/core/thread.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2015, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "thread.h"
|
||||
|
||||
void Thread::run() {
|
||||
Utilities::SetThreadIOPriority(io_priority_);
|
||||
QThread::run();
|
||||
}
|
||||
41
src/core/thread.h
Normal file
41
src/core/thread.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2015, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CORE_THREAD_H_
|
||||
#define CORE_THREAD_H_
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QThread>
|
||||
#include "core/utilities.h"
|
||||
|
||||
// Improve QThread by adding a SetIoPriority function
|
||||
class Thread : public QThread {
|
||||
public:
|
||||
Thread(QObject* parent = nullptr)
|
||||
: QThread(parent), io_priority_(Utilities::IOPRIO_CLASS_NONE) {}
|
||||
|
||||
void SetIoPriority(Utilities::IoPriority priority) {
|
||||
io_priority_ = priority;
|
||||
}
|
||||
virtual void run() override;
|
||||
|
||||
private:
|
||||
Utilities::IoPriority io_priority_;
|
||||
};
|
||||
|
||||
#endif // CORE_THREAD_H_
|
||||
32
src/core/timeconstants.h
Normal file
32
src/core/timeconstants.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/* This file was part of Clementine.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TIMECONSTANTS_H
|
||||
#define TIMECONSTANTS_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
// Use these to convert between time units
|
||||
const qint64 kMsecPerSec = 1000ll;
|
||||
const qint64 kUsecPerMsec = 1000ll;
|
||||
const qint64 kUsecPerSec = 1000000ll;
|
||||
const qint64 kNsecPerUsec = 1000ll;
|
||||
const qint64 kNsecPerMsec = 1000000ll;
|
||||
const qint64 kNsecPerSec = 1000000000ll;
|
||||
|
||||
const qint64 kSecsPerDay = 24 * 60 * 60;
|
||||
|
||||
#endif // TIMECONSTANTS_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user