aboutsummaryrefslogtreecommitdiffstats
path: root/3rdparty/QtPropertyBrowser/src/qtgroupboxpropertybrowser.cpp
blob: 9979b3c098531625e982f68e87f0833804810434 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of a Qt Solutions component.
**
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
**     the names of its contributors may be used to endorse or promote
**     products derived from this software without specific prior written
**     permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
****************************************************************************/


#include "qtgroupboxpropertybrowser.h"
#include <QtCore/QSet>
#include <QGridLayout>
#include <QLabel>
#include <QGroupBox>
#include <QtCore/QTimer>
#include <QtCore/QMap>

#if QT_VERSION >= 0x040400
QT_BEGIN_NAMESPACE
#endif

class QtGroupBoxPropertyBrowserPrivate
{
    QtGroupBoxPropertyBrowser *q_ptr;
    Q_DECLARE_PUBLIC(QtGroupBoxPropertyBrowser)
public:

    void init(QWidget *parent);

    void propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex);
    void propertyRemoved(QtBrowserItem *index);
    void propertyChanged(QtBrowserItem *index);
    QWidget *createEditor(QtProperty *property, QWidget *parent) const
        { return q_ptr->createEditor(property, parent); }

    void slotEditorDestroyed();
    void slotUpdate();

    struct WidgetItem
    {
        WidgetItem() : widget(0), label(0), widgetLabel(0),
                groupBox(0), layout(0), line(0), parent(0) { }
        QWidget *widget; // can be null
        QLabel *label;
        QLabel *widgetLabel;
        QGroupBox *groupBox;
        QGridLayout *layout;
        QFrame *line;
        WidgetItem *parent;
        QList<WidgetItem *> children;
    };
private:
    void updateLater();
    void updateItem(WidgetItem *item);
    void insertRow(QGridLayout *layout, int row) const;
    void removeRow(QGridLayout *layout, int row) const;

    bool hasHeader(WidgetItem *item) const;

    QMap<QtBrowserItem *, WidgetItem *> m_indexToItem;
    QMap<WidgetItem *, QtBrowserItem *> m_itemToIndex;
    QMap<QWidget *, WidgetItem *> m_widgetToItem;
    QGridLayout *m_mainLayout;
    QList<WidgetItem *> m_children;
    QList<WidgetItem *> m_recreateQueue;
};

void QtGroupBoxPropertyBrowserPrivate::init(QWidget *parent)
{
    m_mainLayout = new QGridLayout();
    parent->setLayout(m_mainLayout);
    QLayoutItem *item = new QSpacerItem(0, 0,
                QSizePolicy::Fixed, QSizePolicy::Expanding);
    m_mainLayout->addItem(item, 0, 0);
}

void QtGroupBoxPropertyBrowserPrivate::slotEditorDestroyed()
{
    QWidget *editor = qobject_cast<QWidget *>(q_ptr->sender());
    if (!editor)
        return;
    if (!m_widgetToItem.contains(editor))
        return;
    m_widgetToItem[editor]->widget = 0;
    m_widgetToItem.remove(editor);
}

void QtGroupBoxPropertyBrowserPrivate::slotUpdate()
{
    QListIterator<WidgetItem *> itItem(m_recreateQueue);
    while (itItem.hasNext()) {
        WidgetItem *item = itItem.next();

        WidgetItem *par = item->parent;
        QWidget *w = 0;
        QGridLayout *l = 0;
        int oldRow = -1;
        if (!par) {
            w = q_ptr;
            l = m_mainLayout;
            oldRow = m_children.indexOf(item);
        } else {
            w = par->groupBox;
            l = par->layout;
            oldRow = par->children.indexOf(item);
            if (hasHeader(par))
                oldRow += 2;
        }

        if (item->widget) {
            item->widget->setParent(w);
        } else if (item->widgetLabel) {
            item->widgetLabel->setParent(w);
        } else {
            item->widgetLabel = new QLabel(w);
            item->widgetLabel->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed));
            item->widgetLabel->setTextFormat(Qt::PlainText);
        }
        int span = 1;
        if (item->widget)
            l->addWidget(item->widget, oldRow, 1, 1, 1);
        else if (item->widgetLabel)
            l->addWidget(item->widgetLabel, oldRow, 1, 1, 1);
        else
            span = 2;
        item->label = new QLabel(w);
        item->label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
        l->addWidget(item->label, oldRow, 0, 1, span);

        updateItem(item);
    }
    m_recreateQueue.clear();
}

void QtGroupBoxPropertyBrowserPrivate::updateLater()
{
    QTimer::singleShot(0, q_ptr, SLOT(slotUpdate()));
}

void QtGroupBoxPropertyBrowserPrivate::propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex)
{
    WidgetItem *afterItem = m_indexToItem.value(afterIndex);
    WidgetItem *parentItem = m_indexToItem.value(index->parent());

    WidgetItem *newItem = new WidgetItem();
    newItem->parent = parentItem;

    QGridLayout *layout = 0;
    QWidget *parentWidget = 0;
    int row = -1;
    if (!afterItem) {
        row = 0;
        if (parentItem)
            parentItem->children.insert(0, newItem);
        else
            m_children.insert(0, newItem);
    } else {
        if (parentItem) {
            row = parentItem->children.indexOf(afterItem) + 1;
            parentItem->children.insert(row, newItem);
        } else {
            row = m_children.indexOf(afterItem) + 1;
            m_children.insert(row, newItem);
        }
    }
    if (parentItem && hasHeader(parentItem))
        row += 2;

    if (!parentItem) {
        layout = m_mainLayout;
        parentWidget = q_ptr;;
    } else {
        if (!parentItem->groupBox) {
            m_recreateQueue.removeAll(parentItem);
            WidgetItem *par = parentItem->parent;
            QWidget *w = 0;
            QGridLayout *l = 0;
            int oldRow = -1;
            if (!par) {
                w = q_ptr;
                l = m_mainLayout;
                oldRow = m_children.indexOf(parentItem);
            } else {
                w = par->groupBox;
                l = par->layout;
                oldRow = par->children.indexOf(parentItem);
                if (hasHeader(par))
                    oldRow += 2;
            }
            parentItem->groupBox = new QGroupBox(w);
            parentItem->layout = new QGridLayout();
            parentItem->groupBox->setLayout(parentItem->layout);
            if (parentItem->label) {
                l->removeWidget(parentItem->label);
                delete parentItem->label;
                parentItem->label = 0;
            }
            if (parentItem->widget) {
                l->removeWidget(parentItem->widget);
                parentItem->widget->setParent(parentItem->groupBox);
                parentItem->layout->addWidget(parentItem->widget, 0, 0, 1, 2);
                parentItem->line = new QFrame(parentItem->groupBox);
            } else if (parentItem->widgetLabel) {
                l->removeWidget(parentItem->widgetLabel);
                delete parentItem->widgetLabel;
                parentItem->widgetLabel = 0;
            }
            if (parentItem->line) {
                parentItem->line->setFrameShape(QFrame::HLine);
                parentItem->line->setFrameShadow(QFrame::Sunken);
                parentItem->layout->addWidget(parentItem->line, 1, 0, 1, 2);
            }
            l->addWidget(parentItem->groupBox, oldRow, 0, 1, 2);
            updateItem(parentItem);
        }
        layout = parentItem->layout;
        parentWidget = parentItem->groupBox;
    }

    newItem->label = new QLabel(parentWidget);
    newItem->label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
    newItem->widget = createEditor(index->property(), parentWidget);
    if (!newItem->widget) {
        newItem->widgetLabel = new QLabel(parentWidget);
        newItem->widgetLabel->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed));
        newItem->widgetLabel->setTextFormat(Qt::PlainText);
    } else {
        QObject::connect(newItem->widget, SIGNAL(destroyed()), q_ptr, SLOT(slotEditorDestroyed()));
        m_widgetToItem[newItem->widget] = newItem;
    }

    insertRow(layout, row);
    int span = 1;
    if (newItem->widget)
        layout->addWidget(newItem->widget, row, 1);
    else if (newItem->widgetLabel)
        layout->addWidget(newItem->widgetLabel, row, 1);
    else
        span = 2;
    layout->addWidget(newItem->label, row, 0, 1, span);

    m_itemToIndex[newItem] = index;
    m_indexToItem[index] = newItem;

    updateItem(newItem);
}

void QtGroupBoxPropertyBrowserPrivate::propertyRemoved(QtBrowserItem *index)
{
    WidgetItem *item = m_indexToItem.value(index);

    m_indexToItem.remove(index);
    m_itemToIndex.remove(item);

    WidgetItem *parentItem = item->parent;

    int row = -1;

    if (parentItem) {
        row = parentItem->children.indexOf(item);
        parentItem->children.removeAt(row);
        if (hasHeader(parentItem))
            row += 2;
    } else {
        row = m_children.indexOf(item);
        m_children.removeAt(row);
    }

    if (item->widget)
        delete item->widget;
    if (item->label)
        delete item->label;
    if (item->widgetLabel)
        delete item->widgetLabel;
    if (item->groupBox)
        delete item->groupBox;

    if (!parentItem) {
        removeRow(m_mainLayout, row);
    } else if (parentItem->children.count() != 0) {
        removeRow(parentItem->layout, row);
    } else {
        WidgetItem *par = parentItem->parent;
        //QWidget *w = 0;
        QGridLayout *l = 0;
        int oldRow = -1;
        if (!par) {
            //w = q_ptr;
            l = m_mainLayout;
            oldRow = m_children.indexOf(parentItem);
        } else {
            //w = par->groupBox;
            l = par->layout;
            oldRow = par->children.indexOf(parentItem);
            if (hasHeader(par))
                oldRow += 2;
        }

        if (parentItem->widget) {
            parentItem->widget->hide();
            parentItem->widget->setParent(0);
        } else if (parentItem->widgetLabel) {
            parentItem->widgetLabel->hide();
            parentItem->widgetLabel->setParent(0);
        } else {
            //parentItem->widgetLabel = new QLabel(w);
        }
        l->removeWidget(parentItem->groupBox);
        delete parentItem->groupBox;
        parentItem->groupBox = 0;
        parentItem->line = 0;
        parentItem->layout = 0;
        if (!m_recreateQueue.contains(parentItem))
            m_recreateQueue.append(parentItem);
        updateLater();
    }
    m_recreateQueue.removeAll(item);

    delete item;
}

void QtGroupBoxPropertyBrowserPrivate::insertRow(QGridLayout *layout, int row) const
{
    QMap<QLayoutItem *, QRect> itemToPos;
    int idx = 0;
    while (idx < layout->count()) {
        int r, c, rs, cs;
        layout->getItemPosition(idx, &r, &c, &rs, &cs);
        if (r >= row) {
            itemToPos[layout->takeAt(idx)] = QRect(r + 1, c, rs, cs);
        } else {
            idx++;
        }
    }

    const QMap<QLayoutItem *, QRect>::ConstIterator icend = itemToPos.constEnd();
    for (QMap<QLayoutItem *, QRect>::ConstIterator it = itemToPos.constBegin(); it != icend; ++it) {
        const QRect r = it.value();
        layout->addItem(it.key(), r.x(), r.y(), r.width(), r.height());
    }
}

void QtGroupBoxPropertyBrowserPrivate::removeRow(QGridLayout *layout, int row) const
{
    QMap<QLayoutItem *, QRect> itemToPos;
    int idx = 0;
    while (idx < layout->count()) {
        int r, c, rs, cs;
        layout->getItemPosition(idx, &r, &c, &rs, &cs);
        if (r > row) {
            itemToPos[layout->takeAt(idx)] = QRect(r - 1, c, rs, cs);
        } else {
            idx++;
        }
    }

    const QMap<QLayoutItem *, QRect>::ConstIterator icend = itemToPos.constEnd();
    for (QMap<QLayoutItem *, QRect>::ConstIterator it = itemToPos.constBegin(); it != icend; ++it) {
        const QRect r = it.value();
        layout->addItem(it.key(), r.x(), r.y(), r.width(), r.height());
    }
}

bool QtGroupBoxPropertyBrowserPrivate::hasHeader(WidgetItem *item) const
{
    if (item->widget)
        return true;
    return false;
}

void QtGroupBoxPropertyBrowserPrivate::propertyChanged(QtBrowserItem *index)
{
    WidgetItem *item = m_indexToItem.value(index);

    updateItem(item);
}

void QtGroupBoxPropertyBrowserPrivate::updateItem(WidgetItem *item)
{
    QtProperty *property = m_itemToIndex[item]->property();
    if (item->groupBox) {
        QFont font = item->groupBox->font();
        font.setUnderline(property->isModified());
        item->groupBox->setFont(font);
        item->groupBox->setTitle(property->propertyName());
        item->groupBox->setToolTip(property->toolTip());
        item->groupBox->setStatusTip(property->statusTip());
        item->groupBox->setWhatsThis(property->whatsThis());
        item->groupBox->setEnabled(property->isEnabled());
    }
    if (item->label) {
        QFont font = item->label->font();
        font.setUnderline(property->isModified());
        item->label->setFont(font);
        item->label->setText(property->propertyName());
        item->label->setToolTip(property->toolTip());
        item->label->setStatusTip(property->statusTip());
        item->label->setWhatsThis(property->whatsThis());
        item->label->setEnabled(property->isEnabled());
    }
    if (item->widgetLabel) {
        QFont font = item->widgetLabel->font();
        font.setUnderline(false);
        item->widgetLabel->setFont(font);
        item->widgetLabel->setText(property->valueText());
        item->widgetLabel->setToolTip(property->valueText());
        item->widgetLabel->setEnabled(property->isEnabled());
    }
    if (item->widget) {
        QFont font = item->widget->font();
        font.setUnderline(false);
        item->widget->setFont(font);
        item->widget->setEnabled(property->isEnabled());
        item->widget->setToolTip(property->valueText());
    }
    //item->setIcon(1, property->valueIcon());
}



/*!
    \class QtGroupBoxPropertyBrowser

    \brief The QtGroupBoxPropertyBrowser class provides a QGroupBox
    based property browser.

    A property browser is a widget that enables the user to edit a
    given set of properties. Each property is represented by a label
    specifying the property's name, and an editing widget (e.g. a line
    edit or a combobox) holding its value. A property can have zero or
    more subproperties.

    QtGroupBoxPropertyBrowser provides group boxes for all nested
    properties, i.e. subproperties are enclosed by a group box with
    the parent property's name as its title. For example:

    \image qtgroupboxpropertybrowser.png

    Use the QtAbstractPropertyBrowser API to add, insert and remove
    properties from an instance of the QtGroupBoxPropertyBrowser
    class. The properties themselves are created and managed by
    implementations of the QtAbstractPropertyManager class.

    \sa QtTreePropertyBrowser, QtAbstractPropertyBrowser
*/

/*!
    Creates a property browser with the given \a parent.
*/
QtGroupBoxPropertyBrowser::QtGroupBoxPropertyBrowser(QWidget *parent)
    : QtAbstractPropertyBrowser(parent)
{
    d_ptr = new QtGroupBoxPropertyBrowserPrivate;
    d_ptr->q_ptr = this;

    d_ptr->init(this);
}

/*!
    Destroys this property browser.

    Note that the properties that were inserted into this browser are
    \e not destroyed since they may still be used in other
    browsers. The properties are owned by the manager that created
    them.

    \sa QtProperty, QtAbstractPropertyManager
*/
QtGroupBoxPropertyBrowser::~QtGroupBoxPropertyBrowser()
{
    const QMap<QtGroupBoxPropertyBrowserPrivate::WidgetItem *, QtBrowserItem *>::ConstIterator icend = d_ptr->m_itemToIndex.constEnd();
    for (QMap<QtGroupBoxPropertyBrowserPrivate::WidgetItem *, QtBrowserItem *>::ConstIterator it = d_ptr->m_itemToIndex.constBegin(); it != icend; ++it)
        delete it.key();
    delete d_ptr;
}

/*!
    \reimp
*/
void QtGroupBoxPropertyBrowser::itemInserted(QtBrowserItem *item, QtBrowserItem *afterItem)
{
    d_ptr->propertyInserted(item, afterItem);
}

/*!
    \reimp
*/
void QtGroupBoxPropertyBrowser::itemRemoved(QtBrowserItem *item)
{
    d_ptr->propertyRemoved(item);
}

/*!
    \reimp
*/
void QtGroupBoxPropertyBrowser::itemChanged(QtBrowserItem *item)
{
    d_ptr->propertyChanged(item);
}

#if QT_VERSION >= 0x040400
QT_END_NAMESPACE
#endif

#include "moc_qtgroupboxpropertybrowser.cpp"
ting bindings via pybind11. The underlying issue is that the ``std::unique_ptr`` holder type that is responsible for managing the lifetime of instances will reference the destructor even if no deallocations ever take place. In order to expose classes with private or protected destructors, it is possible to override the holder type via a holder type argument to ``class_``. Pybind11 provides a helper class ``py::nodelete`` that disables any destructor invocations. In this case, it is crucial that instances are deallocated on the C++ side to avoid memory leaks. .. code-block:: cpp /* ... definition ... */ class MyClass { private: ~MyClass() { } }; /* ... binding code ... */ py::class_<MyClass, std::unique_ptr<MyClass, py::nodelete>>(m, "MyClass") .def(py::init<>()) .. _destructors_that_call_python: Destructors that call Python ============================ If a Python function is invoked from a C++ destructor, an exception may be thrown of type :class:`error_already_set`. If this error is thrown out of a class destructor, ``std::terminate()`` will be called, terminating the process. Class destructors must catch all exceptions of type :class:`error_already_set` to discard the Python exception using :func:`error_already_set::discard_as_unraisable`. Every Python function should be treated as *possibly throwing*. When a Python generator stops yielding items, Python will throw a ``StopIteration`` exception, which can pass though C++ destructors if the generator's stack frame holds the last reference to C++ objects. For more information, see :ref:`the documentation on exceptions <unraisable_exceptions>`. .. code-block:: cpp class MyClass { public: ~MyClass() { try { py::print("Even printing is dangerous in a destructor"); py::exec("raise ValueError('This is an unraisable exception')"); } catch (py::error_already_set &e) { // error_context should be information about where/why the occurred, // e.g. use __func__ to get the name of the current function e.discard_as_unraisable(__func__); } } }; .. note:: pybind11 does not support C++ destructors marked ``noexcept(false)``. .. versionadded:: 2.6 .. _implicit_conversions: Implicit conversions ==================== Suppose that instances of two types ``A`` and ``B`` are used in a project, and that an ``A`` can easily be converted into an instance of type ``B`` (examples of this could be a fixed and an arbitrary precision number type). .. code-block:: cpp py::class_<A>(m, "A") /// ... members ... py::class_<B>(m, "B") .def(py::init<A>()) /// ... members ... m.def("func", [](const B &) { /* .... */ } ); To invoke the function ``func`` using a variable ``a`` containing an ``A`` instance, we'd have to write ``func(B(a))`` in Python. On the other hand, C++ will automatically apply an implicit type conversion, which makes it possible to directly write ``func(a)``. In this situation (i.e. where ``B`` has a constructor that converts from ``A``), the following statement enables similar implicit conversions on the Python side: .. code-block:: cpp py::implicitly_convertible<A, B>(); .. note:: Implicit conversions from ``A`` to ``B`` only work when ``B`` is a custom data type that is exposed to Python via pybind11. To prevent runaway recursion, implicit conversions are non-reentrant: an implicit conversion invoked as part of another implicit conversion of the same type (i.e. from ``A`` to ``B``) will fail. .. _static_properties: Static properties ================= The section on :ref:`properties` discussed the creation of instance properties that are implemented in terms of C++ getters and setters. Static properties can also be created in a similar way to expose getters and setters of static class attributes. Note that the implicit ``self`` argument also exists in this case and is used to pass the Python ``type`` subclass instance. This parameter will often not be needed by the C++ side, and the following example illustrates how to instantiate a lambda getter function that ignores it: .. code-block:: cpp py::class_<Foo>(m, "Foo") .def_property_readonly_static("foo", [](py::object /* self */) { return Foo(); }); Operator overloading ==================== Suppose that we're given the following ``Vector2`` class with a vector addition and scalar multiplication operation, all implemented using overloaded operators in C++. .. code-block:: cpp class Vector2 { public: Vector2(float x, float y) : x(x), y(y) { } Vector2 operator+(const Vector2 &v) const { return Vector2(x + v.x, y + v.y); } Vector2 operator*(float value) const { return Vector2(x * value, y * value); } Vector2& operator+=(const Vector2 &v) { x += v.x; y += v.y; return *this; } Vector2& operator*=(float v) { x *= v; y *= v; return *this; } friend Vector2 operator*(float f, const Vector2 &v) { return Vector2(f * v.x, f * v.y); } std::string toString() const { return "[" + std::to_string(x) + ", " + std::to_string(y) + "]"; } private: float x, y; }; The following snippet shows how the above operators can be conveniently exposed to Python. .. code-block:: cpp #include <pybind11/operators.h> PYBIND11_MODULE(example, m) { py::class_<Vector2>(m, "Vector2") .def(py::init<float, float>()) .def(py::self + py::self) .def(py::self += py::self) .def(py::self *= float()) .def(float() * py::self) .def(py::self * float()) .def(-py::self) .def("__repr__", &Vector2::toString); } Note that a line like .. code-block:: cpp .def(py::self * float()) is really just short hand notation for .. code-block:: cpp .def("__mul__", [](const Vector2 &a, float b) { return a * b; }, py::is_operator()) This can be useful for exposing additional operators that don't exist on the C++ side, or to perform other types of customization. The ``py::is_operator`` flag marker is needed to inform pybind11 that this is an operator, which returns ``NotImplemented`` when invoked with incompatible arguments rather than throwing a type error. .. note:: To use the more convenient ``py::self`` notation, the additional header file :file:`pybind11/operators.h` must be included. .. seealso:: The file :file:`tests/test_operator_overloading.cpp` contains a complete example that demonstrates how to work with overloaded operators in more detail. .. _pickling: Pickling support ================ Python's ``pickle`` module provides a powerful facility to serialize and de-serialize a Python object graph into a binary data stream. To pickle and unpickle C++ classes using pybind11, a ``py::pickle()`` definition must be provided. Suppose the class in question has the following signature: .. code-block:: cpp class Pickleable { public: Pickleable(const std::string &value) : m_value(value) { } const std::string &value() const { return m_value; } void setExtra(int extra) { m_extra = extra; } int extra() const { return m_extra; } private: std::string m_value; int m_extra = 0; }; Pickling support in Python is enabled by defining the ``__setstate__`` and ``__getstate__`` methods [#f3]_. For pybind11 classes, use ``py::pickle()`` to bind these two functions: .. code-block:: cpp py::class_<Pickleable>(m, "Pickleable") .def(py::init<std::string>()) .def("value", &Pickleable::value) .def("extra", &Pickleable::extra) .def("setExtra", &Pickleable::setExtra) .def(py::pickle( [](const Pickleable &p) { // __getstate__ /* Return a tuple that fully encodes the state of the object */ return py::make_tuple(p.value(), p.extra()); }, [](py::tuple t) { // __setstate__ if (t.size() != 2) throw std::runtime_error("Invalid state!"); /* Create a new C++ instance */ Pickleable p(t[0].cast<std::string>()); /* Assign any additional state */ p.setExtra(t[1].cast<int>()); return p; } )); The ``__setstate__`` part of the ``py::picke()`` definition follows the same rules as the single-argument version of ``py::init()``. The return type can be a value, pointer or holder type. See :ref:`custom_constructors` for details. An instance can now be pickled as follows: .. code-block:: python try: import cPickle as pickle # Use cPickle on Python 2.7 except ImportError: import pickle p = Pickleable("test_value") p.setExtra(15) data = pickle.dumps(p, 2) .. note:: Note that only the cPickle module is supported on Python 2.7. The second argument to ``dumps`` is also crucial: it selects the pickle protocol version 2, since the older version 1 is not supported. Newer versions are also fine—for instance, specify ``-1`` to always use the latest available version. Beware: failure to follow these instructions will cause important pybind11 memory allocation routines to be skipped during unpickling, which will likely lead to memory corruption and/or segmentation faults. .. seealso:: The file :file:`tests/test_pickling.cpp` contains a complete example that demonstrates how to pickle and unpickle types using pybind11 in more detail. .. [#f3] http://docs.python.org/3/library/pickle.html#pickling-class-instances Deepcopy support ================ Python normally uses references in assignments. Sometimes a real copy is needed to prevent changing all copies. The ``copy`` module [#f5]_ provides these capabilities. On Python 3, a class with pickle support is automatically also (deep)copy compatible. However, performance can be improved by adding custom ``__copy__`` and ``__deepcopy__`` methods. With Python 2.7, these custom methods are mandatory for (deep)copy compatibility, because pybind11 only supports cPickle. For simple classes (deep)copy can be enabled by using the copy constructor, which should look as follows: .. code-block:: cpp py::class_<Copyable>(m, "Copyable") .def("__copy__", [](const Copyable &self) { return Copyable(self); }) .def("__deepcopy__", [](const Copyable &self, py::dict) { return Copyable(self); }, "memo"_a); .. note:: Dynamic attributes will not be copied in this example. .. [#f5] https://docs.python.org/3/library/copy.html Multiple Inheritance ==================== pybind11 can create bindings for types that derive from multiple base types (aka. *multiple inheritance*). To do so, specify all bases in the template arguments of the ``class_`` declaration: .. code-block:: cpp py::class_<MyType, BaseType1, BaseType2, BaseType3>(m, "MyType") ... The base types can be specified in arbitrary order, and they can even be interspersed with alias types and holder types (discussed earlier in this document)---pybind11 will automatically find out which is which. The only requirement is that the first template argument is the type to be declared. It is also permitted to inherit multiply from exported C++ classes in Python, as well as inheriting from multiple Python and/or pybind11-exported classes. There is one caveat regarding the implementation of this feature: When only one base type is specified for a C++ type that actually has multiple bases, pybind11 will assume that it does not participate in multiple inheritance, which can lead to undefined behavior. In such cases, add the tag ``multiple_inheritance`` to the class constructor: .. code-block:: cpp py::class_<MyType, BaseType2>(m, "MyType", py::multiple_inheritance()); The tag is redundant and does not need to be specified when multiple base types are listed. .. _module_local: Module-local class bindings =========================== When creating a binding for a class, pybind11 by default makes that binding "global" across modules. What this means is that a type defined in one module can be returned from any module resulting in the same Python type. For example, this allows the following: .. code-block:: cpp // In the module1.cpp binding code for module1: py::class_<Pet>(m, "Pet") .def(py::init<std::string>()) .def_readonly("name", &Pet::name); .. code-block:: cpp // In the module2.cpp binding code for module2: m.def("create_pet", [](std::string name) { return new Pet(name); }); .. code-block:: pycon >>> from module1 import Pet >>> from module2 import create_pet >>> pet1 = Pet("Kitty") >>> pet2 = create_pet("Doggy") >>> pet2.name() 'Doggy' When writing binding code for a library, this is usually desirable: this allows, for example, splitting up a complex library into multiple Python modules. In some cases, however, this can cause conflicts. For example, suppose two unrelated modules make use of an external C++ library and each provide custom bindings for one of that library's classes. This will result in an error when a Python program attempts to import both modules (directly or indirectly) because of conflicting definitions on the external type: .. code-block:: cpp // dogs.cpp // Binding for external library class: py::class<pets::Pet>(m, "Pet") .def("name", &pets::Pet::name); // Binding for local extension class: py::class<Dog, pets::Pet>(m, "Dog") .def(py::init<std::string>()); .. code-block:: cpp // cats.cpp, in a completely separate project from the above dogs.cpp. // Binding for external library class: py::class<pets::Pet>(m, "Pet") .def("get_name", &pets::Pet::name); // Binding for local extending class: py::class<Cat, pets::Pet>(m, "Cat") .def(py::init<std::string>()); .. code-block:: pycon >>> import cats >>> import dogs Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: generic_type: type "Pet" is already registered! To get around this, you can tell pybind11 to keep the external class binding localized to the module by passing the ``py::module_local()`` attribute into the ``py::class_`` constructor: .. code-block:: cpp // Pet binding in dogs.cpp: py::class<pets::Pet>(m, "Pet", py::module_local()) .def("name", &pets::Pet::name); .. code-block:: cpp // Pet binding in cats.cpp: py::class<pets::Pet>(m, "Pet", py::module_local()) .def("get_name", &pets::Pet::name); This makes the Python-side ``dogs.Pet`` and ``cats.Pet`` into distinct classes, avoiding the conflict and allowing both modules to be loaded. C++ code in the ``dogs`` module that casts or returns a ``Pet`` instance will result in a ``dogs.Pet`` Python instance, while C++ code in the ``cats`` module will result in a ``cats.Pet`` Python instance. This does come with two caveats, however: First, external modules cannot return or cast a ``Pet`` instance to Python (unless they also provide their own local bindings). Second, from the Python point of view they are two distinct classes. Note that the locality only applies in the C++ -> Python direction. When passing such a ``py::module_local`` type into a C++ function, the module-local classes are still considered. This means that if the following function is added to any module (including but not limited to the ``cats`` and ``dogs`` modules above) it will be callable with either a ``dogs.Pet`` or ``cats.Pet`` argument: .. code-block:: cpp m.def("pet_name", [](const pets::Pet &pet) { return pet.name(); }); For example, suppose the above function is added to each of ``cats.cpp``, ``dogs.cpp`` and ``frogs.cpp`` (where ``frogs.cpp`` is some other module that does *not* bind ``Pets`` at all). .. code-block:: pycon >>> import cats, dogs, frogs # No error because of the added py::module_local() >>> mycat, mydog = cats.Cat("Fluffy"), dogs.Dog("Rover") >>> (cats.pet_name(mycat), dogs.pet_name(mydog)) ('Fluffy', 'Rover') >>> (cats.pet_name(mydog), dogs.pet_name(mycat), frogs.pet_name(mycat)) ('Rover', 'Fluffy', 'Fluffy') It is possible to use ``py::module_local()`` registrations in one module even if another module registers the same type globally: within the module with the module-local definition, all C++ instances will be cast to the associated bound Python type. In other modules any such values are converted to the global Python type created elsewhere. .. note:: STL bindings (as provided via the optional :file:`pybind11/stl_bind.h` header) apply ``py::module_local`` by default when the bound type might conflict with other modules; see :ref:`stl_bind` for details. .. note:: The localization of the bound types is actually tied to the shared object or binary generated by the compiler/linker. For typical modules created with ``PYBIND11_MODULE()``, this distinction is not significant. It is possible, however, when :ref:`embedding` to embed multiple modules in the same binary (see :ref:`embedding_modules`). In such a case, the localization will apply across all embedded modules within the same binary. .. seealso:: The file :file:`tests/test_local_bindings.cpp` contains additional examples that demonstrate how ``py::module_local()`` works. Binding protected member functions ================================== It's normally not possible to expose ``protected`` member functions to Python: .. code-block:: cpp class A { protected: int foo() const { return 42; } }; py::class_<A>(m, "A") .def("foo", &A::foo); // error: 'foo' is a protected member of 'A' On one hand, this is good because non-``public`` members aren't meant to be accessed from the outside. But we may want to make use of ``protected`` functions in derived Python classes. The following pattern makes this possible: .. code-block:: cpp class A { protected: int foo() const { return 42; } }; class Publicist : public A { // helper type for exposing protected functions public: using A::foo; // inherited with different access modifier }; py::class_<A>(m, "A") // bind the primary class .def("foo", &Publicist::foo); // expose protected methods via the publicist This works because ``&Publicist::foo`` is exactly the same function as ``&A::foo`` (same signature and address), just with a different access modifier. The only purpose of the ``Publicist`` helper class is to make the function name ``public``. If the intent is to expose ``protected`` ``virtual`` functions which can be overridden in Python, the publicist pattern can be combined with the previously described trampoline: .. code-block:: cpp class A { public: virtual ~A() = default; protected: virtual int foo() const { return 42; } }; class Trampoline : public A { public: int foo() const override { PYBIND11_OVERRIDE(int, A, foo, ); } }; class Publicist : public A { public: using A::foo; }; py::class_<A, Trampoline>(m, "A") // <-- `Trampoline` here .def("foo", &Publicist::foo); // <-- `Publicist` here, not `Trampoline`! .. note:: MSVC 2015 has a compiler bug (fixed in version 2017) which requires a more explicit function binding in the form of ``.def("foo", static_cast<int (A::*)() const>(&Publicist::foo));`` where ``int (A::*)() const`` is the type of ``A::foo``. Binding final classes ===================== Some classes may not be appropriate to inherit from. In C++11, classes can use the ``final`` specifier to ensure that a class cannot be inherited from. The ``py::is_final`` attribute can be used to ensure that Python classes cannot inherit from a specified type. The underlying C++ type does not need to be declared final. .. code-block:: cpp class IsFinal final {}; py::class_<IsFinal>(m, "IsFinal", py::is_final()); When you try to inherit from such a class in Python, you will now get this error: .. code-block:: pycon >>> class PyFinalChild(IsFinal): ... pass TypeError: type 'IsFinal' is not an acceptable base type .. note:: This attribute is currently ignored on PyPy .. versionadded:: 2.6 Custom automatic downcasters ============================ As explained in :ref:`inheritance`, pybind11 comes with built-in understanding of the dynamic type of polymorphic objects in C++; that is, returning a Pet to Python produces a Python object that knows it's wrapping a Dog, if Pet has virtual methods and pybind11 knows about Dog and this Pet is in fact a Dog. Sometimes, you might want to provide this automatic downcasting behavior when creating bindings for a class hierarchy that does not use standard C++ polymorphism, such as LLVM [#f4]_. As long as there's some way to determine at runtime whether a downcast is safe, you can proceed by specializing the ``pybind11::polymorphic_type_hook`` template: .. code-block:: cpp enum class PetKind { Cat, Dog, Zebra }; struct Pet { // Not polymorphic: has no virtual methods const PetKind kind; int age = 0; protected: Pet(PetKind _kind) : kind(_kind) {} }; struct Dog : Pet { Dog() : Pet(PetKind::Dog) {} std::string sound = "woof!"; std::string bark() const { return sound; } }; namespace pybind11 { template<> struct polymorphic_type_hook<Pet> { static const void *get(const Pet *src, const std::type_info*& type) { // note that src may be nullptr if (src && src->kind == PetKind::Dog) { type = &typeid(Dog); return static_cast<const Dog*>(src); } return src; } }; } // namespace pybind11 When pybind11 wants to convert a C++ pointer of type ``Base*`` to a Python object, it calls ``polymorphic_type_hook<Base>::get()`` to determine if a downcast is possible. The ``get()`` function should use whatever runtime information is available to determine if its ``src`` parameter is in fact an instance of some class ``Derived`` that inherits from ``Base``. If it finds such a ``Derived``, it sets ``type = &typeid(Derived)`` and returns a pointer to the ``Derived`` object that contains ``src``. Otherwise, it just returns ``src``, leaving ``type`` at its default value of nullptr. If you set ``type`` to a type that pybind11 doesn't know about, no downcasting will occur, and the original ``src`` pointer will be used with its static type ``Base*``. It is critical that the returned pointer and ``type`` argument of ``get()`` agree with each other: if ``type`` is set to something non-null, the returned pointer must point to the start of an object whose type is ``type``. If the hierarchy being exposed uses only single inheritance, a simple ``return src;`` will achieve this just fine, but in the general case, you must cast ``src`` to the appropriate derived-class pointer (e.g. using ``static_cast<Derived>(src)``) before allowing it to be returned as a ``void*``. .. [#f4] https://llvm.org/docs/HowToSetUpLLVMStyleRTTI.html .. note:: pybind11's standard support for downcasting objects whose types have virtual methods is implemented using ``polymorphic_type_hook`` too, using the standard C++ ability to determine the most-derived type of a polymorphic object using ``typeid()`` and to cast a base pointer to that most-derived type (even if you don't know what it is) using ``dynamic_cast<void*>``. .. seealso:: The file :file:`tests/test_tagbased_polymorphic.cpp` contains a more complete example, including a demonstration of how to provide automatic downcasting for an entire class hierarchy without writing one get() function for each class. Accessing the type object ========================= You can get the type object from a C++ class that has already been registered using: .. code-block:: python py::type T_py = py::type::of<T>(); You can directly use ``py::type::of(ob)`` to get the type object from any python object, just like ``type(ob)`` in Python. .. note:: Other types, like ``py::type::of<int>()``, do not work, see :ref:`type-conversions`. .. versionadded:: 2.6