I have been writing a library wrapper for pdcurses for a while and implemented each widget/class in it with the pimpl idiom, however I start to see that for large widgets it creates a god object and than I found out about the MVC pattern and decided to give it a try and implement a simple widget first (a checkbox).
The only problem I have with the mvc pattern is that it requires creating 3 objects to represent a single widget (model object, view object, controller object) versus the pimple idiom that requires only 1 object to be created.
So the question I have from the above: Is the mvc suited for module design or just app design ? and should I still stick to the pimple idiom for writing libraries (dll's)?
I have added the code below to show what I mean by the second paragraph (3 vs 1 objects per widget).
MVC implementation
mvc.h:
#ifndef MVC_H
#define MVC_H
#include "curses.h"
#include "cw_types.h"
namespace cw {
class CheckBoxData
{
public:
bool state() const;
void toggle();
private:
bool state_{ false };
};
class CheckBoxView
{
public:
~CheckBoxView();
int create(Position pos, chtype color_pair);
void printCheckBoxState(bool state);
bool clicked(MEVENT const& mevent);
void move(Position pos);
void clear();
private:
WINDOW* view_win_{ nullptr };
};
class CheckBoxController
{
public:
CheckBoxController(CheckBoxData& data, CheckBoxView& view);
int create(Position pos, chtype color_pair);
bool clicked(MEVENT const& mevent) const;
void updateCheckBoxView();
void move(Position pos);
bool state() const;
void toggle();
void clear();
private:
CheckBoxData& data_;
CheckBoxView& view_;
};
}
#endif
mvc.cpp:
#include "mvc.h"
#include "state_flags.h"
namespace cw {
namespace details {
constexpr int CHECKBOX_WIDTH { 3 };
constexpr int CHECKBOX_HEIGHT{ 1 };
constexpr int CENTER_X { 1 };
constexpr int CENTER_Y { 0 };
}
bool CheckBoxData::state() const
{
return state_;
}
void CheckBoxData::toggle()
{
state_ ? state_ = false : state_ = true;
}
CheckBoxView::~CheckBoxView()
{
delwin(view_win_);
}
int CheckBoxView::create(Position pos, chtype color_pair)
{
if (view_win_)
delwin(view_win_);
if (pos.y < 0 || pos.x < 0)
return flags::BAD_POSITION;
view_win_ = newwin(details::CHECKBOX_HEIGHT, details::CHECKBOX_WIDTH, pos.y, pos.x);
if (!view_win_)
return flags::BAD_ALLOC;
wbkgd(view_win_, color_pair);
wprintw(view_win_, "[ ]");
return flags::GOOD;
}
void CheckBoxView::printCheckBoxState(bool state)
{
state ? mvwaddch(view_win_, details::CENTER_Y, details::CENTER_X, 'X')
: mvwaddch(view_win_, details::CENTER_Y, details::CENTER_X, ' ');
wrefresh(view_win_);
}
bool CheckBoxView::clicked(MEVENT const& mevent)
{
if (!wenclose(view_win_, mevent.y, mevent.x))
return false;
return true;
}
void CheckBoxView::move(Position pos)
{
mvwin(view_win_, pos.y, pos.x);
}
void CheckBoxView::clear()
{
delwin(view_win_);
view_win_ = nullptr;
}
CheckBoxController::CheckBoxController(CheckBoxData& data, CheckBoxView& view)
: data_{ data }, view_{ view }
{
}
int CheckBoxController::create(Position pos, chtype color_pair)
{
return view_.create(pos, color_pair);
}
bool CheckBoxController::clicked(MEVENT const & mevent) const
{
return view_.clicked(mevent);
}
void CheckBoxController::updateCheckBoxView()
{
view_.printCheckBoxState(data_.state());
}
void CheckBoxController::move(Position pos)
{
view_.move(pos);
}
bool CheckBoxController::state() const
{
return data_.state();
}
void CheckBoxController::toggle()
{
data_.toggle();
}
void CheckBoxController::clear()
{
view_.clear();
}
}
PIMPL implementation
checkbox.h:
#ifndef CHECKBOX_H
#define CHECKBOX_H
#include <memory>
#include "curses.h"
#include "cw_types.h"
namespace cw {
class CheckBox final
{
public:
CheckBox();
CheckBox(CheckBox const& checkbox) = delete;
CheckBox(CheckBox&& checkbox);
~CheckBox();
CheckBox& operator=(CheckBox const& checkbox) = delete;
CheckBox& operator=(CheckBox&& checkbox);
int create(Position pos, chtype color_pair);
bool clicked(MEVENT const& mevent) const;
void move(Position pos);
bool state() const;
void toggle();
void update();
void clear();
private:
class CheckBoxImpl;
std::unique_ptr<CheckBoxImpl> checkbox_impl_;
};
}
#endif
checkbox.cpp:
#include "checkbox.h"
#include "state_flags.h"
namespace cw {
namespace details {
constexpr int CHECKBOX_WIDTH { 3 };
constexpr int CHECKBOX_HEIGHT{ 1 };
constexpr int CENTER_X { 1 };
constexpr int CENTER_Y { 0 };
}
class CheckBox::CheckBoxImpl final
{
public:
~CheckBoxImpl();
int create(Position pos, chtype color_pair);
bool clicked(MEVENT const& mevent) const;
void move(Position pos);
bool state() const;
void toggle();
void update();
void clear();
private:
WINDOW* checkbox_win_{ nullptr };
bool state_{ false };
};
CheckBox::CheckBox()
: checkbox_impl_{ std::make_unique<CheckBoxImpl>() }
{
}
CheckBox::CheckBox(CheckBox&& checkbox)
: checkbox_impl_{ std::move(checkbox.checkbox_impl_) }
{
}
CheckBox::~CheckBox()
{
}
CheckBox& CheckBox::operator=(CheckBox&& checkbox)
{
if (&checkbox == this)
return *this;
checkbox_impl_ = std::move(checkbox.checkbox_impl_);
return *this;
}
int CheckBox::create(Position pos, chtype color_pair)
{
return checkbox_impl_->create(pos, color_pair);
}
bool CheckBox::clicked(MEVENT const& mevent) const
{
return checkbox_impl_->clicked(mevent);
}
void CheckBox::move(Position pos)
{
checkbox_impl_->move(pos);
}
bool CheckBox::state() const
{
return checkbox_impl_->state();
}
void CheckBox::toggle()
{
checkbox_impl_->toggle();
}
void CheckBox::update()
{
checkbox_impl_->update();
}
void CheckBox::clear()
{
checkbox_impl_->clear();
}
CheckBox::CheckBoxImpl::~CheckBoxImpl()
{
delwin(checkbox_win_);
}
int CheckBox::CheckBoxImpl::create(Position pos, chtype color_pair)
{
if(checkbox_win_)
delwin(checkbox_win_);
if (pos.y < 0 || pos.x < 0)
return flags::BAD_POSITION;
checkbox_win_ = newwin(details::CHECKBOX_HEIGHT, details::CHECKBOX_WIDTH, pos.y, pos.x);
if (!checkbox_win_)
return flags::BAD_ALLOC;
wbkgd(checkbox_win_, color_pair);
wprintw(checkbox_win_, "[ ]");
return flags::GOOD;
}
bool CheckBox::CheckBoxImpl::clicked(MEVENT const& mevent) const
{
if (!wenclose(checkbox_win_, mevent.y, mevent.x))
return false;
return true;
}
void CheckBox::CheckBoxImpl::move(Position pos)
{
mvwin(checkbox_win_, pos.y, pos.x);
}
bool CheckBox::CheckBoxImpl::state() const
{
return state_;
}
void CheckBox::CheckBoxImpl::toggle()
{
state_ ? state_ = false : state_ = true;
}
void CheckBox::CheckBoxImpl::update()
{
state_ ? mvwaddch(checkbox_win_, details::CENTER_Y, details::CENTER_X, 'X')
: mvwaddch(checkbox_win_, details::CENTER_Y, details::CENTER_X, ' ');
wrefresh(checkbox_win_);
}
void CheckBox::CheckBoxImpl::clear()
{
delwin(checkbox_win_);
checkbox_win_ = nullptr;
}
}
MVC example:
#include <exception>
#include "state_flags.h"
#include "mvc.h"
int main()
{
initscr();
mousemask(BUTTON1_CLICKED, NULL);
keypad(stdscr, TRUE);
start_color();
curs_set(0);
noecho();
raw();
refresh();
init_pair(1, COLOR_BLUE, COLOR_WHITE);
try {
cw::CheckBoxData model;
cw::CheckBoxView view;
MEVENT mevent;
cw::CheckBoxController controller{ model, view };
if (controller.create({ 10,10 }, COLOR_PAIR(1)) & cw::flags::GOOD) {
controller.updateCheckBoxView();
while (true) {
if(getch() == KEY_MOUSE)
if(nc_getmouse(&mevent) == OK)
if(mevent.bstate & BUTTON1_CLICKED)
if (controller.clicked(mevent)) {
controller.toggle();
controller.updateCheckBoxView();
}
}
}
}
catch (std::exception const& e) {
addstr(e.what());
}
getch();
endwin();
return 0;
}
PIMPL example:
#include <exception>
#include "state_flags.h"
#include "checkbox.h"
int main()
{
initscr();
mousemask(BUTTON1_CLICKED, NULL);
keypad(stdscr, TRUE);
start_color();
curs_set(0);
noecho();
raw();
refresh();
init_pair(1, COLOR_BLUE, COLOR_WHITE);
try {
cw::CheckBox checkbox;
MEVENT mevent;
if (checkbox.create({ 10,10 }, COLOR_PAIR(1)) & cw::flags::GOOD) {
checkbox.update();
while (true) {
if(getch() == KEY_MOUSE)
if(nc_getmouse(&mevent) == OK)
if(mevent.bstate & BUTTON1_CLICKED)
if (checkbox.clicked(mevent)) {
checkbox.toggle();
checkbox.update();
}
}
}
}
catch (std::exception const& e) {
addstr(e.what());
}
getch();
endwin();
return 0;
}