notcurses/include/ncpp/Reel.hh
Marek Habersack a29bfe9c42 [C++] Deal with widgets grabbing full ownership of Panel
Fixes: https://github.com/dankamongmen/notcurses/issues/1009

Whenever a widget is created with its `*_create` function it currently
claims full ownership of the passed panel, including its destruction.
However, the C++ wrapper around the panel is not aware of this and will
attempt to destroy the native panel in the destructor, leading to
segfaults.

Fix this by introduction of a `Widget` class which contains the logic to
properly modify the `Panel` instance to not double-destroy the native
panel.  The solution is a bit fragile since the `Panel` instance is left
intact (we can't free it for the user) in a state that's safe for the
C++ wrapper, but calling any C function via the wrapper **will** pass a
`NULL` pointer in the panel argument - therefore the C functions MUST be
proofed against this.  The proofing belongs in the C backend code since
this protects also C and other language binding users from such abuse.

The Widget class will first verify that the passed `Plane` instance
hasn't already been "disowned" and will throw an exception to the effect
if it was.  Next, it will proceed to take over ownership of the native
panel instance and mark the passed `Panel` as "invalid" (i.e. not owning
any native panel instance anymore)

The above changes require modification of `Panel` instances and so all
the widget constructors taking `const*` or `const&` have been removed
from widget classes.
2020-10-03 13:19:49 -04:00

146 lines
3.2 KiB
C++

#ifndef __NCPP_REEL_HH
#define __NCPP_REEL_HH
#include <memory>
#include <notcurses/notcurses.h>
#include "Tablet.hh"
#include "Plane.hh"
#include "Utilities.hh"
#include "Widget.hh"
namespace ncpp
{
class NCPP_API_EXPORT NcReel : public Widget
{
public:
static ncreel_options default_options;
explicit NcReel (Plane &plane, const ncreel_options *popts = nullptr)
: Widget (Utilities::get_notcurses_cpp (plane))
{
ensure_valid_plane (plane);
common_init (Utilities::to_ncplane (plane), popts);
take_plane_ownership (plane);
}
explicit NcReel (Plane *plane, const ncreel_options *popts = nullptr)
: Widget (Utilities::get_notcurses_cpp (plane))
{
if (plane == nullptr)
throw invalid_argument ("'plane' must be a valid pointer");
ensure_valid_plane (plane);
common_init (Utilities::to_ncplane (plane), popts);
take_plane_ownership (plane);
}
~NcReel ()
{
if (!is_notcurses_stopped ())
ncreel_destroy (reel);
}
operator ncreel* () const noexcept
{
return reel;
}
operator ncreel const* () const noexcept
{
return reel;
}
// TODO: add an overload using callback that takes NcTablet instance instead of struct tablet
NcTablet* add (NcTablet *after, NcTablet *before, tabletcb cb, void *opaque = nullptr) const
{
nctablet *t = ncreel_add (reel, get_tablet (after), get_tablet (before), cb, opaque);
if (t == nullptr)
throw init_error ("Notcurses failed to create a new tablet");
return NcTablet::map_tablet (t, get_notcurses_cpp ());
}
NcTablet* add (NcTablet &after, NcTablet &before, tabletcb cb, void *opaque = nullptr) const noexcept
{
return add (&after, &before, cb, opaque);
}
int get_tabletcount () const noexcept
{
return ncreel_tabletcount (reel);
}
bool del (NcTablet *t) const NOEXCEPT_MAYBE
{
return error_guard (ncreel_del (reel, get_tablet (t)), -1);
}
bool del (NcTablet &t) const NOEXCEPT_MAYBE
{
return del (&t);
}
bool redraw () const NOEXCEPT_MAYBE
{
return error_guard (ncreel_redraw (reel), -1);
}
NcTablet* get_focused () const noexcept
{
nctablet *t = ncreel_focused (reel);
if (t == nullptr)
return nullptr;
return NcTablet::map_tablet (t, get_notcurses_cpp ());
}
NcTablet* next () const noexcept
{
nctablet *t = ncreel_next (reel);
if (t == nullptr)
return nullptr;
return NcTablet::map_tablet (t, get_notcurses_cpp ());
}
NcTablet* prev () const noexcept
{
nctablet *t = ncreel_prev (reel);
if (t == nullptr)
return nullptr;
return NcTablet::map_tablet (t, get_notcurses_cpp ());
}
bool offer_input (const struct ncinput* nci) const NOEXCEPT_MAYBE
{
return error_guard<bool, bool> (ncreel_offer_input (reel, nci), false);
}
Plane* get_plane () const noexcept;
private:
nctablet* get_tablet (NcTablet *t) const noexcept
{
if (t == nullptr)
return nullptr;
return t->get_tablet ();
}
void common_init (ncplane *plane, const ncreel_options *popts = nullptr)
{
reel = ncreel_create (plane, popts == nullptr ? &default_options : popts);
if (reel == nullptr)
throw init_error ("Notcurses failed to create a new ncreel");
}
private:
ncreel *reel = nullptr;
friend class Plane;
};
}
#endif