/*************************************************************************** * Copyright (c) 2004 Werner Mayer * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library 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 Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include #include #include #ifdef FC_OS_WIN32 # undef max # undef min # ifdef _MSC_VER # pragma warning(disable : 4099) # pragma warning(disable : 4522) # endif #endif #include #include #include #include #include "WidgetFactory.h" #include "PrefWidgets.h" #include "PythonWrapper.h" #include "UiLoader.h" using namespace Gui; Gui::WidgetFactoryInst* Gui::WidgetFactoryInst::_pcSingleton = nullptr; WidgetFactoryInst& WidgetFactoryInst::instance() { if (!_pcSingleton) { _pcSingleton = new WidgetFactoryInst; } return *_pcSingleton; } void WidgetFactoryInst::destruct() { if (_pcSingleton) { delete _pcSingleton; } _pcSingleton = nullptr; } /** * Creates a widget with the name \a sName which is a child of \a parent. * To create an instance of this widget once it must has been registered. * If there is no appropriate widget registered nullptr is returned. */ QWidget* WidgetFactoryInst::createWidget(const char* sName, QWidget* parent) const { auto w = static_cast(Produce(sName)); // this widget class is not registered if (!w) { #ifdef FC_DEBUG Base::Console().warning("\"%s\" is not registered\n", sName); #else Base::Console().log("\"%s\" is not registered\n", sName); #endif return nullptr; } try { #ifdef FC_DEBUG const char* cName = qobject_cast(w)->metaObject()->className(); Base::Console().log("Widget of type '%s' created.\n", cName); #endif } catch (...) { #ifdef FC_DEBUG Base::Console().error("%s does not inherit from \"QWidget\"\n", sName); #else Base::Console().log("%s does not inherit from \"QWidget\"\n", sName); #endif delete w; return nullptr; } // set the parent to the widget if (parent) { w->setParent(parent); } return w; } /** * Creates a widget with the name \a sName which is a child of \a parent. * To create an instance of this widget once it must has been registered. * If there is no appropriate widget registered nullptr is returned. */ Gui::Dialog::PreferencePage* WidgetFactoryInst::createPreferencePage( const char* sName, QWidget* parent ) const { auto w = (Gui::Dialog::PreferencePage*)Produce(sName); // this widget class is not registered if (!w) { #ifdef FC_DEBUG Base::Console().warning("Cannot create an instance of \"%s\"\n", sName); #else Base::Console().log("Cannot create an instance of \"%s\"\n", sName); #endif return nullptr; } if (qobject_cast(w)) { #ifdef FC_DEBUG Base::Console().log("Preference page of type '%s' created.\n", w->metaObject()->className()); #endif } else { #ifdef FC_DEBUG Base::Console().error("%s does not inherit from 'Gui::Dialog::PreferencePage'\n", sName); #endif delete w; return nullptr; } // set the parent to the widget if (parent) { w->setParent(parent); } return w; } /** * Creates a preference widget with the name \a sName and the preference name \a sPref * which is a child of \a parent. * To create an instance of this widget once it must has been registered. * If there is no appropriate widget registered nullptr is returned. * After creation of this widget its recent preferences are restored automatically. */ QWidget* WidgetFactoryInst::createPrefWidget(const char* sName, QWidget* parent, const char* sPref) { QWidget* w = createWidget(sName); // this widget class is not registered if (!w) { return nullptr; // no valid QWidget object } // set the parent to the widget w->setParent(parent); try { auto pw = dynamic_cast(w); if (pw) { pw->setEntryName(sPref); pw->restorePreferences(); } } catch (...) { #ifdef FC_DEBUG Base::Console().error("%s does not inherit from \"PrefWidget\"\n", w->metaObject()->className()); #endif delete w; return nullptr; } return w; } // ---------------------------------------------------- WidgetFactorySupplier* WidgetFactorySupplier::_pcSingleton = nullptr; WidgetFactorySupplier& WidgetFactorySupplier::instance() { // not initialized? if (!_pcSingleton) { _pcSingleton = new WidgetFactorySupplier; } return *_pcSingleton; } void WidgetFactorySupplier::destruct() { // delete the widget factory and all its producers first WidgetFactoryInst::destruct(); delete _pcSingleton; _pcSingleton = nullptr; } // ---------------------------------------------------- PrefPageUiProducer::PrefPageUiProducer(const char* filename, const char* group) : fn(QString::fromUtf8(filename)) { WidgetFactoryInst::instance().AddProducer(filename, this); Gui::Dialog::DlgPreferencesImp::addPage(filename, group); } PrefPageUiProducer::~PrefPageUiProducer() = default; void* PrefPageUiProducer::Produce() const { QWidget* page = new Gui::Dialog::PreferenceUiForm(fn); return static_cast(page); } // ---------------------------------------------------- PrefPagePyProducer::PrefPagePyProducer(const Py::Object& p, const char* group) : type(p) { std::string str; Base::PyGILStateLocker lock; if (type.hasAttr("__name__")) { str = static_cast(Py::String(type.getAttr("__name__"))); } WidgetFactoryInst::instance().AddProducer(str.c_str(), this); Gui::Dialog::DlgPreferencesImp::addPage(str, group); } PrefPagePyProducer::~PrefPagePyProducer() { Base::PyGILStateLocker lock; type = Py::None(); } void* PrefPagePyProducer::Produce() const { Base::PyGILStateLocker lock; try { Py::Callable method(type); Py::Tuple args; Py::Object page = method.apply(args); QWidget* widget = new Gui::Dialog::PreferencePagePython(page); if (!widget->layout()) { delete widget; widget = nullptr; } return widget; } catch (Py::Exception&) { PyErr_Print(); return nullptr; } } // ---------------------------------------------------- using namespace Gui::Dialog; PreferencePagePython::PreferencePagePython(const Py::Object& p, QWidget* parent) : PreferencePage(parent) , page(p) { Base::PyGILStateLocker lock; Gui::PythonWrapper wrap; if (wrap.loadCoreModule()) { // old style class must have a form attribute while // new style classes can be the widget itself Py::Object widget; if (page.hasAttr(std::string("form"))) { widget = page.getAttr(std::string("form")); } else { widget = page; } QObject* object = wrap.toQObject(widget); if (object) { QWidget* form = qobject_cast(object); if (form) { this->setWindowTitle(form->windowTitle()); auto layout = new QVBoxLayout; layout->addWidget(form); setLayout(layout); } } } } PreferencePagePython::~PreferencePagePython() { Base::PyGILStateLocker lock; page = Py::None(); } void PreferencePagePython::changeEvent(QEvent* e) { QWidget::changeEvent(e); } void PreferencePagePython::loadSettings() { Base::PyGILStateLocker lock; try { if (page.hasAttr(std::string("loadSettings"))) { Py::Callable method(page.getAttr(std::string("loadSettings"))); Py::Tuple args; method.apply(args); } } catch (Py::Exception&) { Base::PyException e; // extract the Python error text e.reportException(); } } void PreferencePagePython::saveSettings() { Base::PyGILStateLocker lock; try { if (page.hasAttr(std::string("saveSettings"))) { Py::Callable method(page.getAttr(std::string("saveSettings"))); Py::Tuple args; method.apply(args); } } catch (Py::Exception&) { Base::PyException e; // extract the Python error text e.reportException(); } } // ---------------------------------------------------- /* TRANSLATOR Gui::ContainerDialog */ /** * Constructs a ContainerDialog which embeds the child \a templChild. * The dialog will be modal. */ ContainerDialog::ContainerDialog(QWidget* templChild) : QDialog(QApplication::activeWindow()) { setModal(true); setWindowTitle(templChild->objectName()); setObjectName(templChild->objectName()); setSizeGripEnabled(true); MyDialogLayout = new QGridLayout(this); buttonOk = new QPushButton(this); buttonOk->setObjectName(QLatin1String("buttonOK")); buttonOk->setText(tr("&OK")); buttonOk->setAutoDefault(true); buttonOk->setDefault(true); MyDialogLayout->addWidget(buttonOk, 1, 0); auto spacer = new QSpacerItem(210, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); MyDialogLayout->addItem(spacer, 1, 1); buttonCancel = new QPushButton(this); buttonCancel->setObjectName(QLatin1String("buttonCancel")); buttonCancel->setText(tr("&Cancel")); buttonCancel->setAutoDefault(true); MyDialogLayout->addWidget(buttonCancel, 1, 2); templChild->setParent(this); MyDialogLayout->addWidget(templChild, 0, 0, 0, 2); resize(QSize(307, 197).expandedTo(minimumSizeHint())); // signals and slots connections connect(buttonOk, &QPushButton::clicked, this, &QDialog::accept); connect(buttonCancel, &QPushButton::clicked, this, &QDialog::reject); } /** Destroys the object and frees any allocated resources */ ContainerDialog::~ContainerDialog() = default; // ---------------------------------------------------- void PyResource::init_type() { behaviors().name("PyResource"); behaviors().doc("PyResource"); // you must have overwritten the virtual functions behaviors().supportRepr(); behaviors().supportGetattr(); behaviors().supportSetattr(); add_varargs_method("value", &PyResource::value); add_varargs_method("setValue", &PyResource::setValue); add_varargs_method("show", &PyResource::show); add_varargs_method("connect", &PyResource::connect); } PyResource::PyResource() : myDlg(nullptr) {} PyResource::~PyResource() { delete myDlg; for (auto it : mySignals) { SignalConnect* sc = it; delete sc; } } /** * Loads an .ui file with the name \a name. If the .ui file cannot be found or the QWidgetFactory * cannot create an instance an exception is thrown. If the created resource does not inherit from * QDialog an instance of ContainerDialog is created to embed it. */ void PyResource::load(const char* name) { QString fn = QString::fromUtf8(name); QFileInfo fi(fn); // checks whether it's a relative path if (fi.isRelative()) { QString cwd = QDir::currentPath(); QString home = QDir(QString::fromStdString(App::Application::getHomePath())).path(); // search in cwd and home path for the file // // file does not reside in cwd, check home path now if (!fi.exists()) { if (cwd == home) { QString what = QObject::tr("Cannot find file %1").arg(fi.absoluteFilePath()); throw Base::FileSystemError(what.toUtf8().constData()); } else { fi.setFile(QDir(home), fn); if (!fi.exists()) { QString what = QObject::tr("Cannot find file %1 neither in %2 nor in %3") .arg(fn, cwd, home); throw Base::FileSystemError(what.toUtf8().constData()); } else { fn = fi.absoluteFilePath(); // file resides in FreeCAD's home directory } } } } else { if (!fi.exists()) { QString what = QObject::tr("Cannot find file %1").arg(fn); throw Base::FileSystemError(what.toUtf8().constData()); } } QWidget* w = nullptr; try { auto loader = UiLoader::newInstance(); QFile file(fn); if (file.open(QFile::ReadOnly)) { w = loader->load(&file, QApplication::activeWindow()); } file.close(); } catch (...) { throw Base::RuntimeError("Cannot create resource"); } if (!w) { throw Base::ValueError("Invalid widget."); } if (w->inherits("QDialog")) { myDlg = static_cast(w); } else { myDlg = new ContainerDialog(w); } } /** * Makes a connection between the sender widget \a sender and its signal \a signal * of the created resource and Python callback function \a cb. * If the sender widget does not exist or no resource has been loaded this method returns false, * otherwise it returns true. */ bool PyResource::connect(const char* sender, const char* signal, PyObject* cb) { if (!myDlg) { return false; } QObject* objS = nullptr; QList list = myDlg->findChildren(); QList::const_iterator it = list.cbegin(); QObject* obj; QString sigStr = QStringLiteral("2%1").arg(QString::fromLatin1(signal)); while (it != list.cend()) { obj = *it; ++it; if (obj->objectName() == QLatin1String(sender)) { objS = obj; break; } } if (objS) { auto sc = new SignalConnect(this, cb); mySignals.push_back(sc); return QObject::connect(objS, sigStr.toLatin1(), sc, SLOT(onExecute())); } else { qWarning("'%s' does not exist.\n", sender); } return false; } Py::Object PyResource::repr() { return Py::String("Resource object"); } /** * Searches for a widget and its value in the argument object \a args * to returns its value as Python object. * In the case it fails nullptr is returned. */ Py::Object PyResource::value(const Py::Tuple& args) { char* psName; char* psProperty; if (!PyArg_ParseTuple(args.ptr(), "ss", &psName, &psProperty)) { throw Py::Exception(); } QVariant v; if (myDlg) { QList list = myDlg->findChildren(); QList::const_iterator it = list.cbegin(); QObject* obj; bool fnd = false; while (it != list.cend()) { obj = *it; ++it; if (obj->objectName() == QLatin1String(psName)) { fnd = true; v = obj->property(psProperty); break; } } if (!fnd) { qWarning("'%s' not found.\n", psName); } } Py::Object item = Py::None(); switch (v.userType()) { case QMetaType::QStringList: { QStringList str = v.toStringList(); int nSize = str.count(); Py::List slist(nSize); for (int i = 0; i < nSize; ++i) { slist.setItem(i, Py::String(str[i].toLatin1())); } item = slist; } break; case QMetaType::QByteArray: break; case QMetaType::QString: item = Py::String(v.toString().toLatin1()); break; case QMetaType::Double: item = Py::Float(v.toDouble()); break; case QMetaType::Bool: item = Py::Boolean(v.toBool()); break; case QMetaType::UInt: item = Py::Long(static_cast(v.toUInt())); break; case QMetaType::Int: item = Py::Long(v.toInt()); break; default: item = Py::String(""); break; } return item; } /** * Searches for a widget, its value name and the new value in the argument object \a args * to set even this new value. * In the case it fails nullptr is returned. */ Py::Object PyResource::setValue(const Py::Tuple& args) { char* psName; char* psProperty; PyObject* psValue; if (!PyArg_ParseTuple(args.ptr(), "ssO", &psName, &psProperty, &psValue)) { throw Py::Exception(); } QVariant v; if (PyUnicode_Check(psValue)) { v = QString::fromUtf8(PyUnicode_AsUTF8(psValue)); } else if (PyLong_Check(psValue)) { unsigned int val = PyLong_AsLong(psValue); v = val; } else if (PyFloat_Check(psValue)) { v = PyFloat_AsDouble(psValue); } else if (PyList_Check(psValue)) { QStringList str; int nSize = PyList_Size(psValue); for (int i = 0; i < nSize; ++i) { PyObject* item = PyList_GetItem(psValue, i); if (!PyUnicode_Check(item)) { continue; } const char* pItem = PyUnicode_AsUTF8(item); str.append(QString::fromUtf8(pItem)); } v = str; } else { throw Py::TypeError("Unsupported type"); } if (myDlg) { QList list = myDlg->findChildren(); QList::const_iterator it = list.cbegin(); QObject* obj; bool fnd = false; while (it != list.cend()) { obj = *it; ++it; if (obj->objectName() == QLatin1String(psName)) { fnd = true; obj->setProperty(psProperty, v); break; } } if (!fnd) { qWarning("'%s' not found.\n", psName); } } return Py::None(); } /** * If any resource has been loaded this methods shows it as a modal dialog. */ Py::Object PyResource::show(const Py::Tuple&) { if (myDlg) { // small trick to get focus myDlg->showMinimized(); #ifdef Q_WS_X11 // On X11 this may not work. For further information see QWidget::showMaximized // // workaround for X11 myDlg->hide(); myDlg->show(); #endif myDlg->showNormal(); myDlg->exec(); } return Py::None(); } /** * Searches for the sender, the signal and the callback function to connect with * in the argument object \a args. In the case it fails nullptr is returned. */ Py::Object PyResource::connect(const Py::Tuple& args) { char* psSender; char* psSignal; PyObject* temp; if (PyArg_ParseTuple(args.ptr(), "ssO", &psSender, &psSignal, &temp)) { if (!PyCallable_Check(temp)) { PyErr_SetString(PyExc_TypeError, "parameter must be callable"); throw Py::Exception(); } Py_XINCREF(temp); /* Add a reference to new callback */ std::string sSender = psSender; std::string sSignal = psSignal; if (!connect(psSender, psSignal, temp)) { // no signal object found => dispose the callback object Py_XDECREF(temp); /* Dispose of callback */ } return Py::None(); } // error set by PyArg_ParseTuple throw Py::Exception(); } // ---------------------------------------------------- SignalConnect::SignalConnect(PyObject* res, PyObject* cb) : myResource(res) , myCallback(cb) {} SignalConnect::~SignalConnect() { Base::PyGILStateLocker lock; Py_XDECREF(myCallback); /* Dispose of callback */ } /** * Calls the callback function of the connected Python object. */ void SignalConnect::onExecute() { PyObject* arglist; PyObject* result; /* Time to call the callback */ arglist = Py_BuildValue("(O)", myResource); result = PyObject_CallObject(myCallback, arglist); Py_XDECREF(result); Py_DECREF(arglist); } #include "moc_WidgetFactory.cpp"