1
\$\begingroup\$

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;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.