Merge lp:~nick-dedekind/dee-qt/glib-events into lp:dee-qt
- glib-events
- Merge into trunk
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 |
Related bugs: |
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.
PS Jenkins bot (ps-jenkins) wrote : | # |
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
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:85
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
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_
[] (DeeListModel* model, DeeModelIter* iter) {
QGlibCallba
model-
}
}, m_parent);
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 ‘DeeListModelPr
Michal Hruby (mhr3) wrote : | # |
Well, you still need to cast it with G_CALLBACK([] () {}), that should do it.
Nick Dedekind (nick-dedekind) wrote : | # |
> Well, you still need to cast it with G_CALLBACK([] () {}), that should do it.
FAIL.
g_signal_
QGlibCallba
});
}), m_parent);
Nick Dedekind (nick-dedekind) wrote : | # |
Also, now that it's using an anonymous function, we can no longer disconnect from it. using g_object_
Michal Hruby (mhr3) wrote : | # |
> Also, now that it's using an anonymous function, we can no longer disconnect
> from it. using g_object_
> disconnect using g_signal_
No biggie, but there's still g_signal_
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(*DeeModelI
g_signal_
{
});
})), m_parent);
Michal Hruby (mhr3) wrote : | # |
Better yet:
#define LAMBDA_HANDLER(f) (GCallback) ((void(
g_signal_
;)
Unmerged revisions
- 85. By Nick Dedekind
-
Use qevent to handle glibevents
Preview Diff
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[]) |
FAILED: Continuous integration, rev:85 jenkins. qa.ubuntu. com/job/ dee-qt- ci/13/ jenkins. qa.ubuntu. com/job/ dee-qt- trusty- amd64-ci/ 1/console jenkins. qa.ubuntu. com/job/ dee-qt- trusty- armhf-ci/ 1/console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins: 8080/job/ dee-qt- ci/13/rebuild
http://