diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3da967033..fd6771ac4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -327,6 +327,7 @@ set(HEADERS radio/savedradio.h radio/somafmservice.h + scripting/languageengine.h scripting/scriptdialog.h scripting/scriptdialogtabwidget.h scripting/scriptinterface.h @@ -733,16 +734,23 @@ if(HAVE_SCRIPTING_PYTHON) scripting/python/pythonscript.cpp ) + list(APPEND HEADERS + scripting/python/pythonengine.h + ) + add_sip_binding(SOURCES scripting/python/clementine.sip ${CMAKE_CURRENT_BINARY_DIR}/sipclementineLibraryBackendAlbum.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementinePlaylistItemOptions.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementinePlaylistItemPtr.cpp + ${CMAKE_CURRENT_BINARY_DIR}/sipclementinePlaylistItemSpecialLoadResult.cpp + ${CMAKE_CURRENT_BINARY_DIR}/sipclementineTaskManagerTask.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100Directory.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100LibraryBackendAlbum.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100PlaylistItemPtr.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100Song.cpp ${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100Subdirectory.cpp + ${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100TaskManagerTask.cpp ) endif(HAVE_SCRIPTING_PYTHON) diff --git a/src/radio/radiomodel.cpp b/src/radio/radiomodel.cpp index f026036bf..f9d401189 100644 --- a/src/radio/radiomodel.cpp +++ b/src/radio/radiomodel.cpp @@ -100,7 +100,8 @@ void RadioModel::RemoveService(RadioService* service) { } void RadioModel::ServiceDeleted() { - RadioService* service = qobject_cast(sender()); + // qobject_cast doesn't work here with services created by python + RadioService* service = static_cast(sender()); if (service) RemoveService(service); } diff --git a/src/scripting/languageengine.cpp b/src/scripting/languageengine.cpp index 740f3b5a9..45b48bbcb 100644 --- a/src/scripting/languageengine.cpp +++ b/src/scripting/languageengine.cpp @@ -17,7 +17,8 @@ #include "languageengine.h" -LanguageEngine::LanguageEngine(ScriptManager* manager) - : manager_(manager) +LanguageEngine::LanguageEngine(ScriptManager* parent) + : QObject(parent), + manager_(parent) { } diff --git a/src/scripting/languageengine.h b/src/scripting/languageengine.h index da6367f51..979991354 100644 --- a/src/scripting/languageengine.h +++ b/src/scripting/languageengine.h @@ -20,11 +20,15 @@ #include "scriptmanager.h" +#include + class Script; -class LanguageEngine { +class LanguageEngine : public QObject { + Q_OBJECT + public: - LanguageEngine(ScriptManager* manager); + LanguageEngine(ScriptManager* parent); virtual ~LanguageEngine() {} ScriptManager* manager() const { return manager_; } diff --git a/src/scripting/python/clementine.sip b/src/scripting/python/clementine.sip index 72dbd052d..a9d36f3a9 100644 --- a/src/scripting/python/clementine.sip +++ b/src/scripting/python/clementine.sip @@ -7,13 +7,18 @@ %Include engine_fwd.sip %Include librarybackend.sip %Include libraryquery.sip +%Include mergedproxymodel.sip %Include player.sip %Include playlist.sip %Include playlistitem.sip %Include playlistmanager.sip %Include playlistsequence.sip %Include pythonengine.sip +%Include radiomodel.sip +%Include radioservice.sip %Include queue.sip %Include scriptinterface.sip +%Include settingsdialog.sip %Include song.sip +%Include taskmanager.sip %Include uiinterface.sip diff --git a/src/scripting/python/mergedproxymodel.sip b/src/scripting/python/mergedproxymodel.sip new file mode 100644 index 000000000..08ab7194a --- /dev/null +++ b/src/scripting/python/mergedproxymodel.sip @@ -0,0 +1,17 @@ +class MergedProxyModel : QAbstractProxyModel { + +%TypeHeaderCode +#include "core/mergedproxymodel.h" +%End + +public: + MergedProxyModel(QObject* parent = 0); + + void AddSubModel(const QModelIndex& source_parent, QAbstractItemModel* submodel); + void RemoveSubModel(const QModelIndex& source_parent); + + QModelIndex FindSourceParent(const QModelIndex& proxy_index) const; + +signals: + void SubModelReset(const QModelIndex& root, QAbstractItemModel* model); +}; diff --git a/src/scripting/python/playlistitem.sip b/src/scripting/python/playlistitem.sip index da0df4ad5..397aeb8a8 100644 --- a/src/scripting/python/playlistitem.sip +++ b/src/scripting/python/playlistitem.sip @@ -26,6 +26,22 @@ public: }; typedef QFlags Options; + struct SpecialLoadResult { + enum Type { + NoMoreTracks, + WillLoadAsynchronously, + TrackAvailable, + }; + + SpecialLoadResult(Type type = NoMoreTracks, + const QUrl& original_url = QUrl(), + const QUrl& media_url = QUrl()); + + Type type_; + QUrl original_url_; + QUrl media_url_; + }; + QString type() const; Options options() const; diff --git a/src/scripting/python/pythonengine.cpp b/src/scripting/python/pythonengine.cpp index 48fa7a155..e8ea3d1a6 100644 --- a/src/scripting/python/pythonengine.cpp +++ b/src/scripting/python/pythonengine.cpp @@ -109,6 +109,9 @@ Script* PythonEngine::CreateScript(const QString& path, AddObject(manager()->data().library_->backend(), sipType_LibraryBackend, "library"); AddObject(manager()->data().player_, sipType_Player, "player"); AddObject(manager()->data().playlists_, sipType_PlaylistManager, "playlists"); + AddObject(manager()->data().task_manager_, sipType_TaskManager, "task_manager"); + AddObject(manager()->data().settings_dialog_, sipType_SettingsDialog, "settings_dialog"); + AddObject(manager()->data().radio_model_, sipType_RadioModel, "radio_model"); AddObject(manager()->ui(), sipType_UIInterface, "ui"); AddObject(this, sipType_PythonEngine, "pythonengine"); @@ -201,4 +204,16 @@ void PythonEngine::RegisterNativeObject(QObject* object) { // Finally got the script - tell it about this object so it will get destroyed // when the script is unloaded. script->AddNativeObject(object); + + // Save the script as a property on the object so we can remove it later + object->setProperty("owning_python_script", QVariant::fromValue(script)); + connect(object, SIGNAL(destroyed(QObject*)), SLOT(NativeObjectDestroyed(QObject*))); +} + +void PythonEngine::NativeObjectDestroyed(QObject* object) { + Script* script = object->property("owning_python_script").value(); + if (!script || !loaded_scripts_.values().contains(script)) + return; + + script->RemoveNativeObject(object); } diff --git a/src/scripting/python/pythonengine.h b/src/scripting/python/pythonengine.h index ae5513ab0..056764cea 100644 --- a/src/scripting/python/pythonengine.h +++ b/src/scripting/python/pythonengine.h @@ -25,6 +25,8 @@ struct _sipAPIDef; struct _sipTypeDef; class PythonEngine : public LanguageEngine { + Q_OBJECT + public: PythonEngine(ScriptManager* manager); ~PythonEngine(); @@ -56,6 +58,9 @@ private: // would not. Script* FindScriptMatchingId(const QString& id) const; +private slots: + void NativeObjectDestroyed(QObject* object); + private: static PythonEngine* sInstance; diff --git a/src/scripting/python/pythonscript.cpp b/src/scripting/python/pythonscript.cpp index 43f58adb1..8b3e640ad 100644 --- a/src/scripting/python/pythonscript.cpp +++ b/src/scripting/python/pythonscript.cpp @@ -115,6 +115,7 @@ bool PythonScript::Unload() { // Delete any native objects this script created qDeleteAll(native_objects_); + native_objects_.clear(); return true; } diff --git a/src/scripting/python/radiomodel.sip b/src/scripting/python/radiomodel.sip new file mode 100644 index 000000000..2bbbe605a --- /dev/null +++ b/src/scripting/python/radiomodel.sip @@ -0,0 +1,44 @@ +class RadioModel : QStandardItemModel { + +%TypeHeaderCode +#include "radio/radiomodel.h" +#include "scripting/python/pythonengine.h" +%End + +public: + enum Role { + Role_Type, + Role_PlayBehaviour, + Role_Url, + Role_Title, + Role_Artist, + Role_CanLazyLoad, + Role_Service, + }; + + enum Type { + Type_Service, + TypeCount + }; + + enum PlayBehaviour { + PlayBehaviour_None, + PlayBehaviour_UseSongLoader, + PlayBehaviour_SingleItem, + }; + + void AddService(RadioService* service /Transfer/); +%MethodCode + sipCpp->AddService(a0); + PythonEngine::instance()->RegisterNativeObject(a0); +%End + void RemoveService(RadioService* service /TransferBack/); + + bool IsPlayable(const QModelIndex& index) const; + + MergedProxyModel* merged_model() const; + TaskManager* task_manager() const; + +private: + RadioModel(); +}; diff --git a/src/scripting/python/radioservice.sip b/src/scripting/python/radioservice.sip new file mode 100644 index 000000000..bbef3dbd7 --- /dev/null +++ b/src/scripting/python/radioservice.sip @@ -0,0 +1,40 @@ +class RadioService : QObject { + +%TypeHeaderCode +#include "radio/radioservice.h" +%End + +public: + RadioService(const QString& name, RadioModel* model); + + QString name() const; + RadioModel* model() const; + + virtual QStandardItem* CreateRootItem() = 0 /TransferBack/; + virtual void LazyPopulate(QStandardItem* parent) = 0; + + virtual void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos); + + virtual PlaylistItem::SpecialLoadResult StartLoading(const QUrl& url); + virtual PlaylistItem::SpecialLoadResult LoadNext(const QUrl& url); + + virtual PlaylistItem::Options playlistitem_options() const; + + virtual QWidget* HeaderWidget() const /Transfer/; + + virtual void ReloadSettings(); + + virtual QString Icon(); + +signals: + void AsyncLoadFinished(const PlaylistItem::SpecialLoadResult& result); + void StreamError(const QString& message); + void StreamMetadataFound(const QUrl& original_url, const Song& song); + void OpenSettingsAtPage(SettingsDialog::Page page); + + void AddToPlaylistSignal(QMimeData* data); + +protected: + void AddItemToPlaylist(const QModelIndex& index, bool clear = false, bool enqueue = false); + void AddItemsToPlaylist(const QModelIndexList& indexes, bool clear = false, bool enqueue = false); +}; diff --git a/src/scripting/python/settingsdialog.sip b/src/scripting/python/settingsdialog.sip new file mode 100644 index 000000000..08afd44fb --- /dev/null +++ b/src/scripting/python/settingsdialog.sip @@ -0,0 +1,24 @@ +class SettingsDialog : QDialog { + +%TypeHeaderCode +#include "radio/radioservice.h" +%End + +public: + enum Page { + Page_Playback, + Page_Behaviour, + Page_SongInformation, + Page_GlobalShortcuts, + Page_Notifications, + Page_Library, + Page_Magnatune, + Page_BackgroundStreams, + Page_Proxy, + }; + + void OpenAtPage(Page page); + +private: + SettingsDialog(); +}; diff --git a/src/scripting/python/taskmanager.sip b/src/scripting/python/taskmanager.sip new file mode 100644 index 000000000..3808d08d1 --- /dev/null +++ b/src/scripting/python/taskmanager.sip @@ -0,0 +1,31 @@ +class TaskManager : QObject { + +%TypeHeaderCode +#include "core/taskmanager.h" +%End + +public: + struct Task { + int id; + QString name; + int progress; + int progress_max; + bool blocks_library_scans; + }; + + QList GetTasks(); + + int StartTask(const QString& name); + void SetTaskBlocksLibraryScans(int id); + void SetTaskProgress(int id, int progress, int max = 0); + void SetTaskFinished(int id); + +signals: + void TasksChanged(); + + void PauseLibraryWatchers(); + void ResumeLibraryWatchers(); + +private: + TaskManager(); +}; diff --git a/src/scripting/script.cpp b/src/scripting/script.cpp index c397c3c8b..e9f24c6d4 100644 --- a/src/scripting/script.cpp +++ b/src/scripting/script.cpp @@ -34,3 +34,7 @@ Script::~Script() { void Script::AddNativeObject(QObject* object) { native_objects_ << object; } + +void Script::RemoveNativeObject(QObject* object) { + native_objects_.removeAll(object); +} diff --git a/src/scripting/script.h b/src/scripting/script.h index 9c5e6fd9b..fced3072f 100644 --- a/src/scripting/script.h +++ b/src/scripting/script.h @@ -19,6 +19,7 @@ #define SCRIPT_H #include +#include #include #include @@ -43,6 +44,7 @@ public: // The script can "own" QObjects like QActions that must be deleted (and // removed from the UI, etc.) when the script is unloaded. void AddNativeObject(QObject* object); + void RemoveNativeObject(QObject* object); virtual bool Init() = 0; virtual bool Unload() = 0; @@ -59,5 +61,6 @@ private: QString script_file_; QString id_; }; +Q_DECLARE_METATYPE(Script*); #endif // SCRIPT_H diff --git a/src/scripting/scriptmanager.cpp b/src/scripting/scriptmanager.cpp index 71f43e493..92d1d603b 100644 --- a/src/scripting/scriptmanager.cpp +++ b/src/scripting/scriptmanager.cpp @@ -53,8 +53,6 @@ ScriptManager::~ScriptManager() { info.loaded_->language()->DestroyScript(info.loaded_); } } - - qDeleteAll(engines_); } void ScriptManager::Init(const GlobalData& data) { diff --git a/src/scripting/scriptmanager.h b/src/scripting/scriptmanager.h index bf007f1fa..70bfd2abc 100644 --- a/src/scripting/scriptmanager.h +++ b/src/scripting/scriptmanager.h @@ -27,7 +27,10 @@ class LanguageEngine; class Library; class Player; class PlaylistManager; +class RadioModel; class Script; +class SettingsDialog; +class TaskManager; class UIInterface; class ScriptManager : public QAbstractListModel { @@ -55,15 +58,23 @@ public: struct GlobalData { GlobalData() {} - GlobalData(Library* library, Player* player, PlaylistManager* playlists) + GlobalData(Library* library, Player* player, PlaylistManager* playlists, + TaskManager* task_manager, SettingsDialog* settings_dialog, + RadioModel* radio_model) : library_(library), player_(player), - playlists_(playlists) + playlists_(playlists), + task_manager_(task_manager), + settings_dialog_(settings_dialog), + radio_model_(radio_model) {} Library* library_; Player* player_; PlaylistManager* playlists_; + TaskManager* task_manager_; + SettingsDialog* settings_dialog_; + RadioModel* radio_model_; }; static const char* kSettingsGroup; diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index 6cdc98bb8..a010ee3dc 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -632,7 +632,9 @@ MainWindow::MainWindow( wiimotedev_shortcuts_.reset(new WiimotedevShortcuts(osd_, this, player_)); #endif - scripts_->Init(ScriptManager::GlobalData(library_, player_, playlists_)); + scripts_->Init(ScriptManager::GlobalData( + library_, player_, playlists_, task_manager_, settings_dialog_.get(), + radio_model_)); connect(ui_->action_script_manager, SIGNAL(triggered()), SLOT(ShowScriptDialog())); }