diff --git a/CMakeLists.txt b/CMakeLists.txt index e17aec8..10a5ad5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,8 @@ option( # ============ find_package(xeus-zmq REQUIRED) +find_package(xwidgets REQUIRED) +find_package(xproperty REQUIRED) find_package(PNG REQUIRED) find_package(glad REQUIRED) find_package(glfw3) @@ -112,6 +114,7 @@ set( include/xeus-octave/plotstream.hpp include/xeus-octave/tex2html.hpp include/xeus-octave/display.hpp + include/xeus-octave/xwidgets.hpp ) set( @@ -122,6 +125,7 @@ set( src/xinterpreter.cpp src/input.cpp src/output.cpp + src/xwidgets.cpp ) set(XEUS_OCTAVE_MAIN_SRC src/main.cpp) @@ -226,7 +230,7 @@ macro(xeus_octave_create_target target_name linkage output_name) target_link_libraries( ${target_name} - PUBLIC xtl PkgConfig::octinterp + PUBLIC xtl xwidgets PkgConfig::octinterp PRIVATE glad::glad glfw PNG::PNG ) if(XEUS_OCTAVE_USE_SHARED_XEUS) diff --git a/environment-dev.yml b/environment-dev.yml index cd69249..e88551e 100644 --- a/environment-dev.yml +++ b/environment-dev.yml @@ -3,6 +3,7 @@ channels: dependencies: # Build dependencies - cxx-compiler + - fortran-compiler - c-compiler - cmake - make @@ -11,6 +12,7 @@ dependencies: - libuuid - xtl - xeus-zmq =1.* + - xwidgets =0.27.3 - nlohmann_json - cppzmq - octave =7.* @@ -37,4 +39,5 @@ dependencies: - cmake-format - plotly - ipywidgets + - widgetsnbextension =3.6.1 - jupyter-dash diff --git a/include/xeus-octave/plotstream.hpp b/include/xeus-octave/plotstream.hpp index b2537c2..ea28031 100644 --- a/include/xeus-octave/plotstream.hpp +++ b/include/xeus-octave/plotstream.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Giulio Girardi. + * Copyright (C) 2022 Giulio Girardi. * * This file is part of xeus-octave. * @@ -22,23 +22,81 @@ #include +#include + +namespace oc = octave; + namespace xeus_octave { -inline std::string getPlotStream(octave::graphics_object const& o) +template inline T getPlotStream(oc::graphics_object const& o); + +/** + * Retrieve from the graphics object the plot_stream property + */ +template <> inline xw::image* getPlotStream(oc::graphics_object const& o) +{ + octave_value ps = + dynamic_cast(o.get_ancestor("figure").get_properties()).get___plot_stream__(); + + if (ps.isnumeric() && ps.is_scalar_type()) + return reinterpret_cast(ps.long_value()); + else + return nullptr; +} + +/** + * Retrieve from the graphics object the plot_stream property + */ +template <> inline std::string getPlotStream(oc::graphics_object const& o) +{ + octave_value ps = + dynamic_cast(o.get_ancestor("figure").get_properties()).get___plot_stream__(); + + if (ps.is_string()) + return ps.string_value(); + else + return ""; +} + +/** + * Set in the graphics object the plot_stream propert + */ +inline void setPlotStream(oc::graphics_object& o, xw::image* p) { - return dynamic_cast(o.get_ancestor("figure").get_properties()) - .get___plot_stream__() - .string_value(); + if (o.isa("figure")) + { + auto& fp = dynamic_cast(o.get_properties()); + fp.set___plot_stream__(reinterpret_cast(p)); + } } -inline void setPlotStream(octave::graphics_object& o, std::string p) +/** + * Set in the graphics object the plot_stream propert + */ +inline void setPlotStream(oc::graphics_object& o, std::string p) { if (o.isa("figure")) - dynamic_cast(o.get_properties()).set___plot_stream__(p); + { + auto& fp = dynamic_cast(o.get_properties()); + fp.set___plot_stream__(p); + } +} + +/** + * Set in the graphics object the plot_stream propert (const version) + */ +inline void setPlotStream(oc::graphics_object const& o, xw::image* p) +{ + // deCONSTify the graphics_object + auto _go = o; + setPlotStream(_go, p); } -inline void setPlotStream(octave::graphics_object const& o, std::string p) +/** + * Set in the graphics object the plot_stream propert (const version) + */ +inline void setPlotStream(oc::graphics_object const& o, std::string p) { // deCONSTify the graphics_object auto _go = o; @@ -47,4 +105,4 @@ inline void setPlotStream(octave::graphics_object const& o, std::string p) } // namespace xeus_octave -#endif // XEUS_OCTAVE_PLOTSTREAM_H +#endif diff --git a/include/xeus-octave/tk_notebook.hpp b/include/xeus-octave/tk_notebook.hpp index 1ff737e..9dd4c15 100644 --- a/include/xeus-octave/tk_notebook.hpp +++ b/include/xeus-octave/tk_notebook.hpp @@ -22,26 +22,53 @@ #include #include +#include #include "xeus-octave/config.hpp" namespace xeus_octave::tk::notebook { -class notebook_graphics_toolkit : public octave::base_graphics_toolkit +class glfw_graphics_toolkit : public octave::base_graphics_toolkit +{ +public: + + glfw_graphics_toolkit(std::string const& nm); + ~glfw_graphics_toolkit(); + + bool initialize(octave::graphics_object const&) override; + void redraw_figure(octave::graphics_object const&) const override; + virtual void send_figure( + octave::graphics_object const& go, std::vector const& img, int width, int height, double dpr + ) const = 0; +}; + +class notebook_graphics_toolkit : public glfw_graphics_toolkit { public: notebook_graphics_toolkit(); - ~notebook_graphics_toolkit(); bool is_valid() const override { return true; } bool initialize(octave::graphics_object const&) override; - void redraw_figure(octave::graphics_object const&) const override; + void send_figure(octave::graphics_object const& go, std::vector const& img, int width, int height, double dpr) + const override; void show_figure(octave::graphics_object const&) const override; - void update(octave::graphics_object const&, int) override; +}; + +class widget_graphics_toolkit : public glfw_graphics_toolkit +{ +public: + + widget_graphics_toolkit(); + bool is_valid() const override { return true; } + + bool initialize(octave::graphics_object const&) override; + void send_figure(octave::graphics_object const& go, std::vector const& img, int width, int height, double dpr) + const override; + void show_figure(octave::graphics_object const&) const override; void finalize(octave::graphics_object const&) override; }; diff --git a/include/xeus-octave/utils.hpp b/include/xeus-octave/utils.hpp index 4bfe70f..75606f5 100644 --- a/include/xeus-octave/utils.hpp +++ b/include/xeus-octave/utils.hpp @@ -17,11 +17,16 @@ * along with xeus-octave. If not, see . */ -#ifndef XEUS_OCTAVE_UTILS_HPP -#define XEUS_OCTAVE_UTILS_HPP +#ifndef XEUS_OCTAVE_UTILS_H +#define XEUS_OCTAVE_UTILS_H +#include +#include +#include +#include #include #include +#include #include #include #include @@ -30,10 +35,220 @@ #include #include +#include +#include + +#include "xeus-octave/plotstream.hpp" namespace xeus_octave::utils { +/** + * Convert octave_value to any generic type. Fails at runtime if not implemented in + * octave default conversions + */ +template inline void from_ov(octave_value const&, T&, octave::interpreter&) +{ + static_assert(!sizeof(T*), "Please implement a proper conversion function"); +} + +/** + * Convert octave_value to int + */ +inline void from_ov(octave_value const& from, int& to, octave::interpreter&) +{ + to = from.int_value(); +} + +/** + * Convert octave_value to bool + */ +inline void from_ov(octave_value const& from, bool& to, octave::interpreter&) +{ + to = from.bool_value(); +} + +/** + * Convert octave_value to std::string + */ +inline void from_ov(octave_value const& from, std::string& to, octave::interpreter&) +{ + to = from.string_value(); +} + +/** + * Convert octave_value to double + */ +inline void from_ov(octave_value const& from, double& to, octave::interpreter&) +{ + to = from.scalar_value(); +} + +/** + * Convert octave_value to xguid. + * Used for xwidgets + */ +inline void from_ov(octave_value const& from, xeus::xguid& to, octave::interpreter& interpreter) +{ + std::string _to; + from_ov(from, _to, interpreter); + to = xeus::xguid(_to); +} + +/** + * Convert octave_value to widget (holder) + */ +inline void from_ov(octave_value const& from, xw::xholder& to, octave::interpreter& interpreter) +{ + // This is an xwidget + if (from.is_classdef_object()) + { + octave_classdef* cls = from.classdef_object_value(); + octave_value_list ret = cls->subsref(".", {ovl("id")}, 1); + xeus::xguid id; + + from_ov(ret(0), id, interpreter); + to = xw::make_id_holder(id); + } + // This is a figure handle + else if (from.is_real_scalar()) + { + // Get the handle + graphics_handle gh = interpreter.get_gh_manager().lookup(from); + + if (gh.ok()) + { + // Get the object + octave::graphics_object go = interpreter.get_gh_manager().get_object(gh); + // Change the graphics_toolkit to widget + auto& figureProperties = dynamic_cast(go.get_properties()); + figureProperties.set___graphics_toolkit__("widget"); + auto* figure = getPlotStream(go); + + assert(figure != nullptr); + + to = xw::make_id_holder(figure->id()); + } + } +} + +/** + * Convert octave value to a generic vector + */ +template inline void from_ov(octave_value const& from, std::vector& to, octave::interpreter& interpreter) +{ + Cell cell = from.cell_value(); + + for (int i = 0; i < cell.numel(); i++) + { + T element; + from_ov(cell(0, i), element, interpreter); + to.push_back(element); + } +} + +/** + * Convert octave_value to a vector of chars (i.e. byte array) + */ +inline void from_ov(octave_value const& from, std::vector& to, octave::interpreter&) +{ + if (from.is_string()) + { + std::string _to = from.string_value(); + std::copy(_to.begin(), _to.end(), std::back_inserter(to)); + } + else + { + auto array = from.uint8_array_value(); + for (int i = 0; i < array.numel(); i++) + to.push_back(array.xelem(i).char_value()); + } +} + +/** + * Convert octave_value to a xtl::xoptional generic value + */ +template +inline void from_ov(octave_value const& from, xtl::xoptional& to, octave::interpreter& interpreter) +{ + if (!from.isnull()) + { + T _to; + from_ov(from, _to, interpreter); + to = _to; + } + else + to = xtl::xoptional(); +} + +/** + * Convert generic type to octave_value + */ +template inline void to_ov(octave_value& to, T const& from, octave::interpreter&) +{ + to = octave_value(from); +} + +/** + * Convert widget to octave_value. + * Simply returns string containing id + */ +inline void to_ov(octave_value& to, xw::xholder const& from, octave::interpreter&) +{ + to = octave_value(from.id().c_str()); +} + +/** + * Convert a generic std::vector to octave_value (cell array) + */ +template inline void to_ov(octave_value& to, std::vector const& from, octave::interpreter& interpreter) +{ + Cell a; + octave_idx_type i = 0; + + a.resize(dim_vector(static_cast(from.size()), 1)); + + for (auto e : from) + to_ov(a.elem(i++, 0), e, interpreter); + + to = a; +} + +/** + * Convert a vector of char (i.e. bytearray) to octave_value (uint8 matrix) + */ +inline void to_ov(octave_value& to, std::vector const& from, octave::interpreter&) +{ + auto dv = dim_vector(1, static_cast(from.size())); + uint8NDArray _to(Array(from, dv)); + to = _to; +} + +/** + * Convert a generic xtl::xoptional to octave_value (value or null matrix) + */ +template inline void to_ov(octave_value& to, xtl::xoptional const& from, octave::interpreter& interpreter) +{ + if (from.has_value()) + to_ov(to, from.value(), interpreter); + else + to = octave_null_matrix::instance; +} + +/** + * Convert a xguid to octave_value (sq_string) + */ +inline void to_ov(octave_value& to, xeus::xguid const& from, octave::interpreter& interpreter) +{ + to_ov(to, std::string(from), interpreter); +} + +template inline octave_value make_fcn_handle(M ff, std::string const& nm) +{ + octave_value fcn(new octave_builtin(ff, nm)); + return octave_value(new octave_fcn_handle(fcn)); +} + inline void add_native_binding(octave::interpreter& interpreter, std::string const& name, octave_builtin::fcn ff) { octave_builtin* fcn = new octave_builtin(ff, name, __FILE__, ""); diff --git a/include/xeus-octave/xinterpreter.hpp b/include/xeus-octave/xinterpreter.hpp index ca265d8..56ef5c8 100644 --- a/include/xeus-octave/xinterpreter.hpp +++ b/include/xeus-octave/xinterpreter.hpp @@ -39,6 +39,11 @@ class xoctave_interpreter : public xeus::xinterpreter octave::interpreter interpreter; +public: + + xoctave_interpreter(); + virtual ~xoctave_interpreter() = default; + private: void configure_impl() override; diff --git a/include/xeus-octave/xwidgets.hpp b/include/xeus-octave/xwidgets.hpp new file mode 100644 index 0000000..ddc503d --- /dev/null +++ b/include/xeus-octave/xwidgets.hpp @@ -0,0 +1,372 @@ +/* + * Copyright (C) 2022 Giulio Girardi. + * + * This file is part of xeus-octave. + * + * xeus-octave is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * xeus-octave 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 xeus-octave. If not, see . + */ + +#ifndef XEUS_OCTAVE_XWIDGETS_H +#define XEUS_OCTAVE_XWIDGETS_H + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "xeus-octave/utils.hpp" + +namespace xeus_octave::widgets +{ + +constexpr inline char const* XWIDGETS_POINTER_PROPERTY = "__pointer__"; + +template struct xwidgets_member_trait; + +template struct xwidgets_member_trait +{ + using widget_type = W; +}; + +template struct widget +{ + inline static constexpr char const* name = nullptr; + + inline static void register_widget_members(octave::interpreter&, octave::cdef_class&){}; +}; + +template inline octave::cdef_class xwidget_get_class(octave::cdef_manager& cm) + +{ + using widget_type = W; + + // Class does not correspond to an octave cdef + if constexpr (!std::is_same::value) + if (widget::name == nullptr) + return xwidget_get_class(cm); + + octave::cdef_class cls = cm.find_class(widget::name, false, false); + + // Class has already been created + if (cls.ok()) + return cls; + + // Class needs to be created + if constexpr (!std::is_same::value) + return cm.make_class(widget::name, xwidget_get_class(cm)); + else + return cm.make_class(widget::name, cm.find_class("handle")); +} + +/** + * Recursively call the functions to register all members of the cdef object + * belonging to a specific class + */ +template inline void xwidget_register_members(octave::interpreter& interpreter, octave::cdef_class& cls) +{ + using widget_type = W; + + widget::register_widget_members(interpreter, cls); + + if constexpr (!std::is_same::value) + xwidget_register_members(interpreter, cls); +} + +/** + * Installs a method in the cdef object. + */ +template +inline void xwidgets_add_method(octave::interpreter& interpreter, octave::cdef_class& cls, std::string name, M ff) +{ + octave::cdef_manager& cm = interpreter.get_cdef_manager(); + + cls.install_method(cm.make_method(cls, name, ff)); +} + +/** + * Retrieves from a cdef object the pointer to the xwidget class instance + */ +template inline W* get_widget(octave_classdef const* cls) +{ + return reinterpret_cast(cls->get_property(0, XWIDGETS_POINTER_PROPERTY).ulong_value()); +} + +/** + * Stores in a cdef object a pointer to an xwidget class instance + */ +template inline void set_widget(octave_classdef* cls, W const* wdg) +{ + cls->set_property(0, XWIDGETS_POINTER_PROPERTY, reinterpret_cast(wdg)); +} + +/** + * Octave cdef constructor, it simply instanciates the corresponding xwidget class + * and stores the pointer in the cdef object. + * + * The xwidget class is instanciated dynamically + */ +template inline octave_value_list constructor(octave_value_list const& args, int) +{ + using widget_type = W; + + set_widget(args(0).classdef_object_value(), new widget_type); + return args; +} + +/** + * Retrieves the xwidget instance from the cdef object and destroys it + */ +template inline octave_value_list destructor(octave_value_list const& args, int) +{ + using widget_type = W; + + delete get_widget(args(0).classdef_object_value()); + return ovl(); +} + +/** + * Set an xproperty value from an octave_value, performing automatic conversion + * under the assumption that the corresponding from_ov function is defined + */ +template +inline void set_property(xp::xproperty& property, octave_value const& value, octave::interpreter& interpreter) +{ + using namespace utils; + using value_type = V; + + value_type pvalue; + from_ov(value, pvalue, interpreter); + property = pvalue; +} + +/** + * cdef object property setter function + */ +template +inline octave_value_list set_property(octave::interpreter& interpreter, octave_value_list const& args, int) +{ + using property_type = decltype(P); + using widget_type = typename xwidgets_member_trait::widget_type; + + std::string name(N); + // Get cdef object + octave_classdef* cls = args(0).classdef_object_value(); + // Retrieve xwidget instance + widget_type* w = get_widget(cls); + + // If the variable is cached set a cdef "static" property as cache + if constexpr (C) + cls->set_property(0, name + "_cache", args(1)); + + // Set the xwidget property + set_property(w->*P, args(1), interpreter); + return ovl(); +} + +/** + * Get an octave value for an xproperty actual value, performing automatic conversion + * under the assumption that the corresponding to_ function is defined + */ +template +inline octave_value get_property(xp::xproperty& property, octave::interpreter& interpreter) +{ + using namespace utils; + + octave_value value; + to_ov(value, property(), interpreter); + return value; +} + +/** + * cdef object property getter function + */ +template +inline octave_value_list get_property(octave::interpreter& interpreter, octave_value_list const& args, int) +{ + using property_type = decltype(P); + using widget_type = typename xwidgets_member_trait::widget_type; + + std::string name(N); + // Get cdef object + octave_classdef* cls = args(0).classdef_object_value(); + // Retrieve xwidget instance + widget_type* w = get_widget(cls); + + // If the variable is cached return the cache value instead of converting the + // actual xproperty value + if constexpr (C) + return cls->get_property(0, name + "_cache"); + else + return get_property(w->*P, interpreter); +} + +/** + * cdef object observer function + */ +template inline octave_value_list observe(octave::interpreter& interpreter, octave_value_list const& args, int) +{ + using widget_type = typename xwidgets_member_trait::widget_type; + + // Get cdef object + octave_classdef* cls = args(0).classdef_object_value(); + // Retrieve xwidget instance + widget_type* w = get_widget(cls); + // Property instance + auto p = w->*P; + + // Register the xwidget observer + w->observe( + p.name(), + [cls = args(0), // cdef object + cb = args(1), // Callback object + args = args.slice(2, args.length() - 2), // Other parameters + &interpreter](auto const&) + { + octave_value_list cb_args; + + // Prepare the parameters passed to the callback + cb_args(0) = cls; // The cdef object + cb_args.append(args); // All the other parameters + + // Evaluate the callback + interpreter.feval(cb, cb_args); + } + ); + + return ovl(); +} + +/** + * Cdef function to set a callback + */ +template +inline octave_value_list set_callback(octave::interpreter& interpreter, octave_value_list const& args, int) +{ + using property_type = decltype(P); + using widget_type = typename xwidgets_member_trait::widget_type; + + // Get cdef object + octave_classdef* cls = args(0).classdef_object_value(); + // Retrieve xwidget instance + widget_type* w = get_widget(cls); + + (w->*P)( + [cls = args(0), // cdef object + cb = args(1), // Callback object + args = args.slice(2, args.length() - 2), // Other parameters + &interpreter]() + { + octave_value_list cb_args; + + // Prepare the parameters passed to the callback + cb_args(0) = cls; // The cdef object + cb_args.append(args); // All the other parameters + + // Evaluate the callback + interpreter.feval(cb, cb_args); + } + ); + + return ovl(); +} + +/** + * Add a function to register a callback to the cdef class + */ +template +inline void xwidgets_add_callback(octave::interpreter& interpreter, octave::cdef_class& cls, std::string name) +{ + + octave::cdef_manager& cm = interpreter.get_cdef_manager(); + + // Add basic methods + cls.install_method(cm.make_method(cls, name, set_callback

)); +} + +/** + * Link a xwidget property with a cdef property + */ +template +inline void xwidgets_add_property(octave::interpreter& interpreter, octave::cdef_class& cls) +{ + using namespace xeus_octave::utils; + using property_type = decltype(P); + using widget_type = typename xwidgets_member_trait::widget_type; + + octave::cdef_manager& cm = interpreter.get_cdef_manager(); + std::string name(N); + + // Readonly property + if constexpr (R) + { + xwidgets_add_method(interpreter, cls, name, get_property); + } + // Read-write property + else + { + cls.install_property(cm.make_property( + cls, + name, + make_fcn_handle(get_property, widget::name + (">get." + name)), + "public", + make_fcn_handle(set_property, widget::name + (">set." + name)), + "public" + )); + + cls.install_method(cm.make_method(cls, "observe_" + name, observe

)); + } +} + +/** + * Register an xwidget class. End users must call this function to perform the + * registration + */ +template