1 /*
2  * SPDX-FileCopyrightText: 2008-2012 Peter Penz <peter.penz19@gmail.com>
3  *
4  * Based on KFilePlacesView from kdelibs:
5  * SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
6  * SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
7  *
8  * SPDX-License-Identifier: GPL-2.0-or-later
9  */
10 
11 #include "placespanel.h"
12 
13 #include "dolphin_generalsettings.h"
14 #include "global.h"
15 #include "kitemviews/kitemlistcontainer.h"
16 #include "kitemviews/kitemlistcontroller.h"
17 #include "kitemviews/kitemlistselectionmanager.h"
18 #include "kitemviews/kstandarditem.h"
19 #include "placesitem.h"
20 #include "placesitemlistgroupheader.h"
21 #include "placesitemlistwidget.h"
22 #include "placesitemmodel.h"
23 #include "placesview.h"
24 #include "trash/dolphintrash.h"
25 #include "views/draganddrophelper.h"
26 #include "settings/dolphinsettingsdialog.h"
27 
28 #include <KFilePlaceEditDialog>
29 #include <KFilePlacesModel>
30 #include <KIO/DropJob>
31 #include <KIO/EmptyTrashJob>
32 #include <KIO/Job>
33 #include <KIconLoader>
34 #include <KLocalizedString>
35 #include <KMountPoint>
36 #include <KPropertiesDialog>
37 
38 #include <QActionGroup>
39 #include <QGraphicsSceneDragDropEvent>
40 #include <QIcon>
41 #include <QMenu>
42 #include <QMimeData>
43 #include <QVBoxLayout>
44 #include <QToolTip>
45 
PlacesPanel(QWidget * parent)46 PlacesPanel::PlacesPanel(QWidget* parent) :
47     Panel(parent),
48     m_controller(nullptr),
49     m_model(nullptr),
50     m_view(nullptr),
51     m_storageSetupFailedUrl(),
52     m_triggerStorageSetupButton(),
53     m_itemDropEventIndex(-1),
54     m_itemDropEventMimeData(nullptr),
55     m_itemDropEvent(nullptr),
56     m_tooltipTimer()
57 {
58     m_tooltipTimer.setInterval(500);
59     m_tooltipTimer.setSingleShot(true);
60     connect(&m_tooltipTimer, &QTimer::timeout, this, &PlacesPanel::slotShowTooltip);
61 }
62 
~PlacesPanel()63 PlacesPanel::~PlacesPanel()
64 {
65 }
66 
proceedWithTearDown()67 void PlacesPanel::proceedWithTearDown()
68 {
69     m_model->proceedWithTearDown();
70 }
71 
urlChanged()72 bool PlacesPanel::urlChanged()
73 {
74     if (!url().isValid() || url().scheme().contains(QLatin1String("search"))) {
75         // Skip results shown by a search, as possible identical
76         // directory names are useless without parent-path information.
77         return false;
78     }
79 
80     if (m_controller) {
81         selectItem();
82     }
83 
84     return true;
85 }
86 
readSettings()87 void PlacesPanel::readSettings()
88 {
89     if (m_controller) {
90         const int delay = GeneralSettings::autoExpandFolders() ? 750 : -1;
91         m_controller->setAutoActivationDelay(delay);
92     }
93 }
94 
showEvent(QShowEvent * event)95 void PlacesPanel::showEvent(QShowEvent* event)
96 {
97     if (event->spontaneous()) {
98         Panel::showEvent(event);
99         return;
100     }
101 
102     if (!m_controller) {
103         // Postpone the creating of the controller to the first show event.
104         // This assures that no performance and memory overhead is given when the folders panel is not
105         // used at all and stays invisible.
106         m_model = new PlacesItemModel(this);
107         m_model->setGroupedSorting(true);
108         connect(m_model, &PlacesItemModel::errorMessage,
109                 this, &PlacesPanel::errorMessage);
110         connect(m_model, &PlacesItemModel::storageTearDownRequested,
111                 this, &PlacesPanel::storageTearDownRequested);
112         connect(m_model, &PlacesItemModel::storageTearDownExternallyRequested,
113                 this, &PlacesPanel::storageTearDownExternallyRequested);
114         connect(m_model, &PlacesItemModel::storageTearDownSuccessful,
115                 this, &PlacesPanel::storageTearDownSuccessful);
116 
117         m_view = new PlacesView();
118         m_view->setWidgetCreator(new KItemListWidgetCreator<PlacesItemListWidget>());
119         m_view->setGroupHeaderCreator(new KItemListGroupHeaderCreator<PlacesItemListGroupHeader>());
120 
121         installEventFilter(this);
122 
123         m_controller = new KItemListController(m_model, m_view, this);
124         m_controller->setSelectionBehavior(KItemListController::SingleSelection);
125         m_controller->setSingleClickActivationEnforced(true);
126 
127         readSettings();
128 
129         connect(m_controller, &KItemListController::itemActivated, this, &PlacesPanel::slotItemActivated);
130         connect(m_controller, &KItemListController::itemMiddleClicked, this, &PlacesPanel::slotItemMiddleClicked);
131         connect(m_controller, &KItemListController::itemContextMenuRequested, this, &PlacesPanel::slotItemContextMenuRequested);
132         connect(m_controller, &KItemListController::viewContextMenuRequested, this, &PlacesPanel::slotViewContextMenuRequested);
133         connect(m_controller, &KItemListController::itemDropEvent, this, &PlacesPanel::slotItemDropEvent);
134         connect(m_controller, &KItemListController::aboveItemDropEvent, this, &PlacesPanel::slotAboveItemDropEvent);
135 
136         KItemListContainer* container = new KItemListContainer(m_controller, this);
137         container->setEnabledFrame(false);
138 
139         QVBoxLayout* layout = new QVBoxLayout(this);
140         layout->setContentsMargins(0, 0, 0, 0);
141         layout->addWidget(container);
142 
143         selectItem();
144     }
145 
146     Panel::showEvent(event);
147 }
148 
eventFilter(QObject *,QEvent * event)149 bool PlacesPanel::eventFilter(QObject * /* obj */, QEvent *event)
150 {
151     if (event->type() == QEvent::ToolTip) {
152 
153         QHelpEvent *hoverEvent = reinterpret_cast<QHelpEvent *>(event);
154 
155         m_hoveredIndex = m_view->itemAt(hoverEvent->pos());
156         m_hoverPos = mapToGlobal(hoverEvent->pos());
157 
158         m_tooltipTimer.start();
159         return true;
160     }
161     return false;
162 }
163 
slotItemActivated(int index)164 void PlacesPanel::slotItemActivated(int index)
165 {
166     triggerItem(index, Qt::LeftButton);
167 }
168 
slotItemMiddleClicked(int index)169 void PlacesPanel::slotItemMiddleClicked(int index)
170 {
171     triggerItem(index, Qt::MiddleButton);
172 }
173 
slotItemContextMenuRequested(int index,const QPointF & pos)174 void PlacesPanel::slotItemContextMenuRequested(int index, const QPointF& pos)
175 {
176     PlacesItem* item = m_model->placesItem(index);
177     if (!item) {
178         return;
179     }
180 
181     QMenu menu(this);
182 
183     QAction* emptyTrashAction = nullptr;
184     QAction* configureTrashAction = nullptr;
185     QAction* editAction = nullptr;
186     QAction* teardownAction = nullptr;
187     QAction* ejectAction = nullptr;
188     QAction* mountAction = nullptr;
189 
190     const bool isDevice = !item->udi().isEmpty();
191     const bool isTrash = (item->url().scheme() == QLatin1String("trash"));
192     if (isTrash) {
193         emptyTrashAction = menu.addAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18nc("@action:inmenu", "Empty Trash"));
194         emptyTrashAction->setEnabled(item->icon() == QLatin1String("user-trash-full"));
195         menu.addSeparator();
196     }
197 
198     QAction* openInNewTabAction = menu.addAction(QIcon::fromTheme(QStringLiteral("tab-new")), i18nc("@item:inmenu", "Open in New Tab"));
199     QAction* openInNewWindowAction = menu.addAction(QIcon::fromTheme(QStringLiteral("window-new")), i18nc("@item:inmenu", "Open in New Window"));
200     QAction* propertiesAction = nullptr;
201     if (item->url().isLocalFile()) {
202         propertiesAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18nc("@action:inmenu", "Properties"));
203     }
204     if (!isDevice) {
205         menu.addSeparator();
206     }
207 
208     if (isDevice) {
209         ejectAction = m_model->ejectAction(index);
210         if (ejectAction) {
211             ejectAction->setParent(&menu);
212             menu.addAction(ejectAction);
213         }
214 
215         teardownAction = m_model->teardownAction(index);
216         if (teardownAction) {
217             // Disable teardown option for root and home partitions
218             bool teardownEnabled = item->url() != QUrl::fromLocalFile(QDir::rootPath());
219             if (teardownEnabled) {
220                 KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(QDir::homePath());
221                 if (mountPoint && item->url() == QUrl::fromLocalFile(mountPoint->mountPoint())) {
222                     teardownEnabled = false;
223                 }
224             }
225             teardownAction->setEnabled(teardownEnabled);
226 
227             teardownAction->setParent(&menu);
228             menu.addAction(teardownAction);
229         }
230 
231         if (item->storageSetupNeeded()) {
232             mountAction = menu.addAction(QIcon::fromTheme(QStringLiteral("media-mount")), i18nc("@action:inmenu", "Mount"));
233         }
234 
235         if (teardownAction || ejectAction || mountAction) {
236             menu.addSeparator();
237         }
238     }
239 
240     if (isTrash) {
241         configureTrashAction = menu.addAction(QIcon::fromTheme(QStringLiteral("configure")), i18nc("@action:inmenu", "Configure Trash..."));
242     }
243 
244     if (!isDevice) {
245         editAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-entry")), i18nc("@item:inmenu", "Edit..."));
246     }
247 
248     QAction* removeAction = nullptr;
249     if (!isDevice && !item->isSystemItem()) {
250         removeAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@item:inmenu", "Remove"));
251     }
252 
253     QAction* hideAction = menu.addAction(QIcon::fromTheme(QStringLiteral("view-hidden")), i18nc("@item:inmenu", "Hide"));
254     hideAction->setCheckable(true);
255     hideAction->setChecked(item->isHidden());
256 
257     buildGroupContextMenu(&menu, index);
258 
259     QAction* action = menu.exec(pos.toPoint());
260     if (action) {
261         if (action == emptyTrashAction) {
262             Trash::empty(this);
263         } else if (action == configureTrashAction) {
264             DolphinSettingsDialog* settingsDialog = new DolphinSettingsDialog(item->url(), this);
265             settingsDialog->setCurrentPage(settingsDialog->trashSettings);
266             settingsDialog->setAttribute(Qt::WA_DeleteOnClose);
267             settingsDialog->show();
268         } else {
269             // The index might have changed if devices were added/removed while
270             // the context menu was open.
271             index = m_model->index(item);
272             if (index < 0) {
273                 // The item is not in the model any more, probably because it was an
274                 // external device that has been removed while the context menu was open.
275                 return;
276             }
277 
278             if (action == editAction) {
279                 editEntry(index);
280             } else if (action == removeAction) {
281                 m_model->deleteItem(index);
282             } else if (action == hideAction) {
283                 item->setHidden(hideAction->isChecked());
284                 if (!m_model->hiddenCount()) {
285                     showHiddenEntries(false);
286                 }
287             } else if (action == openInNewWindowAction) {
288                 Dolphin::openNewWindow({KFilePlacesModel::convertedUrl(m_model->data(index).value("url").toUrl())}, this);
289             } else if (action == openInNewTabAction) {
290                 // TriggerItem does set up the storage first and then it will
291                 // emit the slotItemMiddleClicked signal, because of Qt::MiddleButton.
292                 triggerItem(index, Qt::MiddleButton);
293             } else if (action == mountAction) {
294                 m_model->requestStorageSetup(index);
295             } else if (action == teardownAction) {
296                 m_model->requestTearDown(index);
297             } else if (action == ejectAction) {
298                 m_model->requestEject(index);
299             } else if (action == propertiesAction) {
300                 KPropertiesDialog* dialog = new KPropertiesDialog(item->url(), this);
301                 dialog->setAttribute(Qt::WA_DeleteOnClose);
302                 dialog->show();
303             }
304         }
305     }
306 
307     selectItem();
308 }
309 
slotViewContextMenuRequested(const QPointF & pos)310 void PlacesPanel::slotViewContextMenuRequested(const QPointF& pos)
311 {
312     QMenu menu(this);
313 
314     QAction* addAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18nc("@item:inmenu", "Add Entry..."));
315 
316     QAction* showAllAction = menu.addAction(i18nc("@item:inmenu", "Show Hidden Places"));
317     showAllAction->setCheckable(true);
318     showAllAction->setChecked(m_model->hiddenItemsShown());
319     showAllAction->setIcon(QIcon::fromTheme(m_model->hiddenItemsShown() ? QStringLiteral("view-visible") : QStringLiteral("view-hidden")));
320     showAllAction->setEnabled(m_model->hiddenCount());
321 
322     buildGroupContextMenu(&menu, m_controller->indexCloseToMousePressedPosition());
323 
324     QMenu* iconSizeSubMenu = new QMenu(i18nc("@item:inmenu", "Icon Size"), &menu);
325 
326     struct IconSizeInfo
327     {
328         int size;
329         const char* context;
330         const char* text;
331     };
332 
333     const int iconSizeCount = 4;
334     static const IconSizeInfo iconSizes[iconSizeCount] = {
335         {KIconLoader::SizeSmall,        I18NC_NOOP("Small icon size", "Small (%1x%2)")},
336         {KIconLoader::SizeSmallMedium,  I18NC_NOOP("Medium icon size", "Medium (%1x%2)")},
337         {KIconLoader::SizeMedium,       I18NC_NOOP("Large icon size", "Large (%1x%2)")},
338         {KIconLoader::SizeLarge,        I18NC_NOOP("Huge icon size", "Huge (%1x%2)")}
339     };
340 
341     QHash<QAction*, int> iconSizeActionMap;
342     QActionGroup* iconSizeGroup = new QActionGroup(iconSizeSubMenu);
343 
344     for (int i = 0; i < iconSizeCount; ++i) {
345         const int size = iconSizes[i].size;
346         const QString text = i18nc(iconSizes[i].context, iconSizes[i].text,
347                                    size, size);
348 
349         QAction* action = iconSizeSubMenu->addAction(text);
350         iconSizeActionMap.insert(action, size);
351         action->setActionGroup(iconSizeGroup);
352         action->setCheckable(true);
353         action->setChecked(m_view->iconSize() == size);
354     }
355 
356     menu.addMenu(iconSizeSubMenu);
357 
358     menu.addSeparator();
359     const auto actions = customContextMenuActions();
360     for (QAction* action : actions) {
361         menu.addAction(action);
362     }
363 
364     QAction* action = menu.exec(pos.toPoint());
365     if (action) {
366         if (action == addAction) {
367             addEntry();
368         } else if (action == showAllAction) {
369             showHiddenEntries(showAllAction->isChecked());
370         } else if (iconSizeActionMap.contains(action)) {
371             m_view->setIconSize(iconSizeActionMap.value(action));
372         }
373     }
374 
375     selectItem();
376 }
377 
buildGroupContextMenu(QMenu * menu,int index)378 QAction *PlacesPanel::buildGroupContextMenu(QMenu *menu, int index)
379 {
380     if (index == -1) {
381         return nullptr;
382     }
383 
384     KFilePlacesModel::GroupType groupType = m_model->groupType(index);
385     QAction *hideGroupAction = menu->addAction(QIcon::fromTheme(QStringLiteral("view-hidden")), i18nc("@item:inmenu", "Hide Section '%1'", m_model->item(index)->group()));
386     hideGroupAction->setCheckable(true);
387     hideGroupAction->setChecked(m_model->isGroupHidden(groupType));
388 
389     connect(hideGroupAction, &QAction::triggered, this, [this, groupType, hideGroupAction]{
390         m_model->setGroupHidden(groupType, hideGroupAction->isChecked());
391         if (!m_model->hiddenCount()) {
392             showHiddenEntries(false);
393         }
394     });
395 
396     return hideGroupAction;
397 }
398 
slotItemDropEvent(int index,QGraphicsSceneDragDropEvent * event)399 void PlacesPanel::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event)
400 {
401     if (index < 0) {
402         return;
403     }
404 
405     const PlacesItem* destItem = m_model->placesItem(index);
406 
407     if (destItem->isSearchOrTimelineUrl()) {
408         return;
409     }
410 
411     if (m_model->storageSetupNeeded(index)) {
412         connect(m_model, &PlacesItemModel::storageSetupDone,
413                 this, &PlacesPanel::slotItemDropEventStorageSetupDone);
414 
415         m_itemDropEventIndex = index;
416 
417         // Make a full copy of the Mime-Data
418         m_itemDropEventMimeData = new QMimeData;
419         m_itemDropEventMimeData->setText(event->mimeData()->text());
420         m_itemDropEventMimeData->setHtml(event->mimeData()->html());
421         m_itemDropEventMimeData->setUrls(event->mimeData()->urls());
422         m_itemDropEventMimeData->setImageData(event->mimeData()->imageData());
423         m_itemDropEventMimeData->setColorData(event->mimeData()->colorData());
424 
425         m_itemDropEvent = new QDropEvent(event->pos().toPoint(),
426                                          event->possibleActions(),
427                                          m_itemDropEventMimeData,
428                                          event->buttons(),
429                                          event->modifiers());
430 
431         m_model->requestStorageSetup(index);
432         return;
433     }
434 
435     QUrl destUrl = destItem->url();
436     QDropEvent dropEvent(event->pos().toPoint(),
437                          event->possibleActions(),
438                          event->mimeData(),
439                          event->buttons(),
440                          event->modifiers());
441 
442     slotUrlsDropped(destUrl, &dropEvent, this);
443 }
444 
slotItemDropEventStorageSetupDone(int index,bool success)445 void PlacesPanel::slotItemDropEventStorageSetupDone(int index, bool success)
446 {
447     disconnect(m_model, &PlacesItemModel::storageSetupDone,
448                this, &PlacesPanel::slotItemDropEventStorageSetupDone);
449 
450     if ((index == m_itemDropEventIndex) && m_itemDropEvent && m_itemDropEventMimeData) {
451         if (success) {
452             QUrl destUrl = m_model->placesItem(index)->url();
453             slotUrlsDropped(destUrl, m_itemDropEvent, this);
454         }
455 
456         delete m_itemDropEventMimeData;
457         delete m_itemDropEvent;
458 
459         m_itemDropEventIndex = -1;
460         m_itemDropEventMimeData = nullptr;
461         m_itemDropEvent = nullptr;
462     }
463 }
464 
slotAboveItemDropEvent(int index,QGraphicsSceneDragDropEvent * event)465 void PlacesPanel::slotAboveItemDropEvent(int index, QGraphicsSceneDragDropEvent* event)
466 {
467     m_model->dropMimeDataBefore(index, event->mimeData());
468 }
469 
slotUrlsDropped(const QUrl & dest,QDropEvent * event,QWidget * parent)470 void PlacesPanel::slotUrlsDropped(const QUrl& dest, QDropEvent* event, QWidget* parent)
471 {
472     KIO::DropJob *job = DragAndDropHelper::dropUrls(dest, event, parent);
473     if (job) {
474         connect(job, &KIO::DropJob::result, this, [this](KJob *job) { if (job->error()) Q_EMIT errorMessage(job->errorString()); });
475     }
476 }
477 
slotStorageSetupDone(int index,bool success)478 void PlacesPanel::slotStorageSetupDone(int index, bool success)
479 {
480     disconnect(m_model, &PlacesItemModel::storageSetupDone,
481                this, &PlacesPanel::slotStorageSetupDone);
482 
483     if (m_triggerStorageSetupButton == Qt::NoButton) {
484         return;
485     }
486 
487     if (success) {
488         Q_ASSERT(!m_model->storageSetupNeeded(index));
489         triggerItem(index, m_triggerStorageSetupButton);
490         m_triggerStorageSetupButton = Qt::NoButton;
491     } else {
492         setUrl(m_storageSetupFailedUrl);
493         m_storageSetupFailedUrl = QUrl();
494     }
495 }
496 
slotShowTooltip()497 void PlacesPanel::slotShowTooltip()
498 {
499     const QUrl url = m_model->data(m_hoveredIndex).value("url").value<QUrl>();
500     const QString text = url.toDisplayString(QUrl::PreferLocalFile);
501     QToolTip::showText(m_hoverPos, text);
502 }
503 
addEntry()504 void PlacesPanel::addEntry()
505 {
506     const int index = m_controller->selectionManager()->currentItem();
507     const QUrl url = m_model->data(index).value("url").toUrl();
508     const QString text = url.fileName().isEmpty() ? url.toDisplayString(QUrl::PreferLocalFile) : url.fileName();
509 
510     QPointer<KFilePlaceEditDialog> dialog = new KFilePlaceEditDialog(true, url, text, QString(), true, false, KIconLoader::SizeMedium, this);
511     if (dialog->exec() == QDialog::Accepted) {
512         const QString appName = dialog->applicationLocal() ? QCoreApplication::applicationName() : QString();
513         m_model->createPlacesItem(dialog->label(), dialog->url(), dialog->icon(), appName);
514     }
515 
516     delete dialog;
517 }
518 
editEntry(int index)519 void PlacesPanel::editEntry(int index)
520 {
521     QHash<QByteArray, QVariant> data = m_model->data(index);
522     const QUrl url = data.value("url").toUrl();
523     const QString text = data.value("text").toString();
524     const QString iconName = data.value("iconName").toString();
525     const bool applicationLocal = !data.value("applicationName").toString().isEmpty();
526 
527     QPointer<KFilePlaceEditDialog> dialog = new KFilePlaceEditDialog(true, url, text, iconName, true, applicationLocal, KIconLoader::SizeMedium, this);
528     if (dialog->exec() == QDialog::Accepted) {
529         PlacesItem* oldItem = m_model->placesItem(index);
530         if (oldItem) {
531             const QString appName = dialog->applicationLocal() ? QCoreApplication::applicationName() : QString();
532             oldItem->setApplicationName(appName);
533             oldItem->setText(dialog->label());
534             oldItem->setUrl(dialog->url());
535             oldItem->setIcon(dialog->icon());
536             m_model->refresh();
537         }
538     }
539 
540     delete dialog;
541 }
542 
selectItem()543 void PlacesPanel::selectItem()
544 {
545     const int index = m_model->closestItem(url());
546     KItemListSelectionManager* selectionManager = m_controller->selectionManager();
547     selectionManager->setCurrentItem(index);
548     selectionManager->clearSelection();
549 
550     const QUrl closestUrl = m_model->url(index);
551     if (!closestUrl.path().isEmpty() && url() == closestUrl) {
552         selectionManager->setSelected(index);
553     }
554 }
555 
triggerItem(int index,Qt::MouseButton button)556 void PlacesPanel::triggerItem(int index, Qt::MouseButton button)
557 {
558     const PlacesItem* item = m_model->placesItem(index);
559     if (!item) {
560         return;
561     }
562 
563     if (m_model->storageSetupNeeded(index)) {
564         m_triggerStorageSetupButton = button;
565         m_storageSetupFailedUrl = url();
566 
567         connect(m_model, &PlacesItemModel::storageSetupDone,
568                 this, &PlacesPanel::slotStorageSetupDone);
569 
570         m_model->requestStorageSetup(index);
571     } else {
572         m_triggerStorageSetupButton = Qt::NoButton;
573 
574         const QUrl url = m_model->data(index).value("url").toUrl();
575         if (!url.isEmpty()) {
576             if (button == Qt::MiddleButton) {
577                 Q_EMIT placeMiddleClicked(KFilePlacesModel::convertedUrl(url));
578             } else {
579                 Q_EMIT placeActivated(KFilePlacesModel::convertedUrl(url));
580             }
581         }
582     }
583 }
584 
showHiddenEntries(bool shown)585 void PlacesPanel::showHiddenEntries(bool shown)
586 {
587     m_model->setHiddenItemsShown(shown);
588     Q_EMIT showHiddenEntriesChanged(shown);
589 }
590 
hiddenListCount()591 int PlacesPanel::hiddenListCount()
592 {
593     if(!m_model) {
594         return 0;
595     }
596     return m_model->hiddenCount();
597 }
598