Merge lp:~nick-dedekind/dee-qt/glib-events into lp:dee-qt

Proposed by Nick Dedekind
Status: Needs review
Proposed branch: lp:~nick-dedekind/dee-qt/glib-events
Merge into: lp:dee-qt
Diff against target: 406 lines (+227/-74)
5 files modified
deelistmodel.cpp (+67/-52)
glibcallbackevent.h (+69/-0)
tests/CMakeLists.txt (+14/-14)
tests/deelistmodeltest.cpp (+32/-1)
tests/test-helper.cpp (+45/-7)
To merge this branch: bzr merge lp:~nick-dedekind/dee-qt/glib-events
Reviewer Review Type Date Requested Status
Michal Hruby (community) Needs Fixing
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+195584@code.launchpad.net

Commit message

Fixes the issue where calling into Qt directly from glib callbacks can cause deleteLater events not to serviced by qt event loop.

Description of the change

Send qt events from glib callbacks.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

The build failure is a regression of cmake 2.8.12 in trusty, the regression is fixed in cmake 2.8.12.1 that at the time of writing this comment is in trusty-proposed

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michal Hruby (mhr3) wrote :

Instead of putting everything inside a lambda and increasing the indent inside the actual signal handlers, could we move the QGlibCallbackEvent into a lambda where the signals are connected? That way it's clearer why it's there plus the handler methods themselves could be cleaned up from the unused parameters (and made non-static).

So for example:
g_signal_connect_swapped(m_deeModel, "row-added",
  [] (DeeListModel* model, DeeModelIter* iter) {
    QGlibCallbackEvent::sendEvent(model, [model, iter] (QObject*) {
      model->d->onRowAdded(iter);
    }
  }, m_parent);

review: Needs Fixing
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

hmm. I'm getting a conversion error for the callback. You sure a lambda can be used in place of GCallback?

invalid user-defined conversion from ‘DeeListModelPrivate::connectToDeeModel(DeeModel*)::__lambda0’ to ‘GCallback {aka void (*)()}’ [-fpermissive]

Revision history for this message
Michal Hruby (mhr3) wrote :

Well, you still need to cast it with G_CALLBACK([] () {}), that should do it.

Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

> Well, you still need to cast it with G_CALLBACK([] () {}), that should do it.

FAIL.

g_signal_connect(m_deeModel, "row-added", G_CALLBACK([](GObject *emitter,
                                                        DeeModelIter* iter,
                                                        DeeListModel* model) {
    QGlibCallbackEvent::sendEvent(model, [model, iter](QObject*) {
        model->d->onRowAdded(iter);
    });
}), m_parent);

Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

Also, now that it's using an anonymous function, we can no longer disconnect from it. using g_object_disconnect. Would need to store the signal id and disconnect using g_signal_handler_disconnect for each connection.

Revision history for this message
Michal Hruby (mhr3) wrote :

> Also, now that it's using an anonymous function, we can no longer disconnect
> from it. using g_object_disconnect. Would need to store the signal id and
> disconnect using g_signal_handler_disconnect for each connection.

No biggie, but there's still g_signal_handlers_disconnect_by_data() if you didn't want to keep the signal ids.

Revision history for this message
Michal Hruby (mhr3) wrote :

> Well, you still need to cast it with G_CALLBACK([] () {}), that should do it.

Had hard time believing that it wouldn't work, but it doesn't indeed... Nonetheless with a cast in-between it does:

    typedef void(*DeeModelIterFunc)(DeeListModel*, DeeModelIter*);

    g_signal_connect_swapped(m_deeModel, "row-added", G_CALLBACK((DeeModelIterFunc)([] (DeeListModel* model, DeeModelIter* iter)
    {
        QGlibCallbackEvent::sendEvent(model, [model, iter](QObject*) {
            model->d->onRowAdded(iter);
        });
    })), m_parent);

Revision history for this message
Michal Hruby (mhr3) wrote :

Better yet:

#define LAMBDA_HANDLER(f) (GCallback) ((void(*)(DeeListModel*, ...)) (f))
g_signal_connect_swapped(m_deeModel, "row-added", LAMBDA_HANDLER([](..){..}), ..);

;)

Unmerged revisions

85. By Nick Dedekind

Use qevent to handle glibevents

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'deelistmodel.cpp'
2--- deelistmodel.cpp 2013-09-19 17:47:01 +0000
3+++ deelistmodel.cpp 2013-11-18 11:20:05 +0000
4@@ -25,6 +25,9 @@
5
6 #include "deelistmodel.h"
7 #include "variantconversions.h"
8+#include "glibcallbackevent.h"
9+
10+const QEvent::Type QGlibCallbackEvent::eventType = static_cast<QEvent::Type>(QEvent::registerEventType());
11
12 class DeeListModelPrivate {
13 public:
14@@ -287,12 +290,14 @@
15 GParamSpec *pspec,
16 DeeListModel *model)
17 {
18- model->d->createRoles();
19- model->beginResetModel();
20- model->d->m_count = dee_model_get_n_rows(model->d->m_deeModel);
21- model->synchronizedChanged(model->synchronized());
22- model->endResetModel();
23- Q_EMIT model->countChanged();
24+ QGlibCallbackEvent::sendEvent(model, [model](QObject*) {
25+ model->d->createRoles();
26+ model->beginResetModel();
27+ model->d->m_count = dee_model_get_n_rows(model->d->m_deeModel);
28+ model->synchronizedChanged(model->synchronized());
29+ model->endResetModel();
30+ Q_EMIT model->countChanged();
31+ });
32 }
33
34 void
35@@ -300,19 +305,21 @@
36 DeeModelIter* iter,
37 DeeListModel* model)
38 {
39- if(!model->synchronized()) {
40- return;
41- }
42-
43- gint position = dee_model_get_position(model->d->m_deeModel, iter);
44-
45- /* if we're inside transaction, we'll consider this row hidden */
46- model->d->m_rowBeingAdded = position;
47- model->d->processChange(ChangeType::ADDITION, position);
48- if (!model->d->m_changesetInProgress) {
49- model->d->flushChanges();
50- }
51- model->d->m_rowBeingAdded = -1;
52+ QGlibCallbackEvent::sendEvent(model, [model, iter](QObject*) {
53+ if(!model->synchronized()) {
54+ return;
55+ }
56+
57+ gint position = dee_model_get_position(model->d->m_deeModel, iter);
58+
59+ /* if we're inside transaction, we'll consider this row hidden */
60+ model->d->m_rowBeingAdded = position;
61+ model->d->processChange(ChangeType::ADDITION, position);
62+ if (!model->d->m_changesetInProgress) {
63+ model->d->flushChanges();
64+ }
65+ model->d->m_rowBeingAdded = -1;
66+ });
67 }
68
69 void
70@@ -320,23 +327,25 @@
71 DeeModelIter* iter,
72 DeeListModel* model)
73 {
74- if(!model->synchronized()) {
75- return;
76- }
77-
78- /* Note that at this point the row is still present and valid in the DeeModel.
79- Therefore the value returned by dee_model_get_n_rows() might not be
80- what one would expect.
81- See Dee's dee_sequence_model_remove() method.
82- */
83- gint position = dee_model_get_position(model->d->m_deeModel, iter);
84-
85- model->d->m_rowBeingRemoved = position;
86- model->d->processChange(ChangeType::REMOVAL, position);
87- if (!model->d->m_changesetInProgress) {
88- model->d->flushChanges();
89- }
90- model->d->m_rowBeingRemoved = -1;
91+ QGlibCallbackEvent::sendEvent(model, [model, iter](QObject*) {
92+ if(!model->synchronized()) {
93+ return;
94+ }
95+
96+ /* Note that at this point the row is still present and valid in the DeeModel.
97+ Therefore the value returned by dee_model_get_n_rows() might not be
98+ what one would expect.
99+ See Dee's dee_sequence_model_remove() method.
100+ */
101+ gint position = dee_model_get_position(model->d->m_deeModel, iter);
102+
103+ model->d->m_rowBeingRemoved = position;
104+ model->d->processChange(ChangeType::REMOVAL, position);
105+ if (!model->d->m_changesetInProgress) {
106+ model->d->flushChanges();
107+ }
108+ model->d->m_rowBeingRemoved = -1;
109+ });
110 }
111
112 void
113@@ -344,32 +353,38 @@
114 DeeModelIter* iter,
115 DeeListModel* model)
116 {
117- if(!model->synchronized()) {
118- return;
119- }
120-
121- /* If we're inside a transaction we need to flush the currently queued
122- * changes and emit the dataChanged signal after that */
123- if (model->d->m_changesetInProgress) {
124- model->d->flushChanges();
125- }
126-
127- gint position = dee_model_get_position(model->d->m_deeModel, iter);
128- QModelIndex index = model->index(position);
129- Q_EMIT model->dataChanged(index, index);
130+ QGlibCallbackEvent::sendEvent(model, [model, iter](QObject*) {
131+ if(!model->synchronized()) {
132+ return;
133+ }
134+
135+ /* If we're inside a transaction we need to flush the currently queued
136+ * changes and emit the dataChanged signal after that */
137+ if (model->d->m_changesetInProgress) {
138+ model->d->flushChanges();
139+ }
140+
141+ gint position = dee_model_get_position(model->d->m_deeModel, iter);
142+ QModelIndex index = model->index(position);
143+ Q_EMIT model->dataChanged(index, index);
144+ });
145 }
146
147 void
148 DeeListModelPrivate::onStartChangeset(DeeListModel* model)
149 {
150- model->d->m_changesetInProgress = true;
151+ QGlibCallbackEvent::sendEvent(model, [model](QObject*) {
152+ model->d->m_changesetInProgress = true;
153+ });
154 }
155
156 void
157 DeeListModelPrivate::onFinishChangeset(DeeListModel* model)
158 {
159- model->d->flushChanges();
160- model->d->m_changesetInProgress = false;
161+ QGlibCallbackEvent::sendEvent(model, [model](QObject*) {
162+ model->d->flushChanges();
163+ model->d->m_changesetInProgress = false;
164+ });
165 }
166
167
168
169=== added file 'glibcallbackevent.h'
170--- glibcallbackevent.h 1970-01-01 00:00:00 +0000
171+++ glibcallbackevent.h 2013-11-18 11:20:05 +0000
172@@ -0,0 +1,69 @@
173+/*
174+ * Copyright (C) 2010 Canonical, Ltd.
175+ *
176+ * Authors:
177+ * Florian Boucault <florian.boucault@canonical.com>
178+ *
179+ * This program is free software; you can redistribute it and/or modify
180+ * it under the terms of the GNU General Public License as published by
181+ * the Free Software Foundation; version 3.
182+ *
183+ * This program is distributed in the hope that it will be useful,
184+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
185+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
186+ * GNU General Public License for more details.
187+ *
188+ * You should have received a copy of the GNU General Public License
189+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
190+ */
191+
192+#include <QtCore/QEvent>
193+#include <QtCore/QPointer>
194+#include <QtCore/QCoreApplication>
195+
196+class QGlibCallbackEvent : public QEvent
197+{
198+public:
199+ static const QEvent::Type eventType;
200+ QGlibCallbackEvent(QObject* reciever, std::function<void(QObject*)> func)
201+ : QEvent(QGlibCallbackEvent::eventType),
202+ m_func(func)
203+ {
204+ m_handler = new QEventHandlerObject(reciever);
205+ }
206+
207+ QObject* handler() const { return m_handler; }
208+
209+ void exec() {
210+ if (!m_handler.isNull()) {
211+ m_func(m_handler->parent());
212+ m_handler->deleteLater();
213+ }
214+ }
215+
216+ static void sendEvent(QObject* reciever, std::function<void(QObject*)> func) {
217+ QGlibCallbackEvent gce(reciever, func);
218+ QCoreApplication::sendEvent(gce.handler(), &gce);
219+ }
220+
221+private:
222+ class QEventHandlerObject : public QObject
223+ {
224+ public:
225+ QEventHandlerObject(QObject* parent)
226+ : QObject(parent)
227+ {}
228+
229+ bool event(QEvent* e)
230+ {
231+ if (e->type() == QGlibCallbackEvent::eventType) {
232+ QGlibCallbackEvent *gce = static_cast<QGlibCallbackEvent*>(e);
233+ gce->exec();
234+ return true;
235+ }
236+ return QObject::event(e);
237+ }
238+ };
239+ QPointer<QEventHandlerObject> m_handler;
240+ std::function<void(QObject*)> m_func;
241+};
242\ No newline at end of file
243
244=== modified file 'tests/CMakeLists.txt'
245--- tests/CMakeLists.txt 2013-09-11 12:53:28 +0000
246+++ tests/CMakeLists.txt 2013-11-18 11:20:05 +0000
247@@ -18,21 +18,21 @@
248 add_test(NAME conversiontest COMMAND "${CMAKE_CURRENT_BINARY_DIR}/conversiontest" "-o" "${CMAKE_CURRENT_BINARY_DIR}/conversiontest-xunit.xml,xunitxml" "-o" "-,txt")
249 set_property(TEST conversiontest PROPERTY ENVIRONMENT "LD_LIBRARY_PATH=.")
250
251-if (WITHQT5)
252- # the test is using signal connections with lambdas, only available in Qt5
253- add_executable(signaltest signaltest.cpp)
254- target_link_libraries(signaltest ${OUR_QT_TEST_LIB} ${DEE_QT_LIBNAME})
255- set_target_properties(signaltest PROPERTIES COMPILE_FLAGS -fPIC)
256- add_test(NAME signaltest COMMAND "${CMAKE_CURRENT_BINARY_DIR}/signaltest" "-o" "${CMAKE_CURRENT_BINARY_DIR}/signaltest-xunit.xml,xunitxml" "-o" "-,txt")
257- set_property(TEST signaltest PROPERTY ENVIRONMENT "LD_LIBRARY_PATH=.")
258-endif ()
259-
260 add_executable(test-helper test-helper.cpp)
261 target_link_libraries(test-helper ${OUR_QT_CORE_LIB} ${OUR_QT_DBUS_LIB} ${DEE_LDFLAGS})
262 set_target_properties(test-helper PROPERTIES COMPILE_FLAGS -fPIC)
263
264-add_executable(deelistmodeltest deelistmodeltest.cpp)
265-target_link_libraries(deelistmodeltest ${OUR_QT_TEST_LIB} ${OUR_QT_DBUS_LIB} ${DEE_QT_LIBNAME})
266-set_target_properties(deelistmodeltest PROPERTIES COMPILE_FLAGS -fPIC)
267-add_test(NAME deelistmodeltest COMMAND "dbus-test-runner" "--task" "${CMAKE_CURRENT_BINARY_DIR}/test-helper" "--task" "${CMAKE_CURRENT_BINARY_DIR}/deelistmodeltest" "-p" "-xunitxml" "-p" "-o" "-p" "deelistmodeltest-xunit.xml")
268-set_property(TEST deelistmodeltest PROPERTY ENVIRONMENT "LD_LIBRARY_PATH=.")
269+if (WITHQT5)
270+ # the test is using signal connections with lambdas, only available in Qt5
271+ add_executable(signaltest signaltest.cpp)
272+ target_link_libraries(signaltest ${OUR_QT_TEST_LIB} ${DEE_QT_LIBNAME})
273+ set_target_properties(signaltest PROPERTIES COMPILE_FLAGS -fPIC)
274+ add_test(NAME signaltest COMMAND "${CMAKE_CURRENT_BINARY_DIR}/signaltest" "-o" "${CMAKE_CURRENT_BINARY_DIR}/signaltest-xunit.xml,xunitxml" "-o" "-,txt")
275+ set_property(TEST signaltest PROPERTY ENVIRONMENT "LD_LIBRARY_PATH=.")
276+
277+ add_executable(deelistmodeltest deelistmodeltest.cpp)
278+ target_link_libraries(deelistmodeltest ${OUR_QT_TEST_LIB} ${OUR_QT_DBUS_LIB} ${DEE_QT_LIBNAME})
279+ set_target_properties(deelistmodeltest PROPERTIES COMPILE_FLAGS -fPIC)
280+ add_test(NAME deelistmodeltest COMMAND "dbus-test-runner" "--task" "${CMAKE_CURRENT_BINARY_DIR}/test-helper" "--task" "${CMAKE_CURRENT_BINARY_DIR}/deelistmodeltest" "-p" "-xunitxml" "-p" "-o" "-p" "deelistmodeltest-xunit.xml")
281+ set_property(TEST deelistmodeltest PROPERTY ENVIRONMENT "LD_LIBRARY_PATH=.")
282+endif ()
283\ No newline at end of file
284
285=== modified file 'tests/deelistmodeltest.cpp'
286--- tests/deelistmodeltest.cpp 2013-09-11 12:53:28 +0000
287+++ tests/deelistmodeltest.cpp 2013-11-18 11:20:05 +0000
288@@ -104,7 +104,38 @@
289 QCOMPARE(model_qt.synchronized(), false);
290 QCOMPARE(model_qt.rowCount(), 0);
291 }
292-
293+
294+ void deleteLaterTest()
295+ {
296+ DeeListModel model_qt;
297+ model_qt.setName("com.deeqt.test.model2");
298+ while(!model_qt.synchronized())
299+ qApp->processEvents();
300+
301+ QObject* object_to_delete = new QObject(this);
302+ bool object_deleted = false;
303+ connect(object_to_delete, &QObject::destroyed, [&] (QObject* object) {
304+ object_deleted = true;
305+ });
306+
307+ QTimer timer;
308+ timer.setSingleShot(true);
309+ timer.start(2000);
310+
311+ connect(&model_qt, &QAbstractItemModel::rowsRemoved, [&object_to_delete] (const QModelIndex &parent, int start, int end) {
312+ if (object_to_delete) {
313+ object_to_delete->deleteLater();
314+ object_to_delete = NULL;
315+ }
316+ });
317+
318+ while(timer.isActive() && !object_deleted) {
319+ qApp->processEvents();
320+ }
321+
322+ QVERIFY(object_deleted);
323+ }
324+
325 void cleanupTestCase()
326 {
327 tell_service_to_exit();
328
329=== modified file 'tests/test-helper.cpp'
330--- tests/test-helper.cpp 2012-11-30 12:56:33 +0000
331+++ tests/test-helper.cpp 2013-11-18 11:20:05 +0000
332@@ -17,6 +17,7 @@
333 #include <QCoreApplication>
334 #include <QDBusConnection>
335 #include <QObject>
336+#include <QTimer>
337
338 #include <dee.h>
339
340@@ -26,15 +27,11 @@
341 Q_OBJECT
342 public:
343 TestControl()
344+ : m_model2(NULL)
345 {
346 g_type_init();
347- DeeModel* model = dee_shared_model_new("com.deeqt.test.model");
348- dee_model_set_schema(model, "b", NULL);
349-
350- // Doc says we need to be synchronized before doing anything
351- while(!dee_shared_model_is_synchronized(DEE_SHARED_MODEL(model)))
352- qApp->processEvents();
353-
354+ create_model_1();
355+ create_model_2();
356 QDBusConnection::sessionBus().registerService("com.deeqt.test.control");
357 QDBusConnection::sessionBus().registerObject("/control", this, QDBusConnection::ExportAllSlots);
358 }
359@@ -44,6 +41,47 @@
360 {
361 qApp->quit();
362 }
363+
364+ void onTimer()
365+ {
366+ static bool add = true;
367+ static int iter = 0;
368+
369+ if (add) {
370+ dee_model_append(m_model2, iter++);
371+ } else {
372+ dee_model_remove(m_model2, dee_model_get_first_iter(m_model2));
373+ }
374+ add = !add;
375+ }
376+
377+private:
378+ void create_model_1()
379+ {
380+ DeeModel* model = dee_shared_model_new("com.deeqt.test.model");
381+ dee_model_set_schema(model, "b", NULL);
382+
383+ // Doc says we need to be synchronized before doing anything
384+ while(!dee_shared_model_is_synchronized(DEE_SHARED_MODEL(model)))
385+ qApp->processEvents();
386+ }
387+
388+ void create_model_2()
389+ {
390+ m_model2 = dee_shared_model_new("com.deeqt.test.model2");
391+ dee_model_set_schema(m_model2, "i", NULL);
392+
393+ // Doc says we need to be synchronized before doing anything
394+ while(!dee_shared_model_is_synchronized(DEE_SHARED_MODEL(m_model2)))
395+ qApp->processEvents();
396+
397+ QTimer* timer(new QTimer(this));
398+ connect(timer, SIGNAL(timeout()), this, SLOT(onTimer()));
399+ timer->start(100);
400+ }
401+
402+private:
403+ DeeModel* m_model2;
404 };
405
406 int main(int argc, char *argv[])

Subscribers

People subscribed via source and target branches

to all changes: