diff --git a/CMakeLists.txt b/CMakeLists.txt index 963406ebd5b0..5fe24e153cb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,6 +194,21 @@ if(WITH_CORE) endif() endif() + 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() + # 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/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..1419bf0f9ce4 --- /dev/null +++ b/cmake/FindRCpp.cmake @@ -0,0 +1,39 @@ +# 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() + +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/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/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/python/console/console.py b/python/console/console.py index 94df050e6494..0ee5451f1885 100644 --- a/python/console/console.py +++ b/python/console/console.py @@ -782,7 +782,7 @@ def saveSettingsConsole(self): self.settings.setValue("pythonConsole/splitterObj", self.splitterObj.saveState()) self.settings.setValue("pythonConsole/splitterEditor", self.splitterEditor.saveState()) - self.shell.writeHistoryFile(True) + self.shell.writeHistoryFile() def restoreSettingsConsole(self): storedTabScripts = self.settings.value("pythonConsole/tabScripts", []) diff --git a/python/console/console_sci.py b/python/console/console_sci.py index 77e3a4ce550c..ed4970196f84 100644 --- a/python/console/console_sci.py +++ b/python/console/console_sci.py @@ -19,28 +19,22 @@ Some portions of code were taken from https://code.google.com/p/pydee/ """ -from qgis.PyQt.QtCore import Qt, QByteArray, QCoreApplication, QFile, QSize -from qgis.PyQt.QtWidgets import QDialog, QMenu, QShortcut, QApplication -from qgis.PyQt.QtGui import QKeySequence, QFontMetrics, QStandardItemModel, QStandardItem, QClipboard -from qgis.PyQt.Qsci import QsciScintilla -from qgis.gui import ( - QgsCodeEditorPython, - QgsCodeEditorColorScheme -) - -import sys -import os import code -import codecs +import os import re +import sys import traceback +from qgis.PyQt.Qsci import QsciScintilla +from qgis.PyQt.QtCore import Qt, QCoreApplication +from qgis.PyQt.QtGui import QKeySequence, QFontMetrics, QClipboard +from qgis.PyQt.QtWidgets import QShortcut, QApplication from qgis.core import QgsApplication, QgsSettings, Qgis -from qgis.gui import QgsCodeEditor - -from .ui_console_history_dlg import Ui_HistoryDialogPythonConsole - -_historyFile = os.path.join(QgsApplication.qgisSettingsDirPath(), "console_history.txt") +from qgis.gui import ( + QgsCodeEditorPython, + QgsCodeEditorColorScheme, + QgsCodeEditor +) _init_statements = [ # Python @@ -75,7 +69,7 @@ class ShellScintilla(QgsCodeEditorPython, code.InteractiveInterpreter): def __init__(self, parent=None): - super(QgsCodeEditorPython, self).__init__(parent) + super(QgsCodeEditorPython, self).__init__(parent, [], QgsCodeEditor.Mode.CommandInput) code.InteractiveInterpreter.__init__(self, locals=None) self.parent = parent @@ -98,27 +92,13 @@ def __init__(self, parent=None): except ModuleNotFoundError: pass - self.history = [] - self.softHistory = [''] - self.softHistoryIndex = 0 - # Read history command file - self.readHistoryFile() + self.setHistoryFilePath( + os.path.join(QgsApplication.qgisSettingsDirPath(), "console_history.txt")) - self.historyDlg = HistoryDialog(self) + self.historyDlg = None # HistoryDialog(self) self.refreshSettingsShell() - # Don't want to see the horizontal scrollbar at all - # Use raw message to Scintilla here (all messages are documented - # here: http://www.scintilla.org/ScintillaDoc.html) - self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0) - - # not too small - # self.setMinimumSize(500, 300) - - self.setWrapMode(QsciScintilla.WrapCharacter) - self.SendScintilla(QsciScintilla.SCI_EMPTYUNDOBUFFER) - # Disable command key ctrl, shift = self.SCMOD_CTRL << 16, self.SCMOD_SHIFT << 16 self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord('L') + ctrl) @@ -136,6 +116,9 @@ def __init__(self, parent=None): self.newShortcutCAS.activated.connect(self.autoComplete) self.newShortcutCSS.activated.connect(self.showHistory) + self.sessionHistoryCleared.connect(self.on_session_history_cleared) + self.persistentHistoryCleared.connect(self.on_persistent_history_cleared) + def initializeLexer(self): super().initializeLexer() self.setCaretLineVisible(False) @@ -161,39 +144,6 @@ def refreshSettingsShell(self): # Sets minimum height for input area based of font metric self._setMinimumHeight() - def showHistory(self): - if not self.historyDlg.isVisible(): - self.historyDlg.show() - self.historyDlg._reloadHistory() - self.historyDlg.activateWindow() - - def commandConsole(self, commands): - if not self.isCursorOnLastLine(): - self.moveCursorToEnd() - for cmd in commands: - self.setText(cmd) - self.entered() - self.moveCursorToEnd() - self.setFocus() - - def getText(self): - """ Get the text as a unicode string. """ - value = self.getBytes().decode('utf-8') - # print (value) printing can give an error because the console font - # may not have all unicode characters - return value - - def getBytes(self): - """ Get the text as bytes (utf-8 encoded). This is how - the data is stored internally. """ - len = self.SendScintilla(self.SCI_GETLENGTH) + 1 - bb = QByteArray(len, '0') - self.SendScintilla(self.SCI_GETTEXT, len, bb) - return bytes(bb)[:-1] - - def getTextLength(self): - return self.SendScintilla(QsciScintilla.SCI_GETLENGTH) - def moveCursorToStart(self): super().moveCursorToStart() self.displayPrompt(self.continuationLine) @@ -202,99 +152,19 @@ def moveCursorToEnd(self): super().moveCursorToEnd() self.displayPrompt(self.continuationLine) - def new_prompt(self, prompt): - """ - Print a new prompt and save its (line, index) position - """ - self.write(prompt, prompt=True) - # now we update our cursor giving end of prompt - line, index = self.getCursorPosition() - self.ensureCursorVisible() - self.ensureLineVisible(line) - def displayPrompt(self, more=False): - self.SendScintilla(QsciScintilla.SCI_MARGINSETTEXT, 0, str.encode("..." if more else ">>>")) - - def syncSoftHistory(self): - self.softHistory = self.history[:] - self.softHistory.append('') - self.softHistoryIndex = len(self.softHistory) - 1 - - def updateSoftHistory(self): - self.softHistory[self.softHistoryIndex] = self.text() - - def updateHistory(self, command, skipSoftHistory=False): - if isinstance(command, list): - for line in command: - self.history.append(line) - elif not command == "": - if len(self.history) <= 0 or \ - command != self.history[-1]: - self.history.append(command) - if not skipSoftHistory: - self.syncSoftHistory() - - def writeHistoryFile(self, fromCloseConsole=False): - ok = False - try: - wH = codecs.open(_historyFile, 'w', encoding='utf-8') - for s in self.history: - wH.write(s + '\n') - ok = True - except: - raise - wH.close() - if ok and not fromCloseConsole: - msgText = QCoreApplication.translate('PythonConsole', - 'History saved successfully.') - self.parent.callWidgetMessageBar(msgText) - - def readHistoryFile(self): - fileExist = QFile.exists(_historyFile) - if fileExist: - with codecs.open(_historyFile, 'r', encoding='utf-8') as rH: - for line in rH: - if line != "\n": - l = line.rstrip('\n') - self.updateHistory(l, True) - self.syncSoftHistory() - else: - return + self.SendScintilla(QsciScintilla.SCI_MARGINSETTEXT, 0, + str.encode("..." if more else ">>>")) - def clearHistory(self, clearSession=False): - if clearSession: - self.history = [] - self.readHistoryFile() - self.syncSoftHistory() - msgText = QCoreApplication.translate('PythonConsole', - 'Session history cleared successfully.') - self.parent.callWidgetMessageBar(msgText) - else: - self.history = [] - if QFile.exists(_historyFile): - with open(_historyFile, 'w', encoding='utf-8') as h: - h.truncate() - - msgText = QCoreApplication.translate('PythonConsole', - 'History cleared successfully.') - self.parent.callWidgetMessageBar(msgText) - - def clearHistorySession(self): - self.clearHistory(True) - - def showPrevious(self): - if self.softHistoryIndex < len(self.softHistory) - 1 and self.softHistory: - self.softHistoryIndex += 1 - self.setText(self.softHistory[self.softHistoryIndex]) - self.moveCursorToEnd() - # self.SendScintilla(QsciScintilla.SCI_DELETEBACK) + def on_session_history_cleared(self): + msgText = QCoreApplication.translate('PythonConsole', + 'Session history cleared successfully.') + self.parent.callWidgetMessageBar(msgText) - def showNext(self): - if self.softHistoryIndex > 0 and self.softHistory: - self.softHistoryIndex -= 1 - self.setText(self.softHistory[self.softHistoryIndex]) - self.moveCursorToEnd() - # self.SendScintilla(QsciScintilla.SCI_DELETEBACK) + def on_persistent_history_cleared(self): + msgText = QCoreApplication.translate('PythonConsole', + 'History cleared successfully.') + self.parent.callWidgetMessageBar(msgText) def keyPressEvent(self, e): # update the live history @@ -350,16 +220,18 @@ def keyPressEvent(self, e): e.accept() elif e.key() == Qt.Key_Down and not self.isListActive(): - self.showPrevious() + self.showPreviousCommand() elif e.key() == Qt.Key_Up and not self.isListActive(): - self.showNext() + self.showNextCommand() # TODO: press event for auto-completion file directory else: t = e.text() - self.autoCloseBracket = self.settings.value("pythonConsole/autoCloseBracket", False, type=bool) - self.autoImport = self.settings.value("pythonConsole/autoInsertionImport", True, type=bool) + self.autoCloseBracket = self.settings.value("pythonConsole/autoCloseBracket", False, + type=bool) + self.autoImport = self.settings.value("pythonConsole/autoInsertionImport", True, + type=bool) # Close bracket automatically if t in self.opening and self.autoCloseBracket: i = self.opening.index(t) @@ -392,42 +264,13 @@ def keyPressEvent(self, e): self.displayPrompt(self.continuationLine) - def contextMenuEvent(self, e): - menu = QMenu(self) - subMenu = QMenu(menu) - titleHistoryMenu = QCoreApplication.translate("PythonConsole", "Command History") - subMenu.setTitle(titleHistoryMenu) - subMenu.addAction( - QCoreApplication.translate("PythonConsole", "Show"), - self.showHistory, 'Ctrl+Shift+SPACE') - subMenu.addAction( - QCoreApplication.translate("PythonConsole", "Clear File"), - self.clearHistory) - subMenu.addAction( - QCoreApplication.translate("PythonConsole", "Clear Session"), - self.clearHistorySession) - menu.addMenu(subMenu) - menu.addSeparator() - copyAction = menu.addAction( - QgsApplication.getThemeIcon("mActionEditCopy.svg"), - QCoreApplication.translate("PythonConsole", "Copy"), - self.copy, QKeySequence.Copy) - pasteAction = menu.addAction( - QgsApplication.getThemeIcon("mActionEditPaste.svg"), - QCoreApplication.translate("PythonConsole", "Paste"), - self.paste, QKeySequence.Paste) - pyQGISHelpAction = menu.addAction(QgsApplication.getThemeIcon("console/iconHelpConsole.svg"), - QCoreApplication.translate("PythonConsole", "Search Selected in PyQGIS docs"), - self.searchSelectedTextInPyQGISDocs) - copyAction.setEnabled(False) - pasteAction.setEnabled(False) - pyQGISHelpAction.setEnabled(False) - if self.hasSelectedText(): - copyAction.setEnabled(True) - pyQGISHelpAction.setEnabled(True) - if QApplication.clipboard().text(): - pasteAction.setEnabled(True) - menu.exec_(self.mapToGlobal(e.pos())) + def populateContextMenu(self, menu): + pyQGISHelpAction = menu.addAction( + QgsApplication.getThemeIcon("console/iconHelpConsole.svg"), + QCoreApplication.translate("PythonConsole", "Search Selected in PyQGIS docs"), + self.searchSelectedTextInPyQGISDocs + ) + pyQGISHelpAction.setEnabled(self.hasSelectedText()) def mousePressEvent(self, e): """ @@ -504,23 +347,25 @@ def entered(self): def runCommand(self, cmd): self.writeCMD(cmd) import webbrowser - self.updateHistory(cmd) - version = 'master' if 'master' in Qgis.QGIS_VERSION.lower() else re.findall(r'^\d.[0-9]*', Qgis.QGIS_VERSION)[0] + self.updateHistory([cmd]) + version = 'master' if 'master' in Qgis.QGIS_VERSION.lower() else \ + re.findall(r'^\d.[0-9]*', Qgis.QGIS_VERSION)[0] if cmd in ('_pyqgis', '_api', '_cookbook'): if cmd == '_pyqgis': webbrowser.open("https://qgis.org/pyqgis/{}".format(version)) elif cmd == '_api': - webbrowser.open("https://qgis.org/api/{}".format('' if version == 'master' else version)) + webbrowser.open( + "https://qgis.org/api/{}".format('' if version == 'master' else version)) elif cmd == '_cookbook': - webbrowser.open("https://docs.qgis.org/{}/en/docs/pyqgis_developer_cookbook/".format( - 'testing' if version == 'master' else version)) + webbrowser.open( + "https://docs.qgis.org/{}/en/docs/pyqgis_developer_cookbook/".format( + 'testing' if version == 'master' else version)) else: self.buffer.append(cmd) src = "\n".join(self.buffer) more = self.runsource(src) - self.continuationLine = True + self.continuationLine = more if not more: - self.continuationLine = False self.buffer = [] # prevents to commands with more lines to break the console @@ -553,69 +398,3 @@ def excepthook(etype, value, tb): return super(ShellScintilla, self).runsource(source, filename, symbol) finally: sys.excepthook = hook - - -class HistoryDialog(QDialog, Ui_HistoryDialogPythonConsole): - - def __init__(self, parent): - QDialog.__init__(self, parent) - self.setupUi(self) - self.parent = parent - self.setWindowTitle(QCoreApplication.translate("PythonConsole", - "Python Console - Command History")) - self.listView.setToolTip(QCoreApplication.translate("PythonConsole", - "Double-click on item to execute")) - - self.listView.setFont(QgsCodeEditorPython.getMonospaceFont()) - - self.model = QStandardItemModel(self.listView) - - self._reloadHistory() - - self.deleteScut = QShortcut(QKeySequence(Qt.Key_Delete), self) - self.deleteScut.activated.connect(self._deleteItem) - self.listView.doubleClicked.connect(self._runHistory) - self.reloadHistory.clicked.connect(self._reloadHistory) - self.saveHistory.clicked.connect(self._saveHistory) - self.runHistoryButton.clicked.connect(self._executeSelectedHistory) - - def _executeSelectedHistory(self): - items = self.listView.selectionModel().selectedIndexes() - items.sort() - for item in items: - self.parent.runCommand(item.data(Qt.DisplayRole)) - - def _runHistory(self, item): - cmd = item.data(Qt.DisplayRole) - self.parent.runCommand(cmd) - - def _saveHistory(self): - self.parent.writeHistoryFile(True) - - def _reloadHistory(self): - self.model.clear() - item = None - for i in self.parent.history: - item = QStandardItem(i) - if sys.platform.startswith('win'): - item.setSizeHint(QSize(18, 18)) - self.model.appendRow(item) - - self.listView.setModel(self.model) - self.listView.scrollToBottom() - if item: - self.listView.setCurrentIndex(self.model.indexFromItem(item)) - - def _deleteItem(self): - itemsSelected = self.listView.selectionModel().selectedIndexes() - if itemsSelected: - item = itemsSelected[0].row() - # Remove item from the command history (just for the current session) - self.parent.history.pop(item) - self.parent.softHistory.pop(item) - if item < self.parent.softHistoryIndex: - self.parent.softHistoryIndex -= 1 - self.parent.setText(self.parent.softHistory[self.parent.softHistoryIndex]) - self.parent.moveCursorToEnd() - # Remove row from the command history dialog - self.model.removeRow(item) diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index b3451f486e3a..c8a463104222 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -2015,3 +2015,16 @@ Qgis.CoordinateDisplayType.__doc__ = 'Formats for displaying coordinates\n\n.. versionadded:: 3.28\n\n' + '* ``MapCrs``: ' + Qgis.CoordinateDisplayType.MapCrs.__doc__ + '\n' + '* ``MapGeographic``: ' + Qgis.CoordinateDisplayType.MapGeographic.__doc__ + '\n' + '* ``CustomCrs``: ' + Qgis.CoordinateDisplayType.CustomCrs.__doc__ # -- Qgis.CoordinateDisplayType.baseClass = Qgis +# monkey patching scoped based enum +Qgis.ScriptLanguage.Css.__doc__ = "CSS" +Qgis.ScriptLanguage.QgisExpression.__doc__ = "QGIS expressions" +Qgis.ScriptLanguage.Html.__doc__ = "HTML" +Qgis.ScriptLanguage.JavaScript.__doc__ = "JavaScript" +Qgis.ScriptLanguage.Json.__doc__ = "JSON" +Qgis.ScriptLanguage.Python.__doc__ = "Python" +Qgis.ScriptLanguage.R.__doc__ = "R Stats" +Qgis.ScriptLanguage.Sql.__doc__ = "SQL" +Qgis.ScriptLanguage.Unknown.__doc__ = "Unknown/other language" +Qgis.ScriptLanguage.__doc__ = 'Scripting languages.\n\n.. versionadded:: 3.30\n\n' + '* ``Css``: ' + Qgis.ScriptLanguage.Css.__doc__ + '\n' + '* ``QgisExpression``: ' + Qgis.ScriptLanguage.QgisExpression.__doc__ + '\n' + '* ``Html``: ' + Qgis.ScriptLanguage.Html.__doc__ + '\n' + '* ``JavaScript``: ' + Qgis.ScriptLanguage.JavaScript.__doc__ + '\n' + '* ``Json``: ' + Qgis.ScriptLanguage.Json.__doc__ + '\n' + '* ``Python``: ' + Qgis.ScriptLanguage.Python.__doc__ + '\n' + '* ``R``: ' + Qgis.ScriptLanguage.R.__doc__ + '\n' + '* ``Sql``: ' + Qgis.ScriptLanguage.Sql.__doc__ + '\n' + '* ``Unknown``: ' + Qgis.ScriptLanguage.Unknown.__doc__ +# -- +Qgis.ScriptLanguage.baseClass = Qgis diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index ac854848a931..de15de17a37b 100644 --- a/python/core/auto_generated/qgis.sip.in +++ b/python/core/auto_generated/qgis.sip.in @@ -1298,6 +1298,19 @@ The development version CustomCrs, }; + enum class ScriptLanguage + { + Css, + QgisExpression, + Html, + JavaScript, + Json, + Python, + R, + Sql, + Unknown, + }; + static const double DEFAULT_SEARCH_RADIUS_MM; static const float DEFAULT_MAPTOPIXEL_THRESHOLD; 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/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..6b406a7cb94e 100644 --- a/python/gui/auto_generated/codeeditors/qgscodeeditor.sip.in +++ b/python/gui/auto_generated/codeeditors/qgscodeeditor.sip.in @@ -13,6 +13,7 @@ + class QgsCodeEditor : QsciScintilla { %Docstring(signature="appended") @@ -30,6 +31,13 @@ A text editor based on QScintilla2. %End public: + enum class Mode + { + ScriptEditor, + OutputDisplay, + CommandInput, + }; + enum class MarginRole { LineNumbers, @@ -45,7 +53,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 +62,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 @@ -63,6 +72,20 @@ Construct a new code editor. Set the widget title :param title: widget title +%End + + virtual Qgis::ScriptLanguage language() const; +%Docstring +Returns the associated scripting language. + +.. versionadded:: 3.30 +%End + + static QString languageToString( Qgis::ScriptLanguage language ); +%Docstring +Returns a user-friendly, translated name of the specified script ``language``. + +.. versionadded:: 3.30 %End void setMarginVisible( bool margin ) /Deprecated/; @@ -188,6 +211,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; @@ -197,8 +227,32 @@ Returns ``True`` if the cursor is on the last line of the document. .. versionadded:: 3.28 %End + void setHistoryFilePath( const QString &path ); +%Docstring +Sets the file path to use for recording and retrieving previously +executed commands. + +.. note:: + + Applies to code editors in the QgsCodeEditor.Mode.CommandInput mode only. + +.. versionadded:: 3.30 +%End + + + QStringList history() const; + public slots: + virtual void runCommand( const QString &command ); +%Docstring +Runs a command in the editor. + +The base class method does nothing. + +.. versionadded:: 3.30 +%End + virtual void moveCursorToStart(); %Docstring Moves the cursor to the start of the document and scrolls to ensure @@ -215,13 +269,62 @@ it is visible. .. versionadded:: 3.28 %End + void showPreviousCommand(); +%Docstring +Shows the previous command from the session in the editor. + +.. note:: + + Applies to code editors in the QgsCodeEditor.Mode.CommandInput mode only. + +.. versionadded:: 3.30 +%End + + void showNextCommand(); +%Docstring +Shows the next command from the session in the editor. + +.. note:: + + Applies to code editors in the QgsCodeEditor.Mode.CommandInput mode only. + +.. versionadded:: 3.30 +%End + + void showHistory(); + + void removeHistoryCommand( int index ); + + void clearSessionHistory(); +%Docstring +Clears the history of commands run in the current session. + +.. note:: + + Applies to code editors in the QgsCodeEditor.Mode.CommandInput mode only. + +.. versionadded:: 3.30 +%End + + + void clearPersistentHistory(); + + bool writeHistoryFile(); + + signals: + + void sessionHistoryCleared(); + void persistentHistoryCleared(); + 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 contextMenuEvent( QContextMenuEvent *event ); virtual void initializeLexer(); @@ -254,6 +357,13 @@ Performs tasks which must be run after a lexer has been set for the widget. .. versionadded:: 3.16 %End + + void syncSoftHistory(); + void updateSoftHistory(); + void updateHistory( const QStringList &commands, bool skipSoftHistory = false ); + + virtual void populateContextMenu( QMenu *menu ); + }; QFlags operator|(QgsCodeEditor::Flag f1, QFlags f2); diff --git a/python/gui/auto_generated/codeeditors/qgscodeeditorcss.sip.in b/python/gui/auto_generated/codeeditors/qgscodeeditorcss.sip.in index 0135fccd1bd0..789e46460b10 100644 --- a/python/gui/auto_generated/codeeditors/qgscodeeditorcss.sip.in +++ b/python/gui/auto_generated/codeeditors/qgscodeeditorcss.sip.in @@ -30,6 +30,8 @@ code autocompletion. %Docstring Constructor for QgsCodeEditorCSS %End + virtual Qgis::ScriptLanguage language() const; + protected: virtual void initializeLexer(); diff --git a/python/gui/auto_generated/codeeditors/qgscodeeditorexpression.sip.in b/python/gui/auto_generated/codeeditors/qgscodeeditorexpression.sip.in index f1dfad103da6..075da559b100 100644 --- a/python/gui/auto_generated/codeeditors/qgscodeeditorexpression.sip.in +++ b/python/gui/auto_generated/codeeditors/qgscodeeditorexpression.sip.in @@ -29,6 +29,9 @@ code autocompletion. Constructor for QgsCodeEditorExpression %End + virtual Qgis::ScriptLanguage language() const; + + void setExpressionContext( const QgsExpressionContext &context ); %Docstring Variables and functions from this expression context will be added to diff --git a/python/gui/auto_generated/codeeditors/qgscodeeditorhtml.sip.in b/python/gui/auto_generated/codeeditors/qgscodeeditorhtml.sip.in index 3043ad7fbbe9..4014b85d6313 100644 --- a/python/gui/auto_generated/codeeditors/qgscodeeditorhtml.sip.in +++ b/python/gui/auto_generated/codeeditors/qgscodeeditorhtml.sip.in @@ -32,6 +32,9 @@ code autocompletion. Constructor for QgsCodeEditorHTML %End + virtual Qgis::ScriptLanguage language() const; + + protected: virtual void initializeLexer(); diff --git a/python/gui/auto_generated/codeeditors/qgscodeeditorjs.sip.in b/python/gui/auto_generated/codeeditors/qgscodeeditorjs.sip.in index aeeda92c88fc..d9b43f3e8bd1 100644 --- a/python/gui/auto_generated/codeeditors/qgscodeeditorjs.sip.in +++ b/python/gui/auto_generated/codeeditors/qgscodeeditorjs.sip.in @@ -28,6 +28,8 @@ code autocompletion. %Docstring Constructor for QgsCodeEditorJavascript %End + virtual Qgis::ScriptLanguage language() const; + protected: virtual void initializeLexer(); diff --git a/python/gui/auto_generated/codeeditors/qgscodeeditorjson.sip.in b/python/gui/auto_generated/codeeditors/qgscodeeditorjson.sip.in index d47156bf094f..9766e4f19bcf 100644 --- a/python/gui/auto_generated/codeeditors/qgscodeeditorjson.sip.in +++ b/python/gui/auto_generated/codeeditors/qgscodeeditorjson.sip.in @@ -29,6 +29,9 @@ code autocompletion. Constructor for QgsCodeEditorJson %End + virtual Qgis::ScriptLanguage language() const; + + protected: virtual void initializeLexer(); diff --git a/python/gui/auto_generated/codeeditors/qgscodeeditorpython.sip.in b/python/gui/auto_generated/codeeditors/qgscodeeditorpython.sip.in index 6516a3f24426..1bda741d097f 100644 --- a/python/gui/auto_generated/codeeditors/qgscodeeditorpython.sip.in +++ b/python/gui/auto_generated/codeeditors/qgscodeeditorpython.sip.in @@ -28,7 +28,8 @@ code autocompletion. %End public: - QgsCodeEditorPython( QWidget *parent /TransferThis/ = 0, const QList &filenames = QList() ); + QgsCodeEditorPython( QWidget *parent /TransferThis/ = 0, const QList &filenames = QList(), + QgsCodeEditor::Mode mode = QgsCodeEditor::Mode::ScriptEditor ); %Docstring Construct a new Python editor. @@ -38,6 +39,9 @@ Construct a new Python editor. .. versionadded:: 2.6 %End + virtual Qgis::ScriptLanguage language() const; + + void loadAPIs( const QList &filenames ); %Docstring Load APIs from one or more files 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..8166c6e07498 --- /dev/null +++ b/python/gui/auto_generated/codeeditors/qgscodeeditorr.sip.in @@ -0,0 +1,46 @@ +/************************************************************************ + * 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, QgsCodeEditor::Mode mode = QgsCodeEditor::Mode::ScriptEditor ); +%Docstring +Constructor for QgsCodeEditorR +%End + virtual Qgis::ScriptLanguage language() const; + + + 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/auto_generated/codeeditors/qgscodeeditorsql.sip.in b/python/gui/auto_generated/codeeditors/qgscodeeditorsql.sip.in index a055fb1d7e02..5f594b7e8b7c 100644 --- a/python/gui/auto_generated/codeeditors/qgscodeeditorsql.sip.in +++ b/python/gui/auto_generated/codeeditors/qgscodeeditorsql.sip.in @@ -31,6 +31,8 @@ code autocompletion. Constructor for QgsCodeEditorSQL %End + virtual Qgis::ScriptLanguage language() const; + virtual ~QgsCodeEditorSQL(); 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/CMakeLists.txt b/src/app/CMakeLists.txt index e9f6d1b07804..a5030dc61c91 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -415,6 +415,36 @@ if (POSTGRES_FOUND) endif() endif() +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}" + ) + + 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 + ) +endif() + # Test data dir for QgsAppScreenShots add_definitions(-DTEST_DATA_DIR="${TEST_DATA_DIR}") @@ -656,6 +686,18 @@ if (WITH_PDAL) target_link_libraries(qgis_app ${PDAL_LIBRARIES}) endif() +if (WITH_R) + target_include_directories(qgis_app SYSTEM PUBLIC + ${CMAKE_SOURCE_DIR}/external/r_inside/inst/include + ${R_INCLUDE_DIR} + ${RCpp_INCLUDE_DIR} + ) + target_link_libraries(qgis_app + ${R_LIB} + Rcpp + ) +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/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/app/qgisapp.cpp b/src/app/qgisapp.cpp index 15400a0136dd..0bf93bab651f 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,11 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipBadLayers mActionShowPythonDialog = nullptr; } +#ifdef HAVE_R + mRStatsRunner = new QgsRStatsRunner(); + mRConsole = new QgsRStatsConsole( nullptr, mRStatsRunner ); +#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 +1957,13 @@ QgisApp::~QgisApp() } #endif +#ifdef HAVE_R + delete mRConsole; + mRConsole = nullptr; + delete mRStatsRunner; + mRStatsRunner = nullptr; +#endif + mNetworkLoggerWidgetFactory.reset(); delete mInternalClipboard; diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index e4567b5c020e..83b42652c5fa 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; @@ -194,6 +195,10 @@ class QgsLegendFilterButton; class QgsGeoreferencerMainWindow; #endif +#ifdef HAVE_R +class QgsRStatsConsole; +#endif + /** * \class QgisApp * \brief Main window for the QGIS application @@ -2517,6 +2522,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow QgsPythonUtils *mPythonUtils = nullptr; + QgsRStatsRunner *mRStatsRunner = nullptr; + static QgisApp *sInstance; QgsUndoWidget *mUndoWidget = nullptr; @@ -2525,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/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 new file mode 100644 index 000000000000..67cc35080e42 --- /dev/null +++ b/src/app/rstats/qgsrstatsconsole.cpp @@ -0,0 +1,155 @@ +/*************************************************************************** + 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 "qgisapp.h" +#include "qgsdockablewidgethelper.h" +#include "qgscodeeditorr.h" +#include "qgscodeeditor.h" + +#include +#include +#include +#include +#include +#include + +QgsRStatsConsole::QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ) + : QWidget( parent ) + , mRunner( runner ) +{ + 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 ); + + QVBoxLayout *vl = new QVBoxLayout(); + vl->setContentsMargins( 0, 0, 0, 0 ); + vl->addWidget( toolBar ); + + QSplitter *splitter = new QSplitter(); + splitter->setOrientation( Qt::Vertical ); + splitter->setHandleWidth( 3 ); + splitter->setChildrenCollapsible( false ); + + mOutput = new QgsCodeEditorR( nullptr, QgsCodeEditor::Mode::OutputDisplay ); + splitter->addWidget( mOutput ); + mInputEdit = new QgsInteractiveRWidget(); + mInputEdit->setFont( QgsCodeEditor::getMonospaceFont() ); + splitter->addWidget( mInputEdit ); + + vl->addWidget( splitter ); + + connect( mInputEdit, &QgsInteractiveRWidget::runCommand, this, [ = ]( const QString & command ) + { + if ( mRunner->busy() ) + return; + + mInputEdit->clear(); + 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 ) + { + if ( type == 0 ) + 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 ) + { + //mInputEdit->setEnabled( !busy ); + } ); + + setLayout( vl ); + + mRunner->showStartupMessage(); +} + +QgsRStatsConsole::~QgsRStatsConsole() +{ + delete mDockableWidgetHelper; +} + +QgsInteractiveRWidget::QgsInteractiveRWidget( QWidget *parent ) + : QgsCodeEditorR( parent, QgsCodeEditor::Mode::CommandInput ) +{ + displayPrompt( false ); + + QgsInteractiveRWidget::initializeLexer(); +} + +void QgsInteractiveRWidget::clear() +{ + QgsCodeEditorR::clear(); + displayPrompt( false ); +} + +void QgsInteractiveRWidget::keyPressEvent( QKeyEvent *event ) +{ + switch ( event->key() ) + { + case Qt::Key_Return: + case Qt::Key_Enter: + emit runCommand( text() ); + + break; + + default: + QgsCodeEditorR::keyPressEvent( event ); + } +} + +void QgsInteractiveRWidget::initializeLexer() +{ + QgsCodeEditorR::initializeLexer(); + + setCaretLineVisible( false ); + setLineNumbersVisible( false ); // NO linenumbers for the input line + // 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() ); +} diff --git a/src/app/rstats/qgsrstatsconsole.h b/src/app/rstats/qgsrstatsconsole.h new file mode 100644 index 000000000000..3de4c93bda3b --- /dev/null +++ b/src/app/rstats/qgsrstatsconsole.h @@ -0,0 +1,66 @@ +/*************************************************************************** + 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 + +#include "qgscodeeditorr.h" + +class QgsRStatsRunner; +class QLineEdit; +class QTextBrowser; +class QgsDockableWidgetHelper; + +class QgsInteractiveRWidget : public QgsCodeEditorR +{ + Q_OBJECT + public: + + QgsInteractiveRWidget( QWidget *parent = nullptr ); + + void clear() override; + + signals: + + void runCommand( const QString &command ); + + protected: + + void keyPressEvent( QKeyEvent *event ) override; + + void initializeLexer() override; + void displayPrompt( bool more = false ); + +}; + +class QgsRStatsConsole: public QWidget +{ + public: + QgsRStatsConsole( QWidget *parent, QgsRStatsRunner *runner ); + ~QgsRStatsConsole() override; + + private: + + QgsRStatsRunner *mRunner = nullptr; + QgsInteractiveRWidget *mInputEdit = nullptr; + QgsCodeEditorR *mOutput = nullptr; + QgsDockableWidgetHelper *mDockableWidgetHelper = nullptr; + +}; + +#endif // QGSRSTATSCONSOLE_H diff --git a/src/app/rstats/qgsrstatsrunner.cpp b/src/app/rstats/qgsrstatsrunner.cpp new file mode 100644 index 000000000000..9fd45fc47680 --- /dev/null +++ b/src/app/rstats/qgsrstatsrunner.cpp @@ -0,0 +1,1122 @@ +#include "qgsrstatsrunner.h" +#include "qgsapplication.h" + +#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" +#include "qgsrasterlayer.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( bool selectedOnly ) 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; + 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; + + QList< int > attributesToFetch; + for ( int index = 0; index < fields.count(); ++index ) + { + Rcpp::RObject column; + const QgsField field = fields.at( index ); + + 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() ); + 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; + + int prevProgress = 0; + while ( it.nextFeature( feature ) ) + { + const int progress = 100 * static_cast< double>( featureNumber ) / featureCount; + if ( progress > prevProgress ) + { + task->setProgress( progress ); + prevProgress = progress; + } + + if ( task->isCanceled() ) + break; + + int settingColumn = 0; + + const QgsAttributes attributes = feature.attributes(); + const QVariant *attributeData = attributes.constData(); + + for ( int i = 0; i < fields.count(); i ++, attributeData++ ) + { + QgsField field = fields.at( i ); + + switch ( field.type() ) + { + case QVariant::Bool: + { + Rcpp::LogicalVector column = result[settingColumn]; + column[featureNumber] = attributeData->toBool(); + break; + } + case QVariant::Int: + { + Rcpp::IntegerVector column = result[settingColumn]; + column[featureNumber] = attributeData->toInt(); + break; + } + case QVariant::LongLong: + { + Rcpp::DoubleVector column = result[settingColumn]; + bool ok; + double val = attributeData->toDouble( &ok ); + if ( ok ) + column[featureNumber] = val; + else + column[featureNumber] = R_NaReal; + break; + } + case QVariant::Double: + { + Rcpp::DoubleVector column = result[settingColumn]; + column[featureNumber] = attributeData->toDouble(); + break; + } + case QVariant::String: + { + Rcpp::StringVector column = result[settingColumn]; + column[featureNumber] = attributeData->toString().toStdString(); + break; + } + + default: + continue; + } + settingColumn++; + } + featureNumber++; + } + 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() ); + } + + 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; + } + + enum RasterPackage { raster, stars, terra}; + + SEXP toRaster() + { + return this->toRasterDataObject( RasterPackage::raster ); + } + + SEXP toTerra() + { + return this->toRasterDataObject( RasterPackage::terra ); + } + + SEXP toStars() + { + return this->toRasterDataObject( RasterPackage::stars ); + } + + private: + + QString mLayerId; + + SEXP toRasterDataObject( RasterPackage rasterPackage ) + { + if ( ! this->isRasterLayer()( 0 ) ) + return R_NilValue; + + bool prepared = false; + QString rasterPath; + + auto prepareOnMainThread = [&rasterPath, &prepared, this] + { + Q_ASSERT_X( QThread::currentThread() == qApp->thread(), "toRaster", "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; + + switch ( rasterPackage ) + { + case RasterPackage::raster: + { + Rcpp::Function raster( "raster", Rcpp::Environment::namespace_env( "raster" ) ); + return raster( rasterPath.toStdString() ); + } + case RasterPackage::terra: + { + Rcpp::Function rast( "rast", Rcpp::Environment::namespace_env( "terra" ) ); + return rast( rasterPath.toStdString() ); + } + case RasterPackage::stars: + { + Rcpp::Function read_stars( "read_stars", Rcpp::Environment::namespace_env( "stars" ) ); + return read_stars( rasterPath.toStdString() ); + } + default: + return Rcpp::wrap( rasterPath.toStdString() ); + } + } +}; + + +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, bool selectedOnly ) +{ + 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 ) +{ + QList< QgsMapLayer * > layers = QgsProject::instance()->mapLayersByName( QString::fromStdString( name ) ); + if ( !layers.empty() ) + return Rcpp::XPtr( new MapLayerWrapper( layers.at( 0 ) ) ); + + return nullptr; +} + +SEXP MapLayerWrapperToRaster( Rcpp::XPtr obj ) +{ + return obj->toRaster(); +} + +SEXP MapLayerWrapperToTerra( Rcpp::XPtr obj ) +{ + return obj->toTerra(); +} + +SEXP MapLayerWrapperToStars( Rcpp::XPtr obj ) +{ + return obj->toStars(); +} + +SEXP MapLayerWrapperIsVectorLayer( Rcpp::XPtr obj ) +{ + return obj->isVectorLayer(); +} + +SEXP MapLayerWrapperIsRasterLayer( Rcpp::XPtr obj ) +{ + return obj->isRasterLayer(); +} + + +SEXP MapLayerWrapperDollar( Rcpp::XPtr obj, std::string name ) +{ + if ( name == "id" ) + { + return Rcpp::wrap( obj->id() ); + } + else + { + return NULL; + } +} + + +class QgsApplicationRWrapper +{ + public: + QgsApplicationRWrapper() {} + + int version() const { return Qgis::versionInt(); } + + SEXP activeLayer() const + { + 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 ) +{ + if ( name == "versionInt" ) + { + return Rcpp::wrap( obj->version() ); + } + else if ( name == "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 if ( name == "toNumericVector" ) + { + return Rcpp::InternalFunction( & MapLayerWrapperToNumericVector ); + } + else if ( name == "toSf" ) + { + return Rcpp::InternalFunction( & MapLayerWrapperToSf ); + } + else if ( name == "isVectorLayer" ) + { + return Rcpp::InternalFunction( & MapLayerWrapperIsVectorLayer ); + } + else if ( name == "isRasterLayer" ) + { + return Rcpp::InternalFunction( & MapLayerWrapperIsRasterLayer ); + } + else if ( name == "toRaster" ) + { + return Rcpp::InternalFunction( & MapLayerWrapperToRaster ); + } + else if ( name == "toTerra" ) + { + return Rcpp::InternalFunction( & MapLayerWrapperToTerra ); + } + else if ( name == "toStars" ) + { + return Rcpp::InternalFunction( & MapLayerWrapperToStars ); + } + 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" ); + ret.push_back( "layerId" ); + ret.push_back( "featureCount" ); + ret.push_back( "mapLayerByName" ); + ret.push_back( "toDataFrame" ); + ret.push_back( "toNumericVector" ); + ret.push_back( "toSf" ); + ret.push_back( "toRaster" ); + ret.push_back( "toTerra" ); + ret.push_back( "toStars" ); + ret.push_back( "isVectorLayer" ); + ret.push_back( "isRasterLayer" ); + return ret; +} + + + +// +// QgsRStatsSession +// +QgsRStatsSession::QgsRStatsSession() +{ + mRSession = std::make_unique< RInside >( 0, nullptr, true, false, true ); + mRSession->set_callbacks( this ); + + const QString userPath = QgsApplication::qgisSettingsDirPath() + QStringLiteral( "r_libs" ); + if ( !QFile::exists( userPath ) ) + { + QDir().mkpath( userPath ); + } + execCommandNR( QStringLiteral( ".libPaths(\"%1\")" ).arg( userPath ) ); + + Rcpp::XPtr wr( new QgsApplicationRWrapper() ); + wr.attr( "class" ) = ".QGISPrivate"; + mRSession->assign( wr, ".QGISPrivate" ); + mRSession->assign( Rcpp::InternalFunction( & Dollar ), "$..QGISPrivate" ); + + QString error; + execCommandPrivate( QStringLiteral( R"""( + QGIS <- list( + 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) }, + toRaster=function(layer) {.QGISPrivate$toRaster(layer)}, + toTerra=function(layer) {.QGISPrivate$toTerra(layer)}, + toStars=function(layer) {.QGISPrivate$toStars(layer)}, + isVectorLayer=function(layer) { .QGISPrivate$isVectorLayer(layer) }, + isRasterLayer=function(layer) { .QGISPrivate$isRasterLayer(layer) } + ) + class(QGIS) <- "QGIS" + )""" ), error ); + + if ( !error.isEmpty() ) + { + QgsDebugMsg( error ); + } +} + +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; + +QString QgsRStatsSession::sexpToString( const SEXP exp ) +{ + switch ( TYPEOF( exp ) ) + { + case EXPRSXP: + case CLOSXP: + case ENVSXP: + case LANGSXP: + case S4SXP: + case PROMSXP: + case DOTSXP: + // these types can't be converted to StringVector, will raise exceptions + return QString(); + + case CHARSXP: + { + // special case + return QStringLiteral( "[1] \"%1\"" ).arg( QString::fromStdString( Rcpp::as( exp ) ) ); + } + + case LGLSXP: + case INTSXP: + case REALSXP: + case STRSXP: + case EXTPTRSXP: + case VECSXP: + break; // we know these types are fine to convert to StringVector + + case NILSXP: + return QStringLiteral( "NULL" ); + + 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++ ) + { + Rcpp::String line( it->get() ); + outcome.append( line ); + if ( it < lines.end() - 1 ) + outcome.append( "\n" ); + } + return QString::fromStdString( outcome ); +} + +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: + 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; + } + + 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: + return QVariant(); + + case LGLSXP: + { + 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 ) + return QVariant(); + else + return static_cast< bool >( expInt ); + } + } + + case INTSXP: + { + 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 + { + const int res = Rcpp::as( exp ); + return res == NA_INTEGER ? QVariant() : QVariant( res ); + } + } + + case REALSXP: + { + 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 + { + const double res = Rcpp::as( exp ); + return std::isnan( res ) ? QVariant() : res; + } + } + + case STRSXP: + 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 QString::fromStdString( Rcpp::as( exp ) ); + } + + case CHARSXP: + return QString::fromStdString( Rcpp::as( exp ) ); + + //case RAWSXP: + // 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 PROMSXP: + // unreachable, handled earlier + return QVariant(); + + default: + QgsDebugMsg( QStringLiteral( "Unhandled type: %1" ).arg( TYPEOF( exp ) ) ); + return QVariant(); + } + + 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 + { + const SEXP sexpRes = mRSession->parseEval( command.toStdString() ); + if ( res ) + *res = sexpToVariant( sexpRes ); + if ( output ) + *output = sexpToString( sexpRes ); + } + catch ( std::exception &ex ) + { + error = QString::fromStdString( ex.what() ); + } + catch ( ... ) + { + std::cerr << "Unknown exception caught" << std::endl; + } +} + +void QgsRStatsSession::execCommandNR( const QString &command ) +{ + if ( mBusy ) + return; + + mBusy = true; + emit busyChanged( true ); + + mEncounteredErrorMessageType = false; + QString error; + execCommandPrivate( command, error ); + + if ( ! error.isEmpty() && !mEncounteredErrorMessageType ) + emit errorOccurred( error ); + + mBusy = false; + emit busyChanged( false ); +} + +void QgsRStatsSession::execCommand( const QString &command ) +{ + if ( mBusy ) + return; + + mBusy = true; + emit busyChanged( true ); + QString error; + QVariant res; + QString output; + mEncounteredErrorMessageType = false; + execCommandPrivate( command, error, &res, &output ); + + if ( ! error.isEmpty() ) + { + if ( !mEncounteredErrorMessageType ) + emit errorOccurred( error ); + } + else + { + if ( !output.isEmpty() ) + emit consoleMessage( output, 0 ); + emit commandFinished( res ); + } + + 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; +} + + + +// +// 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 ); + 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() +{ + // todo -- gracefully shut down session! + mSessionThread.quit(); + mSessionThread.wait(); +} + +void QgsRStatsRunner::execCommand( const QString &command ) +{ + // todo result handling... + QMetaObject::invokeMethod( mSession.get(), "execCommand", Qt::QueuedConnection, + Q_ARG( QString, command ) ); +} + +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 new file mode 100644 index 000000000000..05f7b2260a24 --- /dev/null +++ b/src/app/rstats/qgsrstatsrunner.h @@ -0,0 +1,120 @@ +/*************************************************************************** + 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 + +#include +#include +#include "Callbacks.h" + +#include "qgis_app.h" + + +class RInside; +class QVariant; +class QString; + +class APP_EXPORT QgsRStatsSession: public QObject, public Callbacks +{ + Q_OBJECT + public: + + QgsRStatsSession(); + ~QgsRStatsSession() override; + + void execCommandNR( const QString &command ); + + 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; } + + /** + * 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 ); + + /** + * Converts a variant to a SEXP. + */ + static SEXP variantToSexp( const QVariant &variant ); + + public slots: + + void execCommand( const QString &command ); + + void showStartupMessage(); + + signals: + + void busyChanged( bool busy ); + + void consoleMessage( const QString &message, int type ); + void showMessage( const QString &message ); + void errorOccurred( const QString &error ); + void commandFinished( const QVariant &result ); + + private: + void execCommandPrivate( const QString &command, QString &error, QVariant *res = nullptr, QString *output = nullptr ); + + std::unique_ptr< RInside > mRSession; + bool mBusy = false; + bool mEncounteredErrorMessageType = false; + + +}; + + +class QgsRStatsRunner: public QObject +{ + Q_OBJECT + public: + + QgsRStatsRunner(); + ~QgsRStatsRunner(); + + void execCommand( const QString &command ); + bool busy() const; + void showStartupMessage(); + + signals: + + void consoleMessage( const QString &message, int type ); + void showMessage( const QString &message ); + void errorOccurred( const QString &error ); + void busyChanged( bool busy ); + void commandFinished( const QVariant &result ); + + private: + + QThread mSessionThread; + std::unique_ptr mSession; +}; + +#endif // QGSRSTATSRUNNER_H 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(); diff --git a/src/core/qgis.h b/src/core/qgis.h index 3b2b7bed8e46..00de18e06e46 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -2245,6 +2245,25 @@ class CORE_EXPORT Qgis }; Q_ENUM( CoordinateDisplayType ) + /** + * Scripting languages. + * + * \since QGIS 3.30 + */ + enum class ScriptLanguage : int + { + Css, //!< CSS + QgisExpression, //!< QGIS expressions + Html, //!< HTML + JavaScript, //!< JavaScript + Json, //!< JSON + Python, //!< Python + R, //!< R Stats + Sql, //!< SQL + Unknown, //!< Unknown/other language + }; + Q_ENUM( ScriptLanguage ) + /** * Identify search radius in mm * \since QGIS 2.3 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; diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index f4641a136592..a7d05b53c888 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -128,10 +128,12 @@ set(QGIS_GUI_SRCS codeeditors/qgscodeeditorcolorscheme.cpp codeeditors/qgscodeeditorcolorschemeregistry.cpp codeeditors/qgscodeeditorcss.cpp + codeeditors/qgscodeeditorhistorydialog.cpp codeeditors/qgscodeeditorhtml.cpp codeeditors/qgscodeeditorjs.cpp codeeditors/qgscodeeditorjson.cpp codeeditors/qgscodeeditorpython.cpp + codeeditors/qgscodeeditorr.cpp codeeditors/qgscodeeditorsql.cpp codeeditors/qgscodeeditorexpression.cpp @@ -997,10 +999,12 @@ set(QGIS_GUI_HDRS codeeditors/qgscodeeditorcolorschemeregistry.h codeeditors/qgscodeeditorcss.h codeeditors/qgscodeeditorexpression.h + codeeditors/qgscodeeditorhistorydialog.h codeeditors/qgscodeeditorhtml.h codeeditors/qgscodeeditorjs.h codeeditors/qgscodeeditorjson.h codeeditors/qgscodeeditorpython.h + codeeditors/qgscodeeditorr.h codeeditors/qgscodeeditorsql.h devtools/qgsdevtoolwidget.h diff --git a/src/gui/codeeditors/qgscodeeditor.cpp b/src/gui/codeeditors/qgscodeeditor.cpp index 8f032f9f8ef4..5e2b6a0088db 100644 --- a/src/gui/codeeditors/qgscodeeditor.cpp +++ b/src/gui/codeeditors/qgscodeeditor.cpp @@ -20,6 +20,7 @@ #include "qgssymbollayerutils.h" #include "qgsgui.h" #include "qgscodeeditorcolorschemeregistry.h" +#include "qgscodeeditorhistorydialog.h" #include #include @@ -28,6 +29,8 @@ #include #include #include +#include +#include QMap< QgsCodeEditorColorScheme::ColorRole, QString > QgsCodeEditor::sColorRoleToSettingsKey { @@ -69,11 +72,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() ) { @@ -87,6 +91,8 @@ QgsCodeEditor::QgsCodeEditor( QWidget *parent, const QString &title, bool foldin if ( folding ) mFlags |= QgsCodeEditor::Flag::CodeFolding; + mSoftHistory.append( QString() ); + setSciWidget(); setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded ); @@ -104,6 +110,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 @@ -150,6 +181,34 @@ void QgsCodeEditor::keyPressEvent( QKeyEvent *event ) } } +void QgsCodeEditor::contextMenuEvent( QContextMenuEvent *event ) +{ + if ( mMode != QgsCodeEditor::Mode::CommandInput ) + { + QsciScintilla::contextMenuEvent( event ); + return; + } + + QMenu *menu = new QMenu( this ); + QMenu *historySubMenu = new QMenu( tr( "Command History" ), menu ); + + historySubMenu->addAction( tr( "Show" ), this, &QgsCodeEditor::showHistory, QStringLiteral( "Ctrl+Shift+SPACE" ) ); + historySubMenu->addAction( tr( "Clear File" ), this, &QgsCodeEditor::clearPersistentHistory ); + historySubMenu->addAction( tr( "Clear Session" ), this, &QgsCodeEditor::clearSessionHistory ); + + menu->addMenu( historySubMenu ); + menu->addSeparator(); + + QAction *copyAction = menu->addAction( QgsApplication::getThemeIcon( "mActionEditCopy.svg" ), tr( "Copy" ), this, &QgsCodeEditor::copy, QKeySequence::Copy ); + QAction *pasteAction = menu->addAction( QgsApplication::getThemeIcon( "mActionEditPaste.svg" ), tr( "Paste" ), this, &QgsCodeEditor::paste, QKeySequence::Paste ); + copyAction->setEnabled( hasSelectedText() ); + pasteAction->setEnabled( !QApplication::clipboard()->text().isEmpty() ); + + populateContextMenu( menu ); + + menu->exec( mapToGlobal( event->pos() ) ); +} + void QgsCodeEditor::initializeLexer() { @@ -270,6 +329,37 @@ void QgsCodeEditor::setTitle( const QString &title ) setWindowTitle( title ); } +Qgis::ScriptLanguage QgsCodeEditor::language() const +{ + return Qgis::ScriptLanguage::Unknown; +} + +QString QgsCodeEditor::languageToString( Qgis::ScriptLanguage language ) +{ + switch ( language ) + { + case Qgis::ScriptLanguage::Css: + return tr( "CSS" ); + case Qgis::ScriptLanguage::QgisExpression: + return tr( "Expression" ); + case Qgis::ScriptLanguage::Html: + return tr( "HTML" ); + case Qgis::ScriptLanguage::JavaScript: + return tr( "JavaScript" ); + case Qgis::ScriptLanguage::Json: + return tr( "JSON" ); + case Qgis::ScriptLanguage::Python: + return tr( "Python" ); + case Qgis::ScriptLanguage::R: + return tr( "R" ); + case Qgis::ScriptLanguage::Sql: + return tr( "SQL" ); + case Qgis::ScriptLanguage::Unknown: + return QString(); + } + BUILTIN_UNREACHABLE +} + void QgsCodeEditor::setMarginVisible( bool margin ) { mMargin = margin; @@ -335,7 +425,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 ) ); @@ -349,6 +439,162 @@ void QgsCodeEditor::updateFolding() } } +bool QgsCodeEditor::readHistoryFile() +{ + if ( mHistoryFilePath.isEmpty() || !QFile::exists( mHistoryFilePath ) ) + return false; + + QFile file( mHistoryFilePath ); + if ( file.open( QIODevice::ReadOnly ) ) + { + QTextStream stream( &file ); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + // Always use UTF-8 + stream.setCodec( "UTF-8" ); +#endif + QString line; + while ( !stream.atEnd() ) + { + line = stream.readLine(); // line of text excluding '\n' + mHistory.append( line ); + } + syncSoftHistory(); + return true; + } + + return false; +} + +void QgsCodeEditor::syncSoftHistory() +{ + mSoftHistory = mHistory; + mSoftHistory.append( QString() ); + mSoftHistoryIndex = mSoftHistory.length() - 1; +} + +void QgsCodeEditor::updateSoftHistory() +{ + mSoftHistory[mSoftHistoryIndex] = text(); +} + +void QgsCodeEditor::updateHistory( const QStringList &commands, bool skipSoftHistory ) +{ + if ( commands.size() > 1 ) + { + mHistory.append( commands ); + } + else if ( !commands.value( 0 ).isEmpty() ) + { + const QString command = commands.value( 0 ); + if ( mHistory.empty() || command != mHistory.constLast() ) + mHistory.append( command ); + } + + if ( !skipSoftHistory ) + syncSoftHistory(); +} + +void QgsCodeEditor::populateContextMenu( QMenu * ) +{ + +} + +QStringList QgsCodeEditor::history() const +{ + return mHistory; +} + +void QgsCodeEditor::runCommand( const QString & ) +{ + +} + +void QgsCodeEditor::clearSessionHistory() +{ + mHistory.clear(); + readHistoryFile(); + syncSoftHistory(); + + emit sessionHistoryCleared(); +} + +void QgsCodeEditor::clearPersistentHistory() +{ + mHistory.clear(); + + if ( !mHistoryFilePath.isEmpty() && QFile::exists( mHistoryFilePath ) ) + { + QFile file( mHistoryFilePath ); + file.open( QFile::WriteOnly | QFile::Truncate ); + } + + emit persistentHistoryCleared(); +} + +bool QgsCodeEditor::writeHistoryFile() +{ + if ( mHistoryFilePath.isEmpty() ) + return false; + + QFile f( mHistoryFilePath ); + if ( !f.open( QFile::WriteOnly | QIODevice::Truncate ) ) + { + return false; + } + + QTextStream ts( &f ); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + ts.setCodec( "UTF-8" ); +#endif + for ( const QString &command : std::as_const( mHistory ) ) + { + ts << command + '\n'; + } + return true; +} + +void QgsCodeEditor::showPreviousCommand() +{ + if ( mSoftHistoryIndex < mSoftHistory.length() - 1 && !mSoftHistory.isEmpty() ) + { + mSoftHistoryIndex += 1; + setText( mSoftHistory[mSoftHistoryIndex] ); + moveCursorToEnd(); + } +} + +void QgsCodeEditor::showNextCommand() +{ + if ( mSoftHistoryIndex > 0 && !mSoftHistory.empty() ) + { + mSoftHistoryIndex -= 1; + setText( mSoftHistory[mSoftHistoryIndex] ); + moveCursorToEnd(); + } +} + +void QgsCodeEditor::showHistory() +{ + QgsCodeEditorHistoryDialog *dialog = new QgsCodeEditorHistoryDialog( this, this ); + dialog->setAttribute( Qt::WA_DeleteOnClose ); + + dialog->show(); + dialog->activateWindow(); +} + +void QgsCodeEditor::removeHistoryCommand( int index ) +{ + // remove item from the command history (just for the current session) + mHistory.removeAt( index ); + mSoftHistory.removeAt( index ); + if ( index < mSoftHistoryIndex ) + { + mSoftHistoryIndex -= 1; + if ( mSoftHistoryIndex < 0 ) + mSoftHistoryIndex = mSoftHistory.length() - 1; + } +} + void QgsCodeEditor::insertText( const QString &text ) { // Insert the text or replace selected text @@ -547,6 +793,12 @@ bool QgsCodeEditor::isCursorOnLastLine() const return line == lines() - 1; } +void QgsCodeEditor::setHistoryFilePath( const QString &path ) +{ + mHistoryFilePath = path; + readHistoryFile(); +} + void QgsCodeEditor::moveCursorToStart() { setCursorPosition( 0, 0 ); diff --git a/src/gui/codeeditors/qgscodeeditor.h b/src/gui/codeeditors/qgscodeeditor.h index df4c300b11d8..965c81e0581d 100644 --- a/src/gui/codeeditors/qgscodeeditor.h +++ b/src/gui/codeeditors/qgscodeeditor.h @@ -19,6 +19,8 @@ #include #include "qgscodeeditorcolorscheme.h" +#include "qgis.h" + // qscintilla includes #include #include "qgis_sip.h" @@ -44,6 +46,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 +101,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 @@ -96,6 +112,20 @@ class GUI_EXPORT QgsCodeEditor : public QsciScintilla */ void setTitle( const QString & title ); + /** + * Returns the associated scripting language. + * + * \since QGIS 3.30 + */ + virtual Qgis::ScriptLanguage language() const; + + /** + * Returns a user-friendly, translated name of the specified script \a language. + * + * \since QGIS 3.30 + */ + static QString languageToString( Qgis::ScriptLanguage language ); + /** * Set margin visible state * \param margin Set margin in the editor @@ -215,6 +245,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. * @@ -222,8 +259,30 @@ class GUI_EXPORT QgsCodeEditor : public QsciScintilla */ bool isCursorOnLastLine() const; + /** + * Sets the file path to use for recording and retrieving previously + * executed commands. + * + * \note Applies to code editors in the QgsCodeEditor::Mode::CommandInput mode only. + * + * \since QGIS 3.30 + */ + void setHistoryFilePath( const QString &path ); + + + QStringList history() const; + public slots: + /** + * Runs a command in the editor. + * + * The base class method does nothing. + + * \since QGIS 3.30 + */ + virtual void runCommand( const QString &command ); + /** * Moves the cursor to the start of the document and scrolls to ensure * it is visible. @@ -240,12 +299,54 @@ class GUI_EXPORT QgsCodeEditor : public QsciScintilla */ virtual void moveCursorToEnd(); + /** + * Shows the previous command from the session in the editor. + * + * \note Applies to code editors in the QgsCodeEditor::Mode::CommandInput mode only. + * + * \since QGIS 3.30 + */ + void showPreviousCommand(); + + /** + * Shows the next command from the session in the editor. + * + * \note Applies to code editors in the QgsCodeEditor::Mode::CommandInput mode only. + * + * \since QGIS 3.30 + */ + void showNextCommand(); + + void showHistory(); + + void removeHistoryCommand( int index ); + + /** + * Clears the history of commands run in the current session. + * + * \note Applies to code editors in the QgsCodeEditor::Mode::CommandInput mode only. + * + * \since QGIS 3.30 + */ + void clearSessionHistory(); + + + void clearPersistentHistory(); + + bool writeHistoryFile(); + + signals: + + void sessionHistoryCleared(); + void persistentHistoryCleared(); + 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; + void contextMenuEvent( QContextMenuEvent *event ) override; /** * Called when the dialect specific code lexer needs to be initialized (or reinitialized). @@ -277,14 +378,23 @@ class GUI_EXPORT QgsCodeEditor : public QsciScintilla */ void runPostLexerConfigurationTasks(); + + void syncSoftHistory(); + void updateSoftHistory(); + void updateHistory( const QStringList &commands, bool skipSoftHistory = false ); + + virtual void populateContextMenu( QMenu *menu ); + private: void setSciWidget(); void updateFolding(); + bool readHistoryFile(); 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: @@ -296,6 +406,12 @@ class GUI_EXPORT QgsCodeEditor : public QsciScintilla QVector< int > mWarningLines; + // for use in command input mode + QStringList mHistory; + QStringList mSoftHistory; + int mSoftHistoryIndex = 0; + QString mHistoryFilePath; + static QMap< QgsCodeEditorColorScheme::ColorRole, QString > sColorRoleToSettingsKey; static constexpr int MARKER_NUMBER = 6; diff --git a/src/gui/codeeditors/qgscodeeditorcss.cpp b/src/gui/codeeditors/qgscodeeditorcss.cpp index 0cd9c43ed44d..889478250b6f 100644 --- a/src/gui/codeeditors/qgscodeeditorcss.cpp +++ b/src/gui/codeeditors/qgscodeeditorcss.cpp @@ -13,7 +13,6 @@ * * ***************************************************************************/ -#include "qgsapplication.h" #include "qgscodeeditorcss.h" #include @@ -36,6 +35,11 @@ QgsCodeEditorCSS::QgsCodeEditorCSS( QWidget *parent ) QgsCodeEditorCSS::initializeLexer(); } +Qgis::ScriptLanguage QgsCodeEditorCSS::language() const +{ + return Qgis::ScriptLanguage::Css; +} + void QgsCodeEditorCSS::initializeLexer() { QsciLexerCSS *lexer = new QgsQsciLexerCSS( this ); diff --git a/src/gui/codeeditors/qgscodeeditorcss.h b/src/gui/codeeditors/qgscodeeditorcss.h index 41e044c03e9a..f47e1ad22739 100644 --- a/src/gui/codeeditors/qgscodeeditorcss.h +++ b/src/gui/codeeditors/qgscodeeditorcss.h @@ -54,6 +54,7 @@ class GUI_EXPORT QgsCodeEditorCSS : public QgsCodeEditor //! Constructor for QgsCodeEditorCSS QgsCodeEditorCSS( QWidget *parent SIP_TRANSFERTHIS = nullptr ); + Qgis::ScriptLanguage language() const override; protected: void initializeLexer() override; diff --git a/src/gui/codeeditors/qgscodeeditorexpression.cpp b/src/gui/codeeditors/qgscodeeditorexpression.cpp index a7f518c64c80..31262fe807f6 100644 --- a/src/gui/codeeditors/qgscodeeditorexpression.cpp +++ b/src/gui/codeeditors/qgscodeeditorexpression.cpp @@ -13,9 +13,8 @@ * * ***************************************************************************/ -#include "qgsapplication.h" #include "qgscodeeditorexpression.h" -#include "qgssymbollayerutils.h" +#include "qgsexpression.h" #include #include @@ -31,6 +30,11 @@ QgsCodeEditorExpression::QgsCodeEditorExpression( QWidget *parent ) QgsCodeEditorExpression::initializeLexer(); // avoid cppcheck warning by explicitly specifying namespace } +Qgis::ScriptLanguage QgsCodeEditorExpression::language() const +{ + return Qgis::ScriptLanguage::QgisExpression; +} + void QgsCodeEditorExpression::setExpressionContext( const QgsExpressionContext &context ) { mVariables.clear(); diff --git a/src/gui/codeeditors/qgscodeeditorexpression.h b/src/gui/codeeditors/qgscodeeditorexpression.h index 5a5eb595838d..92cd2c873274 100644 --- a/src/gui/codeeditors/qgscodeeditorexpression.h +++ b/src/gui/codeeditors/qgscodeeditorexpression.h @@ -41,6 +41,8 @@ class GUI_EXPORT QgsCodeEditorExpression : public QgsCodeEditor //! Constructor for QgsCodeEditorExpression QgsCodeEditorExpression( QWidget *parent SIP_TRANSFERTHIS = nullptr ); + Qgis::ScriptLanguage language() const override; + /** * Variables and functions from this expression context will be added to * the API. diff --git a/src/gui/codeeditors/qgscodeeditorhistorydialog.cpp b/src/gui/codeeditors/qgscodeeditorhistorydialog.cpp new file mode 100644 index 000000000000..fb4b148a57ad --- /dev/null +++ b/src/gui/codeeditors/qgscodeeditorhistorydialog.cpp @@ -0,0 +1,125 @@ +/*************************************************************************** + qgscodeeditorhistorydialog.cpp + ---------------------- + begin : 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 "qgscodeeditorhistorydialog.h" +#include "qgscodeeditor.h" +#include +#include + +QgsCodeEditorHistoryDialog::QgsCodeEditorHistoryDialog( QgsCodeEditor *editor, QWidget *parent ) + : QDialog( parent ) + , mEditor( editor ) +{ + setupUi( this ); + + if ( mEditor ) + { + setWindowTitle( tr( "%1 Console - Command History" ).arg( QgsCodeEditor::languageToString( mEditor->language() ) ) ); + } + + listView->setToolTip( tr( "Double-click on item to execute" ) ); + + mModel = new CodeHistoryModel( listView ); + listView->setModel( mModel ); + + reloadHistory(); + + QShortcut *deleteShortcut = new QShortcut( QKeySequence( Qt::Key_Delete ), this ); + connect( deleteShortcut, &QShortcut::activated, this, &QgsCodeEditorHistoryDialog::deleteItem ); + connect( listView, &QListView::doubleClicked, this, &QgsCodeEditorHistoryDialog::runCommand ); + connect( mButtonReloadHistory, &QPushButton::clicked, this, & QgsCodeEditorHistoryDialog::reloadHistory ); + connect( mButtonSaveHistory, &QPushButton::clicked, this, & QgsCodeEditorHistoryDialog::saveHistory ); + connect( mButtonRunHistory, &QPushButton::clicked, this, &QgsCodeEditorHistoryDialog::executeSelectedHistory ); +} + +void QgsCodeEditorHistoryDialog::executeSelectedHistory() +{ + if ( !mEditor ) + return; + + QModelIndexList selection = listView->selectionModel()->selectedIndexes(); + std::sort( selection.begin(), selection.end() ); + for ( const QModelIndex &index : std::as_const( selection ) ) + { + mEditor->runCommand( index.data( Qt::DisplayRole ).toString() ); + } +} + +void QgsCodeEditorHistoryDialog::runCommand( const QModelIndex &index ) +{ + if ( !mEditor ) + return; + + mEditor->runCommand( index.data( Qt::DisplayRole ).toString() ); +} + +void QgsCodeEditorHistoryDialog::saveHistory() +{ + if ( !mEditor ) + return; + + mEditor->writeHistoryFile(); +} + +void QgsCodeEditorHistoryDialog::reloadHistory() +{ + if ( mEditor ) + { + mModel->setStringList( mEditor->history() ); + } + + listView->scrollToBottom(); + listView->setCurrentIndex( mModel->index( mModel->rowCount() - 1, 0 ) ); +} + +void QgsCodeEditorHistoryDialog::deleteItem() +{ + const QModelIndexList selection = listView->selectionModel()->selectedRows(); + if ( selection.empty() ) + return; + + QList< int > selectedRows; + selectedRows.reserve( selection.size() ); + for ( const QModelIndex &index : selection ) + selectedRows << index.row(); + std::sort( selectedRows.begin(), selectedRows.end(), std::greater< int >() ); + + for ( int row : std::as_const( selectedRows ) ) + { + if ( mEditor ) + mEditor->removeHistoryCommand( row ); + + // Remove row from the command history dialog + mModel->removeRow( row ); + } +} + +///@cond PRIVATE +CodeHistoryModel::CodeHistoryModel( QObject *parent ) + : QStringListModel( parent ) +{ + mFont = QgsCodeEditor::getMonospaceFont(); +} + +QVariant CodeHistoryModel::data( const QModelIndex &index, int role ) const +{ + if ( role == Qt::FontRole ) + { + return mFont; + } + + return QStringListModel::data( index, role ); +} +///@endcond diff --git a/src/gui/codeeditors/qgscodeeditorhistorydialog.h b/src/gui/codeeditors/qgscodeeditorhistorydialog.h new file mode 100644 index 000000000000..b6dc1bf9f485 --- /dev/null +++ b/src/gui/codeeditors/qgscodeeditorhistorydialog.h @@ -0,0 +1,84 @@ +/*************************************************************************** + qgscodeeditorhistorydialog.h + ---------------------- + begin : 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 QGSCODEEDITORHISTORYDIALOG_H +#define QGSCODEEDITORHISTORYDIALOG_H + +#include "ui_qgscodeditorhistorydialogbase.h" +#include +#include +#include +#include "qgis_gui.h" +#include "qgis_sip.h" + +#define SIP_NO_FILE + +class QgsCodeEditor; + + +///@cond PRIVATE + +class CodeHistoryModel : public QStringListModel +{ + Q_OBJECT + + public: + CodeHistoryModel( QObject *parent ); + QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override; + + private: + + QFont mFont; +}; + +///@endcond + + +/** + * \ingroup gui + * \class QgsCodeEditorHistoryDialog + * \brief A dialog for displaying and managing command history for a QgsCodeEditor widget. + * \note Not available in Python bindings + * \since QGIS 3.30 + */ +class GUI_EXPORT QgsCodeEditorHistoryDialog : public QDialog, private Ui::QgsCodeEditorHistoryDialogBase +{ + Q_OBJECT + + public: + + /** + * Constructor for QgsCodeEditorHistoryDialog. + * \param editor associated code editor widget + * \param parent parent widget + */ + QgsCodeEditorHistoryDialog( QgsCodeEditor *editor, QWidget *parent SIP_TRANSFERTHIS = nullptr ); + + private slots: + + void executeSelectedHistory(); + void runCommand( const QModelIndex &index ); + void saveHistory(); + void reloadHistory(); + void deleteItem(); + + private: + + QPointer< QgsCodeEditor > mEditor; + CodeHistoryModel *mModel = nullptr; + +}; + +#endif // QGSCODEEDITORHISTORYDIALOG_H diff --git a/src/gui/codeeditors/qgscodeeditorhtml.cpp b/src/gui/codeeditors/qgscodeeditorhtml.cpp index 3bc18e18614a..2697296a83bb 100644 --- a/src/gui/codeeditors/qgscodeeditorhtml.cpp +++ b/src/gui/codeeditors/qgscodeeditorhtml.cpp @@ -13,9 +13,7 @@ * * ***************************************************************************/ -#include "qgsapplication.h" #include "qgscodeeditorhtml.h" -#include "qgssymbollayerutils.h" #include #include @@ -37,6 +35,11 @@ QgsCodeEditorHTML::QgsCodeEditorHTML( QWidget *parent ) QgsCodeEditorHTML::initializeLexer(); } +Qgis::ScriptLanguage QgsCodeEditorHTML::language() const +{ + return Qgis::ScriptLanguage::Html; +} + void QgsCodeEditorHTML::initializeLexer() { QFont font = lexerFont(); diff --git a/src/gui/codeeditors/qgscodeeditorhtml.h b/src/gui/codeeditors/qgscodeeditorhtml.h index bed9209759ae..862c29cafbf6 100644 --- a/src/gui/codeeditors/qgscodeeditorhtml.h +++ b/src/gui/codeeditors/qgscodeeditorhtml.h @@ -38,6 +38,8 @@ class GUI_EXPORT QgsCodeEditorHTML : public QgsCodeEditor //! Constructor for QgsCodeEditorHTML QgsCodeEditorHTML( QWidget *parent SIP_TRANSFERTHIS = nullptr ); + Qgis::ScriptLanguage language() const override; + protected: void initializeLexer() override; }; diff --git a/src/gui/codeeditors/qgscodeeditorjs.cpp b/src/gui/codeeditors/qgscodeeditorjs.cpp index 41cb7c53f90b..7c40be898ef7 100644 --- a/src/gui/codeeditors/qgscodeeditorjs.cpp +++ b/src/gui/codeeditors/qgscodeeditorjs.cpp @@ -13,7 +13,6 @@ * * ***************************************************************************/ -#include "qgsapplication.h" #include "qgscodeeditorjs.h" #include @@ -36,6 +35,11 @@ QgsCodeEditorJavascript::QgsCodeEditorJavascript( QWidget *parent ) QgsCodeEditorJavascript::initializeLexer(); } +Qgis::ScriptLanguage QgsCodeEditorJavascript::language() const +{ + return Qgis::ScriptLanguage::JavaScript; +} + void QgsCodeEditorJavascript::initializeLexer() { QsciLexerJavaScript *lexer = new QsciLexerJavaScript( this ); diff --git a/src/gui/codeeditors/qgscodeeditorjs.h b/src/gui/codeeditors/qgscodeeditorjs.h index 370a99f48d34..b1849f62df3b 100644 --- a/src/gui/codeeditors/qgscodeeditorjs.h +++ b/src/gui/codeeditors/qgscodeeditorjs.h @@ -37,6 +37,7 @@ class GUI_EXPORT QgsCodeEditorJavascript : public QgsCodeEditor //! Constructor for QgsCodeEditorJavascript QgsCodeEditorJavascript( QWidget *parent SIP_TRANSFERTHIS = nullptr ); + Qgis::ScriptLanguage language() const override; protected: void initializeLexer() override; diff --git a/src/gui/codeeditors/qgscodeeditorjson.cpp b/src/gui/codeeditors/qgscodeeditorjson.cpp index c1980c336a23..df5a381beed3 100644 --- a/src/gui/codeeditors/qgscodeeditorjson.cpp +++ b/src/gui/codeeditors/qgscodeeditorjson.cpp @@ -13,7 +13,6 @@ * * ***************************************************************************/ -#include "qgsapplication.h" #include "qgscodeeditorjson.h" #include @@ -36,6 +35,11 @@ QgsCodeEditorJson::QgsCodeEditorJson( QWidget *parent ) QgsCodeEditorJson::initializeLexer(); } +Qgis::ScriptLanguage QgsCodeEditorJson::language() const +{ + return Qgis::ScriptLanguage::Json; +} + void QgsCodeEditorJson::initializeLexer() { QsciLexerJSON *lexer = new QsciLexerJSON( this ); diff --git a/src/gui/codeeditors/qgscodeeditorjson.h b/src/gui/codeeditors/qgscodeeditorjson.h index f7a1a175fa39..43008a4b15c3 100644 --- a/src/gui/codeeditors/qgscodeeditorjson.h +++ b/src/gui/codeeditors/qgscodeeditorjson.h @@ -38,6 +38,8 @@ class GUI_EXPORT QgsCodeEditorJson : public QgsCodeEditor //! Constructor for QgsCodeEditorJson QgsCodeEditorJson( QWidget *parent SIP_TRANSFERTHIS = nullptr ); + Qgis::ScriptLanguage language() const override; + protected: void initializeLexer() override; diff --git a/src/gui/codeeditors/qgscodeeditorpython.cpp b/src/gui/codeeditors/qgscodeeditorpython.cpp index d91581b3062f..6dabd3499ac3 100644 --- a/src/gui/codeeditors/qgscodeeditorpython.cpp +++ b/src/gui/codeeditors/qgscodeeditorpython.cpp @@ -30,12 +30,12 @@ #include #include -QgsCodeEditorPython::QgsCodeEditorPython( QWidget *parent, const QList &filenames ) +QgsCodeEditorPython::QgsCodeEditorPython( QWidget *parent, const QList &filenames, Mode mode ) : QgsCodeEditor( parent, QString(), false, false, - QgsCodeEditor::Flag::CodeFolding ) + QgsCodeEditor::Flag::CodeFolding, mode ) , mAPISFilesList( filenames ) { if ( !parent ) @@ -48,6 +48,11 @@ QgsCodeEditorPython::QgsCodeEditorPython( QWidget *parent, const QList QgsCodeEditorPython::initializeLexer(); } +Qgis::ScriptLanguage QgsCodeEditorPython::language() const +{ + return Qgis::ScriptLanguage::Python; +} + void QgsCodeEditorPython::initializeLexer() { // current line diff --git a/src/gui/codeeditors/qgscodeeditorpython.h b/src/gui/codeeditors/qgscodeeditorpython.h index d4b10bf95872..5562f0a37869 100644 --- a/src/gui/codeeditors/qgscodeeditorpython.h +++ b/src/gui/codeeditors/qgscodeeditorpython.h @@ -58,7 +58,10 @@ class GUI_EXPORT QgsCodeEditorPython : public QgsCodeEditor * \param filenames The list of apis files to load for the Python lexer * \since QGIS 2.6 */ - QgsCodeEditorPython( QWidget *parent SIP_TRANSFERTHIS = nullptr, const QList &filenames = QList() ); + QgsCodeEditorPython( QWidget *parent SIP_TRANSFERTHIS = nullptr, const QList &filenames = QList(), + QgsCodeEditor::Mode mode = QgsCodeEditor::Mode::ScriptEditor ); + + Qgis::ScriptLanguage language() const override; /** * Load APIs from one or more files diff --git a/src/gui/codeeditors/qgscodeeditorr.cpp b/src/gui/codeeditors/qgscodeeditorr.cpp new file mode 100644 index 000000000000..a5ac37528488 --- /dev/null +++ b/src/gui/codeeditors/qgscodeeditorr.cpp @@ -0,0 +1,198 @@ +/*************************************************************************** + 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, Mode mode ) + : QgsCodeEditor( parent, QString(), false, false, QgsCodeEditor::Flag::CodeFolding, mode ) +{ + if ( !parent ) + { + setTitle( tr( "R Editor" ) ); + } + QgsCodeEditorR::initializeLexer(); +} + +Qgis::ScriptLanguage QgsCodeEditorR::language() const +{ + return Qgis::ScriptLanguage::R; +} + +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..fba0ef243a19 --- /dev/null +++ b/src/gui/codeeditors/qgscodeeditorr.h @@ -0,0 +1,87 @@ +/*************************************************************************** + 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, QgsCodeEditor::Mode mode = QgsCodeEditor::Mode::ScriptEditor ); + Qgis::ScriptLanguage language() const override; + + protected: + void initializeLexer() override; + +}; + +#endif // QGSCODEEDITORR_H diff --git a/src/gui/codeeditors/qgscodeeditorsql.cpp b/src/gui/codeeditors/qgscodeeditorsql.cpp index 46cf6971b8cd..5e3edcccdeac 100644 --- a/src/gui/codeeditors/qgscodeeditorsql.cpp +++ b/src/gui/codeeditors/qgscodeeditorsql.cpp @@ -13,9 +13,7 @@ * * ***************************************************************************/ -#include "qgsapplication.h" #include "qgscodeeditorsql.h" -#include "qgssymbollayerutils.h" #include #include @@ -33,6 +31,11 @@ QgsCodeEditorSQL::QgsCodeEditorSQL( QWidget *parent ) QgsCodeEditorSQL::initializeLexer(); // avoid cppcheck warning by explicitly specifying namespace } +Qgis::ScriptLanguage QgsCodeEditorSQL::language() const +{ + return Qgis::ScriptLanguage::Sql; +} + QgsCodeEditorSQL::~QgsCodeEditorSQL() { if ( mApis ) diff --git a/src/gui/codeeditors/qgscodeeditorsql.h b/src/gui/codeeditors/qgscodeeditorsql.h index 2f50bf8c89e7..92859139e4bc 100644 --- a/src/gui/codeeditors/qgscodeeditorsql.h +++ b/src/gui/codeeditors/qgscodeeditorsql.h @@ -39,6 +39,7 @@ class GUI_EXPORT QgsCodeEditorSQL : public QgsCodeEditor //! Constructor for QgsCodeEditorSQL QgsCodeEditorSQL( QWidget *parent SIP_TRANSFERTHIS = nullptr ); + Qgis::ScriptLanguage language() const override; virtual ~QgsCodeEditorSQL(); diff --git a/python/console/console_history_dlg.ui b/src/ui/qgscodeditorhistorydialogbase.ui similarity index 90% rename from python/console/console_history_dlg.ui rename to src/ui/qgscodeditorhistorydialogbase.ui index 3c6e0aff5351..116fe5a536f5 100644 --- a/python/console/console_history_dlg.ui +++ b/src/ui/qgscodeditorhistorydialogbase.ui @@ -1,7 +1,7 @@ - HistoryDialogPythonConsole - + QgsCodeEditorHistoryDialogBase + 0 @@ -27,7 +27,7 @@ 4 - + Reload @@ -37,7 +37,7 @@ - + true @@ -65,7 +65,7 @@ - + Run @@ -124,7 +124,7 @@ buttonBox accepted() - HistoryDialogPythonConsole + QgsCodeEditorHistoryDialogBase accept() @@ -140,7 +140,7 @@ buttonBox rejected() - HistoryDialogPythonConsole + QgsCodeEditorHistoryDialogBase reject() 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 diff --git a/tests/src/app/CMakeLists.txt b/tests/src/app/CMakeLists.txt index 6016597ef072..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 @@ -55,6 +54,11 @@ if (WITH_BINDINGS) set(TESTS ${TESTS} testqgisapppython.cpp) endif() +if (WITH_R) + add_compile_definitions(RINSIDE_CALLBACKS=1) + 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..5cba61c536c1 --- /dev/null +++ b/tests/src/app/testqgsrstats.cpp @@ -0,0 +1,178 @@ +/*************************************************************************** + 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 + +#include "qgstest.h" + +#include "qgisapp.h" +#include "qgsapplication.h" +#include "rstats/qgsrstatsrunner.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. + void testSexpToVariant(); + void testSexpToString(); + void testVariantToSexp(); + + private: + QString mTestDataDir; + QgisApp *mQgisApp; + std::unique_ptr< QgsRStatsSession > mSession; +}; + +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(); + + 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\" \"\" \"\" " ) ); +} + +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"