1 /*
2  * Copyright (C) 2010 Emweb bv, Herent, Belgium.
3  *
4  * See the LICENSE file for terms of use.
5  */
6 #include <boost/test/unit_test.hpp>
7 #include <iostream>
8 
9 #include <Wt/WStandardItemModel.h>
10 #include <Wt/WStandardItem.h>
11 #include <Wt/WBatchEditProxyModel.h>
12 
13 using namespace Wt;
14 
15 enum ModelType { SourceModel, ProxyModel };
16 
17 namespace {
18   std::string d(WAbstractItemModel *model, int row, int column,
19 		const WModelIndex& parent = WModelIndex())
20   {
21     return cpp17::any_cast<std::string>
22       (model->data(row, column, ItemDataRole::Display, parent));
23   }
24 }
25 
26 struct BatchEditFixture {
BatchEditFixtureBatchEditFixture27   BatchEditFixture() {
28     sourceModel_ = std::make_shared<WStandardItemModel>(0, 4);
29     proxyModel_ = std::make_shared<WBatchEditProxyModel>();
30     proxyModel_->setSourceModel(sourceModel_);
31 
32     proxyModel_->setNewRowData(0, std::string("New column 0"));
33     proxyModel_->setNewRowData(1, std::string("New column 1"));
34     proxyModel_->setNewRowData(2, std::string("New column 2"));
35     proxyModel_->setNewRowData(3, std::string("New column 3"));
36 
37     connectEvents(sourceModel_, SourceModel);
38     connectEvents(proxyModel_, ProxyModel);
39   }
40 
41   std::shared_ptr<Wt::WStandardItemModel> sourceModel_;
42   std::shared_ptr<Wt::WBatchEditProxyModel> proxyModel_;
43 
44   enum EventType {
45     RowsInserted,
46     ColumnsInserted,
47     RowsRemoved,
48     ColumnsRemoved,
49     DataChanged
50   };
51 
52   struct ModelEvent {
53     Wt::WModelIndex index;
54     int start, end;
55     bool ended;
56     EventType type;
57   };
58 
59   std::vector<ModelEvent> modelEvents_[2];
60 
connectEventsBatchEditFixture61   void connectEvents(std::shared_ptr<WAbstractItemModel> model,
62 		     ModelType modelType)
63   {
64     typedef BatchEditFixture This;
65     using namespace std::placeholders;
66 
67     model->rowsAboutToBeInserted().connect
68       (std::bind(&This::geometryChanged, this, _1, _2, _3,
69 		   modelType, RowsInserted, false));
70     model->rowsInserted().connect
71       (std::bind(&This::geometryChanged, this, _1, _2, _3,
72 		   modelType, RowsInserted, true));
73 
74     model->rowsAboutToBeRemoved().connect
75       (std::bind(&This::geometryChanged, this, _1, _2, _3,
76 		   modelType, RowsRemoved, false));
77     model->rowsRemoved().connect
78       (std::bind(&This::geometryChanged, this, _1, _2, _3,
79 		   modelType, RowsRemoved, true));
80 
81     model->columnsAboutToBeInserted().connect
82       (std::bind(&This::geometryChanged, this, _1, _2, _3,
83 		   modelType, ColumnsInserted, false));
84     model->columnsInserted().connect
85       (std::bind(&This::geometryChanged, this, _1, _2, _3,
86 		   modelType, ColumnsInserted, true));
87 
88     model->columnsAboutToBeRemoved().connect
89       (std::bind(&This::geometryChanged, this, _1, _2, _3,
90 		   modelType, ColumnsRemoved, false));
91     model->columnsRemoved().connect
92       (std::bind(&This::geometryChanged, this, _1, _2, _3,
93 		   modelType, ColumnsRemoved, true));
94   }
95 
96 
clearEventsBatchEditFixture97   void clearEvents()
98   {
99     modelEvents_[0].clear();
100     modelEvents_[1].clear();
101   }
102 
geometryChangedBatchEditFixture103   void geometryChanged(const WModelIndex& parent,
104 		       int start, int end,
105 		       ModelType model, EventType type,
106 		       bool ended)
107   {
108     if (!ended) {
109       ModelEvent event;
110       event.index = parent;
111       event.start = start;
112       event.end = end;
113       event.type = type;
114       event.ended = false;
115 
116       modelEvents_[model].push_back(event);
117     } else {
118       for (int i = (int)(modelEvents_[model].size()) - 1; i >= 0; --i) {
119 	ModelEvent& e = modelEvents_[model][i];
120 	if (e.type == type
121 	    && e.index == parent
122 	    && e.start == start
123 	    && e.end == end
124 	    && !e.ended) {
125 	  e.ended = true;
126 	  return;
127 	}
128       }
129 
130       BOOST_FAIL("Non-matched geometry ending event");
131     }
132   }
133 
134 };
135 
BOOST_AUTO_TEST_CASE(batchedit_test1)136 BOOST_AUTO_TEST_CASE( batchedit_test1 )
137 {
138   BatchEditFixture f;
139 
140   auto sm = f.sourceModel_.get();
141   auto pm = f.proxyModel_.get();
142 
143   BOOST_REQUIRE(sm->columnCount() == 4);
144   BOOST_REQUIRE(pm->columnCount() == 4);
145 
146   BOOST_REQUIRE(sm->rowCount() == 0);
147   BOOST_REQUIRE(pm->rowCount() == 0);
148 
149   pm->insertRows(0, 2);
150 
151   BOOST_REQUIRE(f.modelEvents_[SourceModel].size() == 0);
152   BOOST_REQUIRE(f.modelEvents_[ProxyModel].size() == 1);
153 
154   BOOST_REQUIRE(sm->rowCount() == 0);
155   BOOST_REQUIRE(pm->rowCount() == 2);
156 
157   f.clearEvents();
158 
159   BOOST_REQUIRE(d(pm, 0, 0) == "New column 0");
160   BOOST_REQUIRE(d(pm, 0, 1) == "New column 1");
161   BOOST_REQUIRE(d(pm, 0, 2) == "New column 2");
162   BOOST_REQUIRE(d(pm, 0, 3) == "New column 3");
163 
164   pm->setData(1, 0, std::string("Column 0"), ItemDataRole::Display);
165   pm->setData(1, 1, std::string("Column 1"), ItemDataRole::Display);
166   pm->setData(1, 2, std::string("Column 2"), ItemDataRole::Display);
167   pm->setData(1, 3, std::string("Column 3"), ItemDataRole::Display);
168 
169   BOOST_REQUIRE(d(pm, 1, 0) == "Column 0");
170   BOOST_REQUIRE(d(pm, 1, 1) == "Column 1");
171   BOOST_REQUIRE(d(pm, 1, 2) == "Column 2");
172   BOOST_REQUIRE(d(pm, 1, 3) == "Column 3");
173 
174   pm->removeRow(1);
175 
176   BOOST_REQUIRE(f.modelEvents_[SourceModel].size() == 0);
177   BOOST_REQUIRE(f.modelEvents_[ProxyModel].size() == 1);
178 
179   BOOST_REQUIRE(sm->rowCount() == 0);
180   BOOST_REQUIRE(pm->rowCount() == 1);
181 
182   f.clearEvents();
183 
184   f.proxyModel_->commitAll();
185 
186   BOOST_REQUIRE(f.modelEvents_[SourceModel].size() == 1);
187   BOOST_REQUIRE(f.modelEvents_[ProxyModel].size() == 0);
188 
189   BOOST_REQUIRE(sm->rowCount() == 1);
190   BOOST_REQUIRE(pm->rowCount() == 1);
191 
192   f.clearEvents();
193 
194   sm->insertRows(1, 3);
195 
196   BOOST_REQUIRE(f.modelEvents_[SourceModel].size() == 1);
197   BOOST_REQUIRE(f.modelEvents_[ProxyModel].size() == 3);
198 
199   BOOST_REQUIRE(sm->rowCount() == 4);
200   BOOST_REQUIRE(pm->rowCount() == 4);
201 
202   f.clearEvents();
203 
204   pm->removeRow(3);
205   pm->removeRow(1);
206 
207   BOOST_REQUIRE(pm->rowCount() == 2);
208 
209   BOOST_REQUIRE(f.modelEvents_[SourceModel].size() == 0);
210   BOOST_REQUIRE(f.modelEvents_[ProxyModel].size() == 2);
211 
212   f.clearEvents();
213 
214   pm->setData(1, 0, std::string("sm(1, 0)"), ItemDataRole::Edit);
215   BOOST_REQUIRE(d(pm, 1, 0) == "sm(1, 0)");
216 
217   f.proxyModel_->commitAll();
218 
219   BOOST_REQUIRE(sm->rowCount() == 2);
220 
221   BOOST_REQUIRE(d(pm, 1, 0) == "sm(1, 0)");
222   BOOST_REQUIRE(d(sm, 1, 0) == "sm(1, 0)");
223 
224   WModelIndex p = pm->index(1, 0);
225   pm->insertColumns(0, 4, p);
226   BOOST_REQUIRE(pm->columnCount(p) == 4);
227   pm->insertRow(0, p);
228   BOOST_REQUIRE(pm->rowCount(p) == 1);
229 
230   f.proxyModel_->commitAll();
231 
232   BOOST_REQUIRE(sm->rowCount(sm->index(1, 0)) == 1);
233   BOOST_REQUIRE(sm->columnCount(sm->index(1, 0)) == 4);
234 }
235 
236 
BOOST_AUTO_TEST_CASE(batchedit_test2)237 BOOST_AUTO_TEST_CASE( batchedit_test2 )
238 {
239   BatchEditFixture f;
240 
241   auto sm = f.sourceModel_.get();
242   auto pm = f.proxyModel_.get();
243 
244   BOOST_REQUIRE(sm->columnCount() == 4);
245   BOOST_REQUIRE(pm->columnCount() == 4);
246 
247   BOOST_REQUIRE(sm->rowCount() == 0);
248   BOOST_REQUIRE(pm->rowCount() == 0);
249 
250   sm->insertRows(0, 2);
251 
252   BOOST_REQUIRE(sm->rowCount() == 2);
253   BOOST_REQUIRE(pm->rowCount() == 2);
254 
255   f.clearEvents();
256 
257   pm->setData(0, 0, std::string("sm(1, 0)"), ItemDataRole::Edit);
258   pm->setData(1, 0, std::string("sm(2, 0)"), ItemDataRole::Edit);
259   BOOST_REQUIRE(d(pm, 0, 0) == "sm(1, 0)");
260   BOOST_REQUIRE(d(pm, 1, 0) == "sm(2, 0)");
261 
262   pm->insertRows(2, 1);
263   pm->setData(2, 0, std::string("sm(3, 0)"), ItemDataRole::Edit);
264 
265   BOOST_REQUIRE(sm->rowCount() == 2);
266   BOOST_REQUIRE(pm->rowCount() == 3);
267 
268   pm->removeRow(0);
269 
270   BOOST_REQUIRE(sm->rowCount() == 2);
271   BOOST_REQUIRE(pm->rowCount() == 2);
272 
273   BOOST_REQUIRE(d(pm, 0, 0) == "sm(2, 0)");
274   BOOST_REQUIRE(d(pm, 1, 0) == "sm(3, 0)");
275 
276   f.proxyModel_->commitAll();
277 
278   BOOST_REQUIRE(sm->rowCount() == 2);
279 
280   BOOST_REQUIRE(d(sm, 0, 0) == "sm(2, 0)");
281   BOOST_REQUIRE(d(sm, 1, 0) == "sm(3, 0)");
282 
283   BOOST_REQUIRE(d(pm, 0, 0) == "sm(2, 0)");
284   BOOST_REQUIRE(d(pm, 1, 0) == "sm(3, 0)");
285 }
286 
BOOST_AUTO_TEST_CASE(batchedit_test3)287 BOOST_AUTO_TEST_CASE( batchedit_test3 )
288 {
289   // Test flags
290   BatchEditFixture f;
291 
292   WStandardItemModel *sm = f.sourceModel_.get();
293   WBatchEditProxyModel *pm = f.proxyModel_.get();
294 
295   pm->insertRows(0, 3);
296   pm->commitAll();
297 
298   sm->item(0)->setFlags(ItemFlag::Selectable);
299   sm->item(1)->setFlags(ItemFlag::Editable);
300   sm->item(2)->setFlags(ItemFlag::UserCheckable);
301 
302   pm->setNewRowFlags(0, ItemFlag::DragEnabled);
303   pm->insertRows(1, 1);
304 
305   BOOST_REQUIRE(pm->flags(pm->index(0, 0)) == ItemFlag::Selectable);
306   BOOST_REQUIRE(pm->flags(pm->index(1, 0)) == ItemFlag::DragEnabled);
307   BOOST_REQUIRE(pm->flags(pm->index(2, 0)) == ItemFlag::Editable);
308   BOOST_REQUIRE(pm->flags(pm->index(3, 0)) == ItemFlag::UserCheckable);
309 }
310