1 /*****************************************************************************
2 * Copyright (C) 2010-2011 by Frank Reininghaus (frank78ac@googlemail.com) *
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. *
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. *
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 *****************************************************************************/
20 #include <qtest_kde.h>
24 #include "views/dolphintreeview.h"
26 #include <qtestkeyboard.h>
27 #include <qtestmouse.h>
28 #include <QtGui/QStringListModel>
30 class DolphinTreeViewTest
: public QObject
36 void testKeyboardNavigationSelectionUpdate();
38 void bug218114_visualRegionForSelection();
39 void bug220898_focusOut();
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
));
53 /** Use this method if only one item is selected */
54 static void verifyCurrentItemAndSelection(const QAbstractItemView
& view
, const QModelIndex
& current
, const QModelIndex
& selected
) {
57 verifyCurrentItemAndSelection(view
, current
, list
);
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
);
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.
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.
76 class TestView
: public DolphinTreeView
82 TestView(QWidget
* parent
= 0) : DolphinTreeView(parent
) {};
85 QRect
visualRect(const QModelIndex
& index
) const {
86 QRect rect
= DolphinTreeView::visualRect(index
);
88 const QStyleOptionViewItem option
= viewOptions();
89 const QFontMetrics
fontMetrics(option
.font
);
90 int width
= option
.decorationSize
.width() + fontMetrics
.width(model()->data(index
).toString());
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.
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).
108 class TreeViewWithDeleteShortcut
: public DolphinTreeView
{
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()));
122 ~TreeViewWithDeleteShortcut() {};
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();
138 void DolphinTreeViewTest::testKeyboardNavigationSelectionUpdate() {
140 items
<< "a" << "b" << "c" << "d" << "e";
141 QStringListModel
model(items
);
143 QModelIndex index
[5];
144 for (int i
= 0; i
< 5; i
++) {
145 index
[i
] = model
.index(i
, 0);
148 TreeViewWithDeleteShortcut view
;
149 view
.setModel(&model
);
150 view
.setSelectionMode(QAbstractItemView::ExtendedSelection
);
151 view
.resize(400, 400);
153 QTest::qWaitForWindowShown(&view
);
155 view
.clearSelection();
156 QVERIFY(view
.selectionModel()->selectedIndexes().isEmpty());
159 * Check that basic keyboard navigation with arrow keys works.
162 view
.setCurrentIndex(index
[0]);
163 verifyCurrentItemAndSelection(view
, index
[0]);
165 // Go down -> item 1 ("b") should be selected
167 QTest::keyClick(view
.viewport(), Qt::Key_Down
);
168 verifyCurrentItemAndSelection(view
, index
[1]);
170 // Go down -> item 2 ("c") should be selected
172 QTest::keyClick(view
.viewport(), Qt::Key_Down
);
173 verifyCurrentItemAndSelection(view
, index
[2]);
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]);
180 // Go up -> item 0 ("a") should be selected
182 QTest::keyClick(view
.viewport(), Qt::Key_Up
);
183 verifyCurrentItemAndSelection(view
, index
[0]);
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
);
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
197 * https://bugs.kde.org/show_bug.cgi?id=201459
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.
203 view
.clearSelection();
204 QVERIFY(view
.selectionModel()->selectedIndexes().isEmpty());
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]);
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]);
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
);
224 * Starting a drag&drop operation should not clear the selection, see
226 * https://bugs.kde.org/show_bug.cgi?id=158649
229 view
.clearSelection();
230 QVERIFY(view
.selectionModel()->selectedIndexes().isEmpty());
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]);
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
);
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
);
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
);
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
);
262 * Keeping Shift+Delete pressed for some time should delete only one item, see
264 * https://bugs.kde.org/show_bug.cgi?id=259656
267 view
.clearSelection();
268 QVERIFY(view
.selectionModel()->selectedIndexes().isEmpty());
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]);
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
);
279 QTest::keyRelease(view
.viewport(), Qt::Key_Delete
, Qt::ShiftModifier
);
281 // Verify that only one item has been deleted
282 QCOMPARE(view
.model()->rowCount(), 4);
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
289 * https://bugs.kde.org/show_bug.cgi?id=218114
291 * To fix this, DolphinTreeView has a custom implementation of visualRegionForSelection(). The following
292 * unit test checks that.
295 void DolphinTreeViewTest::bug218114_visualRegionForSelection()
298 items
<< "a" << "an item with a long name" << "a";
299 QStringListModel
model(items
);
301 QModelIndex index0
= model
.index(0, 0);
302 QModelIndex index1
= model
.index(1, 0);
303 QModelIndex index2
= model
.index(2, 0);
306 view
.setModel(&model
);
307 view
.setSelectionMode(QAbstractItemView::ExtendedSelection
);
308 view
.resize(400, 400);
310 QTest::qWaitForWindowShown(&view
);
312 // First check that the width of index1 is larger than that of index0 and index2 (this triggers the bug).
314 QVERIFY(view
.visualRect(index0
).width() < view
.visualRect(index1
).width());
315 QVERIFY(view
.visualRect(index2
).width() < view
.visualRect(index1
).width());
317 // Select all items in one go.
320 const QItemSelection selection
= view
.selectionModel()->selection();
321 QCOMPARE(selection
.count(), 1);
322 QCOMPARE(selection
.indexes().count(), 3);
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.
329 const QRegion region
= view
.visualRegionForSelection(selection
);
330 const QRect boundingRect
= region
.boundingRect();
332 QVERIFY(boundingRect
.contains(view
.visualRect(index0
)));
333 QVERIFY(boundingRect
.contains(view
.visualRect(index1
)));
334 QVERIFY(boundingRect
.contains(view
.visualRect(index2
)));
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
342 * https://bugs.kde.org/show_bug.cgi?id=220898
345 void DolphinTreeViewTest::bug220898_focusOut()
348 items
<< "a" << "b" << "c" << "d" << "e";
349 QStringListModel
model(items
);
351 QModelIndex index
[5];
352 for (int i
= 0; i
< 5; i
++) {
353 index
[i
] = model
.index(i
, 0);
357 view
.setModel(&model
);
358 view
.setSelectionMode(QAbstractItemView::ExtendedSelection
);
359 view
.resize(400, 400);
361 QTest::qWaitForWindowShown(&view
);
363 view
.setCurrentIndex(index
[0]);
364 verifyCurrentItemAndSelection(view
, index
[0]);
367 QTest::keyPress(view
.viewport(), Qt::Key_Down
, Qt::NoModifier
);
369 // Move keyboard focus to another widget
372 QTest::qWaitForWindowShown(&widget
);
375 // Wait until the widgets have received the focus events
376 while (view
.viewport()->hasFocus()) {
379 QVERIFY(!view
.viewport()->hasFocus());
381 // Release the "Down" key
382 QTest::keyRelease(&widget
, Qt::Key_Down
, Qt::NoModifier
);
384 // Move keyboard focus back to the view
386 view
.viewport()->setFocus();
388 // Wait until the widgets have received the focus events
389 while (widget
.hasFocus()) {
392 QVERIFY(!widget
.hasFocus());
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
));
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());
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
);
411 QTEST_KDEMAIN(DolphinTreeViewTest
, GUI
)
413 #include "dolphintreeviewtest.moc"