]> cloud.milkyroute.net Git - dolphin.git/blob - src/tests/dolphintreeviewtest.cpp
61922acd77ee3bda5fdc426b277b5d29fb32c75b
[dolphin.git] / src / tests / dolphintreeviewtest.cpp
1 /*****************************************************************************
2 * Copyright (C) 2010-2011 by Frank Reininghaus (frank78ac@googlemail.com) *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 *****************************************************************************/
19
20 #include <qtest_kde.h>
21 #include <kdebug.h>
22 #include <kaction.h>
23
24 #include "views/dolphintreeview.h"
25
26 #include <qtestkeyboard.h>
27 #include <qtestmouse.h>
28 #include <QtGui/QStringListModel>
29
30 class DolphinTreeViewTest : public QObject
31 {
32 Q_OBJECT
33
34 private slots:
35
36 void testKeyboardNavigationSelectionUpdate();
37
38 void bug218114_visualRegionForSelection();
39 void bug220898_focusOut();
40
41 private:
42
43 /** A method that simplifies checking a view's current item and selection */
44 static void verifyCurrentItemAndSelection(const QAbstractItemView& view, const QModelIndex& expectedCurrent, const QModelIndexList& expectedSelection) {
45 QCOMPARE(view.currentIndex(), expectedCurrent);
46 const QModelIndexList selectedIndexes = view.selectionModel()->selectedIndexes();
47 QCOMPARE(selectedIndexes.count(), expectedSelection.count());
48 foreach(const QModelIndex& index, expectedSelection) {
49 QVERIFY(selectedIndexes.contains(index));
50 }
51 }
52
53 /** Use this method if only one item is selected */
54 static void verifyCurrentItemAndSelection(const QAbstractItemView& view, const QModelIndex& current, const QModelIndex& selected) {
55 QModelIndexList list;
56 list << selected;
57 verifyCurrentItemAndSelection(view, current, list);
58 }
59
60 /** Use this method if the only selected item is the current item */
61 static void verifyCurrentItemAndSelection(const QAbstractItemView& view, const QModelIndex& current) {
62 verifyCurrentItemAndSelection(view, current, current);
63 }
64
65 };
66
67 /**
68 * TestView is a simple view class derived from DolphinTreeView.
69 * It makes sure that the visualRect for each index contains only the item text as
70 * returned by QAbstractItemModel::data(...) for the role Qt::DisplayRole.
71 *
72 * We have to check that DolphinTreeView handles the case of visualRects with different widths
73 * correctly because this is the case in DolphinDetailsView which is derived from DolphinTreeView.
74 */
75
76 class TestView : public DolphinTreeView
77 {
78 Q_OBJECT
79
80 public:
81
82 TestView(QWidget* parent = 0) : DolphinTreeView(parent) {};
83 ~TestView() {};
84
85 QRect visualRect(const QModelIndex& index) const {
86 QRect rect = DolphinTreeView::visualRect(index);
87
88 const QStyleOptionViewItem option = viewOptions();
89 const QFontMetrics fontMetrics(option.font);
90 int width = option.decorationSize.width() + fontMetrics.width(model()->data(index).toString());
91
92 rect.setWidth(width);
93 return rect;
94 }
95
96 };
97
98 /**
99 * This test checks that updating the selection after key presses works as expected.
100 * Qt does not handle this internally if the first letter of an item is pressed, which
101 * is why DolphinTreeView has some custom code for this. The test verifies that this
102 * works without unwanted side effects.
103 *
104 * The test uses the class TreeViewWithDeleteShortcut which deletes the selected items
105 * when Shift-Delete is pressed. This is needed to test the fix for bug 259656 (see below).
106 */
107
108 class TreeViewWithDeleteShortcut : public DolphinTreeView {
109
110 Q_OBJECT
111
112 public:
113
114 TreeViewWithDeleteShortcut(QWidget* parent = 0) : DolphinTreeView(parent) {
115 // To test the fix for bug 259656, we need a delete shortcut.
116 KAction* deleteAction = new KAction(this);
117 deleteAction->setShortcut(Qt::SHIFT | Qt::Key_Delete);
118 addAction(deleteAction);
119 connect(deleteAction, SIGNAL(triggered()), this, SLOT(deleteSelectedItems()));
120 };
121
122 ~TreeViewWithDeleteShortcut() {};
123
124 public slots:
125
126 void deleteSelectedItems() {
127 // We have to delete the items one by one and update the list of selected items after
128 // each step because every removal will invalidate the model indexes in the list.
129 QModelIndexList selectedItems = selectionModel()->selectedIndexes();
130 while (!selectedItems.isEmpty()) {
131 const QModelIndex index = selectedItems.takeFirst();
132 model()->removeRow(index.row());
133 selectedItems = selectionModel()->selectedIndexes();
134 }
135 }
136 };
137
138 void DolphinTreeViewTest::testKeyboardNavigationSelectionUpdate() {
139 QStringList items;
140 items << "a" << "b" << "c" << "d" << "e";
141 QStringListModel model(items);
142
143 QModelIndex index[5];
144 for (int i = 0; i < 5; i++) {
145 index[i] = model.index(i, 0);
146 }
147
148 TreeViewWithDeleteShortcut view;
149 view.setModel(&model);
150 view.setSelectionMode(QAbstractItemView::ExtendedSelection);
151 view.resize(400, 400);
152 view.show();
153 QTest::qWaitForWindowShown(&view);
154
155 view.clearSelection();
156 QVERIFY(view.selectionModel()->selectedIndexes().isEmpty());
157
158 /**
159 * Check that basic keyboard navigation with arrow keys works.
160 */
161
162 view.setCurrentIndex(index[0]);
163 verifyCurrentItemAndSelection(view, index[0]);
164
165 // Go down -> item 1 ("b") should be selected
166 kDebug() << "Down";
167 QTest::keyClick(view.viewport(), Qt::Key_Down);
168 verifyCurrentItemAndSelection(view, index[1]);
169
170 // Go down -> item 2 ("c") should be selected
171 kDebug() << "Down";
172 QTest::keyClick(view.viewport(), Qt::Key_Down);
173 verifyCurrentItemAndSelection(view, index[2]);
174
175 // Ctrl-Up -> item 2 ("c") remains selected
176 kDebug() << "Ctrl-Up";
177 QTest::keyClick(view.viewport(), Qt::Key_Up, Qt::ControlModifier);
178 verifyCurrentItemAndSelection(view, index[1], index[2]);
179
180 // Go up -> item 0 ("a") should be selected
181 kDebug() << "Up";
182 QTest::keyClick(view.viewport(), Qt::Key_Up);
183 verifyCurrentItemAndSelection(view, index[0]);
184
185 // Shift-Down -> items 0 and 1 ("a" and "b") should be selected
186 kDebug() << "Shift-Down";
187 QTest::keyClick(view.viewport(), Qt::Key_Down, Qt::ShiftModifier);
188 QModelIndexList expectedSelection;
189 expectedSelection << index[0] << index[1];
190 verifyCurrentItemAndSelection(view, index[1], expectedSelection);
191
192 /**
193 * When the first letter of a file name is pressed, this file becomes the current item
194 * and gets selected. If the user then Shift-clicks another item, it is expected that
195 * all items between these two items get selected. Before the bug
196 *
197 * https://bugs.kde.org/show_bug.cgi?id=201459
198 *
199 * was fixed, this was not the case: the starting point for the Shift-selection was not
200 * updated if an item was selected by pressing the first letter of the file name.
201 */
202
203 view.clearSelection();
204 QVERIFY(view.selectionModel()->selectedIndexes().isEmpty());
205
206 // Control-click item 0 ("a")
207 kDebug() << "Ctrl-click on \"a\"";
208 QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ControlModifier, view.visualRect(index[0]).center());
209 verifyCurrentItemAndSelection(view, index[0]);
210
211 // Press "c", such that item 2 ("c") should be the current one.
212 kDebug() << "Press \"c\"";
213 QTest::keyClick(view.viewport(), Qt::Key_C);
214 verifyCurrentItemAndSelection(view, index[2]);
215
216 // Now Shift-Click the last item ("e"). We expect that 3 items ("c", "d", "e") are selected.
217 kDebug() << "Shift-click on \"e\"";
218 QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ShiftModifier, view.visualRect(index[4]).center());
219 expectedSelection.clear();
220 expectedSelection << index[2] << index[3] << index[4];
221 verifyCurrentItemAndSelection(view, index[4], expectedSelection);
222
223 /**
224 * Starting a drag&drop operation should not clear the selection, see
225 *
226 * https://bugs.kde.org/show_bug.cgi?id=158649
227 */
228
229 view.clearSelection();
230 QVERIFY(view.selectionModel()->selectedIndexes().isEmpty());
231
232 // Click item 0 ("a")
233 kDebug() << "Click on \"a\"";
234 QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, view.visualRect(index[0]).center());
235 verifyCurrentItemAndSelection(view, index[0]);
236
237 // Shift-Down -> "a" and "b" should be selected
238 kDebug() << "Shift-Down";
239 QTest::keyClick(view.viewport(), Qt::Key_Down, Qt::ShiftModifier);
240 expectedSelection.clear();
241 expectedSelection << index[0] << index[1];
242 verifyCurrentItemAndSelection(view, index[1], expectedSelection);
243
244 // Press mouse button on item 0 ("a"), but do not release it. Check that the selection is unchanged
245 kDebug() << "Mouse press on \"a\"";
246 QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::NoModifier, view.visualRect(index[0]).center());
247 verifyCurrentItemAndSelection(view, index[0], expectedSelection);
248
249 // Move mouse to item 1 ("b"), check that selection is unchanged
250 kDebug() << "Move mouse to \"b\"";
251 QMouseEvent moveEvent(QEvent::MouseMove, view.visualRect(index[1]).center(), Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
252 bool moveEventReceived = qApp->notify(view.viewport(), &moveEvent);
253 QVERIFY(moveEventReceived);
254 verifyCurrentItemAndSelection(view, index[0], expectedSelection);
255
256 // Release mouse button on item 1 ("b"), check that selection is unchanged
257 kDebug() << "Mouse release on \"b\"";
258 QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::NoModifier, view.visualRect(index[1]).center());
259 verifyCurrentItemAndSelection(view, index[0], expectedSelection);
260
261 /**
262 * Keeping Shift+Delete pressed for some time should delete only one item, see
263 *
264 * https://bugs.kde.org/show_bug.cgi?id=259656
265 */
266
267 view.clearSelection();
268 QVERIFY(view.selectionModel()->selectedIndexes().isEmpty());
269
270 // Click item 0 ("a")
271 kDebug() << "Click on \"a\"";
272 QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, view.visualRect(index[0]).center());
273 verifyCurrentItemAndSelection(view, index[0]);
274
275 // Press Shift-Delete and keep the keys pressed for some time
276 kDebug() << "Press Shift-Delete";
277 QTest::keyPress(view.viewport(), Qt::Key_Delete, Qt::ShiftModifier);
278 QTest::qWait(200);
279 QTest::keyRelease(view.viewport(), Qt::Key_Delete, Qt::ShiftModifier);
280
281 // Verify that only one item has been deleted
282 QCOMPARE(view.model()->rowCount(), 4);
283 }
284
285 /**
286 * QTreeView assumes implicitly that the width of each item's visualRect is the same. This leads to painting
287 * problems in Dolphin if items with different widths are in one QItemSelectionRange, see
288 *
289 * https://bugs.kde.org/show_bug.cgi?id=218114
290 *
291 * To fix this, DolphinTreeView has a custom implementation of visualRegionForSelection(). The following
292 * unit test checks that.
293 */
294
295 void DolphinTreeViewTest::bug218114_visualRegionForSelection()
296 {
297 QStringList items;
298 items << "a" << "an item with a long name" << "a";
299 QStringListModel model(items);
300
301 QModelIndex index0 = model.index(0, 0);
302 QModelIndex index1 = model.index(1, 0);
303 QModelIndex index2 = model.index(2, 0);
304
305 TestView view;
306 view.setModel(&model);
307 view.setSelectionMode(QAbstractItemView::ExtendedSelection);
308 view.resize(400, 400);
309 view.show();
310 QTest::qWaitForWindowShown(&view);
311
312 // First check that the width of index1 is larger than that of index0 and index2 (this triggers the bug).
313
314 QVERIFY(view.visualRect(index0).width() < view.visualRect(index1).width());
315 QVERIFY(view.visualRect(index2).width() < view.visualRect(index1).width());
316
317 // Select all items in one go.
318
319 view.selectAll();
320 const QItemSelection selection = view.selectionModel()->selection();
321 QCOMPARE(selection.count(), 1);
322 QCOMPARE(selection.indexes().count(), 3);
323
324 // Verify that the visualRegionForSelection contains all visualRects.
325 // We do this indirectly using QRegion::boundingRect() because
326 // QRegion::contains(const QRect&) returns true even if the QRect is not
327 // entirely inside the QRegion.
328
329 const QRegion region = view.visualRegionForSelection(selection);
330 const QRect boundingRect = region.boundingRect();
331
332 QVERIFY(boundingRect.contains(view.visualRect(index0)));
333 QVERIFY(boundingRect.contains(view.visualRect(index1)));
334 QVERIFY(boundingRect.contains(view.visualRect(index2)));
335 }
336
337 /**
338 * This test verifies that selection of multiple items with the mouse works
339 * if a key was pressed and the keyboard focus moved to another window before the
340 * key was released, see
341 *
342 * https://bugs.kde.org/show_bug.cgi?id=220898
343 */
344
345 void DolphinTreeViewTest::bug220898_focusOut()
346 {
347 QStringList items;
348 items << "a" << "b" << "c" << "d" << "e";
349 QStringListModel model(items);
350
351 QModelIndex index[5];
352 for (int i = 0; i < 5; i++) {
353 index[i] = model.index(i, 0);
354 }
355
356 TestView view;
357 view.setModel(&model);
358 view.setSelectionMode(QAbstractItemView::ExtendedSelection);
359 view.resize(400, 400);
360 view.show();
361 QTest::qWaitForWindowShown(&view);
362
363 view.setCurrentIndex(index[0]);
364 verifyCurrentItemAndSelection(view, index[0]);
365
366 // Press Down
367 QTest::keyPress(view.viewport(), Qt::Key_Down, Qt::NoModifier);
368
369 // Move keyboard focus to another widget
370 QWidget widget;
371 widget.show();
372 QTest::qWaitForWindowShown(&widget);
373 widget.setFocus();
374
375 // Wait until the widgets have received the focus events
376 while (view.viewport()->hasFocus()) {
377 QTest::qWait(10);
378 }
379 QVERIFY(!view.viewport()->hasFocus());
380
381 // Release the "Down" key
382 QTest::keyRelease(&widget, Qt::Key_Down, Qt::NoModifier);
383
384 // Move keyboard focus back to the view
385 widget.hide();
386 view.viewport()->setFocus();
387
388 // Wait until the widgets have received the focus events
389 while (widget.hasFocus()) {
390 QTest::qWait(10);
391 }
392 QVERIFY(!widget.hasFocus());
393
394 // Press left mouse button below the last item
395 const int lastRowHeight = view.sizeHintForRow(4);
396 QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::NoModifier, view.visualRect(index[4]).center() + QPoint(0, lastRowHeight));
397
398 // Move mouse to the first item and release
399 QTest::mouseMove(view.viewport(), view.visualRect(index[0]).center());
400 QMouseEvent moveEvent(QEvent::MouseMove, view.visualRect(index[0]).center(), Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
401 bool moveEventReceived = qApp->notify(view.viewport(), &moveEvent);
402 QVERIFY(moveEventReceived);
403 QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::NoModifier, view.visualRect(index[0]).center());
404
405 // All items should be selected
406 QModelIndexList expectedSelection;
407 expectedSelection << index[0] << index[1] << index[2] << index[3] << index[4];
408 verifyCurrentItemAndSelection(view, index[0], expectedSelection);
409 }
410
411 QTEST_KDEMAIN(DolphinTreeViewTest, GUI)
412
413 #include "dolphintreeviewtest.moc"