Merge lp:~mhr3/dee-qt/changeset-support into lp:dee-qt

Proposed by Michal Hruby
Status: Merged
Approved by: Paweł Stołowski
Approved revision: 89
Merged at revision: 83
Proposed branch: lp:~mhr3/dee-qt/changeset-support
Merge into: lp:dee-qt
Diff against target: 1133 lines (+894/-37)
9 files modified
CMakeLists.txt (+7/-2)
cmake/COPYING-CMAKE-SCRIPTS (+22/-0)
cmake/Coverage.cmake (+37/-0)
debian/changelog (+6/-0)
deelistmodel.cpp (+174/-25)
tests/CMakeLists.txt (+10/-1)
tests/conversiontest.cpp (+59/-6)
tests/deelistmodeltest.cpp (+5/-3)
tests/signaltest.cpp (+574/-0)
To merge this branch: bzr merge lp:~mhr3/dee-qt/changeset-support
Reviewer Review Type Date Requested Status
Paweł Stołowski (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+185042@code.launchpad.net

Commit message

Takes advantage of added changeset API in dee 1.2.7 to map more naturally to Qt's way of dealing with changes to models.

Description of the change

Takes advantage of added changeset API in dee 1.2.7 to map more naturally to Qt's way of dealing with changes to models.

Also added support for generating coverage and added a bunch of tests to bump the coverage number to >95% (from ~50%).

Requires lp:~mhr3/dee/add-changesets

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
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Paweł Stołowski (stolowski) wrote :

357 + if (model->d->m_changesetInProgress) {

Shouldn't this say "if (!model->d->m_changesetInProgress) {"?

review: Needs Information
Revision history for this message
Paweł Stołowski (stolowski) wrote :

221 + // have the "extra" row from the addition, so we need to hide it
Can you enhance this comment a bit - how is "hiding" actually achieved here?

230 + m_count += m_changesetRowEnd - m_changesetRowStart + 1;
241 + m_count -= m_changesetRowEnd - m_changesetRowStart + 1;

Why "+1"

review: Needs Information
lp:~mhr3/dee-qt/changeset-support updated
89. By Michal Hruby

Explain things better, split up the flushing into separate method

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

> 357 + if (model->d->m_changesetInProgress) {
>
> Shouldn't this say "if (!model->d->m_changesetInProgress) {"?

No, this is callback for row-changed, in case of transaction we need to flush everything we received so far.

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

> 221 + // have the "extra" row from the addition, so we need to hide
> it
> Can you enhance this comment a bit - how is "hiding" actually achieved here?

Updated the comments, hopefully it's more clear now.

> 230 + m_count += m_changesetRowEnd - m_changesetRowStart + 1;
> 241 + m_count -= m_changesetRowEnd - m_changesetRowStart + 1;
>
> Why "+1"

Because a single insert is beginInsertRows(0, 0) => ie rowStart = 0, rowEnd = 0; count += 0 - 0 + 1;
For insertion of two rows: beginInsertRows(0, 1) => rowStart = 0, rowEnd = 1; count += 1 - 0 + 1;

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Paweł Stołowski (stolowski) wrote :

398 + // there's an extra row in the backend, skip it by incrementing
399 + row++;
400 + }
401 + } else {
402 + if (d->m_rowBeingRemoved >= 0 && row >= d->m_rowBeingRemoved) {
403 + // we need to skip the about-to-be-removed row
404 + row++;

Why are we skipping only one row? The changeset may span multiple rows, shouldn't this be taken into account?

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

When processing a transaction/changeset the row-* signals from Dee are used as synchronization points and since the Qt model signals are emitted during the transaction, there's really always just one row that needs to be skipped.

The primary thing here is that the skipping only happens when inside the row-* callback.

Consider how the transaction looks like in pseudocode:

// starting inside dee
var rows = dbus_signal.get_rows_iter();
while (rows.has_next())
{
  row = rows.next();
  var row_signal = this.submit_change(row, out iter);
  this.emit(row_signal, iter, () =>
  {
    // inside the signal emission we get into the DeeListModel plugin code
    if (consecutive_changes(iter)) processChange(...);
    else flushChangesAndProcessChange(() =>
    {
      beginRowsInserted(begin, end);
      // now qt will emit rowsInserted() or rowsRemoved()
      endRowsInserted(begin, end, () =>
      {
        // view code that will read the model
        for (int i = begin; i < end; i++)
          model.data(i, someRole); // at this point we need to skip the row that's currently being added (current iter, always just one)
      });
    });
  });
}

Of course the situation is different when the entire transaction is consecutive:
// starting inside dee
var rows = dbus_signal.get_rows_iter();
while (rows.has_next())
{
  row = rows.next();
  var row_signal = this.submit_change(row, out iter);
  this.emit(row_signal, iter, () => { processChange(...); }
}

this.emit(changeset_finished, () =>
{
  // inside the signal emission we get into the DeeListModel plugin code
  flushChanges(() =>
  {
    beginRowsInserted(begin, end);
    // now qt will emit rowsInserted() or rowsRemoved()
    endRowsInserted(begin, end, () =>
    {
      // view code that will read the model
      for (int i = begin; i < end; i++)
        model.data(i, someRole); // no need to skip anything, models are in sync
    });
  });
});

Revision history for this message
Paweł Stołowski (stolowski) wrote :

Nice stuff! Thanks for explanations!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2013-07-25 14:23:11 +0000
3+++ CMakeLists.txt 2013-09-19 17:47:15 +0000
4@@ -2,7 +2,7 @@
5 cmake_minimum_required(VERSION 2.8.6)
6
7 set(SONAME 3)
8-set(VERSION 3.2)
9+set(VERSION 3.3)
10 set(SOVERSION 3.0.0)
11
12 # Instruct CMake to run moc automatically when needed.
13@@ -10,6 +10,11 @@
14
15 # Dependencies
16 include(FindPkgConfig)
17+
18+set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" "${CMAKE_MODULE_PATH}")
19+
20+include(Coverage)
21+
22 if (WITHQT5)
23 message("Building Qt5 version")
24
25@@ -39,7 +44,7 @@
26
27 set(QT_DEE_PKGCONFIG_FILE lib${DEE_QT_LIBNAME}.pc)
28
29-pkg_check_modules(DEE REQUIRED dee-1.0)
30+pkg_check_modules(DEE REQUIRED dee-1.0>=1.2.7)
31
32 # Build flags
33 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -Wall -Wundef -std=c++0x")
34
35=== added directory 'cmake'
36=== added file 'cmake/COPYING-CMAKE-SCRIPTS'
37--- cmake/COPYING-CMAKE-SCRIPTS 1970-01-01 00:00:00 +0000
38+++ cmake/COPYING-CMAKE-SCRIPTS 2013-09-19 17:47:15 +0000
39@@ -0,0 +1,22 @@
40+Redistribution and use in source and binary forms, with or without
41+modification, are permitted provided that the following conditions
42+are met:
43+
44+1. Redistributions of source code must retain the copyright
45+ notice, this list of conditions and the following disclaimer.
46+2. Redistributions in binary form must reproduce the copyright
47+ notice, this list of conditions and the following disclaimer in the
48+ documentation and/or other materials provided with the distribution.
49+3. The name of the author may not be used to endorse or promote products
50+ derived from this software without specific prior written permission.
51+
52+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
53+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
54+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
55+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
56+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
57+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
58+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
59+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
60+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
61+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
62
63=== added file 'cmake/Coverage.cmake'
64--- cmake/Coverage.cmake 1970-01-01 00:00:00 +0000
65+++ cmake/Coverage.cmake 2013-09-19 17:47:15 +0000
66@@ -0,0 +1,37 @@
67+if (CMAKE_BUILD_TYPE MATCHES coverage)
68+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage")
69+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage")
70+ set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} --coverage")
71+ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage")
72+
73+ find_program(GCOVR_EXECUTABLE gcovr HINTS ${GCOVR_ROOT} "${GCOVR_ROOT}/bin")
74+ if (NOT GCOVR_EXECUTABLE)
75+ message(STATUS "Gcovr binary was not found, can not generate XML coverage info.")
76+ else ()
77+ message(STATUS "Gcovr found, can generate XML coverage info.")
78+ add_custom_target (coverage-xml
79+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
80+ COMMAND "${GCOVR_EXECUTABLE}" --exclude="test.*" --exclude="obj.*" -x -r "${CMAKE_SOURCE_DIR}"
81+ --object-directory=${CMAKE_BINARY_DIR} -o coverage.xml)
82+ endif()
83+
84+ find_program(LCOV_EXECUTABLE lcov HINTS ${LCOV_ROOT} "${GCOVR_ROOT}/bin")
85+ find_program(GENHTML_EXECUTABLE genhtml HINTS ${GENHTML_ROOT})
86+ if (NOT LCOV_EXECUTABLE)
87+ message(STATUS "Lcov binary was not found, can not generate HTML coverage info.")
88+ else ()
89+ if(NOT GENHTML_EXECUTABLE)
90+ message(STATUS "Genthml binary not found, can not generate HTML coverage info.")
91+ else()
92+ message(STATUS "Lcov and genhtml found, can generate HTML coverage info.")
93+ add_custom_target (coverage-html
94+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
95+ COMMAND "${LCOV_EXECUTABLE}" --capture --output-file "${CMAKE_BINARY_DIR}/coverage.info" --no-checksum --directory "${CMAKE_BINARY_DIR}"
96+ COMMAND "${LCOV_EXECUTABLE}" --remove "${CMAKE_BINARY_DIR}/coverage.info" '/usr/*' --output-file "${CMAKE_BINARY_DIR}/coverage.info"
97+ COMMAND "${LCOV_EXECUTABLE}" --remove "${CMAKE_BINARY_DIR}/coverage.info" '${CMAKE_BINARY_DIR}/moc_*' --output-file "${CMAKE_BINARY_DIR}/coverage.info"
98+ COMMAND "${LCOV_EXECUTABLE}" --remove "${CMAKE_BINARY_DIR}/coverage.info" '${CMAKE_SOURCE_DIR}/tests/*' --output-file "${CMAKE_BINARY_DIR}/coverage.info"
99+ COMMAND "${GENHTML_EXECUTABLE}" --prefix "${CMAKE_BINARY_DIR}" --output-directory coveragereport --title "Code Coverage" --legend --show-details coverage.info
100+ )
101+ endif()
102+ endif()
103+endif()
104
105=== modified file 'debian/changelog'
106--- debian/changelog 2013-08-21 16:18:56 +0000
107+++ debian/changelog 2013-09-19 17:47:15 +0000
108@@ -1,3 +1,9 @@
109+dee-qt (3.3-0ubuntu1) UNRELEASED; urgency=low
110+
111+ * Take advantage of new changeset API in dee
112+
113+ -- Michal Hruby <michal.hruby@canonical.com> Tue, 10 Sep 2013 16:56:00 +0100
114+
115 dee-qt (3.2+13.10.20130821.1-0ubuntu1) saucy; urgency=low
116
117 [ Albert Astals ]
118
119=== modified file 'deelistmodel.cpp'
120--- deelistmodel.cpp 2013-08-21 11:03:07 +0000
121+++ deelistmodel.cpp 2013-09-19 17:47:15 +0000
122@@ -28,6 +28,19 @@
123
124 class DeeListModelPrivate {
125 public:
126+ enum ProcessingState
127+ {
128+ START,
129+ ADDITIONS,
130+ REMOVALS
131+ };
132+
133+ enum ChangeType
134+ {
135+ ADDITION,
136+ REMOVAL
137+ };
138+
139 DeeListModelPrivate(DeeListModel* parent);
140 ~DeeListModelPrivate();
141
142@@ -37,22 +50,43 @@
143 void createRoles();
144 bool synchronized() const;
145
146+ void processChange(ChangeType changeType, int changePos);
147+ void flushChanges();
148+
149 /* GObject signal handlers for m_deeModel */
150 static void onSynchronizedChanged(GObject* emitter, GParamSpec *pspec, DeeListModel* model);
151 static void onRowAdded(GObject* emitter, DeeModelIter* iter, DeeListModel* model);
152 static void onRowRemoved(GObject* emitter, DeeModelIter* iter, DeeListModel* model);
153 static void onRowChanged(GObject* emitter, DeeModelIter* iter, DeeListModel* model);
154
155+ static void onStartChangeset(DeeListModel* model);
156+ static void onFinishChangeset(DeeListModel* model);
157+
158 DeeListModel* m_parent;
159 DeeModel* m_deeModel;
160 QString m_name;
161 int m_count;
162 bool m_listeningSynchronized;
163 QHash<int, QByteArray> m_roleNames;
164+ int m_rowBeingAdded;
165 int m_rowBeingRemoved;
166+ ProcessingState m_changesetState;
167+ bool m_changesetInProgress;
168+ int m_changesetRowStart;
169+ int m_changesetRowEnd;
170 };
171
172-DeeListModelPrivate::DeeListModelPrivate(DeeListModel* parent) : m_parent(parent), m_deeModel(NULL), m_count(0), m_listeningSynchronized(false), m_rowBeingRemoved(-1)
173+DeeListModelPrivate::DeeListModelPrivate(DeeListModel* parent)
174+ : m_parent(parent)
175+ , m_deeModel(NULL)
176+ , m_count(0)
177+ , m_listeningSynchronized(false)
178+ , m_rowBeingAdded(-1)
179+ , m_rowBeingRemoved(-1)
180+ , m_changesetState(ProcessingState::START)
181+ , m_changesetInProgress(false)
182+ , m_changesetRowStart(-1)
183+ , m_changesetRowEnd(-1)
184 {
185 }
186
187@@ -72,7 +106,10 @@
188 }
189 g_object_disconnect(m_deeModel, "any_signal", G_CALLBACK(onRowAdded), m_parent,
190 "any_signal", G_CALLBACK(onRowRemoved), m_parent,
191- "any_signal", G_CALLBACK(onRowChanged), m_parent, NULL);
192+ "any_signal", G_CALLBACK(onRowChanged), m_parent,
193+ "any_signal", G_CALLBACK(onStartChangeset), m_parent,
194+ "any_signal", G_CALLBACK(onFinishChangeset), m_parent,
195+ NULL);
196
197 g_object_unref(m_deeModel);
198 m_deeModel = NULL;
199@@ -104,6 +141,8 @@
200 g_signal_connect(m_deeModel, "row-added", G_CALLBACK(onRowAdded), m_parent);
201 g_signal_connect(m_deeModel, "row-removed", G_CALLBACK(onRowRemoved), m_parent);
202 g_signal_connect(m_deeModel, "row-changed", G_CALLBACK(onRowChanged), m_parent);
203+ g_signal_connect_swapped(m_deeModel, "changeset-started", G_CALLBACK(onStartChangeset), m_parent);
204+ g_signal_connect_swapped(m_deeModel, "changeset-finished", G_CALLBACK(onFinishChangeset), m_parent);
205 if (synchronized())
206 {
207 createRoles();
208@@ -134,6 +173,90 @@
209 }
210
211 void
212+DeeListModelPrivate::flushChanges()
213+{
214+ bool countChanged = false;
215+ // The problem we're facing here is that the signals emitted by DeeListModel are not completely
216+ // in sync with the backend DeeModel - Qt will likely call our data() method right after calling
217+ // end{Insert|Remove}Rows() and the backend model might still have extra rows, because we're
218+ // inside onRowRemoved/onRowAdded callback
219+ // See the data() method for more details
220+ if (m_changesetState == ProcessingState::ADDITIONS) {
221+ /* Force emission of QAbstractItemModel::rowsInserted by calling
222+ beginInsertRows and endInsertRows. Necessary because according to the
223+ documentation:
224+ "It can only be emitted by the QAbstractItemModel implementation, and
225+ cannot be explicitly emitted in subclass code."
226+ */
227+ m_parent->beginInsertRows(QModelIndex(), m_changesetRowStart, m_changesetRowEnd);
228+ m_count += m_changesetRowEnd - m_changesetRowStart + 1;
229+ countChanged = true;
230+ m_parent->endInsertRows();
231+ } else if (m_changesetState == ProcessingState::REMOVALS) {
232+ /* Force emission of QAbstractItemModel::rowsRemoved by calling
233+ beginRemoveRows and endRemoveRows. Necessary because according to the
234+ documentation:
235+ "It can only be emitted by the QAbstractItemModel implementation, and
236+ cannot be explicitly emitted in subclass code."
237+ */
238+ m_parent->beginRemoveRows(QModelIndex(), m_changesetRowStart, m_changesetRowEnd);
239+ m_count -= m_changesetRowEnd - m_changesetRowStart + 1;
240+ countChanged = true;
241+ m_parent->endRemoveRows();
242+ }
243+
244+ if (countChanged) Q_EMIT m_parent->countChanged();
245+
246+ m_changesetState = ProcessingState::START;
247+ m_changesetRowStart = -1;
248+ m_changesetRowEnd = -1;
249+}
250+
251+void
252+DeeListModelPrivate::processChange(ChangeType changeType, int changePos)
253+{
254+ /* flush if changeType doesn't match current processing state */
255+ if (m_changesetState != ProcessingState::START &&
256+ ((changeType == ChangeType::ADDITION && m_changesetState != ProcessingState::ADDITIONS) ||
257+ (changeType == ChangeType::REMOVAL && m_changesetState != ProcessingState::REMOVALS))) {
258+ flushChanges();
259+ }
260+
261+ /* flush also if current changeType isn't consecutive:
262+ * - consecutive additions are if changePos == m_changesetRowEnd + 1
263+ * - consecutive removals are if changePos == m_changesetRowStart
264+ */
265+ if ((m_changesetState == ProcessingState::ADDITIONS && changePos != m_changesetRowEnd + 1) ||
266+ (m_changesetState == ProcessingState::REMOVALS && changePos != m_changesetRowStart)) {
267+ flushChanges();
268+ }
269+
270+ switch (m_changesetState) {
271+ case ProcessingState::START:
272+ switch (changeType) {
273+ case ChangeType::ADDITION:
274+ m_changesetState = ProcessingState::ADDITIONS;
275+ m_changesetRowStart = changePos;
276+ m_changesetRowEnd = changePos;
277+ break;
278+ case ChangeType::REMOVAL:
279+ m_changesetState = ProcessingState::REMOVALS;
280+ m_changesetRowStart = changePos;
281+ m_changesetRowEnd = changePos;
282+ break;
283+ default: break;
284+ }
285+ break;
286+ case ProcessingState::ADDITIONS:
287+ m_changesetRowEnd = changePos;
288+ break;
289+ case ProcessingState::REMOVALS:
290+ m_changesetRowEnd++;
291+ break;
292+ }
293+}
294+
295+void
296 DeeListModelPrivate::createRoles()
297 {
298 if (m_deeModel == NULL) {
299@@ -182,16 +305,14 @@
300 }
301
302 gint position = dee_model_get_position(model->d->m_deeModel, iter);
303- /* Force emission of QAbstractItemModel::rowsInserted by calling
304- beginInsertRows and endInsertRows. Necessary because according to the
305- documentation:
306- "It can only be emitted by the QAbstractItemModel implementation, and
307- cannot be explicitly emitted in subclass code."
308- */
309- model->beginInsertRows(QModelIndex(), position, position);
310- model->d->m_count++;
311- model->endInsertRows();
312- Q_EMIT model->countChanged();
313+
314+ /* if we're inside transaction, we'll consider this row hidden */
315+ model->d->m_rowBeingAdded = position;
316+ model->d->processChange(ChangeType::ADDITION, position);
317+ if (!model->d->m_changesetInProgress) {
318+ model->d->flushChanges();
319+ }
320+ model->d->m_rowBeingAdded = -1;
321 }
322
323 void
324@@ -204,22 +325,17 @@
325 }
326
327 /* Note that at this point the row is still present and valid in the DeeModel.
328- Therefore the value returned by dee_model_get_n_columns() might not be
329+ Therefore the value returned by dee_model_get_n_rows() might not be
330 what one would expect.
331 See Dee's dee_sequence_model_remove() method.
332 */
333 gint position = dee_model_get_position(model->d->m_deeModel, iter);
334- /* Force emission of QAbstractItemModel::rowsRemoved by calling
335- beginRemoveRows and endRemoveRows. Necessary because according to the
336- documentation:
337- "It can only be emitted by the QAbstractItemModel implementation, and
338- cannot be explicitly emitted in subclass code."
339- */
340- model->beginRemoveRows(QModelIndex(), position, position);
341+
342 model->d->m_rowBeingRemoved = position;
343- model->d->m_count--;
344- model->endRemoveRows();
345- Q_EMIT model->countChanged();
346+ model->d->processChange(ChangeType::REMOVAL, position);
347+ if (!model->d->m_changesetInProgress) {
348+ model->d->flushChanges();
349+ }
350 model->d->m_rowBeingRemoved = -1;
351 }
352
353@@ -232,11 +348,30 @@
354 return;
355 }
356
357+ /* If we're inside a transaction we need to flush the currently queued
358+ * changes and emit the dataChanged signal after that */
359+ if (model->d->m_changesetInProgress) {
360+ model->d->flushChanges();
361+ }
362+
363 gint position = dee_model_get_position(model->d->m_deeModel, iter);
364 QModelIndex index = model->index(position);
365 Q_EMIT model->dataChanged(index, index);
366 }
367
368+void
369+DeeListModelPrivate::onStartChangeset(DeeListModel* model)
370+{
371+ model->d->m_changesetInProgress = true;
372+}
373+
374+void
375+DeeListModelPrivate::onFinishChangeset(DeeListModel* model)
376+{
377+ model->d->flushChanges();
378+ model->d->m_changesetInProgress = false;
379+}
380+
381
382
383 DeeListModel::DeeListModel(QObject *parent) :
384@@ -320,8 +455,22 @@
385 DeeModelIter* iter;
386
387 int row = index.row();
388- if (d->m_rowBeingRemoved >= 0 && row >= d->m_rowBeingRemoved) {
389- row++;
390+ // we're inside the row-{added|removed} callback and the backend model still has the removed row
391+ // (in case of row-removed) or already has the added row (in case of row-added), there are two cases:
392+ // 1) inside a transaction, we need to hide about-to-be-added row (because we just flushed changes unrelated
393+ // to this addition)
394+ // 2) outside of transaction, we need to hide about-to-be-removed row (because we're flushing right after
395+ // receiving the signal)
396+ if (d->m_changesetInProgress) {
397+ if (d->m_rowBeingAdded >= 0 && row >= d->m_rowBeingAdded) {
398+ // there's an extra row in the backend, skip it by incrementing
399+ row++;
400+ }
401+ } else {
402+ if (d->m_rowBeingRemoved >= 0 && row >= d->m_rowBeingRemoved) {
403+ // we need to skip the about-to-be-removed row
404+ row++;
405+ }
406 }
407
408 iter = dee_model_get_iter_at_row(d->m_deeModel, row);
409
410=== modified file 'tests/CMakeLists.txt'
411--- tests/CMakeLists.txt 2012-11-30 14:51:06 +0000
412+++ tests/CMakeLists.txt 2013-09-19 17:47:15 +0000
413@@ -15,9 +15,18 @@
414 add_executable(conversiontest conversiontest.cpp)
415 target_link_libraries(conversiontest ${OUR_QT_TEST_LIB} ${DEE_QT_LIBNAME})
416 set_target_properties(conversiontest PROPERTIES COMPILE_FLAGS -fPIC)
417-add_test(NAME conversiontest COMMAND "dbus-test-runner" "--task" "${CMAKE_CURRENT_BINARY_DIR}/conversiontest" "-p" "-xunitxml" "-p" "-o" "-p" "conversiontest-xunit.xml")
418+add_test(NAME conversiontest COMMAND "${CMAKE_CURRENT_BINARY_DIR}/conversiontest" "-o" "${CMAKE_CURRENT_BINARY_DIR}/conversiontest-xunit.xml,xunitxml" "-o" "-,txt")
419 set_property(TEST conversiontest PROPERTY ENVIRONMENT "LD_LIBRARY_PATH=.")
420
421+if (WITHQT5)
422+ # the test is using signal connections with lambdas, only available in Qt5
423+ add_executable(signaltest signaltest.cpp)
424+ target_link_libraries(signaltest ${OUR_QT_TEST_LIB} ${DEE_QT_LIBNAME})
425+ set_target_properties(signaltest PROPERTIES COMPILE_FLAGS -fPIC)
426+ add_test(NAME signaltest COMMAND "${CMAKE_CURRENT_BINARY_DIR}/signaltest" "-o" "${CMAKE_CURRENT_BINARY_DIR}/signaltest-xunit.xml,xunitxml" "-o" "-,txt")
427+ set_property(TEST signaltest PROPERTY ENVIRONMENT "LD_LIBRARY_PATH=.")
428+endif ()
429+
430 add_executable(test-helper test-helper.cpp)
431 target_link_libraries(test-helper ${OUR_QT_CORE_LIB} ${OUR_QT_DBUS_LIB} ${DEE_LDFLAGS})
432 set_target_properties(test-helper PROPERTIES COMPILE_FLAGS -fPIC)
433
434=== modified file 'tests/conversiontest.cpp'
435--- tests/conversiontest.cpp 2013-06-21 17:12:07 +0000
436+++ tests/conversiontest.cpp 2013-09-19 17:47:15 +0000
437@@ -31,15 +31,11 @@
438 g_type_init();
439 }
440
441- void GVariantToQVariantConversionTest()
442+ void ModelQVariantConversionTest()
443 {
444- DeeModel* model = dee_shared_model_new("com.deeqt.conversiontest");
445+ DeeModel* model = dee_sequence_model_new();
446 dee_model_set_schema(model, "b", "y", "n", "q", "i", "u", "x", "t", "d", "s", "a(ii)", "a{sv}", NULL);
447
448- // Doc says we need to be synchronized before doing anything
449- while(!dee_shared_model_is_synchronized(DEE_SHARED_MODEL(model)))
450- qApp->processEvents();
451-
452 GVariant **tuples = g_new(GVariant *, 2);
453
454 GVariant **t1 = g_new(GVariant *, 2);
455@@ -68,6 +64,7 @@
456 QCOMPARE(model_qt.count(), 0);
457
458 model_qt.setModel(model);
459+ QCOMPARE(model_qt.synchronized(), true);
460 QCOMPARE(model_qt.count(), 1);
461
462 const QModelIndex row0Index = model_qt.index(0, 0);
463@@ -99,6 +96,62 @@
464 g_free(t2);
465 g_object_unref(model);
466 }
467+
468+ void GVariantToQVariantConversionTest()
469+ {
470+ GVariant **tuples = g_new(GVariant *, 2);
471+
472+ GVariant **t1 = g_new(GVariant *, 2);
473+ t1[0] = g_variant_new_int32(1);
474+ t1[1] = g_variant_new_int32(2);
475+ tuples[0] = g_variant_new_tuple(t1, 2);
476+
477+ GVariant **t2 = g_new(GVariant *, 2);
478+ t2[0] = g_variant_new_int32(3);
479+ t2[1] = g_variant_new_int32(4);
480+ tuples[1] = g_variant_new_tuple(t2, 2);
481+
482+ GVariant* array_of_tuples = g_variant_new_array(((const GVariantType *) "(ii)"), tuples, 2);
483+
484+ QVariant variant(DeeListModel::VariantForData(array_of_tuples));
485+
486+ QList< QVariant > expected_array;
487+ QList< QVariant > tuple1, tuple2;
488+ tuple1 << 1 << 2;
489+ tuple2 << 3 << 4;
490+ expected_array << QVariant(tuple1) << QVariant(tuple2);
491+ QCOMPARE(variant, QVariant(expected_array));
492+
493+ g_free(t1);
494+ g_free(t2);
495+ g_free(tuples);
496+
497+ GVariant* nested_variant = g_variant_new_variant(g_variant_new_string("nested"));
498+ variant = DeeListModel::VariantForData(nested_variant);
499+ QCOMPARE(variant.toString(), QString("nested"));
500+ }
501+
502+ void QVariantToGVariantConversionTest()
503+ {
504+ int cnt = 0;
505+ GVariant **children = new GVariant*[8];
506+ children[cnt++] = g_variant_new_dict_entry(g_variant_new_string("int"), g_variant_new_variant(g_variant_new_int32(-82)));
507+ children[cnt++] = g_variant_new_dict_entry(g_variant_new_string("bool"), g_variant_new_variant(g_variant_new_boolean(TRUE)));
508+ children[cnt++] = g_variant_new_dict_entry(g_variant_new_string("string"), g_variant_new_variant(g_variant_new_string("foo")));
509+ children[cnt++] = g_variant_new_dict_entry(g_variant_new_string("uint"), g_variant_new_variant(g_variant_new_uint32(90)));
510+ children[cnt++] = g_variant_new_dict_entry(g_variant_new_string("int64"), g_variant_new_variant(g_variant_new_int64(-401230)));
511+ children[cnt++] = g_variant_new_dict_entry(g_variant_new_string("uint64"), g_variant_new_variant(g_variant_new_uint64(401230)));
512+ children[cnt++] = g_variant_new_dict_entry(g_variant_new_string("double"), g_variant_new_variant(g_variant_new_double(3.1415)));
513+ children[cnt++] = g_variant_new_dict_entry(g_variant_new_string("int-array"), g_variant_new_variant(g_variant_new_parsed("[1, 2, 3]")));
514+ GVariant* hints = g_variant_new_array(G_VARIANT_TYPE("{sv}"), children, cnt);
515+
516+ QVariant variant(DeeListModel::VariantForData(hints));
517+ GVariant* reconstructed_gvariant = DeeListModel::DataFromVariant(variant);
518+
519+ // the ordering in the variant might be different, so don't compare directly
520+ QVERIFY(g_variant_type_equal(g_variant_get_type(hints), g_variant_get_type(reconstructed_gvariant)));
521+ QCOMPARE(g_variant_n_children(hints), g_variant_n_children(reconstructed_gvariant));
522+ }
523 };
524
525 QTEST_MAIN(ConversionTest)
526
527=== modified file 'tests/deelistmodeltest.cpp'
528--- tests/deelistmodeltest.cpp 2012-11-30 12:56:33 +0000
529+++ tests/deelistmodeltest.cpp 2013-09-19 17:47:15 +0000
530@@ -87,12 +87,10 @@
531
532 void setExistingModelTest()
533 {
534- DeeModel* model = dee_shared_model_new("com.deeqt.test.model");
535-
536 DeeListModel model_qt;
537 QCOMPARE(model_qt.count(), 0);
538
539- model_qt.setModel(model);
540+ model_qt.setName("com.deeqt.test.model");
541 QCOMPARE(model_qt.synchronized(), false);
542
543 while(!model_qt.synchronized())
544@@ -101,6 +99,10 @@
545 QCOMPARE(model_qt.synchronized(), true);
546 QCOMPARE(model_qt.roleNames().count(), 1);
547 QCOMPARE(model_qt.roleNames()[0], QByteArray("column_0"));
548+
549+ model_qt.setName("");
550+ QCOMPARE(model_qt.synchronized(), false);
551+ QCOMPARE(model_qt.rowCount(), 0);
552 }
553
554 void cleanupTestCase()
555
556=== added file 'tests/signaltest.cpp'
557--- tests/signaltest.cpp 1970-01-01 00:00:00 +0000
558+++ tests/signaltest.cpp 2013-09-19 17:47:15 +0000
559@@ -0,0 +1,574 @@
560+/*
561+ * Copyright (C) 2012 Canonical, Ltd.
562+ *
563+ * This program is free software; you can redistribute it and/or modify
564+ * it under the terms of the GNU General Public License as published by
565+ * the Free Software Foundation; version 3.
566+ *
567+ * This program is distributed in the hope that it will be useful,
568+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
569+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
570+ * GNU General Public License for more details.
571+ *
572+ * You should have received a copy of the GNU General Public License
573+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
574+ */
575+
576+#include <QtTest>
577+#include <QObject>
578+
579+#include "deelistmodel.h"
580+
581+#include <dee.h>
582+
583+class DeeListModelTest : public QObject
584+{
585+ Q_OBJECT
586+
587+private Q_SLOTS:
588+ void initTestCase()
589+ {
590+ }
591+
592+ void synchronizationTest()
593+ {
594+ DeeModel* model = dee_sequence_model_new();
595+ dee_model_set_schema(model, "s", "i", NULL);
596+
597+ DeeListModel model_qt;
598+ QCOMPARE(model_qt.rowCount(), 0);
599+ QCOMPARE(model_qt.count(), 0);
600+
601+ model_qt.setModel(model);
602+ QCOMPARE(model_qt.synchronized(), true);
603+ QVERIFY(model_qt.name().isNull());
604+
605+ model_qt.setModel(NULL);
606+ QCOMPARE(model_qt.synchronized(), false);
607+ QCOMPARE(model_qt.rowCount(), 0);
608+ QCOMPARE(model_qt.count(), 0);
609+ }
610+
611+ void emitBasicSignalsTest()
612+ {
613+ DeeModel* model = dee_sequence_model_new();
614+ dee_model_set_schema(model, "s", "i", NULL);
615+
616+ DeeListModel model_qt;
617+ model_qt.setModel(model);
618+
619+ int num_insertions = 0;
620+
621+ connect(&model_qt, &QAbstractItemModel::rowsInserted, [&num_insertions] (const QModelIndex &parent, int start, int end) {
622+ num_insertions++;
623+ });
624+
625+ dee_model_append(model, "foo", 5);
626+
627+ QCOMPARE(num_insertions, 1);
628+ QCOMPARE(model_qt.count(), 1);
629+ QCOMPARE(model_qt.rowCount(), 1);
630+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("foo"));
631+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 5);
632+
633+ int num_removals = 0;
634+ connect(&model_qt, &QAbstractItemModel::rowsRemoved, [&num_removals] (const QModelIndex &parent, int start, int end) {
635+ num_removals++;
636+ });
637+
638+ dee_model_remove(model, dee_model_get_first_iter(model));
639+
640+ QCOMPARE(num_removals, 1);
641+ QCOMPARE(model_qt.count(), 0);
642+ QCOMPARE(model_qt.rowCount(), 0);
643+ QCOMPARE(num_insertions, 1);
644+ }
645+
646+ void emitMultipleSignalsTest()
647+ {
648+ DeeModel* model = dee_sequence_model_new();
649+ dee_model_set_schema(model, "s", "i", NULL);
650+
651+ DeeListModel model_qt;
652+ model_qt.setModel(model);
653+
654+ int num_insertions = 0;
655+
656+ connect(&model_qt, &QAbstractItemModel::rowsInserted, [&num_insertions] (const QModelIndex &parent, int start, int end) {
657+ num_insertions++;
658+ });
659+
660+ dee_model_append(model, "foo", 5);
661+ QCOMPARE(num_insertions, 1);
662+ QCOMPARE(model_qt.count(), 1);
663+ QCOMPARE(model_qt.rowCount(), 1);
664+ dee_model_append(model, "bar", 6);
665+ QCOMPARE(num_insertions, 2);
666+ QCOMPARE(model_qt.count(), 2);
667+ QCOMPARE(model_qt.rowCount(), 2);
668+ dee_model_append(model, "baz", 7);
669+ QCOMPARE(num_insertions, 3);
670+ QCOMPARE(model_qt.count(), 3);
671+ QCOMPARE(model_qt.rowCount(), 3);
672+ dee_model_append(model, "qoo", 8);
673+
674+ QCOMPARE(num_insertions, 4);
675+ QCOMPARE(model_qt.count(), 4);
676+ QCOMPARE(model_qt.rowCount(), 4);
677+
678+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("foo"));
679+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 5);
680+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 0).toString(), QString("bar"));
681+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 1).toInt(), 6);
682+ QCOMPARE(model_qt.data(model_qt.index(2, 0), 0).toString(), QString("baz"));
683+ QCOMPARE(model_qt.data(model_qt.index(2, 0), 1).toInt(), 7);
684+ QCOMPARE(model_qt.data(model_qt.index(3, 0), 0).toString(), QString("qoo"));
685+ QCOMPARE(model_qt.data(model_qt.index(3, 0), 1).toInt(), 8);
686+
687+ int num_removals = 0;
688+ connect(&model_qt, &QAbstractItemModel::rowsRemoved, [&num_removals] (const QModelIndex &parent, int start, int end) {
689+ num_removals++;
690+ });
691+
692+ num_insertions = 0;
693+
694+ dee_model_remove(model, dee_model_get_first_iter(model));
695+ QCOMPARE(num_removals, 1);
696+ QCOMPARE(model_qt.count(), 3);
697+ QCOMPARE(model_qt.rowCount(), 3);
698+ dee_model_remove(model, dee_model_get_first_iter(model));
699+ QCOMPARE(num_removals, 2);
700+ QCOMPARE(model_qt.count(), 2);
701+ QCOMPARE(model_qt.rowCount(), 2);
702+ dee_model_remove(model, dee_model_get_first_iter(model));
703+ QCOMPARE(num_removals, 3);
704+ QCOMPARE(model_qt.count(), 1);
705+ QCOMPARE(model_qt.rowCount(), 1);
706+ dee_model_remove(model, dee_model_get_first_iter(model));
707+ QCOMPARE(num_removals, 4);
708+ QCOMPARE(model_qt.count(), 0);
709+ QCOMPARE(model_qt.rowCount(), 0);
710+ dee_model_clear(model);
711+ QCOMPARE(num_removals, 4);
712+ QCOMPARE(model_qt.count(), 0);
713+ QCOMPARE(model_qt.rowCount(), 0);
714+
715+ QCOMPARE(num_insertions, 0);
716+ }
717+
718+ void emitConsecutiveInsertsTest()
719+ {
720+ DeeModel* model = dee_sequence_model_new();
721+ dee_model_set_schema(model, "s", "i", NULL);
722+
723+ DeeListModel model_qt;
724+ model_qt.setModel(model);
725+
726+ int num_insertions = 0;
727+
728+ connect(&model_qt, &QAbstractItemModel::rowsInserted, [&num_insertions] (const QModelIndex &parent, int start, int end) {
729+ num_insertions++;
730+ });
731+
732+ dee_model_begin_changeset(model);
733+
734+ dee_model_append(model, "foo", 5);
735+ dee_model_append(model, "bar", 6);
736+ dee_model_append(model, "baz", 7);
737+ dee_model_append(model, "qoo", 8);
738+
739+ QCOMPARE(num_insertions, 0);
740+
741+ dee_model_end_changeset(model);
742+
743+ QCOMPARE(num_insertions, 1);
744+ QCOMPARE(model_qt.count(), 4);
745+ QCOMPARE(model_qt.rowCount(), 4);
746+
747+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("foo"));
748+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 5);
749+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 0).toString(), QString("bar"));
750+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 1).toInt(), 6);
751+ QCOMPARE(model_qt.data(model_qt.index(2, 0), 0).toString(), QString("baz"));
752+ QCOMPARE(model_qt.data(model_qt.index(2, 0), 1).toInt(), 7);
753+ QCOMPARE(model_qt.data(model_qt.index(3, 0), 0).toString(), QString("qoo"));
754+ QCOMPARE(model_qt.data(model_qt.index(3, 0), 1).toInt(), 8);
755+
756+ QVariantMap row2 = model_qt.get(2);
757+ QCOMPARE(row2["column_0"].toString(), QString("baz"));
758+ QCOMPARE(row2["column_1"].toInt(), 7);
759+ }
760+
761+ void emitConsecutiveRemovalsTest()
762+ {
763+ DeeModel* model = dee_sequence_model_new();
764+ dee_model_set_schema(model, "s", "i", NULL);
765+
766+ DeeListModel model_qt;
767+ model_qt.setModel(model);
768+
769+ dee_model_append(model, "foo", 5);
770+ dee_model_append(model, "bar", 6);
771+ dee_model_append(model, "baz", 7);
772+ dee_model_append(model, "qoo", 8);
773+
774+ QCOMPARE(model_qt.count(), 4);
775+ QCOMPARE(model_qt.rowCount(), 4);
776+
777+ int num_removals = 0;
778+
779+ connect(&model_qt, &QAbstractItemModel::rowsRemoved, [&num_removals] (const QModelIndex &parent, int start, int end) {
780+ num_removals++;
781+ });
782+
783+ dee_model_begin_changeset(model);
784+
785+ dee_model_clear(model);
786+
787+ dee_model_end_changeset(model);
788+
789+ QCOMPARE(num_removals, 1);
790+ QCOMPARE(model_qt.count(), 0);
791+ QCOMPARE(model_qt.rowCount(), 0);
792+ }
793+
794+ void emitConsecutiveInsertsWithChangeTest()
795+ {
796+ DeeModel* model = dee_sequence_model_new();
797+ dee_model_set_schema(model, "s", "i", NULL);
798+
799+ DeeListModel model_qt;
800+ model_qt.setModel(model);
801+
802+ int num_insertions = 0;
803+
804+ connect(&model_qt, &QAbstractItemModel::rowsInserted, [&num_insertions] (const QModelIndex &parent, int start, int end) {
805+ num_insertions++;
806+ });
807+
808+ dee_model_begin_changeset(model);
809+
810+ dee_model_append(model, "koo", -5);
811+ dee_model_append(model, "bar", 6);
812+ dee_model_append(model, "baz", 7);
813+ dee_model_set(model, dee_model_get_first_iter(model), "foo", 5);
814+ dee_model_append(model, "qoo", 8);
815+
816+ QCOMPARE(num_insertions, 1);
817+ QCOMPARE(model_qt.count(), 3);
818+ QCOMPARE(model_qt.rowCount(), 3);
819+
820+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("foo"));
821+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 5);
822+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 0).toString(), QString("bar"));
823+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 1).toInt(), 6);
824+ QCOMPARE(model_qt.data(model_qt.index(2, 0), 0).toString(), QString("baz"));
825+ QCOMPARE(model_qt.data(model_qt.index(2, 0), 1).toInt(), 7);
826+
827+ dee_model_end_changeset(model);
828+
829+ QCOMPARE(num_insertions, 2);
830+ QCOMPARE(model_qt.count(), 4);
831+ QCOMPARE(model_qt.rowCount(), 4);
832+
833+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("foo"));
834+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 5);
835+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 0).toString(), QString("bar"));
836+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 1).toInt(), 6);
837+ QCOMPARE(model_qt.data(model_qt.index(2, 0), 0).toString(), QString("baz"));
838+ QCOMPARE(model_qt.data(model_qt.index(2, 0), 1).toInt(), 7);
839+ QCOMPARE(model_qt.data(model_qt.index(3, 0), 0).toString(), QString("qoo"));
840+ QCOMPARE(model_qt.data(model_qt.index(3, 0), 1).toInt(), 8);
841+ }
842+
843+ void emitSignalsMixedTest()
844+ {
845+ DeeModel* model = dee_sequence_model_new();
846+ dee_model_set_schema(model, "s", "i", NULL);
847+
848+ DeeListModel model_qt;
849+ model_qt.setModel(model);
850+
851+ int num_insertions = 0;
852+ int num_removals = 0;
853+
854+ connect(&model_qt, &QAbstractItemModel::rowsInserted, [&num_insertions] (const QModelIndex &parent, int start, int end) {
855+ num_insertions++;
856+ });
857+
858+ connect(&model_qt, &QAbstractItemModel::rowsRemoved, [&num_removals] (const QModelIndex &parent, int start, int end) {
859+ num_removals++;
860+ });
861+
862+ dee_model_begin_changeset(model);
863+
864+ dee_model_append(model, "foo", 5);
865+ dee_model_append(model, "bar", 6);
866+ dee_model_append(model, "baz", 7);
867+ dee_model_append(model, "qoo", 8);
868+
869+ dee_model_remove(model, dee_model_get_first_iter(model));
870+ dee_model_remove(model, dee_model_get_first_iter(model));
871+
872+ dee_model_end_changeset(model);
873+
874+ QCOMPARE(num_insertions, 1);
875+ QCOMPARE(num_removals, 1);
876+ QCOMPARE(model_qt.count(), 2);
877+ QCOMPARE(model_qt.rowCount(), 2);
878+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("baz"));
879+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 7);
880+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 0).toString(), QString("qoo"));
881+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 1).toInt(), 8);
882+
883+ // continue with more changes that aren't consecutive
884+ num_insertions = 0;
885+ num_removals = 0;
886+
887+ dee_model_begin_changeset(model);
888+
889+ dee_model_append(model, "foo", 5);
890+ dee_model_remove(model, dee_model_get_iter_at_row(model, 1));
891+ dee_model_remove(model, dee_model_get_iter_at_row(model, 0));
892+ dee_model_prepend(model, "qoo", 8);
893+
894+ dee_model_end_changeset(model);
895+
896+ QCOMPARE(num_insertions, 2);
897+ QCOMPARE(num_removals, 2);
898+ QCOMPARE(model_qt.count(), 2);
899+ QCOMPARE(model_qt.rowCount(), 2);
900+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("qoo"));
901+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 8);
902+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 0).toString(), QString("foo"));
903+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 1).toInt(), 5);
904+
905+ // and once again
906+ num_insertions = 0;
907+ num_removals = 0;
908+
909+ dee_model_begin_changeset(model);
910+
911+ dee_model_remove(model, dee_model_get_first_iter(model)); // ++ rem
912+ dee_model_append(model, "foo", 5); // ++ ins
913+ dee_model_clear(model); // ++ rem
914+ dee_model_append(model, "baz", 7);
915+ dee_model_append(model, "qoo", 8);
916+ dee_model_prepend(model, "bar", 6);
917+ dee_model_prepend(model, "foo", 5);
918+
919+ dee_model_end_changeset(model);
920+
921+ QCOMPARE(num_insertions, 4);
922+ QCOMPARE(num_removals, 2);
923+ QCOMPARE(model_qt.count(), 4);
924+ QCOMPARE(model_qt.rowCount(), 4);
925+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("foo"));
926+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 5);
927+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 0).toString(), QString("bar"));
928+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 1).toInt(), 6);
929+ QCOMPARE(model_qt.data(model_qt.index(2, 0), 0).toString(), QString("baz"));
930+ QCOMPARE(model_qt.data(model_qt.index(2, 0), 1).toInt(), 7);
931+ QCOMPARE(model_qt.data(model_qt.index(3, 0), 0).toString(), QString("qoo"));
932+ QCOMPARE(model_qt.data(model_qt.index(3, 0), 1).toInt(), 8);
933+ }
934+
935+ void readDuringTransactionTest()
936+ {
937+ DeeModel* model = dee_sequence_model_new();
938+ dee_model_set_schema(model, "s", "i", NULL);
939+
940+ DeeListModel model_qt;
941+ model_qt.setModel(model);
942+
943+ int num_insertions = 0;
944+ int num_removals = 0;
945+ int state = 0;
946+
947+ connect(&model_qt, &QAbstractItemModel::rowsInserted, [&] (const QModelIndex &parent, int start, int end) {
948+ num_insertions++;
949+ QVERIFY(state >= 0 && state <= 2);
950+ if (state == 0) {
951+ QCOMPARE(start, 0);
952+ QCOMPARE(end, 1);
953+ QCOMPARE(model_qt.rowCount(), 2);
954+ QCOMPARE(model_qt.count(), 2);
955+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("foo"));
956+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 5);
957+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 0).toString(), QString("baz"));
958+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 1).toInt(), 7);
959+ } else if (state == 1) {
960+ QCOMPARE(start, 0);
961+ QCOMPARE(end, 0);
962+ QCOMPARE(model_qt.rowCount(), 3);
963+ QCOMPARE(model_qt.count(), 3);
964+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("bar"));
965+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 6);
966+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 0).toString(), QString("foo"));
967+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 1).toInt(), 5);
968+ QCOMPARE(model_qt.data(model_qt.index(2, 0), 0).toString(), QString("baz"));
969+ QCOMPARE(model_qt.data(model_qt.index(2, 0), 1).toInt(), 7);
970+ } else if (state == 2) {
971+ QCOMPARE(start, 1);
972+ QCOMPARE(end, 1);
973+ QCOMPARE(model_qt.rowCount(), 2);
974+ QCOMPARE(model_qt.count(), 2);
975+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("baz"));
976+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 7);
977+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 0).toString(), QString("qoo"));
978+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 1).toInt(), 8);
979+ }
980+ });
981+
982+ connect(&model_qt, &QAbstractItemModel::rowsRemoved, [&] (const QModelIndex &parent, int start, int end) {
983+ num_removals++;
984+ QVERIFY(state >= 2 && state <= 2);
985+ if (state == 2) {
986+ QCOMPARE(start, 0);
987+ QCOMPARE(end, 1);
988+ QCOMPARE(model_qt.rowCount(), 1);
989+ QCOMPARE(model_qt.count(), 1);
990+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("baz"));
991+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 7);
992+ }
993+ });
994+
995+ dee_model_begin_changeset(model);
996+
997+ dee_model_append(model, "foo", 5);
998+ dee_model_append(model, "baz", 7);
999+ dee_model_prepend(model, "bar", 6);
1000+
1001+ state = 1;
1002+
1003+ dee_model_remove(model, dee_model_get_first_iter(model));
1004+ dee_model_remove(model, dee_model_get_first_iter(model));
1005+
1006+ state = 2;
1007+
1008+ dee_model_append(model, "qoo", 8);
1009+
1010+ dee_model_end_changeset(model);
1011+
1012+ QCOMPARE(num_insertions, 3);
1013+ QCOMPARE(num_removals, 1);
1014+ QCOMPARE(model_qt.count(), 2);
1015+ QCOMPARE(model_qt.rowCount(), 2);
1016+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("baz"));
1017+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 7);
1018+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 0).toString(), QString("qoo"));
1019+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 1).toInt(), 8);
1020+ }
1021+
1022+ void readOutsideOfTransactionTest()
1023+ {
1024+ DeeModel* model = dee_sequence_model_new();
1025+ dee_model_set_schema(model, "s", "i", NULL);
1026+
1027+ DeeListModel model_qt;
1028+ model_qt.setModel(model);
1029+
1030+ int num_insertions = 0;
1031+ int num_removals = 0;
1032+ int state = 0;
1033+
1034+ connect(&model_qt, &QAbstractItemModel::rowsInserted, [&] (const QModelIndex &parent, int start, int end) {
1035+ num_insertions++;
1036+ QVERIFY((state >= 0 && state <= 2) || state == 5);
1037+ if (state == 0) {
1038+ QCOMPARE(start, 0);
1039+ QCOMPARE(end, 0);
1040+ QCOMPARE(model_qt.rowCount(), 1);
1041+ QCOMPARE(model_qt.count(), 1);
1042+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("foo"));
1043+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 5);
1044+ } else if (state == 1) {
1045+ QCOMPARE(start, 1);
1046+ QCOMPARE(end, 1);
1047+ QCOMPARE(model_qt.rowCount(), 2);
1048+ QCOMPARE(model_qt.count(), 2);
1049+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("foo"));
1050+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 5);
1051+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 0).toString(), QString("baz"));
1052+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 1).toInt(), 7);
1053+ } else if (state == 2) {
1054+ QCOMPARE(start, 0);
1055+ QCOMPARE(end, 0);
1056+ QCOMPARE(model_qt.rowCount(), 3);
1057+ QCOMPARE(model_qt.count(), 3);
1058+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("bar"));
1059+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 6);
1060+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 0).toString(), QString("foo"));
1061+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 1).toInt(), 5);
1062+ QCOMPARE(model_qt.data(model_qt.index(2, 0), 0).toString(), QString("baz"));
1063+ QCOMPARE(model_qt.data(model_qt.index(2, 0), 1).toInt(), 7);
1064+ } else if (state == 5) {
1065+ QCOMPARE(start, 1);
1066+ QCOMPARE(end, 1);
1067+ QCOMPARE(model_qt.rowCount(), 2);
1068+ QCOMPARE(model_qt.count(), 2);
1069+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("baz"));
1070+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 7);
1071+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 0).toString(), QString("qoo"));
1072+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 1).toInt(), 8);
1073+ }
1074+ });
1075+
1076+ connect(&model_qt, &QAbstractItemModel::rowsRemoved, [&] (const QModelIndex &parent, int start, int end) {
1077+ num_removals++;
1078+ QVERIFY(state >= 3 && state <= 4);
1079+ if (state == 3) {
1080+ QCOMPARE(start, 0);
1081+ QCOMPARE(end, 0);
1082+ QCOMPARE(model_qt.rowCount(), 2);
1083+ QCOMPARE(model_qt.count(), 2);
1084+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("foo"));
1085+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 5);
1086+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 0).toString(), QString("baz"));
1087+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 1).toInt(), 7);
1088+ } else if (state == 4) {
1089+ QCOMPARE(start, 0);
1090+ QCOMPARE(end, 0);
1091+ QCOMPARE(model_qt.rowCount(), 1);
1092+ QCOMPARE(model_qt.count(), 1);
1093+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("baz"));
1094+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 7);
1095+ }
1096+ });
1097+
1098+ dee_model_append(model, "foo", 5);
1099+
1100+ state = 1;
1101+
1102+ dee_model_append(model, "baz", 7);
1103+
1104+ state = 2;
1105+
1106+ dee_model_prepend(model, "bar", 6);
1107+
1108+ state = 3;
1109+
1110+ dee_model_remove(model, dee_model_get_first_iter(model));
1111+
1112+ state = 4;
1113+
1114+ dee_model_remove(model, dee_model_get_first_iter(model));
1115+
1116+ state = 5;
1117+
1118+ dee_model_append(model, "qoo", 8);
1119+
1120+ QCOMPARE(num_insertions, 4);
1121+ QCOMPARE(num_removals, 2);
1122+ QCOMPARE(model_qt.count(), 2);
1123+ QCOMPARE(model_qt.rowCount(), 2);
1124+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 0).toString(), QString("baz"));
1125+ QCOMPARE(model_qt.data(model_qt.index(0, 0), 1).toInt(), 7);
1126+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 0).toString(), QString("qoo"));
1127+ QCOMPARE(model_qt.data(model_qt.index(1, 0), 1).toInt(), 8);
1128+ }
1129+};
1130+
1131+QTEST_MAIN(DeeListModelTest)
1132+
1133+#include "signaltest.moc"

Subscribers

People subscribed via source and target branches

to all changes: