/*
MVC base classes
Qt Based Implementation
Copyright (C) 2020, John Ryland
*/
#include "ModelViewController.hpp"
#include <QtUiTools>
namespace
{
// Based on: QMetaObject::connectSlotsByName
// Searches rootObject and all it's descendants for signals, and connects them to any slots in receiverObject where the signatures match
// the pattern on_<senderName>_<signalName>
void connectSlotsByName(QObject* rootObject, const QObject* receiverObject)
{
const QMetaObject* slotInfo = receiverObject->metaObject();
if (slotInfo && rootObject)
{
const QObjectList list = rootObject->findChildren<QObject*>(QString()) << rootObject; // look for signals in rootObject and all it's descendants
// for each method/slot of mo ...
for (int i = 0; i < slotInfo->methodCount(); ++i)
{
const QByteArray slotSignature = slotInfo->method(i).methodSignature();
const char* slot = slotSignature.constData();
// ...that starts with "on_", ...
if (slot[0] == 'o' && slot[1] == 'n' && slot[2] == '_')
{
// ...we check each object in our list, ...
for (int j = 0; j < list.count(); ++j)
{
const QObject* signalObject = list.at(j);
const QByteArray coName = signalObject->objectName().toLatin1();
// ...discarding those whose objectName is not fitting the pattern "on_<objectName>_...", ...
if (coName.isEmpty() || qstrncmp(slot + 3, coName.constData(), coName.size()) || slot[coName.size() + 3] != '_')
continue;
std::string signal = slot + coName.size() + 4;
// we attempt to connect it...
rootObject->connect(signalObject, ("2" + signal).c_str(), receiverObject, (std::string("1") + slot).c_str());
}
}
}
}
}
class LayoutWrapper : public QVBoxLayout
{
public:
LayoutWrapper(QWidget* parent, QWidget* view)
: QVBoxLayout(parent)
{
addWidget(view); // view is now a child of parent
parent->resize(view->size()); // resize parent to the size of view
}
};
}
ModelBase::ModelBase()
{
setObjectName("model");
}
ViewBase::ViewBase(const char* uiFileName)
{
setObjectName("view");
new LayoutWrapper(this, QUiLoader{}.load(&QFile(uiFileName)));
}
ControllerBase::ControllerBase(ModelBase* model, ViewBase* view)
: _model(model)
, _view(view)
, _controller(this)
{
setObjectName("controller");
new LayoutWrapper(this, view); // view is now a child of controller
view->layout()->addWidget(model); // model is now a child of view
}
void ControllerBase::start()
{
::connectSlotsByName(this, _model);
::connectSlotsByName(this, _view);
::connectSlotsByName(this, _controller);
show();
}