From 8fcb608f8bf0d6d0743d942251f174da9677f523 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 22 Sep 2022 13:41:58 +1000 Subject: [PATCH 01/42] WIP --- CMakeLists.txt | 5 ++ cmake_templates/qgsconfig.h.in | 2 + src/app/CMakeLists.txt | 92 +++++++++++++++++++++++++++++ src/app/qgisapp.cpp | 16 +++++ src/app/qgisapp.h | 3 + src/app/rstats/qgsrstatsconsole.cpp | 53 +++++++++++++++++ src/app/rstats/qgsrstatsconsole.h | 38 ++++++++++++ src/app/rstats/qgsrstatsrunner.cpp | 83 ++++++++++++++++++++++++++ src/app/rstats/qgsrstatsrunner.h | 40 +++++++++++++ 9 files changed, 332 insertions(+) create mode 100644 src/app/rstats/qgsrstatsconsole.cpp create mode 100644 src/app/rstats/qgsrstatsconsole.h create mode 100644 src/app/rstats/qgsrstatsrunner.cpp create mode 100644 src/app/rstats/qgsrstatsrunner.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 963406ebd5b0..af2af85dd69b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,6 +194,11 @@ if(WITH_CORE) endif() endif() + set (WITH_R FALSE CACHE BOOL "Determines whether the inbuilt R integration should be built") + if(WITH_R) + set(HAVE_R TRUE) # used in qgisconfig.h + endif() + # server disabled default because it needs FastCGI (which is optional dependency) set (WITH_SERVER FALSE CACHE BOOL "Determines whether QGIS server should be built") if(WITH_SERVER) diff --git a/cmake_templates/qgsconfig.h.in b/cmake_templates/qgsconfig.h.in index b6002acd0357..8e058ef6ca48 100644 --- a/cmake_templates/qgsconfig.h.in +++ b/cmake_templates/qgsconfig.h.in @@ -116,5 +116,7 @@ #cmakedefine HAVE_CRASH_HANDLER +#cmakedefine HAVE_R + #endif diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index e9f6d1b07804..73d89e717bd2 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -409,12 +409,21 @@ else() set(QWTPOLAR_INCLUDE_DIR "") endif() + if (POSTGRES_FOUND) if(HAVE_PGCONFIG) add_definitions(-DHAVE_PGCONFIG=1) endif() endif() +if(HAVE_R) + set(QGIS_APP_SRCS + ${QGIS_APP_SRCS} + rstats/qgsrstatsrunner.cpp + rstats/qgsrstatsconsole.cpp + ) +endif() + # Test data dir for QgsAppScreenShots add_definitions(-DTEST_DATA_DIR="${TEST_DATA_DIR}") @@ -656,6 +665,89 @@ if (WITH_PDAL) target_link_libraries(qgis_app ${PDAL_LIBRARIES}) endif() +if (WITH_R) + execute_process(COMMAND R RHOME + OUTPUT_VARIABLE R_HOME) + set(NUM_TRUNC_CHARS 2) + + execute_process(COMMAND R CMD config --cppflags + OUTPUT_VARIABLE R_INCLUDE_DIRS) + string(REGEX REPLACE "^-I" "" R_INCLUDE_DIRS "${R_INCLUDE_DIRS}") + string(STRIP ${R_INCLUDE_DIRS} R_INCLUDE_DIRS ) + target_include_directories(qgis_app SYSTEM PUBLIC + ${R_INCLUDE_DIRS} + ) + + execute_process(COMMAND R CMD config --ldflags + OUTPUT_VARIABLE RLDFLAGS) + if (${RLDFLAGS} MATCHES "[-][L]([^ ;])+") + string(SUBSTRING ${CMAKE_MATCH_0} ${NUM_TRUNC_CHARS} -1 RLDFLAGS_L) + string(STRIP ${RLDFLAGS_L} R_LIB_DIR ) + message(STATUS "Found R libs: ${R_LIB_DIR}") + #target_link_directories(qgis_app PUBLIC ${R_LIB_DIR}) +target_link_libraries(qgis_app /usr/lib64/R/lib/libR.so) +endif() + + if (${RLDFLAGS} MATCHES "[-][l]([^;])+") + string(SUBSTRING ${CMAKE_MATCH_0} ${NUM_TRUNC_CHARS} -1 RLDFLAGS_l) + string(STRIP ${RLDFLAGS_l} RLDFLAGS_l ) + endif() + + execute_process(COMMAND Rscript -e "Rcpp:::CxxFlags()" + OUTPUT_VARIABLE R_CPP_INCLUDE_DIRS) + string(REGEX REPLACE "^-I" "" R_CPP_INCLUDE_DIRS "${R_CPP_INCLUDE_DIRS}") + string(STRIP ${R_CPP_INCLUDE_DIRS} R_CPP_INCLUDE_DIRS ) + string(REGEX REPLACE "^\"" "" R_CPP_INCLUDE_DIRS "${R_CPP_INCLUDE_DIRS}") + string(REGEX REPLACE "\"$" "" R_CPP_INCLUDE_DIRS "${R_CPP_INCLUDE_DIRS}") + target_include_directories(qgis_app SYSTEM PUBLIC + ${R_CPP_INCLUDE_DIRS} + ) + + # execute_process(COMMAND Rscript -e "Rcpp:::LdFlags()" + # OUTPUT_VARIABLE R_CPP_LIBS) + # if (${R_CPP_LIBS} MATCHES "[-][L]([^ ;])+") + # string(SUBSTRING ${CMAKE_MATCH_0} ${NUM_TRUNC_CHARS} -1 R_CPP_LIBS_L) + # message(STATUS "Found R cpp libs: ${R_CPP_LIBS_L}") + #target_link_directories(qgis_app PUBLIC ${R_CPP_LIBS_L} ) + #endif() + + if (${R_CPP_LIBS} MATCHES "[-][l][R]([^;])+") + string(SUBSTRING ${CMAKE_MATCH_0} ${NUM_TRUNC_CHARS} -1 R_CPP_LIBS_l) + message(STATUS "Found R cpp libs: ${R_CPP_LIBS_l}") + target_link_libraries(qgis_app ${R_CPP_LIBS_l}) + endif() +# target_link_libraries(qgis_app /usr/lib64/R/library/Rcpp/libs/Rcpp.so) +# add_library(rcpp_lib SHARED IMPORTED) +# set_target_properties(rcpp_lib PROPERTIES +# IMPORTED_LOCATION /usr/lib64/R/library/Rcpp/libs/Rcpp.so +# IMPORTED_NO_SONAME FALSE +# ) +#target_link_libraries(qgis_app rcpp_lib) + + execute_process(COMMAND Rscript -e "RInside:::CxxFlags()" + OUTPUT_VARIABLE R_INSIDE_INCLUDE_DIRS) + string(REGEX REPLACE "^-I" "" R_INSIDE_INCLUDE_DIRS "${R_INSIDE_INCLUDE_DIRS}") + string(STRIP ${R_INSIDE_INCLUDE_DIRS} R_INSIDE_INCLUDE_DIRS ) + target_include_directories(qgis_app SYSTEM PUBLIC + ${R_INSIDE_INCLUDE_DIRS} + ) + + execute_process(COMMAND Rscript -e "RInside:::LdFlags()" + OUTPUT_VARIABLE R_INSIDE_LIBS) + if (${R_INSIDE_LIBS} MATCHES "[-][L]([^ ;])+") + string(SUBSTRING ${CMAKE_MATCH_0} ${NUM_TRUNC_CHARS} -1 R_INSIDE_LIBS_L) + message(STATUS "Found R-inside libs: ${R_INSIDE_LIBS_L}") + target_link_directories(qgis_app PUBLIC ${R_INSIDE_LIBS_L}) + endif() + + if (${R_INSIDE_LIBS} MATCHES "[-][l][R]([^;])+") + string(SUBSTRING ${CMAKE_MATCH_0} ${NUM_TRUNC_CHARS} -1 R_INSIDE_LIBS_l) + message(STATUS "Found R-inside libs: ${R_INSIDE_LIBS_l}") + target_link_libraries(qgis_app ${R_INSIDE_LIBS_l}) + endif() + +endif() + if(MSVC) install(FILES qgis.ico qgis-mime.ico qgis-qgs.ico qgis-qlr.ico qgis-qml.ico qgis-qpt.ico DESTINATION ${CMAKE_INSTALL_PREFIX}/icons) endif() diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 15400a0136dd..87cbf8a72fe9 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -526,6 +526,11 @@ extern "C" #include "qgspythonutils.h" #endif +#ifdef HAVE_R +#include "rstats/qgsrstatsrunner.h" +#include "rstats/qgsrstatsconsole.h" +#endif + #ifndef Q_OS_WIN #include #else @@ -1588,6 +1593,12 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipBadLayers mActionShowPythonDialog = nullptr; } +#ifdef HAVE_R + mRStatsRunner = new QgsRStatsRunner(); + QgsRStatsConsole *rConsole = new QgsRStatsConsole( nullptr, mRStatsRunner ); + rConsole->show(); +#endif + // Update recent project list (as possible custom project storages are now registered by plugins) mSplash->showMessage( tr( "Updating recent project paths" ), Qt::AlignHCenter | Qt::AlignBottom ); qApp->processEvents(); @@ -1947,6 +1958,11 @@ QgisApp::~QgisApp() } #endif +#ifdef HAVE_R + delete mRStatsRunner; + mRStatsRunner = nullptr; +#endif + mNetworkLoggerWidgetFactory.reset(); delete mInternalClipboard; diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index e4567b5c020e..f7c5a3268aed 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -80,6 +80,7 @@ class QgsPrintLayout; class QgsProviderRegistry; class QgsProviderSublayerDetails; class QgsPythonUtils; +class QgsRStatsRunner; class QgsRasterLayer; class QgsRectangle; class QgsRuntimeProfiler; @@ -2517,6 +2518,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow QgsPythonUtils *mPythonUtils = nullptr; + QgsRStatsRunner *mRStatsRunner = nullptr; + static QgisApp *sInstance; QgsUndoWidget *mUndoWidget = nullptr; diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp new file mode 100644 index 000000000000..dbba067ec399 --- /dev/null +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -0,0 +1,53 @@ +/*************************************************************************** + qgsrstatsconsole.cpp + -------------- + begin : September 2022 + copyright : (C) 2022 Nyall Dawson + email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#include "qgsrstatsconsole.h" +#include "qgsrstatsrunner.h" + +#include +#include +#include +#include + +QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) + : QWidget( parent ) + , mRunner( runner ) +{ + QVBoxLayout *vl = new QVBoxLayout(); + mOutput = new QTextBrowser(); + vl->addWidget( mOutput, 1 ); + mInputEdit = new QLineEdit(); + vl->addWidget( mInputEdit ); + QPushButton *run = new QPushButton( "go" ); + connect( run, &QPushButton::clicked, this, [ = ] + { + const QString command = mInputEdit->text(); + QString error; + const QVariant out = mRunner->execCommand( command, error ); + if ( !error.isEmpty() ) + { + mOutput->setHtml( mOutput->toHtml() + QStringLiteral( "

%1

" ).arg( error ) ); + } + else + { + mOutput->append( out.toString() ); + } + } ); + + vl->addWidget( run ); + setLayout( vl ); + +} diff --git a/src/app/rstats/qgsrstatsconsole.h b/src/app/rstats/qgsrstatsconsole.h new file mode 100644 index 000000000000..d1c0f4645104 --- /dev/null +++ b/src/app/rstats/qgsrstatsconsole.h @@ -0,0 +1,38 @@ +/*************************************************************************** + qgsrstatsconsole.h + -------------- + begin : September 2022 + copyright : (C) 2022 Nyall Dawson + email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#ifndef QGSRSTATSCONSOLE_H +#define QGSRSTATSCONSOLE_H + +#include + +class QgsRStatsRunner; +class QLineEdit; +class QTextBrowser; + +class QgsRStatsConsole: public QWidget +{ + public: + QgsRStatsConsole( QWidget* parent, QgsRStatsRunner* runner ); + + private: + + QgsRStatsRunner* mRunner = nullptr; + QLineEdit* mInputEdit = nullptr; + QTextBrowser* mOutput = nullptr; +}; + +#endif // QGSRSTATSCONSOLE_H diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp new file mode 100644 index 000000000000..37bd131f5ecd --- /dev/null +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -0,0 +1,83 @@ +#include "qgsrstatsrunner.h" +#include "qgsapplication.h" + +#include +#include + + +#include "qgslogger.h" +#include +#include +#include +#include + +QgsRStatsRunner::QgsRStatsRunner() +{ + mRSession = std::make_unique< RInside >( 0, nullptr, false, false, true ); + + const QString userPath = QgsApplication::qgisSettingsDirPath() + QStringLiteral( "r_libs" ); + if ( !QFile::exists( userPath ) ) + { + QDir().mkpath( userPath ); + } + QString error; + execCommand( QStringLiteral( ".libPaths(\"%1\")" ).arg( userPath ), error ); + + + + //( *mRSession )["val"] = 5; + //mRSession->parseEvalQ( "val2<-7" ); + +// double aDouble = Rcpp::as( mRSession->parseEval( "1+2" ) ); +// std::string aString = Rcpp::as( mRSession->parseEval( "'asdasdas'" ) ); + +// R.parseEvalQ( "cat(txt)" ); +// QgsDebugMsg( QString::fromStdString( Rcpp::as( R.parseEval( "cat(txt)" ) ) ) ); +// QgsDebugMsg( QString::fromStdString( Rcpp::as( mRSession->parseEval( "as.character(val+2)" ) ) ) ); +// QgsDebugMsg( QStringLiteral( "val as double: %1" ).arg( Rcpp::as( mRSession->parseEval( "val+val2" ) ) ) ); + +} + +QVariant QgsRStatsRunner::execCommand( const QString &command, QString &error ) +{ + try + { + SEXP res = mRSession->parseEval( command.toStdString() ); + + switch ( TYPEOF( res ) ) + { + case NILSXP: + return QVariant(); + + case LGLSXP: + return Rcpp::as( res ); + + case INTSXP: + return Rcpp::as( res ); + + case REALSXP: + return Rcpp::as( res ); + + case STRSXP: + return QString::fromStdString( Rcpp::as( res ) ); + + //case RAWSXP: + // return R::rawPointer( res ); + + default: + QgsDebugMsg( "Unhandledtype!!!" ); + return QVariant(); + } + } + catch ( std::exception &ex ) + { + error = QString::fromStdString( ex.what() ); + } + catch ( ... ) + { + std::cerr << "Unknown exception caught" << std::endl; + } + return QVariant(); +} + +QgsRStatsRunner::~QgsRStatsRunner() = default; diff --git a/src/app/rstats/qgsrstatsrunner.h b/src/app/rstats/qgsrstatsrunner.h new file mode 100644 index 000000000000..0efa64e94816 --- /dev/null +++ b/src/app/rstats/qgsrstatsrunner.h @@ -0,0 +1,40 @@ +/*************************************************************************** + qgsrstatsrunner.h + -------------- + begin : September 2022 + copyright : (C) 2022 Nyall Dawson + email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#ifndef QGSRSTATSRUNNER_H +#define QGSRSTATSRUNNER_H + +#include + +class RInside; +class QVariant; +class QString; + +class QgsRStatsRunner +{ + public: + + QgsRStatsRunner(); + ~QgsRStatsRunner(); + + QVariant execCommand( const QString& command, QString& error ); + + private: + + std::unique_ptr< RInside > mRSession; +}; + +#endif // QGSRSTATSRUNNER_H From ddf5ff00268a7e37d334929b2d40f1a27a53f10f Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 22 Sep 2022 17:29:14 +1000 Subject: [PATCH 02/42] Vendor rinside --- external/r_inside/README.md | 62 + external/r_inside/inst/NEWS.Rd | 281 ++++ external/r_inside/inst/THANKS | 18 + external/r_inside/inst/include/Callbacks.h | 70 + external/r_inside/inst/include/MemBuf.h | 34 + external/r_inside/inst/include/RInside.h | 103 ++ .../r_inside/inst/include/RInsideAutoloads.h | 1191 +++++++++++++++++ .../r_inside/inst/include/RInsideCommon.h | 73 + .../r_inside/inst/include/RInsideConfig.h | 26 + .../r_inside/inst/include/RInsideEnvVars.h | 24 + external/r_inside/inst/include/RInside_C.h | 32 + external/r_inside/src/Makevars | 57 + external/r_inside/src/Makevars.win | 60 + external/r_inside/src/MemBuf.cpp | 53 + external/r_inside/src/RInside.cpp | 531 ++++++++ external/r_inside/src/RInside_C.cpp | 54 + external/r_inside/src/RcppExports.cpp | 27 + external/r_inside/src/compiler.cpp | 17 + external/r_inside/src/setenv/setenv.c | 87 ++ .../r_inside/src/tools/RInsideAutoloads.r | 27 + external/r_inside/src/tools/RInsideEnvVars.r | 19 + external/r_inside/src/tools/unix2dos.r | 17 + src/app/CMakeLists.txt | 67 +- src/app/rstats/qgsrstatsconsole.cpp | 10 +- src/app/rstats/qgsrstatsrunner.cpp | 11 +- src/app/rstats/qgsrstatsrunner.h | 18 +- 26 files changed, 2945 insertions(+), 24 deletions(-) create mode 100644 external/r_inside/README.md create mode 100644 external/r_inside/inst/NEWS.Rd create mode 100644 external/r_inside/inst/THANKS create mode 100644 external/r_inside/inst/include/Callbacks.h create mode 100644 external/r_inside/inst/include/MemBuf.h create mode 100644 external/r_inside/inst/include/RInside.h create mode 100644 external/r_inside/inst/include/RInsideAutoloads.h create mode 100644 external/r_inside/inst/include/RInsideCommon.h create mode 100644 external/r_inside/inst/include/RInsideConfig.h create mode 100644 external/r_inside/inst/include/RInsideEnvVars.h create mode 100644 external/r_inside/inst/include/RInside_C.h create mode 100644 external/r_inside/src/Makevars create mode 100644 external/r_inside/src/Makevars.win create mode 100644 external/r_inside/src/MemBuf.cpp create mode 100644 external/r_inside/src/RInside.cpp create mode 100644 external/r_inside/src/RInside_C.cpp create mode 100644 external/r_inside/src/RcppExports.cpp create mode 100644 external/r_inside/src/compiler.cpp create mode 100644 external/r_inside/src/setenv/setenv.c create mode 100755 external/r_inside/src/tools/RInsideAutoloads.r create mode 100755 external/r_inside/src/tools/RInsideEnvVars.r create mode 100644 external/r_inside/src/tools/unix2dos.r diff --git a/external/r_inside/README.md b/external/r_inside/README.md new file mode 100644 index 000000000000..2090f049018f --- /dev/null +++ b/external/r_inside/README.md @@ -0,0 +1,62 @@ +## RInside: Easy embedding of R inside C++ (and C) + +[![CI](https://github.com/eddelbuettel/rinside/workflows/ci/badge.svg)](https://github.com/eddelbuettel/rinside/actions?query=workflow%3Aci) +[![License](http://img.shields.io/badge/license-GPL%20%28%3E=%202%29-brightgreen.svg?style=flat)](http://www.gnu.org/licenses/gpl-2.0.html) +[![CRAN](http://www.r-pkg.org/badges/version/RInside)](https://cran.r-project.org/package=RInside) +[![Dependencies](https://tinyverse.netlify.com/badge/RInside)](https://cran.r-project.org/package=RInside) +[![Debian package](https://img.shields.io/debian/v/r-cran-rinside/sid?color=brightgreen)](https://packages.debian.org/sid/r-cran-rinside) +[![Downloads](http://cranlogs.r-pkg.org/badges/RInside?color=brightgreen)](https://cran.r-project.org/package=RInside) +[![Last Commit](https://img.shields.io/github/last-commit/eddelbuettel/rinside)](https://github.com/eddelbuettel/rinside) + +### About + +The RInside package provides a few classes for seamless embedding of [R](https://www.r-project.org) inside of +C++ applications by relying on [Rcpp](https://www.rcpp.org/). + +### Examples + +Provided with the package itself are nine subdirectories with examples: from more than a dozen basic command-line examples (in directory +`standard`) to graphical user-interfaces (using both [Qt](https://www.qt.io/) and [Wt](https://www.webtoolkit.eu/wt)), linear algebra with +[Armadillo](http://arma.sourceforge.net/) and [Eigen](http://eigen.tuxfamily.org/index.php?title=Main_Page), parallel computing with MPI to a +sandboxed server, and (since release 0.2.16) a simple (and more limited) interface for embedding insice C applications. + +The simplest example (modulo its header) is [examples/standard/rinside_sample0.cpp](inst/examples/standard/rinside_sample0.cpp) + +```c++ +#include // for the embedded R via RInside + +int main(int argc, char *argv[]) { + + RInside R(argc, argv); // create an embedded R instance + + R["txt"] = "Hello, world!\n"; // assign a char* (string) to 'txt' + + R.parseEvalQ("cat(txt)"); // eval the init string, ignoring any returns + + exit(0); +} +``` +The [Qt example directory](https://github.com/eddelbuettel/rinside/tree/master/inst/examples/qt) produces +this application for showing how to use R (to estimate densities) inside a C++ executable (providing the GUI): + +![](https://github.com/eddelbuettel/rinside/blob/master/local/qtdensitySVG.png) + +The code is portable across operating systems. Similar, the +[Wt example directory](https://github.com/eddelbuettel/rinside/tree/master/inst/examples/wt) +contains this C++-based web application doing the same: + +![](https://github.com/eddelbuettel/rinside/blob/master/local/wtdensity.png) + + +### See Also + +The [RInside](http://dirk.eddelbuettel.com/code/rinside.html) web page has +some more details. + +### Authors + +Dirk Eddelbuettel, Romain Francois, and Lance Bachmeier + +### License + +GPL (>= 2) diff --git a/external/r_inside/inst/NEWS.Rd b/external/r_inside/inst/NEWS.Rd new file mode 100644 index 000000000000..0486af01dd9d --- /dev/null +++ b/external/r_inside/inst/NEWS.Rd @@ -0,0 +1,281 @@ +\name{NEWS} +\title{News for Package \pkg{RInside}} +\newcommand{\ghpr}{\href{https://github.com/eddelbuettel/rinside/pull/#1}{##1}} +\newcommand{\ghit}{\href{https://github.com/eddelbuettel/rinside/issues/#1}{##1}} + +\section{Changes in RInside version 0.2.17 (2022-03-31)}{ + \itemize{ + \item A Windows-only patch for R 4.2.0 kindly provided by Tomas + Kalibera was applied, and also conditioned on R (>= 4.2.0) + \item Continuous Integration setup was updated and now uses + \href{https://eddelbuettel.github.io/r-ci/}{r-ci}. + \item Several updates were made to README.md (badges etc) and + DESCRIPTION + } +} + +\section{Changes in RInside version 0.2.16 (2020-03-12)}{ + \itemize{ + \item RInside is now embeddable (with a reduced interface) from C + applications thanks to Lance Bachmeier (who is now co-author) plus + some polish by Dirk in \ghpr{43}) + \item Added \code{R_SESSION_INIIALIZED} to list of excluded variables. + \item Added simple diagnostics function to have a registered function. + } +} + +\section{Changes in RInside version 0.2.15 (2019-03-06)}{ + \itemize{ + \item Improved Windows build support by copying + \code{getenv("R_HOME")} result and improving backslash handling in + environemt variable setting (Jonathon Love in \ghpr{27} and + \ghpr{28}) + \item Improved Windows build support by quote-protecting + \code{Rscript} path in \code{Makevars.win} (François-David Collin in + \ghpr{33}) + \item A URL was corrected in README.md (Zé Vinícius in \ghpr{34}). + \item Temporary \code{SEXP} objects are handled more carefully at + initialization to satisfy `rchk` (Dirk in \ghpr{36}) + } +} + +\section{Changes in RInside version 0.2.14 (2017-04-28)}{ + \itemize{ + \item Interactive mode can use readline REPL (Łukasz Łaniewski-Wołłk + in \ghpr{25}, and Dirk in \ghpr{26}) + \item Windows macros checks now uses \code{_WIN32} (Kevin Ushey in + \ghpr{22}) + \item The wt example now links with \code{libboost_system} + \item The \code{Makevars} file is now more robist (Mattias Ellert in + \ghpr{21}) + \item A problem with empty environment variable definitions on + Windows was addressed (Jeroen Ooms in \ghpr{17} addressing \ghit{16}) + \item \code{HAVE_UINTPTR_T} is defined only if not already defined + \item Travis CI is now driven via \code{run.sh} from our forked r-travis + } +} + +\section{Changes in RInside version 0.2.13 (2015-05-20)}{ + \itemize{ + \item Added workaround for a bug in R 3.2.0: by including the file + \code{RInterface.h} only once we do not getting linker errors due to + multiple definitions of \code{R_running_as_main_program} (which is now + addressed in R-patched as well). + \item Small improvements to the Travis CI script. + } +} + +\section{Changes in RInside version 0.2.12 (2015-01-27)}{ + \itemize{ + \item Several new examples have been added (with most of the work + done by Christian Authmann): + \itemize{ + \item \code{standard/rinside_sample15.cpp} shows how to create a + lattice plot (following a StackOverflow question) + \item \code{standard/rinside_sample16.cpp} shows object wrapping, + and exposing of C++ functions + \item \code{standard/rinside_sample17.cpp} does the same via C++11 + \item \code{sandboxed_servers/} adds an entire framework of + client/server communication outside the main process (but using a + subset of supported types) + } + \item \code{standard/rinside_module_sample9.cpp} was repaired + following a fix to \code{InternalFunction} in \CRANpkg{Rcpp} + \item For the seven example directories which contain a + \code{Makefile}, the \code{Makefile} was renamed \code{GNUmakefile} + to please \code{R CMD check} as well as the CRAN Maintainers. + } +} + +\section{Changes in RInside version 0.2.11 (2014-02-11)}{ + \itemize{ + \item Updated for \CRANpkg{Rcpp} 0.11.0: + \itemize{ + \item Updated initialization by assigning global environment via + pointer only after R itself has been initialized -- with special + thanks to Kevin Ushey for the fix + \item Updated \code{DESCRIPTION} with \code{Imports:} instead of + \code{Depends:} + \item Added correspondiing \code{importFrom(Rcpp, evalCpp)} to + \code{NAMESPACE} + \item Noted in all \code{inst/examples/*/Makefile} that + \CRANpkg{Rcpp} no longer requires a library argument, but left code for + backwards compatibility in case 0.11.0 is not yet installed. + } + \item Added \code{--vanilla --slave} to default arguments for R + initialization + \item Added a few more explicit \code{#include} statements in the \code{qt} + example which Qt 5.1 now appears to require -- with thanks to + Spencer Behling for the patch + \item Added new MPI example with worker functions and RInside + instance, kindly contributed by Nicholas Pezolano and Martin Morgan + } +} + +\section{Changes in RInside version 0.2.10 (2012-12-05)}{ + \itemize{ + \item Adjusted to change in R which requires turning checking of the + stack limit off in order to allow for access from multiple threads + as in the Wt examples. As there are have been no side-effects, this + is enabled by default on all platforms (with the exception of Windows). + \item Added new \sQuote{threads} example directory with a simple + example based on a Boost mutex example. + \item Disabled two examples (passing an external function down) + which do not currently work; external pointer use should still work. + } +} + +\section{Changes in RInside version 0.2.9 (2012-11-04)}{ + \itemize{ + \item Applied (modified) patch by Theodore Lytras which lets RInside + recover from some parsing errors and makes RInside applications more + tolerant of errors + \item Added non-throwing variants of parseEval() and parseEvalQ() + \item Modified Qt and Wt examples of density estimation applications + to be much more resilient to bad user input + \item On Windows, have RInside use R's get_R_HOME() function to get + R_HOME value from registry if not set by user + \item Added note to examples/standard/Makefile.win that R_HOME may + need to be set to run the executables -- so either export your local + value, or re-install RInside from source to have it reflected in the + library build of libRinside + \item Updated CMake build support for standard, armadillo and eigen + \item Improved CMake builds of examples/standard, examples/eigen and + examples/armadillo by detecting architecture + } +} +\section{Changes in RInside version 0.2.8 (2012-09-07)}{ + \itemize{ + \item Added CMake build support for armadillo and eigen examples, + once again kindly contributed by Peter Aberline + \item Corrected Windows package build to always generate a 64 bit + static library too + \item Updated package build to no longer require configure / configure.win to + update the two header file supplying compile-time information; + tightened build dependencies on headers in Makevars / Makevars.win + \item Improved examples/standard/Makefile.win by detecting architecture + } +} +\section{Changes in RInside version 0.2.7 (2012-08-12)}{ + \itemize{ + \item New fifth examples subdirectory 'armadillo' with two new + examples showing how to combine \CRANpkg{RInside} with \CRANpkg{RcppArmadillo} + \item New sixth examples subdirectory 'eigen' with two new examples + showing how to combine \CRANpkg{RInside} with \CRANpkg{RcppEigen} + \item Prettified the Wt example 'web application' with CSS use, also added + and XML file with simple headers and description text + \item New example rinside_sample12 motivated by StackOverflow + question on using \code{sample()} from C + \item Added CMake build support on Windows for the examples + } +} +\section{Changes in RInside version 0.2.6 (2012-01-11)}{ + \itemize{ + \item Correct Windows initialization by not using Rprintf in internal + console writer, with thanks to both James Bates and John Brzustowski + \item Update RNG seeding (used by tmpnam et al) to same scheme used by + R since 2.14.0: blending both millisecond time and process id + \item Added CMake build support for all four example directories as kindly + provided by Peter Aberline; this helps when writing RInside code + inside of IDEs such as Eclipse, KDevelop or Code::Blocks + \item Small update to standard examples Makefile for Windows permitting + to explicitly set i386 or x64 as a build architecture + } +} +\section{Changes in RInside version 0.2.5 (2011-12-07)}{ + \itemize{ + \item Applied (somewhat simplified) patch by James Bates which restores + RInside to working on Windows -- with a big Thank You! to James for + fixing a long-standing bug we inadvertendly introduced right after + 0.2.0 almost two years ago + \item New example embedding R inside a Wt (aka Webtoolkit, pronounced + 'witty') application, mirroring the previous Qt application + \item Qt example qtdensity now uses the new svg() device in base R; removed + test for cairoDevice package as well as fallback png code + \item Very minor fix to qmake.pro file for Qt app correcting link order + } +} +\section{Changes in RInside version 0.2.4 (2011-04-24)}{ + \itemize{ + \item Minor code cleanups in initialization code + \item New example embedding R inside a Qt application, along with pro file + for Qt's qmake providing a complete simple C++ GUI application + \item New examples rinside_sample\{10,11\} based on questions on the + r-help and r-devel mailing list + \item Some improvements and simplifications throughout examples/standard + as well as examples/mpi/ + \item Added this NEWS files -- with entries below summarised from ChangeLog + and the corresponding blog posts + } +} +\section{Changes in RInside version 0.2.3 (2010-08-06)}{ + \itemize{ + \item New example rinside_sample9 on how to expose C++ to embedded R + \item New example rinside_module_sample0 to show module access from RInside + \item Simplified rinside_sample3 and rinside_sample4 + \item Some code cleanup to help Solaris builds + \item Implicit use of new Proxy class with operator T(), see rinside_sample8 + } +} +\section{Changes in RInside version 0.2.2 (2010-03-22)}{ + \itemize{ + \item New operator[](string) lets RInside act as proxy to R's global + environment so that we can R["x"] = 10 to assign; all the actual + work is done by Rcpp::Environment + \item No longer ship doxygen-generated docs in build + \item Use std::string for all arguments inside throw() to help Windows build + \item Default to static linking on OS X and Windows just like Rcpp does + \item parseEval() now returns SEXP and has just a string argument for more + functional use; it and void sibbling parseEvalQ() now throw exections + \item rinside_sample\{2,4,5\} updated accordingly + \item Two new 'R inside an MPI app' examples contributed by Jianping Hua + \item Also added two C++ variants of the C examples for RInside and MPI + \item rinside_sample8 updated with parseEval changes + \item Internal MemBuf class simplified via STL std::string + \item Autoload simplied via the new Rcpp API + \item Added default constructor for RInside + \item Retire assign(vector >) via template specialisation + \item Include Rcpp.h; switch to Rf_ prefixed R API to avoid Redefine macros + \item Windows version currently segfaults on startup + } +} +\section{Changes in RInside version 0.2.1 (2010-01-06)}{ + \itemize{ + \item Startup now defaults to FALSE, no longer call Rf_KillAllDevices + \item Some minor build and code fixes for Windows + } +} +\section{Changes in RInside version 0.2.0 (2009-12-20)}{ + \itemize{ + \item Initial Windows support, with thanks to Richard Holbrey for both the + initial push and a setenv() implementation + \item Added Makefile.win for build with the MinGW toolchain to src/ and examples/ + \item Some improvements to destructor per example in Writing R Extensions + \item New rinside_sample5 based on r-devel post + } +} +\section{Changes in RInside version 0.1.1 (2009-02-19)}{ + \itemize{ + \item The examples/ Makefile now sets $R_HOME via 'R RHOME', and also employs + $R_ARCH for arch-dependent headers -- with thanks for Jeff, Jan and Simon + \item Added THANKS file to give recognition to those who helped RInside along + \item Added rinside_sample4 as another example based on an r-devel question + } +} +\section{Changes in RInside version 0.1.0 (2009-02-19)}{ + \itemize{ + \item Initial CRAN release + \item Improved build process + \item Added doxygen generated documentation + \item Added two more example + } +} +\section{Changes in RInside version 0.0.1 (2009-07-19)}{ + \itemize{ + \item Corrected error in memory buffer class with thanks to Miguel Lechón for + a finding the issue and sending a patch + \item Added two regression test examples to demonstrate bug and fix + \item Minor code cleanups + \item Initial version in SVN at R-Forge + } +} diff --git a/external/r_inside/inst/THANKS b/external/r_inside/inst/THANKS new file mode 100644 index 000000000000..2c1cac4cb1e2 --- /dev/null +++ b/external/r_inside/inst/THANKS @@ -0,0 +1,18 @@ + +Miguel Lechón for finding (and fixing!) a memory-management bug +Daniel F Schwarz for a patch to not override pre-set environment variables +Michael Kane for testing on RHEL +Jan de Leeuw for testing on OS X +Jeffrey Horner for finding and fixing an OS X build bug +Simon Urbanek for OS X (and general) build tips +Richard Holbrey for initial help with the the Windows build +Jianping Hua for contributing two MPI-based examples +Murray Stokely for a patch regarding timing of Rcpp autoloads +James Bates for a patch restoring RInside on Windows +John Brzustowski for a correction to the Windows initialization +Peter Aberline for contributing CMake support for all examples +Theodore Lytras for a patch helping to recover from (some) errors +Spencer Behling for a patch getting the Qt example ready for Qt 5.1 +Nicholas Pezolano for a new MPI example +Martin Morgan for a new MPI example +Kevin Ushey for debugging a seg.fault issue post Rcpp 0.11.0 diff --git a/external/r_inside/inst/include/Callbacks.h b/external/r_inside/inst/include/Callbacks.h new file mode 100644 index 000000000000..722331efca0f --- /dev/null +++ b/external/r_inside/inst/include/Callbacks.h @@ -0,0 +1,70 @@ +// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; tab-width: 8 -*- +// +// Callbacks.h: R/C++ interface class library -- Easier R embedding into C++ +// +// Copyright (C) 2010 Dirk Eddelbuettel and Romain Francois +// +// This file is part of RInside. +// +// RInside is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// RInside is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RInside. If not, see . + +#ifndef RINSIDE_CALLBACKS_H +#define RINSIDE_CALLBACKS_H + +#include + +#ifdef RINSIDE_CALLBACKS + +class Callbacks { +public: + + Callbacks() : R_is_busy(false), buffer() {} ; + virtual ~Callbacks(){} ; + + virtual void ShowMessage(const char* message) {} ; + virtual void Suicide(const char* message) {}; + virtual std::string ReadConsole( const char* prompt, bool addtohistory ) { return ""; }; + virtual void WriteConsole( const std::string& line, int type ) {}; + virtual void FlushConsole() {}; + virtual void ResetConsole() {}; + virtual void CleanerrConsole(){} ; + virtual void Busy( bool is_busy ) {} ; + + void Busy_( int which ) ; + int ReadConsole_( const char* prompt, unsigned char* buf, int len, int addtohistory ) ; + void WriteConsole_( const char* buf, int len, int oType ) ; + + // TODO: ShowFiles + // TODO: ChooseFile + // TODO: loadHistory + // TODO: SaveHistory + + virtual bool has_ShowMessage() { return false ; } ; + virtual bool has_Suicide() { return false ; } ; + virtual bool has_ReadConsole() { return false ; } ; + virtual bool has_WriteConsole() { return false ; } ; + virtual bool has_ResetConsole() { return false ; } ; + virtual bool has_CleanerrConsole() { return false ; } ; + virtual bool has_Busy() { return false ; } ; + virtual bool has_FlushConsole(){ return false; } ; + +private: + bool R_is_busy ; + std::string buffer ; + +} ; + +#endif + +#endif diff --git a/external/r_inside/inst/include/MemBuf.h b/external/r_inside/inst/include/MemBuf.h new file mode 100644 index 000000000000..cbcfb655ca9b --- /dev/null +++ b/external/r_inside/inst/include/MemBuf.h @@ -0,0 +1,34 @@ +// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; tab-width: 8 -*- +// +// MemBuf.h: R/C++ interface class library -- Easier R embedding into C++ +// +// Copyright (C) 2009 Dirk Eddelbuettel +// Copyright (C) 2010 Dirk Eddelbuettel and Romain Francois +// +// This file is part of RInside. +// +// RInside is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// RInside is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RInside. If not, see . + +class MemBuf { // simple C++-ification of littler's membuf +private: + std::string buffer ; + +public: + MemBuf(int sizebytes=1024); + ~MemBuf(); + void resize(); + void rewind(); + void add(const std::string& ); + inline const char* getBufPtr() { return buffer.c_str() ; }; +}; diff --git a/external/r_inside/inst/include/RInside.h b/external/r_inside/inst/include/RInside.h new file mode 100644 index 000000000000..6b25fa72a507 --- /dev/null +++ b/external/r_inside/inst/include/RInside.h @@ -0,0 +1,103 @@ +// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; tab-width: 4 -*- +// +// RInside.h: R/C++ interface class library -- Easier R embedding into C++ +// +// Copyright (C) 2009 Dirk Eddelbuettel +// Copyright (C) 2010 - 2017 Dirk Eddelbuettel and Romain Francois +// +// This file is part of RInside. +// +// RInside is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// RInside is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RInside. If not, see . + +#ifndef RINSIDE_RINSIDE_H +#define RINSIDE_RINSIDE_H + +#include +#include + +class RInside { +private: + MemBuf mb_m; + Rcpp::Environment* global_env_m; + + bool verbose_m; // switch toggled by constructor, or setter + bool interactive_m; // switch set by constructor only + + void init_tempdir(void); + void init_rand(void); + void autoloads(void); + + void initialize(const int argc, const char* const argv[], + const bool loadRcpp, const bool verbose, const bool interactive); + + static RInside* instance_m ; + +#ifdef RINSIDE_CALLBACKS + Callbacks* callbacks ; + friend void RInside_ShowMessage( const char* message); + friend void RInside_WriteConsoleEx( const char* message, int len, int oType ); + friend int RInside_ReadConsole(const char *prompt, unsigned char *buf, int len, int addtohistory); + friend void RInside_ResetConsole(); + friend void RInside_FlushConsole(); + friend void RInside_ClearerrConsole(); + friend void RInside_Busy(int which); +#endif + +public: + + class Proxy { + public: + Proxy(SEXP xx): x(xx) { }; + + template + operator T() { + return ::Rcpp::as(x); + } + private: + Rcpp::RObject x; + }; + + int parseEval(const std::string &line, SEXP &ans); // parse line, return in ans; error code rc + void parseEvalQ(const std::string &line); // parse line, no return (throws on error) + void parseEvalQNT(const std::string &line); // parse line, no return (no throw) + Proxy parseEval(const std::string &line); // parse line, return SEXP (throws on error) + Proxy parseEvalNT(const std::string &line); // parse line, return SEXP (no throw) + + template + void assign(const T& object, const std::string& nam) { + global_env_m->assign( nam, object ) ; + } + + RInside() ; + RInside(const int argc, const char* const argv[], + const bool loadRcpp=true, // overridden in code, cannot be set to false + const bool verbose=false, const bool interactive=false); + ~RInside(); + + void setVerbose(const bool verbose) { verbose_m = verbose; } + + Rcpp::Environment::Binding operator[]( const std::string& name ); + + static RInside& instance(); + static RInside* instancePtr(); + + void repl(); + +#ifdef RINSIDE_CALLBACKS + void set_callbacks(Callbacks* callbacks_) ; +#endif + +}; + +#endif diff --git a/external/r_inside/inst/include/RInsideAutoloads.h b/external/r_inside/inst/include/RInsideAutoloads.h new file mode 100644 index 000000000000..14bca168ec7f --- /dev/null +++ b/external/r_inside/inst/include/RInsideAutoloads.h @@ -0,0 +1,1191 @@ +int packc = 6; + const char *pack[] = { + "datasets", + "utils", + "grDevices", + "graphics", + "stats", + "methods" + }; + int packobjc[] = { + 104, + 217, + 112, + 87, + 449, + 203 + }; + const char *packobj[] = { + "ability.cov", + "airmiles", + "AirPassengers", + "airquality", + "anscombe", + "attenu", + "attitude", + "austres", + "beaver1", + "beaver2", + "BJsales", + "BJsales.lead", + "BOD", + "cars", + "ChickWeight", + "chickwts", + "co2", + "CO2", + "crimtab", + "discoveries", + "DNase", + "esoph", + "euro", + "euro.cross", + "eurodist", + "EuStockMarkets", + "faithful", + "fdeaths", + "Formaldehyde", + "freeny", + "freeny.x", + "freeny.y", + "HairEyeColor", + "Harman23.cor", + "Harman74.cor", + "Indometh", + "infert", + "InsectSprays", + "iris", + "iris3", + "islands", + "JohnsonJohnson", + "LakeHuron", + "ldeaths", + "lh", + "LifeCycleSavings", + "Loblolly", + "longley", + "lynx", + "mdeaths", + "morley", + "mtcars", + "nhtemp", + "Nile", + "nottem", + "npk", + "occupationalStatus", + "Orange", + "OrchardSprays", + "PlantGrowth", + "precip", + "presidents", + "pressure", + "Puromycin", + "quakes", + "randu", + "rivers", + "rock", + "Seatbelts", + "sleep", + "stack.loss", + "stack.x", + "stackloss", + "state.abb", + "state.area", + "state.center", + "state.division", + "state.name", + "state.region", + "state.x77", + "sunspot.month", + "sunspot.year", + "sunspots", + "swiss", + "Theoph", + "Titanic", + "ToothGrowth", + "treering", + "trees", + "UCBAdmissions", + "UKDriverDeaths", + "UKgas", + "USAccDeaths", + "USArrests", + "UScitiesD", + "USJudgeRatings", + "USPersonalExpenditure", + "uspop", + "VADeaths", + "volcano", + "warpbreaks", + "women", + "WorldPhones", + "WWWusage", + "?", + "adist", + "alarm", + "apropos", + "aregexec", + "argsAnywhere", + "as.person", + "as.personList", + "as.relistable", + "as.roman", + "asDateBuilt", + "askYesNo", + "aspell", + "aspell_package_C_files", + "aspell_package_R_files", + "aspell_package_Rd_files", + "aspell_package_vignettes", + "aspell_write_personal_dictionary_file", + "assignInMyNamespace", + "assignInNamespace", + "available.packages", + "bibentry", + "browseEnv", + "browseURL", + "browseVignettes", + "bug.report", + "capture.output", + "changedFiles", + "charClass", + "checkCRAN", + "chooseBioCmirror", + "chooseCRANmirror", + "citation", + "cite", + "citeNatbib", + "citEntry", + "citFooter", + "citHeader", + "close.socket", + "combn", + "compareVersion", + "contrib.url", + "count.fields", + "create.post", + "data", + "data.entry", + "dataentry", + "de", + "de.ncols", + "de.restore", + "de.setup", + "debugcall", + "debugger", + "demo", + "download.file", + "download.packages", + "dump.frames", + "edit", + "emacs", + "example", + "file_test", + "file.edit", + "fileSnapshot", + "find", + "findLineNum", + "fix", + "fixInNamespace", + "flush.console", + "formatOL", + "formatUL", + "getAnywhere", + "getCRANmirrors", + "getFromNamespace", + "getParseData", + "getParseText", + "getS3method", + "getSrcDirectory", + "getSrcFilename", + "getSrcLocation", + "getSrcref", + "getTxtProgressBar", + "glob2rx", + "globalVariables", + "hasName", + "head", + "head.matrix", + "help", + "help.request", + "help.search", + "help.start", + "history", + "hsearch_db", + "hsearch_db_concepts", + "hsearch_db_keywords", + "install.packages", + "installed.packages", + "is.relistable", + "isS3method", + "isS3stdGeneric", + "limitedLabels", + "loadhistory", + "localeToCharset", + "ls.str", + "lsf.str", + "maintainer", + "make.packages.html", + "make.socket", + "makeRweaveLatexCodeRunner", + "memory.limit", + "memory.size", + "menu", + "methods", + "mirror2html", + "modifyList", + "new.packages", + "news", + "nsl", + "object.size", + "old.packages", + "osVersion", + "package.skeleton", + "packageDate", + "packageDescription", + "packageName", + "packageStatus", + "packageVersion", + "page", + "person", + "personList", + "pico", + "process.events", + "prompt", + "promptData", + "promptImport", + "promptPackage", + "rc.getOption", + "rc.options", + "rc.settings", + "rc.status", + "read.csv", + "read.csv2", + "read.delim", + "read.delim2", + "read.DIF", + "read.fortran", + "read.fwf", + "read.socket", + "read.table", + "readCitationFile", + "recover", + "relist", + "remove.packages", + "removeSource", + "Rprof", + "Rprofmem", + "RShowDoc", + "RSiteSearch", + "rtags", + "Rtangle", + "RtangleFinish", + "RtangleRuncode", + "RtangleSetup", + "RtangleWritedoc", + "RweaveChunkPrefix", + "RweaveEvalWithOpt", + "RweaveLatex", + "RweaveLatexFinish", + "RweaveLatexOptions", + "RweaveLatexSetup", + "RweaveLatexWritedoc", + "RweaveTryStop", + "savehistory", + "select.list", + "sessionInfo", + "setBreakpoint", + "setRepositories", + "setTxtProgressBar", + "stack", + "Stangle", + "str", + "strcapture", + "strOptions", + "summaryRprof", + "suppressForeignCheck", + "Sweave", + "SweaveHooks", + "SweaveSyntaxLatex", + "SweaveSyntaxNoweb", + "SweaveSyntConv", + "tail", + "tail.matrix", + "tar", + "timestamp", + "toBibtex", + "toLatex", + "txtProgressBar", + "type.convert", + "undebugcall", + "unstack", + "untar", + "unzip", + "update.packages", + "upgrade", + "url.show", + "URLdecode", + "URLencode", + "vi", + "View", + "vignette", + "warnErrList", + "write.csv", + "write.csv2", + "write.socket", + "write.table", + "xedit", + "xemacs", + "zip", + "adjustcolor", + "as.graphicsAnnot", + "as.raster", + "axisTicks", + "bitmap", + "blues9", + "bmp", + "boxplot.stats", + "cairo_pdf", + "cairo_ps", + "cairoSymbolFont", + "check.options", + "chull", + "CIDFont", + "cm", + "cm.colors", + "col2rgb", + "colorConverter", + "colorRamp", + "colorRampPalette", + "colors", + "colorspaces", + "colours", + "contourLines", + "convertColor", + "densCols", + "dev.capabilities", + "dev.capture", + "dev.control", + "dev.copy", + "dev.copy2eps", + "dev.copy2pdf", + "dev.cur", + "dev.flush", + "dev.hold", + "dev.interactive", + "dev.list", + "dev.new", + "dev.next", + "dev.off", + "dev.prev", + "dev.print", + "dev.set", + "dev.size", + "dev2bitmap", + "devAskNewPage", + "deviceIsInteractive", + "embedFonts", + "extendrange", + "getGraphicsEvent", + "getGraphicsEventEnv", + "graphics.off", + "gray", + "gray.colors", + "grey", + "grey.colors", + "grSoftVersion", + "hcl", + "hcl.colors", + "hcl.pals", + "heat.colors", + "Hershey", + "hsv", + "is.raster", + "jpeg", + "make.rgb", + "n2mfrow", + "nclass.FD", + "nclass.scott", + "nclass.Sturges", + "palette", + "palette.colors", + "palette.pals", + "pdf", + "pdf.options", + "pdfFonts", + "pictex", + "png", + "postscript", + "postscriptFonts", + "ps.options", + "quartz", + "quartz.options", + "quartz.save", + "quartzFont", + "quartzFonts", + "rainbow", + "recordGraphics", + "recordPlot", + "replayPlot", + "rgb", + "rgb2hsv", + "savePlot", + "setEPS", + "setGraphicsEventEnv", + "setGraphicsEventHandlers", + "setPS", + "svg", + "terrain.colors", + "tiff", + "topo.colors", + "trans3d", + "Type1Font", + "x11", + "X11", + "X11.options", + "X11Font", + "X11Fonts", + "xfig", + "xy.coords", + "xyTable", + "xyz.coords", + "abline", + "arrows", + "assocplot", + "axis", + "Axis", + "axis.Date", + "axis.POSIXct", + "axTicks", + "barplot", + "barplot.default", + "box", + "boxplot", + "boxplot.default", + "boxplot.matrix", + "bxp", + "cdplot", + "clip", + "close.screen", + "co.intervals", + "contour", + "contour.default", + "coplot", + "curve", + "dotchart", + "erase.screen", + "filled.contour", + "fourfoldplot", + "frame", + "grconvertX", + "grconvertY", + "grid", + "hist", + "hist.default", + "identify", + "image", + "image.default", + "layout", + "layout.show", + "lcm", + "legend", + "lines", + "lines.default", + "locator", + "matlines", + "matplot", + "matpoints", + "mosaicplot", + "mtext", + "pairs", + "pairs.default", + "panel.smooth", + "par", + "persp", + "pie", + "plot", + "plot.default", + "plot.design", + "plot.function", + "plot.new", + "plot.window", + "plot.xy", + "points", + "points.default", + "polygon", + "polypath", + "rasterImage", + "rect", + "rug", + "screen", + "segments", + "smoothScatter", + "spineplot", + "split.screen", + "stars", + "stem", + "strheight", + "stripchart", + "strwidth", + "sunflowerplot", + "symbols", + "text", + "text.default", + "title", + "xinch", + "xspline", + "xyinch", + "yinch", + "acf", + "acf2AR", + "add.scope", + "add1", + "addmargins", + "aggregate", + "aggregate.data.frame", + "aggregate.ts", + "AIC", + "alias", + "anova", + "ansari.test", + "aov", + "approx", + "approxfun", + "ar", + "ar.burg", + "ar.mle", + "ar.ols", + "ar.yw", + "arima", + "arima.sim", + "arima0", + "arima0.diag", + "ARMAacf", + "ARMAtoMA", + "as.dendrogram", + "as.dist", + "as.formula", + "as.hclust", + "as.stepfun", + "as.ts", + "asOneSidedFormula", + "ave", + "bandwidth.kernel", + "bartlett.test", + "BIC", + "binom.test", + "binomial", + "biplot", + "Box.test", + "bw.bcv", + "bw.nrd", + "bw.nrd0", + "bw.SJ", + "bw.ucv", + "C", + "cancor", + "case.names", + "ccf", + "chisq.test", + "cmdscale", + "coef", + "coefficients", + "complete.cases", + "confint", + "confint.default", + "confint.lm", + "constrOptim", + "contr.helmert", + "contr.poly", + "contr.SAS", + "contr.sum", + "contr.treatment", + "contrasts", + "contrasts<-", + "convolve", + "cooks.distance", + "cophenetic", + "cor", + "cor.test", + "cov", + "cov.wt", + "cov2cor", + "covratio", + "cpgram", + "cutree", + "cycle", + "D", + "dbeta", + "dbinom", + "dcauchy", + "dchisq", + "decompose", + "delete.response", + "deltat", + "dendrapply", + "density", + "density.default", + "deriv", + "deriv3", + "deviance", + "dexp", + "df", + "df.kernel", + "df.residual", + "DF2formula", + "dfbeta", + "dfbetas", + "dffits", + "dgamma", + "dgeom", + "dhyper", + "diffinv", + "dist", + "dlnorm", + "dlogis", + "dmultinom", + "dnbinom", + "dnorm", + "dpois", + "drop.scope", + "drop.terms", + "drop1", + "dsignrank", + "dt", + "dummy.coef", + "dummy.coef.lm", + "dunif", + "dweibull", + "dwilcox", + "ecdf", + "eff.aovlist", + "effects", + "embed", + "end", + "estVar", + "expand.model.frame", + "extractAIC", + "factanal", + "factor.scope", + "family", + "fft", + "filter", + "fisher.test", + "fitted", + "fitted.values", + "fivenum", + "fligner.test", + "formula", + "frequency", + "friedman.test", + "ftable", + "Gamma", + "gaussian", + "get_all_vars", + "getCall", + "getInitial", + "glm", + "glm.control", + "glm.fit", + "hasTsp", + "hat", + "hatvalues", + "hclust", + "heatmap", + "HoltWinters", + "influence", + "influence.measures", + "integrate", + "interaction.plot", + "inverse.gaussian", + "IQR", + "is.empty.model", + "is.leaf", + "is.mts", + "is.stepfun", + "is.ts", + "is.tskernel", + "isoreg", + "KalmanForecast", + "KalmanLike", + "KalmanRun", + "KalmanSmooth", + "kernapply", + "kernel", + "kmeans", + "knots", + "kruskal.test", + "ks.test", + "ksmooth", + "lag", + "lag.plot", + "line", + "lm", + "lm.fit", + "lm.influence", + "lm.wfit", + "loadings", + "loess", + "loess.control", + "loess.smooth", + "logLik", + "loglin", + "lowess", + "ls.diag", + "ls.print", + "lsfit", + "mad", + "mahalanobis", + "make.link", + "makeARIMA", + "makepredictcall", + "manova", + "mantelhaen.test", + "mauchly.test", + "mcnemar.test", + "median", + "median.default", + "medpolish", + "model.extract", + "model.frame", + "model.frame.default", + "model.matrix", + "model.matrix.default", + "model.matrix.lm", + "model.offset", + "model.response", + "model.tables", + "model.weights", + "monthplot", + "mood.test", + "mvfft", + "na.action", + "na.contiguous", + "na.exclude", + "na.fail", + "na.omit", + "na.pass", + "napredict", + "naprint", + "naresid", + "nextn", + "nlm", + "nlminb", + "nls", + "nls.control", + "NLSstAsymptotic", + "NLSstClosestX", + "NLSstLfAsymptote", + "NLSstRtAsymptote", + "nobs", + "numericDeriv", + "offset", + "oneway.test", + "optim", + "optimHess", + "optimise", + "optimize", + "order.dendrogram", + "p.adjust", + "p.adjust.methods", + "pacf", + "Pair", + "pairwise.prop.test", + "pairwise.t.test", + "pairwise.table", + "pairwise.wilcox.test", + "pbeta", + "pbinom", + "pbirthday", + "pcauchy", + "pchisq", + "pexp", + "pf", + "pgamma", + "pgeom", + "phyper", + "plclust", + "plnorm", + "plogis", + "plot.ecdf", + "plot.spec.coherency", + "plot.spec.phase", + "plot.stepfun", + "plot.ts", + "pnbinom", + "pnorm", + "poisson", + "poisson.test", + "poly", + "polym", + "power", + "power.anova.test", + "power.prop.test", + "power.t.test", + "PP.test", + "ppoints", + "ppois", + "ppr", + "prcomp", + "predict", + "predict.glm", + "predict.lm", + "preplot", + "princomp", + "printCoefmat", + "profile", + "proj", + "promax", + "prop.test", + "prop.trend.test", + "psignrank", + "pt", + "ptukey", + "punif", + "pweibull", + "pwilcox", + "qbeta", + "qbinom", + "qbirthday", + "qcauchy", + "qchisq", + "qexp", + "qf", + "qgamma", + "qgeom", + "qhyper", + "qlnorm", + "qlogis", + "qnbinom", + "qnorm", + "qpois", + "qqline", + "qqnorm", + "qqplot", + "qsignrank", + "qt", + "qtukey", + "quade.test", + "quantile", + "quasi", + "quasibinomial", + "quasipoisson", + "qunif", + "qweibull", + "qwilcox", + "r2dtable", + "rbeta", + "rbinom", + "rcauchy", + "rchisq", + "read.ftable", + "rect.hclust", + "reformulate", + "relevel", + "reorder", + "replications", + "reshape", + "resid", + "residuals", + "residuals.glm", + "residuals.lm", + "rexp", + "rf", + "rgamma", + "rgeom", + "rhyper", + "rlnorm", + "rlogis", + "rmultinom", + "rnbinom", + "rnorm", + "rpois", + "rsignrank", + "rstandard", + "rstudent", + "rt", + "runif", + "runmed", + "rweibull", + "rwilcox", + "rWishart", + "scatter.smooth", + "screeplot", + "sd", + "se.contrast", + "selfStart", + "setNames", + "shapiro.test", + "sigma", + "simulate", + "smooth", + "smooth.spline", + "smoothEnds", + "sortedXyData", + "spec.ar", + "spec.pgram", + "spec.taper", + "spectrum", + "spline", + "splinefun", + "splinefunH", + "SSasymp", + "SSasympOff", + "SSasympOrig", + "SSbiexp", + "SSD", + "SSfol", + "SSfpl", + "SSgompertz", + "SSlogis", + "SSmicmen", + "SSweibull", + "start", + "stat.anova", + "step", + "stepfun", + "stl", + "StructTS", + "summary.aov", + "summary.glm", + "summary.lm", + "summary.manova", + "summary.stepfun", + "supsmu", + "symnum", + "t.test", + "termplot", + "terms", + "terms.formula", + "time", + "toeplitz", + "ts", + "ts.intersect", + "ts.plot", + "ts.union", + "tsdiag", + "tsp", + "tsp<-", + "tsSmooth", + "TukeyHSD", + "uniroot", + "update", + "update.default", + "update.formula", + "var", + "var.test", + "variable.names", + "varimax", + "vcov", + "weighted.mean", + "weighted.residuals", + "weights", + "wilcox.test", + "window", + "window<-", + "write.ftable", + "xtabs", + "addNextMethod", + "allNames", + "Arith", + "as", + "as<-", + "asMethodDefinition", + "assignClassDef", + "assignMethodsMetaData", + "balanceMethodsList", + "body<-", + "cacheGenericsMetaData", + "cacheMetaData", + "cacheMethod", + "callGeneric", + "callNextMethod", + "canCoerce", + "cbind2", + "checkAtAssignment", + "checkSlotAssignment", + "classesToAM", + "classLabel", + "classMetaName", + "className", + "coerce", + "coerce<-", + "Compare", + "completeClassDefinition", + "completeExtends", + "completeSubclasses", + "Complex", + "conformMethod", + "defaultDumpName", + "defaultPrototype", + "doPrimitiveMethod", + "dumpMethod", + "dumpMethods", + "el", + "el<-", + "elNamed", + "elNamed<-", + "empty.dump", + "emptyMethodsList", + "evalOnLoad", + "evalqOnLoad", + "evalSource", + "existsFunction", + "existsMethod", + "extends", + "externalRefMethod", + "finalDefaultMethod", + "findClass", + "findFunction", + "findMethod", + "findMethods", + "findMethodSignatures", + "findUnique", + "fixPre1.8", + "formalArgs", + "functionBody", + "functionBody<-", + "generic.skeleton", + "getAllSuperClasses", + "getClass", + "getClassDef", + "getClasses", + "getDataPart", + "getFunction", + "getGeneric", + "getGenerics", + "getGroup", + "getGroupMembers", + "getLoadActions", + "getMethod", + "getMethods", + "getMethodsForDispatch", + "getMethodsMetaData", + "getPackageName", + "getRefClass", + "getSlots", + "getValidity", + "hasArg", + "hasLoadAction", + "hasMethod", + "hasMethods", + "implicitGeneric", + "inheritedSlotNames", + "initFieldArgs", + "initialize", + "initRefFields", + "insertClassMethods", + "insertMethod", + "insertSource", + "is", + "isClass", + "isClassDef", + "isClassUnion", + "isGeneric", + "isGrammarSymbol", + "isGroup", + "isRematched", + "isSealedClass", + "isSealedMethod", + "isVirtualClass", + "isXS3Class", + "kronecker", + "languageEl", + "languageEl<-", + "linearizeMlist", + "listFromMethods", + "listFromMlist", + "loadMethod", + "Logic", + "makeClassRepresentation", + "makeExtends", + "makeGeneric", + "makeMethodsList", + "makePrototypeFromClassDef", + "makeStandardGeneric", + "matchSignature", + "Math", + "Math2", + "mergeMethods", + "metaNameUndo", + "method.skeleton", + "MethodAddCoerce", + "methodSignatureMatrix", + "MethodsList", + "MethodsListSelect", + "methodsPackageMetaName", + "missingArg", + "multipleClasses", + "new", + "newBasic", + "newClassRepresentation", + "newEmptyObject", + "Ops", + "packageSlot", + "packageSlot<-", + "possibleExtends", + "prohibitGeneric", + "promptClass", + "promptMethods", + "prototype", + "Quote", + "rbind2", + "reconcilePropertiesAndPrototype", + "registerImplicitGenerics", + "rematchDefinition", + "removeClass", + "removeGeneric", + "removeMethod", + "removeMethods", + "representation", + "requireMethods", + "resetClass", + "resetGeneric", + "S3Class", + "S3Class<-", + "S3Part", + "S3Part<-", + "sealClass", + "selectMethod", + "selectSuperClasses", + "setAs", + "setClass", + "setClassUnion", + "setDataPart", + "setGeneric", + "setGenericImplicit", + "setGroupGeneric", + "setIs", + "setLoadAction", + "setLoadActions", + "setMethod", + "setOldClass", + "setPackageName", + "setPrimitiveMethods", + "setRefClass", + "setReplaceMethod", + "setValidity", + "show", + "showClass", + "showDefault", + "showExtends", + "showMethods", + "showMlist", + "signature", + "SignatureMethod", + "sigToEnv", + "slot", + "slot<-", + "slotNames", + "slotsFromS3", + "substituteDirect", + "substituteFunctionArgs", + "Summary", + "superClassDepth", + "testInheritedMethods", + "testVirtual", + "tryNew", + "unRematchDefinition", + "validObject", + "validSlotNames" + }; \ No newline at end of file diff --git a/external/r_inside/inst/include/RInsideCommon.h b/external/r_inside/inst/include/RInsideCommon.h new file mode 100644 index 000000000000..675b3e599339 --- /dev/null +++ b/external/r_inside/inst/include/RInsideCommon.h @@ -0,0 +1,73 @@ +// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; indent-tabs-mode: nil; -*- +// +// RInsideCommon.h: R/C++ interface class library -- Easier R embedding into C++ +// +// Copyright (C) 2010 - 2011 Dirk Eddelbuettel and Romain Francois +// +// This file is part of RInside. +// +// RInside is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// RInside is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RInside. If not, see . + +#ifndef RINSIDE_RINSIDECOMMON_H +#define RINSIDE_RINSIDECOMMON_H + +#include + +#include // gettimeofday() +#include // pid_t +#include // getpid() + +#include // intptr_t (one day we use cinttypes from C++11) +#include // uint64_t (one day we use cstdint from C++11) + +#include +#include +#include + +#include + +#ifdef WIN32 + #ifndef Win32 + // needed for parts of Rembedded.h + #define Win32 + #endif +#endif + +#ifndef WIN32 + // needed to turn-off stack checking, and we already have uintptr_t + #define CSTACK_DEFNS + #ifndef HAVE_UINTPTR_T + #define HAVE_UINTPTR_T + #endif +#endif + +#include +#include + +#include + +// simple logging help +inline void logTxtFunction(const char* file, const int line, const char* expression, const bool verbose) { + if (verbose) { + std::cout << file << ":" << line << " expression: " << expression << std::endl; + } +} + +#ifdef logTxt +#undef logTxt +#endif +//#define logTxt(x, b) logTxtFunction(__FILE__, __LINE__, x, b); +#define logTxt(x, b) + +#endif diff --git a/external/r_inside/inst/include/RInsideConfig.h b/external/r_inside/inst/include/RInsideConfig.h new file mode 100644 index 000000000000..a4b0683035e7 --- /dev/null +++ b/external/r_inside/inst/include/RInsideConfig.h @@ -0,0 +1,26 @@ +// RInsideConfig.h: R/C++ interface class library -- Easier R embedding into C++ +// +// Copyright (C) 2010 Dirk Eddelbuettel and Romain Francois +// +// This file is part of RInside. +// +// RInside is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// RInside is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RInside. If not, see . + +#ifndef RINSIDE_RINSIDECONFIG_H +#define RINSIDE_RINSIDECONFIG_H + +// uncomment to turn on the experimental callbacks +// #define RINSIDE_CALLBACKS + +#endif diff --git a/external/r_inside/inst/include/RInsideEnvVars.h b/external/r_inside/inst/include/RInsideEnvVars.h new file mode 100644 index 000000000000..6da72a896a5a --- /dev/null +++ b/external/r_inside/inst/include/RInsideEnvVars.h @@ -0,0 +1,24 @@ +const char *R_VARS[] = { + "R_ARCH","", + "R_BROWSER","/usr/bin/xdg-open", + "R_BZIPCMD","/usr/bin/bzip2", + "R_DOC_DIR","/usr/share/doc/R", + "R_GZIPCMD","/usr/bin/gzip", + "R_HOME","/usr/lib64/R", + "R_INCLUDE_DIR","/usr/include/R", + "R_LIBS_SITE","/usr/local/lib/R/site-library:/usr/local/lib/R/library:/usr/lib64/R/library:/usr/share/R/library", + "R_LIBS_USER","~/R/x86_64-redhat-linux-gnu-library/4.1", + "R_PAPERSIZE","a4", + "R_PDFVIEWER","/usr/bin/xdg-open", + "R_PLATFORM","x86_64-redhat-linux-gnu", + "R_PRINTCMD","lpr", + "R_RD4PDF","times,inconsolata,hyper", + "R_SHARE_DIR","/usr/share/R", + "R_STRIP_SHARED_LIB","strip --strip-unneeded", + "R_STRIP_STATIC_LIB","strip --strip-debug", + "R_SYSTEM_ABI","linux,gcc,gxx,gfortran,gfortran", + "R_TEXI2DVICMD","/usr/bin/texi2dvi", + "R_UNZIPCMD","/usr/bin/unzip", + "R_ZIPCMD","/usr/bin/zip", + NULL + }; \ No newline at end of file diff --git a/external/r_inside/inst/include/RInside_C.h b/external/r_inside/inst/include/RInside_C.h new file mode 100644 index 000000000000..5e3b10907963 --- /dev/null +++ b/external/r_inside/inst/include/RInside_C.h @@ -0,0 +1,32 @@ + +// RInside_C.h: R/C++ interface class library -- Easier R embedding into C +// +// Copyright (C) 2020 - Lance Bachmeier and Dirk Eddelbuettel +// +// This file is part of RInside. +// +// RInside is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// RInside is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RInside. If not, see . + +#include + +#ifndef RINSIDE_RINSIDE_C_H +#define RINSIDE_RINSIDE_C_H + +void setupRinC(); +void passToR(SEXP x, char * name); +SEXP evalInR(char * cmd); +void evalQuietlyInR(char * cmd); +void teardownRinC(); + +#endif diff --git a/external/r_inside/src/Makevars b/external/r_inside/src/Makevars new file mode 100644 index 000000000000..d0892ec33fce --- /dev/null +++ b/external/r_inside/src/Makevars @@ -0,0 +1,57 @@ +## -*- mode: Makefile; tab-width: 8 -*- +## +## Copyright (C) 2010 - 2014 Dirk Eddelbuettel and Romain Francois +## +## This file is part of RInside. +## +## RInside is free software: you can redistribute it and/or modify it +## under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 2 of the License, or +## (at your option) any later version. +## +## RInside is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with RInside. If not, see . + +USERLIB=libRInside$(DYLIB_EXT) +USERLIBST=libRInside.a +USERDIR=../inst/lib + +PKG_CPPFLAGS = -I. -I../inst/include/ +PKG_LIBS = + +all: headers $(SHLIB) userLibrary + +headers: RInsideAutoloads.h RInsideEnvVars.h + +RInsideAutoloads.h: + ${R_HOME}/bin/Rscript tools/RInsideAutoloads.r > RInsideAutoloads.h + +RInsideEnvVars.h: + ${R_HOME}/bin/Rscript tools/RInsideEnvVars.r > RInsideEnvVars.h + +RInside.cpp: headers + +userLibrary: $(USERLIB) $(USERLIBST) + -@if test ! -e $(USERDIR)$(R_ARCH); then mkdir -p $(USERDIR)$(R_ARCH); fi + cp $(USERLIB) $(USERDIR)$(R_ARCH) + cp $(USERLIBST) $(USERDIR)$(R_ARCH) + rm $(USERLIB) $(USERLIBST) + +$(USERLIB): $(OBJECTS) + $(SHLIB_CXXLD) -o $(USERLIB) $^ $(SHLIB_CXXLDFLAGS) $(LDFLAGS) $(ALL_LIBS) + @if test -e "/usr/bin/install_name_tool"; then /usr/bin/install_name_tool -id $(R_PACKAGE_DIR)/lib$(R_ARCH)/$(USERLIB) $(USERLIB); fi + +$(USERLIBST): $(OBJECTS) + $(AR) qc $(USERLIBST) $^ + @if test -n "$(RANLIB)"; then $(RANLIB) $(USERLIBST); fi + +.PHONY: all clean userLibrary headers + +clean: + rm -f $(OBJECTS) $(SHLIB) $(USERLIB) $(USERLIBST) + diff --git a/external/r_inside/src/Makevars.win b/external/r_inside/src/Makevars.win new file mode 100644 index 000000000000..3bbc60c3650f --- /dev/null +++ b/external/r_inside/src/Makevars.win @@ -0,0 +1,60 @@ +## -*- mode: Makefile; tab-width: 8 -*- +## +## Copyright (C) 2010 - 2014 Dirk Eddelbuettel and Romain Francois +## +## This file is part of RInside. +## +## RInside is free software: you can redistribute it and/or modify it +## under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 2 of the License, or +## (at your option) any later version. +## +## RInside is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with RInside. If not, see . + +USERLIBST = libRInside.a +USERLIB = libRInside.dll +#USERDIR = $(R_PACKAGE_DIR)/inst/lib$(R_ARCH) +USERDIR = ../inst/lib$(R_ARCH) + +PKG_CPPFLAGS = -I. -I../inst/include/ +PKG_LIBS = $(shell "${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" -e "Rcpp:::LdFlags()") + +RSCRIPT = "${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" + +all: headers $(SHLIB) userLibrary + +headers: RInsideAutoloads.h RInsideEnvVars.h + +RInsideAutoloads.h: + $(RSCRIPT) tools/RInsideAutoloads.r > RInsideAutoloads.h + +RInsideEnvVars.h: + $(RSCRIPT) tools/RInsideEnvVars.r > RInsideEnvVars.h + +RInside.cpp: headers + +userLibrary: $(USERLIBST) $(USERLIB) + -@if test ! -e $(USERDIR); then mkdir -p $(USERDIR); fi + cp $(USERLIB) $(USERDIR) + +$(USERLIBST): $(OBJECTS) + -@if test ! -e $(USERDIR); then mkdir -p $(USERDIR); fi + $(AR) qc $(USERLIBST) $^ + @if test -n "$(RANLIB)"; then $(RANLIB) $(USERLIBST); fi + cp $(USERLIBST) $(USERDIR) + ls -lR $(USERDIR) + +$(USERLIB): $(OBJECTS) + $(CXX) -Wl,--export-all-symbols -shared -o $(USERLIB) $^ $(ALL_LIBS) -lws2_32 + +.PHONY: all clean userLibrary headers + +clean: + rm -f $(OBJECTS) $(SHLIB) $(USERLIBST) $(USERLIB) + diff --git a/external/r_inside/src/MemBuf.cpp b/external/r_inside/src/MemBuf.cpp new file mode 100644 index 000000000000..59ebe40857e8 --- /dev/null +++ b/external/r_inside/src/MemBuf.cpp @@ -0,0 +1,53 @@ +// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; indent-tabs-mode: nil; -*- +// +// MemBuf.cpp: R/C++ interface class library -- Easier R embedding into C++ +// +// Copyright (C) 2009 Dirk Eddelbuettel +// Copyright (C) 2010 - 2012 Dirk Eddelbuettel and Romain Francois +// +// This file is part of RInside. +// +// RInside is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// RInside is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RInside. If not, see . + +#include +#include +#include + +#include + +extern bool verbose; +extern const char *programName; + +MemBuf::~MemBuf() {} + +MemBuf::MemBuf(int sizebytes) : buffer() { + buffer.reserve(sizebytes) ; +} + +void MemBuf::resize() { // Use power of 2 resizing + buffer.reserve( 2*buffer.capacity() ) ; +} + +void MemBuf::rewind(){ + buffer.clear() ; +} + +void MemBuf::add(const std::string& buf){ + int buflen = buf.size() ; + while ( ( buflen + buffer.size() ) >= buffer.capacity() ) { + resize(); + } + buffer += buf ; +} + diff --git a/external/r_inside/src/RInside.cpp b/external/r_inside/src/RInside.cpp new file mode 100644 index 000000000000..f764415cd553 --- /dev/null +++ b/external/r_inside/src/RInside.cpp @@ -0,0 +1,531 @@ +// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; indent-tabs-mode: nil; -*- +// +// RInside.cpp: R/C++ interface class library -- Easier R embedding into C++ +// +// Copyright (C) 2009 Dirk Eddelbuettel +// Copyright (C) 2010 - 2019 Dirk Eddelbuettel and Romain Francois +// +// This file is part of RInside. +// +// RInside is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// RInside is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RInside. If not, see . + +#include +#include +#ifndef _WIN32 + #define R_INTERFACE_PTRS + #include +#endif + +RInside* RInside::instance_m = 0 ; + +const char *programName = "RInside"; + +#ifdef _WIN32 + // on Windows, we need to provide setenv which is in the file setenv.c here + #include "setenv/setenv.c" + extern int optind; + + #include + char rHome[MAX_PATH+1]; +#endif + +RInside::~RInside() { // now empty as MemBuf is internal + R_dot_Last(); + R_RunExitFinalizers(); + R_CleanTempDir(); + //Rf_KillAllDevices(); + //#ifndef WIN32 + //fpu_setup(FALSE); + //#endif + Rf_endEmbeddedR(0); + instance_m = 0 ; + delete global_env_m; +} + +RInside::RInside(): global_env_m(NULL) +#ifdef RINSIDE_CALLBACKS + , callbacks(0) +#endif +{ + initialize(0, 0, false, false, false); +} + +#ifdef _WIN32 +#if R_VERSION >= R_Version(4,2,0) +static int myReadConsole(const char *prompt, unsigned char *buf, int len, int addtohistory) { +#else +static int myReadConsole(const char *prompt, char *buf, int len, int addtohistory) { +#endif + fputs(prompt, stdout); + fflush(stdout); + if (fgets((char *)buf, len, stdin)) + return 1; + else + return 0; +} + +static void myWriteConsole(const char *buf, int len) { + fwrite(buf, sizeof(char), len, stdout); + fflush(stdout); +} + +static void myCallBack() { + /* called during i/o, eval, graphics in ProcessEvents */ +} + +static void myBusy(int which) { + /* set a busy cursor ... if which = 1, unset if which = 0 */ +} + +void myAskOk(const char *info) { + +} + +int myAskYesNoCancel(const char *question) { + const int yes = 1; + return yes; +} + +#endif + +RInside::RInside(const int argc, const char* const argv[], const bool loadRcpp, + const bool verbose, const bool interactive) +#ifdef RINSIDE_CALLBACKS + : callbacks(0) +#endif +{ + initialize(argc, argv, loadRcpp, verbose, interactive); +} + +// TODO: use a vector would make all this a bit more readable +void RInside::initialize(const int argc, const char* const argv[], const bool loadRcpp, + const bool verbose, const bool interactive) { + + if (instance_m) { + throw std::runtime_error( "can only have one RInside instance" ) ; + } else { + instance_m = this ; + } + + verbose_m = verbose; // Default is false + interactive_m = interactive; + + // generated from Makevars{.win} + #include "RInsideEnvVars.h" + + #ifdef _WIN32 + // we need a special case for Windows where users may deploy an RInside binary from CRAN + // which will have R_HOME set at compile time to CRAN's value -- so let's try to correct + // this here: a) allow user's setting of R_HOME and b) use R's get_R_HOME() function + if (getenv("R_HOME") == NULL) { // if on Windows and not set + char *rhome = get_R_HOME(); // query it, including registry + if (rhome != NULL) { // if something was found + setenv("R_HOME", get_R_HOME(), 1); // store what we got as R_HOME + } // this will now be used in next blocks + } + #endif + + for (int i = 0; R_VARS[i] != NULL; i+= 2) { + if (getenv(R_VARS[i]) == NULL) { // if env variable is not yet set + if (setenv(R_VARS[i],R_VARS[i+1],1) != 0){ + throw std::runtime_error(std::string("Could not set R environment variable ") + + std::string(R_VARS[i]) + std::string(" to ") + + std::string(R_VARS[i+1])); + } + } + } + + #ifndef _WIN32 + R_SignalHandlers = 0; // Don't let R set up its own signal handlers + #endif + + init_tempdir(); + + const char *R_argv[] = {(char*)programName, "--gui=none", "--no-save", + "--silent", "--vanilla", "--slave", "--no-readline"}; + int R_argc = sizeof(R_argv) / sizeof(R_argv[0]); + if (interactive_m) R_argc--; //Deleting the --no-readline option in interactive mode + Rf_initEmbeddedR(R_argc, (char**)R_argv); + + #ifndef _WIN32 + R_CStackLimit = -1; // Don't do any stack checking, see R Exts, '8.1.5 Threading issues' + #endif + + R_ReplDLLinit(); // this is to populate the repl console buffers + + structRstart Rst; + R_DefParams(&Rst); + Rst.R_Interactive = (Rboolean) interactive_m; // sets interactive() to eval to false + #ifdef _WIN32 + + char *temp = getenv("R_HOME"); // which is set above as part of R_VARS + strncpy(rHome, temp, MAX_PATH); + Rst.rhome = rHome; + + Rst.home = getRUser(); + Rst.CharacterMode = LinkDLL; + Rst.ReadConsole = myReadConsole; + Rst.WriteConsole = myWriteConsole; + Rst.CallBack = myCallBack; + Rst.ShowMessage = myAskOk; + Rst.YesNoCancel = myAskYesNoCancel; + Rst.Busy = myBusy; + #endif + R_SetParams(&Rst); + + if (true || loadRcpp) { // we always need Rcpp, so load it anyway + // Rf_install is used best by first assigning like this so that symbols get into + // the symbol table where they cannot be garbage collected; doing it on the fly + // does expose a minuscule risk of garbage collection -- with thanks to Doug Bates + // for the explanation and Luke Tierney for the heads-up + SEXP suppressMessagesSymbol = Rf_install("suppressMessages"); + SEXP requireSymbol = Rf_install("require"); + SEXP reqsymlang, langobj; + // Protect temporaries as suggested by 'rchk', with thanks to Tomas Kalibera + PROTECT(reqsymlang = Rf_lang2(requireSymbol, Rf_mkString("Rcpp"))); + PROTECT(langobj = Rf_lang2(suppressMessagesSymbol, reqsymlang)); + Rf_eval(langobj, R_GlobalEnv); + UNPROTECT(2); + } + + global_env_m = new Rcpp::Environment(); // member variable for access to R's global environment + + autoloads(); // loads all default packages, using code autogenerate from Makevars{,.win} + + if ((argc - optind) > 1){ // for argv vector in Global Env */ + Rcpp::CharacterVector s_argv( argv+(1+optind), argv+argc ); + assign(s_argv, "argv"); + } else { + assign(R_NilValue, "argv") ; + } + + init_rand(); // for tempfile() to work correctly */ +} + +void RInside::init_tempdir(void) { + const char *tmp; + // FIXME: if per-session temp directory is used (as R does) then return + tmp = getenv("TMPDIR"); + if (tmp == NULL) { + tmp = getenv("TMP"); + if (tmp == NULL) { + tmp = getenv("TEMP"); + if (tmp == NULL) + tmp = "/tmp"; + } + } + R_TempDir = (char*) tmp; + if (setenv("R_SESSION_TMPDIR",tmp,1) != 0){ + throw std::runtime_error(std::string("Could not set / replace R_SESSION_TMPDIR to ") + std::string(tmp)); + } +} + +void RInside::init_rand(void) { // code borrows from R's TimeToSeed() in datetime.c + unsigned int pid = getpid(); + struct timeval tv; // this is ifdef'ed by R, we just assume we have it + gettimeofday (&tv, NULL); + unsigned int seed = ((uint64_t) tv.tv_usec << 16) ^ tv.tv_sec; + seed ^= (pid << 16); // R 2.14.0 started to also use pid to support parallel + srand(seed); +} + +void RInside::autoloads() { + + #include "RInsideAutoloads.h" + + // Autoload default packages and names from autoloads.h + // + // This function behaves in almost every way like + // R's autoload: + // function (name, package, reset = FALSE, ...) + // { + // if (!reset && exists(name, envir = .GlobalEnv, inherits = FALSE)) + // stop("an object with that name already exists") + // m <- match.call() + // m[[1]] <- as.name("list") + // newcall <- eval(m, parent.frame()) + // newcall <- as.call(c(as.name("autoloader"), newcall)) + // newcall$reset <- NULL + // if (is.na(match(package, .Autoloaded))) + // assign(".Autoloaded", c(package, .Autoloaded), env = .AutoloadEnv) + // do.call("delayedAssign", list(name, newcall, .GlobalEnv, + // .AutoloadEnv)) + // invisible() + // } + // + // What's missing is the updating of the string vector .Autoloaded with + // the list of packages, which by my code analysis is useless and only + // for informational purposes. + // + // + + // we build the call : + // + // delayedAssign( NAME, + // autoloader( name = NAME, package = PACKAGE), + // .GlobalEnv, + // .AutoloadEnv ) + // + // where : + // - PACKAGE is updated in a loop + // - NAME is updated in a loop + // + // + + int i,j, idx=0, nobj ; + Rcpp::Language delayed_assign_call(Rcpp::Function("delayedAssign"), + R_NilValue, // arg1: assigned in loop + R_NilValue, // arg2: assigned in loop + *global_env_m, + global_env_m->find(".AutoloadEnv") + ); + Rcpp::Language::Proxy delayed_assign_name = delayed_assign_call[1]; + + Rcpp::Language autoloader_call(Rcpp::Function("autoloader"), + Rcpp::Named( "name", R_NilValue) , // arg1 : assigned in loop + Rcpp::Named( "package", R_NilValue) // arg2 : assigned in loop + ); + Rcpp::Language::Proxy autoloader_name = autoloader_call[1]; + Rcpp::Language::Proxy autoloader_pack = autoloader_call[2]; + delayed_assign_call[2] = autoloader_call; + + try { + for( i=0; i 1 + for(i = 0; i < Rf_length(cmdexpr); i++){ + ans = R_tryEval(VECTOR_ELT(cmdexpr, i), *global_env_m, &errorOccurred); + if (errorOccurred) { + if (verbose_m) Rf_warning("%s: Error in evaluating R code (%d)\n", programName, status); + UNPROTECT(2); + mb_m.rewind(); + return 1; + } + if (verbose_m) { + Rf_PrintValue(ans); + } + } + mb_m.rewind(); + break; + case PARSE_INCOMPLETE: + // need to read another line + break; + case PARSE_NULL: + if (verbose_m) Rf_warning("%s: ParseStatus is null (%d)\n", programName, status); + UNPROTECT(2); + mb_m.rewind(); + return 1; + break; + case PARSE_ERROR: + if (verbose_m) Rf_warning("Parse Error: \"%s\"\n", line.c_str()); + UNPROTECT(2); + mb_m.rewind(); + return 1; + break; + case PARSE_EOF: + if (verbose_m) Rf_warning("%s: ParseStatus is eof (%d)\n", programName, status); + break; + default: + if (verbose_m) Rf_warning("%s: ParseStatus is not documented %d\n", programName, status); + UNPROTECT(2); + mb_m.rewind(); + return 1; + break; + } + UNPROTECT(2); + return 0; +} + +void RInside::parseEvalQ(const std::string & line) { + SEXP ans; + int rc = parseEval(line, ans); + if (rc != 0) { + throw std::runtime_error(std::string("Error evaluating: ") + line); + } +} + +void RInside::parseEvalQNT(const std::string & line) { + SEXP ans; + parseEval(line, ans); +} + +RInside::Proxy RInside::parseEval(const std::string & line) { + SEXP ans; + int rc = parseEval(line, ans); + if (rc != 0) { + throw std::runtime_error(std::string("Error evaluating: ") + line); + } + return Proxy( ans ); +} + +RInside::Proxy RInside::parseEvalNT(const std::string & line) { + SEXP ans; + parseEval(line, ans); + return Proxy( ans ); +} + +Rcpp::Environment::Binding RInside::operator[]( const std::string& name ){ + return (*global_env_m)[name]; +} + +RInside& RInside::instance(){ + return *instance_m; +} + +RInside* RInside::instancePtr(){ + return instance_m; +} + +void RInside::repl() { + R_ReplDLLinit(); + while (R_ReplDLLdo1() > 0) {} +} + +/* callbacks */ + +#ifdef RINSIDE_CALLBACKS + +void Callbacks::Busy_( int which ){ + R_is_busy = static_cast( which ) ; + Busy( R_is_busy ) ; +} + +int Callbacks::ReadConsole_( const char* prompt, unsigned char* buf, int len, int addtohistory ){ + try { + std::string res( ReadConsole( prompt, static_cast(addtohistory) ) ) ; + + /* At some point we need to figure out what to do if the result is + * longer than "len"... For now, just truncate. */ + + int l = res.size() ; + int last = (l>len-1)?len-1:l ; + strncpy( (char*)buf, res.c_str(), last ) ; + buf[last] = 0 ; + return 1 ; + } catch( const std::exception& ex){ + return -1 ; + } +} + + +void Callbacks::WriteConsole_( const char* buf, int len, int oType ){ + if( len ){ + buffer.assign( buf, len ) ; + WriteConsole( buffer, oType) ; + } +} + +void RInside_ShowMessage( const char* message ){ + RInside::instance().callbacks->ShowMessage( message ) ; +} + +void RInside_WriteConsoleEx( const char* message, int len, int oType ){ + RInside::instance().callbacks->WriteConsole_( message, len, oType ) ; +} + +int RInside_ReadConsole(const char *prompt, unsigned char *buf, int len, int addtohistory){ + return RInside::instance().callbacks->ReadConsole_( prompt, buf, len, addtohistory ) ; +} + +void RInside_ResetConsole(){ + RInside::instance().callbacks->ResetConsole() ; +} + +void RInside_FlushConsole(){ + RInside::instance().callbacks->FlushConsole() ; +} + +void RInside_ClearerrConsole(){ + RInside::instance().callbacks->CleanerrConsole() ; +} + +void RInside_Busy( int which ){ + RInside::instance().callbacks->Busy_(which) ; +} + +void RInside::set_callbacks(Callbacks* callbacks_){ + callbacks = callbacks_ ; + +#ifdef _WIN32 + // do something to tell user that he doesn't get this +#else + + /* short circuit the callback function pointers */ + if( callbacks->has_ShowMessage() ){ + ptr_R_ShowMessage = RInside_ShowMessage ; + } + if( callbacks->has_ReadConsole() ){ + ptr_R_ReadConsole = RInside_ReadConsole; + } + if( callbacks->has_WriteConsole() ){ + ptr_R_WriteConsoleEx = RInside_WriteConsoleEx ; + ptr_R_WriteConsole = NULL; + } + if( callbacks->has_ResetConsole() ){ + ptr_R_ResetConsole = RInside_ResetConsole; + } + if( callbacks->has_FlushConsole() ){ + ptr_R_FlushConsole = RInside_FlushConsole; + } + if( callbacks->has_CleanerrConsole() ){ + ptr_R_ClearerrConsole = RInside_ClearerrConsole; + } + if( callbacks->has_Busy() ){ + ptr_R_Busy = RInside_Busy; + } + + R_Outputfile = NULL; + R_Consolefile = NULL; +#endif +} + +#endif diff --git a/external/r_inside/src/RInside_C.cpp b/external/r_inside/src/RInside_C.cpp new file mode 100644 index 000000000000..cbab87a5e20c --- /dev/null +++ b/external/r_inside/src/RInside_C.cpp @@ -0,0 +1,54 @@ + +// RInside_C.cpp: R/C++ interface class library -- Easier R embedding into C +// +// Copyright (C) 2020 - Lance Bachmeier and Dirk Eddelbuettel +// +// This file is part of RInside. +// +// RInside is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// RInside is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RInside. If not, see . + +#include + +RInside *rr = NULL; + +extern "C" { + void setupRinC() { + if (rr == NULL) + rr = new RInside; + } + + void passToR(SEXP x, char * name) { + if (rr != NULL) + rr->assign(x, std::string(name)); + } + + SEXP evalInR(char * cmd) { + if (rr != NULL) + return rr->parseEval(std::string(cmd)); + else + return R_NilValue; + } + + void evalQuietlyInR(char * cmd) { + if (rr != NULL) + rr->parseEvalQ(std::string(cmd)); + } + + void teardownRinC() { + if (rr != NULL) { + delete rr; + rr = NULL; + } + } +} diff --git a/external/r_inside/src/RcppExports.cpp b/external/r_inside/src/RcppExports.cpp new file mode 100644 index 000000000000..6d6e614b5cf1 --- /dev/null +++ b/external/r_inside/src/RcppExports.cpp @@ -0,0 +1,27 @@ +// Generated by using Rcpp::compileAttributes() -> do not edit by hand +// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 + +#include "../inst/include/RInside.h" +#include + +using namespace Rcpp; + +// showCompiler +void showCompiler(); +RcppExport SEXP _RInside_showCompiler() { +BEGIN_RCPP + Rcpp::RNGScope rcpp_rngScope_gen; + showCompiler(); + return R_NilValue; +END_RCPP +} + +static const R_CallMethodDef CallEntries[] = { + {"_RInside_showCompiler", (DL_FUNC) &_RInside_showCompiler, 0}, + {NULL, NULL, 0} +}; + +RcppExport void R_init_RInside(DllInfo *dll) { + R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); + R_useDynamicSymbols(dll, FALSE); +} diff --git a/external/r_inside/src/compiler.cpp b/external/r_inside/src/compiler.cpp new file mode 100644 index 000000000000..874b4fbbab86 --- /dev/null +++ b/external/r_inside/src/compiler.cpp @@ -0,0 +1,17 @@ + +#include + +// [[Rcpp::export]] +void showCompiler() { +#if defined(__DATE__) + const char *date = __DATE__; +#else + const char *date = ""; +#endif +#if defined(__VERSION__) + const char *ver = __VERSION__; +#else + const char *ver = ""; +#endif + Rcpp::Rcout << "Compiled on " << date << " by compiler version " << ver << std::endl; +} diff --git a/external/r_inside/src/setenv/setenv.c b/external/r_inside/src/setenv/setenv.c new file mode 100644 index 000000000000..e163d9d6efae --- /dev/null +++ b/external/r_inside/src/setenv/setenv.c @@ -0,0 +1,87 @@ +// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; indent-tabs-mode: nil; -*- +// +// RInside.cpp: R/C++ interface class library -- Easier R embedding into C++ +// +// Copyright (C) 2009 - 2010 Dirk Eddelbuettel and Richard Holbrey +// Copyright (C) 2012 Dirk Eddelbuettel and Romain Francois +// +// This file is part of RInside. +// +// RInside is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// RInside is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RInside. If not, see . + +#include +#include +#include + +// borrowed from Renviron.c +extern "C" int setenv(const char *env_var, const char *env_val, int dummy) { + char *buf, *value, *p, *q, *a, *b, quote='\0'; + int inquote = 0; + + //make non-const copies + a = (char *) malloc((strlen(env_var) + 1) * sizeof(char)); + b = (char *) malloc((strlen(env_val) + 1) * sizeof(char)); + if (!a || !b) { + Rf_error("memory allocation failure in setenv"); + } + strcpy(a, env_var); + strcpy(b, env_val); + + buf = (char *) malloc((strlen(a) + strlen(b) + 2) * sizeof(char)); + if (!buf) { + Rf_error("memory allocation failure in setenv"); + } + strcpy(buf, a); strcat(buf, "="); + value = buf+strlen(buf); + + /* now process the value */ + for(p = b, q = value; *p; p++) { + /* remove quotes around sections, preserve \ inside quotes */ + if(!inquote && (*p == '"' || *p == '\'')) { + inquote = 1; + quote = *p; + continue; + } + + if(inquote && *p == quote && *(p-1) != '\\') { + inquote = 0; + continue; + } + + if(!inquote && *p == '\\') { + if(*(p+1) == '\n') { + p++; + } + else if(*(p+1) == '\\') { + *q++ = '/'; + p++; + } + else { + *q++ = '/'; + } + continue; + } + + if(inquote && *p == '\\' && *(p+1) == quote) + continue; + *q++ = *p; + } + *q = '\0'; + //if (putenv(buf)) + //warningcall(R_NilValue, _("problem in setting variable '%s' in Renviron"), a); + return putenv(buf); + + /* no free here: storage remains in use */ +} + diff --git a/external/r_inside/src/tools/RInsideAutoloads.r b/external/r_inside/src/tools/RInsideAutoloads.r new file mode 100755 index 000000000000..5f028478d0eb --- /dev/null +++ b/external/r_inside/src/tools/RInsideAutoloads.r @@ -0,0 +1,27 @@ +#!/usr/bin/r +# +# This owes a lot to autoloads.R in the littler sources + +dp <- getOption("defaultPackages") +#dp <- dp[dp != 'datasets'] ## Rscript loads it too +#dp <- dp[dp != 'methods'] ## Rscript (in R 2.6.1) doesn't load methods either + +# Count of default packages +cat(" int packc = ",length(dp),";\n",sep='') + +# List of packages +cat(" const char *pack[] = {\n",paste(' "',dp,'"',sep='',collapse=",\n"),"\n };\n", sep="") + +packobjc <- array(0,dim=length(dp)) +packobj <- NULL +for (i in 1:length(dp)){ + obj = ls(paste("package:",dp[i],sep='')) + packobjc[i] = length(obj) + packobj = c(packobj,obj) +} + +# List of counts of objects per package +cat(" int packobjc[] = {\n ",paste(packobjc,sep='',collapse=",\n "),"\n };\n", sep="") + +# List of object names +cat(" const char *packobj[] = {\n ",paste('"',packobj,'"',sep='',collapse=",\n "),"\n };\n", sep="") diff --git a/external/r_inside/src/tools/RInsideEnvVars.r b/external/r_inside/src/tools/RInsideEnvVars.r new file mode 100755 index 000000000000..99d37513acf0 --- /dev/null +++ b/external/r_inside/src/tools/RInsideEnvVars.r @@ -0,0 +1,19 @@ +#!/usr/bin/r -q +# +# This owes a lot to littler.R in the littler sources + +ExcludeVars <- c("R_SESSION_TMPDIR", "R_HISTFILE", "R_LIBRARY_DIR", "R_LIBS", + "R_PACKAGE_DIR", "R_SESSION_INITIALIZED") +IncludeVars <- Sys.getenv() +IncludeVars <- IncludeVars[grep("^R_",names(IncludeVars),perl=TRUE)] +if (.Platform$OS.type == "windows") { + IncludeVars <- gsub("\\\\", "/", IncludeVars, perl=TRUE) + IncludeVars <- gsub("\r", "", IncludeVars, fixed = TRUE) +} +cat(" const char *R_VARS[] = {\n") +for (i in 1:length(IncludeVars)){ + if (names(IncludeVars)[i] %in% ExcludeVars) + next + cat(' "',names(IncludeVars)[i],'","',IncludeVars[i],'",\n',sep='') +} +cat(" NULL\n };\n") diff --git a/external/r_inside/src/tools/unix2dos.r b/external/r_inside/src/tools/unix2dos.r new file mode 100644 index 000000000000..b7de21b844f1 --- /dev/null +++ b/external/r_inside/src/tools/unix2dos.r @@ -0,0 +1,17 @@ + +## simple 0d 0a -> 0a converter to suppress a warning on Windows + +filename <- commandArgs(trailingOnly=TRUE)[1] +if (!file.exists(filename)) q() + +con <- file(filename, "rb") +bin <- readBin(con, raw(), 100000) +bin <- bin[ which(bin != "0d") ] +close(con) + +Sys.sleep(1) + +con <- file(filename, "wb") +writeBin(bin, con) +close(con) + diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 73d89e717bd2..fb29626cc85f 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -417,11 +417,32 @@ if (POSTGRES_FOUND) endif() if(HAVE_R) +add_compile_definitions(RINSIDE_CALLBACKS=1) + EXEC_PROGRAM(Rscript + ARGS ${CMAKE_SOURCE_DIR}/external/r_inside/src/tools/RInsideAutoloads.r + OUTPUT_VARIABLE R_INSIDE_AUTOLOADS) + file(WRITE ${CMAKE_SOURCE_DIR}/external/r_inside/inst/include/RInsideAutoloads.h "${R_INSIDE_AUTOLOADS}") + + EXEC_PROGRAM(Rscript + ARGS ${CMAKE_SOURCE_DIR}/external/r_inside/src/tools/RInsideEnvVars.r + OUTPUT_VARIABLE R_INSIDE_ENV_VARS ) + file(WRITE ${CMAKE_SOURCE_DIR}/external/r_inside/inst/include/RInsideEnvVars.h "${R_INSIDE_ENV_VARS}") + set(QGIS_APP_SRCS ${QGIS_APP_SRCS} rstats/qgsrstatsrunner.cpp rstats/qgsrstatsconsole.cpp + + + + ${CMAKE_SOURCE_DIR}/external/r_inside/src/MemBuf.cpp + ${CMAKE_SOURCE_DIR}/external/r_inside/src/RInside.cpp + ${CMAKE_SOURCE_DIR}/external/r_inside/src/RInside_C.cpp + ${CMAKE_SOURCE_DIR}/external/r_inside/src/RcppExports.cpp + ${CMAKE_SOURCE_DIR}/external/r_inside/src/compiler.cpp ) + + endif() # Test data dir for QgsAppScreenShots @@ -724,27 +745,31 @@ endif() # ) #target_link_libraries(qgis_app rcpp_lib) - execute_process(COMMAND Rscript -e "RInside:::CxxFlags()" - OUTPUT_VARIABLE R_INSIDE_INCLUDE_DIRS) - string(REGEX REPLACE "^-I" "" R_INSIDE_INCLUDE_DIRS "${R_INSIDE_INCLUDE_DIRS}") - string(STRIP ${R_INSIDE_INCLUDE_DIRS} R_INSIDE_INCLUDE_DIRS ) - target_include_directories(qgis_app SYSTEM PUBLIC - ${R_INSIDE_INCLUDE_DIRS} - ) - - execute_process(COMMAND Rscript -e "RInside:::LdFlags()" - OUTPUT_VARIABLE R_INSIDE_LIBS) - if (${R_INSIDE_LIBS} MATCHES "[-][L]([^ ;])+") - string(SUBSTRING ${CMAKE_MATCH_0} ${NUM_TRUNC_CHARS} -1 R_INSIDE_LIBS_L) - message(STATUS "Found R-inside libs: ${R_INSIDE_LIBS_L}") - target_link_directories(qgis_app PUBLIC ${R_INSIDE_LIBS_L}) - endif() - - if (${R_INSIDE_LIBS} MATCHES "[-][l][R]([^;])+") - string(SUBSTRING ${CMAKE_MATCH_0} ${NUM_TRUNC_CHARS} -1 R_INSIDE_LIBS_l) - message(STATUS "Found R-inside libs: ${R_INSIDE_LIBS_l}") - target_link_libraries(qgis_app ${R_INSIDE_LIBS_l}) - endif() + #execute_process(COMMAND Rscript -e "RInside:::CxxFlags()" + # OUTPUT_VARIABLE R_INSIDE_INCLUDE_DIRS) + # string(REGEX REPLACE "^-I" "" R_INSIDE_INCLUDE_DIRS "${R_INSIDE_INCLUDE_DIRS}") + # string(STRIP ${R_INSIDE_INCLUDE_DIRS} R_INSIDE_INCLUDE_DIRS ) + ## target_include_directories(qgis_app SYSTEM PUBLIC + # ${R_INSIDE_INCLUDE_DIRS} + # ) + + # execute_process(COMMAND Rscript -e "RInside:::LdFlags()" + # OUTPUT_VARIABLE R_INSIDE_LIBS) + # if (${R_INSIDE_LIBS} MATCHES "[-][L]([^ ;])+") + ## string(SUBSTRING ${CMAKE_MATCH_0} ${NUM_TRUNC_CHARS} -1 R_INSIDE_LIBS_L) + # message(STATUS "Found R-inside libs: ${R_INSIDE_LIBS_L}") + # target_link_directories(qgis_app PUBLIC ${R_INSIDE_LIBS_L}) + # endif() + + # if (${R_INSIDE_LIBS} MATCHES "[-][l][R]([^;])+") + # string(SUBSTRING ${CMAKE_MATCH_0} ${NUM_TRUNC_CHARS} -1 R_INSIDE_LIBS_l) + # message(STATUS "Found R-inside libs: ${R_INSIDE_LIBS_l}") + # target_link_libraries(qgis_app ${R_INSIDE_LIBS_l}) + # endif() + + target_include_directories(qgis_app PUBLIC + ${CMAKE_SOURCE_DIR}/external/r_inside/inst/include + ) endif() diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index dbba067ec399..3323e5e88ebd 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -43,10 +43,18 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) } else { - mOutput->append( out.toString() ); + if ( !out.isValid() ) + mOutput->append( "NA" ); + else + mOutput->append( out.toString() ); } } ); + connect( mRunner, &QgsRStatsRunner::consoleMessage, this, [ = ]( const QString & message ) + { + mOutput->append( message ); + } ); + vl->addWidget( run ); setLayout( vl ); diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index 37bd131f5ecd..f0e09067c459 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -14,6 +14,7 @@ QgsRStatsRunner::QgsRStatsRunner() { mRSession = std::make_unique< RInside >( 0, nullptr, false, false, true ); + mRSession->set_callbacks( this ); const QString userPath = QgsApplication::qgisSettingsDirPath() + QStringLiteral( "r_libs" ); if ( !QFile::exists( userPath ) ) @@ -50,12 +51,20 @@ QVariant QgsRStatsRunner::execCommand( const QString &command, QString &error ) return QVariant(); case LGLSXP: - return Rcpp::as( res ); + { + const int resInt = Rcpp::as( res ); + if ( resInt < 0 ) + return QVariant(); + else + return static_cast< bool >( resInt ); + } case INTSXP: + // handle NA_integer_ as NA! return Rcpp::as( res ); case REALSXP: + // handle nan as NA return Rcpp::as( res ); case STRSXP: diff --git a/src/app/rstats/qgsrstatsrunner.h b/src/app/rstats/qgsrstatsrunner.h index 0efa64e94816..33a7ab228b78 100644 --- a/src/app/rstats/qgsrstatsrunner.h +++ b/src/app/rstats/qgsrstatsrunner.h @@ -18,13 +18,16 @@ #define QGSRSTATSRUNNER_H #include +#include +#include "Callbacks.h" class RInside; class QVariant; class QString; -class QgsRStatsRunner +class QgsRStatsRunner: public QObject, public Callbacks { + Q_OBJECT public: QgsRStatsRunner(); @@ -32,6 +35,19 @@ class QgsRStatsRunner QVariant execCommand( const QString& command, QString& error ); + void WriteConsole( const std::string& line, int type ) override { + emit consoleMessage( QString::fromStdString( line ) ); + }; + + virtual bool has_WriteConsole() { + return true; + }; + + + signals: + + void consoleMessage( const QString& message ); + private: std::unique_ptr< RInside > mRSession; From a8c948075dd64c1ed5f141d09043cf52f50b4f67 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 22 Sep 2022 19:32:32 +1000 Subject: [PATCH 03/42] Dock widget --- src/app/qgisapp.cpp | 5 +++-- src/app/qgisapp.h | 10 +++++++++- src/app/rstats/qgsrstatsconsole.cpp | 30 +++++++++++++++++++++++++++++ src/app/rstats/qgsrstatsconsole.h | 13 ++++++++----- src/app/rstats/qgsrstatsrunner.h | 24 +++++++++++++++++------ 5 files changed, 68 insertions(+), 14 deletions(-) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 87cbf8a72fe9..b9c258d48c98 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -1595,8 +1595,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipBadLayers #ifdef HAVE_R mRStatsRunner = new QgsRStatsRunner(); - QgsRStatsConsole *rConsole = new QgsRStatsConsole( nullptr, mRStatsRunner ); - rConsole->show(); + mRStatsConsole = new QgsRStatsConsole( nullptr, mRStatsRunner ); #endif // Update recent project list (as possible custom project storages are now registered by plugins) @@ -1959,6 +1958,8 @@ QgisApp::~QgisApp() #endif #ifdef HAVE_R + delete mRStatsConsole; + mRStatsConsole = nullptr; delete mRStatsRunner; mRStatsRunner = nullptr; #endif diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index f7c5a3268aed..83b42652c5fa 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -195,6 +195,10 @@ class QgsLegendFilterButton; class QgsGeoreferencerMainWindow; #endif +#ifdef HAVE_R +class QgsRStatsConsole; +#endif + /** * \class QgisApp * \brief Main window for the QGIS application @@ -2518,7 +2522,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow QgsPythonUtils *mPythonUtils = nullptr; - QgsRStatsRunner *mRStatsRunner = nullptr; + QgsRStatsRunner *mRStatsRunner = nullptr; static QgisApp *sInstance; @@ -2528,6 +2532,10 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow QgsBrowserDockWidget *mBrowserWidget = nullptr; QgsBrowserDockWidget *mBrowserWidget2 = nullptr; +#ifdef HAVE_R + QgsRStatsConsole *mRConsole = nullptr; +#endif + QgsTemporalControllerDockWidget *mTemporalControllerWidget = nullptr; QgsAdvancedDigitizingDockWidget *mAdvancedDigitizingDockWidget = nullptr; diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index 3323e5e88ebd..677cf544743d 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -16,17 +16,37 @@ #include "qgsrstatsconsole.h" #include "qgsrstatsrunner.h" +#include "qgisapp.h" +#include "qgsdockablewidgethelper.h" #include #include #include #include +#include QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) : QWidget( parent ) , mRunner( runner ) { + setObjectName( QStringLiteral( "RStatsConsole" ) ); + + QToolBar *toolBar = new QToolBar( this ); + toolBar->setIconSize( QgisApp::instance()->iconSize( true ) ); + + mDockableWidgetHelper = new QgsDockableWidgetHelper( true, tr( "R Stats Console" ), this, QgisApp::instance(), Qt::BottomDockWidgetArea, QStringList(), true ); + QToolButton *toggleButton = mDockableWidgetHelper->createDockUndockToolButton(); + toggleButton->setToolTip( tr( "Dock R Stats Console" ) ); + toolBar->addWidget( toggleButton ); + connect( mDockableWidgetHelper, &QgsDockableWidgetHelper::closed, this, [ = ]() + { +// close(); + } ); + QVBoxLayout *vl = new QVBoxLayout(); + vl->setContentsMargins( 0, 0, 0, 0 ); + vl->addWidget( toolBar ); + mOutput = new QTextBrowser(); vl->addWidget( mOutput, 1 ); mInputEdit = new QLineEdit(); @@ -36,6 +56,7 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) { const QString command = mInputEdit->text(); QString error; + mOutput->append( QStringLiteral( "> " ) + command ); const QVariant out = mRunner->execCommand( command, error ); if ( !error.isEmpty() ) { @@ -55,7 +76,16 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) mOutput->append( message ); } ); + connect( mRunner, &QgsRStatsRunner::showMessage, this, [ = ]( const QString & message ) + { + mOutput->append( message ); + } ); + vl->addWidget( run ); setLayout( vl ); +} +QgsRStatsConsole::~QgsRStatsConsole() +{ + delete mDockableWidgetHelper; } diff --git a/src/app/rstats/qgsrstatsconsole.h b/src/app/rstats/qgsrstatsconsole.h index d1c0f4645104..394f49f694b4 100644 --- a/src/app/rstats/qgsrstatsconsole.h +++ b/src/app/rstats/qgsrstatsconsole.h @@ -22,17 +22,20 @@ class QgsRStatsRunner; class QLineEdit; class QTextBrowser; +class QgsDockableWidgetHelper; class QgsRStatsConsole: public QWidget { public: - QgsRStatsConsole( QWidget* parent, QgsRStatsRunner* runner ); - + QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ); + ~QgsRStatsConsole() override; private: - QgsRStatsRunner* mRunner = nullptr; - QLineEdit* mInputEdit = nullptr; - QTextBrowser* mOutput = nullptr; + QgsRStatsRunner *mRunner = nullptr; + QLineEdit *mInputEdit = nullptr; + QTextBrowser *mOutput = nullptr; + QgsDockableWidgetHelper *mDockableWidgetHelper = nullptr; + }; #endif // QGSRSTATSCONSOLE_H diff --git a/src/app/rstats/qgsrstatsrunner.h b/src/app/rstats/qgsrstatsrunner.h index 33a7ab228b78..9149dbc4e263 100644 --- a/src/app/rstats/qgsrstatsrunner.h +++ b/src/app/rstats/qgsrstatsrunner.h @@ -33,20 +33,32 @@ class QgsRStatsRunner: public QObject, public Callbacks QgsRStatsRunner(); ~QgsRStatsRunner(); - QVariant execCommand( const QString& command, QString& error ); + QVariant execCommand( const QString &command, QString &error ); - void WriteConsole( const std::string& line, int type ) override { - emit consoleMessage( QString::fromStdString( line ) ); + void WriteConsole( const std::string &line, int type ) override + { + emit consoleMessage( QString::fromStdString( line ) ); }; - virtual bool has_WriteConsole() { - return true; + bool has_WriteConsole() override + { + return true; }; + void ShowMessage( const char *message ) override + { + emit showMessage( QString( message ) ); + } + + bool has_ShowMessage() override + { + return true; + } signals: - void consoleMessage( const QString& message ); + void consoleMessage( const QString &message ); + void showMessage( const QString &message ); private: From e5195c12fcaf6829f2b286f266efc514c3d1eda6 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 22 Sep 2022 19:44:02 +1000 Subject: [PATCH 04/42] Fix object name --- src/app/qgisapp.cpp | 6 +++--- src/app/qgsdockablewidgethelper.cpp | 9 +++++++++ src/app/qgsdockablewidgethelper.h | 4 ++++ src/app/rstats/qgsrstatsconsole.cpp | 7 +------ 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index b9c258d48c98..0bf93bab651f 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -1595,7 +1595,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipBadLayers #ifdef HAVE_R mRStatsRunner = new QgsRStatsRunner(); - mRStatsConsole = new QgsRStatsConsole( nullptr, mRStatsRunner ); + mRConsole = new QgsRStatsConsole( nullptr, mRStatsRunner ); #endif // Update recent project list (as possible custom project storages are now registered by plugins) @@ -1958,8 +1958,8 @@ QgisApp::~QgisApp() #endif #ifdef HAVE_R - delete mRStatsConsole; - mRStatsConsole = nullptr; + delete mRConsole; + mRConsole = nullptr; delete mRStatsRunner; mRStatsRunner = nullptr; #endif diff --git a/src/app/qgsdockablewidgethelper.cpp b/src/app/qgsdockablewidgethelper.cpp index 2c4d42a4b9e6..1056727332b9 100644 --- a/src/app/qgsdockablewidgethelper.cpp +++ b/src/app/qgsdockablewidgethelper.cpp @@ -228,6 +228,8 @@ void QgsDockableWidgetHelper::toggleDockMode( bool docked ) mDock->setWindowTitle( mWindowTitle ); mDock->setWidget( mWidget ); mDock->setProperty( "dock_uuid", mUuid ); + if ( !mDockObjectName.isEmpty() ) + mDock->setObjectName( mDockObjectName ); setupDockWidget(); connect( mDock, &QgsDockWidget::closed, this, [ = ]() @@ -286,6 +288,13 @@ void QgsDockableWidgetHelper::setWindowTitle( const QString &title ) } } +void QgsDockableWidgetHelper::setDockObjectName( const QString &name ) +{ + mDockObjectName = name; + if ( mDock ) + mDock->setObjectName( mDockObjectName ); +} + void QgsDockableWidgetHelper::setupDockWidget( const QStringList &tabSiblings ) { if ( !mDock ) diff --git a/src/app/qgsdockablewidgethelper.h b/src/app/qgsdockablewidgethelper.h index a13a230a4918..6fe09e635ff4 100644 --- a/src/app/qgsdockablewidgethelper.h +++ b/src/app/qgsdockablewidgethelper.h @@ -68,6 +68,8 @@ class APP_EXPORT QgsDockableWidgetHelper : public QObject //! Returns the displayed title of the dialog and the dock widget QString windowTitle() const { return mWindowTitle; } + void setDockObjectName( const QString &name ); + /** * Create a tool button for docking/undocking the widget * \note The ownership of the tool button is managed by the caller @@ -104,6 +106,8 @@ class APP_EXPORT QgsDockableWidgetHelper : public QObject QString mWindowTitle; QMainWindow *mOwnerWindow = nullptr; + QString mDockObjectName; + QStringList mTabifyWith; bool mRaiseTab = false; diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index 677cf544743d..f70465660283 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -29,19 +29,14 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) : QWidget( parent ) , mRunner( runner ) { - setObjectName( QStringLiteral( "RStatsConsole" ) ); - QToolBar *toolBar = new QToolBar( this ); toolBar->setIconSize( QgisApp::instance()->iconSize( true ) ); mDockableWidgetHelper = new QgsDockableWidgetHelper( true, tr( "R Stats Console" ), this, QgisApp::instance(), Qt::BottomDockWidgetArea, QStringList(), true ); + mDockableWidgetHelper->setDockObjectName( QStringLiteral( "RStatsConsole" ) ); QToolButton *toggleButton = mDockableWidgetHelper->createDockUndockToolButton(); toggleButton->setToolTip( tr( "Dock R Stats Console" ) ); toolBar->addWidget( toggleButton ); - connect( mDockableWidgetHelper, &QgsDockableWidgetHelper::closed, this, [ = ]() - { -// close(); - } ); QVBoxLayout *vl = new QVBoxLayout(); vl->setContentsMargins( 0, 0, 0, 0 ); From 4b74caea007d487f343950dff76da151fc139d06 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 22 Sep 2022 20:12:46 +1000 Subject: [PATCH 05/42] Inject some functions --- src/app/rstats/qgsrstatsconsole.cpp | 4 +-- src/app/rstats/qgsrstatsrunner.cpp | 50 ++++++++++++++++++++++++++++- src/app/rstats/qgsrstatsrunner.h | 1 + 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index f70465660283..23a8d95d6437 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -46,8 +46,7 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) vl->addWidget( mOutput, 1 ); mInputEdit = new QLineEdit(); vl->addWidget( mInputEdit ); - QPushButton *run = new QPushButton( "go" ); - connect( run, &QPushButton::clicked, this, [ = ] + connect( mInputEdit, &QLineEdit::returnPressed, this, [ = ] { const QString command = mInputEdit->text(); QString error; @@ -76,7 +75,6 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) mOutput->append( message ); } ); - vl->addWidget( run ); setLayout( vl ); } diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index f0e09067c459..f72d282f4f3f 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -4,13 +4,56 @@ #include #include - +#include "qgisapp.h" #include "qgslogger.h" #include #include #include #include +class QgsApplicationRWrapper +{ + public: + QgsApplicationRWrapper() {} + + int version() const { return Qgis::versionInt(); } + + std::string activeLayer() const + { + return QgisApp::instance()->activeLayer() ? + QgisApp::instance()->activeLayer()->name().toStdString() : std::string{}; + } + + +}; + +// The function which is called when running QGIS$... +SEXP Dollar( Rcpp::XPtr obj, std::string name ) +{ + if ( name == "versionInt" ) + { + return Rcpp::wrap( obj->version() ); + } + else if ( name == "activeLayer" ) + { + return Rcpp::wrap( obj->activeLayer() ); + } + else + { + return NULL; + } +} + + +// The function listing the elements of QGIS +Rcpp::CharacterVector Names( Rcpp::XPtr ) +{ + Rcpp::CharacterVector ret; + ret.push_back( "versionInt" ); + ret.push_back( "activeLayer" ); + return ret; +} + QgsRStatsRunner::QgsRStatsRunner() { mRSession = std::make_unique< RInside >( 0, nullptr, false, false, true ); @@ -25,6 +68,11 @@ QgsRStatsRunner::QgsRStatsRunner() execCommand( QStringLiteral( ".libPaths(\"%1\")" ).arg( userPath ), error ); + Rcpp::XPtr wr( new QgsApplicationRWrapper() ); + wr.attr( "class" ) = "QGIS"; + mRSession->assign( wr, "QGIS" ); + mRSession->assign( Rcpp::InternalFunction( & Dollar ), "$.QGIS" ); + mRSession->assign( Rcpp::InternalFunction( & Names ), "names.QGIS" ); //( *mRSession )["val"] = 5; //mRSession->parseEvalQ( "val2<-7" ); diff --git a/src/app/rstats/qgsrstatsrunner.h b/src/app/rstats/qgsrstatsrunner.h index 9149dbc4e263..1bbac57a12df 100644 --- a/src/app/rstats/qgsrstatsrunner.h +++ b/src/app/rstats/qgsrstatsrunner.h @@ -25,6 +25,7 @@ class RInside; class QVariant; class QString; + class QgsRStatsRunner: public QObject, public Callbacks { Q_OBJECT From 8da8f8f24ec8ffc5fe472b0007b00470323a418f Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 22 Sep 2022 20:24:50 +1000 Subject: [PATCH 06/42] Monospace font --- src/app/rstats/qgsrstatsconsole.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index 23a8d95d6437..f399bffbcec0 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -18,6 +18,7 @@ #include "qgsrstatsrunner.h" #include "qgisapp.h" #include "qgsdockablewidgethelper.h" +#include "qgscodeeditor.h" #include #include @@ -43,8 +44,10 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) vl->addWidget( toolBar ); mOutput = new QTextBrowser(); + mOutput->setFont( QgsCodeEditor::getMonospaceFont() ); vl->addWidget( mOutput, 1 ); mInputEdit = new QLineEdit(); + mInputEdit->setFont( QgsCodeEditor::getMonospaceFont() ); vl->addWidget( mInputEdit ); connect( mInputEdit, &QLineEdit::returnPressed, this, [ = ] { From f7ca9b0ec9c1b68e61e7cf33658a6b6468fc6312 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 24 Sep 2022 09:46:29 +1000 Subject: [PATCH 07/42] Cmake cleanup --- CMakeLists.txt | 10 ++++ cmake/FindR.cmake | 40 +++++++++++++ cmake/FindRCpp.cmake | 36 +++++++++++ src/app/CMakeLists.txt | 133 +++++++++-------------------------------- 4 files changed, 115 insertions(+), 104 deletions(-) create mode 100644 cmake/FindR.cmake create mode 100644 cmake/FindRCpp.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index af2af85dd69b..5fe24e153cb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -196,6 +196,16 @@ if(WITH_CORE) set (WITH_R FALSE CACHE BOOL "Determines whether the inbuilt R integration should be built") if(WITH_R) + find_package(R) + if (NOT ${R_FOUND}) + message(FATAL_ERROR "R library not found") + endif() + + find_package(RCpp) + if (NOT ${RCpp_FOUND}) + message(FATAL_ERROR "Rcpp library not found") + endif() + set(HAVE_R TRUE) # used in qgisconfig.h endif() diff --git a/cmake/FindR.cmake b/cmake/FindR.cmake new file mode 100644 index 000000000000..3bcf75d67527 --- /dev/null +++ b/cmake/FindR.cmake @@ -0,0 +1,40 @@ +# CMake module to search for R +# +# Once done this will define +# +# R_FOUND - system has the R library +# R_INCLUDE_DIR - the R library include directories +# R_LIB - the R library +# +# Copyright (c) 2022, Nyall Dawson, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +execute_process(COMMAND R CMD config --cppflags OUTPUT_VARIABLE R_INCLUDE_DIR_TMP) +string(REGEX REPLACE "^-I" "" R_INCLUDE_DIR_TMP "${R_INCLUDE_DIR_TMP}") +string(STRIP ${R_INCLUDE_DIR_TMP} R_INCLUDE_DIR_TMP) +set(R_INCLUDE_DIR "${R_INCLUDE_DIR_TMP}" CACHE STRING INTERNAL) + +#message(STATUS "Found R include dirs: ${R_INCLUDE_DIR}") + +execute_process(COMMAND R CMD config --ldflags OUTPUT_VARIABLE R_LDFLAGS) +if (${R_LDFLAGS} MATCHES "[-][L]([^ ;])+") + string(SUBSTRING ${CMAKE_MATCH_0} 2 -1 R_LIB_DIR) + string(STRIP ${R_LIB_DIR} R_LIB_DIR) + find_library(R_LIB + NAMES libR.so PATHS + "${R_LIB_DIR}" + ) +endif() + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(R DEFAULT_MSG + R_LIB R_INCLUDE_DIR) + +if(R_FOUND) + message(STATUS "Found R library: ${R_LIB}") +endif() + +mark_as_advanced(R_INCLUDE_DIR) +mark_as_advanced(R_LIB) diff --git a/cmake/FindRCpp.cmake b/cmake/FindRCpp.cmake new file mode 100644 index 000000000000..df8cc17fbc58 --- /dev/null +++ b/cmake/FindRCpp.cmake @@ -0,0 +1,36 @@ +# CMake module to search for RCpp +# +# Once done this will define +# +# RCpp_FOUND - system has the RCpp library +# RCpp_INCLUDE_DIR - the RCpp library include directories +# RCpp_LIB - the RCpp library +# +# Copyright (c) 2022, Nyall Dawson, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +execute_process(COMMAND Rscript -e "Rcpp:::CxxFlags()" + OUTPUT_VARIABLE RCpp_INCLUDE_DIR_TMP) +string(REGEX REPLACE "^-I" "" RCpp_INCLUDE_DIR_TMP "${RCpp_INCLUDE_DIR_TMP}") +string(STRIP ${RCpp_INCLUDE_DIR_TMP} RCpp_INCLUDE_DIR_TMP ) +string(REGEX REPLACE "^\"" "" RCpp_INCLUDE_DIR_TMP "${RCpp_INCLUDE_DIR_TMP}") +string(REGEX REPLACE "\"$" "" RCpp_INCLUDE_DIR_TMP "${RCpp_INCLUDE_DIR_TMP}") +set(RCpp_INCLUDE_DIR "${RCpp_INCLUDE_DIR_TMP}" CACHE STRING INTERNAL) + +find_library(RCpp_LIB + NAMES Rcpp.so PATHS + "${RCpp_INCLUDE_DIR}/../libs" +) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(RCpp DEFAULT_MSG + RCpp_LIB RCpp_INCLUDE_DIR) + +if(RCpp_FOUND) + message(STATUS "Found Rcpp library: ${RCpp_LIB}") +endif() + +mark_as_advanced(RCpp_INCLUDE_DIR) +mark_as_advanced(RCpp_LIB) diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index fb29626cc85f..34cd5078e430 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -409,40 +409,40 @@ else() set(QWTPOLAR_INCLUDE_DIR "") endif() - if (POSTGRES_FOUND) if(HAVE_PGCONFIG) add_definitions(-DHAVE_PGCONFIG=1) endif() endif() -if(HAVE_R) -add_compile_definitions(RINSIDE_CALLBACKS=1) - EXEC_PROGRAM(Rscript - ARGS ${CMAKE_SOURCE_DIR}/external/r_inside/src/tools/RInsideAutoloads.r - OUTPUT_VARIABLE R_INSIDE_AUTOLOADS) - file(WRITE ${CMAKE_SOURCE_DIR}/external/r_inside/inst/include/RInsideAutoloads.h "${R_INSIDE_AUTOLOADS}") +if(WITH_R) + add_compile_definitions(RINSIDE_CALLBACKS=1) + exec_program(Rscript + ARGS ${CMAKE_SOURCE_DIR}/external/r_inside/src/tools/RInsideAutoloads.r + OUTPUT_VARIABLE R_INSIDE_AUTOLOADS + ) + file(WRITE + ${CMAKE_SOURCE_DIR}/external/r_inside/inst/include/RInsideAutoloads.h "${R_INSIDE_AUTOLOADS}" + ) - EXEC_PROGRAM(Rscript - ARGS ${CMAKE_SOURCE_DIR}/external/r_inside/src/tools/RInsideEnvVars.r - OUTPUT_VARIABLE R_INSIDE_ENV_VARS ) - file(WRITE ${CMAKE_SOURCE_DIR}/external/r_inside/inst/include/RInsideEnvVars.h "${R_INSIDE_ENV_VARS}") + exec_program(Rscript + ARGS ${CMAKE_SOURCE_DIR}/external/r_inside/src/tools/RInsideEnvVars.r + OUTPUT_VARIABLE R_INSIDE_ENV_VARS ) + file(WRITE + ${CMAKE_SOURCE_DIR}/external/r_inside/inst/include/RInsideEnvVars.h "${R_INSIDE_ENV_VARS}" + ) set(QGIS_APP_SRCS ${QGIS_APP_SRCS} + ${CMAKE_SOURCE_DIR}/external/r_inside/src/MemBuf.cpp + ${CMAKE_SOURCE_DIR}/external/r_inside/src/RInside.cpp + ${CMAKE_SOURCE_DIR}/external/r_inside/src/RInside_C.cpp + ${CMAKE_SOURCE_DIR}/external/r_inside/src/RcppExports.cpp + ${CMAKE_SOURCE_DIR}/external/r_inside/src/compiler.cpp + rstats/qgsrstatsrunner.cpp rstats/qgsrstatsconsole.cpp - - - - ${CMAKE_SOURCE_DIR}/external/r_inside/src/MemBuf.cpp - ${CMAKE_SOURCE_DIR}/external/r_inside/src/RInside.cpp - ${CMAKE_SOURCE_DIR}/external/r_inside/src/RInside_C.cpp - ${CMAKE_SOURCE_DIR}/external/r_inside/src/RcppExports.cpp - ${CMAKE_SOURCE_DIR}/external/r_inside/src/compiler.cpp ) - - endif() # Test data dir for QgsAppScreenShots @@ -687,90 +687,15 @@ if (WITH_PDAL) endif() if (WITH_R) - execute_process(COMMAND R RHOME - OUTPUT_VARIABLE R_HOME) - set(NUM_TRUNC_CHARS 2) - - execute_process(COMMAND R CMD config --cppflags - OUTPUT_VARIABLE R_INCLUDE_DIRS) - string(REGEX REPLACE "^-I" "" R_INCLUDE_DIRS "${R_INCLUDE_DIRS}") - string(STRIP ${R_INCLUDE_DIRS} R_INCLUDE_DIRS ) - target_include_directories(qgis_app SYSTEM PUBLIC - ${R_INCLUDE_DIRS} - ) - - execute_process(COMMAND R CMD config --ldflags - OUTPUT_VARIABLE RLDFLAGS) - if (${RLDFLAGS} MATCHES "[-][L]([^ ;])+") - string(SUBSTRING ${CMAKE_MATCH_0} ${NUM_TRUNC_CHARS} -1 RLDFLAGS_L) - string(STRIP ${RLDFLAGS_L} R_LIB_DIR ) - message(STATUS "Found R libs: ${R_LIB_DIR}") - #target_link_directories(qgis_app PUBLIC ${R_LIB_DIR}) -target_link_libraries(qgis_app /usr/lib64/R/lib/libR.so) -endif() - - if (${RLDFLAGS} MATCHES "[-][l]([^;])+") - string(SUBSTRING ${CMAKE_MATCH_0} ${NUM_TRUNC_CHARS} -1 RLDFLAGS_l) - string(STRIP ${RLDFLAGS_l} RLDFLAGS_l ) - endif() - - execute_process(COMMAND Rscript -e "Rcpp:::CxxFlags()" - OUTPUT_VARIABLE R_CPP_INCLUDE_DIRS) - string(REGEX REPLACE "^-I" "" R_CPP_INCLUDE_DIRS "${R_CPP_INCLUDE_DIRS}") - string(STRIP ${R_CPP_INCLUDE_DIRS} R_CPP_INCLUDE_DIRS ) - string(REGEX REPLACE "^\"" "" R_CPP_INCLUDE_DIRS "${R_CPP_INCLUDE_DIRS}") - string(REGEX REPLACE "\"$" "" R_CPP_INCLUDE_DIRS "${R_CPP_INCLUDE_DIRS}") target_include_directories(qgis_app SYSTEM PUBLIC - ${R_CPP_INCLUDE_DIRS} - ) - - # execute_process(COMMAND Rscript -e "Rcpp:::LdFlags()" - # OUTPUT_VARIABLE R_CPP_LIBS) - # if (${R_CPP_LIBS} MATCHES "[-][L]([^ ;])+") - # string(SUBSTRING ${CMAKE_MATCH_0} ${NUM_TRUNC_CHARS} -1 R_CPP_LIBS_L) - # message(STATUS "Found R cpp libs: ${R_CPP_LIBS_L}") - #target_link_directories(qgis_app PUBLIC ${R_CPP_LIBS_L} ) - #endif() - - if (${R_CPP_LIBS} MATCHES "[-][l][R]([^;])+") - string(SUBSTRING ${CMAKE_MATCH_0} ${NUM_TRUNC_CHARS} -1 R_CPP_LIBS_l) - message(STATUS "Found R cpp libs: ${R_CPP_LIBS_l}") - target_link_libraries(qgis_app ${R_CPP_LIBS_l}) - endif() -# target_link_libraries(qgis_app /usr/lib64/R/library/Rcpp/libs/Rcpp.so) -# add_library(rcpp_lib SHARED IMPORTED) -# set_target_properties(rcpp_lib PROPERTIES -# IMPORTED_LOCATION /usr/lib64/R/library/Rcpp/libs/Rcpp.so -# IMPORTED_NO_SONAME FALSE -# ) -#target_link_libraries(qgis_app rcpp_lib) - - #execute_process(COMMAND Rscript -e "RInside:::CxxFlags()" - # OUTPUT_VARIABLE R_INSIDE_INCLUDE_DIRS) - # string(REGEX REPLACE "^-I" "" R_INSIDE_INCLUDE_DIRS "${R_INSIDE_INCLUDE_DIRS}") - # string(STRIP ${R_INSIDE_INCLUDE_DIRS} R_INSIDE_INCLUDE_DIRS ) - ## target_include_directories(qgis_app SYSTEM PUBLIC - # ${R_INSIDE_INCLUDE_DIRS} - # ) - - # execute_process(COMMAND Rscript -e "RInside:::LdFlags()" - # OUTPUT_VARIABLE R_INSIDE_LIBS) - # if (${R_INSIDE_LIBS} MATCHES "[-][L]([^ ;])+") - ## string(SUBSTRING ${CMAKE_MATCH_0} ${NUM_TRUNC_CHARS} -1 R_INSIDE_LIBS_L) - # message(STATUS "Found R-inside libs: ${R_INSIDE_LIBS_L}") - # target_link_directories(qgis_app PUBLIC ${R_INSIDE_LIBS_L}) - # endif() - - # if (${R_INSIDE_LIBS} MATCHES "[-][l][R]([^;])+") - # string(SUBSTRING ${CMAKE_MATCH_0} ${NUM_TRUNC_CHARS} -1 R_INSIDE_LIBS_l) - # message(STATUS "Found R-inside libs: ${R_INSIDE_LIBS_l}") - # target_link_libraries(qgis_app ${R_INSIDE_LIBS_l}) - # endif() - - target_include_directories(qgis_app PUBLIC - ${CMAKE_SOURCE_DIR}/external/r_inside/inst/include - ) - + ${CMAKE_SOURCE_DIR}/external/r_inside/inst/include + ${R_INCLUDE_DIR} + ${RCpp_INCLUDE_DIR} + ) + target_link_libraries(qgis_app + ${R_LIB} + ${RCpp_LIB} + ) endif() if(MSVC) From cf53d5b926a52bad5f2a18b156d6b44a398c2349 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 24 Sep 2022 09:54:56 +1000 Subject: [PATCH 08/42] Fix linking to RCpp --- cmake/FindRCpp.cmake | 3 +++ src/app/CMakeLists.txt | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cmake/FindRCpp.cmake b/cmake/FindRCpp.cmake index df8cc17fbc58..1419bf0f9ce4 100644 --- a/cmake/FindRCpp.cmake +++ b/cmake/FindRCpp.cmake @@ -32,5 +32,8 @@ if(RCpp_FOUND) message(STATUS "Found Rcpp library: ${RCpp_LIB}") endif() +add_library(Rcpp UNKNOWN IMPORTED) +set_property(TARGET Rcpp PROPERTY IMPORTED_LOCATION "${RCpp_LIB}") + mark_as_advanced(RCpp_INCLUDE_DIR) mark_as_advanced(RCpp_LIB) diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 34cd5078e430..a5030dc61c91 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -694,7 +694,7 @@ if (WITH_R) ) target_link_libraries(qgis_app ${R_LIB} - ${RCpp_LIB} + Rcpp ) endif() From 067758326a247ac331bcb8724229f6c035eeb42e Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 24 Sep 2022 10:13:55 +1000 Subject: [PATCH 09/42] Run R session in a background thread to avoid blocking ui --- src/app/rstats/qgsrstatsrunner.cpp | 50 ++++++++++++++++++++++++++---- src/app/rstats/qgsrstatsrunner.h | 35 ++++++++++++++++++--- 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index f72d282f4f3f..848db4528ce0 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -54,7 +54,10 @@ Rcpp::CharacterVector Names( Rcpp::XPtr ) return ret; } -QgsRStatsRunner::QgsRStatsRunner() +// +// QgsRStatsSession +// +QgsRStatsSession::QgsRStatsSession() { mRSession = std::make_unique< RInside >( 0, nullptr, false, false, true ); mRSession->set_callbacks( this ); @@ -74,8 +77,8 @@ QgsRStatsRunner::QgsRStatsRunner() mRSession->assign( Rcpp::InternalFunction( & Dollar ), "$.QGIS" ); mRSession->assign( Rcpp::InternalFunction( & Names ), "names.QGIS" ); - //( *mRSession )["val"] = 5; - //mRSession->parseEvalQ( "val2<-7" ); +//( *mRSession )["val"] = 5; +//mRSession->parseEvalQ( "val2<-7" ); // double aDouble = Rcpp::as( mRSession->parseEval( "1+2" ) ); // std::string aString = Rcpp::as( mRSession->parseEval( "'asdasdas'" ) ); @@ -84,10 +87,11 @@ QgsRStatsRunner::QgsRStatsRunner() // QgsDebugMsg( QString::fromStdString( Rcpp::as( R.parseEval( "cat(txt)" ) ) ) ); // QgsDebugMsg( QString::fromStdString( Rcpp::as( mRSession->parseEval( "as.character(val+2)" ) ) ) ); // QgsDebugMsg( QStringLiteral( "val as double: %1" ).arg( Rcpp::as( mRSession->parseEval( "val+val2" ) ) ) ); - } -QVariant QgsRStatsRunner::execCommand( const QString &command, QString &error ) +QgsRStatsSession::~QgsRStatsSession() = default; + +QVariant QgsRStatsSession::execCommand( const QString &command, QString &error ) { try { @@ -135,6 +139,40 @@ QVariant QgsRStatsRunner::execCommand( const QString &command, QString &error ) std::cerr << "Unknown exception caught" << std::endl; } return QVariant(); + +} + +void QgsRStatsSession::execCommand( const QString &command ) +{ + QString error; + execCommand( command, error ); } -QgsRStatsRunner::~QgsRStatsRunner() = default; + +// +// QgsRStatsRunner +// + +QgsRStatsRunner::QgsRStatsRunner() +{ + mSession = std::make_unique(); + mSession->moveToThread( &mSessionThread ); + mSessionThread.start(); + + connect( mSession.get(), &QgsRStatsSession::consoleMessage, this, &QgsRStatsRunner::consoleMessage ); + connect( mSession.get(), &QgsRStatsSession::showMessage, this, &QgsRStatsRunner::showMessage ); +} + +QgsRStatsRunner::~QgsRStatsRunner() +{ + mSessionThread.quit(); + mSessionThread.wait(); +} + +QVariant QgsRStatsRunner::execCommand( const QString &command, QString &error ) +{ + // todo error handling, result handling... + QMetaObject::invokeMethod( mSession.get(), "execCommand", Qt::QueuedConnection, + Q_ARG( QString, command ) ); + return QVariant(); +} diff --git a/src/app/rstats/qgsrstatsrunner.h b/src/app/rstats/qgsrstatsrunner.h index 1bbac57a12df..a65dd5101189 100644 --- a/src/app/rstats/qgsrstatsrunner.h +++ b/src/app/rstats/qgsrstatsrunner.h @@ -19,20 +19,20 @@ #include #include +#include #include "Callbacks.h" class RInside; class QVariant; class QString; - -class QgsRStatsRunner: public QObject, public Callbacks +class QgsRStatsSession: public QObject, public Callbacks { Q_OBJECT public: - QgsRStatsRunner(); - ~QgsRStatsRunner(); + QgsRStatsSession(); + ~QgsRStatsSession() override; QVariant execCommand( const QString &command, QString &error ); @@ -56,6 +56,10 @@ class QgsRStatsRunner: public QObject, public Callbacks return true; } + public slots: + + void execCommand( const QString &command ); + signals: void consoleMessage( const QString &message ); @@ -64,6 +68,29 @@ class QgsRStatsRunner: public QObject, public Callbacks private: std::unique_ptr< RInside > mRSession; + +}; + + +class QgsRStatsRunner: public QObject +{ + Q_OBJECT + public: + + QgsRStatsRunner(); + ~QgsRStatsRunner(); + + QVariant execCommand( const QString &command, QString &error ); + + signals: + + void consoleMessage( const QString &message ); + void showMessage( const QString &message ); + + private: + + QThread mSessionThread; + std::unique_ptr mSession; }; #endif // QGSRSTATSRUNNER_H From f54f18b10511f7ca6a1dc285d2ee184c6c2dd91b Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 24 Sep 2022 13:14:30 +1000 Subject: [PATCH 10/42] Don't accept new commands while session is busy --- src/app/rstats/qgsrstatsconsole.cpp | 8 ++++++++ src/app/rstats/qgsrstatsrunner.cpp | 14 ++++++++++++++ src/app/rstats/qgsrstatsrunner.h | 7 +++++++ 3 files changed, 29 insertions(+) diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index f399bffbcec0..1198776d8d55 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -51,6 +51,9 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) vl->addWidget( mInputEdit ); connect( mInputEdit, &QLineEdit::returnPressed, this, [ = ] { + if ( mRunner->busy() ) + return; + const QString command = mInputEdit->text(); QString error; mOutput->append( QStringLiteral( "> " ) + command ); @@ -78,6 +81,11 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) mOutput->append( message ); } ); + connect( mRunner, &QgsRStatsRunner::busyChanged, this, [ = ]( bool busy ) + { + mInputEdit->setEnabled( !busy ); + } ); + setLayout( vl ); } diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index 848db4528ce0..2e5ad743bda5 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -144,8 +144,15 @@ QVariant QgsRStatsSession::execCommand( const QString &command, QString &error ) void QgsRStatsSession::execCommand( const QString &command ) { + if ( mBusy ) + return; + + mBusy = true; + emit busyChanged( true ); QString error; execCommand( command, error ); + mBusy = false; + emit busyChanged( false ); } @@ -161,10 +168,12 @@ QgsRStatsRunner::QgsRStatsRunner() connect( mSession.get(), &QgsRStatsSession::consoleMessage, this, &QgsRStatsRunner::consoleMessage ); connect( mSession.get(), &QgsRStatsSession::showMessage, this, &QgsRStatsRunner::showMessage ); + connect( mSession.get(), &QgsRStatsSession::busyChanged, this, &QgsRStatsRunner::busyChanged ); } QgsRStatsRunner::~QgsRStatsRunner() { + // todo -- gracefully shut down session! mSessionThread.quit(); mSessionThread.wait(); } @@ -176,3 +185,8 @@ QVariant QgsRStatsRunner::execCommand( const QString &command, QString &error ) Q_ARG( QString, command ) ); return QVariant(); } + +bool QgsRStatsRunner::busy() const +{ + return mSession->busy(); +} diff --git a/src/app/rstats/qgsrstatsrunner.h b/src/app/rstats/qgsrstatsrunner.h index a65dd5101189..2edae2a69a17 100644 --- a/src/app/rstats/qgsrstatsrunner.h +++ b/src/app/rstats/qgsrstatsrunner.h @@ -56,18 +56,23 @@ class QgsRStatsSession: public QObject, public Callbacks return true; } + bool busy() const { return mBusy; } + public slots: void execCommand( const QString &command ); signals: + void busyChanged( bool busy ); + void consoleMessage( const QString &message ); void showMessage( const QString &message ); private: std::unique_ptr< RInside > mRSession; + bool mBusy = false; }; @@ -81,11 +86,13 @@ class QgsRStatsRunner: public QObject ~QgsRStatsRunner(); QVariant execCommand( const QString &command, QString &error ); + bool busy() const; signals: void consoleMessage( const QString &message ); void showMessage( const QString &message ); + void busyChanged( bool busy ); private: From 64748f923219a045a5f4b0159b4389494180499d Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 24 Sep 2022 13:29:31 +1000 Subject: [PATCH 11/42] Fix error handling --- src/app/rstats/qgsrstatsconsole.cpp | 31 ++++++++++++++++------------- src/app/rstats/qgsrstatsrunner.cpp | 12 +++++++---- src/app/rstats/qgsrstatsrunner.h | 10 ++++++---- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index 1198776d8d55..6cbfd0ed8bce 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -55,25 +55,28 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) return; const QString command = mInputEdit->text(); - QString error; mOutput->append( QStringLiteral( "> " ) + command ); - const QVariant out = mRunner->execCommand( command, error ); - if ( !error.isEmpty() ) - { - mOutput->setHtml( mOutput->toHtml() + QStringLiteral( "

%1

" ).arg( error ) ); - } + mRunner->execCommand( command ); +#if 0 + if ( !out.isValid() ) + mOutput->append( "NA" ); else - { - if ( !out.isValid() ) - mOutput->append( "NA" ); - else - mOutput->append( out.toString() ); - } + mOutput->append( out.toString() ); +#endif + } ); - connect( mRunner, &QgsRStatsRunner::consoleMessage, this, [ = ]( const QString & message ) + connect( mRunner, &QgsRStatsRunner::errorOccurred, this, [ = ]( const QString & error ) { - mOutput->append( message ); + mOutput->setHtml( mOutput->toHtml() + QStringLiteral( "

%1

" ).arg( error ) ); + } ); + + connect( mRunner, &QgsRStatsRunner::consoleMessage, this, [ = ]( const QString & message, int type ) + { + if ( type == 0 ) + mOutput->append( message ); + else + mOutput->setHtml( mOutput->toHtml() + QStringLiteral( "

%1

" ).arg( message ) ); } ); connect( mRunner, &QgsRStatsRunner::showMessage, this, [ = ]( const QString & message ) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index 2e5ad743bda5..c226324dafa3 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -96,7 +96,6 @@ QVariant QgsRStatsSession::execCommand( const QString &command, QString &error ) try { SEXP res = mRSession->parseEval( command.toStdString() ); - switch ( TYPEOF( res ) ) { case NILSXP: @@ -129,6 +128,8 @@ QVariant QgsRStatsSession::execCommand( const QString &command, QString &error ) QgsDebugMsg( "Unhandledtype!!!" ); return QVariant(); } + + return QVariant(); } catch ( std::exception &ex ) { @@ -151,6 +152,9 @@ void QgsRStatsSession::execCommand( const QString &command ) emit busyChanged( true ); QString error; execCommand( command, error ); + if ( ! error.isEmpty() ) + emit errorOccurred( error ); + mBusy = false; emit busyChanged( false ); } @@ -168,6 +172,7 @@ QgsRStatsRunner::QgsRStatsRunner() connect( mSession.get(), &QgsRStatsSession::consoleMessage, this, &QgsRStatsRunner::consoleMessage ); connect( mSession.get(), &QgsRStatsSession::showMessage, this, &QgsRStatsRunner::showMessage ); + connect( mSession.get(), &QgsRStatsSession::errorOccurred, this, &QgsRStatsRunner::errorOccurred ); connect( mSession.get(), &QgsRStatsSession::busyChanged, this, &QgsRStatsRunner::busyChanged ); } @@ -178,12 +183,11 @@ QgsRStatsRunner::~QgsRStatsRunner() mSessionThread.wait(); } -QVariant QgsRStatsRunner::execCommand( const QString &command, QString &error ) +void QgsRStatsRunner::execCommand( const QString &command ) { - // todo error handling, result handling... + // todo result handling... QMetaObject::invokeMethod( mSession.get(), "execCommand", Qt::QueuedConnection, Q_ARG( QString, command ) ); - return QVariant(); } bool QgsRStatsRunner::busy() const diff --git a/src/app/rstats/qgsrstatsrunner.h b/src/app/rstats/qgsrstatsrunner.h index 2edae2a69a17..42a2bd34a265 100644 --- a/src/app/rstats/qgsrstatsrunner.h +++ b/src/app/rstats/qgsrstatsrunner.h @@ -38,7 +38,7 @@ class QgsRStatsSession: public QObject, public Callbacks void WriteConsole( const std::string &line, int type ) override { - emit consoleMessage( QString::fromStdString( line ) ); + emit consoleMessage( QString::fromStdString( line ), type ); }; bool has_WriteConsole() override @@ -66,8 +66,9 @@ class QgsRStatsSession: public QObject, public Callbacks void busyChanged( bool busy ); - void consoleMessage( const QString &message ); + void consoleMessage( const QString &message, int type ); void showMessage( const QString &message ); + void errorOccurred( const QString &error ); private: @@ -85,13 +86,14 @@ class QgsRStatsRunner: public QObject QgsRStatsRunner(); ~QgsRStatsRunner(); - QVariant execCommand( const QString &command, QString &error ); + void execCommand( const QString &command ); bool busy() const; signals: - void consoleMessage( const QString &message ); + void consoleMessage( const QString &message, int type ); void showMessage( const QString &message ); + void errorOccurred( const QString &error ); void busyChanged( bool busy ); private: From 5cc66964dcee86a76c4407501327feb778a75028 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 24 Sep 2022 13:32:37 +1000 Subject: [PATCH 12/42] Fix result handling --- src/app/rstats/qgsrstatsconsole.cpp | 14 +++++++------- src/app/rstats/qgsrstatsrunner.cpp | 5 ++++- src/app/rstats/qgsrstatsrunner.h | 2 ++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index 6cbfd0ed8bce..9e84240325ae 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -57,19 +57,19 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) const QString command = mInputEdit->text(); mOutput->append( QStringLiteral( "> " ) + command ); mRunner->execCommand( command ); -#if 0 - if ( !out.isValid() ) - mOutput->append( "NA" ); - else - mOutput->append( out.toString() ); -#endif - } ); connect( mRunner, &QgsRStatsRunner::errorOccurred, this, [ = ]( const QString & error ) { mOutput->setHtml( mOutput->toHtml() + QStringLiteral( "

%1

" ).arg( error ) ); } ); + connect( mRunner, &QgsRStatsRunner::commandFinished, this, [ = ]( const QVariant & result ) + { + if ( !result.isValid() ) + mOutput->append( "NA" ); + else + mOutput->append( result.toString() ); + } ); connect( mRunner, &QgsRStatsRunner::consoleMessage, this, [ = ]( const QString & message, int type ) { diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index c226324dafa3..43d93ab8d3a2 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -151,9 +151,11 @@ void QgsRStatsSession::execCommand( const QString &command ) mBusy = true; emit busyChanged( true ); QString error; - execCommand( command, error ); + const QVariant res = execCommand( command, error ); if ( ! error.isEmpty() ) emit errorOccurred( error ); + else + emit commandFinished( res ); mBusy = false; emit busyChanged( false ); @@ -174,6 +176,7 @@ QgsRStatsRunner::QgsRStatsRunner() connect( mSession.get(), &QgsRStatsSession::showMessage, this, &QgsRStatsRunner::showMessage ); connect( mSession.get(), &QgsRStatsSession::errorOccurred, this, &QgsRStatsRunner::errorOccurred ); connect( mSession.get(), &QgsRStatsSession::busyChanged, this, &QgsRStatsRunner::busyChanged ); + connect( mSession.get(), &QgsRStatsSession::commandFinished, this, &QgsRStatsRunner::commandFinished ); } QgsRStatsRunner::~QgsRStatsRunner() diff --git a/src/app/rstats/qgsrstatsrunner.h b/src/app/rstats/qgsrstatsrunner.h index 42a2bd34a265..43e50a04c33d 100644 --- a/src/app/rstats/qgsrstatsrunner.h +++ b/src/app/rstats/qgsrstatsrunner.h @@ -69,6 +69,7 @@ class QgsRStatsSession: public QObject, public Callbacks void consoleMessage( const QString &message, int type ); void showMessage( const QString &message ); void errorOccurred( const QString &error ); + void commandFinished( const QVariant &result ); private: @@ -95,6 +96,7 @@ class QgsRStatsRunner: public QObject void showMessage( const QString &message ); void errorOccurred( const QString &error ); void busyChanged( bool busy ); + void commandFinished( const QVariant &result ); private: From 10fb9ca72000ee8e1680ce23ae5d54f9256373fc Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Tue, 4 Oct 2022 23:15:31 +0200 Subject: [PATCH 13/42] add functions to convert from SEXP to std::string --- src/app/rstats/qgsrstatsrunner.cpp | 14 ++++++++++++++ src/app/rstats/qgsrstatsrunner.h | 1 + 2 files changed, 15 insertions(+) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index 43d93ab8d3a2..992bedb6fe90 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -91,6 +91,20 @@ QgsRStatsSession::QgsRStatsSession() QgsRStatsSession::~QgsRStatsSession() = default; +std::string QgsRStatsSession::sexpToString(const SEXP exp) +{ + Rcpp::StringVector lines = Rcpp::StringVector(Rf_eval(Rf_lang2(Rf_install("capture.output"), exp), R_GlobalEnv)); + std::string outcome = ""; + for (auto it = lines.begin(); it != lines.end(); it++) + { + Rcpp::String line(it->get()); + outcome.append(line); + if (it < lines.end() - 1) + outcome.append("\n"); + } + return outcome; +} + QVariant QgsRStatsSession::execCommand( const QString &command, QString &error ) { try diff --git a/src/app/rstats/qgsrstatsrunner.h b/src/app/rstats/qgsrstatsrunner.h index 43e50a04c33d..cbd57450c417 100644 --- a/src/app/rstats/qgsrstatsrunner.h +++ b/src/app/rstats/qgsrstatsrunner.h @@ -76,6 +76,7 @@ class QgsRStatsSession: public QObject, public Callbacks std::unique_ptr< RInside > mRSession; bool mBusy = false; + std::string sexpToString(const SEXP exp); }; From 34366aa7d59b0b66dab278e5a44b7d57e8aa1686 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Tue, 4 Oct 2022 23:16:14 +0200 Subject: [PATCH 14/42] when command is evaluated, send result to console, formatted by R --- src/app/rstats/qgsrstatsrunner.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index 992bedb6fe90..fdfff4b82828 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -110,6 +110,9 @@ QVariant QgsRStatsSession::execCommand( const QString &command, QString &error ) try { SEXP res = mRSession->parseEval( command.toStdString() ); + + WriteConsole(sexpToString(res), 0); + switch ( TYPEOF( res ) ) { case NILSXP: From fc83c2025de2c7af8590977ba8938e0a7cd2a1b0 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Tue, 4 Oct 2022 23:16:52 +0200 Subject: [PATCH 15/42] only return value if there is 1, otherwise return nothing for now --- src/app/rstats/qgsrstatsrunner.cpp | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index fdfff4b82828..f99c179120d1 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -120,23 +120,38 @@ QVariant QgsRStatsSession::execCommand( const QString &command, QString &error ) case LGLSXP: { - const int resInt = Rcpp::as( res ); - if ( resInt < 0 ) - return QVariant(); + if (LENGTH(res) == 1) + { + const int resInt = Rcpp::as( res ); + if ( resInt < 0 ) + return QVariant(); + else + return static_cast< bool >( resInt ); + } else - return static_cast< bool >( resInt ); + return QVariant(); + } case INTSXP: // handle NA_integer_ as NA! - return Rcpp::as( res ); + if (LENGTH(res) == 1) + return Rcpp::as( res ); + else + return QVariant(); case REALSXP: // handle nan as NA - return Rcpp::as( res ); + if (LENGTH(res) == 1) + return Rcpp::as( res ); + else + return QVariant(); case STRSXP: - return QString::fromStdString( Rcpp::as( res ) ); + if (LENGTH(res) == 1) + return QString::fromStdString( Rcpp::as( res ) ); + else + return QVariant(); //case RAWSXP: // return R::rawPointer( res ); From eb3657633a976cda9af2726277fc634a85bec444 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Tue, 4 Oct 2022 23:17:43 +0200 Subject: [PATCH 16/42] on command finished, do not add string to output, it is handled elsewhere --- src/app/rstats/qgsrstatsconsole.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index 9e84240325ae..974aa5f1adcf 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -63,12 +63,13 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) { mOutput->setHtml( mOutput->toHtml() + QStringLiteral( "

%1

" ).arg( error ) ); } ); + connect( mRunner, &QgsRStatsRunner::commandFinished, this, [ = ]( const QVariant & result ) { - if ( !result.isValid() ) - mOutput->append( "NA" ); - else - mOutput->append( result.toString() ); + //if ( !result.isValid() ) + // mOutput->append( "NA" ); + //else + // mOutput->append( result.toString() ); } ); connect( mRunner, &QgsRStatsRunner::consoleMessage, this, [ = ]( const QString & message, int type ) From ed8a71a4d596be084879b9030dc8c9774ddf088d Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 3 Oct 2022 16:02:49 +1000 Subject: [PATCH 17/42] Add QgsCodeEditorR code editor subclass for R scripts --- .../codeeditors/qgscodeeditorr.sip.in | 44 ++++ python/gui/gui_auto.sip | 3 + src/app/options/qgscodeeditoroptions.cpp | 22 ++ src/gui/CMakeLists.txt | 2 + src/gui/codeeditors/qgscodeeditorr.cpp | 194 ++++++++++++++++++ src/gui/codeeditors/qgscodeeditorr.h | 86 ++++++++ src/ui/qgscodeditorsettings.ui | 34 ++- 7 files changed, 384 insertions(+), 1 deletion(-) create mode 100644 python/gui/auto_generated/codeeditors/qgscodeeditorr.sip.in create mode 100644 src/gui/codeeditors/qgscodeeditorr.cpp create mode 100644 src/gui/codeeditors/qgscodeeditorr.h diff --git a/python/gui/auto_generated/codeeditors/qgscodeeditorr.sip.in b/python/gui/auto_generated/codeeditors/qgscodeeditorr.sip.in new file mode 100644 index 000000000000..e5e3f0750076 --- /dev/null +++ b/python/gui/auto_generated/codeeditors/qgscodeeditorr.sip.in @@ -0,0 +1,44 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/codeeditors/qgscodeeditorr.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +class QgsCodeEditorR : QgsCodeEditor +{ +%Docstring(signature="appended") +A R stats code editor based on QScintilla2. Adds syntax highlighting and +code autocompletion. + +.. versionadded:: 3.28 +%End + +%TypeHeaderCode +#include "qgscodeeditorr.h" +%End + public: + + QgsCodeEditorR( QWidget *parent /TransferThis/ = 0 ); +%Docstring +Constructor for QgsCodeEditorR +%End + + protected: + virtual void initializeLexer(); + + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/codeeditors/qgscodeeditorr.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index 43f86ac9b792..1d4694bf2995 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -292,6 +292,9 @@ %Include auto_generated/codeeditors/qgscodeeditorpython.sip %End %If ( HAVE_QSCI_SIP ) +%Include auto_generated/codeeditors/qgscodeeditorr.sip +%End +%If ( HAVE_QSCI_SIP ) %Include auto_generated/codeeditors/qgscodeeditorsql.sip %End %Include auto_generated/devtools/qgsdevtoolwidget.sip diff --git a/src/app/options/qgscodeeditoroptions.cpp b/src/app/options/qgscodeeditoroptions.cpp index bcb56b619cdb..ef8094b1389f 100644 --- a/src/app/options/qgscodeeditoroptions.cpp +++ b/src/app/options/qgscodeeditoroptions.cpp @@ -166,6 +166,7 @@ QgsCodeEditorOptionsWidget::QgsCodeEditorOptionsWidget( QWidget *parent ) mListLanguage->addItem( tr( "HTML" ) ); mListLanguage->addItem( tr( "CSS" ) ); mListLanguage->addItem( tr( "JavaScript" ) ); + mListLanguage->addItem( tr( "R" ) ); connect( mListLanguage, &QListWidget::currentRowChanged, this, [ = ] { @@ -262,6 +263,26 @@ window.onAction(function update() { } });)""" ); + mRPreview->setText( R"""(# a comment +x <- 1:12 +sample(x) +sample(x, replace = TRUE) + +resample <- function(x, ...) x[sample.int(length(x), ...)] +resample(x[x > 8]) # length 2 + +a_variable <- "My string" + +`%func_name%` <- function(arg_1,arg_2) { + # function body +} + +`%pwr%` <- function(x,y) +{ + return(x^y) +} +)"""); + mListLanguage->setCurrentRow( 0 ); mPreviewStackedWidget->setCurrentIndex( 0 ); @@ -336,6 +357,7 @@ void QgsCodeEditorOptionsWidget::updatePreview() mHtmlPreview->setCustomAppearance( theme, colors, fontFamily, fontSize ); mCssPreview->setCustomAppearance( theme, colors, fontFamily, fontSize ); mJsPreview->setCustomAppearance( theme, colors, fontFamily, fontSize ); + mRPreview->setCustomAppearance( theme, colors, fontFamily, fontSize ); } // diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index f4641a136592..5e4bf356f01d 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -132,6 +132,7 @@ set(QGIS_GUI_SRCS codeeditors/qgscodeeditorjs.cpp codeeditors/qgscodeeditorjson.cpp codeeditors/qgscodeeditorpython.cpp + codeeditors/qgscodeeditorr.cpp codeeditors/qgscodeeditorsql.cpp codeeditors/qgscodeeditorexpression.cpp @@ -1001,6 +1002,7 @@ set(QGIS_GUI_HDRS codeeditors/qgscodeeditorjs.h codeeditors/qgscodeeditorjson.h codeeditors/qgscodeeditorpython.h + codeeditors/qgscodeeditorr.h codeeditors/qgscodeeditorsql.h devtools/qgsdevtoolwidget.h diff --git a/src/gui/codeeditors/qgscodeeditorr.cpp b/src/gui/codeeditors/qgscodeeditorr.cpp new file mode 100644 index 000000000000..71270dff4c8d --- /dev/null +++ b/src/gui/codeeditors/qgscodeeditorr.cpp @@ -0,0 +1,194 @@ +/*************************************************************************** + qgscodeeditorr.cpp - A R stats editor based on QScintilla + -------------------------------------- + Date : October 2022 + Copyright : (C) 2022 by Nyall Dawson + Email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsapplication.h" +#include "qgscodeeditorr.h" + +#include +#include +#include +#include + + +QgsCodeEditorR::QgsCodeEditorR( QWidget *parent ) + : QgsCodeEditor( parent ) +{ + if ( !parent ) + { + setTitle( tr( "R Editor" ) ); + } + setFoldingVisible( true ); + QgsCodeEditorR::initializeLexer(); +} + +void QgsCodeEditorR::initializeLexer() +{ + QgsQsciLexerR *lexer = new QgsQsciLexerR( this ); + + QFont font = lexerFont(); + lexer->setDefaultFont( font ); + lexer->setFont( font, -1 ); + + font.setItalic( true ); + lexer->setFont( font, QgsQsciLexerR::Comment ); + + lexer->setDefaultColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Default ) ); + lexer->setDefaultPaper( lexerColor( QgsCodeEditorColorScheme::ColorRole::Background ) ); + lexer->setPaper( lexerColor( QgsCodeEditorColorScheme::ColorRole::Background ), -1 ); + + lexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Default ), QgsQsciLexerR::Default ); + lexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::CommentLine ), QgsQsciLexerR::Comment ); + lexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Keyword ), QgsQsciLexerR::Kword ); + lexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Method ), QgsQsciLexerR::BaseKword ); + lexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Class ), QgsQsciLexerR::OtherKword ); + lexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Number ), QgsQsciLexerR::Number ); + lexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::DoubleQuote ), QgsQsciLexerR::String ); + lexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::DoubleQuote ), QgsQsciLexerR::String2 ); + lexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Operator ), QgsQsciLexerR::Operator ); + lexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Identifier ), QgsQsciLexerR::Identifier ); + lexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Tag ), QgsQsciLexerR::Infix ); + lexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::UnknownTag ), QgsQsciLexerR::InfixEOL ); + lexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Tag ), QgsQsciLexerR::Backticks ); + lexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::SingleQuote ), QgsQsciLexerR::RawString ); + lexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::SingleQuote ), QgsQsciLexerR::RawString2 ); + lexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Decoration ), QgsQsciLexerR::EscapeSequence ); + + setLexer( lexer ); + setLineNumbersVisible( true ); + runPostLexerConfigurationTasks(); +} + +/// @cond PRIVATE +QgsQsciLexerR::QgsQsciLexerR( QObject *parent ) + : QsciLexer( parent ) +{ + +} + +const char *QgsQsciLexerR::language() const +{ + return "r"; +} + +const char *QgsQsciLexerR::lexer() const +{ + return nullptr; +} + +int QgsQsciLexerR::lexerId() const +{ + return QsciScintillaBase::SCLEX_R; +} + +QString QgsQsciLexerR::description( int style ) const +{ + switch ( style ) + { + case Default: + return tr( "Default" ); + case Comment: + return tr( "Comment" ); + case Kword: + return tr( "Keyword" ); + case BaseKword: + return tr( "Base Keyword" ); + case OtherKword: + return tr( "Other Keyword" ); + case Number: + return tr( "Number" ); + case String: + return tr( "String" ); + case String2: + return tr( "String 2" ); + case Operator: + return tr( "Operator" ); + case Identifier: + return tr( "Identifier" ); + case Infix: + return tr( "Infix" ); + case InfixEOL: + return tr( "Infix EOL" ); + case Backticks: + return tr( "Backticks" ); + case RawString: + return tr( "Raw String" ); + case RawString2: + return tr( "Raw String 2" ); + case EscapeSequence: + return tr( "Escape Sequence" ); + } + return QString(); +} + +const char *QgsQsciLexerR::keywords( int set ) const +{ + switch ( set ) + { + case 1: + return "if else repeat while function for in next break TRUE FALSE NULL NA Inf NaN"; + + case 2: + return "abbreviate abline abs acf acos acosh addmargins aggregate agrep alarm alias alist all anova any aov aperm append apply approx " + "approxfun apropos ar args arima array arrows asin asinh assign assocplot atan atanh attach attr attributes autoload autoloader " + "ave axis backsolve barplot basename beta bindtextdomain binomial biplot bitmap bmp body box boxplot bquote break browser builtins " + "bxp by bzfile c call cancor capabilities casefold cat category cbind ccf ceiling character charmatch chartr chol choose chull " + "citation class close cm cmdscale codes coef coefficients col colnames colors colorspaces colours comment complex confint " //#spellok + "conflicts contour contrasts contributors convolve cophenetic coplot cor cos cosh cov covratio cpgram crossprod cummax cummin " + "cumprod cumsum curve cut cutree cycle data dataentry date dbeta dbinom dcauchy dchisq de debug debugger decompose delay deltat " + "demo dendrapply density deparse deriv det detach determinant deviance dexp df dfbeta dfbetas dffits dgamma dgeom dget dhyper " + "diag diff diffinv difftime digamma dim dimnames dir dirname dist dlnorm dlogis dmultinom dnbinom dnorm dotchart double dpois " + "dput drop dsignrank dt dump dunif duplicated dweibull dwilcox eapply ecdf edit effects eigen emacs embed end environment eval " + "evalq example exists exp expression factanal factor factorial family fft fifo file filter find fitted fivenum fix floor flush " + "for force formals format formula forwardsolve fourfoldplot frame frequency ftable function gamma gaussian gc gcinfo gctorture " + "get getenv geterrmessage gettext gettextf getwd gl glm globalenv gray grep grey grid gsub gzcon gzfile hat hatvalues hcl " + "hclust head heatmap help hist history hsv httpclient iconv iconvlist identical identify if ifelse image influence inherits " + "integer integrate interaction interactive intersect invisible isoreg jitter jpeg julian kappa kernapply kernel kmeans knots " + "kronecker ksmooth labels lag lapply layout lbeta lchoose lcm legend length letters levels lfactorial lgamma library licence " + "license line lines list lm load loadhistory loadings local locator loess log logb logical loglin lowess ls lsfit machine mad " + "mahalanobis makepredictcall manova mapply match matlines matplot matpoints matrix max mean median medpolish menu merge " + "message methods mget min missing mode monthplot months mosaicplot mtext mvfft names napredict naprint naresid nargs nchar " + "ncol next nextn ngettext nlevels nlm nls noquote nrow numeric objects offset open optim optimise optimize options order " + "ordered outer pacf page pairlist pairs palette par parse paste pbeta pbinom pbirthday pcauchy pchisq pdf pentagamma person " + "persp pexp pf pgamma pgeom phyper pi pico pictex pie piechart pipe plclust plnorm plogis plot pmatch pmax pmin pnbinom png " + "pnorm points poisson poly polygon polym polyroot postscript power ppoints ppois ppr prcomp predict preplot pretty princomp " + "print prmatrix prod profile profiler proj promax prompt provide psigamma psignrank pt ptukey punif pweibull pwilcox q qbeta " + "qbinom qbirthday qcauchy qchisq qexp qf qgamma qgeom qhyper qlnorm qlogis qnbinom qnorm qpois qqline qqnorm qqplot qr " + "qsignrank qt qtukey quantile quarters quasi quasibinomial quasipoisson quit qunif quote qweibull qwilcox rainbow range " + "rank raw rbeta rbind rbinom rcauchy rchisq readline real recover rect reformulate regexpr relevel remove reorder rep repeat " + "replace replicate replications require reshape resid residuals restart return rev rexp rf rgamma rgb rgeom rhyper rle rlnorm " + "rlogis rm rmultinom rnbinom rnorm round row rownames rowsum rpois rsignrank rstandard rstudent rt rug runif runmed rweibull " + "rwilcox sample sapply save savehistory scale scan screen screeplot sd search searchpaths seek segments seq sequence serialize " + "setdiff setequal setwd shell sign signif sin single sinh sink smooth solve sort source spectrum spline splinefun split sprintf " + "sqrt stack stars start stderr stdin stdout stem step stepfun stl stop stopifnot str strftime strheight stripchart strptime " + "strsplit strtrim structure strwidth strwrap sub subset substitute substr substring sum summary sunflowerplot supsmu svd sweep " + "switch symbols symnum system t table tabulate tail tan tanh tapply tempdir tempfile termplot terms tetragamma text time title " + "toeplitz tolower topenv toupper trace traceback transform trigamma trunc truncate try ts tsdiag tsp typeof unclass undebug " + "union unique uniroot unix unlink unlist unname unserialize unsplit unstack untrace unz update upgrade url var varimax vcov " + "vector version vi vignette warning warnings weekdays weights which while window windows with write wsbrowser xedit xemacs " + "xfig xinch xor xtabs xyinch yinch zapsmall"; + + case 3: + return "acme aids aircondit amis aml banking barchart barley beaver bigcity boot brambles breslow bs bwplot calcium cane capability " + "cav censboot channing city claridge cloth cloud coal condense contourplot control corr darwin densityplot dogs dotplot ducks " + "empinf envelope environmental ethanol fir frets gpar grav gravity grob hirose histogram islay knn larrows levelplot llines " + "logit lpoints lsegments lset ltext lvqinit lvqtest manaus melanoma melanoma motor multiedit neuro nitrofen nodal ns nuclear " + "oneway parallel paulsen poisons polar qq qqmath remission rfs saddle salinity shingle simplex singer somgrid splom stripplot " + "survival tau tmd tsboot tuna unit urine viewport wireframe wool xyplot"; + } + + return nullptr; +} + +///@endcond diff --git a/src/gui/codeeditors/qgscodeeditorr.h b/src/gui/codeeditors/qgscodeeditorr.h new file mode 100644 index 000000000000..3ebf8139a69e --- /dev/null +++ b/src/gui/codeeditors/qgscodeeditorr.h @@ -0,0 +1,86 @@ +/*************************************************************************** + qgscodeeditorjs.h - A R stats editor based on QScintilla + -------------------------------------- + Date : October 2022 + Copyright : (C) 2022 by Nyall Dawson + Email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSCODEEDITORR_H +#define QGSCODEEDITORR_H + +#include "qgscodeeditor.h" +#include "qgis_sip.h" +#include "qgis_gui.h" +#include + +SIP_IF_MODULE( HAVE_QSCI_SIP ) + +#ifndef SIP_RUN + +///@cond PRIVATE +class GUI_EXPORT QgsQsciLexerR : public QsciLexer +{ + Q_OBJECT + public: + + enum Styles + { + Default = 0, + Comment = 1, + Kword = 2, + BaseKword = 3, + OtherKword = 4, + Number = 5, + String = 6, + String2 = 7, + Operator = 8, + Identifier = 9, + Infix = 10, + InfixEOL = 11, + Backticks = 12, + RawString = 13, + RawString2 = 14, + EscapeSequence = 15 + }; + + QgsQsciLexerR( QObject *parent = nullptr ); + const char *language() const override; + const char *lexer() const override; + int lexerId() const override; + QString description( int style ) const override; + const char *keywords( int set ) const override; + + +}; +///@endcond +#endif + +/** + * \ingroup gui + * \brief A R stats code editor based on QScintilla2. Adds syntax highlighting and + * code autocompletion. + * \since QGIS 3.28 + */ +class GUI_EXPORT QgsCodeEditorR : public QgsCodeEditor +{ + Q_OBJECT + + public: + + //! Constructor for QgsCodeEditorR + QgsCodeEditorR( QWidget *parent SIP_TRANSFERTHIS = nullptr ); + + protected: + void initializeLexer() override; + +}; + +#endif // QGSCODEEDITORR_H diff --git a/src/ui/qgscodeditorsettings.ui b/src/ui/qgscodeditorsettings.ui index 45a412678aff..e53638f973b4 100644 --- a/src/ui/qgscodeditorsettings.ui +++ b/src/ui/qgscodeditorsettings.ui @@ -42,7 +42,7 @@ - 4 + 0 @@ -200,6 +200,32 @@ + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 100 + + + + + + @@ -861,6 +887,12 @@
qgscodeeditorjs.h
1 + + QgsCodeEditorR + QWidget +
qgscodeeditorr.h
+ 1 +
scrollArea From d68f6f8eff203211813ff1553b0b44d3c025e691 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 5 Oct 2022 09:18:15 +1000 Subject: [PATCH 18/42] Remove unused code --- src/app/rstats/qgsrstatsconsole.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index 974aa5f1adcf..4738d7725a7c 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -64,14 +64,6 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) mOutput->setHtml( mOutput->toHtml() + QStringLiteral( "

%1

" ).arg( error ) ); } ); - connect( mRunner, &QgsRStatsRunner::commandFinished, this, [ = ]( const QVariant & result ) - { - //if ( !result.isValid() ) - // mOutput->append( "NA" ); - //else - // mOutput->append( result.toString() ); - } ); - connect( mRunner, &QgsRStatsRunner::consoleMessage, this, [ = ]( const QString & message, int type ) { if ( type == 0 ) From 53a2d6bbe058dfa71b929682a33d24d11d183200 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 5 Oct 2022 09:18:34 +1000 Subject: [PATCH 19/42] Formatting --- src/app/rstats/qgsrstatsrunner.cpp | 57 +++++++++++++++--------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index f99c179120d1..fbee8ab63022 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -91,18 +91,18 @@ QgsRStatsSession::QgsRStatsSession() QgsRStatsSession::~QgsRStatsSession() = default; -std::string QgsRStatsSession::sexpToString(const SEXP exp) +std::string QgsRStatsSession::sexpToString( const SEXP exp ) { - Rcpp::StringVector lines = Rcpp::StringVector(Rf_eval(Rf_lang2(Rf_install("capture.output"), exp), R_GlobalEnv)); - std::string outcome = ""; - for (auto it = lines.begin(); it != lines.end(); it++) - { - Rcpp::String line(it->get()); - outcome.append(line); - if (it < lines.end() - 1) - outcome.append("\n"); - } - return outcome; + Rcpp::StringVector lines = Rcpp::StringVector( Rf_eval( Rf_lang2( Rf_install( "capture.output" ), exp ), R_GlobalEnv ) ); + std::string outcome = ""; + for ( auto it = lines.begin(); it != lines.end(); it++ ) + { + Rcpp::String line( it->get() ); + outcome.append( line ); + if ( it < lines.end() - 1 ) + outcome.append( "\n" ); + } + return outcome; } QVariant QgsRStatsSession::execCommand( const QString &command, QString &error ) @@ -111,7 +111,7 @@ QVariant QgsRStatsSession::execCommand( const QString &command, QString &error ) { SEXP res = mRSession->parseEval( command.toStdString() ); - WriteConsole(sexpToString(res), 0); + WriteConsole( sexpToString( res ), 0 ); switch ( TYPEOF( res ) ) { @@ -120,38 +120,38 @@ QVariant QgsRStatsSession::execCommand( const QString &command, QString &error ) case LGLSXP: { - if (LENGTH(res) == 1) + if ( LENGTH( res ) == 1 ) { - const int resInt = Rcpp::as( res ); - if ( resInt < 0 ) - return QVariant(); - else - return static_cast< bool >( resInt ); + const int resInt = Rcpp::as( res ); + if ( resInt < 0 ) + return QVariant(); + else + return static_cast< bool >( resInt ); } else - return QVariant(); + return QVariant(); } case INTSXP: // handle NA_integer_ as NA! - if (LENGTH(res) == 1) - return Rcpp::as( res ); + if ( LENGTH( res ) == 1 ) + return Rcpp::as( res ); else - return QVariant(); + return QVariant(); case REALSXP: // handle nan as NA - if (LENGTH(res) == 1) - return Rcpp::as( res ); + if ( LENGTH( res ) == 1 ) + return Rcpp::as( res ); else - return QVariant(); + return QVariant(); case STRSXP: - if (LENGTH(res) == 1) - return QString::fromStdString( Rcpp::as( res ) ); + if ( LENGTH( res ) == 1 ) + return QString::fromStdString( Rcpp::as( res ) ); else - return QVariant(); + return QVariant(); //case RAWSXP: // return R::rawPointer( res ); @@ -172,7 +172,6 @@ QVariant QgsRStatsSession::execCommand( const QString &command, QString &error ) std::cerr << "Unknown exception caught" << std::endl; } return QVariant(); - } void QgsRStatsSession::execCommand( const QString &command ) From e62d37c3b3b55adaa89c1ab0971d787a5d789c57 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 5 Oct 2022 09:44:29 +1000 Subject: [PATCH 20/42] Use R code editor --- src/app/rstats/qgsrstatsconsole.cpp | 14 +++++++------- src/app/rstats/qgsrstatsconsole.h | 3 ++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index 4738d7725a7c..2d37db8ab854 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -18,6 +18,7 @@ #include "qgsrstatsrunner.h" #include "qgisapp.h" #include "qgsdockablewidgethelper.h" +#include "qgscodeeditorr.h" #include "qgscodeeditor.h" #include @@ -43,8 +44,7 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) vl->setContentsMargins( 0, 0, 0, 0 ); vl->addWidget( toolBar ); - mOutput = new QTextBrowser(); - mOutput->setFont( QgsCodeEditor::getMonospaceFont() ); + mOutput = new QgsCodeEditorR(); vl->addWidget( mOutput, 1 ); mInputEdit = new QLineEdit(); mInputEdit->setFont( QgsCodeEditor::getMonospaceFont() ); @@ -55,26 +55,26 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) return; const QString command = mInputEdit->text(); - mOutput->append( QStringLiteral( "> " ) + command ); + mOutput->append( ( mOutput->text().isEmpty() ? QString() : QString( '\n' ) ) + QStringLiteral( "> " ) + command ); mRunner->execCommand( command ); } ); connect( mRunner, &QgsRStatsRunner::errorOccurred, this, [ = ]( const QString & error ) { - mOutput->setHtml( mOutput->toHtml() + QStringLiteral( "

%1

" ).arg( error ) ); + mOutput->setText( mOutput->text() + QStringLiteral( "

%1

" ).arg( error ) ); } ); connect( mRunner, &QgsRStatsRunner::consoleMessage, this, [ = ]( const QString & message, int type ) { if ( type == 0 ) - mOutput->append( message ); + mOutput->append( ( mOutput->text().isEmpty() ? QString() : QString( '\n' ) ) + message ); else - mOutput->setHtml( mOutput->toHtml() + QStringLiteral( "

%1

" ).arg( message ) ); + mOutput->setText( mOutput->text() + QStringLiteral( "

%1

" ).arg( message ) ); } ); connect( mRunner, &QgsRStatsRunner::showMessage, this, [ = ]( const QString & message ) { - mOutput->append( message ); + mOutput->append( ( mOutput->text().isEmpty() ? QString() : QString( '\n' ) ) + message ); } ); connect( mRunner, &QgsRStatsRunner::busyChanged, this, [ = ]( bool busy ) diff --git a/src/app/rstats/qgsrstatsconsole.h b/src/app/rstats/qgsrstatsconsole.h index 394f49f694b4..e79bc212f9d3 100644 --- a/src/app/rstats/qgsrstatsconsole.h +++ b/src/app/rstats/qgsrstatsconsole.h @@ -23,6 +23,7 @@ class QgsRStatsRunner; class QLineEdit; class QTextBrowser; class QgsDockableWidgetHelper; +class QgsCodeEditorR; class QgsRStatsConsole: public QWidget { @@ -33,7 +34,7 @@ class QgsRStatsConsole: public QWidget QgsRStatsRunner *mRunner = nullptr; QLineEdit *mInputEdit = nullptr; - QTextBrowser *mOutput = nullptr; + QgsCodeEditorR *mOutput = nullptr; QgsDockableWidgetHelper *mDockableWidgetHelper = nullptr; }; From 421f95e9b20ab665f279336388559716d5801344 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 5 Oct 2022 09:44:47 +1000 Subject: [PATCH 21/42] Minor cleanup and add no-return execCommand method --- src/app/rstats/qgsrstatsrunner.cpp | 124 +++++++++++++++++------------ src/app/rstats/qgsrstatsrunner.h | 6 +- 2 files changed, 79 insertions(+), 51 deletions(-) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index fbee8ab63022..c9d43926d51d 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -67,9 +67,7 @@ QgsRStatsSession::QgsRStatsSession() { QDir().mkpath( userPath ); } - QString error; - execCommand( QStringLiteral( ".libPaths(\"%1\")" ).arg( userPath ), error ); - + execCommandNR( QStringLiteral( ".libPaths(\"%1\")" ).arg( userPath ) ); Rcpp::XPtr wr( new QgsApplicationRWrapper() ); wr.attr( "class" ) = "QGIS"; @@ -105,63 +103,68 @@ std::string QgsRStatsSession::sexpToString( const SEXP exp ) return outcome; } -QVariant QgsRStatsSession::execCommand( const QString &command, QString &error ) +QVariant QgsRStatsSession::sexpToVariant( const SEXP exp ) { - try + switch ( TYPEOF( exp ) ) { - SEXP res = mRSession->parseEval( command.toStdString() ); + case NILSXP: + return QVariant(); - WriteConsole( sexpToString( res ), 0 ); - - switch ( TYPEOF( res ) ) + case LGLSXP: { - case NILSXP: - return QVariant(); - - case LGLSXP: + if ( LENGTH( exp ) == 1 ) { - if ( LENGTH( res ) == 1 ) - { - const int resInt = Rcpp::as( res ); - if ( resInt < 0 ) - return QVariant(); - else - return static_cast< bool >( resInt ); - } - else + const int expInt = Rcpp::as( exp ); + if ( expInt < 0 ) return QVariant(); - - } - - case INTSXP: - // handle NA_integer_ as NA! - if ( LENGTH( res ) == 1 ) - return Rcpp::as( res ); else - return QVariant(); + return static_cast< bool >( expInt ); + } + else + return QVariant(); - case REALSXP: - // handle nan as NA - if ( LENGTH( res ) == 1 ) - return Rcpp::as( res ); - else - return QVariant(); + } - case STRSXP: - if ( LENGTH( res ) == 1 ) - return QString::fromStdString( Rcpp::as( res ) ); - else - return QVariant(); + case INTSXP: + // handle NA_integer_ as NA! + if ( LENGTH( exp ) == 1 ) + return Rcpp::as( exp ); + else + return QVariant(); - //case RAWSXP: - // return R::rawPointer( res ); + case REALSXP: + // handle nan as NA + if ( LENGTH( exp ) == 1 ) + return Rcpp::as( exp ); + else + return QVariant(); - default: - QgsDebugMsg( "Unhandledtype!!!" ); + case STRSXP: + if ( LENGTH( exp ) == 1 ) + return QString::fromStdString( Rcpp::as( exp ) ); + else return QVariant(); - } - return QVariant(); + //case RAWSXP: + // return R::rawPointer( exp ); + + default: + QgsDebugMsg( "Unhandledtype!!!" ); + return QVariant(); + } + + return QVariant(); +} + +void QgsRStatsSession::execCommandPrivate( const QString &command, QString &error, QVariant *res, std::string *output ) +{ + try + { + const SEXP sexpRes = mRSession->parseEval( command.toStdString() ); + if ( res ) + *res = sexpToVariant( sexpRes ); + if ( output ) + *output = sexpToString( sexpRes ); } catch ( std::exception &ex ) { @@ -171,7 +174,25 @@ QVariant QgsRStatsSession::execCommand( const QString &command, QString &error ) { std::cerr << "Unknown exception caught" << std::endl; } - return QVariant(); + if ( res ) + *res = QVariant(); +} + +void QgsRStatsSession::execCommandNR( const QString &command ) +{ + if ( mBusy ) + return; + + mBusy = true; + emit busyChanged( true ); + QString error; + execCommandPrivate( command, error ); + + if ( ! error.isEmpty() ) + emit errorOccurred( error ); + + mBusy = false; + emit busyChanged( false ); } void QgsRStatsSession::execCommand( const QString &command ) @@ -182,7 +203,12 @@ void QgsRStatsSession::execCommand( const QString &command ) mBusy = true; emit busyChanged( true ); QString error; - const QVariant res = execCommand( command, error ); + QVariant res; + std::string output; + execCommandPrivate( command, error, &res, &output ); + + emit consoleMessage( QString::fromStdString( output ), 0 ); + if ( ! error.isEmpty() ) emit errorOccurred( error ); else diff --git a/src/app/rstats/qgsrstatsrunner.h b/src/app/rstats/qgsrstatsrunner.h index cbd57450c417..0dd6b39c7278 100644 --- a/src/app/rstats/qgsrstatsrunner.h +++ b/src/app/rstats/qgsrstatsrunner.h @@ -34,7 +34,7 @@ class QgsRStatsSession: public QObject, public Callbacks QgsRStatsSession(); ~QgsRStatsSession() override; - QVariant execCommand( const QString &command, QString &error ); + void execCommandNR( const QString &command ); void WriteConsole( const std::string &line, int type ) override { @@ -72,11 +72,13 @@ class QgsRStatsSession: public QObject, public Callbacks void commandFinished( const QVariant &result ); private: + void execCommandPrivate( const QString &command, QString &error, QVariant *res = nullptr, std::string *output = nullptr ); std::unique_ptr< RInside > mRSession; bool mBusy = false; - std::string sexpToString(const SEXP exp); + static std::string sexpToString( const SEXP exp ); + static QVariant sexpToVariant( const SEXP exp ); }; From 22ea2945ee070998aa0f1fbaa36dc9c2959cca1b Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 5 Oct 2022 11:33:36 +1000 Subject: [PATCH 22/42] Cleanup error handling and some crash fixes --- src/app/rstats/qgsrstatsconsole.cpp | 6 +-- src/app/rstats/qgsrstatsrunner.cpp | 76 ++++++++++++++++++++++++++--- src/app/rstats/qgsrstatsrunner.h | 29 ++++------- 3 files changed, 80 insertions(+), 31 deletions(-) diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index 2d37db8ab854..3b463ef13be6 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -61,15 +61,15 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) connect( mRunner, &QgsRStatsRunner::errorOccurred, this, [ = ]( const QString & error ) { - mOutput->setText( mOutput->text() + QStringLiteral( "

%1

" ).arg( error ) ); + mOutput->append( ( mOutput->text().isEmpty() ? QString() : QString( '\n' ) ) + error ); } ); connect( mRunner, &QgsRStatsRunner::consoleMessage, this, [ = ]( const QString & message, int type ) { if ( type == 0 ) mOutput->append( ( mOutput->text().isEmpty() ? QString() : QString( '\n' ) ) + message ); - else - mOutput->setText( mOutput->text() + QStringLiteral( "

%1

" ).arg( message ) ); + else // TODO should we format errors differently? + mOutput->append( ( mOutput->text().isEmpty() ? QString() : QString( '\n' ) ) + message ); } ); connect( mRunner, &QgsRStatsRunner::showMessage, this, [ = ]( const QString & message ) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index c9d43926d51d..8f599da83e10 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -91,6 +91,26 @@ QgsRStatsSession::~QgsRStatsSession() = default; std::string QgsRStatsSession::sexpToString( const SEXP exp ) { + switch ( TYPEOF( exp ) ) + { + case EXPRSXP: + case CLOSXP: + case ENVSXP: + case LANGSXP: + // these types can't be converted to StringVector, will raise exceptions + return {}; + + case LGLSXP: + case INTSXP: + case REALSXP: + case STRSXP: + break; // we know these types are fine to convert to StringVector + + default: + QgsDebugMsg( QStringLiteral( "Possibly unsafe type: %1" ).arg( TYPEOF( exp ) ) ); + break; + } + Rcpp::StringVector lines = Rcpp::StringVector( Rf_eval( Rf_lang2( Rf_install( "capture.output" ), exp ), R_GlobalEnv ) ); std::string outcome = ""; for ( auto it = lines.begin(); it != lines.end(); it++ ) @@ -148,15 +168,22 @@ QVariant QgsRStatsSession::sexpToVariant( const SEXP exp ) //case RAWSXP: // return R::rawPointer( exp ); + case EXPRSXP: + case CLOSXP: + case ENVSXP: + case LANGSXP: + // these types can't possibly be converted to variants + return QVariant(); + default: - QgsDebugMsg( "Unhandledtype!!!" ); + QgsDebugMsg( QStringLiteral( "Unhandled type: %1" ).arg( TYPEOF( exp ) ) ); return QVariant(); } return QVariant(); } -void QgsRStatsSession::execCommandPrivate( const QString &command, QString &error, QVariant *res, std::string *output ) +void QgsRStatsSession::execCommandPrivate( const QString &command, QString &error, QVariant *res, QString *output ) { try { @@ -164,7 +191,7 @@ void QgsRStatsSession::execCommandPrivate( const QString &command, QString &erro if ( res ) *res = sexpToVariant( sexpRes ); if ( output ) - *output = sexpToString( sexpRes ); + *output = QString::fromStdString( sexpToString( sexpRes ) ); } catch ( std::exception &ex ) { @@ -185,16 +212,43 @@ void QgsRStatsSession::execCommandNR( const QString &command ) mBusy = true; emit busyChanged( true ); + + mEncounteredErrorMessageType = false; QString error; execCommandPrivate( command, error ); - if ( ! error.isEmpty() ) + if ( ! error.isEmpty() && !mEncounteredErrorMessageType ) emit errorOccurred( error ); mBusy = false; emit busyChanged( false ); } +void QgsRStatsSession::WriteConsole( const std::string &line, int type ) +{ + if ( type > 0 ) + mEncounteredErrorMessageType = true; + + const QString message = QString::fromStdString( line ); + emit consoleMessage( message, type ); +} + +bool QgsRStatsSession::has_WriteConsole() +{ + return true; +} + +void QgsRStatsSession::ShowMessage( const char *message ) +{ + const QString messageString( message ); + emit showMessage( messageString ); +} + +bool QgsRStatsSession::has_ShowMessage() +{ + return true; +} + void QgsRStatsSession::execCommand( const QString &command ) { if ( mBusy ) @@ -204,15 +258,21 @@ void QgsRStatsSession::execCommand( const QString &command ) emit busyChanged( true ); QString error; QVariant res; - std::string output; + QString output; + mEncounteredErrorMessageType = false; execCommandPrivate( command, error, &res, &output ); - emit consoleMessage( QString::fromStdString( output ), 0 ); - if ( ! error.isEmpty() ) - emit errorOccurred( error ); + { + if ( !mEncounteredErrorMessageType ) + emit errorOccurred( error ); + } else + { + if ( !output.isEmpty() ) + emit consoleMessage( output, 0 ); emit commandFinished( res ); + } mBusy = false; emit busyChanged( false ); diff --git a/src/app/rstats/qgsrstatsrunner.h b/src/app/rstats/qgsrstatsrunner.h index 0dd6b39c7278..9186045fafa5 100644 --- a/src/app/rstats/qgsrstatsrunner.h +++ b/src/app/rstats/qgsrstatsrunner.h @@ -36,25 +36,13 @@ class QgsRStatsSession: public QObject, public Callbacks void execCommandNR( const QString &command ); - void WriteConsole( const std::string &line, int type ) override - { - emit consoleMessage( QString::fromStdString( line ), type ); - }; - - bool has_WriteConsole() override - { - return true; - }; - - void ShowMessage( const char *message ) override - { - emit showMessage( QString( message ) ); - } - - bool has_ShowMessage() override - { - return true; - } + void WriteConsole( const std::string &line, int type ) override;; + + bool has_WriteConsole() override;; + + void ShowMessage( const char *message ) override; + + bool has_ShowMessage() override; bool busy() const { return mBusy; } @@ -72,10 +60,11 @@ class QgsRStatsSession: public QObject, public Callbacks void commandFinished( const QVariant &result ); private: - void execCommandPrivate( const QString &command, QString &error, QVariant *res = nullptr, std::string *output = nullptr ); + void execCommandPrivate( const QString &command, QString &error, QVariant *res = nullptr, QString *output = nullptr ); std::unique_ptr< RInside > mRSession; bool mBusy = false; + bool mEncounteredErrorMessageType = false; static std::string sexpToString( const SEXP exp ); static QVariant sexpToVariant( const SEXP exp ); From e07b4337db344eefb83dc3752614412fb44a970e Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 5 Oct 2022 11:33:58 +1000 Subject: [PATCH 23/42] Code shuffle --- src/app/rstats/qgsrstatsrunner.cpp | 51 +++++++++++++++--------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index 8f599da83e10..25cefc3f59f4 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -224,31 +224,6 @@ void QgsRStatsSession::execCommandNR( const QString &command ) emit busyChanged( false ); } -void QgsRStatsSession::WriteConsole( const std::string &line, int type ) -{ - if ( type > 0 ) - mEncounteredErrorMessageType = true; - - const QString message = QString::fromStdString( line ); - emit consoleMessage( message, type ); -} - -bool QgsRStatsSession::has_WriteConsole() -{ - return true; -} - -void QgsRStatsSession::ShowMessage( const char *message ) -{ - const QString messageString( message ); - emit showMessage( messageString ); -} - -bool QgsRStatsSession::has_ShowMessage() -{ - return true; -} - void QgsRStatsSession::execCommand( const QString &command ) { if ( mBusy ) @@ -278,6 +253,32 @@ void QgsRStatsSession::execCommand( const QString &command ) emit busyChanged( false ); } +void QgsRStatsSession::WriteConsole( const std::string &line, int type ) +{ + if ( type > 0 ) + mEncounteredErrorMessageType = true; + + const QString message = QString::fromStdString( line ); + emit consoleMessage( message, type ); +} + +bool QgsRStatsSession::has_WriteConsole() +{ + return true; +} + +void QgsRStatsSession::ShowMessage( const char *message ) +{ + const QString messageString( message ); + emit showMessage( messageString ); +} + +bool QgsRStatsSession::has_ShowMessage() +{ + return true; +} + + // // QgsRStatsRunner From d9da2fb1d571585370685ca165a2da813e8037bf Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 5 Oct 2022 12:10:09 +1000 Subject: [PATCH 24/42] Show startup message --- src/app/rstats/qgsrstatsconsole.cpp | 2 ++ src/app/rstats/qgsrstatsrunner.cpp | 48 +++++++++++++++++++++++++++-- src/app/rstats/qgsrstatsrunner.h | 3 ++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index 3b463ef13be6..1dd90642de46 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -83,6 +83,8 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) } ); setLayout( vl ); + + mRunner->showStartupMessage(); } QgsRStatsConsole::~QgsRStatsConsole() diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index 25cefc3f59f4..68a66fdfbcc3 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -84,7 +84,43 @@ QgsRStatsSession::QgsRStatsSession() // R.parseEvalQ( "cat(txt)" ); // QgsDebugMsg( QString::fromStdString( Rcpp::as( R.parseEval( "cat(txt)" ) ) ) ); // QgsDebugMsg( QString::fromStdString( Rcpp::as( mRSession->parseEval( "as.character(val+2)" ) ) ) ); -// QgsDebugMsg( QStringLiteral( "val as double: %1" ).arg( Rcpp::as( mRSession->parseEval( "val+val2" ) ) ) ); + // QgsDebugMsg( QStringLiteral( "val as double: %1" ).arg( Rcpp::as( mRSession->parseEval( "val+val2" ) ) ) ); +} + +void QgsRStatsSession::showStartupMessage() +{ + QVariant versionString; + QString error; + execCommandPrivate( QStringLiteral( "R.version$version.string" ), error, &versionString ); + QVariant nicknameString; + execCommandPrivate( QStringLiteral( "R.version$nickname" ), error, &nicknameString ); + QVariant platformString; + execCommandPrivate( QStringLiteral( "R.version$platform" ), error, &platformString ); + QVariant yearString; + execCommandPrivate( QStringLiteral( "R.version$year" ), error, &yearString ); + QVariant sizeInt; + execCommandPrivate( QStringLiteral( ".Machine$sizeof.pointer" ), error, &sizeInt ); + + emit showMessage( QStringLiteral( "%1 -- %2" ).arg( versionString.toString(), nicknameString.toString() ) ); + emit showMessage( QStringLiteral( "Copyright (C) %1 The R Foundation for Statistical Computing" ).arg( yearString.toString() ) ); + const int bits = sizeInt.toInt() == 8 ? 64 : 32; + emit showMessage( QStringLiteral( "Platform: %1 (%2-bit)" ).arg( platformString.toString() ).arg( bits ) ); + emit showMessage( QString() ); + + emit showMessage( QStringLiteral( "R is free software and comes with ABSOLUTELY NO WARRANTY." ) ); + emit showMessage( QStringLiteral( "You are welcome to redistribute it under certain conditions." ) ); + emit showMessage( QStringLiteral( "Type 'license()' or 'licence()' for distribution details." ) ); + emit showMessage( QString() ); + + emit showMessage( QStringLiteral( "R is a collaborative project with many contributors." ) ); + emit showMessage( QStringLiteral( "Type 'contributors()' for more information and" ) ); + emit showMessage( QStringLiteral( "'citation()' on how to cite R or R packages in publications." ) ); + emit showMessage( QString() ); + + // TODO -- these don't actually work! + // emit showMessage( QStringLiteral( "Type 'demo()' for some demos, 'help()' for on-line help, or" ) ); + // emit showMessage( QStringLiteral( "'help.start()' for an HTML browser interface to help." ) ); + emit showMessage( QString() ); } QgsRStatsSession::~QgsRStatsSession() = default; @@ -106,6 +142,9 @@ std::string QgsRStatsSession::sexpToString( const SEXP exp ) case STRSXP: break; // we know these types are fine to convert to StringVector + case NILSXP: + return "NULL"; + default: QgsDebugMsg( QStringLiteral( "Possibly unsafe type: %1" ).arg( TYPEOF( exp ) ) ); break; @@ -201,8 +240,6 @@ void QgsRStatsSession::execCommandPrivate( const QString &command, QString &erro { std::cerr << "Unknown exception caught" << std::endl; } - if ( res ) - *res = QVariant(); } void QgsRStatsSession::execCommandNR( const QString &command ) @@ -315,3 +352,8 @@ bool QgsRStatsRunner::busy() const { return mSession->busy(); } + +void QgsRStatsRunner::showStartupMessage() +{ + QMetaObject::invokeMethod( mSession.get(), "showStartupMessage", Qt::QueuedConnection ); +} diff --git a/src/app/rstats/qgsrstatsrunner.h b/src/app/rstats/qgsrstatsrunner.h index 9186045fafa5..10f1ee3693c2 100644 --- a/src/app/rstats/qgsrstatsrunner.h +++ b/src/app/rstats/qgsrstatsrunner.h @@ -50,6 +50,8 @@ class QgsRStatsSession: public QObject, public Callbacks void execCommand( const QString &command ); + void showStartupMessage(); + signals: void busyChanged( bool busy ); @@ -81,6 +83,7 @@ class QgsRStatsRunner: public QObject void execCommand( const QString &command ); bool busy() const; + void showStartupMessage(); signals: From 3f84fd7c155d8e3fd3adb6a1b145366b550365ca Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 5 Oct 2022 14:44:17 +1000 Subject: [PATCH 25/42] Nicer ui --- src/app/rstats/qgsrstatsconsole.cpp | 88 +++++++++++++++++++++++++++-- src/app/rstats/qgsrstatsconsole.h | 37 +++++++++++- 2 files changed, 116 insertions(+), 9 deletions(-) diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index 1dd90642de46..405ce6bd5971 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -26,6 +26,7 @@ #include #include #include +#include QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) : QWidget( parent ) @@ -44,17 +45,24 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) vl->setContentsMargins( 0, 0, 0, 0 ); vl->addWidget( toolBar ); - mOutput = new QgsCodeEditorR(); - vl->addWidget( mOutput, 1 ); - mInputEdit = new QLineEdit(); + QSplitter *splitter = new QSplitter(); + splitter->setOrientation( Qt::Vertical ); + splitter->setHandleWidth( 3 ); + splitter->setChildrenCollapsible( false ); + + mOutput = new QgsROutputWidget(); + splitter->addWidget( mOutput ); + mInputEdit = new QgsInteractiveRWidget(); mInputEdit->setFont( QgsCodeEditor::getMonospaceFont() ); - vl->addWidget( mInputEdit ); - connect( mInputEdit, &QLineEdit::returnPressed, this, [ = ] + splitter->addWidget( mInputEdit ); + + vl->addWidget( splitter ); + + connect( mInputEdit, &QgsInteractiveRWidget::runCommand, this, [ = ]( const QString & command ) { if ( mRunner->busy() ) return; - const QString command = mInputEdit->text(); mOutput->append( ( mOutput->text().isEmpty() ? QString() : QString( '\n' ) ) + QStringLiteral( "> " ) + command ); mRunner->execCommand( command ); } ); @@ -91,3 +99,71 @@ QgsRStatsConsole::~QgsRStatsConsole() { delete mDockableWidgetHelper; } + +QgsInteractiveRWidget::QgsInteractiveRWidget( QWidget *parent ) + : QgsCodeEditorR( parent ) +{ + displayPrompt( false ); + + // Don't want to see the horizontal scrollbar at all + SendScintilla( QsciScintilla::SCI_SETHSCROLLBAR, 0 ); + + setWrapMode( QsciScintilla::WrapCharacter ); + SendScintilla( QsciScintilla::SCI_EMPTYUNDOBUFFER ); + + QgsInteractiveRWidget::initializeLexer(); +} + +void QgsInteractiveRWidget::keyPressEvent( QKeyEvent *event ) +{ + switch ( event->key() ) + { + case Qt::Key_Return: + case Qt::Key_Enter: + emit runCommand( text() ); + clear(); + displayPrompt( false ); + break; + + default: + QgsCodeEditorR::keyPressEvent( event ); + } +} + +void QgsInteractiveRWidget::initializeLexer() +{ + QgsCodeEditorR::initializeLexer(); + setCaretLineVisible( false ); + setLineNumbersVisible( false ); // NO linenumbers for the input line + setFoldingVisible( false ); + // Margin 1 is used for the '>' prompt (console input) + setMarginLineNumbers( 1, true ); + setMarginWidth( 1, "00" ); + setMarginType( 1, QsciScintilla::MarginType::TextMarginRightJustified ); + setMarginsBackgroundColor( color( QgsCodeEditorColorScheme::ColorRole::Background ) ); + setEdgeMode( QsciScintilla::EdgeNone ); +} + +void QgsInteractiveRWidget::displayPrompt( bool more ) +{ + const QString prompt = !more ? ">" : "+"; + SendScintilla( QsciScintilla::SCI_MARGINSETTEXT, static_cast< uintptr_t >( 0 ), prompt.toUtf8().constData() ); +} + +QgsROutputWidget::QgsROutputWidget( QWidget *parent ) + : QgsCodeEditorR( parent ) +{ + + // Don't want to see the horizontal scrollbar at all + SendScintilla( QsciScintilla::SCI_SETHSCROLLBAR, 0 ); + + setWrapMode( QsciScintilla::WrapCharacter ); + + QgsROutputWidget::initializeLexer(); +} + +void QgsROutputWidget::initializeLexer() +{ + QgsCodeEditorR::initializeLexer(); + setFoldingVisible( false ); +} diff --git a/src/app/rstats/qgsrstatsconsole.h b/src/app/rstats/qgsrstatsconsole.h index e79bc212f9d3..c56c8448bcf1 100644 --- a/src/app/rstats/qgsrstatsconsole.h +++ b/src/app/rstats/qgsrstatsconsole.h @@ -19,22 +19,53 @@ #include +#include "qgscodeeditorr.h" + class QgsRStatsRunner; class QLineEdit; class QTextBrowser; class QgsDockableWidgetHelper; -class QgsCodeEditorR; + +class QgsInteractiveRWidget : public QgsCodeEditorR +{ + Q_OBJECT + public: + + QgsInteractiveRWidget( QWidget *parent = nullptr ); + + signals: + + void runCommand( const QString &command ); + + protected: + + void keyPressEvent( QKeyEvent *event ) override; + + void initializeLexer() override; + void displayPrompt( bool more = false ); + +}; + +class QgsROutputWidget : public QgsCodeEditorR +{ + Q_OBJECT + public: + QgsROutputWidget( QWidget *parent = nullptr ); + protected: + void initializeLexer() override; +}; class QgsRStatsConsole: public QWidget { public: QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ); ~QgsRStatsConsole() override; + private: QgsRStatsRunner *mRunner = nullptr; - QLineEdit *mInputEdit = nullptr; - QgsCodeEditorR *mOutput = nullptr; + QgsInteractiveRWidget *mInputEdit = nullptr; + QgsROutputWidget *mOutput = nullptr; QgsDockableWidgetHelper *mDockableWidgetHelper = nullptr; }; From 421b9e0b1bdb3a522c93a777ee94596a13b34932 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 5 Oct 2022 17:02:21 +1000 Subject: [PATCH 26/42] Move some common code to base class --- python/gui/auto_additions/qgscodeeditor.py | 7 +++++ .../codeeditors/qgscodeeditor.sip.in | 23 +++++++++++--- .../codeeditors/qgscodeeditorr.sip.in | 2 +- src/app/rstats/qgsrstatsconsole.cpp | 30 ++----------------- src/app/rstats/qgsrstatsconsole.h | 11 +------ src/gui/codeeditors/qgscodeeditor.cpp | 30 +++++++++++++++++-- src/gui/codeeditors/qgscodeeditor.h | 30 ++++++++++++++++--- src/gui/codeeditors/qgscodeeditorr.cpp | 5 ++-- src/gui/codeeditors/qgscodeeditorr.h | 2 +- 9 files changed, 88 insertions(+), 52 deletions(-) diff --git a/python/gui/auto_additions/qgscodeeditor.py b/python/gui/auto_additions/qgscodeeditor.py index b46cef77e2ea..3a73c1be1def 100644 --- a/python/gui/auto_additions/qgscodeeditor.py +++ b/python/gui/auto_additions/qgscodeeditor.py @@ -1,5 +1,12 @@ # The following has been generated automatically from src/gui/codeeditors/qgscodeeditor.h # monkey patching scoped based enum +QgsCodeEditor.Mode.ScriptEditor.__doc__ = "Standard mode, allows for display and edit of entire scripts" +QgsCodeEditor.Mode.OutputDisplay.__doc__ = "Read only mode for display of command outputs" +QgsCodeEditor.Mode.CommandInput.__doc__ = "Command input mode" +QgsCodeEditor.Mode.__doc__ = 'Code editor modes.\n\n.. versionadded:: 3.30\n\n' + '* ``ScriptEditor``: ' + QgsCodeEditor.Mode.ScriptEditor.__doc__ + '\n' + '* ``OutputDisplay``: ' + QgsCodeEditor.Mode.OutputDisplay.__doc__ + '\n' + '* ``CommandInput``: ' + QgsCodeEditor.Mode.CommandInput.__doc__ +# -- +QgsCodeEditor.Mode.baseClass = QgsCodeEditor +# monkey patching scoped based enum QgsCodeEditor.LineNumbers = QgsCodeEditor.MarginRole.LineNumbers QgsCodeEditor.LineNumbers.is_monkey_patched = True QgsCodeEditor.LineNumbers.__doc__ = "Line numbers" diff --git a/python/gui/auto_generated/codeeditors/qgscodeeditor.sip.in b/python/gui/auto_generated/codeeditors/qgscodeeditor.sip.in index 14bcd26dea42..088a5d0a5910 100644 --- a/python/gui/auto_generated/codeeditors/qgscodeeditor.sip.in +++ b/python/gui/auto_generated/codeeditors/qgscodeeditor.sip.in @@ -30,6 +30,13 @@ A text editor based on QScintilla2. %End public: + enum class Mode + { + ScriptEditor, + OutputDisplay, + CommandInput, + }; + enum class MarginRole { LineNumbers, @@ -45,7 +52,7 @@ A text editor based on QScintilla2. typedef QFlags Flags; - QgsCodeEditor( QWidget * parent /TransferThis/ = 0, const QString & title = QString(), bool folding = false, bool margin = false, QgsCodeEditor::Flags flags = QgsCodeEditor::Flags() ); + QgsCodeEditor( QWidget * parent /TransferThis/ = 0, const QString & title = QString(), bool folding = false, bool margin = false, QgsCodeEditor::Flags flags = QgsCodeEditor::Flags(), QgsCodeEditor::Mode mode = QgsCodeEditor::Mode::ScriptEditor ); %Docstring Construct a new code editor. @@ -54,6 +61,7 @@ Construct a new code editor. :param folding: ``False``: Enable folding for code editor (deprecated, use ``flags`` instead) :param margin: ``False``: Enable margin for code editor (deprecated) :param flags: flags controlling behavior of code editor (since QGIS 3.28) +:param mode: code editor mode (since QGIS 3.30) .. versionadded:: 2.6 %End @@ -188,6 +196,13 @@ Clears all warning messages from the editor. .. seealso:: :py:func:`addWarning` .. versionadded:: 3.16 +%End + + QgsCodeEditor::Mode mode() const; +%Docstring +Returns the code editor mode. + +.. versionadded:: 3.30 %End bool isCursorOnLastLine() const; @@ -217,11 +232,11 @@ it is visible. protected: - bool isFixedPitch( const QFont & font ); + bool isFixedPitch( const QFont &font ); - virtual void focusOutEvent( QFocusEvent * event ); + virtual void focusOutEvent( QFocusEvent *event ); - virtual void keyPressEvent( QKeyEvent * event ); + virtual void keyPressEvent( QKeyEvent *event ); virtual void initializeLexer(); diff --git a/python/gui/auto_generated/codeeditors/qgscodeeditorr.sip.in b/python/gui/auto_generated/codeeditors/qgscodeeditorr.sip.in index e5e3f0750076..8d432fc39721 100644 --- a/python/gui/auto_generated/codeeditors/qgscodeeditorr.sip.in +++ b/python/gui/auto_generated/codeeditors/qgscodeeditorr.sip.in @@ -24,7 +24,7 @@ code autocompletion. %End public: - QgsCodeEditorR( QWidget *parent /TransferThis/ = 0 ); + QgsCodeEditorR( QWidget *parent /TransferThis/ = 0, QgsCodeEditor::Mode mode = QgsCodeEditor::Mode::ScriptEditor ); %Docstring Constructor for QgsCodeEditorR %End diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index 405ce6bd5971..12a7d7d2a789 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -50,7 +50,7 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) splitter->setHandleWidth( 3 ); splitter->setChildrenCollapsible( false ); - mOutput = new QgsROutputWidget(); + mOutput = new QgsCodeEditorR( nullptr, QgsCodeEditor::Mode::OutputDisplay ); splitter->addWidget( mOutput ); mInputEdit = new QgsInteractiveRWidget(); mInputEdit->setFont( QgsCodeEditor::getMonospaceFont() ); @@ -101,16 +101,10 @@ QgsRStatsConsole::~QgsRStatsConsole() } QgsInteractiveRWidget::QgsInteractiveRWidget( QWidget *parent ) - : QgsCodeEditorR( parent ) + : QgsCodeEditorR( parent, QgsCodeEditor::Mode::CommandInput ) { displayPrompt( false ); - // Don't want to see the horizontal scrollbar at all - SendScintilla( QsciScintilla::SCI_SETHSCROLLBAR, 0 ); - - setWrapMode( QsciScintilla::WrapCharacter ); - SendScintilla( QsciScintilla::SCI_EMPTYUNDOBUFFER ); - QgsInteractiveRWidget::initializeLexer(); } @@ -133,9 +127,9 @@ void QgsInteractiveRWidget::keyPressEvent( QKeyEvent *event ) void QgsInteractiveRWidget::initializeLexer() { QgsCodeEditorR::initializeLexer(); + setCaretLineVisible( false ); setLineNumbersVisible( false ); // NO linenumbers for the input line - setFoldingVisible( false ); // Margin 1 is used for the '>' prompt (console input) setMarginLineNumbers( 1, true ); setMarginWidth( 1, "00" ); @@ -149,21 +143,3 @@ void QgsInteractiveRWidget::displayPrompt( bool more ) const QString prompt = !more ? ">" : "+"; SendScintilla( QsciScintilla::SCI_MARGINSETTEXT, static_cast< uintptr_t >( 0 ), prompt.toUtf8().constData() ); } - -QgsROutputWidget::QgsROutputWidget( QWidget *parent ) - : QgsCodeEditorR( parent ) -{ - - // Don't want to see the horizontal scrollbar at all - SendScintilla( QsciScintilla::SCI_SETHSCROLLBAR, 0 ); - - setWrapMode( QsciScintilla::WrapCharacter ); - - QgsROutputWidget::initializeLexer(); -} - -void QgsROutputWidget::initializeLexer() -{ - QgsCodeEditorR::initializeLexer(); - setFoldingVisible( false ); -} diff --git a/src/app/rstats/qgsrstatsconsole.h b/src/app/rstats/qgsrstatsconsole.h index c56c8448bcf1..b96fea3aeb6b 100644 --- a/src/app/rstats/qgsrstatsconsole.h +++ b/src/app/rstats/qgsrstatsconsole.h @@ -46,15 +46,6 @@ class QgsInteractiveRWidget : public QgsCodeEditorR }; -class QgsROutputWidget : public QgsCodeEditorR -{ - Q_OBJECT - public: - QgsROutputWidget( QWidget *parent = nullptr ); - protected: - void initializeLexer() override; -}; - class QgsRStatsConsole: public QWidget { public: @@ -65,7 +56,7 @@ class QgsRStatsConsole: public QWidget QgsRStatsRunner *mRunner = nullptr; QgsInteractiveRWidget *mInputEdit = nullptr; - QgsROutputWidget *mOutput = nullptr; + QgsCodeEditorR *mOutput = nullptr; QgsDockableWidgetHelper *mDockableWidgetHelper = nullptr; }; diff --git a/src/gui/codeeditors/qgscodeeditor.cpp b/src/gui/codeeditors/qgscodeeditor.cpp index 8f032f9f8ef4..5226c1395915 100644 --- a/src/gui/codeeditors/qgscodeeditor.cpp +++ b/src/gui/codeeditors/qgscodeeditor.cpp @@ -69,11 +69,12 @@ QMap< QgsCodeEditorColorScheme::ColorRole, QString > QgsCodeEditor::sColorRoleTo }; -QgsCodeEditor::QgsCodeEditor( QWidget *parent, const QString &title, bool folding, bool margin, QgsCodeEditor::Flags flags ) +QgsCodeEditor::QgsCodeEditor( QWidget *parent, const QString &title, bool folding, bool margin, QgsCodeEditor::Flags flags, QgsCodeEditor::Mode mode ) : QsciScintilla( parent ) , mWidgetTitle( title ) , mMargin( margin ) , mFlags( flags ) + , mMode( mode ) { if ( !parent && mWidgetTitle.isEmpty() ) { @@ -104,6 +105,31 @@ QgsCodeEditor::QgsCodeEditor( QWidget *parent, const QString &title, bool foldin setSciWidget(); initializeLexer(); } ); + + switch ( mMode ) + { + case QgsCodeEditor::Mode::ScriptEditor: + break; + + case QgsCodeEditor::Mode::OutputDisplay: + { + // Don't want to see the horizontal scrollbar at all + SendScintilla( QsciScintilla::SCI_SETHSCROLLBAR, 0 ); + + setWrapMode( QsciScintilla::WrapCharacter ); + break; + } + + case QgsCodeEditor::Mode::CommandInput: + { + // Don't want to see the horizontal scrollbar at all + SendScintilla( QsciScintilla::SCI_SETHSCROLLBAR, 0 ); + + setWrapMode( QsciScintilla::WrapCharacter ); + SendScintilla( QsciScintilla::SCI_EMPTYUNDOBUFFER ); + break; + } + } } // Workaround a bug in QScintilla 2.8.X @@ -335,7 +361,7 @@ bool QgsCodeEditor::foldingVisible() void QgsCodeEditor::updateFolding() { - if ( mFlags & QgsCodeEditor::Flag::CodeFolding ) + if ( ( mFlags & QgsCodeEditor::Flag::CodeFolding ) && mMode == QgsCodeEditor::Mode::ScriptEditor ) { setMarginWidth( static_cast< int >( QgsCodeEditor::MarginRole::FoldingControls ), "0" ); setMarginsForegroundColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::MarginForeground ) ); diff --git a/src/gui/codeeditors/qgscodeeditor.h b/src/gui/codeeditors/qgscodeeditor.h index df4c300b11d8..f84f447e8d0c 100644 --- a/src/gui/codeeditors/qgscodeeditor.h +++ b/src/gui/codeeditors/qgscodeeditor.h @@ -44,6 +44,19 @@ class GUI_EXPORT QgsCodeEditor : public QsciScintilla public: + /** + * Code editor modes. + * + * \since QGIS 3.30 + */ + enum class Mode + { + ScriptEditor, //!< Standard mode, allows for display and edit of entire scripts + OutputDisplay, //!< Read only mode for display of command outputs + CommandInput, //!< Command input mode + }; + Q_ENUM( Mode ) + /** * Margin roles. * @@ -86,9 +99,10 @@ class GUI_EXPORT QgsCodeEditor : public QsciScintilla * \param folding FALSE: Enable folding for code editor (deprecated, use \a flags instead) * \param margin FALSE: Enable margin for code editor (deprecated) * \param flags flags controlling behavior of code editor (since QGIS 3.28) + * \param mode code editor mode (since QGIS 3.30) * \since QGIS 2.6 */ - QgsCodeEditor( QWidget * parent SIP_TRANSFERTHIS = nullptr, const QString & title = QString(), bool folding = false, bool margin = false, QgsCodeEditor::Flags flags = QgsCodeEditor::Flags() ); + QgsCodeEditor( QWidget * parent SIP_TRANSFERTHIS = nullptr, const QString & title = QString(), bool folding = false, bool margin = false, QgsCodeEditor::Flags flags = QgsCodeEditor::Flags(), QgsCodeEditor::Mode mode = QgsCodeEditor::Mode::ScriptEditor ); /** * Set the widget title @@ -215,6 +229,13 @@ class GUI_EXPORT QgsCodeEditor : public QsciScintilla */ void clearWarnings(); + /** + * Returns the code editor mode. + * + * \since QGIS 3.30 + */ + QgsCodeEditor::Mode mode() const { return mMode; } + /** * Returns TRUE if the cursor is on the last line of the document. * @@ -242,10 +263,10 @@ class GUI_EXPORT QgsCodeEditor : public QsciScintilla protected: - bool isFixedPitch( const QFont & font ); + bool isFixedPitch( const QFont &font ); - void focusOutEvent( QFocusEvent * event ) override; - void keyPressEvent( QKeyEvent * event ) override; + void focusOutEvent( QFocusEvent *event ) override; + void keyPressEvent( QKeyEvent *event ) override; /** * Called when the dialect specific code lexer needs to be initialized (or reinitialized). @@ -285,6 +306,7 @@ class GUI_EXPORT QgsCodeEditor : public QsciScintilla QString mWidgetTitle; bool mMargin = false; QgsCodeEditor::Flags mFlags; + QgsCodeEditor::Mode mMode = QgsCodeEditor::Mode::ScriptEditor; bool mUseDefaultSettings = true; // used if above is false, inplace of values taken from QSettings: diff --git a/src/gui/codeeditors/qgscodeeditorr.cpp b/src/gui/codeeditors/qgscodeeditorr.cpp index 71270dff4c8d..18bf66a69f23 100644 --- a/src/gui/codeeditors/qgscodeeditorr.cpp +++ b/src/gui/codeeditors/qgscodeeditorr.cpp @@ -22,14 +22,13 @@ #include -QgsCodeEditorR::QgsCodeEditorR( QWidget *parent ) - : QgsCodeEditor( parent ) +QgsCodeEditorR::QgsCodeEditorR( QWidget *parent, Mode mode ) + : QgsCodeEditor( parent, QString(), false, false, QgsCodeEditor::Flag::CodeFolding, mode ) { if ( !parent ) { setTitle( tr( "R Editor" ) ); } - setFoldingVisible( true ); QgsCodeEditorR::initializeLexer(); } diff --git a/src/gui/codeeditors/qgscodeeditorr.h b/src/gui/codeeditors/qgscodeeditorr.h index 3ebf8139a69e..649e368bd306 100644 --- a/src/gui/codeeditors/qgscodeeditorr.h +++ b/src/gui/codeeditors/qgscodeeditorr.h @@ -76,7 +76,7 @@ class GUI_EXPORT QgsCodeEditorR : public QgsCodeEditor public: //! Constructor for QgsCodeEditorR - QgsCodeEditorR( QWidget *parent SIP_TRANSFERTHIS = nullptr ); + QgsCodeEditorR( QWidget *parent SIP_TRANSFERTHIS = nullptr, QgsCodeEditor::Mode mode = QgsCodeEditor::Mode::ScriptEditor ); protected: void initializeLexer() override; From 732fc19805f35b39b84980db53b8657e1f0c95fd Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Thu, 6 Oct 2022 08:30:18 +0200 Subject: [PATCH 27/42] functions to get data from QGIS to R --- src/app/rstats/qgsrstatsrunner.cpp | 181 +++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index 68a66fdfbcc3..69f6e40bbd91 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -10,6 +10,7 @@ #include #include #include +#include "qgsproviderregistry.h" class QgsApplicationRWrapper { @@ -54,6 +55,183 @@ Rcpp::CharacterVector Names( Rcpp::XPtr ) return ret; } +Rcpp::NumericVector activeLayerNumericField(const std::string fieldName) +{ + Rcpp::NumericVector result; + + QgsMapLayer *layer = QgisApp::instance()->activeLayer(); + QgsVectorLayer *vlayer = qobject_cast( layer ); + + if ( !vlayer || (vlayer->dataProvider()->featureCount() < 1)) + return result; + + int fieldIndex = vlayer->fields().lookupField(QString::fromStdString(fieldName)); + + if (fieldIndex < 0) + return result; + + QgsField field = vlayer->fields().field(fieldIndex); + + if (!(field.type() == QVariant::Double || field.type() == QVariant::Int)) + return result; + + result = Rcpp::NumericVector(vlayer->dataProvider()->featureCount(), 0); + + QgsFeature feature; + + int i = 0; + QgsFeatureIterator it = vlayer->dataProvider()->getFeatures( QgsFeatureRequest() ); + + while ( it.nextFeature( feature ) ) + { + result[i] = feature.attribute(fieldIndex).toDouble(); + i++; + } + + return result; +} + +Rcpp::DataFrame activeLayerTable(){ + + Rcpp::DataFrame result = Rcpp::DataFrame(); + QgsMapLayer *layer = QgisApp::instance()->activeLayer(); + QgsVectorLayer *vlayer = qobject_cast( layer ); + + int featureCount = vlayer->dataProvider()->featureCount(); + + if ( !vlayer || ( featureCount < 1)) + return result; + + QgsFields fields = vlayer->fields(); + + for (int i =0; i < fields.count(); i++){ + + QgsField field = fields.at(i); + Rcpp::RObject column; + bool addColumn = false; + + switch(field.type()) + { + case QVariant::Bool: + { + column = Rcpp::LogicalVector(featureCount); + addColumn = true; + break; + } + case QVariant::Int: + { + column = Rcpp::IntegerVector(featureCount); + addColumn = true; + break; + } + case QVariant::Double: + { + column = Rcpp::DoubleVector(featureCount); + addColumn = true; + break; + } + case QVariant::LongLong: + { + column = Rcpp::DoubleVector(featureCount); + addColumn = true; + break; + } + case QVariant::String: + { + column = Rcpp::StringVector(featureCount); + addColumn = true; + break; + } + default: + break; + } + + if (addColumn) + result.push_back(column, field.name().toStdString()); + } + + QgsFeature feature; + QgsFeatureIterator it = vlayer->dataProvider()->getFeatures( QgsFeatureRequest() ); + int featureNumber = 0; + + while ( it.nextFeature( feature ) ) + { + int settingColumn = 0; + + for (int i = 0; i < fields.count(); i ++) + { + QgsField field = fields.at(i); + + switch (field.type()) + { + case QVariant::Bool: + { + Rcpp::LogicalVector column = result[settingColumn]; + column[featureNumber] = feature.attribute(i).toBool(); + break; + } + case QVariant::Int: + { + Rcpp::IntegerVector column = result[settingColumn]; + column[featureNumber] = feature.attribute(i).toInt(); + break; + } + case QVariant::LongLong: + { + Rcpp::DoubleVector column = result[settingColumn]; + bool ok; + double val = feature.attribute(i).toDouble(&ok); + if (ok) + column[featureNumber] = val; + else + column[featureNumber] = R_NaReal; + break; + } + case QVariant::Double: + { + Rcpp::DoubleVector column = result[settingColumn]; + column[featureNumber] = feature.attribute(i).toDouble(); + break; + } + case QVariant::String: + { + Rcpp::StringVector column = result[settingColumn]; + column[featureNumber] = feature.attribute(i).toString().toStdString(); + break; + } + default: + break; + } + settingColumn++; + } + featureNumber++; + } + return result; +} + +SEXP activeLayerToSf(){ + + QgsMapLayer *layer = QgisApp::instance()->activeLayer(); + QgsVectorLayer *vlayer = qobject_cast( layer ); + + if ( !vlayer) + return R_NilValue; + + if (vlayer->dataProvider()->name() != QStringLiteral("ogr")) + return R_NilValue; + + const QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( layer->dataProvider()->name(), layer->source() ); + std::string path = parts[ QStringLiteral( "path" ) ].toString().toStdString(); + std::string layerName = parts[ QStringLiteral( "layerName" ) ].toString().toStdString(); + + if (path.empty()) + return R_NilValue; + + Rcpp::Function st_read("st_read"); + + return st_read(path, layerName); +} + // // QgsRStatsSession // @@ -74,6 +252,9 @@ QgsRStatsSession::QgsRStatsSession() mRSession->assign( wr, "QGIS" ); mRSession->assign( Rcpp::InternalFunction( & Dollar ), "$.QGIS" ); mRSession->assign( Rcpp::InternalFunction( & Names ), "names.QGIS" ); + mRSession->assign( Rcpp::InternalFunction( & activeLayerNumericField ), "activeLayerNumericField" ); + mRSession->assign( Rcpp::InternalFunction( & activeLayerTable ), "activeLayerTable" ); + mRSession->assign( Rcpp::InternalFunction( & activeLayerToSf ), "readActiveLayerToSf" ); //( *mRSession )["val"] = 5; //mRSession->parseEvalQ( "val2<-7" ); From e24063b6e926e73a3a380133c60b3c4c757fd4b2 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 7 Oct 2022 09:13:43 +1000 Subject: [PATCH 28/42] Setup test framework --- tests/src/app/CMakeLists.txt | 4 ++ tests/src/app/testqgsrstats.cpp | 71 +++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 tests/src/app/testqgsrstats.cpp diff --git a/tests/src/app/CMakeLists.txt b/tests/src/app/CMakeLists.txt index 6016597ef072..d0317a1bb6e7 100644 --- a/tests/src/app/CMakeLists.txt +++ b/tests/src/app/CMakeLists.txt @@ -55,6 +55,10 @@ if (WITH_BINDINGS) set(TESTS ${TESTS} testqgisapppython.cpp) endif() +if (WITH_R) + set(TESTS ${TESTS} testqgsrstats.cpp) +endif() + foreach(TESTSRC ${TESTS}) add_qgis_test(${TESTSRC} MODULE app LINKEDLIBRARIES qgis_app) endforeach(TESTSRC) diff --git a/tests/src/app/testqgsrstats.cpp b/tests/src/app/testqgsrstats.cpp new file mode 100644 index 000000000000..9818a40fa77d --- /dev/null +++ b/tests/src/app/testqgsrstats.cpp @@ -0,0 +1,71 @@ +/*************************************************************************** + testqgsrstats.cpp + -------------------- + Date : October 2022 + Copyright : (C) 2022 by Nyall Dawson + Email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#include +#include +#include +#include +#include +#include "qgstest.h" + +#include "qgisapp.h" +#include "qgsapplication.h" + +/** + * \ingroup UnitTests + * This is a unit test for the R stats support + */ +class TestQgsRStats : public QObject +{ + Q_OBJECT + + public: + TestQgsRStats(); + + private slots: + void initTestCase();// will be called before the first testfunction is executed. + void cleanupTestCase();// will be called after the last testfunction was executed. + + private: + QString mTestDataDir; + QgisApp *mQgisApp; +}; + +TestQgsRStats::TestQgsRStats() = default; + +//runs before all tests +void TestQgsRStats::initTestCase() +{ + qputenv( "QGIS_PLUGINPATH", QByteArray( TEST_DATA_DIR ) + "/test_plugin_path" ); + + // Set up the QgsSettings environment + QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) ); + QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) ); + QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) ); + + qDebug() << "TestQgisAppClipboard::initTestCase()"; + // init QGIS's paths - true means that all path will be inited from prefix + QgsApplication::init(); + QgsApplication::initQgis(); + mTestDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt + mQgisApp = new QgisApp(); +} + +void TestQgsRStats::cleanupTestCase() +{ + QgsApplication::exitQgis(); +} + +QGSTEST_MAIN( TestQgsRStats ) +#include "testqgsrstats.moc" From a407190554f4064da3377f464ae696862ac9c7c6 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 7 Oct 2022 10:36:49 +1000 Subject: [PATCH 29/42] Handle list values during conversion, add tests --- src/app/rstats/qgsrstatsrunner.cpp | 391 +++++++++++++++++------------ src/app/rstats/qgsrstatsrunner.h | 18 +- tests/src/app/CMakeLists.txt | 2 +- tests/src/app/testqgsrstats.cpp | 83 ++++++ 4 files changed, 334 insertions(+), 160 deletions(-) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index 69f6e40bbd91..b139a3988fd5 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -55,181 +55,184 @@ Rcpp::CharacterVector Names( Rcpp::XPtr ) return ret; } -Rcpp::NumericVector activeLayerNumericField(const std::string fieldName) +Rcpp::NumericVector activeLayerNumericField( const std::string fieldName ) { - Rcpp::NumericVector result; + Rcpp::NumericVector result; - QgsMapLayer *layer = QgisApp::instance()->activeLayer(); - QgsVectorLayer *vlayer = qobject_cast( layer ); + QgsMapLayer *layer = QgisApp::instance()->activeLayer(); + QgsVectorLayer *vlayer = qobject_cast( layer ); - if ( !vlayer || (vlayer->dataProvider()->featureCount() < 1)) - return result; + if ( !vlayer || ( vlayer->dataProvider()->featureCount() < 1 ) ) + return result; - int fieldIndex = vlayer->fields().lookupField(QString::fromStdString(fieldName)); + int fieldIndex = vlayer->fields().lookupField( QString::fromStdString( fieldName ) ); - if (fieldIndex < 0) - return result; + if ( fieldIndex < 0 ) + return result; - QgsField field = vlayer->fields().field(fieldIndex); + QgsField field = vlayer->fields().field( fieldIndex ); - if (!(field.type() == QVariant::Double || field.type() == QVariant::Int)) - return result; + if ( !( field.type() == QVariant::Double || field.type() == QVariant::Int ) ) + return result; - result = Rcpp::NumericVector(vlayer->dataProvider()->featureCount(), 0); + result = Rcpp::NumericVector( vlayer->dataProvider()->featureCount(), 0 ); - QgsFeature feature; + QgsFeature feature; - int i = 0; - QgsFeatureIterator it = vlayer->dataProvider()->getFeatures( QgsFeatureRequest() ); + int i = 0; + QgsFeatureIterator it = vlayer->dataProvider()->getFeatures( QgsFeatureRequest() ); - while ( it.nextFeature( feature ) ) - { - result[i] = feature.attribute(fieldIndex).toDouble(); - i++; - } + while ( it.nextFeature( feature ) ) + { + result[i] = feature.attribute( fieldIndex ).toDouble(); + i++; + } - return result; + return result; } -Rcpp::DataFrame activeLayerTable(){ - - Rcpp::DataFrame result = Rcpp::DataFrame(); - QgsMapLayer *layer = QgisApp::instance()->activeLayer(); - QgsVectorLayer *vlayer = qobject_cast( layer ); +Rcpp::DataFrame activeLayerTable() +{ - int featureCount = vlayer->dataProvider()->featureCount(); + Rcpp::DataFrame result = Rcpp::DataFrame(); + QgsMapLayer *layer = QgisApp::instance()->activeLayer(); + QgsVectorLayer *vlayer = qobject_cast( layer ); - if ( !vlayer || ( featureCount < 1)) - return result; + int featureCount = vlayer->dataProvider()->featureCount(); - QgsFields fields = vlayer->fields(); + if ( !vlayer || ( featureCount < 1 ) ) + return result; - for (int i =0; i < fields.count(); i++){ + QgsFields fields = vlayer->fields(); - QgsField field = fields.at(i); - Rcpp::RObject column; - bool addColumn = false; + for ( int i = 0; i < fields.count(); i++ ) + { - switch(field.type()) - { - case QVariant::Bool: - { - column = Rcpp::LogicalVector(featureCount); - addColumn = true; - break; - } - case QVariant::Int: - { - column = Rcpp::IntegerVector(featureCount); - addColumn = true; - break; - } - case QVariant::Double: - { - column = Rcpp::DoubleVector(featureCount); - addColumn = true; - break; - } - case QVariant::LongLong: - { - column = Rcpp::DoubleVector(featureCount); - addColumn = true; - break; - } - case QVariant::String: - { - column = Rcpp::StringVector(featureCount); - addColumn = true; - break; - } - default: - break; - } + QgsField field = fields.at( i ); + Rcpp::RObject column; + bool addColumn = false; - if (addColumn) - result.push_back(column, field.name().toStdString()); + switch ( field.type() ) + { + case QVariant::Bool: + { + column = Rcpp::LogicalVector( featureCount ); + addColumn = true; + break; + } + case QVariant::Int: + { + column = Rcpp::IntegerVector( featureCount ); + addColumn = true; + break; + } + case QVariant::Double: + { + column = Rcpp::DoubleVector( featureCount ); + addColumn = true; + break; + } + case QVariant::LongLong: + { + column = Rcpp::DoubleVector( featureCount ); + addColumn = true; + break; + } + case QVariant::String: + { + column = Rcpp::StringVector( featureCount ); + addColumn = true; + break; + } + default: + break; } - QgsFeature feature; - QgsFeatureIterator it = vlayer->dataProvider()->getFeatures( QgsFeatureRequest() ); - int featureNumber = 0; + if ( addColumn ) + result.push_back( column, field.name().toStdString() ); + } + + QgsFeature feature; + QgsFeatureIterator it = vlayer->dataProvider()->getFeatures( QgsFeatureRequest() ); + int featureNumber = 0; - while ( it.nextFeature( feature ) ) + while ( it.nextFeature( feature ) ) + { + int settingColumn = 0; + + for ( int i = 0; i < fields.count(); i ++ ) { - int settingColumn = 0; + QgsField field = fields.at( i ); - for (int i = 0; i < fields.count(); i ++) + switch ( field.type() ) + { + case QVariant::Bool: { - QgsField field = fields.at(i); - - switch (field.type()) - { - case QVariant::Bool: - { - Rcpp::LogicalVector column = result[settingColumn]; - column[featureNumber] = feature.attribute(i).toBool(); - break; - } - case QVariant::Int: - { - Rcpp::IntegerVector column = result[settingColumn]; - column[featureNumber] = feature.attribute(i).toInt(); - break; - } - case QVariant::LongLong: - { - Rcpp::DoubleVector column = result[settingColumn]; - bool ok; - double val = feature.attribute(i).toDouble(&ok); - if (ok) - column[featureNumber] = val; - else - column[featureNumber] = R_NaReal; - break; - } - case QVariant::Double: - { - Rcpp::DoubleVector column = result[settingColumn]; - column[featureNumber] = feature.attribute(i).toDouble(); - break; - } - case QVariant::String: - { - Rcpp::StringVector column = result[settingColumn]; - column[featureNumber] = feature.attribute(i).toString().toStdString(); - break; - } - default: - break; - } - settingColumn++; + Rcpp::LogicalVector column = result[settingColumn]; + column[featureNumber] = feature.attribute( i ).toBool(); + break; } - featureNumber++; + case QVariant::Int: + { + Rcpp::IntegerVector column = result[settingColumn]; + column[featureNumber] = feature.attribute( i ).toInt(); + break; + } + case QVariant::LongLong: + { + Rcpp::DoubleVector column = result[settingColumn]; + bool ok; + double val = feature.attribute( i ).toDouble( &ok ); + if ( ok ) + column[featureNumber] = val; + else + column[featureNumber] = R_NaReal; + break; + } + case QVariant::Double: + { + Rcpp::DoubleVector column = result[settingColumn]; + column[featureNumber] = feature.attribute( i ).toDouble(); + break; + } + case QVariant::String: + { + Rcpp::StringVector column = result[settingColumn]; + column[featureNumber] = feature.attribute( i ).toString().toStdString(); + break; + } + default: + break; + } + settingColumn++; } - return result; + featureNumber++; + } + return result; } -SEXP activeLayerToSf(){ +SEXP activeLayerToSf() +{ - QgsMapLayer *layer = QgisApp::instance()->activeLayer(); - QgsVectorLayer *vlayer = qobject_cast( layer ); + QgsMapLayer *layer = QgisApp::instance()->activeLayer(); + QgsVectorLayer *vlayer = qobject_cast( layer ); - if ( !vlayer) - return R_NilValue; + if ( !vlayer ) + return R_NilValue; - if (vlayer->dataProvider()->name() != QStringLiteral("ogr")) - return R_NilValue; + if ( vlayer->dataProvider()->name() != QStringLiteral( "ogr" ) ) + return R_NilValue; - const QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( layer->dataProvider()->name(), layer->source() ); - std::string path = parts[ QStringLiteral( "path" ) ].toString().toStdString(); - std::string layerName = parts[ QStringLiteral( "layerName" ) ].toString().toStdString(); + const QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( layer->dataProvider()->name(), layer->source() ); + std::string path = parts[ QStringLiteral( "path" ) ].toString().toStdString(); + std::string layerName = parts[ QStringLiteral( "layerName" ) ].toString().toStdString(); - if (path.empty()) - return R_NilValue; + if ( path.empty() ) + return R_NilValue; - Rcpp::Function st_read("st_read"); + Rcpp::Function st_read( "st_read" ); - return st_read(path, layerName); + return st_read( path, layerName ); } // @@ -306,7 +309,7 @@ void QgsRStatsSession::showStartupMessage() QgsRStatsSession::~QgsRStatsSession() = default; -std::string QgsRStatsSession::sexpToString( const SEXP exp ) +QString QgsRStatsSession::sexpToString( const SEXP exp ) { switch ( TYPEOF( exp ) ) { @@ -315,7 +318,13 @@ std::string QgsRStatsSession::sexpToString( const SEXP exp ) case ENVSXP: case LANGSXP: // these types can't be converted to StringVector, will raise exceptions - return {}; + return QString(); + + case CHARSXP: + { + // special case + return QStringLiteral( "[1] \"%1\"" ).arg( QString::fromStdString( Rcpp::as( exp ) ) ); + } case LGLSXP: case INTSXP: @@ -340,11 +349,22 @@ std::string QgsRStatsSession::sexpToString( const SEXP exp ) if ( it < lines.end() - 1 ) outcome.append( "\n" ); } - return outcome; + return QString::fromStdString( outcome ); } QVariant QgsRStatsSession::sexpToVariant( const SEXP exp ) { + const int length = LENGTH( exp ); + if ( length == 0 ) + { + if ( TYPEOF( exp ) == NILSXP ) + return QVariant(); + else if ( TYPEOF( exp ) == CHARSXP ) + return QString( "" ); + else + return QVariantList(); + } + switch ( TYPEOF( exp ) ) { case NILSXP: @@ -352,7 +372,23 @@ QVariant QgsRStatsSession::sexpToVariant( const SEXP exp ) case LGLSXP: { - if ( LENGTH( exp ) == 1 ) + if ( length > 1 ) + { + const Rcpp::LogicalVector logicalVector( exp ); + + QVariantList res; + res.reserve( length ); + for ( int i = 0; i < length; i++ ) + { + const int expInt = logicalVector[i]; + if ( expInt < 0 ) + res << QVariant(); + else + res << static_cast< bool >( expInt ); + } + return res; + } + else { const int expInt = Rcpp::as( exp ); if ( expInt < 0 ) @@ -360,30 +396,73 @@ QVariant QgsRStatsSession::sexpToVariant( const SEXP exp ) else return static_cast< bool >( expInt ); } - else - return QVariant(); - } case INTSXP: - // handle NA_integer_ as NA! - if ( LENGTH( exp ) == 1 ) - return Rcpp::as( exp ); + { + if ( length > 1 ) + { + const Rcpp::IntegerVector intVector( exp ); + + QVariantList res; + res.reserve( length ); + for ( int i = 0; i < length; i++ ) + { + const int elementInt = intVector[i]; + res << ( elementInt == NA_INTEGER ? QVariant() : QVariant( elementInt ) ); + } + return res; + } else - return QVariant(); + { + const int res = Rcpp::as( exp ); + return res == NA_INTEGER ? QVariant() : QVariant( res ); + } + } case REALSXP: - // handle nan as NA - if ( LENGTH( exp ) == 1 ) - return Rcpp::as( exp ); + { + if ( length > 1 ) + { + const Rcpp::DoubleVector realVector( exp ); + + QVariantList res; + res.reserve( length ); + for ( int i = 0; i < length; i++ ) + { + const double elementReal = realVector[i]; + res << ( std::isnan( elementReal ) ? QVariant() : QVariant( elementReal ) ); + } + return res; + } else - return QVariant(); + { + const double res = Rcpp::as( exp ); + return std::isnan( res ) ? QVariant() : res; + } + } case STRSXP: - if ( LENGTH( exp ) == 1 ) - return QString::fromStdString( Rcpp::as( exp ) ); + if ( length > 1 ) + { + const Rcpp::StringVector stringVector( exp ); + + QVariantList res; + res.reserve( length ); + for ( int i = 0; i < length; i++ ) + { + const char *elementString = stringVector[i]; + res << QVariant( QString( elementString ) ); + } + return res; + } else - return QVariant(); + { + return QString::fromStdString( Rcpp::as( exp ) ); + } + + case CHARSXP: + return QString::fromStdString( Rcpp::as( exp ) ); //case RAWSXP: // return R::rawPointer( exp ); @@ -411,7 +490,7 @@ void QgsRStatsSession::execCommandPrivate( const QString &command, QString &erro if ( res ) *res = sexpToVariant( sexpRes ); if ( output ) - *output = QString::fromStdString( sexpToString( sexpRes ) ); + *output = sexpToString( sexpRes ); } catch ( std::exception &ex ) { diff --git a/src/app/rstats/qgsrstatsrunner.h b/src/app/rstats/qgsrstatsrunner.h index 10f1ee3693c2..ef9b85883db6 100644 --- a/src/app/rstats/qgsrstatsrunner.h +++ b/src/app/rstats/qgsrstatsrunner.h @@ -18,15 +18,18 @@ #define QGSRSTATSRUNNER_H #include + #include #include #include "Callbacks.h" +#include "qgis_app.h" + class RInside; class QVariant; class QString; -class QgsRStatsSession: public QObject, public Callbacks +class APP_EXPORT QgsRStatsSession: public QObject, public Callbacks { Q_OBJECT public: @@ -46,6 +49,16 @@ class QgsRStatsSession: public QObject, public Callbacks bool busy() const { return mBusy; } + /** + * Converts a SEXP object to a string. + */ + static QString sexpToString( const SEXP exp ); + + /** + * Converts a SEXP object to a QVariant. + */ + static QVariant sexpToVariant( const SEXP exp ); + public slots: void execCommand( const QString &command ); @@ -68,8 +81,7 @@ class QgsRStatsSession: public QObject, public Callbacks bool mBusy = false; bool mEncounteredErrorMessageType = false; - static std::string sexpToString( const SEXP exp ); - static QVariant sexpToVariant( const SEXP exp ); + }; diff --git a/tests/src/app/CMakeLists.txt b/tests/src/app/CMakeLists.txt index d0317a1bb6e7..204758754b66 100644 --- a/tests/src/app/CMakeLists.txt +++ b/tests/src/app/CMakeLists.txt @@ -7,7 +7,6 @@ include_directories( ) - set(TESTS testqgisapp.cpp testqgsappbrowserproviders.cpp @@ -56,6 +55,7 @@ if (WITH_BINDINGS) endif() if (WITH_R) + add_compile_definitions(RINSIDE_CALLBACKS=1) set(TESTS ${TESTS} testqgsrstats.cpp) endif() diff --git a/tests/src/app/testqgsrstats.cpp b/tests/src/app/testqgsrstats.cpp index 9818a40fa77d..bd1f18577392 100644 --- a/tests/src/app/testqgsrstats.cpp +++ b/tests/src/app/testqgsrstats.cpp @@ -17,10 +17,13 @@ #include #include #include +#include + #include "qgstest.h" #include "qgisapp.h" #include "qgsapplication.h" +#include "rstats/qgsrstatsrunner.h" /** * \ingroup UnitTests @@ -36,10 +39,13 @@ class TestQgsRStats : public QObject private slots: void initTestCase();// will be called before the first testfunction is executed. void cleanupTestCase();// will be called after the last testfunction was executed. + void testSexpToVariant(); + void testSexpToString(); private: QString mTestDataDir; QgisApp *mQgisApp; + std::unique_ptr< QgsRStatsSession > mSession; }; TestQgsRStats::TestQgsRStats() = default; @@ -60,12 +66,89 @@ void TestQgsRStats::initTestCase() QgsApplication::initQgis(); mTestDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt mQgisApp = new QgisApp(); + + mSession = std::make_unique< QgsRStatsSession >(); } void TestQgsRStats::cleanupTestCase() { + mSession.reset(); QgsApplication::exitQgis(); } +void TestQgsRStats::testSexpToVariant() +{ + QCOMPARE( QgsRStatsSession::sexpToVariant( R_NilValue ), QVariant() ); + + // logical + Rcpp::LogicalVector vLogical = Rcpp::LogicalVector::create( 1, 0, NA_LOGICAL ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vLogical[0] ) ), QVariant( true ) ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vLogical[1] ) ), QVariant( false ) ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vLogical[2] ) ), QVariant() ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vLogical ) ), QVariant( QVariantList() << true << false << QVariant() ) ); + + // int + Rcpp::IntegerVector vInteger = Rcpp::IntegerVector::create( 100, 0, -100, NA_INTEGER ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vInteger[0] ) ), QVariant( 100 ) ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vInteger[1] ) ), QVariant( 0 ) ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vInteger[2] ) ), QVariant( - 100 ) ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vInteger[3] ) ), QVariant() ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vInteger ) ), QVariant( QVariantList() << 100 << 0 << -100 << QVariant() ) ); + + // double + Rcpp::DoubleVector vDouble = Rcpp::DoubleVector::create( 100.2, 0, -100.2, std::numeric_limits< double >::quiet_NaN() ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vDouble[0] ) ), QVariant( 100.2 ) ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vDouble[1] ) ), QVariant( 0.0 ) ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vDouble[2] ) ), QVariant( - 100.2 ) ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vDouble[3] ) ), QVariant() ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vDouble ) ), QVariant( QVariantList() << 100.2 << 0.0 << -100.2 << QVariant() ) ); + + // string + Rcpp::StringVector vString = Rcpp::StringVector::create( "string 1", "string2", "", std::string() ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vString[0] ) ), QVariant( QStringLiteral( "string 1" ) ) ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vString[1] ) ), QVariant( QStringLiteral( "string2" ) ) ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vString[2] ) ), QVariant( QString( "" ) ) ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vString[3] ) ), QVariant( QString( "" ) ) ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( std::string( "test" ) ) ), QVariant( QStringLiteral( "test" ) ) ); + QCOMPARE( QgsRStatsSession::sexpToVariant( Rcpp::wrap( vString ) ), QVariant( QVariantList() << QStringLiteral( "string 1" ) << QStringLiteral( "string2" ) << QString( "" ) << QString( "" ) ) ); +} + +void TestQgsRStats::testSexpToString() +{ + QCOMPARE( QgsRStatsSession::sexpToString( R_NilValue ), QStringLiteral( "NULL" ) ); + + // logical + Rcpp::LogicalVector vLogical = Rcpp::LogicalVector::create( 1, 0, NA_LOGICAL ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vLogical[0] ) ), QStringLiteral( "[1] 1" ) ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vLogical[1] ) ), QStringLiteral( "[1] 0" ) ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vLogical[2] ) ), QStringLiteral( "[1] NA" ) ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vLogical ) ), QStringLiteral( "[1] TRUE FALSE NA" ) ); + + // int + Rcpp::IntegerVector vInteger = Rcpp::IntegerVector::create( 100, 0, -100, NA_INTEGER ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vInteger[0] ) ), QStringLiteral( "[1] 100" ) ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vInteger[1] ) ), QStringLiteral( "[1] 0" ) ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vInteger[2] ) ), QStringLiteral( "[1] -100" ) ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vInteger[3] ) ), QStringLiteral( "[1] NA" ) ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vInteger ) ), QStringLiteral( "[1] 100 0 -100 NA" ) ); + + // double + Rcpp::DoubleVector vDouble = Rcpp::DoubleVector::create( 100.2, 0, -100.2, std::numeric_limits< double >::quiet_NaN() ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vDouble[0] ) ), QStringLiteral( "[1] 100.2" ) ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vDouble[1] ) ), QStringLiteral( "[1] 0" ) ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vDouble[2] ) ), QStringLiteral( "[1] -100.2" ) ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vDouble[3] ) ), QStringLiteral( "[1] NaN" ) ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vDouble ) ), QStringLiteral( "[1] 100.2 0.0 -100.2 NaN" ) ); + + // string + Rcpp::StringVector vString = Rcpp::StringVector::create( "string 1", "string2", "", std::string() ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vString[0] ) ), QStringLiteral( "[1] \"string 1\"" ) ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vString[1] ) ), QStringLiteral( "[1] \"string2\"" ) ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vString[2] ) ), QStringLiteral( "[1] \"\"" ) ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vString[3] ) ), QStringLiteral( "[1] \"\"" ) ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( std::string( "test" ) ) ), QStringLiteral( "[1] \"test\"" ) ); + QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vString ) ), QStringLiteral( "[1] \"string 1\" \"string2\" \"\" \"\" " ) ); +} + QGSTEST_MAIN( TestQgsRStats ) #include "testqgsrstats.moc" From 28235a5d8df4cbea44c819ae071d8320ba5c25d8 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 7 Oct 2022 10:49:03 +1000 Subject: [PATCH 30/42] Add variant to SEXP methods --- src/app/rstats/qgsrstatsrunner.cpp | 39 ++++++++++++++++++++++++++++++ src/app/rstats/qgsrstatsrunner.h | 5 ++++ tests/src/app/testqgsrstats.cpp | 24 ++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index b139a3988fd5..cbb70edbce2a 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -6,6 +6,7 @@ #include "qgisapp.h" #include "qgslogger.h" +#include "qgsvariantutils.h" #include #include #include @@ -482,6 +483,44 @@ QVariant QgsRStatsSession::sexpToVariant( const SEXP exp ) return QVariant(); } +SEXP QgsRStatsSession::variantToSexp( const QVariant &variant ) +{ + switch ( variant.type() ) + { + case QVariant::Invalid: + return R_NilValue; + + case QVariant::Bool: + if ( QgsVariantUtils::isNull( variant ) ) + return Rcpp::wrap( NA_LOGICAL ); + + return Rcpp::wrap( variant.toBool() ? 1 : 0 ); + + case QVariant::Int: + if ( QgsVariantUtils::isNull( variant ) ) + return Rcpp::wrap( NA_INTEGER ); + + return Rcpp::wrap( variant.toInt() ); + + case QVariant::Double: + if ( QgsVariantUtils::isNull( variant ) ) + return Rcpp::wrap( std::numeric_limits< double >::quiet_NaN() ); + + return Rcpp::wrap( variant.toDouble() ); + + case QVariant::String: + return Rcpp::wrap( variant.toString().toStdString() ); + + case QVariant::UserType: + QgsDebugMsg( QStringLiteral( "unsupported user variant type %1" ).arg( QMetaType::typeName( variant.userType() ) ) ); + return nullptr; + + default: + QgsDebugMsg( QStringLiteral( "unsupported variant type %1" ).arg( QVariant::typeToName( variant.type() ) ) ); + return nullptr; + } +} + void QgsRStatsSession::execCommandPrivate( const QString &command, QString &error, QVariant *res, QString *output ) { try diff --git a/src/app/rstats/qgsrstatsrunner.h b/src/app/rstats/qgsrstatsrunner.h index ef9b85883db6..cf855d3b5523 100644 --- a/src/app/rstats/qgsrstatsrunner.h +++ b/src/app/rstats/qgsrstatsrunner.h @@ -59,6 +59,11 @@ class APP_EXPORT QgsRStatsSession: public QObject, public Callbacks */ static QVariant sexpToVariant( const SEXP exp ); + /** + * Converts a variant to a SEXP. + */ + static SEXP variantToSexp( const QVariant &variant ); + public slots: void execCommand( const QString &command ); diff --git a/tests/src/app/testqgsrstats.cpp b/tests/src/app/testqgsrstats.cpp index bd1f18577392..5cba61c536c1 100644 --- a/tests/src/app/testqgsrstats.cpp +++ b/tests/src/app/testqgsrstats.cpp @@ -41,6 +41,7 @@ class TestQgsRStats : public QObject void cleanupTestCase();// will be called after the last testfunction was executed. void testSexpToVariant(); void testSexpToString(); + void testVariantToSexp(); private: QString mTestDataDir; @@ -150,5 +151,28 @@ void TestQgsRStats::testSexpToString() QCOMPARE( QgsRStatsSession::sexpToString( Rcpp::wrap( vString ) ), QStringLiteral( "[1] \"string 1\" \"string2\" \"\" \"\" " ) ); } +void TestQgsRStats::testVariantToSexp() +{ + QCOMPARE( QgsRStatsSession::variantToSexp( QVariant() ), R_NilValue ); + + QCOMPARE( Rcpp::as( QgsRStatsSession::variantToSexp( QVariant( QVariant::Bool ) ) ), NA_LOGICAL ); + QCOMPARE( Rcpp::as( QgsRStatsSession::variantToSexp( QVariant( true ) ) ), 1 ); + QCOMPARE( Rcpp::as( QgsRStatsSession::variantToSexp( QVariant( false ) ) ), 0 ); + + QCOMPARE( Rcpp::as( QgsRStatsSession::variantToSexp( QVariant( QVariant::Int ) ) ), NA_INTEGER ); + QCOMPARE( Rcpp::as( QgsRStatsSession::variantToSexp( QVariant( 100 ) ) ), 100 ); + QCOMPARE( Rcpp::as( QgsRStatsSession::variantToSexp( QVariant( 0 ) ) ), 0 ); + QCOMPARE( Rcpp::as( QgsRStatsSession::variantToSexp( QVariant( -100 ) ) ), -100 ); + + QVERIFY( std::isnan( Rcpp::as( QgsRStatsSession::variantToSexp( QVariant( QVariant::Double ) ) ) ) ); + QCOMPARE( Rcpp::as( QgsRStatsSession::variantToSexp( QVariant( 100.2 ) ) ), 100.2 ); + QCOMPARE( Rcpp::as( QgsRStatsSession::variantToSexp( QVariant( 0.0 ) ) ), 0.0 ); + QCOMPARE( Rcpp::as( QgsRStatsSession::variantToSexp( QVariant( -100.2 ) ) ), -100.2 ); + + QCOMPARE( Rcpp::as( QgsRStatsSession::variantToSexp( QVariant( QVariant::String ) ) ), "" ); + QCOMPARE( Rcpp::as( QgsRStatsSession::variantToSexp( QVariant( QStringLiteral( "test string" ) ) ) ), "test string" ); + QCOMPARE( Rcpp::as( QgsRStatsSession::variantToSexp( QVariant( QString( "" ) ) ) ), "" ); +} + QGSTEST_MAIN( TestQgsRStats ) #include "testqgsrstats.moc" From 423074d3bea70c2dbb6a69f952653d947bb3260c Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 7 Oct 2022 17:24:52 +1000 Subject: [PATCH 31/42] Support cancelation in QgsScopedProxyProgressTask --- .../auto_generated/qgsproxyprogresstask.sip.in | 16 +++++++++++++++- src/core/qgsproxyprogresstask.cpp | 10 ++++++++-- src/core/qgsproxyprogresstask.h | 17 ++++++++++++++++- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/python/core/auto_generated/qgsproxyprogresstask.sip.in b/python/core/auto_generated/qgsproxyprogresstask.sip.in index e5b137d4f84f..6f447aa751cf 100644 --- a/python/core/auto_generated/qgsproxyprogresstask.sip.in +++ b/python/core/auto_generated/qgsproxyprogresstask.sip.in @@ -49,6 +49,13 @@ to remove this proxy task from the task manager. Sets the ``progress`` (from 0 to 100) for the proxied operation. This method is safe to call from the main thread. +%End + + bool isCanceled() const; +%Docstring +Returns ``True`` if the task has been canceled. + +.. versionadded:: 3.30 %End virtual void cancel(); @@ -81,7 +88,7 @@ when it goes out of scope. %End public: - QgsScopedProxyProgressTask( const QString &description ); + QgsScopedProxyProgressTask( const QString &description, bool canCancel = false ); %Docstring Constructor for QgsScopedProxyProgressTask, with the specified ``description``. %End @@ -91,6 +98,13 @@ Constructor for QgsScopedProxyProgressTask, with the specified ``description``. void setProgress( double progress ); %Docstring Sets the ``progress`` (from 0 to 100) for the proxied operation. +%End + + bool isCanceled() const; +%Docstring +Returns ``True`` if the task has been canceled. + +.. versionadded:: 3.30 %End }; diff --git a/src/core/qgsproxyprogresstask.cpp b/src/core/qgsproxyprogresstask.cpp index bcf683dd9e31..55a85193364c 100644 --- a/src/core/qgsproxyprogresstask.cpp +++ b/src/core/qgsproxyprogresstask.cpp @@ -51,6 +51,7 @@ void QgsProxyProgressTask::setProxyProgress( double progress ) void QgsProxyProgressTask::cancel() { + mCanceled = true; emit canceled(); QgsTask::cancel(); @@ -60,8 +61,8 @@ void QgsProxyProgressTask::cancel() // QgsScopedProxyProgressTask // -QgsScopedProxyProgressTask::QgsScopedProxyProgressTask( const QString &description ) - : mTask( new QgsProxyProgressTask( description ) ) +QgsScopedProxyProgressTask::QgsScopedProxyProgressTask( const QString &description, bool canCancel ) + : mTask( new QgsProxyProgressTask( description, canCancel ) ) { QgsApplication::taskManager()->addTask( mTask ); } @@ -75,3 +76,8 @@ void QgsScopedProxyProgressTask::setProgress( double progress ) { mTask->setProxyProgress( progress ); } + +bool QgsScopedProxyProgressTask::isCanceled() const +{ + return mTask->isCanceled(); +} diff --git a/src/core/qgsproxyprogresstask.h b/src/core/qgsproxyprogresstask.h index 58777a2959fc..d01282e9f655 100644 --- a/src/core/qgsproxyprogresstask.h +++ b/src/core/qgsproxyprogresstask.h @@ -62,6 +62,13 @@ class CORE_EXPORT QgsProxyProgressTask : public QgsTask */ void setProxyProgress( double progress ); + /** + * Returns TRUE if the task has been canceled. + * + * \since QGIS 3.30 + */ + bool isCanceled() const { return mCanceled; } + void cancel() override; signals: @@ -79,6 +86,7 @@ class CORE_EXPORT QgsProxyProgressTask : public QgsTask QMutex mNotFinishedMutex; bool mAlreadyFinished = false; bool mResult = true; + bool mCanceled = false; }; @@ -98,7 +106,7 @@ class CORE_EXPORT QgsScopedProxyProgressTask /** * Constructor for QgsScopedProxyProgressTask, with the specified \a description. */ - QgsScopedProxyProgressTask( const QString &description ); + QgsScopedProxyProgressTask( const QString &description, bool canCancel = false ); ~QgsScopedProxyProgressTask(); @@ -107,6 +115,13 @@ class CORE_EXPORT QgsScopedProxyProgressTask */ void setProgress( double progress ); + /** + * Returns TRUE if the task has been canceled. + * + * \since QGIS 3.30 + */ + bool isCanceled() const; + private: QgsProxyProgressTask *mTask = nullptr; From 1fd1ab79ee7a3dbc340d022ef58dba70ecabdcf9 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 7 Oct 2022 17:25:19 +1000 Subject: [PATCH 32/42] Fix some crashes --- src/app/rstats/qgsrstatsrunner.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index cbb70edbce2a..2182fae45800 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -318,6 +318,7 @@ QString QgsRStatsSession::sexpToString( const SEXP exp ) case CLOSXP: case ENVSXP: case LANGSXP: + case S4SXP: // these types can't be converted to StringVector, will raise exceptions return QString(); @@ -331,10 +332,12 @@ QString QgsRStatsSession::sexpToString( const SEXP exp ) case INTSXP: case REALSXP: case STRSXP: + case EXTPTRSXP: + case VECSXP: break; // we know these types are fine to convert to StringVector case NILSXP: - return "NULL"; + return QStringLiteral( "NULL" ); default: QgsDebugMsg( QStringLiteral( "Possibly unsafe type: %1" ).arg( TYPEOF( exp ) ) ); @@ -355,6 +358,21 @@ QString QgsRStatsSession::sexpToString( const SEXP exp ) QVariant QgsRStatsSession::sexpToVariant( const SEXP exp ) { + switch ( TYPEOF( exp ) ) + { + case S4SXP: + case LANGSXP: + case SYMSXP: + case EXTPTRSXP: + case CLOSXP: + // not safe to call LENGTH on! + return QVariant(); + + default: + break; + } + + QgsDebugMsg( QStringLiteral( "Handing type: %1" ).arg( TYPEOF( exp ) ) ); const int length = LENGTH( exp ); if ( length == 0 ) { From 36651688c1fa7eba792d9c5e457095586fd1effc Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 7 Oct 2022 17:32:53 +1000 Subject: [PATCH 33/42] Lots of stuff! - Thread safe layer access - QGIS$mapLayerByName('...') - QGIS$featureCount( a layer ) - QGIS$toDataFrame( a layer ) eg summary(QGIS$toDataFrame(QGIS$activeLayer)) summary(QGIS$toDataFrame(QGIS$mapLayerByName('my layer'))) --- src/app/rstats/qgsrstatsconsole.cpp | 2 +- src/app/rstats/qgsrstatsrunner.cpp | 392 +++++++++++++++++++--------- src/app/rstats/qgsrstatsrunner.h | 1 + 3 files changed, 267 insertions(+), 128 deletions(-) diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index 12a7d7d2a789..b2af6b30e532 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -87,7 +87,7 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) connect( mRunner, &QgsRStatsRunner::busyChanged, this, [ = ]( bool busy ) { - mInputEdit->setEnabled( !busy ); + //mInputEdit->setEnabled( !busy ); } ); setLayout( vl ); diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index 2182fae45800..fd6b3bc20ab6 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -1,17 +1,243 @@ #include "qgsrstatsrunner.h" #include "qgsapplication.h" -#include +#include #include +#include #include "qgisapp.h" #include "qgslogger.h" #include "qgsvariantutils.h" +#include "qgstaskmanager.h" +#include "qgsproxyprogresstask.h" #include #include #include #include + #include "qgsproviderregistry.h" +#include "qgsvectorlayerfeatureiterator.h" + + +class MapLayerWrapper +{ + public: + + MapLayerWrapper( const QgsMapLayer *layer = nullptr ) + : mLayerId( layer ? layer->id() : QString() ) + { + + } + + std::string id() const + { + return mLayerId.toStdString(); \ + } + + long long featureCount() const + { + long long res = -1; + auto countOnMainThread = [&res, this] + { + Q_ASSERT_X( QThread::currentThread() == qApp->thread(), "featureCount", "featureCount must be run on the main thread" ); + + if ( QgsMapLayer *layer = QgsProject::instance()->mapLayer( mLayerId ) ) + { + if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) ) + { + res = vl->featureCount(); + } + } + }; + + QMetaObject::invokeMethod( qApp, countOnMainThread, Qt::BlockingQueuedConnection ); + return res; + } + + Rcpp::DataFrame toDataFrame() const + { + Rcpp::DataFrame result = Rcpp::DataFrame(); + + bool prepared = false; + QgsFields fields; + long long featureCount = -1; + std::unique_ptr< QgsVectorLayerFeatureSource > source; + std::unique_ptr< QgsScopedProxyProgressTask > task; + auto prepareOnMainThread = [&prepared, &fields, &featureCount, &source, &task, this] + { + Q_ASSERT_X( QThread::currentThread() == qApp->thread(), "toDataFrame", "prepareOnMainThread must be run on the main thread" ); + + prepared = false; + if ( QgsMapLayer *layer = QgsProject::instance()->mapLayer( mLayerId ) ) + { + if ( QgsVectorLayer *vlayer = qobject_cast< QgsVectorLayer * >( layer ) ) + { + featureCount = vlayer->featureCount(); + fields = vlayer->fields(); + source = std::make_unique< QgsVectorLayerFeatureSource >( vlayer ); + } + } + prepared = true; + + task = std::make_unique< QgsScopedProxyProgressTask >( QObject::tr( "Creating R dataframe" ), true ); + }; + + QMetaObject::invokeMethod( qApp, prepareOnMainThread, Qt::BlockingQueuedConnection ); + if ( !prepared ) + return result; + + for ( const QgsField &field : fields ) + { + Rcpp::RObject column; + + switch ( field.type() ) + { + case QVariant::Bool: + { + column = Rcpp::LogicalVector( featureCount ); + break; + } + case QVariant::Int: + { + column = Rcpp::IntegerVector( featureCount ); + break; + } + case QVariant::Double: + { + column = Rcpp::DoubleVector( featureCount ); + break; + } + case QVariant::LongLong: + { + column = Rcpp::DoubleVector( featureCount ); + break; + } + case QVariant::String: + { + column = Rcpp::StringVector( featureCount ); + break; + } + + default: + continue; + } + + result.push_back( column, field.name().toStdString() ); + } + + QgsFeature feature; + QgsFeatureRequest req; + req.setFlags( QgsFeatureRequest::NoGeometry ); + QgsFeatureIterator it = source->getFeatures( req ); + std::size_t featureNumber = 0; + + while ( it.nextFeature( feature ) ) + { + task->setProgress( 100 * static_cast< double>( featureNumber ) / featureCount ); + if ( task->isCanceled() ) + break; + + int settingColumn = 0; + + for ( int i = 0; i < fields.count(); i ++ ) + { + QgsField field = fields.at( i ); + + switch ( field.type() ) + { + case QVariant::Bool: + { + Rcpp::LogicalVector column = result[settingColumn]; + column[featureNumber] = feature.attribute( i ).toBool(); + break; + } + case QVariant::Int: + { + Rcpp::IntegerVector column = result[settingColumn]; + column[featureNumber] = feature.attribute( i ).toInt(); + break; + } + case QVariant::LongLong: + { + Rcpp::DoubleVector column = result[settingColumn]; + bool ok; + double val = feature.attribute( i ).toDouble( &ok ); + if ( ok ) + column[featureNumber] = val; + else + column[featureNumber] = R_NaReal; + break; + } + case QVariant::Double: + { + Rcpp::DoubleVector column = result[settingColumn]; + column[featureNumber] = feature.attribute( i ).toDouble(); + break; + } + case QVariant::String: + { + Rcpp::StringVector column = result[settingColumn]; + column[featureNumber] = feature.attribute( i ).toString().toStdString(); + break; + } + + default: + continue; + } + settingColumn++; + } + featureNumber++; + } + return result; + } + + + private: + + QString mLayerId; + +}; + + +SEXP MapLayerWrapperId( Rcpp::XPtr obj ) +{ + return Rcpp::wrap( obj->id() ); +} + +SEXP MapLayerWrapperFeatureCount( Rcpp::XPtr obj ) +{ + return Rcpp::wrap( obj->featureCount() ); +} + +SEXP MapLayerWrapperToDataFrame( Rcpp::XPtr obj ) +{ + return obj->toDataFrame(); +} + + +SEXP MapLayerWrapperByName( std::string name ) +{ + QList< QgsMapLayer * > layers = QgsProject::instance()->mapLayersByName( QString::fromStdString( name ) ); + if ( !layers.empty() ) + return Rcpp::XPtr( new MapLayerWrapper( layers.at( 0 ) ) ); + + return nullptr; +} + + + +SEXP MapLayerWrapperDollar( Rcpp::XPtr obj, std::string name ) +{ + if ( name == "id" ) + { + return Rcpp::wrap( obj->id() ); + } + else + { + return NULL; + } +} + class QgsApplicationRWrapper { @@ -20,15 +246,20 @@ class QgsApplicationRWrapper int version() const { return Qgis::versionInt(); } - std::string activeLayer() const + SEXP activeLayer() const { - return QgisApp::instance()->activeLayer() ? - QgisApp::instance()->activeLayer()->name().toStdString() : std::string{}; - } + Rcpp::XPtr res( new MapLayerWrapper( QgisApp::instance()->activeLayer() ) ); + res.attr( "class" ) = "MapLayerWrapper"; + // res.attr( "id" ) = Rcpp::InternalFunction( & MapLayerWrapperId ); + //res.assign( Rcpp::InternalFunction( & MapLayerWrapperDollar ), "$.QGIS" ); + // res.attr( "axxx" ) = "QGIS"; + return res; + } }; + // The function which is called when running QGIS$... SEXP Dollar( Rcpp::XPtr obj, std::string name ) { @@ -38,7 +269,23 @@ SEXP Dollar( Rcpp::XPtr obj, std::string name ) } else if ( name == "activeLayer" ) { - return Rcpp::wrap( obj->activeLayer() ); + return obj->activeLayer(); + } + else if ( name == "mapLayerByName" ) + { + return Rcpp::InternalFunction( & MapLayerWrapperByName ); + } + else if ( name == "layerId" ) + { + return Rcpp::InternalFunction( & MapLayerWrapperId ); + } + else if ( name == "featureCount" ) + { + return Rcpp::InternalFunction( & MapLayerWrapperFeatureCount ); + } + else if ( name == "toDataFrame" ) + { + return Rcpp::InternalFunction( & MapLayerWrapperToDataFrame ); } else { @@ -53,6 +300,9 @@ Rcpp::CharacterVector Names( Rcpp::XPtr ) Rcpp::CharacterVector ret; ret.push_back( "versionInt" ); ret.push_back( "activeLayer" ); + ret.push_back( "layerId" ); + ret.push_back( "featureCount" ); + ret.push_back( "mapLayerByName" ); return ret; } @@ -92,126 +342,6 @@ Rcpp::NumericVector activeLayerNumericField( const std::string fieldName ) return result; } -Rcpp::DataFrame activeLayerTable() -{ - - Rcpp::DataFrame result = Rcpp::DataFrame(); - QgsMapLayer *layer = QgisApp::instance()->activeLayer(); - QgsVectorLayer *vlayer = qobject_cast( layer ); - - int featureCount = vlayer->dataProvider()->featureCount(); - - if ( !vlayer || ( featureCount < 1 ) ) - return result; - - QgsFields fields = vlayer->fields(); - - for ( int i = 0; i < fields.count(); i++ ) - { - - QgsField field = fields.at( i ); - Rcpp::RObject column; - bool addColumn = false; - - switch ( field.type() ) - { - case QVariant::Bool: - { - column = Rcpp::LogicalVector( featureCount ); - addColumn = true; - break; - } - case QVariant::Int: - { - column = Rcpp::IntegerVector( featureCount ); - addColumn = true; - break; - } - case QVariant::Double: - { - column = Rcpp::DoubleVector( featureCount ); - addColumn = true; - break; - } - case QVariant::LongLong: - { - column = Rcpp::DoubleVector( featureCount ); - addColumn = true; - break; - } - case QVariant::String: - { - column = Rcpp::StringVector( featureCount ); - addColumn = true; - break; - } - default: - break; - } - - if ( addColumn ) - result.push_back( column, field.name().toStdString() ); - } - - QgsFeature feature; - QgsFeatureIterator it = vlayer->dataProvider()->getFeatures( QgsFeatureRequest() ); - int featureNumber = 0; - - while ( it.nextFeature( feature ) ) - { - int settingColumn = 0; - - for ( int i = 0; i < fields.count(); i ++ ) - { - QgsField field = fields.at( i ); - - switch ( field.type() ) - { - case QVariant::Bool: - { - Rcpp::LogicalVector column = result[settingColumn]; - column[featureNumber] = feature.attribute( i ).toBool(); - break; - } - case QVariant::Int: - { - Rcpp::IntegerVector column = result[settingColumn]; - column[featureNumber] = feature.attribute( i ).toInt(); - break; - } - case QVariant::LongLong: - { - Rcpp::DoubleVector column = result[settingColumn]; - bool ok; - double val = feature.attribute( i ).toDouble( &ok ); - if ( ok ) - column[featureNumber] = val; - else - column[featureNumber] = R_NaReal; - break; - } - case QVariant::Double: - { - Rcpp::DoubleVector column = result[settingColumn]; - column[featureNumber] = feature.attribute( i ).toDouble(); - break; - } - case QVariant::String: - { - Rcpp::StringVector column = result[settingColumn]; - column[featureNumber] = feature.attribute( i ).toString().toStdString(); - break; - } - default: - break; - } - settingColumn++; - } - featureNumber++; - } - return result; -} - SEXP activeLayerToSf() { @@ -241,7 +371,7 @@ SEXP activeLayerToSf() // QgsRStatsSession::QgsRStatsSession() { - mRSession = std::make_unique< RInside >( 0, nullptr, false, false, true ); + mRSession = std::make_unique< RInside >( 0, nullptr, true, false, true ); mRSession->set_callbacks( this ); const QString userPath = QgsApplication::qgisSettingsDirPath() + QStringLiteral( "r_libs" ); @@ -256,9 +386,17 @@ QgsRStatsSession::QgsRStatsSession() mRSession->assign( wr, "QGIS" ); mRSession->assign( Rcpp::InternalFunction( & Dollar ), "$.QGIS" ); mRSession->assign( Rcpp::InternalFunction( & Names ), "names.QGIS" ); + /* mRSession->assign( Rcpp::InternalFunction( & activeLayerNumericField ), "activeLayerNumericField" ); mRSession->assign( Rcpp::InternalFunction( & activeLayerTable ), "activeLayerTable" ); mRSession->assign( Rcpp::InternalFunction( & activeLayerToSf ), "readActiveLayerToSf" ); + */ + + + + QString error; + + //execCommandPrivate( QStringLiteral( "loadModule(\"QgsRLayerWrapper\", TRUE)" ), error ); //( *mRSession )["val"] = 5; //mRSession->parseEvalQ( "val2<-7" ); diff --git a/src/app/rstats/qgsrstatsrunner.h b/src/app/rstats/qgsrstatsrunner.h index cf855d3b5523..05f7b2260a24 100644 --- a/src/app/rstats/qgsrstatsrunner.h +++ b/src/app/rstats/qgsrstatsrunner.h @@ -25,6 +25,7 @@ #include "qgis_app.h" + class RInside; class QVariant; class QString; From 33b4f704d7e9453fd04ed9cc5318e690d9f52a51 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 8 Oct 2022 10:46:22 +1000 Subject: [PATCH 34/42] Minor optimisations --- src/app/rstats/qgsrstatsrunner.cpp | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index fd6b3bc20ab6..1b0cc631de1d 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -86,9 +86,11 @@ class MapLayerWrapper if ( !prepared ) return result; - for ( const QgsField &field : fields ) + QList< int > attributesToFetch; + for ( int index = 0; index < fields.count(); ++index ) { Rcpp::RObject column; + const QgsField field = fields.at( index ); switch ( field.type() ) { @@ -123,23 +125,35 @@ class MapLayerWrapper } result.push_back( column, field.name().toStdString() ); + attributesToFetch.append( index ); } QgsFeature feature; QgsFeatureRequest req; req.setFlags( QgsFeatureRequest::NoGeometry ); + req.setSubsetOfAttributes( attributesToFetch ); QgsFeatureIterator it = source->getFeatures( req ); std::size_t featureNumber = 0; + int prevProgress = 0; while ( it.nextFeature( feature ) ) { - task->setProgress( 100 * static_cast< double>( featureNumber ) / featureCount ); + const int progress = 100 * static_cast< double>( featureNumber ) / featureCount; + if ( progress > prevProgress ) + { + task->setProgress( progress ); + prevProgress = progress; + } + if ( task->isCanceled() ) break; int settingColumn = 0; - for ( int i = 0; i < fields.count(); i ++ ) + const QgsAttributes attributes = feature.attributes(); + const QVariant *attributeData = attributes.constData(); + + for ( int i = 0; i < fields.count(); i ++, attributeData++ ) { QgsField field = fields.at( i ); @@ -148,20 +162,20 @@ class MapLayerWrapper case QVariant::Bool: { Rcpp::LogicalVector column = result[settingColumn]; - column[featureNumber] = feature.attribute( i ).toBool(); + column[featureNumber] = attributeData->toBool(); break; } case QVariant::Int: { Rcpp::IntegerVector column = result[settingColumn]; - column[featureNumber] = feature.attribute( i ).toInt(); + column[featureNumber] = attributeData->toInt(); break; } case QVariant::LongLong: { Rcpp::DoubleVector column = result[settingColumn]; bool ok; - double val = feature.attribute( i ).toDouble( &ok ); + double val = attributeData->toDouble( &ok ); if ( ok ) column[featureNumber] = val; else @@ -171,13 +185,13 @@ class MapLayerWrapper case QVariant::Double: { Rcpp::DoubleVector column = result[settingColumn]; - column[featureNumber] = feature.attribute( i ).toDouble(); + column[featureNumber] = attributeData->toDouble(); break; } case QVariant::String: { Rcpp::StringVector column = result[settingColumn]; - column[featureNumber] = feature.attribute( i ).toString().toStdString(); + column[featureNumber] = attributeData->toString().toStdString(); break; } From 7850f7bfb1de396f9ce71b9c3adea5c4c9bfa2c4 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 8 Oct 2022 10:47:31 +1000 Subject: [PATCH 35/42] [ogr] Optimise attribute population during feature iteration Shaves a few percentage points off the execution time when iterating over OGR layers --- .../providers/ogr/qgsogrfeatureiterator.cpp | 45 +++++++++++++------ .../providers/ogr/qgsogrfeatureiterator.h | 4 +- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/core/providers/ogr/qgsogrfeatureiterator.cpp b/src/core/providers/ogr/qgsogrfeatureiterator.cpp index 5f248ce92890..9b793e95a0ac 100644 --- a/src/core/providers/ogr/qgsogrfeatureiterator.cpp +++ b/src/core/providers/ogr/qgsogrfeatureiterator.cpp @@ -272,6 +272,13 @@ QgsOgrFeatureIterator::QgsOgrFeatureIterator( QgsOgrFeatureSource *source, bool OGR_L_SetAttributeFilter( mOgrLayer, nullptr ); } + if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes ) + { + const QgsAttributeList attrs = mRequest.subsetOfAttributes(); + mRequestAttributes = QVector< int >( attrs.begin(), attrs.end() ); + std::sort( mRequestAttributes.begin(), mRequestAttributes.end() ); + } + //start with first feature rewind(); @@ -529,27 +536,21 @@ bool QgsOgrFeatureIterator::close() } -void QgsOgrFeatureIterator::getFeatureAttribute( OGRFeatureH ogrFet, QgsFeature &f, int attindex ) const +QVariant QgsOgrFeatureIterator::getFeatureAttribute( OGRFeatureH ogrFet, int attindex ) const { if ( mFirstFieldIsFid && attindex == 0 ) { - f.setAttribute( 0, static_cast( OGR_F_GetFID( ogrFet ) ) ); - return; + return static_cast( OGR_F_GetFID( ogrFet ) ); } int attindexWithoutFid = ( mFirstFieldIsFid ) ? attindex - 1 : attindex; bool ok = false; - QVariant value = QgsOgrUtils::getOgrFeatureAttribute( ogrFet, mFieldsWithoutFid, attindexWithoutFid, mSource->mEncoding, &ok ); - if ( !ok ) - return; - - f.setAttribute( attindex, value ); + return QgsOgrUtils::getOgrFeatureAttribute( ogrFet, mFieldsWithoutFid, attindexWithoutFid, mSource->mEncoding, &ok ); } bool QgsOgrFeatureIterator::readFeature( const gdal::ogr_feature_unique_ptr &fet, QgsFeature &feature ) const { feature.setId( OGR_F_GetFID( fet.get() ) ); - feature.initAttributes( mSource->mFields.count() ); feature.setFields( mSource->mFields ); // allow name-based attribute lookups const bool useExactIntersect = mRequest.spatialFilterType() == Qgis::SpatialFilterType::BoundingBox && ( mRequest.flags() & QgsFeatureRequest::ExactIntersect ); @@ -596,23 +597,39 @@ bool QgsOgrFeatureIterator::readFeature( const gdal::ogr_feature_unique_ptr &fet } // fetch attributes + const int fieldCount = mSource->mFields.count(); + QgsAttributes attributes( fieldCount ); + QVariant *attributeData = attributes.data(); if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes ) { - QgsAttributeList attrs = mRequest.subsetOfAttributes(); - for ( QgsAttributeList::const_iterator it = attrs.constBegin(); it != attrs.constEnd(); ++it ) + const int requestedAttributeTotal = mRequestAttributes.size(); + int fetchedAttributes = 0; + if ( requestedAttributeTotal > 0 ) { - getFeatureAttribute( fet.get(), feature, *it ); + const int *requestAttribute = mRequestAttributes.constData(); + for ( int idx = 0; idx < fieldCount; ++idx ) + { + if ( *requestAttribute == idx ) + { + *attributeData = getFeatureAttribute( fet.get(), idx ); + fetchedAttributes++; + if ( fetchedAttributes == requestedAttributeTotal ) + break; + requestAttribute++; + } + attributeData++; + } } } else { // all attributes - const auto fieldCount = mSource->mFields.count(); for ( int idx = 0; idx < fieldCount; ++idx ) { - getFeatureAttribute( fet.get(), feature, idx ); + *attributeData++ = getFeatureAttribute( fet.get(), idx ); } } + feature.setAttributes( attributes ); if ( mRequest.flags() & QgsFeatureRequest::EmbeddedSymbols ) { diff --git a/src/core/providers/ogr/qgsogrfeatureiterator.h b/src/core/providers/ogr/qgsogrfeatureiterator.h index 059fb3a7efb9..5a69b9abfd04 100644 --- a/src/core/providers/ogr/qgsogrfeatureiterator.h +++ b/src/core/providers/ogr/qgsogrfeatureiterator.h @@ -87,7 +87,7 @@ class QgsOgrFeatureIterator final: public QgsAbstractFeatureIteratorFromSource mDistanceWithinEngine; + QVector< int > mRequestAttributes; + bool fetchFeatureWithId( QgsFeatureId id, QgsFeature &feature ) const; void resetReading(); From 766ee5062ba444edcd506dffa5971999a8d855d4 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 8 Oct 2022 15:50:19 +1000 Subject: [PATCH 36/42] Add "selectedOnly" argument to QGIS$toDataFrame This involves quite a dance -- we can't have optional arguments for cpp functions exposed via InternalFunction, so now we have to create pure R wrappers for the exposed functions instead and set the default values for optional arguments in those. The nice thing is that this gives us the opportunity to add R "sugar" to our functions so that they behave more like standard R modules (eg we could add documentation for them) --- src/app/rstats/qgsrstatsrunner.cpp | 50 ++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index 1b0cc631de1d..481a7bff16de 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -54,7 +54,7 @@ class MapLayerWrapper return res; } - Rcpp::DataFrame toDataFrame() const + Rcpp::DataFrame toDataFrame( bool selectedOnly ) const { Rcpp::DataFrame result = Rcpp::DataFrame(); @@ -63,7 +63,8 @@ class MapLayerWrapper long long featureCount = -1; std::unique_ptr< QgsVectorLayerFeatureSource > source; std::unique_ptr< QgsScopedProxyProgressTask > task; - auto prepareOnMainThread = [&prepared, &fields, &featureCount, &source, &task, this] + QgsFeatureIds selectedFeatureIds; + auto prepareOnMainThread = [&prepared, &fields, &featureCount, &source, &task, selectedOnly, &selectedFeatureIds, this] { Q_ASSERT_X( QThread::currentThread() == qApp->thread(), "toDataFrame", "prepareOnMainThread must be run on the main thread" ); @@ -72,9 +73,17 @@ class MapLayerWrapper { if ( QgsVectorLayer *vlayer = qobject_cast< QgsVectorLayer * >( layer ) ) { - featureCount = vlayer->featureCount(); fields = vlayer->fields(); source = std::make_unique< QgsVectorLayerFeatureSource >( vlayer ); + if ( selectedOnly ) + { + selectedFeatureIds = vlayer->selectedFeatureIds(); + featureCount = selectedFeatureIds.size(); + } + else + { + featureCount = vlayer->featureCount(); + } } } prepared = true; @@ -128,10 +137,16 @@ class MapLayerWrapper attributesToFetch.append( index ); } + if ( selectedOnly && selectedFeatureIds.empty() ) + return result; + QgsFeature feature; QgsFeatureRequest req; req.setFlags( QgsFeatureRequest::NoGeometry ); req.setSubsetOfAttributes( attributesToFetch ); + if ( selectedOnly ) + req.setFilterFids( selectedFeatureIds ); + QgsFeatureIterator it = source->getFeatures( req ); std::size_t featureNumber = 0; @@ -223,9 +238,9 @@ SEXP MapLayerWrapperFeatureCount( Rcpp::XPtr obj ) return Rcpp::wrap( obj->featureCount() ); } -SEXP MapLayerWrapperToDataFrame( Rcpp::XPtr obj ) +SEXP MapLayerWrapperToDataFrame( Rcpp::XPtr obj, bool selectedOnly ) { - return obj->toDataFrame(); + return obj->toDataFrame( selectedOnly ); } @@ -396,10 +411,26 @@ QgsRStatsSession::QgsRStatsSession() execCommandNR( QStringLiteral( ".libPaths(\"%1\")" ).arg( userPath ) ); Rcpp::XPtr wr( new QgsApplicationRWrapper() ); - wr.attr( "class" ) = "QGIS"; - mRSession->assign( wr, "QGIS" ); - mRSession->assign( Rcpp::InternalFunction( & Dollar ), "$.QGIS" ); - mRSession->assign( Rcpp::InternalFunction( & Names ), "names.QGIS" ); + wr.attr( "class" ) = ".QGISPrivate"; + mRSession->assign( wr, ".QGISPrivate" ); + mRSession->assign( Rcpp::InternalFunction( & Dollar ), "$..QGISPrivate" ); + + QString error; + execCommandPrivate( QStringLiteral( R"""( + QGIS <- list( + toDataFrame=function(layer, selectedOnly=FALSE) { .QGISPrivate$toDataFrame(layer, selectedOnly) }, + versionInt=function() { .QGISPrivate$versionInt }, + mapLayerByName=function(name) { .QGISPrivate$mapLayerByName(name) }, + activeLayer=function() { .QGISPrivate$activeLayer } + ) + class(QGIS) <- "QGIS" + )""" ), error ); + + if ( !error.isEmpty() ) + { + QgsDebugMsg( error ); + } + /* mRSession->assign( Rcpp::InternalFunction( & activeLayerNumericField ), "activeLayerNumericField" ); mRSession->assign( Rcpp::InternalFunction( & activeLayerTable ), "activeLayerTable" ); @@ -517,6 +548,7 @@ QVariant QgsRStatsSession::sexpToVariant( const SEXP exp ) case SYMSXP: case EXTPTRSXP: case CLOSXP: + case ENVSXP: // not safe to call LENGTH on! return QVariant(); From 96017ba0e34315e3db9bc700c0ca318b7e9690ea Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 8 Oct 2022 16:08:25 +1000 Subject: [PATCH 37/42] Scroll to end of output automatically --- src/app/rstats/qgsrstatsconsole.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index b2af6b30e532..617b3e627519 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -64,12 +64,14 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) return; mOutput->append( ( mOutput->text().isEmpty() ? QString() : QString( '\n' ) ) + QStringLiteral( "> " ) + command ); + mOutput->moveCursorToEnd(); mRunner->execCommand( command ); } ); connect( mRunner, &QgsRStatsRunner::errorOccurred, this, [ = ]( const QString & error ) { mOutput->append( ( mOutput->text().isEmpty() ? QString() : QString( '\n' ) ) + error ); + mOutput->moveCursorToEnd(); } ); connect( mRunner, &QgsRStatsRunner::consoleMessage, this, [ = ]( const QString & message, int type ) @@ -78,11 +80,13 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) mOutput->append( ( mOutput->text().isEmpty() ? QString() : QString( '\n' ) ) + message ); else // TODO should we format errors differently? mOutput->append( ( mOutput->text().isEmpty() ? QString() : QString( '\n' ) ) + message ); + mOutput->moveCursorToEnd(); } ); connect( mRunner, &QgsRStatsRunner::showMessage, this, [ = ]( const QString & message ) { mOutput->append( ( mOutput->text().isEmpty() ? QString() : QString( '\n' ) ) + message ); + mOutput->moveCursorToEnd(); } ); connect( mRunner, &QgsRStatsRunner::busyChanged, this, [ = ]( bool busy ) From b32e3aa6bee2670589cac3b899a4419bee98f242 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 8 Oct 2022 16:08:33 +1000 Subject: [PATCH 38/42] Cleanups --- src/app/rstats/qgsrstatsrunner.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index 481a7bff16de..c84e19db16be 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -433,26 +433,8 @@ QgsRStatsSession::QgsRStatsSession() /* mRSession->assign( Rcpp::InternalFunction( & activeLayerNumericField ), "activeLayerNumericField" ); - mRSession->assign( Rcpp::InternalFunction( & activeLayerTable ), "activeLayerTable" ); mRSession->assign( Rcpp::InternalFunction( & activeLayerToSf ), "readActiveLayerToSf" ); */ - - - - QString error; - - //execCommandPrivate( QStringLiteral( "loadModule(\"QgsRLayerWrapper\", TRUE)" ), error ); - -//( *mRSession )["val"] = 5; -//mRSession->parseEvalQ( "val2<-7" ); - -// double aDouble = Rcpp::as( mRSession->parseEval( "1+2" ) ); -// std::string aString = Rcpp::as( mRSession->parseEval( "'asdasdas'" ) ); - -// R.parseEvalQ( "cat(txt)" ); -// QgsDebugMsg( QString::fromStdString( Rcpp::as( R.parseEval( "cat(txt)" ) ) ) ); -// QgsDebugMsg( QString::fromStdString( Rcpp::as( mRSession->parseEval( "as.character(val+2)" ) ) ) ); - // QgsDebugMsg( QStringLiteral( "val as double: %1" ).arg( Rcpp::as( mRSession->parseEval( "val+val2" ) ) ) ); } void QgsRStatsSession::showStartupMessage() From 0e350cd0fb1c70b47b69b4b579c2b61b98ae1cc9 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 8 Oct 2022 16:19:23 +1000 Subject: [PATCH 39/42] Adapt activeLayerNumericField and readActiveLayerToSF to generic thread-safe methods Now available as: - QGIS$toNumericVector(layer, field, selectedOnly) - QGIS$toSf(layer) --- src/app/rstats/qgsrstatsrunner.cpp | 208 ++++++++++++++++++++--------- 1 file changed, 144 insertions(+), 64 deletions(-) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index c84e19db16be..ca44d3cf96dc 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -220,6 +220,127 @@ class MapLayerWrapper return result; } + Rcpp::NumericVector toNumericVector( const std::string &fieldName, bool selectedOnly ) + { + Rcpp::NumericVector result; + + bool prepared = false; + QgsFields fields; + long long featureCount = -1; + std::unique_ptr< QgsVectorLayerFeatureSource > source; + std::unique_ptr< QgsScopedProxyProgressTask > task; + QgsFeatureIds selectedFeatureIds; + + auto prepareOnMainThread = [&prepared, &fields, &featureCount, &source, &task, selectedOnly, &selectedFeatureIds, this] + { + Q_ASSERT_X( QThread::currentThread() == qApp->thread(), "toDataFrame", "prepareOnMainThread must be run on the main thread" ); + + prepared = false; + if ( QgsMapLayer *layer = QgsProject::instance()->mapLayer( mLayerId ) ) + { + if ( QgsVectorLayer *vlayer = qobject_cast< QgsVectorLayer * >( layer ) ) + { + fields = vlayer->fields(); + source = std::make_unique< QgsVectorLayerFeatureSource >( vlayer ); + if ( selectedOnly ) + { + selectedFeatureIds = vlayer->selectedFeatureIds(); + featureCount = selectedFeatureIds.size(); + } + else + { + featureCount = vlayer->featureCount(); + } + } + } + prepared = true; + + task = std::make_unique< QgsScopedProxyProgressTask >( QObject::tr( "Creating R dataframe" ), true ); + }; + + QMetaObject::invokeMethod( qApp, prepareOnMainThread, Qt::BlockingQueuedConnection ); + if ( !prepared ) + return result; + + const int fieldIndex = fields.lookupField( QString::fromStdString( fieldName ) ); + if ( fieldIndex < 0 ) + return result; + + const QgsField field = fields.at( fieldIndex ); + if ( !( field.type() == QVariant::Double || field.type() == QVariant::Int ) ) + return result; + + result = Rcpp::NumericVector( featureCount, 0 ); + if ( selectedOnly && selectedFeatureIds.empty() ) + return result; + + std::size_t i = 0; + + QgsFeature feature; + QgsFeatureRequest req; + req.setFlags( QgsFeatureRequest::NoGeometry ); + req.setSubsetOfAttributes( {fieldIndex} ); + if ( selectedOnly ) + req.setFilterFids( selectedFeatureIds ); + + QgsFeatureIterator it = source->getFeatures( req ); + + int prevProgress = 0; + while ( it.nextFeature( feature ) ) + { + const int progress = 100 * static_cast< double>( i ) / featureCount; + if ( progress > prevProgress ) + { + task->setProgress( progress ); + prevProgress = progress; + } + + if ( task->isCanceled() ) + break; + + result[i] = feature.attribute( fieldIndex ).toDouble(); + i++; + } + + return result; + } + + SEXP toSf() + { + bool prepared = false; + QString path; + QString layerName; + auto prepareOnMainThread = [&prepared, &path, &layerName, this] + { + Q_ASSERT_X( QThread::currentThread() == qApp->thread(), "toSf", "prepareOnMainThread must be run on the main thread" ); + + prepared = false; + if ( QgsMapLayer *layer = QgsProject::instance()->mapLayer( mLayerId ) ) + { + if ( QgsVectorLayer *vlayer = qobject_cast< QgsVectorLayer * >( layer ) ) + { + if ( vlayer->dataProvider()->name() != QStringLiteral( "ogr" ) ) + return; + + const QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( layer->dataProvider()->name(), layer->source() ); + path = parts[ QStringLiteral( "path" ) ].toString(); + layerName = parts[ QStringLiteral( "layerName" ) ].toString(); + prepared = true; + } + } + }; + + QMetaObject::invokeMethod( qApp, prepareOnMainThread, Qt::BlockingQueuedConnection ); + if ( !prepared ) + return R_NilValue; + + if ( path.isEmpty() ) + return R_NilValue; + + Rcpp::Function st_read( "st_read" ); + + return st_read( path.toStdString(), layerName.toStdString() ); + } private: @@ -243,6 +364,15 @@ SEXP MapLayerWrapperToDataFrame( Rcpp::XPtr obj, bool selectedO return obj->toDataFrame( selectedOnly ); } +SEXP MapLayerWrapperToNumericVector( Rcpp::XPtr obj, const std::string &field, bool selectedOnly ) +{ + return obj->toNumericVector( field, selectedOnly ); +} + +SEXP MapLayerWrapperToSf( Rcpp::XPtr obj ) +{ + return obj->toSf(); +} SEXP MapLayerWrapperByName( std::string name ) { @@ -316,6 +446,14 @@ SEXP Dollar( Rcpp::XPtr obj, std::string name ) { return Rcpp::InternalFunction( & MapLayerWrapperToDataFrame ); } + else if ( name == "toNumericVector" ) + { + return Rcpp::InternalFunction( & MapLayerWrapperToNumericVector ); + } + else if ( name == "toSf" ) + { + return Rcpp::InternalFunction( & MapLayerWrapperToSf ); + } else { return NULL; @@ -332,68 +470,13 @@ Rcpp::CharacterVector Names( Rcpp::XPtr ) ret.push_back( "layerId" ); ret.push_back( "featureCount" ); ret.push_back( "mapLayerByName" ); + ret.push_back( "toDataFrame" ); + ret.push_back( "toNumericVector" ); + ret.push_back( "toSf" ); return ret; } -Rcpp::NumericVector activeLayerNumericField( const std::string fieldName ) -{ - Rcpp::NumericVector result; - - QgsMapLayer *layer = QgisApp::instance()->activeLayer(); - QgsVectorLayer *vlayer = qobject_cast( layer ); - - if ( !vlayer || ( vlayer->dataProvider()->featureCount() < 1 ) ) - return result; - - int fieldIndex = vlayer->fields().lookupField( QString::fromStdString( fieldName ) ); - - if ( fieldIndex < 0 ) - return result; - - QgsField field = vlayer->fields().field( fieldIndex ); - - if ( !( field.type() == QVariant::Double || field.type() == QVariant::Int ) ) - return result; - - result = Rcpp::NumericVector( vlayer->dataProvider()->featureCount(), 0 ); - - QgsFeature feature; - - int i = 0; - QgsFeatureIterator it = vlayer->dataProvider()->getFeatures( QgsFeatureRequest() ); - - while ( it.nextFeature( feature ) ) - { - result[i] = feature.attribute( fieldIndex ).toDouble(); - i++; - } - - return result; -} - -SEXP activeLayerToSf() -{ - - QgsMapLayer *layer = QgisApp::instance()->activeLayer(); - QgsVectorLayer *vlayer = qobject_cast( layer ); - - if ( !vlayer ) - return R_NilValue; - if ( vlayer->dataProvider()->name() != QStringLiteral( "ogr" ) ) - return R_NilValue; - - const QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( layer->dataProvider()->name(), layer->source() ); - std::string path = parts[ QStringLiteral( "path" ) ].toString().toStdString(); - std::string layerName = parts[ QStringLiteral( "layerName" ) ].toString().toStdString(); - - if ( path.empty() ) - return R_NilValue; - - Rcpp::Function st_read( "st_read" ); - - return st_read( path, layerName ); -} // // QgsRStatsSession @@ -418,10 +501,12 @@ QgsRStatsSession::QgsRStatsSession() QString error; execCommandPrivate( QStringLiteral( R"""( QGIS <- list( - toDataFrame=function(layer, selectedOnly=FALSE) { .QGISPrivate$toDataFrame(layer, selectedOnly) }, versionInt=function() { .QGISPrivate$versionInt }, mapLayerByName=function(name) { .QGISPrivate$mapLayerByName(name) }, activeLayer=function() { .QGISPrivate$activeLayer } + toDataFrame=function(layer, selectedOnly=FALSE) { .QGISPrivate$toDataFrame(layer, selectedOnly) }, + toNumericVector=function(layer, field, selectedOnly=FALSE) { .QGISPrivate$toNumericVector(layer, field, selectedOnly) }, + toSf=function(layer) { .QGISPrivate$toSf(layer) }, ) class(QGIS) <- "QGIS" )""" ), error ); @@ -430,11 +515,6 @@ QgsRStatsSession::QgsRStatsSession() { QgsDebugMsg( error ); } - - /* - mRSession->assign( Rcpp::InternalFunction( & activeLayerNumericField ), "activeLayerNumericField" ); - mRSession->assign( Rcpp::InternalFunction( & activeLayerToSf ), "readActiveLayerToSf" ); - */ } void QgsRStatsSession::showStartupMessage() From f152959e6b28fd551e782c88eb5ceafe71c0c1f8 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 11 Oct 2022 08:50:38 +1000 Subject: [PATCH 40/42] Fix some crashes --- src/app/rstats/qgsrstatsconsole.cpp | 10 +++++++-- src/app/rstats/qgsrstatsconsole.h | 2 ++ src/app/rstats/qgsrstatsrunner.cpp | 32 +++++++++++++++++++++++++---- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/app/rstats/qgsrstatsconsole.cpp b/src/app/rstats/qgsrstatsconsole.cpp index 617b3e627519..67cc35080e42 100644 --- a/src/app/rstats/qgsrstatsconsole.cpp +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -63,6 +63,7 @@ QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) if ( mRunner->busy() ) return; + mInputEdit->clear(); mOutput->append( ( mOutput->text().isEmpty() ? QString() : QString( '\n' ) ) + QStringLiteral( "> " ) + command ); mOutput->moveCursorToEnd(); mRunner->execCommand( command ); @@ -112,6 +113,12 @@ QgsInteractiveRWidget::QgsInteractiveRWidget( QWidget *parent ) QgsInteractiveRWidget::initializeLexer(); } +void QgsInteractiveRWidget::clear() +{ + QgsCodeEditorR::clear(); + displayPrompt( false ); +} + void QgsInteractiveRWidget::keyPressEvent( QKeyEvent *event ) { switch ( event->key() ) @@ -119,8 +126,7 @@ void QgsInteractiveRWidget::keyPressEvent( QKeyEvent *event ) case Qt::Key_Return: case Qt::Key_Enter: emit runCommand( text() ); - clear(); - displayPrompt( false ); + break; default: diff --git a/src/app/rstats/qgsrstatsconsole.h b/src/app/rstats/qgsrstatsconsole.h index b96fea3aeb6b..3de4c93bda3b 100644 --- a/src/app/rstats/qgsrstatsconsole.h +++ b/src/app/rstats/qgsrstatsconsole.h @@ -33,6 +33,8 @@ class QgsInteractiveRWidget : public QgsCodeEditorR QgsInteractiveRWidget( QWidget *parent = nullptr ); + void clear() override; + signals: void runCommand( const QString &command ); diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index ca44d3cf96dc..cb3c011afcf0 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -564,6 +564,8 @@ QString QgsRStatsSession::sexpToString( const SEXP exp ) case ENVSXP: case LANGSXP: case S4SXP: + case PROMSXP: + case DOTSXP: // these types can't be converted to StringVector, will raise exceptions return QString(); @@ -605,20 +607,35 @@ QVariant QgsRStatsSession::sexpToVariant( const SEXP exp ) { switch ( TYPEOF( exp ) ) { + // these types are not safe to call LENGTH on, and don't make sense to convert to a variant anyway case S4SXP: case LANGSXP: case SYMSXP: case EXTPTRSXP: case CLOSXP: case ENVSXP: - // not safe to call LENGTH on! + case PROMSXP: + case DOTSXP: + case BCODESXP: + case WEAKREFSXP: + case 26: // ??? return QVariant(); + // confirmed safe types, handled in depth below + case NILSXP: + case LGLSXP: + case INTSXP: + case REALSXP: + case STRSXP: + case CHARSXP: + case EXPRSXP: + break; + default: + QgsDebugMsg( QStringLiteral( "Trying to convert potentially unsafe SEXP type %1 to variant... watch out!" ).arg( TYPEOF( exp ) ) ); break; } - QgsDebugMsg( QStringLiteral( "Handing type: %1" ).arg( TYPEOF( exp ) ) ); const int length = LENGTH( exp ); if ( length == 0 ) { @@ -733,10 +750,17 @@ QVariant QgsRStatsSession::sexpToVariant( const SEXP exp ) // return R::rawPointer( exp ); case EXPRSXP: + // we don't have any variant type which matches this one + return QVariant(); + + case S4SXP: + case LANGSXP: + case SYMSXP: + case EXTPTRSXP: case CLOSXP: case ENVSXP: - case LANGSXP: - // these types can't possibly be converted to variants + case PROMSXP: + // unreachable, handled earlier return QVariant(); default: From d8f422d56f49ad8c9b4062d20ebdd4df7f37b604 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 11 Oct 2022 08:50:44 +1000 Subject: [PATCH 41/42] Fix initialization --- src/app/rstats/qgsrstatsrunner.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index cb3c011afcf0..9f74cd05b231 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -503,10 +503,10 @@ QgsRStatsSession::QgsRStatsSession() QGIS <- list( versionInt=function() { .QGISPrivate$versionInt }, mapLayerByName=function(name) { .QGISPrivate$mapLayerByName(name) }, - activeLayer=function() { .QGISPrivate$activeLayer } + activeLayer=function() { .QGISPrivate$activeLayer }, toDataFrame=function(layer, selectedOnly=FALSE) { .QGISPrivate$toDataFrame(layer, selectedOnly) }, toNumericVector=function(layer, field, selectedOnly=FALSE) { .QGISPrivate$toNumericVector(layer, field, selectedOnly) }, - toSf=function(layer) { .QGISPrivate$toSf(layer) }, + toSf=function(layer) { .QGISPrivate$toSf(layer) } ) class(QGIS) <- "QGIS" )""" ), error ); From 709197643dc5d4f544a4f0bc16ee5401789e0bbe Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Tue, 11 Oct 2022 14:15:45 +0200 Subject: [PATCH 42/42] function toRaster() --- src/app/rstats/qgsrstatsrunner.cpp | 125 ++++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 2 deletions(-) diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp index 9f74cd05b231..a6a7f053f32d 100644 --- a/src/app/rstats/qgsrstatsrunner.cpp +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -17,7 +17,7 @@ #include "qgsproviderregistry.h" #include "qgsvectorlayerfeatureiterator.h" - +#include "qgsrasterlayer.h" class MapLayerWrapper { @@ -342,6 +342,94 @@ class MapLayerWrapper return st_read( path.toStdString(), layerName.toStdString() ); } + Rcpp::LogicalVector isVectorLayer() + { + bool prepared; + bool isVectorLayer = false; + + auto prepareOnMainThread = [&isVectorLayer, &prepared, this] + { + Q_ASSERT_X( QThread::currentThread() == qApp->thread(), "isVectorLayer", "prepareOnMainThread must be run on the main thread" ); + + prepared = false; + if ( QgsMapLayer *layer = QgsProject::instance()->mapLayer( mLayerId ) ) + { + if ( QgsVectorLayer *vlayer = qobject_cast< QgsVectorLayer * >( layer ) ) + { + isVectorLayer = true; + } + } + prepared = true; + }; + + QMetaObject::invokeMethod( qApp, prepareOnMainThread, Qt::BlockingQueuedConnection ); + if ( !prepared ) + return false; + + return isVectorLayer; + } + + Rcpp::LogicalVector isRasterLayer() + { + bool prepared; + bool isRasterLayer = false; + + auto prepareOnMainThread = [&isRasterLayer, &prepared, this] + { + Q_ASSERT_X( QThread::currentThread() == qApp->thread(), "isRasterLayer", "prepareOnMainThread must be run on the main thread" ); + + prepared = false; + if ( QgsMapLayer *layer = QgsProject::instance()->mapLayer( mLayerId ) ) + { + if ( QgsRasterLayer *rlayer = qobject_cast< QgsRasterLayer * >( layer ) ) + { + isRasterLayer = true; + } + } + prepared = true; + }; + + QMetaObject::invokeMethod( qApp, prepareOnMainThread, Qt::BlockingQueuedConnection ); + if ( !prepared ) + return false; + + return isRasterLayer; + } + + SEXP toRaster() + { + if ( ! this->isRasterLayer()( 0 ) ) + return R_NilValue; + + bool prepared = false; + QString rasterPath; + + auto prepareOnMainThread = [&rasterPath, &prepared, this] + { + Q_ASSERT_X( QThread::currentThread() == qApp->thread(), "isRasterLayer", "prepareOnMainThread must be run on the main thread" ); + + if ( QgsMapLayer *layer = QgsProject::instance()->mapLayer( mLayerId ) ) + { + if ( QgsRasterLayer *rlayer = qobject_cast< QgsRasterLayer * >( layer ) ) + { + rasterPath = rlayer->dataProvider()->dataSourceUri(); + } + } + prepared = true; + }; + + QMetaObject::invokeMethod( qApp, prepareOnMainThread, Qt::BlockingQueuedConnection ); + if ( !prepared ) + return R_NilValue; + + if ( rasterPath.isEmpty() ) + return R_NilValue; + + Rcpp::Function raster( "raster" ); + + return raster( rasterPath.toStdString() ); + } + private: QString mLayerId; @@ -374,6 +462,21 @@ SEXP MapLayerWrapperToSf( Rcpp::XPtr obj ) return obj->toSf(); } +SEXP MapLayerWrapperToRaster( Rcpp::XPtr obj ) +{ + return obj->toRaster(); +} + +SEXP MapLayerWrapperIsVectorLayer( Rcpp::XPtr obj ) +{ + return obj->isVectorLayer(); +} + +SEXP MapLayerWrapperIsRasterLayer( Rcpp::XPtr obj ) +{ + return obj->isRasterLayer(); +} + SEXP MapLayerWrapperByName( std::string name ) { QList< QgsMapLayer * > layers = QgsProject::instance()->mapLayersByName( QString::fromStdString( name ) ); @@ -450,10 +553,22 @@ SEXP Dollar( Rcpp::XPtr obj, std::string name ) { return Rcpp::InternalFunction( & MapLayerWrapperToNumericVector ); } + else if ( name == "isVectorLayer" ) + { + return Rcpp::InternalFunction( & MapLayerWrapperIsVectorLayer ); + } + else if ( name == "isRasterLayer" ) + { + return Rcpp::InternalFunction( & MapLayerWrapperIsRasterLayer ); + } else if ( name == "toSf" ) { return Rcpp::InternalFunction( & MapLayerWrapperToSf ); } + else if ( name == "toRaster" ) + { + return Rcpp::InternalFunction( & MapLayerWrapperToRaster ); + } else { return NULL; @@ -473,6 +588,9 @@ Rcpp::CharacterVector Names( Rcpp::XPtr ) ret.push_back( "toDataFrame" ); ret.push_back( "toNumericVector" ); ret.push_back( "toSf" ); + ret.push_back( "toRaster" ); + ret.push_back( "isVectorLayer" ); + ret.push_back( "isRasterLayer" ); return ret; } @@ -506,7 +624,10 @@ QgsRStatsSession::QgsRStatsSession() activeLayer=function() { .QGISPrivate$activeLayer }, toDataFrame=function(layer, selectedOnly=FALSE) { .QGISPrivate$toDataFrame(layer, selectedOnly) }, toNumericVector=function(layer, field, selectedOnly=FALSE) { .QGISPrivate$toNumericVector(layer, field, selectedOnly) }, - toSf=function(layer) { .QGISPrivate$toSf(layer) } + toSf=function(layer) { .QGISPrivate$toSf(layer) }, + toRaster=function(layer) {.QGISPrivate$toRaster(layer)}, + isVectorLayer=function(layer) { .QGISPrivate$isVectorLayer(layer) }, + isRasterLayer=function(layer) { .QGISPrivate$isRasterLayer(layer) } ) class(QGIS) <- "QGIS" )""" ), error );