diff --git a/CMakeLists.txt b/CMakeLists.txt index 75571950d..486cf3482 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,8 +55,9 @@ find_package( Qt5 ${QT_VERSION} CONFIG REQUIRED Core Gui Widgets LinguistTools S find_package( YAMLCPP 0.5.1 REQUIRED ) find_package( PolkitQt5-1 REQUIRED ) -option( WITH_PYTHON "Enable Python modules support." ON ) +option( WITH_PYTHON "Enable Python modules API (requires Boost.Python)." ON ) option( WITH_CRASHREPORTER "Build with CrashReporter" ON ) +option( WITH_PYTHONQT "Enable next generation Python modules API (experimental, requires PythonQt)." OFF ) if( CMAKE_SYSTEM_PROCESSOR MATCHES "arm" OR NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/libcrashreporter-qt/CMakeLists.txt" ) message( STATUS "Build of crashreporter disabled." ) @@ -84,11 +85,23 @@ if ( PYTHONLIBS_FOUND ) FALSE "1.54.0" "Boost.Python is used for interfacing with Calamares job modules written in Python 3." ) + + macro_optional_find_package( PythonQt ) + macro_log_feature( PYTHONQT_FOUND + "PythonQt" + "A Python embedding solution for Qt applications." + "http://pythonqt.sourceforge.net" + FALSE "3.1" + "PythonQt is used for the Python modules API." + ) endif() if ( PYTHONLIBS_NOTFOUND OR NOT CALAMARES_BOOST_PYTHON3_FOUND ) set( WITH_PYTHON OFF ) endif() +if ( PYTHONLIBS_NOTFOUND OR NOT PYTHONQT_FOUND ) + set( WITH_PYTHONQT OFF ) +endif() ### ### Calamares application info @@ -167,8 +180,12 @@ add_subdirectory( src ) macro_display_feature_log() if ( NOT WITH_PYTHON ) - message( "-- WARNING: Building Calamares without Python support. Python modules will not work.\n" ) + message( "-- WARNING: Building Calamares without Python support. Legacy Python job modules will not work.\n" ) endif() +if ( NOT WITH_PYTHONQT ) + message( "-- WARNING: Building Calamares without PythonQt support. Python modules will not work.\n" ) +endif() + # Add all targets to the build-tree export set set( CMAKE_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/Calamares" CACHE PATH "Installation directory for CMake files" ) diff --git a/CMakeModules/FindPythonQt.cmake b/CMakeModules/FindPythonQt.cmake new file mode 100644 index 000000000..c01d68be1 --- /dev/null +++ b/CMakeModules/FindPythonQt.cmake @@ -0,0 +1,69 @@ +# Find PythonQt +# +# Sets PYTHONQT_FOUND, PYTHONQT_INCLUDE_DIR, PYTHONQT_LIBRARY, PYTHONQT_LIBRARIES +# + +# Python is required +find_package(PythonLibs) +if(NOT PYTHONLIBS_FOUND) + message(FATAL_ERROR "error: Python is required to build PythonQt") +endif() + +if(NOT EXISTS "${PYTHONQT_INSTALL_DIR}") + find_path(PYTHONQT_INSTALL_DIR include/PythonQt/PythonQt.h DOC "Directory where PythonQt was installed.") +endif() +# XXX Since PythonQt 3.0 is not yet cmakeified, depending +# on how PythonQt is built, headers will not always be +# installed in "include/PythonQt". That is why "src" +# is added as an option. See [1] for more details. +# [1] https://github.com/commontk/CTK/pull/538#issuecomment-86106367 +find_path(PYTHONQT_INCLUDE_DIR PythonQt.h + PATHS "${PYTHONQT_INSTALL_DIR}/include/PythonQt" + "${PYTHONQT_INSTALL_DIR}/src" + DOC "Path to the PythonQt include directory") +find_library(PYTHONQT_LIBRARY_RELEASE PythonQt PATHS "${PYTHONQT_INSTALL_DIR}/lib" DOC "The PythonQt library.") +find_library(PYTHONQT_LIBRARY_DEBUG NAMES PythonQt${CTK_CMAKE_DEBUG_POSTFIX} PythonQt${CMAKE_DEBUG_POSTFIX} PythonQt PATHS "${PYTHONQT_INSTALL_DIR}/lib" DOC "The PythonQt library.") +find_library(PYTHONQT_QTALL_LIBRARY_RELEASE PythonQt_QtAll PATHS "${PYTHONQT_INSTALL_DIR}/lib" DOC "Full Qt bindings for the PythonQt library.") +find_library(PYTHONQT_QTALL_LIBRARY_DEBUG NAMES PythonQt_QtAll${CTK_CMAKE_DEBUG_POSTFIX} PythonQt_QtAll${CMAKE_DEBUG_POSTFIX} PythonQt_QtAll PATHS "${PYTHONQT_INSTALL_DIR}/lib" DOC "Full Qt bindings for the PythonQt library.") + +set(PYTHONQT_LIBRARY) + +if(PYTHONQT_LIBRARY_RELEASE) + list(APPEND PYTHONQT_LIBRARY optimized ${PYTHONQT_LIBRARY_RELEASE}) +endif() +if(PYTHONQT_LIBRARY_DEBUG) + list(APPEND PYTHONQT_LIBRARY debug ${PYTHONQT_LIBRARY_DEBUG}) +endif() + +set(PYTHONQT_QTALL_LIBRARY) +if(PYTHONQT_QTALL_LIBRARY_RELEASE) + list(APPEND PYTHONQT_QTALL_LIBRARY optimized ${PYTHONQT_QTALL_LIBRARY_RELEASE}) +endif() +if(PYTHONQT_QTALL_LIBRARY_DEBUG) + list(APPEND PYTHONQT_QTALL_LIBRARY debug ${PYTHONQT_QTALL_LIBRARY_DEBUG}) +endif() + +mark_as_advanced(PYTHONQT_INSTALL_DIR) +mark_as_advanced(PYTHONQT_INCLUDE_DIR) +mark_as_advanced(PYTHONQT_LIBRARY_RELEASE) +mark_as_advanced(PYTHONQT_LIBRARY_DEBUG) +mark_as_advanced(PYTHONQT_QTALL_LIBRARY_RELEASE) +mark_as_advanced(PYTHONQT_QTALL_LIBRARY_DEBUG) + +# On linux, also find libutil +if(UNIX AND NOT APPLE) + find_library(PYTHONQT_LIBUTIL util) + mark_as_advanced(PYTHONQT_LIBUTIL) +endif() + +# All upper case _FOUND variable is maintained for backwards compatibility. +set(PYTHONQT_FOUND 0) +set(PythonQt_FOUND 0) +if(PYTHONQT_INCLUDE_DIR AND PYTHONQT_LIBRARY AND PYTHONQT_QTALL_LIBRARY) + # Currently CMake'ified PythonQt only supports building against a python Release build. + # This applies independently of CTK build type (Release, Debug, ...) + add_definitions(-DPYTHONQT_USE_RELEASE_PYTHON_FALLBACK) + set(PYTHONQT_FOUND 1) + set(PythonQt_FOUND ${PYTHONQT_FOUND}) + set(PYTHONQT_LIBRARIES ${PYTHONQT_LIBRARY} ${PYTHONQT_LIBUTIL} ${PYTHONQT_QTALL_LIBRARY}) +endif() diff --git a/src/libcalamares/CMakeLists.txt b/src/libcalamares/CMakeLists.txt index b1f6c48cf..fd94e3eab 100644 --- a/src/libcalamares/CMakeLists.txt +++ b/src/libcalamares/CMakeLists.txt @@ -58,6 +58,20 @@ if( WITH_PYTHON ) ) endif() +if( WITH_PYTHONQT ) + include_directories(${PYTHON_INCLUDE_DIRS}) + link_directories(${PYTHON_LIBRARIES}) + + include_directories(${PYTHONQT_INCLUDE_DIR}) + link_directories(${PYTHONQT_LIBRARY}) + + set( OPTIONAL_PRIVATE_LIBRARIES + ${OPTIONAL_PRIVATE_LIBRARIES} + ${PYTHON_LIBRARIES} + ${PYTHONQT_LIBRARY} + ) +endif() + add_library( calamares SHARED ${libSources} ) set_target_properties( calamares diff --git a/src/libcalamares/CalamaresConfig.h.in b/src/libcalamares/CalamaresConfig.h.in index 94467ae74..f278853a9 100644 --- a/src/libcalamares/CalamaresConfig.h.in +++ b/src/libcalamares/CalamaresConfig.h.in @@ -11,5 +11,6 @@ //cmakedefines for CMake variables (e.g. for optdepends) go here #cmakedefine WITH_PYTHON #cmakedefine WITH_CRASHREPORTER +#cmakedefine WITH_PYTHONQT #endif // CALAMARESCONFIG_H diff --git a/src/libcalamares/Job.h b/src/libcalamares/Job.h index e632ec885..b9d3baf85 100644 --- a/src/libcalamares/Job.h +++ b/src/libcalamares/Job.h @@ -29,37 +29,33 @@ namespace Calamares { class DLLEXPORT JobResult { public: - operator bool() const; + virtual ~JobResult() {} - QString message() const; - void setMessage( const QString& message ); + virtual operator bool() const; - QString details() const; - void setDetails( const QString& details ); + virtual QString message() const; + virtual void setMessage( const QString& message ); + + virtual QString details() const; + virtual void setDetails( const QString& details ); static JobResult ok(); static JobResult error( const QString& message, const QString& details = QString() ); +protected: + explicit JobResult( bool ok, const QString& message, const QString& details ); + private: bool m_ok; QString m_message; QString m_details; - - JobResult( bool ok, const QString& message, const QString& details ); }; class DLLEXPORT Job : public QObject { Q_OBJECT public: - enum State - { - Pending = 0, - Running, - Finished - }; - explicit Job( QObject* parent = nullptr ); virtual ~Job(); diff --git a/src/libcalamares/PythonHelper.cpp b/src/libcalamares/PythonHelper.cpp index 51bb03228..b252f90b2 100644 --- a/src/libcalamares/PythonHelper.cpp +++ b/src/libcalamares/PythonHelper.cpp @@ -189,7 +189,8 @@ Helper::Helper( QObject* parent ) // Let's make extra sure we only call Py_Initialize once if ( !s_instance ) { - Py_Initialize(); + if ( !Py_IsInitialized() ) + Py_Initialize(); m_mainModule = bp::import( "__main__" ); m_mainNamespace = m_mainModule.attr( "__dict__" ); diff --git a/src/libcalamaresui/CMakeLists.txt b/src/libcalamaresui/CMakeLists.txt index 478034b3e..cdde19789 100644 --- a/src/libcalamaresui/CMakeLists.txt +++ b/src/libcalamaresui/CMakeLists.txt @@ -39,6 +39,25 @@ if( WITH_PYTHON ) ) endif() +if( WITH_PYTHONQT ) + include_directories(${PYTHON_INCLUDE_DIRS}) + include_directories(${PYTHONQT_INCLUDE_DIR}) + + list( APPEND ${CALAMARESUI_LIBRARY_TARGET}_SOURCES + modulesystem/PythonQtViewModule.cpp + utils/PythonQtUtils.cpp + viewpages/PythonQtJob.cpp + viewpages/PythonQtViewStep.cpp + viewpages/PythonQtGlobalStorageWrapper.cpp + viewpages/PythonQtUtilsWrapper.cpp + ) + set( OPTIONAL_PRIVATE_LIBRARIES + ${OPTIONAL_PRIVATE_LIBRARIES} + ${PYTHON_LIBRARIES} + ${PYTHONQT_LIBRARIES} + ) +endif() + calamares_add_library( ${CALAMARESUI_LIBRARY_TARGET} SOURCES ${${CALAMARESUI_LIBRARY_TARGET}_SOURCES} UI ${${CALAMARESUI_LIBRARY_TARGET}_UI} @@ -47,6 +66,7 @@ calamares_add_library( ${CALAMARESUI_LIBRARY_TARGET} yaml-cpp Qt5::Svg Qt5::QuickWidgets + ${OPTIONAL_PRIVATE_LIBRARIES} RESOURCES libcalamaresui.qrc EXPORT CalamaresLibraryDepends VERSION ${CALAMARES_VERSION_SHORT} diff --git a/src/libcalamaresui/modulesystem/CppJobModule.cpp b/src/libcalamaresui/modulesystem/CppJobModule.cpp index 15e41c2e5..6ff846027 100644 --- a/src/libcalamaresui/modulesystem/CppJobModule.cpp +++ b/src/libcalamaresui/modulesystem/CppJobModule.cpp @@ -39,7 +39,7 @@ CppJobModule::type() const Module::Interface CppJobModule::interface() const { - return QtPlugin; + return QtPluginInterface; } diff --git a/src/libcalamaresui/modulesystem/Module.cpp b/src/libcalamaresui/modulesystem/Module.cpp index 2a2fe779b..5395ff7f2 100644 --- a/src/libcalamaresui/modulesystem/Module.cpp +++ b/src/libcalamaresui/modulesystem/Module.cpp @@ -31,6 +31,10 @@ #include "PythonJobModule.h" #endif +#ifdef WITH_PYTHONQT +#include "PythonQtViewModule.h" +#endif + #include #include @@ -71,9 +75,18 @@ Module::fromDescriptor( const QVariantMap& moduleDescriptor, << instanceId; return nullptr; } - if ( typeString == "view" && intfString == "qtplugin" ) + if ( typeString == "view" ) { - m = new ViewModule(); + if ( intfString == "qtplugin" ) + { + m = new ViewModule(); + } +#ifdef WITH_PYTHONQT + else if ( intfString == "pythonqt" ) + { + m = new PythonQtViewModule(); + } +#endif } else if ( typeString == "job" ) { @@ -215,6 +228,38 @@ Module::location() const } +QString +Module::typeString() const +{ + switch ( type() ) + { + case Job: + return "Job Module"; + case View: + return "View Module"; + } + return QString(); +} + + +QString +Module::interfaceString() const +{ + switch ( interface() ) + { + case ProcessInterface: + return "External process"; + case PythonInterface: + return "Python (Boost.Python)"; + case PythonQtInterface: + return "Python (experimental)"; + case QtPluginInterface: + return "Qt Plugin"; + } + return QString(); +} + + bool Module::isLoaded() const { diff --git a/src/libcalamaresui/modulesystem/Module.h b/src/libcalamaresui/modulesystem/Module.h index 336890ee9..6934a554e 100644 --- a/src/libcalamaresui/modulesystem/Module.h +++ b/src/libcalamaresui/modulesystem/Module.h @@ -48,9 +48,10 @@ public: enum Interface { - QtPlugin, - Python, - Process + QtPluginInterface, + PythonInterface, + ProcessInterface, + PythonQtInterface }; virtual ~Module(); @@ -65,7 +66,9 @@ public: virtual QStringList requiredModules() const; virtual QString location() const final; virtual Type type() const = 0; + virtual QString typeString() const; virtual Interface interface() const = 0; + virtual QString interfaceString() const; virtual bool isLoaded() const; diff --git a/src/libcalamaresui/modulesystem/ProcessJobModule.cpp b/src/libcalamaresui/modulesystem/ProcessJobModule.cpp index 5b6fa24f0..aefcbf6f0 100644 --- a/src/libcalamaresui/modulesystem/ProcessJobModule.cpp +++ b/src/libcalamaresui/modulesystem/ProcessJobModule.cpp @@ -35,7 +35,7 @@ ProcessJobModule::type() const Module::Interface ProcessJobModule::interface() const { - return Process; + return ProcessInterface; } diff --git a/src/libcalamaresui/modulesystem/PythonJobModule.cpp b/src/libcalamaresui/modulesystem/PythonJobModule.cpp index e2982ef48..544f27e1f 100644 --- a/src/libcalamaresui/modulesystem/PythonJobModule.cpp +++ b/src/libcalamaresui/modulesystem/PythonJobModule.cpp @@ -36,7 +36,7 @@ PythonJobModule::type() const Module::Interface PythonJobModule::interface() const { - return Python; + return PythonInterface; } diff --git a/src/libcalamaresui/modulesystem/PythonQtViewModule.cpp b/src/libcalamaresui/modulesystem/PythonQtViewModule.cpp new file mode 100644 index 000000000..3620e117d --- /dev/null +++ b/src/libcalamaresui/modulesystem/PythonQtViewModule.cpp @@ -0,0 +1,203 @@ +/* === This file is part of Calamares - === + * + * Copyright 2016, Teo Mrnjavac + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "PythonQtViewModule.h" + +#include "utils/Logger.h" +#include "viewpages/ViewStep.h" +#include "viewpages/PythonQtViewStep.h" +#include "ViewManager.h" +#include "CalamaresConfig.h" +#include "viewpages/PythonQtGlobalStorageWrapper.h" +#include "viewpages/PythonQtUtilsWrapper.h" +#include "GlobalStorage.h" +#include "JobQueue.h" + +#include +#include + +#include +#include + + +static QPointer< GlobalStorage > s_gs = nullptr; +static QPointer< Utils > s_utils = nullptr; + +namespace Calamares { + +Module::Type +PythonQtViewModule::type() const +{ + return View; +} + + +Module::Interface +PythonQtViewModule::interface() const +{ + return PythonQtInterface; +} + + +void +PythonQtViewModule::loadSelf() +{ + if ( !m_scriptFileName.isEmpty() ) + { + if ( PythonQt::self() == nullptr ) + { + if ( Py_IsInitialized() ) + PythonQt::init( PythonQt::IgnoreSiteModule | + PythonQt::RedirectStdOut | + PythonQt::PythonAlreadyInitialized ); + else + PythonQt::init(); + + PythonQt_QtAll::init(); + cDebug() << "Initializing PythonQt bindings." + << "This should only happen once."; + + //TODO: register classes here into the PythonQt environment, like this: + //PythonQt::self()->registerClass( &PythonQtViewStep::staticMetaObject, + // "calamares" ); + + // We only do the following to force PythonQt to create a submodule + // "calamares" for us to put our static objects in + PythonQt::self()->registerClass( &::GlobalStorage::staticMetaObject, + "calamares" ); + + // Get a PythonQtObjectPtr to the PythonQt.calamares submodule + PythonQtObjectPtr pqtm = PythonQt::priv()->pythonQtModule(); + PythonQtObjectPtr cala = PythonQt::self()->lookupObject( pqtm, "calamares" ); + + // Prepare GlobalStorage object, in module PythonQt.calamares + if ( !s_gs ) + s_gs = new ::GlobalStorage( Calamares::JobQueue::instance()->globalStorage() ); + cala.addObject( "global_storage", s_gs ); + + // Prepare Utils object, in module PythonQt.calamares + if ( !s_utils ) + s_utils = new ::Utils( Calamares::JobQueue::instance()->globalStorage() ); + cala.addObject( "utils", s_utils ); + + // Basic stdout/stderr handling + QObject::connect( PythonQt::self(), &PythonQt::pythonStdOut, + []( const QString& message ) + { + cDebug() << "PythonQt OUT>" << message; + } ); + QObject::connect( PythonQt::self(), &PythonQt::pythonStdErr, + []( const QString& message ) + { + cDebug() << "PythonQt ERR>" << message; + } ); + + } + + QDir workingDir( m_workingPath ); + if ( !workingDir.exists() ) + { + cDebug() << "Invalid working directory" + << m_workingPath + << "for module" + << name(); + return; + } + + QString fullPath = workingDir.absoluteFilePath( m_scriptFileName ); + QFileInfo scriptFileInfo( fullPath ); + if ( !scriptFileInfo.isReadable() ) + { + cDebug() << "Invalid main script file path" + << fullPath + << "for module" + << name(); + return; + } + + // Construct empty Python module with the given name + PythonQtObjectPtr cxt = + PythonQt::self()-> + createModuleFromScript( name() ); + if ( cxt.isNull() ) + { + cDebug() << "Cannot load PythonQt context from file" + << fullPath + << "for module" + << name(); + return; + } + + QString calamares_module_annotation = + "_calamares_module_typename = 'foo'\n" + "def calamares_module(viewmodule_type):\n" + " global _calamares_module_typename\n" + " _calamares_module_typename = viewmodule_type.__name__\n" + " return viewmodule_type\n"; + + // Load in the decorator + PythonQt::self()->evalScript( cxt, calamares_module_annotation ); + + // Load the module + PythonQt::self()->evalFile( cxt, fullPath ); + + m_viewStep = new PythonQtViewStep( cxt ); + + cDebug() << "PythonQtViewModule loading self for instance" << instanceKey() + << "\nPythonQtViewModule at address" << this + << "\nViewStep at address" << m_viewStep; + + m_viewStep->setModuleInstanceKey( instanceKey() ); + m_viewStep->setConfigurationMap( m_configurationMap ); + ViewManager::instance()->addViewStep( m_viewStep ); + m_loaded = true; + cDebug() << "PythonQtViewModule" << instanceKey() << "loading complete."; + } +} + + +QList< job_ptr > +PythonQtViewModule::jobs() const +{ + return m_viewStep->jobs(); +} + + +void +PythonQtViewModule::initFrom( const QVariantMap& moduleDescriptor ) +{ + Module::initFrom( moduleDescriptor ); + QDir directory( location() ); + m_workingPath = directory.absolutePath(); + + if ( !moduleDescriptor.value( "script" ).toString().isEmpty() ) + { + m_scriptFileName = moduleDescriptor.value( "script" ).toString(); + } +} + +PythonQtViewModule::PythonQtViewModule() + : Module() +{ +} + +PythonQtViewModule::~PythonQtViewModule() +{ +} + +} // namespace Calamares diff --git a/src/libcalamaresui/modulesystem/PythonQtViewModule.h b/src/libcalamaresui/modulesystem/PythonQtViewModule.h new file mode 100644 index 000000000..ba18cfac6 --- /dev/null +++ b/src/libcalamaresui/modulesystem/PythonQtViewModule.h @@ -0,0 +1,54 @@ +/* === This file is part of Calamares - === + * + * Copyright 2016, Teo Mrnjavac + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef CALAMARES_PYTHONQTVIEWMODULE_H +#define CALAMARES_PYTHONQTVIEWMODULE_H + +#include "UiDllMacro.h" +#include "Module.h" + +namespace Calamares { + +class ViewStep; + +class UIDLLEXPORT PythonQtViewModule : public Module +{ +public: + Type type() const override; + Interface interface() const override; + + void loadSelf() override; + QList< job_ptr > jobs() const override; + +protected: + void initFrom( const QVariantMap& moduleDescriptor ) override; + +private: + friend class Module; //so only the superclass can instantiate + explicit PythonQtViewModule(); + virtual ~PythonQtViewModule(); + + ViewStep* m_viewStep = nullptr; + + QString m_scriptFileName; + QString m_workingPath; +}; + +} // namespace Calamares + +#endif // CALAMARES_PYTHONQTVIEWMODULE_H diff --git a/src/libcalamaresui/modulesystem/ViewModule.cpp b/src/libcalamaresui/modulesystem/ViewModule.cpp index ddc70d869..a06ec3fba 100644 --- a/src/libcalamaresui/modulesystem/ViewModule.cpp +++ b/src/libcalamaresui/modulesystem/ViewModule.cpp @@ -39,7 +39,7 @@ ViewModule::type() const Module::Interface ViewModule::interface() const { - return QtPlugin; + return QtPluginInterface; } diff --git a/src/libcalamaresui/utils/DebugWindow.cpp b/src/libcalamaresui/utils/DebugWindow.cpp index 879ef3e46..5e41e732d 100644 --- a/src/libcalamaresui/utils/DebugWindow.cpp +++ b/src/libcalamaresui/utils/DebugWindow.cpp @@ -26,6 +26,12 @@ #include "modulesystem/ModuleManager.h" #include "modulesystem/Module.h" +#ifdef WITH_PYTHONQT +#include +#include "ViewManager.h" +#include "viewpages/PythonQtViewStep.h" +#endif + #include #include #include @@ -68,11 +74,6 @@ DebugWindow::DebugWindow() } ); // Modules page - QSplitter* splitter = new QSplitter( modulesTab ); - modulesTab->layout()->addWidget( splitter ); - splitter->addWidget( modulesListView ); - splitter->addWidget( moduleConfigView ); - QStringListModel* modulesModel = new QStringListModel( ModuleManager::instance()->loadedInstanceKeys() ); modulesListView->setModel( modulesModel ); modulesListView->setSelectionMode( QAbstractItemView::SingleSelection ); @@ -80,8 +81,76 @@ DebugWindow::DebugWindow() QJsonModel* moduleConfigModel = new QJsonModel( this ); moduleConfigView->setModel( moduleConfigModel ); +#ifdef WITH_PYTHONQT + QPushButton* pythonConsoleButton = new QPushButton; + pythonConsoleButton->setText( "Attach Python console" ); + modulesVerticalLayout->insertWidget( 1, pythonConsoleButton ); + pythonConsoleButton->hide(); + + QObject::connect( pythonConsoleButton, &QPushButton::clicked, + this, [ this, moduleConfigModel ] + { + QString moduleName = modulesListView->currentIndex().data().toString(); + Module* module = ModuleManager::instance()->moduleInstance( moduleName ); + if ( module->interface() != Module::PythonQtInterface || + module->type() != Module::View ) + return; + + for ( ViewStep* step : ViewManager::instance()->viewSteps() ) + { + if ( step->moduleInstanceKey() == module->instanceKey() ) + { + PythonQtViewStep* pqvs = + qobject_cast< PythonQtViewStep* >( step ); + if ( pqvs ) + { + QWidget* consoleWindow = new QWidget; + + QWidget* console = pqvs->createScriptingConsole(); + console->setParent( consoleWindow ); + + QVBoxLayout* layout = new QVBoxLayout; + consoleWindow->setLayout( layout ); + layout->addWidget( console ); + + QHBoxLayout* bottomLayout = new QHBoxLayout; + layout->addLayout( bottomLayout ); + + QLabel* bottomLabel = new QLabel( consoleWindow ); + bottomLayout->addWidget( bottomLabel ); + QString line = + QString( "Module: %1
" + "Python class: %2" ) + .arg( module->instanceKey() ) + .arg( console->property( "classname" ).toString() ); + bottomLabel->setText( line ); + + QPushButton* closeButton = new QPushButton( consoleWindow ); + closeButton->setText( "&Close" ); + QObject::connect( closeButton, &QPushButton::clicked, + [ consoleWindow ] + { + consoleWindow->close(); + } ); + bottomLayout->addWidget( closeButton ); + bottomLabel->setSizePolicy( QSizePolicy::Expanding, + QSizePolicy::Preferred ); + + consoleWindow->setParent( this ); + consoleWindow->setWindowFlags( Qt::Window ); + consoleWindow->setWindowTitle( "Calamares Python console" ); + consoleWindow->setAttribute( Qt::WA_DeleteOnClose, true ); + consoleWindow->showNormal(); + break; + } + } + } + } ); + +#endif + connect( modulesListView->selectionModel(), &QItemSelectionModel::selectionChanged, - this, [ this, moduleConfigModel ] + this, [ this, moduleConfigModel, pythonConsoleButton ] { QString moduleName = modulesListView->currentIndex().data().toString(); Module* module = ModuleManager::instance()->moduleInstance( moduleName ); @@ -89,6 +158,13 @@ DebugWindow::DebugWindow() { moduleConfigModel->loadJson( QJsonDocument::fromVariant( module->configurationMap() ).toJson() ); moduleConfigView->expandAll(); + moduleTypeLabel->setText( module->typeString() ); + moduleInterfaceLabel->setText( module->interfaceString() ); +#ifdef WITH_PYTHONQT + pythonConsoleButton->setVisible( + module->interface() == Module::PythonQtInterface && + module->type() == Module::View ); +#endif } } ); diff --git a/src/libcalamaresui/utils/DebugWindow.ui b/src/libcalamaresui/utils/DebugWindow.ui index fa6b28acf..eb6d5c3e7 100644 --- a/src/libcalamaresui/utils/DebugWindow.ui +++ b/src/libcalamaresui/utils/DebugWindow.ui @@ -6,8 +6,8 @@ 0 0 - 632 - 497 + 962 + 651 @@ -17,7 +17,7 @@ - 0 + 2 @@ -48,7 +48,43 @@ - + + + + + + + Type: + + + + + + + none + + + + + + + Interface: + + + + + + + none + + + + + + + + + diff --git a/src/libcalamaresui/utils/PythonQtUtils.cpp b/src/libcalamaresui/utils/PythonQtUtils.cpp new file mode 100644 index 000000000..ee386c0fd --- /dev/null +++ b/src/libcalamaresui/utils/PythonQtUtils.cpp @@ -0,0 +1,44 @@ +/* === This file is part of Calamares - === + * + * Copyright 2016, Teo Mrnjavac + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "PythonQtUtils.h" + +namespace CalamaresUtils +{ + +QVariant +lookupAndCall( PyObject* object, + const QStringList& candidateNames, + const QVariantList& args, + const QVariantMap& kwargs ) +{ + Q_ASSERT( object ); + Q_ASSERT( !candidateNames.isEmpty() ); + + for ( const QString& name : candidateNames ) + { + PythonQtObjectPtr callable = PythonQt::self()->lookupCallable( object, name ); + if ( callable ) + return callable.call( args, kwargs ); + } + + // If we haven't found a callable with the given names, we force an error: + return PythonQt::self()->call( object, candidateNames.first(), args, kwargs ); +} + +} diff --git a/src/libcalamaresui/utils/PythonQtUtils.h b/src/libcalamaresui/utils/PythonQtUtils.h new file mode 100644 index 000000000..a94cf25e5 --- /dev/null +++ b/src/libcalamaresui/utils/PythonQtUtils.h @@ -0,0 +1,38 @@ +/* === This file is part of Calamares - === + * + * Copyright 2016, Teo Mrnjavac + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef PYTHONQTUTILS_H +#define PYTHONQTUTILS_H + +#include + +#include + + +namespace CalamaresUtils +{ +//NOTE: when running this, it is assumed that Python is initialized and +// PythonQt::self() is valid. +QVariant lookupAndCall( PyObject* object, + const QStringList& candidateNames, + const QVariantList& args = QVariantList(), + const QVariantMap& kwargs = QVariantMap() ); + +} //ns + +#endif // PYTHONQTUTILS_H diff --git a/src/libcalamaresui/viewpages/PythonQtGlobalStorageWrapper.cpp b/src/libcalamaresui/viewpages/PythonQtGlobalStorageWrapper.cpp new file mode 100644 index 000000000..2f1fcd731 --- /dev/null +++ b/src/libcalamaresui/viewpages/PythonQtGlobalStorageWrapper.cpp @@ -0,0 +1,69 @@ +/* === This file is part of Calamares - === + * + * Copyright 2016, Teo Mrnjavac + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "PythonQtGlobalStorageWrapper.h" + +#include "GlobalStorage.h" + + +GlobalStorage::GlobalStorage( Calamares::GlobalStorage* gs ) + : QObject( gs ) + , m_gs( gs ) +{} + + +bool +GlobalStorage::contains( const QString& key ) const +{ + return m_gs->contains( key ); +} + + +int +GlobalStorage::count() const +{ + return m_gs->count(); +} + + +void +GlobalStorage::insert( const QString& key, const QVariant& value ) +{ + m_gs->insert( key, value ); +} + + +QStringList +GlobalStorage::keys() const +{ + return m_gs->keys(); +} + + +int +GlobalStorage::remove( const QString& key ) +{ + return m_gs->remove( key ); +} + + +QVariant +GlobalStorage::value( const QString& key ) const +{ + return m_gs->value( key ); +} diff --git a/src/libcalamaresui/viewpages/PythonQtGlobalStorageWrapper.h b/src/libcalamaresui/viewpages/PythonQtGlobalStorageWrapper.h new file mode 100644 index 000000000..415dd33b6 --- /dev/null +++ b/src/libcalamaresui/viewpages/PythonQtGlobalStorageWrapper.h @@ -0,0 +1,55 @@ +/* === This file is part of Calamares - === + * + * Copyright 2016, Teo Mrnjavac + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef PYTHONQTGLOBALSTORAGEWRAPPER_H +#define PYTHONQTGLOBALSTORAGEWRAPPER_H + + +#include + +namespace Calamares +{ +class GlobalStorage; +} + + +/** + * @brief This GlobalStorage class is a namespace-free wrapper for + * Calamares::GlobalStorage. This is unfortunately a necessity + * because PythonQt doesn't like namespaces. + */ +class GlobalStorage : public QObject +{ + Q_OBJECT +public: + explicit GlobalStorage( Calamares::GlobalStorage* gs ); + virtual ~GlobalStorage() {} + +public slots: + bool contains( const QString& key ) const; + int count() const; + void insert( const QString& key, const QVariant& value ); + QStringList keys() const; + int remove( const QString& key ); + QVariant value( const QString& key ) const; + +private: + Calamares::GlobalStorage* m_gs; +}; + +#endif // PYTHONQTGLOBALSTORAGEWRAPPER_H diff --git a/src/libcalamaresui/viewpages/PythonQtJob.cpp b/src/libcalamaresui/viewpages/PythonQtJob.cpp new file mode 100644 index 000000000..6768a947b --- /dev/null +++ b/src/libcalamaresui/viewpages/PythonQtJob.cpp @@ -0,0 +1,77 @@ +/* === This file is part of Calamares - === + * + * Copyright 2016, Teo Mrnjavac + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "PythonQtJob.h" + +#include "utils/PythonQtUtils.h" + +PythonQtJob::PythonQtJob( PythonQtObjectPtr cxt, + PythonQtObjectPtr pyJob, + QObject* parent ) + : Calamares::Job( parent ) + , m_cxt( cxt ) + , m_pyJob( pyJob ) +{ + +} + + +QString +PythonQtJob::prettyName() const +{ + return CalamaresUtils::lookupAndCall( m_pyJob, + { "prettyName", + "prettyname", + "pretty_name" } ).toString(); +} + + +QString +PythonQtJob::prettyDescription() const +{ + return CalamaresUtils::lookupAndCall( m_pyJob, + { "prettyDescription", + "prettydescription", + "pretty_description" } ).toString(); +} + + +QString +PythonQtJob::prettyStatusMessage() const +{ + return CalamaresUtils::lookupAndCall( m_pyJob, + { "prettyStatusMessage", + "prettystatusmessage", + "pretty_status_message" } ).toString(); +} + + +Calamares::JobResult +PythonQtJob::exec() +{ + QVariant response = m_pyJob.call( "exec" ); + if ( response.isNull() ) + return Calamares::JobResult::ok(); + + QVariantMap map = response.toMap(); + if ( map.isEmpty() || map.value( "ok" ).toBool() ) + return Calamares::JobResult::ok(); + + return Calamares::JobResult::error( map.value( "message" ).toString(), + map.value( "details" ).toString() ); +} diff --git a/src/libcalamaresui/viewpages/PythonQtJob.h b/src/libcalamaresui/viewpages/PythonQtJob.h new file mode 100644 index 000000000..aa93f9922 --- /dev/null +++ b/src/libcalamaresui/viewpages/PythonQtJob.h @@ -0,0 +1,65 @@ +/* === This file is part of Calamares - === + * + * Copyright 2016, Teo Mrnjavac + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef PYTHONQTJOB_H +#define PYTHONQTJOB_H + +#include "Job.h" + +#include + +namespace Calamares +{ +class PythonQtViewStep; +} + +class PythonQtJobResult : public QObject, public Calamares::JobResult +{ + Q_OBJECT +public: + explicit PythonQtJobResult( bool ok, + const QString& message, + const QString& details ) + : QObject( nullptr ) + , Calamares::JobResult( ok, message, details ) + {} +}; + + +class PythonQtJob : public Calamares::Job +{ + Q_OBJECT +public: + virtual ~PythonQtJob() {} + + QString prettyName() const override; + QString prettyDescription() const override; + QString prettyStatusMessage() const override; + Calamares::JobResult exec() override; + +private: + explicit PythonQtJob( PythonQtObjectPtr cxt, + PythonQtObjectPtr pyJob, + QObject* parent = nullptr ); + friend class Calamares::PythonQtViewStep; // only this one can call the ctor + + PythonQtObjectPtr m_cxt; + PythonQtObjectPtr m_pyJob; +}; + +#endif // PYTHONQTJOB_H diff --git a/src/libcalamaresui/viewpages/PythonQtUtilsWrapper.cpp b/src/libcalamaresui/viewpages/PythonQtUtilsWrapper.cpp new file mode 100644 index 000000000..ce53c810b --- /dev/null +++ b/src/libcalamaresui/viewpages/PythonQtUtilsWrapper.cpp @@ -0,0 +1,146 @@ +/* === This file is part of Calamares - === + * + * Copyright 2016, Teo Mrnjavac + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "PythonQtUtilsWrapper.h" + +#include "utils/CalamaresUtilsSystem.h" +#include "utils/CalamaresUtils.h" +#include "utils/Logger.h" + +#include + + +Utils::Utils(QObject* parent) + : QObject( parent ) + , m_exceptionCxt( PythonQt::self()->createUniqueModule() ) +{ + PythonQt::self()->evalScript( m_exceptionCxt, "import subprocess" ); +} + + +void +Utils::debug(const QString& s) const +{ + cDebug() << "PythonQt DBG>" << s; +} + + +int +Utils::mount( const QString& device_path, + const QString& mount_point, + const QString& filesystem_name, + const QString& options ) const +{ + return CalamaresUtils::System::instance()-> + mount( device_path, mount_point, filesystem_name, options ); +} + + +int +Utils::target_env_call( const QString& command, + const QString& stdin, + int timeout ) const +{ + return CalamaresUtils::System::instance()-> + targetEnvCall( command, QString(), stdin, timeout ); +} + + +int +Utils::target_env_call( const QStringList& args, + const QString& stdin, + int timeout ) const +{ + return CalamaresUtils::System::instance()-> + targetEnvCall( args, QString(), stdin, timeout ); +} + + +int +Utils::check_target_env_call( const QString& command, + const QString& stdin, + int timeout ) const +{ + int ec = target_env_call( command, stdin, timeout ); + return _handle_check_target_env_call_error( ec, command ); +} + + +int +Utils::check_target_env_call( const QStringList& args, + const QString& stdin, + int timeout) const +{ + int ec = target_env_call( args, stdin, timeout ); + return _handle_check_target_env_call_error( ec, args.join( ' ' ) ); +} + + +QString +Utils::check_target_env_output( const QString& command, + const QString& stdin, + int timeout ) const +{ + QString output; + int ec = CalamaresUtils::System::instance()-> + targetEnvOutput( command, + output, + QString(), + stdin, + timeout ); + _handle_check_target_env_call_error( ec, command ); + return output; +} + + +QString +Utils::check_target_env_output( const QStringList& args, + const QString& stdin, + int timeout ) const +{ + QString output; + int ec = CalamaresUtils::System::instance()-> + targetEnvOutput( args, + output, + QString(), + stdin, + timeout ); + _handle_check_target_env_call_error( ec, args.join( ' ' ) ); + return output; +} + + +QString +Utils::obscure( const QString& string ) const +{ + return CalamaresUtils::obscure( string ); +} + + +int +Utils::_handle_check_target_env_call_error( int ec, const QString& cmd) const +{ + if ( ec ) + { + QString raise = QString( "raise subprocess.CalledProcessError(%1,\"%2\")" ) + .arg( ec ) + .arg( cmd ); + PythonQt::self()->evalScript( m_exceptionCxt, raise ); + } + return ec; +} diff --git a/src/libcalamaresui/viewpages/PythonQtUtilsWrapper.h b/src/libcalamaresui/viewpages/PythonQtUtilsWrapper.h new file mode 100644 index 000000000..4e3833833 --- /dev/null +++ b/src/libcalamaresui/viewpages/PythonQtUtilsWrapper.h @@ -0,0 +1,74 @@ +/* === This file is part of Calamares - === + * + * Copyright 2016, Teo Mrnjavac + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef PYTHONQTUTILSWRAPPER_H +#define PYTHONQTUTILSWRAPPER_H + +#include + +#include + + +class Utils : public QObject +{ + Q_OBJECT +public: + explicit Utils( QObject* parent = nullptr ); + virtual ~Utils() {} + +public slots: + void debug( const QString& s ) const; + + int mount( const QString& device_path, + const QString& mount_point, + const QString& filesystem_name, + const QString& options ) const; + + int target_env_call( const QString& command, + const QString& stdin = QString(), + int timeout = 0 ) const; + + int target_env_call( const QStringList& args, + const QString& stdin = QString(), + int timeout = 0 ) const; + + int check_target_env_call( const QString& command, + const QString& stdin = QString(), + int timeout = 0 ) const; + + int check_target_env_call( const QStringList& args, + const QString& stdin = QString(), + int timeout = 0 ) const; + + QString check_target_env_output( const QString& command, + const QString& stdin = QString(), + int timeout = 0 ) const; + + QString check_target_env_output( const QStringList& args, + const QString& stdin = QString(), + int timeout = 0 ) const; + + QString obscure( const QString& string ) const; + +private: + inline int _handle_check_target_env_call_error( int ec, const QString& cmd ) const; + + PythonQtObjectPtr m_exceptionCxt; +}; + +#endif // PYTHONQTUTILSWRAPPER_H diff --git a/src/libcalamaresui/viewpages/PythonQtViewStep.cpp b/src/libcalamaresui/viewpages/PythonQtViewStep.cpp new file mode 100644 index 000000000..ab34fdeca --- /dev/null +++ b/src/libcalamaresui/viewpages/PythonQtViewStep.cpp @@ -0,0 +1,206 @@ +/* === This file is part of Calamares - === + * + * Copyright 2016, Teo Mrnjavac + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "PythonQtViewStep.h" +#include "utils/Logger.h" +#include "utils/CalamaresUtilsGui.h" +#include "utils/PythonQtUtils.h" +#include "utils/Retranslator.h" +#include "viewpages/PythonQtJob.h" + +#include + +#include +#include + + +namespace Calamares +{ + +PythonQtViewStep::PythonQtViewStep( PythonQtObjectPtr cxt, + QObject* parent ) + : ViewStep( parent ) + , m_widget( new QWidget() ) + , m_cxt( cxt ) +{ + PythonQt* pq = PythonQt::self(); + Q_ASSERT( pq ); + + // The @calamares_module decorator should have filled _calamares_module_typename + // for us. + QString className = m_cxt.getVariable( "_calamares_module_typename" ).toString(); + + // Instantiate an object of the class marked with @calamares_module and + // store it as _calamares_module. + pq->evalScript( m_cxt, QString( "_calamares_module = %1()" ) + .arg( className ) ); + m_obj = pq->lookupObject( m_cxt, "_calamares_module" ); + + Q_ASSERT( !m_obj.isNull() ); // no entry point, no party + + // Prepare the base widget for the module's pages + m_widget->setLayout( new QVBoxLayout ); + CalamaresUtils::unmarginLayout( m_widget->layout() ); + m_cxt.addObject( "_calamares_module_basewidget", m_widget ); + + CALAMARES_RETRANSLATE( + CalamaresUtils::lookupAndCall( m_obj, { "retranslate" } ); + ) +} + + +QString +PythonQtViewStep::prettyName() const +{ + return CalamaresUtils::lookupAndCall( m_obj, + { "prettyName", + "prettyname", + "pretty_name" } ).toString(); +} + + +QWidget* +PythonQtViewStep::widget() +{ + if ( m_widget->layout()->count() > 1 ) + cDebug() << "WARNING: PythonQtViewStep wrapper widget has more than 1 child. " + "This should never happen."; + + bool nothingChanged = m_cxt.evalScript( + "_calamares_module.widget() in _calamares_module_basewidget.children()" ).toBool(); + if ( nothingChanged ) + return m_widget; + + // Else, we either don't have a child widget, or we have a child widget that + // was previously set and doesn't apply any more since the Python module + // set a new one. + + // First we clear the layout, which should only ever have 1 item. + // We only remove from the layout and not delete because Python is in charge + // of memory management for these widgets. + while ( m_widget->layout()->itemAt( 0 ) ) + m_widget->layout()->takeAt( 0 ); + + m_cxt.evalScript( + "_calamares_module_basewidget.layout().addWidget(_calamares_module.widget())" ); + + return m_widget; +} + + +void +PythonQtViewStep::next() +{ + CalamaresUtils::lookupAndCall( m_obj, { "next" } ); +} + + +void +PythonQtViewStep::back() +{ + CalamaresUtils::lookupAndCall( m_obj, { "back" } ); +} + + +bool +PythonQtViewStep::isNextEnabled() const +{ + return CalamaresUtils::lookupAndCall( m_obj, + { "isNextEnabled", + "isnextenabled", + "is_next_enabled" } ).toBool(); +} + + +bool +PythonQtViewStep::isBackEnabled() const +{ + return CalamaresUtils::lookupAndCall( m_obj, + { "isBackEnabled", + "isbackenabled", + "is_back_enabled" } ).toBool(); +} + + +bool +PythonQtViewStep::isAtBeginning() const +{ + return CalamaresUtils::lookupAndCall( m_obj, + { "isAtBeginning", + "isatbeginning", + "is_at_beginning" } ).toBool(); +} + + +bool +PythonQtViewStep::isAtEnd() const +{ + return CalamaresUtils::lookupAndCall( m_obj, + { "isAtEnd", + "isatend", + "is_at_end" } ).toBool(); +} + + +QList< Calamares::job_ptr > +PythonQtViewStep::jobs() const +{ + QList< Calamares::job_ptr > jobs; + + PythonQtObjectPtr jobsCallable = PythonQt::self()->lookupCallable( m_obj, "jobs" ); + if ( jobsCallable.isNull() ) + return jobs; + + PythonQtObjectPtr response = PythonQt::self()->callAndReturnPyObject( jobsCallable ); + if ( response.isNull() ) + return jobs; + + PythonQtObjectPtr listPopCallable = PythonQt::self()->lookupCallable( response, "pop" ); + if ( listPopCallable.isNull() ) + return jobs; + + forever + { + PythonQtObjectPtr aJob = PythonQt::self()->callAndReturnPyObject( listPopCallable, { 0 } ); + if ( aJob.isNull() ) + break; + + jobs.append( Calamares::job_ptr( new PythonQtJob( m_cxt, aJob ) ) ); + } + + return jobs; +} + + +void +PythonQtViewStep::setConfigurationMap( const QVariantMap& configurationMap ) +{ + m_obj.addVariable( "configuration", configurationMap ); +} + + +QWidget* +PythonQtViewStep::createScriptingConsole() +{ + PythonQtScriptingConsole* console = new PythonQtScriptingConsole( nullptr, m_cxt ); + console->setProperty( "classname", + m_cxt.getVariable( "_calamares_module_typename" ).toString() ); + return console; +} + +} diff --git a/src/libcalamaresui/viewpages/PythonQtViewStep.h b/src/libcalamaresui/viewpages/PythonQtViewStep.h new file mode 100644 index 000000000..e1f8bd1e5 --- /dev/null +++ b/src/libcalamaresui/viewpages/PythonQtViewStep.h @@ -0,0 +1,65 @@ +/* === This file is part of Calamares - === + * + * Copyright 2016, Teo Mrnjavac + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef PYTHONQTVIEWSTEP_H +#define PYTHONQTVIEWSTEP_H + +#include "ViewStep.h" + +#include + +namespace Calamares +{ + +class PythonQtViewStep : public Calamares::ViewStep +{ + Q_OBJECT +public: + PythonQtViewStep( PythonQtObjectPtr cxt, + QObject* parent = nullptr ); + + QString prettyName() const override; + + QWidget* widget() override; + + void next() override; + void back() override; + + bool isNextEnabled() const override; + bool isBackEnabled() const override; + + bool isAtBeginning() const override; + bool isAtEnd() const override; + + QList< Calamares::job_ptr > jobs() const override; + + void setConfigurationMap( const QVariantMap& configurationMap ) override; + + QWidget* createScriptingConsole(); + +protected: + QWidget* m_widget; + +private: + PythonQtObjectPtr m_cxt; + PythonQtObjectPtr m_obj; +}; + +} + +#endif // PYTHONQTVIEWSTEP_H diff --git a/src/modules/dummypythonqt/dummypythonqt.conf b/src/modules/dummypythonqt/dummypythonqt.conf new file mode 100644 index 000000000..f60e778e1 --- /dev/null +++ b/src/modules/dummypythonqt/dummypythonqt.conf @@ -0,0 +1,18 @@ +--- +syntax: "YAML map of anything" +example: + whats_this: "module-specific configuration" + from_where: "dummypythonqt.conf" +a_list: + - "item1" + - "item2" + - "item3" + - "item4" +a_list_of_maps: + - name: "an Item" + contents: + - "an element" + - "another element" + - name: "another item" + contents: + - "not much" diff --git a/src/modules/dummypythonqt/main.py b/src/modules/dummypythonqt/main.py new file mode 100644 index 000000000..acb125609 --- /dev/null +++ b/src/modules/dummypythonqt/main.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# === This file is part of Calamares - === +# +# Copyright 2016, Teo Mrnjavac +# +# Calamares is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Calamares is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Calamares. If not, see . + +import platform + +from PythonQt.QtGui import * +import PythonQt.calamares as calamares + +# Set up translations. +# You may skip this if your Calamares module has no user visible strings. +# DO NOT install _ into the builtin namespace because each module loads +# its own catalog. +# DO use the gettext class-based API and manually alias _ as described in: +# https://docs.python.org/3.5/library/gettext.html#localizing-your-module +import gettext +import inspect +import os +filename = inspect.getframeinfo(inspect.currentframe()).filename +path = os.path.dirname(os.path.abspath(filename)) +# t = gettext.translation('dummypythonqt', +# os.path.join(path, 'lang'), +# languages=['en']) +#_ = t.lgettext +_ = gettext.gettext + +# Example Python ViewModule. +# A Python ViewModule is a Python program which defines a ViewStep class. +# This class must be marked with the @calamares_module decorator. A +# ViewModule may define other classes, but only one may be decorated with +# @calamares_module. Such a class must conform to the Calamares ViewStep +# interface and functions as the entry point of the module. +# A ViewStep manages one or more "wizard pages" through methods like +# back/next, and reports its status through isNextEnabled/isBackEnabled/ +# isAtBeginning/isAtEnd. The whole UI, including all the pages, must be +# exposed as a single QWidget, returned by the widget function. +@calamares_module +class DummyPythonQtViewStep(): + def __init__(self): + self.main_widget = QFrame() + + self.main_widget.setLayout(QVBoxLayout()) + + label = QLabel() + self.main_widget.layout().addWidget(label) + + accumulator = "\nCalamares+PythonQt running embedded Python " +\ + platform.python_version() + label.text = accumulator + + btn = QPushButton() + btn.setText(_("Click me!")) + self.main_widget.layout().addWidget(btn) + btn.connect("clicked(bool)", self.on_btn_clicked) + + def on_btn_clicked(self): + self.main_widget.layout().addWidget(QLabel("A new QLabel.")) + + def prettyName(self): + return "Dummy PythonQt ViewStep" + + def isNextEnabled(self): + return True + + def isBackEnabled(self): + return True + + def isAtBeginning(self): + return True + + def isAtEnd(self): + return True + + def jobs(self): + return [DummyPQJob("hi there lol")] + + def widget(self): + return self.main_widget + + +class DummyPQJob(): + def __init__(self, my_msg): + self.my_msg = my_msg + + def pretty_name(self): + return _("The Dummy PythonQt Job") + + def pretty_description(self): + return _("This description says that the Dummy PythonQt Job is a dummy. " + "The dummy job says: {}".format(self.my_msg)) + + def pretty_status_message(self): + return _("A status message for DPQ Job.") + + def exec(self): + rmp = calamares.global_storage['rootMountPoint'] + os.system("touch {}/calamares_dpqt_was_here".format(rmp)) + calamares.utils.debug("the dummy job says {}".format(self.my_msg)) + return {'ok': True} diff --git a/src/modules/dummypythonqt/module.desc b/src/modules/dummypythonqt/module.desc new file mode 100644 index 000000000..46633a6db --- /dev/null +++ b/src/modules/dummypythonqt/module.desc @@ -0,0 +1,7 @@ +# Module metadata file for dummy pythonqt jobmodule +# Syntax is YAML 1.2 +--- +type: "view" +name: "dummypythonqt" +interface: "pythonqt" +script: "main.py" #assumed relative to the current directory