1 /*
2 * Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library 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 GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 *
18 */
19
20
21 #include "placesview.h"
22 #include "placesmodel.h"
23 #include "placesmodelitem.h"
24 #include "mountoperation.h"
25 #include "fileoperation.h"
26 #include <QMenu>
27 #include <QContextMenuEvent>
28 #include <QHeaderView>
29 #include <QDebug>
30 #include <QGuiApplication>
31 #include <QTimer>
32 #include "folderitemdelegate.h"
33
34 namespace Fm {
35
36 std::shared_ptr<PlacesProxyModel> PlacesView::proxyModel_;
37
PlacesProxyModel(QObject * parent)38 PlacesProxyModel::PlacesProxyModel(QObject* parent) :
39 QSortFilterProxyModel(parent),
40 showAll_(false),
41 hiddenItemsRestored_(false) {
42 }
43
~PlacesProxyModel()44 PlacesProxyModel::~PlacesProxyModel() {
45 }
46
restoreHiddenItems(const QSet<QString> & items)47 void PlacesProxyModel::restoreHiddenItems(const QSet<QString>& items) {
48 // hidden items should be restored only once
49 if(!hiddenItemsRestored_ && !items.isEmpty()) {
50 hidden_.clear();
51 QSet<QString>::const_iterator i = items.constBegin();
52 while (i != items.constEnd()) {
53 if(!(*i).isEmpty()) {
54 hidden_ << *i;
55 }
56 ++i;
57 }
58 hiddenItemsRestored_ = true;
59 invalidateFilter();
60 }
61 }
62
setHidden(const QString & str,bool hide)63 void PlacesProxyModel::setHidden(const QString& str, bool hide) {
64 if(hide) {
65 if(!str.isEmpty()) {
66 hidden_ << str;
67 }
68 }
69 else {
70 hidden_.remove(str);
71 }
72 invalidateFilter();
73 }
74
showAll(bool show)75 void PlacesProxyModel::showAll(bool show) {
76 showAll_ = show;
77 invalidateFilter();
78 }
79
filterAcceptsRow(int source_row,const QModelIndex & source_parent) const80 bool PlacesProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const {
81 if(showAll_ || hidden_.isEmpty()) {
82 return true;
83 }
84 if(PlacesModel* srcModel = static_cast<PlacesModel*>(sourceModel())) {
85 QModelIndex index = srcModel->index(source_row, 0, source_parent);
86 if(PlacesModelItem* item = static_cast<PlacesModelItem*>(srcModel->itemFromIndex(index))) {
87 if(item->type() == PlacesModelItem::Places) {
88 if(auto path = item->path()) {
89 if(hidden_.contains(QString::fromUtf8(path.toString().get()))) {
90 return false;
91 }
92 }
93 }
94 else if(item->type() == PlacesModelItem::Volume) {
95 CStrPtr uuid{g_volume_get_uuid(static_cast<PlacesModelVolumeItem*>(item)->volume())};
96 if(uuid && hidden_.contains(QString::fromUtf8(uuid.get()))) {
97 return false;
98 }
99 }
100 // show a root items only if, at least, one of its children is shown
101 else if((source_row == 0 || source_row == 1) && !source_parent.isValid()) {
102 QModelIndex indx = index.model()->index(0, 0, index);
103 while(PlacesModelItem* childItem = static_cast<PlacesModelItem*>(srcModel->itemFromIndex(indx))) {
104 if(childItem->type() == PlacesModelItem::Places) {
105 if(auto path = childItem->path()) {
106 if(!hidden_.contains(QString::fromUtf8(path.toString().get()))) {
107 return true;
108 }
109 }
110 }
111 else if(childItem->type() == PlacesModelItem::Volume) {
112 CStrPtr uuid{g_volume_get_uuid(static_cast<PlacesModelVolumeItem*>(childItem)->volume())};
113 if(uuid == nullptr || !hidden_.contains(QString::fromUtf8(uuid.get()))) {
114 return true;
115 }
116 }
117 else {
118 return true;
119 }
120 indx = indx.sibling(indx.row() + 1, 0);
121 }
122 return false;
123 }
124 }
125 }
126 return true;
127 }
128
PlacesView(QWidget * parent)129 PlacesView::PlacesView(QWidget* parent):
130 QTreeView(parent) {
131 setRootIsDecorated(false);
132 setHeaderHidden(true);
133 setIndentation(12);
134
135 connect(this, &QTreeView::clicked, this, &PlacesView::onClicked);
136 connect(this, &QTreeView::pressed, this, &PlacesView::onPressed);
137
138 setIconSize(QSize(24, 24));
139
140 FolderItemDelegate* delegate = new FolderItemDelegate(this, this);
141 delegate->setFileInfoRole(PlacesModel::FileInfoRole);
142 delegate->setIconInfoRole(PlacesModel::FmIconRole);
143 setItemDelegateForColumn(0, delegate);
144
145 model_ = PlacesModel::globalInstance();
146 if(!proxyModel_) {
147 proxyModel_ = std::make_shared<PlacesProxyModel>();
148 }
149 if(!proxyModel_->sourceModel()) { // all places-views may have been closed
150 proxyModel_->setSourceModel(model_.get());
151 }
152 setModel(proxyModel_.get());
153
154 // these 2 connections are needed to update filtering
155 connect(model_.get(), &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex&, int, int) {
156 proxyModel_->setHidden(QString()); // just invalidates filter
157 expandAll();
158 // for some reason (a Qt bug?), spanning is reset
159 spanFirstColumn();
160 });
161 connect(model_.get(), &QAbstractItemModel::rowsRemoved, this, [](const QModelIndex&, int, int) {
162 proxyModel_->setHidden(QString());
163 });
164
165 QHeaderView* headerView = header();
166 // WARNING: Since Qt 5.11, if the minimum header section width isn't set,
167 // Qt will set it by considering the font size, even without text.
168 // See Qt doc and https://bugreports.qt.io/browse/QTBUG-68503
169 headerView->setMinimumSectionSize(1);
170 headerView->setSectionResizeMode(0, QHeaderView::Stretch);
171 headerView->setSectionResizeMode(1, QHeaderView::Fixed);
172 headerView->setStretchLastSection(false);
173 expandAll();
174
175 spanFirstColumn();
176
177 // the 2nd column is for the eject buttons
178 setSelectionBehavior(QAbstractItemView::SelectRows); // FIXME: why this does not work?
179 setAllColumnsShowFocus(false);
180
181 setAcceptDrops(true);
182 setDragEnabled(true);
183
184 // update the umount button's column width based on icon size
185 onIconSizeChanged(iconSize());
186 connect(this, &QAbstractItemView::iconSizeChanged, this, &PlacesView::onIconSizeChanged);
187 }
188
~PlacesView()189 PlacesView::~PlacesView() {
190 // qDebug("delete PlacesView");
191 }
192
spanFirstColumn()193 void PlacesView::spanFirstColumn() {
194 // FIXME: is there any better way to make the first column span the whole row?
195 setFirstColumnSpanned(0, QModelIndex(), true); // places root
196 setFirstColumnSpanned(1, QModelIndex(), true); // devices root
197 setFirstColumnSpanned(2, QModelIndex(), true); // bookmarks root
198 // NOTE: The first column of the devices children shouldn't be spanned
199 // because the second column contains eject buttons.
200 QModelIndex indx = proxyModel_->mapFromSource(model_->placesRoot->index());
201 if(indx.isValid()) {
202 for(int i = 0; i < indx.model()->rowCount(indx); ++i) {
203 setFirstColumnSpanned(i, indx, true);
204 }
205 }
206 indx = proxyModel_->mapFromSource(model_->bookmarksRoot->index());
207 if(indx.isValid()) {
208 for(int i = 0; i < indx.model()->rowCount(indx); ++i) {
209 setFirstColumnSpanned(i, indx, true);
210 }
211 }
212 }
213
activateRow(int type,const QModelIndex & index)214 void PlacesView::activateRow(int type, const QModelIndex& index) {
215 if(!index.parent().isValid()) { // ignore root items
216 return;
217 }
218 PlacesModelItem* item = static_cast<PlacesModelItem*>(model_->itemFromIndex(proxyModel_->mapToSource(index)));
219 if(item) {
220 auto path = item->path();
221 if(!path) {
222 // check if mounting volumes is needed
223 if(item->type() == PlacesModelItem::Volume) {
224 PlacesModelVolumeItem* volumeItem = static_cast<PlacesModelVolumeItem*>(item);
225 if(!volumeItem->isMounted()) {
226 // Mount the volume
227 GVolume* volume = volumeItem->volume();
228 MountOperation* op = new MountOperation(true, this);
229 op->mount(volume);
230 // WARNING: "QAbstractItemView::mouseReleaseEvent" will be dispatched only
231 // after this function returns. Therefore, without a single-shot timer, if
232 // the window is closed before the mount operation is finished, the operation
233 // will be canceled, this function will return and "mouseReleaseEvent" will
234 // be sent to a destroyed QAbstractItemView, resulting in a crash.
235 QTimer::singleShot(0, op, [this, op, type, index] () {
236 if(op->wait()) {
237 if(PlacesModelItem* item = static_cast<PlacesModelItem*>(model_->itemFromIndex(proxyModel_->mapToSource(index)))) {
238 if(auto path = item->path()) {
239 Q_EMIT chdirRequested(type, path);
240 }
241 }
242 }
243 });
244 }
245 }
246 }
247 else {
248 Q_EMIT chdirRequested(type, path);
249 }
250 }
251 }
252
253 // mouse button pressed
onPressed(const QModelIndex & index)254 void PlacesView::onPressed(const QModelIndex& index) {
255 // if middle button is pressed
256 if(QGuiApplication::mouseButtons() & Qt::MiddleButton) {
257 // the real item is at column 0
258 activateRow(1, 0 == index.column() ? index : index.sibling(index.row(), 0));
259 }
260 }
261
onIconSizeChanged(const QSize & size)262 void PlacesView::onIconSizeChanged(const QSize& size) {
263 setColumnWidth(1, size.width() + 2 * (style()->pixelMetric(QStyle::PM_FocusFrameHMargin)
264 + 1)); // put it inside the focus rectangle, if any
265 }
266
onEjectButtonClicked(PlacesModelItem * item)267 void PlacesView::onEjectButtonClicked(PlacesModelItem* item) {
268 // The eject button is clicked for a device item (volume or mount)
269 if(item->type() == PlacesModelItem::Volume) {
270 PlacesModelVolumeItem* volumeItem = static_cast<PlacesModelVolumeItem*>(item);
271 MountOperation* op = new MountOperation(true, this);
272 if(volumeItem->canEject()) { // do eject if applicable
273 op->eject(volumeItem->volume());
274 }
275 else { // otherwise, do unmount instead
276 op->unmount(volumeItem->volume());
277 }
278 }
279 else if(item->type() == PlacesModelItem::Mount) {
280 PlacesModelMountItem* mountItem = static_cast<PlacesModelMountItem*>(item);
281 MountOperation* op = new MountOperation(true, this);
282 op->unmount(mountItem->mount());
283 }
284 qDebug("PlacesView::onEjectButtonClicked");
285 }
286
onClicked(const QModelIndex & index)287 void PlacesView::onClicked(const QModelIndex& index) {
288 if(!index.parent().isValid()) { // ignore root items
289 return;
290 }
291
292 if(index.column() == 0) {
293 activateRow(0, index);
294 }
295 else if(index.column() == 1) { // column 1 contains eject buttons of the mounted devices
296 if(index.parent() == proxyModel_->mapFromSource(model_->devicesRoot->index())) { // this is a mounted device
297 // the eject button is clicked
298 QModelIndex itemIndex = index.sibling(index.row(), 0); // the real item is at column 0
299 PlacesModelItem* item = static_cast<PlacesModelItem*>(model_->itemFromIndex(proxyModel_->mapToSource(itemIndex)));
300 if(item) {
301 // eject the volume or the mount
302 onEjectButtonClicked(item);
303 }
304 }
305 else {
306 activateRow(0, index.sibling(index.row(), 0));
307 }
308 }
309 }
310
setCurrentPath(Fm::FilePath path)311 void PlacesView::setCurrentPath(Fm::FilePath path) {
312 clearSelection();
313 currentPath_ = std::move(path);
314 if(currentPath_) {
315 // TODO: search for item with the path in model_ and select it.
316 PlacesModelItem* item = model_->itemFromPath(currentPath_);
317 if(item) {
318 selectionModel()->select(proxyModel_->mapFromSource(item->index()), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
319 }
320 }
321 }
322
323
dragMoveEvent(QDragMoveEvent * event)324 void PlacesView::dragMoveEvent(QDragMoveEvent* event) {
325 QTreeView::dragMoveEvent(event);
326 }
327
dropEvent(QDropEvent * event)328 void PlacesView::dropEvent(QDropEvent* event) {
329 QTreeView::dropEvent(event);
330 }
331
onEmptyTrash()332 void PlacesView::onEmptyTrash() {
333 Fm::FilePathList files;
334 files.push_back(Fm::FilePath::fromUri("trash:///"));
335 Fm::FileOperation::deleteFiles(std::move(files), true);
336 }
337
onMoveBookmarkUp()338 void PlacesView::onMoveBookmarkUp() {
339 PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
340 if(!action->index().isValid()) {
341 return;
342 }
343 PlacesModelBookmarkItem* item = static_cast<PlacesModelBookmarkItem*>(model_->itemFromIndex(action->index()));
344
345 int row = item->row();
346 if(row > 0) {
347 auto bookmarkItem = item->bookmark();
348 Fm::Bookmarks::globalInstance()->reorder(bookmarkItem, row - 1);
349 }
350 }
351
onMoveBookmarkDown()352 void PlacesView::onMoveBookmarkDown() {
353 PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
354 if(!action->index().isValid()) {
355 return;
356 }
357 PlacesModelBookmarkItem* item = static_cast<PlacesModelBookmarkItem*>(model_->itemFromIndex(action->index()));
358
359 int row = item->row();
360 if(row < model_->rowCount()) {
361 auto bookmarkItem = item->bookmark();
362 Fm::Bookmarks::globalInstance()->reorder(bookmarkItem, row + 1);
363 }
364 }
365
onDeleteBookmark()366 void PlacesView::onDeleteBookmark() {
367 PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
368 if(!action->index().isValid()) {
369 return;
370 }
371 PlacesModelBookmarkItem* item = static_cast<PlacesModelBookmarkItem*>(model_->itemFromIndex(action->index()));
372 auto bookmarkItem = item->bookmark();
373 Fm::Bookmarks::globalInstance()->remove(bookmarkItem);
374 }
375
376 // virtual
commitData(QWidget * editor)377 void PlacesView::commitData(QWidget* editor) {
378 QTreeView::commitData(editor);
379 PlacesModelBookmarkItem* item = static_cast<PlacesModelBookmarkItem*>(model_->itemFromIndex(proxyModel_->mapToSource(currentIndex())));
380 auto bookmarkItem = item->bookmark();
381 // rename bookmark
382 Fm::Bookmarks::globalInstance()->rename(bookmarkItem, item->text());
383 }
384
onOpenNewTab()385 void PlacesView::onOpenNewTab() {
386 PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
387 if(!action->index().isValid()) {
388 return;
389 }
390 PlacesModelItem* item = static_cast<PlacesModelItem*>(model_->itemFromIndex(action->index()));
391 if(item) {
392 Q_EMIT chdirRequested(1, item->path());
393 }
394 }
395
onOpenNewWindow()396 void PlacesView::onOpenNewWindow() {
397 PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
398 if(!action->index().isValid()) {
399 return;
400 }
401 PlacesModelItem* item = static_cast<PlacesModelItem*>(model_->itemFromIndex(action->index()));
402 if(item) {
403 Q_EMIT chdirRequested(2, item->path());
404 }
405 }
406
onRenameBookmark()407 void PlacesView::onRenameBookmark() {
408 PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
409 if(!action->index().isValid()) {
410 return;
411 }
412 PlacesModelBookmarkItem* item = static_cast<PlacesModelBookmarkItem*>(model_->itemFromIndex(action->index()));
413 setFocus();
414 setCurrentIndex(proxyModel_->mapFromSource(item->index()));
415 edit(proxyModel_->mapFromSource(item->index()));
416 }
417
onMountVolume()418 void PlacesView::onMountVolume() {
419 PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
420 if(!action->index().isValid()) {
421 return;
422 }
423 PlacesModelVolumeItem* item = static_cast<PlacesModelVolumeItem*>(model_->itemFromIndex(action->index()));
424 MountOperation* op = new MountOperation(true, this);
425 op->mount(item->volume());
426 op->wait();
427 }
428
onUnmountVolume()429 void PlacesView::onUnmountVolume() {
430 PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
431 if(!action->index().isValid()) {
432 return;
433 }
434 PlacesModelVolumeItem* item = static_cast<PlacesModelVolumeItem*>(model_->itemFromIndex(action->index()));
435 MountOperation* op = new MountOperation(true, this);
436 op->unmount(item->volume());
437 op->wait();
438 }
439
onUnmountMount()440 void PlacesView::onUnmountMount() {
441 PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
442 if(!action->index().isValid()) {
443 return;
444 }
445 PlacesModelMountItem* item = static_cast<PlacesModelMountItem*>(model_->itemFromIndex(action->index()));
446 GMount* mount = item->mount();
447 MountOperation* op = new MountOperation(true, this);
448 op->unmount(mount);
449 op->wait();
450 }
451
onEjectVolume()452 void PlacesView::onEjectVolume() {
453 PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
454 if(!action->index().isValid()) {
455 return;
456 }
457 PlacesModelVolumeItem* item = static_cast<PlacesModelVolumeItem*>(model_->itemFromIndex(action->index()));
458 MountOperation* op = new MountOperation(true, this);
459 op->eject(item->volume());
460 op->wait();
461 }
462
contextMenuEvent(QContextMenuEvent * event)463 void PlacesView::contextMenuEvent(QContextMenuEvent* event) {
464 QModelIndex index = indexAt(event->pos());
465 if(index.isValid()) {
466 if(index.column() != 0) { // the real item is at column 0
467 index = index.sibling(index.row(), 0);
468 }
469
470 // Do not take the ownership of the menu since
471 // it will be deleted with deleteLater() upon hidden.
472 // This is possibly related to #145 - https://github.com/lxqt/pcmanfm-qt/issues/145
473 QMenu* menu = new QMenu();
474 QAction* action = nullptr;
475 PlacesModelItem* item = static_cast<PlacesModelItem*>(model_->itemFromIndex(proxyModel_->mapToSource(index)));
476
477 if(index.parent().isValid()
478 && item->type() != PlacesModelItem::Mount
479 && (item->type() != PlacesModelItem::Volume
480 || static_cast<PlacesModelVolumeItem*>(item)->isMounted())) {
481 action = new PlacesModel::ItemAction(item->index(), tr("Open in New Tab"), menu);
482 connect(action, &QAction::triggered, this, &PlacesView::onOpenNewTab);
483 menu->addAction(action);
484 action = new PlacesModel::ItemAction(item->index(), tr("Open in New Window"), menu);
485 connect(action, &QAction::triggered, this, &PlacesView::onOpenNewWindow);
486 menu->addAction(action);
487 }
488
489 switch(item->type()) {
490 case PlacesModelItem::Places: {
491 auto path = item->path();
492 // FIXME: inefficient
493 if(path) {
494 auto path_str = path.toString();
495 if(strcmp(path_str.get(), "trash:///") == 0) {
496 action = new PlacesModel::ItemAction(item->index(), tr("Empty Trash"), menu);
497 auto icn = item->icon();
498 if(icn && icn->qicon().name() == QLatin1String("user-trash")) { // surely an empty trash
499 action->setEnabled(false);
500 }
501 else {
502 connect(action, &QAction::triggered, this, &PlacesView::onEmptyTrash);
503 }
504 // add the "Empty Trash" item on the top
505 QList<QAction*> actions = menu->actions();
506 if(!actions.isEmpty()) {
507 menu->insertAction(actions.at(0), action);
508 menu->insertSeparator(actions.at(0));
509 }
510 else { // impossible
511 menu->addAction(action);
512 }
513 }
514 // add a "Hide" action to the end
515 menu->addSeparator();
516 action = new PlacesModel::ItemAction(item->index(), tr("Hide"), menu);
517 QString pathStr(QString::fromUtf8(path_str.get()));
518 action->setCheckable(true);
519 if(proxyModel_->isShowingAll()) {
520 action->setChecked(proxyModel_->isHidden(pathStr));
521 }
522 connect(action, &QAction::triggered, [this, pathStr](bool checked) {
523 proxyModel_->setHidden(pathStr, checked);
524 Q_EMIT hiddenItemSet(pathStr, checked);
525 });
526 menu->addAction(action);
527 }
528 break;
529 }
530 case PlacesModelItem::Bookmark: {
531 // create context menu for bookmark item
532 if(item->index().row() > 0) {
533 action = new PlacesModel::ItemAction(item->index(), tr("Move Bookmark Up"), menu);
534 connect(action, &QAction::triggered, this, &PlacesView::onMoveBookmarkUp);
535 menu->addAction(action);
536 }
537 if(item->index().row() < model_->rowCount()) {
538 action = new PlacesModel::ItemAction(item->index(), tr("Move Bookmark Down"), menu);
539 connect(action, &QAction::triggered, this, &PlacesView::onMoveBookmarkDown);
540 menu->addAction(action);
541 }
542 action = new PlacesModel::ItemAction(item->index(), tr("Rename Bookmark"), menu);
543 connect(action, &QAction::triggered, this, &PlacesView::onRenameBookmark);
544 menu->addAction(action);
545 action = new PlacesModel::ItemAction(item->index(), tr("Remove Bookmark"), menu);
546 connect(action, &QAction::triggered, this, &PlacesView::onDeleteBookmark);
547 menu->addAction(action);
548 break;
549 }
550 case PlacesModelItem::Volume: {
551 PlacesModelVolumeItem* volumeItem = static_cast<PlacesModelVolumeItem*>(item);
552
553 if(volumeItem->isMounted()) {
554 action = new PlacesModel::ItemAction(item->index(), tr("Unmount"), menu);
555 connect(action, &QAction::triggered, this, &PlacesView::onUnmountVolume);
556 }
557 else {
558 action = new PlacesModel::ItemAction(item->index(), tr("Mount"), menu);
559 connect(action, &QAction::triggered, this, &PlacesView::onMountVolume);
560 }
561 menu->addAction(action);
562
563 if(volumeItem->canEject()) {
564 action = new PlacesModel::ItemAction(item->index(), tr("Eject"), menu);
565 connect(action, &QAction::triggered, this, &PlacesView::onEjectVolume);
566 menu->addAction(action);
567 }
568 // add a "Hide" action to the end
569 CStrPtr uuid{g_volume_get_uuid(static_cast<PlacesModelVolumeItem*>(item)->volume())};
570 if(uuid) {
571 QString str = QString::fromUtf8(uuid.get());
572 menu->addSeparator();
573 action = new PlacesModel::ItemAction(item->index(), tr("Hide"), menu);
574 action->setCheckable(true);
575 if(proxyModel_->isShowingAll()) {
576 action->setChecked(proxyModel_->isHidden(str));
577 }
578 connect(action, &QAction::triggered, [this, str](bool checked) {
579 proxyModel_->setHidden(str, checked);
580 Q_EMIT hiddenItemSet(str, checked);
581 });
582 menu->addAction(action);
583 }
584 break;
585 }
586 case PlacesModelItem::Mount: {
587 action = new PlacesModel::ItemAction(item->index(), tr("Unmount"), menu);
588 connect(action, &QAction::triggered, this, &PlacesView::onUnmountMount);
589 menu->addAction(action);
590 break;
591 }
592 }
593
594 // also add an acton for showing all hidden items
595 if(proxyModel_->hasHidden()) {
596 if(item->type() == PlacesModelItem::Bookmark) {
597 menu->addSeparator();
598 }
599 action = new PlacesModel::ItemAction(item->index(), tr("Show All Entries"), menu);
600 action->setCheckable(true);
601 action->setChecked(proxyModel_->isShowingAll());
602 connect(action, &QAction::triggered, [this](bool checked) {
603 showAll(checked);
604 });
605 menu->addAction(action);
606 }
607
608 if(menu->actions().size()) {
609 menu->popup(mapToGlobal(event->pos()));
610 connect(menu, &QMenu::aboutToHide, menu, &QMenu::deleteLater);
611 }
612 else {
613 menu->deleteLater();
614 }
615 }
616 }
617
keyPressEvent(QKeyEvent * event)618 void PlacesView::keyPressEvent(QKeyEvent* event) {
619 // prevent propagation of usual modifiers because
620 // they might be used elsewhere in shortcuts, especially with arrow keys
621 if(event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier)) {
622 return;
623 }
624 // activate child items and expand/collapse root items with Enter/Return
625 if(event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
626 QModelIndex index = currentIndex();
627 if(index.isValid()) {
628 if (index.column() != 0) {
629 index = index.sibling(index.row(), 0); // the real item is at column 0
630 }
631 if (index.isValid()) {
632 if (!index.parent().isValid()) { // root item
633 setExpanded(index, !isExpanded(index));
634 }
635 else {
636 // may have not been selected yet
637 selectionModel()->select(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
638 activateRow(0, index);
639 }
640 return;
641 }
642 }
643 }
644 QTreeView::keyPressEvent(event);
645 }
646
restoreHiddenItems(const QSet<QString> & items)647 void PlacesView::restoreHiddenItems(const QSet<QString>& items) {
648 proxyModel_->restoreHiddenItems(items);
649 }
650
showAll(bool show)651 void PlacesView::showAll(bool show) {
652 proxyModel_->showAll(show);
653 if(show) {
654 expandAll();
655 // for some reason (a Qt bug?), spanning is reset
656 spanFirstColumn();
657 }
658 }
659
660 } // namespace Fm
661