1
/***************************************************************************
2
* Copyright (c) 2004 Werner Mayer <wmayer[at]users.sourceforge.net> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
24
#include "PreCompiled.h"
26
# include <QApplication>
27
# include <QVBoxLayout>
34
#pragma warning( disable : 4099 )
35
#pragma warning( disable : 4522 )
39
#include <App/Application.h>
40
#include <Base/Console.h>
41
#include <Base/Exception.h>
42
#include <Base/Interpreter.h>
44
#include "WidgetFactory.h"
45
#include "PrefWidgets.h"
46
#include "PythonWrapper.h"
52
Gui::WidgetFactoryInst* Gui::WidgetFactoryInst::_pcSingleton = nullptr;
54
WidgetFactoryInst& WidgetFactoryInst::instance()
57
_pcSingleton = new WidgetFactoryInst;
61
void WidgetFactoryInst::destruct ()
65
_pcSingleton = nullptr;
69
* Creates a widget with the name \a sName which is a child of \a parent.
70
* To create an instance of this widget once it must has been registered.
71
* If there is no appropriate widget registered nullptr is returned.
73
QWidget* WidgetFactoryInst::createWidget (const char* sName, QWidget* parent) const
75
auto w = static_cast<QWidget*>(Produce(sName));
77
// this widget class is not registered
80
Base::Console().Warning("\"%s\" is not registered\n", sName);
82
Base::Console().Log("\"%s\" is not registered\n", sName);
89
const char* cName = dynamic_cast<QWidget*>(w)->metaObject()->className();
90
Base::Console().Log("Widget of type '%s' created.\n", cName);
95
Base::Console().Error("%s does not inherit from \"QWidget\"\n", sName);
97
Base::Console().Log("%s does not inherit from \"QWidget\"\n", sName);
103
// set the parent to the widget
105
w->setParent(parent);
111
* Creates a widget with the name \a sName which is a child of \a parent.
112
* To create an instance of this widget once it must has been registered.
113
* If there is no appropriate widget registered nullptr is returned.
115
Gui::Dialog::PreferencePage* WidgetFactoryInst::createPreferencePage (const char* sName, QWidget* parent) const
117
auto w = (Gui::Dialog::PreferencePage*)Produce(sName);
119
// this widget class is not registered
122
Base::Console().Warning("Cannot create an instance of \"%s\"\n", sName);
124
Base::Console().Log("Cannot create an instance of \"%s\"\n", sName);
129
if (qobject_cast<Gui::Dialog::PreferencePage*>(w)) {
131
Base::Console().Log("Preference page of type '%s' created.\n", w->metaObject()->className());
136
Base::Console().Error("%s does not inherit from 'Gui::Dialog::PreferencePage'\n", sName);
142
// set the parent to the widget
144
w->setParent(parent);
150
* Creates a preference widget with the name \a sName and the preference name \a sPref
151
* which is a child of \a parent.
152
* To create an instance of this widget once it must has been registered.
153
* If there is no appropriate widget registered nullptr is returned.
154
* After creation of this widget its recent preferences are restored automatically.
156
QWidget* WidgetFactoryInst::createPrefWidget(const char* sName, QWidget* parent, const char* sPref)
158
QWidget* w = createWidget(sName);
159
// this widget class is not registered
161
return nullptr; // no valid QWidget object
163
// set the parent to the widget
164
w->setParent(parent);
167
auto pw = dynamic_cast<PrefWidget*>(w);
169
pw->setEntryName(sPref);
170
pw->restorePreferences();
175
Base::Console().Error("%s does not inherit from \"PrefWidget\"\n", w->metaObject()->className());
184
// ----------------------------------------------------
186
WidgetFactorySupplier* WidgetFactorySupplier::_pcSingleton = nullptr;
188
WidgetFactorySupplier & WidgetFactorySupplier::instance()
192
_pcSingleton = new WidgetFactorySupplier;
193
return *_pcSingleton;
196
void WidgetFactorySupplier::destruct()
198
// delete the widget factory and all its producers first
199
WidgetFactoryInst::destruct();
201
_pcSingleton=nullptr;
204
// ----------------------------------------------------
206
PrefPageUiProducer::PrefPageUiProducer (const char* filename, const char* group)
207
: fn(QString::fromUtf8(filename))
209
WidgetFactoryInst::instance().AddProducer(filename, this);
210
Gui::Dialog::DlgPreferencesImp::addPage(filename, group);
213
PrefPageUiProducer::~PrefPageUiProducer() = default;
215
void* PrefPageUiProducer::Produce () const
217
QWidget* page = new Gui::Dialog::PreferenceUiForm(fn);
218
return static_cast<void*>(page);
221
// ----------------------------------------------------
223
PrefPagePyProducer::PrefPagePyProducer (const Py::Object& p, const char* group)
227
Base::PyGILStateLocker lock;
228
if (type.hasAttr("__name__")) {
229
str = static_cast<std::string>(Py::String(type.getAttr("__name__")));
232
WidgetFactoryInst::instance().AddProducer(str.c_str(), this);
233
Gui::Dialog::DlgPreferencesImp::addPage(str, group);
236
PrefPagePyProducer::~PrefPagePyProducer ()
238
Base::PyGILStateLocker lock;
242
void* PrefPagePyProducer::Produce () const
244
Base::PyGILStateLocker lock;
246
Py::Callable method(type);
248
Py::Object page = method.apply(args);
249
QWidget* widget = new Gui::Dialog::PreferencePagePython(page);
250
if (!widget->layout()) {
256
catch (Py::Exception&) {
262
// ----------------------------------------------------
264
using namespace Gui::Dialog;
266
PreferencePagePython::PreferencePagePython(const Py::Object& p, QWidget* parent)
267
: PreferencePage(parent), page(p)
269
Base::PyGILStateLocker lock;
270
Gui::PythonWrapper wrap;
271
if (wrap.loadCoreModule()) {
273
// old style class must have a form attribute while
274
// new style classes can be the widget itself
276
if (page.hasAttr(std::string("form")))
277
widget = page.getAttr(std::string("form"));
281
QObject* object = wrap.toQObject(widget);
283
QWidget* form = qobject_cast<QWidget*>(object);
285
this->setWindowTitle(form->windowTitle());
286
auto layout = new QVBoxLayout;
287
layout->addWidget(form);
294
PreferencePagePython::~PreferencePagePython()
296
Base::PyGILStateLocker lock;
300
void PreferencePagePython::changeEvent(QEvent *e)
302
QWidget::changeEvent(e);
305
void PreferencePagePython::loadSettings()
307
Base::PyGILStateLocker lock;
309
if (page.hasAttr(std::string("loadSettings"))) {
310
Py::Callable method(page.getAttr(std::string("loadSettings")));
315
catch (Py::Exception&) {
316
Base::PyException e; // extract the Python error text
321
void PreferencePagePython::saveSettings()
323
Base::PyGILStateLocker lock;
325
if (page.hasAttr(std::string("saveSettings"))) {
326
Py::Callable method(page.getAttr(std::string("saveSettings")));
331
catch (Py::Exception&) {
332
Base::PyException e; // extract the Python error text
337
// ----------------------------------------------------
339
/* TRANSLATOR Gui::ContainerDialog */
342
* Constructs a ContainerDialog which embeds the child \a templChild.
343
* The dialog will be modal.
345
ContainerDialog::ContainerDialog( QWidget* templChild )
346
: QDialog( QApplication::activeWindow())
349
setWindowTitle( templChild->objectName() );
350
setObjectName( templChild->objectName() );
352
setSizeGripEnabled( true );
353
MyDialogLayout = new QGridLayout(this);
355
buttonOk = new QPushButton(this);
356
buttonOk->setObjectName(QLatin1String("buttonOK"));
357
buttonOk->setText( tr( "&OK" ) );
358
buttonOk->setAutoDefault( true );
359
buttonOk->setDefault( true );
361
MyDialogLayout->addWidget( buttonOk, 1, 0 );
362
auto spacer = new QSpacerItem( 210, 20, QSizePolicy::Expanding, QSizePolicy::Minimum );
363
MyDialogLayout->addItem( spacer, 1, 1 );
365
buttonCancel = new QPushButton(this);
366
buttonCancel->setObjectName(QLatin1String("buttonCancel"));
367
buttonCancel->setText( tr( "&Cancel" ) );
368
buttonCancel->setAutoDefault( true );
370
MyDialogLayout->addWidget( buttonCancel, 1, 2 );
372
templChild->setParent(this);
374
MyDialogLayout->addWidget( templChild, 0, 0, 0, 2 );
375
resize( QSize(307, 197).expandedTo(minimumSizeHint()) );
377
// signals and slots connections
378
connect( buttonOk, &QPushButton::clicked, this, &QDialog::accept);
379
connect( buttonCancel, &QPushButton::clicked, this, &QDialog::reject);
382
/** Destroys the object and frees any allocated resources */
383
ContainerDialog::~ContainerDialog() = default;
385
// ----------------------------------------------------
387
void PyResource::init_type()
389
behaviors().name("PyResource");
390
behaviors().doc("PyResource");
391
// you must have overwritten the virtual functions
392
behaviors().supportRepr();
393
behaviors().supportGetattr();
394
behaviors().supportSetattr();
395
add_varargs_method("value",&PyResource::value);
396
add_varargs_method("setValue",&PyResource::setValue);
397
add_varargs_method("show",&PyResource::show);
398
add_varargs_method("connect",&PyResource::connect);
401
PyResource::PyResource() : myDlg(nullptr)
405
PyResource::~PyResource()
408
for (auto it : mySignals) {
409
SignalConnect* sc = it;
415
* Loads an .ui file with the name \a name. If the .ui file cannot be found or the QWidgetFactory
416
* cannot create an instance an exception is thrown. If the created resource does not inherit from
417
* QDialog an instance of ContainerDialog is created to embed it.
419
void PyResource::load(const char* name)
421
QString fn = QString::fromUtf8(name);
424
// checks whether it's a relative path
425
if (fi.isRelative()) {
426
QString cwd = QDir::currentPath ();
427
QString home= QDir(QString::fromStdString(App::Application::getHomePath())).path();
429
// search in cwd and home path for the file
431
// file does not reside in cwd, check home path now
434
QString what = QObject::tr("Cannot find file %1").arg(fi.absoluteFilePath());
435
throw Base::FileSystemError(what.toUtf8().constData());
438
fi.setFile( QDir(home), fn );
441
QString what = QObject::tr("Cannot find file %1 neither in %2 nor in %3")
443
throw Base::FileSystemError(what.toUtf8().constData());
446
fn = fi.absoluteFilePath(); // file resides in FreeCAD's home directory
453
QString what = QObject::tr("Cannot find file %1").arg(fn);
454
throw Base::FileSystemError(what.toUtf8().constData());
460
auto loader = UiLoader::newInstance();
462
if (file.open(QFile::ReadOnly))
463
w = loader->load(&file, QApplication::activeWindow());
467
throw Base::RuntimeError("Cannot create resource");
471
throw Base::ValueError("Invalid widget.");
473
if (w->inherits("QDialog")) {
474
myDlg = static_cast<QDialog*>(w);
477
myDlg = new ContainerDialog(w);
482
* Makes a connection between the sender widget \a sender and its signal \a signal
483
* of the created resource and Python callback function \a cb.
484
* If the sender widget does not exist or no resource has been loaded this method returns false,
485
* otherwise it returns true.
487
bool PyResource::connect(const char* sender, const char* signal, PyObject* cb)
492
QObject* objS=nullptr;
493
QList<QWidget*> list = myDlg->findChildren<QWidget*>();
494
QList<QWidget*>::const_iterator it = list.cbegin();
496
QString sigStr = QString::fromLatin1("2%1").arg(QString::fromLatin1(signal));
498
while ( it != list.cend() ) {
501
if (obj->objectName() == QLatin1String(sender)) {
508
auto sc = new SignalConnect(this, cb);
509
mySignals.push_back(sc);
510
return QObject::connect(objS, sigStr.toLatin1(), sc, SLOT ( onExecute() ) );
513
qWarning( "'%s' does not exist.\n", sender );
518
Py::Object PyResource::repr()
521
std::ostringstream s_out;
522
s_out << "Resource object";
523
return Py::String(s_out.str());
527
* Searches for a widget and its value in the argument object \a args
528
* to returns its value as Python object.
529
* In the case it fails nullptr is returned.
531
Py::Object PyResource::value(const Py::Tuple& args)
535
if (!PyArg_ParseTuple(args.ptr(), "ss", &psName, &psProperty))
536
throw Py::Exception();
540
QList<QWidget*> list = myDlg->findChildren<QWidget*>();
541
QList<QWidget*>::const_iterator it = list.cbegin();
545
while ( it != list.cend() ) {
548
if (obj->objectName() == QLatin1String(psName)) {
550
v = obj->property(psProperty);
556
qWarning( "'%s' not found.\n", psName );
559
Py::Object item = Py::None();
560
switch (v.userType())
562
case QMetaType::QStringList:
564
QStringList str = v.toStringList();
565
int nSize = str.count();
566
Py::List slist(nSize);
567
for (int i=0; i<nSize;++i) {
568
slist.setItem(i, Py::String(str[i].toLatin1()));
572
case QMetaType::QByteArray:
574
case QMetaType::QString:
575
item = Py::String(v.toString().toLatin1());
577
case QMetaType::Double:
578
item = Py::Float(v.toDouble());
580
case QMetaType::Bool:
581
item = Py::Boolean(v.toBool());
583
case QMetaType::UInt:
584
item = Py::Long(static_cast<unsigned long>(v.toUInt()));
587
item = Py::Int(v.toInt());
590
item = Py::String("");
598
* Searches for a widget, its value name and the new value in the argument object \a args
599
* to set even this new value.
600
* In the case it fails nullptr is returned.
602
Py::Object PyResource::setValue(const Py::Tuple& args)
607
if (!PyArg_ParseTuple(args.ptr(), "ssO", &psName, &psProperty, &psValue))
608
throw Py::Exception();
611
if (PyUnicode_Check(psValue)) {
612
v = QString::fromUtf8(PyUnicode_AsUTF8(psValue));
615
else if (PyLong_Check(psValue)) {
616
unsigned int val = PyLong_AsLong(psValue);
619
else if (PyFloat_Check(psValue)) {
620
v = PyFloat_AsDouble(psValue);
622
else if (PyList_Check(psValue)) {
624
int nSize = PyList_Size(psValue);
625
for (int i=0; i<nSize;++i) {
626
PyObject* item = PyList_GetItem(psValue, i);
627
if (!PyUnicode_Check(item))
629
const char* pItem = PyUnicode_AsUTF8(item);
630
str.append(QString::fromUtf8(pItem));
636
throw Py::TypeError("Unsupported type");
640
QList<QWidget*> list = myDlg->findChildren<QWidget*>();
641
QList<QWidget*>::const_iterator it = list.cbegin();
645
while ( it != list.cend() ) {
648
if (obj->objectName() == QLatin1String(psName)) {
650
obj->setProperty(psProperty, v);
656
qWarning( "'%s' not found.\n", psName );
663
* If any resource has been loaded this methods shows it as a modal dialog.
665
Py::Object PyResource::show(const Py::Tuple&)
668
// small trick to get focus
669
myDlg->showMinimized();
672
// On X11 this may not work. For further information see QWidget::showMaximized
674
// workaround for X11
687
* Searches for the sender, the signal and the callback function to connect with
688
* in the argument object \a args. In the case it fails nullptr is returned.
690
Py::Object PyResource::connect(const Py::Tuple& args)
697
if (PyArg_ParseTuple(args.ptr(), "ssO", &psSender, &psSignal, &temp)) {
698
if (!PyCallable_Check(temp)) {
699
PyErr_SetString(PyExc_TypeError, "parameter must be callable");
700
throw Py::Exception();
703
Py_XINCREF(temp); /* Add a reference to new callback */
704
std::string sSender = psSender;
705
std::string sSignal = psSignal;
707
if (!connect(psSender, psSignal, temp)) {
708
// no signal object found => dispose the callback object
709
Py_XDECREF(temp); /* Dispose of callback */
715
// error set by PyArg_ParseTuple
716
throw Py::Exception();
719
// ----------------------------------------------------
721
SignalConnect::SignalConnect(PyObject* res, PyObject* cb)
722
: myResource(res), myCallback(cb)
726
SignalConnect::~SignalConnect()
728
Base::PyGILStateLocker lock;
729
Py_XDECREF(myCallback); /* Dispose of callback */
733
* Calls the callback function of the connected Python object.
735
void SignalConnect::onExecute()
740
/* Time to call the callback */
741
arglist = Py_BuildValue("(O)", myResource);
742
#if PY_VERSION_HEX < 0x03090000
743
result = PyEval_CallObject(myCallback, arglist);
745
result = PyObject_CallObject(myCallback, arglist);
751
#include "moc_WidgetFactory.cpp"