1 /*
2 
3     Copyright (C) 2013  Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
4 
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License along
16     with this program; if not, write to the Free Software Foundation, Inc.,
17     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19 
20 #include "mainwindow.h"
21 
22 #include <QLabel>
23 #include <QMenu>
24 #include <QMenuBar>
25 #include <QAction>
26 #include <QWidgetAction>
27 #include <QVBoxLayout>
28 #include <QMessageBox>
29 #include <QSplitter>
30 #include <QToolButton>
31 #include <QShortcut>
32 #include <QKeySequence>
33 #include <QSettings>
34 #include <QMimeData>
35 #include <QStandardPaths>
36 #include <QClipboard>
37 #include <QDebug>
38 
39 #include "tabpage.h"
40 #include "launcher.h"
41 #include <libfm-qt/filemenu.h>
42 #include <libfm-qt/bookmarkaction.h>
43 #include <libfm-qt/fileoperation.h>
44 #include <libfm-qt/utilities.h>
45 #include <libfm-qt/filepropsdialog.h>
46 #include <libfm-qt/pathedit.h>
47 #include <libfm-qt/pathbar.h>
48 #include <libfm-qt/core/fileinfo.h>
49 #include <libfm-qt/mountoperation.h>
50 #include "ui_about.h"
51 #include "ui_shortcuts.h"
52 #include "application.h"
53 #include "bulkrename.h"
54 
55 using namespace Fm;
56 
57 namespace PCManFM {
58 
ViewFrame(QWidget * parent)59 ViewFrame::ViewFrame(QWidget* parent):
60     QFrame(parent),
61     topBar_(nullptr) {
62     QVBoxLayout* vBox = new QVBoxLayout;
63     vBox->setContentsMargins(0, 0, 0, 0);
64 
65     tabBar_ = new TabBar;
66     tabBar_->setFocusPolicy(Qt::NoFocus);
67     stackedWidget_ = new QStackedWidget;
68     vBox->addWidget(tabBar_);
69     vBox->addWidget(stackedWidget_, 1);
70     setLayout(vBox);
71 
72     // tabbed browsing interface
73     tabBar_->setDocumentMode(true);
74     tabBar_->setElideMode(Qt::ElideRight);
75     tabBar_->setExpanding(false);
76     tabBar_->setMovable(true); // reorder the tabs by dragging
77     // switch to the tab under the cursor during dnd.
78     tabBar_->setChangeCurrentOnDrag(true);
79     tabBar_->setAcceptDrops(true);
80     tabBar_->setContextMenuPolicy(Qt::CustomContextMenu);
81 }
82 
createTopBar(bool usePathButtons)83 void ViewFrame::createTopBar(bool usePathButtons) {
84     if(QVBoxLayout* vBox = qobject_cast<QVBoxLayout*>(layout())) {
85         if(usePathButtons) {
86             if (qobject_cast<Fm::PathEdit*>(topBar_)) {
87                 delete topBar_;
88                 topBar_ = nullptr;
89             }
90             if(topBar_ == nullptr) {
91                 topBar_ = new Fm::PathBar();
92                 vBox->insertWidget(0, topBar_);
93             }
94         }
95         else {
96             if(qobject_cast<Fm::PathBar*>(topBar_)) {
97                 delete topBar_;
98                 topBar_ = nullptr;
99             }
100             if(topBar_ == nullptr) {
101                 topBar_ = new Fm::PathEdit();
102                 vBox->insertWidget(0, topBar_);
103             }
104         }
105     }
106 }
107 
removeTopBar()108 void ViewFrame::removeTopBar() {
109     if(topBar_ != nullptr) {
110         if(QVBoxLayout* vBox = qobject_cast<QVBoxLayout*>(layout())) {
111             vBox->removeWidget(topBar_);
112             delete topBar_;
113             topBar_ = nullptr;
114         }
115     }
116 }
117 
118 //======================================================================
119 
120 // static
121 QPointer<MainWindow> MainWindow::lastActive_;
122 
MainWindow(Fm::FilePath path)123 MainWindow::MainWindow(Fm::FilePath path):
124     QMainWindow(),
125     pathEntry_(nullptr),
126     pathBar_(nullptr),
127     bookmarks_{Fm::Bookmarks::globalInstance()},
128     fileLauncher_(this),
129     rightClickIndex_(-1),
130     updatingViewMenu_(false),
131     menuSpacer_(nullptr),
132     activeViewFrame_(nullptr) {
133 
134     Settings& settings = static_cast<Application*>(qApp)->settings();
135     setAttribute(Qt::WA_DeleteOnClose);
136     // setup user interface
137     ui.setupUi(this);
138 
139     // add a warning label to the root instance
140     if(geteuid() == 0) {
141         QLabel *warningLabel = new QLabel(tr("Root Instance"));
142         warningLabel->setAlignment(Qt::AlignCenter);
143         warningLabel->setTextInteractionFlags(Qt::NoTextInteraction);
144         warningLabel->setStyleSheet(QLatin1String("QLabel {background-color: #7d0000; color: white; font-weight:bold; border-radius: 3px; margin: 2px; padding: 5px;}"));
145         ui.verticalLayout->addWidget(warningLabel);
146         ui.verticalLayout->setStretch(0, 1);
147     }
148 
149     splitView_ = settings.splitView();
150 
151     // hide menu items that are not usable
152     //if(!uriExists("computer:///"))
153     //  ui.actionComputer->setVisible(false);
154     if(!settings.supportTrash()) {
155         ui.actionTrash->setVisible(false);
156     }
157 
158     // add a context menu for showing browse history to back and forward buttons
159     QToolButton* forwardButton = static_cast<QToolButton*>(ui.toolBar->widgetForAction(ui.actionGoForward));
160     forwardButton->setContextMenuPolicy(Qt::CustomContextMenu);
161     connect(forwardButton, &QToolButton::customContextMenuRequested, this, &MainWindow::onBackForwardContextMenu);
162     QToolButton* backButton = static_cast<QToolButton*>(ui.toolBar->widgetForAction(ui.actionGoBack));
163     backButton->setContextMenuPolicy(Qt::CustomContextMenu);
164     connect(backButton, &QToolButton::customContextMenuRequested, this, &MainWindow::onBackForwardContextMenu);
165 
166     connect(ui.actionCloseRight, &QAction::triggered, this, &MainWindow::closeRightTabs);
167     connect(ui.actionCloseLeft, &QAction::triggered, this, &MainWindow::closeLeftTabs);
168     connect(ui.actionCloseOther, &QAction::triggered, this, &MainWindow::closeOtherTabs);
169 
170     ui.actionFilter->setChecked(settings.showFilter());
171     ui.actionShowThumbnails->setChecked(settings.showThumbnails());
172 
173     // menu
174     ui.actionDelete->setText(settings.useTrash() ? tr("&Move to Trash") : tr("&Delete"));
175     ui.actionDelete->setIcon(settings.useTrash() ? QIcon::fromTheme(QStringLiteral("user-trash")) : QIcon::fromTheme(QStringLiteral("edit-delete")));
176 
177     // side pane
178     ui.sidePane->setVisible(settings.isSidePaneVisible());
179     ui.actionSidePane->setChecked(settings.isSidePaneVisible());
180     ui.sidePane->setIconSize(QSize(settings.sidePaneIconSize(), settings.sidePaneIconSize()));
181     ui.sidePane->setMode(settings.sidePaneMode());
182     ui.sidePane->restoreHiddenPlaces(settings.getHiddenPlaces());
183     connect(ui.sidePane, &Fm::SidePane::chdirRequested, this, &MainWindow::onSidePaneChdirRequested);
184     connect(ui.sidePane, &Fm::SidePane::openFolderInNewWindowRequested, this, &MainWindow::onSidePaneOpenFolderInNewWindowRequested);
185     connect(ui.sidePane, &Fm::SidePane::openFolderInNewTabRequested, this, &MainWindow::onSidePaneOpenFolderInNewTabRequested);
186     connect(ui.sidePane, &Fm::SidePane::openFolderInTerminalRequested, this, &MainWindow::onSidePaneOpenFolderInTerminalRequested);
187     connect(ui.sidePane, &Fm::SidePane::createNewFolderRequested, this, &MainWindow::onSidePaneCreateNewFolderRequested);
188     connect(ui.sidePane, &Fm::SidePane::modeChanged, this, &MainWindow::onSidePaneModeChanged);
189     connect(ui.sidePane, &Fm::SidePane::hiddenPlaceSet, this, &MainWindow::onSettingHiddenPlace);
190 
191     // detect change of splitter position
192     connect(ui.splitter, &QSplitter::splitterMoved, this, &MainWindow::onSplitterMoved);
193 
194     // add filesystem info to status bar
195     fsInfoLabel_ = new QLabel(ui.statusbar);
196     ui.statusbar->addPermanentWidget(fsInfoLabel_);
197 
198     // setup the splitter
199     ui.splitter->setStretchFactor(1, 1); // only the right pane can be stretched
200     QList<int> sizes;
201     sizes.append(settings.splitterPos());
202     sizes.append(300);
203     ui.splitter->setSizes(sizes);
204 
205     // load bookmark menu
206     connect(bookmarks_.get(), &Fm::Bookmarks::changed, this, &MainWindow::onBookmarksChanged);
207     loadBookmarksMenu();
208 
209     // use generic icons for view actions only if theme icons don't exist
210     ui.actionIconView->setIcon(QIcon::fromTheme(QLatin1String("view-list-icons"), style()->standardIcon(QStyle::SP_FileDialogContentsView)));
211     ui.actionThumbnailView->setIcon(QIcon::fromTheme(QLatin1String("view-preview"), style()->standardIcon(QStyle::SP_FileDialogInfoView)));
212     ui.actionCompactView->setIcon(QIcon::fromTheme(QLatin1String("view-list-text"), style()->standardIcon(QStyle::SP_FileDialogListView)));
213     ui.actionDetailedList->setIcon(QIcon::fromTheme(QLatin1String("view-list-details"), style()->standardIcon(QStyle::SP_FileDialogDetailedView)));
214 
215     // Fix the menu groups which is not done by Qt designer
216     // To my suprise, this was supported in Qt designer 3 :-(
217     QActionGroup* group = new QActionGroup(ui.menu_View);
218     group->setExclusive(true);
219     group->addAction(ui.actionIconView);
220     group->addAction(ui.actionCompactView);
221     group->addAction(ui.actionThumbnailView);
222     group->addAction(ui.actionDetailedList);
223 
224     group = new QActionGroup(ui.menuSorting);
225     group->setExclusive(true);
226     group->addAction(ui.actionByFileName);
227     group->addAction(ui.actionByMTime);
228     group->addAction(ui.actionByCrTime);
229     group->addAction(ui.actionByDTime);
230     group->addAction(ui.actionByFileSize);
231     group->addAction(ui.actionByFileType);
232     group->addAction(ui.actionByOwner);
233     group->addAction(ui.actionByGroup);
234 
235     group = new QActionGroup(ui.menuSorting);
236     group->setExclusive(true);
237     group->addAction(ui.actionAscending);
238     group->addAction(ui.actionDescending);
239 
240     group = new QActionGroup(ui.menuPathBarStyle);
241     group->setExclusive(true);
242     group->addAction(ui.actionLocationBar);
243     group->addAction(ui.actionPathButtons);
244 
245     // Add menubar actions to the main window this is necessary so that actions
246     // shortcuts are still working when the menubar is hidden.
247     addActions(ui.menubar->actions());
248 
249     // Show or hide the menu bar
250     QMenu* menu = new QMenu(ui.toolBar);
251     menu->addMenu(ui.menu_File);
252     menu->addMenu(ui.menu_Edit);
253     menu->addMenu(ui.menu_View);
254     menu->addMenu(ui.menu_Go);
255     menu->addMenu(ui.menu_Bookmarks);
256     menu->addMenu(ui.menu_Tool);
257     menu->addMenu(ui.menu_Help);
258     ui.actionMenu->setMenu(menu);
259     if(ui.actionMenu->icon().isNull()) {
260         ui.actionMenu->setIcon(QIcon::fromTheme(QStringLiteral("applications-system")));
261     }
262     QToolButton* menuBtn = static_cast<QToolButton*>(ui.toolBar->widgetForAction(ui.actionMenu));
263     menuBtn->setPopupMode(QToolButton::InstantPopup);
264 
265     menuSep_ = ui.toolBar->insertSeparator(ui.actionMenu);
266     menuSep_->setVisible(!settings.showMenuBar() && !splitView_);
267     ui.actionMenu->setVisible(!settings.showMenuBar());
268     ui.menubar->setVisible(settings.showMenuBar());
269     ui.actionMenu_bar->setChecked(settings.showMenuBar());
270     connect(ui.actionMenu_bar, &QAction::triggered, this, &MainWindow::toggleMenuBar);
271 
272     // create shortcuts
273     QShortcut* shortcut;
274     shortcut = new QShortcut(QKeySequence(Qt::Key_Escape), this);
__anon94dc1d920102null275     connect(shortcut, &QShortcut::activated, [this] {
276         if(currentPage()) {
277             currentPage()->clearFilter();
278             currentPage()->folderView()->childView()->setFocus();
279         }
280     });
281 
282     shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Escape), this);
__anon94dc1d920202null283     connect(shortcut, &QShortcut::activated, [this] {
284         if(ui.sidePane->isVisible() && ui.sidePane->view()) {
285             ui.sidePane->view()->setFocus();
286         }
287     });
288 
289     shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_L), this);
290     connect(shortcut, &QShortcut::activated, this, &MainWindow::focusPathEntry);
291 
292     shortcut = new QShortcut(Qt::ALT + Qt::Key_D, this);
293     connect(shortcut, &QShortcut::activated, this, &MainWindow::focusPathEntry);
294 
295     shortcut = new QShortcut(Qt::CTRL + Qt::Key_Tab, this);
296     connect(shortcut, &QShortcut::activated, this, &MainWindow::onShortcutNextTab);
297 
298     shortcut = new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Tab, this);
299     connect(shortcut, &QShortcut::activated, this, &MainWindow::onShortcutPrevTab);
300 
301     // Add Ctrl+PgUp and Ctrl+PgDown as well, because they are common in Firefox
302     // , Opera, Google Chromium/Google Chrome and most other tab-using
303     // applications.
304     shortcut = new QShortcut(Qt::CTRL + Qt::Key_PageDown, this);
305     connect(shortcut, &QShortcut::activated, this, &MainWindow::onShortcutNextTab);
306 
307     shortcut = new QShortcut(Qt::CTRL + Qt::Key_PageUp, this);
308     connect(shortcut, &QShortcut::activated, this, &MainWindow::onShortcutPrevTab);
309 
310     int i;
311     for(i = 0; i < 10; ++i) {
312         shortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::Key_0 + i), this);
313         connect(shortcut, &QShortcut::activated, this, &MainWindow::onShortcutJumpToTab);
314 
315         shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_0 + i), this);
316         connect(shortcut, &QShortcut::activated, this, &MainWindow::onShortcutJumpToTab);
317     }
318 
319     shortcut = new QShortcut(QKeySequence(Qt::Key_Backspace), this);
__anon94dc1d920302null320     connect(shortcut, &QShortcut::activated, [this, &settings] {
321         // pass Backspace to current page if it has a visible, transient filter-bar
322         if(!settings.showFilter() && currentPage() && currentPage()->isFilterBarVisible()) {
323             currentPage()->backspacePressed();
324             return;
325         }
326         on_actionGoUp_triggered();
327     });
328 
329     shortcut = new QShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Delete), this);
330     connect(shortcut, &QShortcut::activated, this, &MainWindow::on_actionDelete_triggered);
331 
332     // in addition to F3, for convenience
333     shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F), this);
334     connect(shortcut, &QShortcut::activated, ui.actionFindFiles, &QAction::trigger);
335 
336     // in addition to Alt+Return, for convenience
337     shortcut = new QShortcut(Qt::ALT + Qt::Key_Enter, this);
338     connect(shortcut, &QShortcut::activated, this, &MainWindow::on_actionFileProperties_triggered);
339 
340     addViewFrame(path);
341     if(splitView_) {
342         // put the menu button on the right (there's no path bar/entry on the toolbar)
343         QWidget* w = new QWidget(this);
344         w->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
345         menuSpacer_ = ui.toolBar->insertWidget(ui.actionMenu, w);
346 
347         ui.actionSplitView->setChecked(true);
348         addViewFrame(path);
349         qApp->removeEventFilter(this); // precaution
350         qApp->installEventFilter(this);
351     }
352     else {
353         ui.actionSplitView->setChecked(false);
354         setAcceptDrops(true); // we want tab dnd in the simple mode
355     }
356     createPathBar(settings.pathBarButtons());
357 
358     if(settings.pathBarButtons()) {
359         ui.actionPathButtons->setChecked(true);
360     }
361     else {
362         ui.actionLocationBar->setChecked(true);
363     }
364 
365     // size from settings
366     resize(settings.windowWidth(), settings.windowHeight());
367     if(settings.rememberWindowSize() && settings.windowMaximized()) {
368         setWindowState(windowState() | Qt::WindowMaximized);
369      }
370 
371     if(QApplication::layoutDirection() == Qt::RightToLeft) {
372         setRTLIcons(true);
373     }
374 }
375 
376 MainWindow::~MainWindow() = default;
377 
378 // Activate a view frame appropriately and give a special style to the inactive one(s).
379 // NOTE: This function is called only with the split mode.
eventFilter(QObject * watched,QEvent * event)380 bool MainWindow::eventFilter(QObject* watched, QEvent* event) {
381     if(qobject_cast<QWidget*>(watched)) {
382         if(event->type() == QEvent::FocusIn
383            // the event has happened inside the splitter
384            && ui.viewSplitter->isAncestorOf(qobject_cast<QWidget*>(watched))) {
385             for(int i = 0; i < ui.viewSplitter->count(); ++i) {
386                 if(ViewFrame* viewFrame = qobject_cast<ViewFrame*>(ui.viewSplitter->widget(i))) {
387                     if(viewFrame->isAncestorOf(qobject_cast<QWidget*>(watched))) {
388                         // a widget inside this view frame has gained focus; ensure the view is active
389                         if(activeViewFrame_ != viewFrame) {
390                             activeViewFrame_ = viewFrame;
391                             updateUIForCurrentPage(false); // WARNING: never set focus here!
392                         }
393                         if(viewFrame->palette().color(QPalette::Base)
394                            != qApp->palette().color(QPalette::Base)) {
395                             viewFrame->setPalette(qApp->palette()); // restore the main palette
396                         }
397                     }
398                     else if (viewFrame->palette().color(QPalette::Base)
399                              == qApp->palette().color(QPalette::Base)) {
400                         // Change the text and base palettes of an inactive view frame a little.
401                         // NOTE: Style-sheets aren't used because they can interfere with QStyle.
402                         QPalette palette = viewFrame->palette();
403 
404                         // There are various ways of getting a distinct color near the base color
405                         // but this one gives the best results with almost all palettes:
406                         QColor txtCol = palette.color(QPalette::Text);
407                         QColor baseCol = palette.color(QPalette::Base);
408                         baseCol.setRgbF(0.9 * baseCol.redF()   + 0.1 * txtCol.redF(),
409                                         0.9 * baseCol.greenF() + 0.1 * txtCol.greenF(),
410                                         0.9 * baseCol.blueF()  + 0.1 * txtCol.blueF(),
411                                         baseCol.alphaF());
412                         palette.setColor(QPalette::Base, baseCol);
413 
414                         // view text
415                         txtCol.setAlphaF(txtCol.alphaF() * 0.7);
416                         palette.setColor(QPalette::Text, txtCol);
417 
418                         // window text (used in tabs)
419                         txtCol = palette.color(QPalette::WindowText);
420                         txtCol.setAlphaF(txtCol.alphaF() * 0.7);
421                         palette.setColor(QPalette::WindowText, txtCol);
422 
423                         // button text (the disabled text color isn't changed because it may be
424                         // used by some styles for drawing disabled path-bar arrow)
425                         txtCol = palette.color(QPalette::ButtonText);
426                         txtCol.setAlphaF(txtCol.alphaF() * 0.7);
427                         palette.setColor(QPalette::Active, QPalette::ButtonText, txtCol);
428                         palette.setColor(QPalette::Inactive, QPalette::ButtonText, txtCol);
429 
430                         viewFrame->setPalette(palette);
431                     }
432                 }
433             }
434         }
435         // Use the Tab key for switching between view frames
436         else if (event->type() == QEvent::KeyPress) {
437             if(QKeyEvent *ke = static_cast<QKeyEvent*>(event)) {
438                 if(ke->key() == Qt::Key_Tab && ke->modifiers() == Qt::NoModifier) {
439                     if(!qobject_cast<QTextEdit*>(watched) // not during inline renaming
440                        && ui.viewSplitter->isAncestorOf(qobject_cast<QWidget*>(watched))) {
441                         // wrap the focus
442                         for(int i = 0; i < ui.viewSplitter->count(); ++i) {
443                             if(ViewFrame* viewFrame = qobject_cast<ViewFrame*>(ui.viewSplitter->widget(i))) {
444                                 if(activeViewFrame_ == viewFrame) {
445                                     int n = i < ui.viewSplitter->count() - 1 ? i + 1 : 0;
446                                     activeViewFrame_ = qobject_cast<ViewFrame*>(ui.viewSplitter->widget(n));
447                                     updateUIForCurrentPage(); // focuses the view and calls this function again
448                                     return true;
449                                 }
450                             }
451                         }
452                     }
453                 }
454             }
455         }
456     }
457     return QMainWindow::eventFilter(watched, event);
458 }
459 
addViewFrame(const Fm::FilePath & path)460 void MainWindow::addViewFrame(const Fm::FilePath& path) {
461     ui.actionGo->setVisible(false);
462     Application* app = static_cast<Application*>(qApp);
463     Settings& settings = app->settings();
464     ViewFrame* viewFrame = new ViewFrame();
465     // No tab DND with the split view.
466     // WARNING: Wayland has a serious issue related to QDrag that can result in a crash.
467     viewFrame->getTabBar()->setDetachable(!splitView_ && app->isX11());
468     viewFrame->getTabBar()->setTabsClosable(settings.showTabClose());
469     ui.viewSplitter->addWidget(viewFrame); // the splitter takes ownership of viewFrame
470     if(ui.viewSplitter->count() == 1) {
471         activeViewFrame_ = viewFrame;
472     }
473     else { // give equal widths to all view frames
474         QTimer::singleShot(0, this, [this] {
475             QList<int> sizes;
476             for(int i = 0; i < ui.viewSplitter->count(); ++i) {
477                 sizes << ui.viewSplitter->width() / ui.viewSplitter->count();
478             }
479             ui.viewSplitter->setSizes(sizes);
480         });
481     }
482 
483     connect(viewFrame->getTabBar(), &QTabBar::currentChanged, this, &MainWindow::onTabBarCurrentChanged);
484     connect(viewFrame->getTabBar(), &QTabBar::tabCloseRequested, this, &MainWindow::onTabBarCloseRequested);
485     connect(viewFrame->getTabBar(), &QTabBar::tabMoved, this, &MainWindow::onTabBarTabMoved);
486     connect(viewFrame->getTabBar(), &QTabBar::tabBarClicked, this, &MainWindow::onTabBarClicked);
487     connect(viewFrame->getTabBar(), &QTabBar::customContextMenuRequested, this, &MainWindow::tabContextMenu);
488     connect(viewFrame->getTabBar(), &QTabBar::tabBarDoubleClicked, this, [this](int index) {
489         if(index == -1) {
490             on_actionNewTab_triggered();
491         }
492     });
493     connect(viewFrame->getTabBar(), &TabBar::tabDetached, this, &MainWindow::detachTab);
494     connect(viewFrame->getStackedWidget(), &QStackedWidget::widgetRemoved, this, &MainWindow::onStackedWidgetWidgetRemoved);
495 
496     if(path) {
497         addTab(path, viewFrame);
498     }
499 }
500 
on_actionSplitView_triggered(bool checked)501 void MainWindow::on_actionSplitView_triggered(bool checked) {
502     if(splitView_ == checked) {
503         return;
504     }
505     Application* app = static_cast<Application*>(qApp);
506     Settings& settings = app->settings();
507     splitView_ = checked;
508     settings.setSplitView(splitView_);
509     if(splitView_) { // split the view
510         // remove the path bar/entry from the toolbar
511         ui.actionGo->setVisible(false);
512         menuSep_->setVisible(false);
513         if(pathBar_ != nullptr) {
514             delete pathBar_;
515             pathBar_ = nullptr;
516         }
517         else if(pathEntry_ != nullptr) {
518             delete pathEntry_;
519             pathEntry_ = nullptr;
520         }
521 
522         // add a spacer before the menu action if not exisitng
523         if(menuSpacer_ == nullptr) {
524             QWidget* w = new QWidget(this);
525             w->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
526             menuSpacer_ = ui.toolBar->insertWidget(ui.actionMenu, w);
527         }
528         menuSpacer_->setVisible(true);
529 
530         // disable tab DND
531         activeViewFrame_->getTabBar()->setDetachable(false);
532         setAcceptDrops(false);
533 
534         // add the current path to a new view frame
535         Fm::FilePath path;
536         TabPage* page = currentPage();
537         if(page) {
538             path = page->path();
539         }
540         addViewFrame(path);
541         qApp->removeEventFilter(this); // precaution
542         qApp->installEventFilter(this);
543         createPathBar(settings.pathBarButtons());
544 
545         // reset the focus for the inactive view frame(s) to be styled by MainWindow::eventFilter()
546         if(page) {
547             page->folderView()->childView()->clearFocus();
548             page->folderView()->childView()->setFocus();
549         }
550     }
551     else { // remove splitting
552         menuSep_->setVisible(!settings.showMenuBar());
553         qApp->removeEventFilter(this);
554         for(int i = 0; i < ui.viewSplitter->count(); ++i) {
555             if(ViewFrame* viewFrame = qobject_cast<ViewFrame*>(ui.viewSplitter->widget(i))) {
556                 if(viewFrame != activeViewFrame_) {
557                     viewFrame->deleteLater(); // this may be called by onStackedWidgetWidgetRemoved()
558                 }
559             }
560         }
561 
562         // enable tab DND
563         activeViewFrame_->getTabBar()->setDetachable(app->isX11());
564         setAcceptDrops(true);
565 
566         activeViewFrame_->removeTopBar();
567         if(menuSpacer_ != nullptr) {
568             menuSpacer_->setVisible(false);
569         }
570         createPathBar(settings.pathBarButtons());
571     }
572 }
573 
viewFrameForTabPage(TabPage * page)574 ViewFrame* MainWindow::viewFrameForTabPage(TabPage* page) {
575     if(page) {
576         if(QStackedWidget* sw = qobject_cast<QStackedWidget*>(page->parentWidget())) {
577             if(ViewFrame* viewFrame = qobject_cast<ViewFrame*>(sw->parentWidget())) {
578                 return viewFrame;
579             }
580         }
581     }
582     return nullptr;
583 }
584 
chdir(Fm::FilePath path,ViewFrame * viewFrame)585 void MainWindow::chdir(Fm::FilePath path, ViewFrame* viewFrame) {
586     // wait until queued events are processed
587     QTimer::singleShot(0, viewFrame, [this, path, viewFrame] {
588         if(TabPage* page = currentPage(viewFrame)) {
589             page->chdir(path, true);
590             setTabIcon(page);
591             if(viewFrame == activeViewFrame_) {
592                 updateUIForCurrentPage();
593             }
594             else {
595                 if(Fm::PathBar* pathBar = qobject_cast<Fm::PathBar*>(viewFrame->getTopBar())) {
596                     pathBar->setPath(page->path());
597                 }
598                 else if(Fm::PathEdit* pathEntry = qobject_cast<Fm::PathEdit*>(viewFrame->getTopBar())) {
599                     pathEntry->setText(page->pathName());
600                 }
601             }
602         }
603     });
604 }
605 
createPathBar(bool usePathButtons)606 void MainWindow::createPathBar(bool usePathButtons) {
607     // NOTE: Path bars/entries may be created after tab pages; so, their paths/texts should be set.
608     if(splitView_) {
609         for(int i = 0; i < ui.viewSplitter->count(); ++i) {
610             if(ViewFrame* viewFrame = qobject_cast<ViewFrame*>(ui.viewSplitter->widget(i))) {
611                 viewFrame->createTopBar(usePathButtons);
612                 TabPage* curPage = currentPage(viewFrame);
613                 if(Fm::PathBar* pathBar = qobject_cast<Fm::PathBar*>(viewFrame->getTopBar())) {
614                     connect(pathBar, &Fm::PathBar::chdir, this, &MainWindow::onPathBarChdir);
615                     connect(pathBar, &Fm::PathBar::middleClickChdir, this, &MainWindow::onPathBarMiddleClickChdir);
616                     connect(pathBar, &Fm::PathBar::editingFinished, this, &MainWindow::onResetFocus);
617                     if(curPage) {
618                         pathBar->setPath(curPage->path());
619                     }
620                 }
621                 else if(Fm::PathEdit* pathEntry = qobject_cast<Fm::PathEdit*>(viewFrame->getTopBar())) {
622                     connect(pathEntry, &Fm::PathEdit::returnPressed, this, &MainWindow::onPathEntryReturnPressed);
623                     if(curPage) {
624                         pathEntry->setText(curPage->pathName());
625                     }
626                 }
627             }
628         }
629     }
630     else {
631         QWidget* bar = nullptr;
632         TabPage* curPage = currentPage();
633         if(usePathButtons) {
634             if(pathEntry_ != nullptr) {
635                 delete pathEntry_;
636                 pathEntry_ = nullptr;
637             }
638             if(pathBar_ == nullptr) {
639                 bar = pathBar_ = new Fm::PathBar(this);
640                 connect(pathBar_, &Fm::PathBar::chdir, this, &MainWindow::onPathBarChdir);
641                 connect(pathBar_, &Fm::PathBar::middleClickChdir, this, &MainWindow::onPathBarMiddleClickChdir);
642                 connect(pathBar_, &Fm::PathBar::editingFinished, this, &MainWindow::onResetFocus);
643                 if(curPage) {
644                     pathBar_->setPath(currentPage()->path());
645                 }
646             }
647         }
648         else {
649             if(pathBar_ != nullptr) {
650                 delete pathBar_;
651                 pathBar_ = nullptr;
652             }
653             if(pathEntry_ == nullptr) {
654                 bar = pathEntry_ = new Fm::PathEdit(this);
655                 connect(pathEntry_, &Fm::PathEdit::returnPressed, this, &MainWindow::onPathEntryReturnPressed);
656                 if(curPage) {
657                     pathEntry_->setText(curPage->pathName());
658                 }
659             }
660         }
661         if(bar != nullptr) {
662             ui.toolBar->insertWidget(ui.actionGo, bar);
663             ui.actionGo->setVisible(!usePathButtons);
664         }
665     }
666 }
667 
addTabWithPage(TabPage * page,ViewFrame * viewFrame,Fm::FilePath path)668 int MainWindow::addTabWithPage(TabPage* page, ViewFrame* viewFrame, Fm::FilePath path) {
669     if(page == nullptr || viewFrame == nullptr) {
670         return -1;
671     }
672     page->setFileLauncher(&fileLauncher_);
673     int index = viewFrame->getStackedWidget()->addWidget(page);
674     connect(page, &TabPage::titleChanged, this, &MainWindow::onTabPageTitleChanged);
675     connect(page, &TabPage::statusChanged, this, &MainWindow::onTabPageStatusChanged);
676     connect(page, &TabPage::sortFilterChanged, this, &MainWindow::onTabPageSortFilterChanged);
677     connect(page, &TabPage::backwardRequested, this, &MainWindow::on_actionGoBack_triggered);
678     connect(page, &TabPage::forwardRequested, this, &MainWindow::on_actionGoForward_triggered);
679     connect(page, &TabPage::folderUnmounted, this, &MainWindow::onFolderUnmounted);
680 
681     if(path) {
682         page->chdir(path, true);
683     }
684 
685     QString tabText = page->title();
686     // remove newline (not all styles can handle it) and distinguish ampersand from mnemonic
687     tabText.replace(QLatin1Char('\n'), QLatin1Char(' '))
688            .replace(QLatin1Char('&'), QLatin1String("&&"));
689     viewFrame->getTabBar()->insertTab(index, tabText);
690 
691     Settings& settings = static_cast<Application*>(qApp)->settings();
692     if(settings.switchToNewTab()) {
693         viewFrame->getTabBar()->setCurrentIndex(index); // also focuses the view
694         if (isMinimized()) {
695             setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
696             show();
697         }
698     }
699     else if(TabPage* tabPage = currentPage()) {
700         tabPage->folderView()->childView()->setFocus();
701     }
702     if(!settings.alwaysShowTabs()) {
703         viewFrame->getTabBar()->setVisible(viewFrame->getTabBar()->count() > 1);
704     }
705 
706     // also set tab icon (if the folder is customized)
707     setTabIcon(page);
708 
709     return index;
710 }
711 
712 // add a new tab
addTab(Fm::FilePath path,ViewFrame * viewFrame)713 void MainWindow::addTab(Fm::FilePath path, ViewFrame* viewFrame) {
714     TabPage* newPage = new TabPage(this);
715     addTabWithPage(newPage, viewFrame, path);
716 }
717 
toggleMenuBar(bool)718 void MainWindow::toggleMenuBar(bool /*checked*/) {
719     Settings& settings = static_cast<Application*>(qApp)->settings();
720     bool showMenuBar = !settings.showMenuBar();
721 
722     if(!showMenuBar) {
723         if(QMessageBox::Cancel == QMessageBox::warning(this,
724                 tr("Hide menu bar"),
725                 tr("This will hide the menu bar completely, use Ctrl+M to show it again."),
726                 QMessageBox::Ok | QMessageBox::Cancel)) {
727             ui.actionMenu_bar->setChecked(true);
728             return;
729         }
730     }
731 
732     ui.menubar->setVisible(showMenuBar);
733     ui.actionMenu_bar->setChecked(showMenuBar);
734     menuSep_->setVisible(!showMenuBar);
735     ui.actionMenu->setVisible(!showMenuBar);
736     settings.setShowMenuBar(showMenuBar);
737 }
738 
onPathEntryReturnPressed()739 void MainWindow::onPathEntryReturnPressed() {
740     Fm::PathEdit* pathEntry = pathEntry_;
741     if(pathEntry == nullptr) {
742         pathEntry = static_cast<Fm::PathEdit*>(sender());
743     }
744     if(pathEntry != nullptr) {
745         QString text = pathEntry->text();
746         QByteArray utext = text.toLocal8Bit();
747         chdir(Fm::FilePath::fromPathStr(utext.constData()));
748     }
749 }
750 
onPathBarChdir(const Fm::FilePath & dirPath)751 void MainWindow::onPathBarChdir(const Fm::FilePath& dirPath) {
752     TabPage* page = nullptr;
753     ViewFrame* viewFrame = nullptr;
754     if(pathBar_ != nullptr) {
755         page = currentPage();
756         viewFrame = activeViewFrame_;
757     }
758     else {
759         Fm::PathBar* pathBar = static_cast<Fm::PathBar*>(sender());
760         viewFrame = qobject_cast<ViewFrame*>(pathBar->parentWidget());
761         if(viewFrame != nullptr) {
762             page = currentPage(viewFrame);
763         }
764     }
765     if(page && dirPath != page->path()) {
766         chdir(dirPath, viewFrame);
767     }
768 }
769 
onPathBarMiddleClickChdir(const Fm::FilePath & dirPath)770 void MainWindow::onPathBarMiddleClickChdir(const Fm::FilePath& dirPath) {
771     ViewFrame* viewFrame = nullptr;
772     if(pathBar_ != nullptr) {
773         viewFrame = activeViewFrame_;
774     }
775     else {
776         Fm::PathBar* pathBar = static_cast<Fm::PathBar*>(sender());
777         viewFrame = qobject_cast<ViewFrame*>(pathBar->parentWidget());
778     }
779     if(viewFrame) {
780         addTab(dirPath, viewFrame);
781     }
782 }
783 
on_actionGoUp_triggered()784 void MainWindow::on_actionGoUp_triggered() {
785     QTimer::singleShot(0, this, [this] {
786         if(TabPage* page = currentPage()) {
787             page->up();
788             setTabIcon(page);
789             updateUIForCurrentPage();
790         }
791     });
792 }
793 
on_actionGoBack_triggered()794 void MainWindow::on_actionGoBack_triggered() {
795     QTimer::singleShot(0, this, [this] {
796         if(TabPage* page = currentPage()) {
797             page->backward();
798             setTabIcon(page);
799             updateUIForCurrentPage();
800         }
801     });
802 }
803 
on_actionGoForward_triggered()804 void MainWindow::on_actionGoForward_triggered() {
805     QTimer::singleShot(0, this, [this] {
806         if(TabPage* page = currentPage()) {
807             page->forward();
808             setTabIcon(page);
809             updateUIForCurrentPage();
810         }
811     });
812 
813 }
814 
on_actionHome_triggered()815 void MainWindow::on_actionHome_triggered() {
816     chdir(Fm::FilePath::homeDir());
817 }
818 
on_actionReload_triggered()819 void MainWindow::on_actionReload_triggered() {
820     currentPage()->reload();
821     if(pathEntry_ != nullptr) {
822         pathEntry_->setText(currentPage()->pathName());
823     }
824 }
825 
on_actionConnectToServer_triggered()826 void MainWindow::on_actionConnectToServer_triggered() {
827     Application* app = static_cast<Application*>(qApp);
828     app->connectToServer();
829 }
830 
on_actionGo_triggered()831 void MainWindow::on_actionGo_triggered() {
832     onPathEntryReturnPressed();
833 }
834 
on_actionNewTab_triggered()835 void MainWindow::on_actionNewTab_triggered() {
836     auto path = currentPage()->path();
837     addTab(path);
838 }
839 
on_actionNewWin_triggered()840 void MainWindow::on_actionNewWin_triggered() {
841     auto path = currentPage()->path();
842     (new MainWindow(path))->show();
843 }
844 
on_actionNewFolder_triggered()845 void MainWindow::on_actionNewFolder_triggered() {
846     if(TabPage* tabPage = currentPage()) {
847         auto dirPath = tabPage->folderView()->path();
848         if(dirPath) {
849             createFileOrFolder(CreateNewFolder, dirPath, nullptr, this);
850         }
851     }
852 }
853 
on_actionNewBlankFile_triggered()854 void MainWindow::on_actionNewBlankFile_triggered() {
855     if(TabPage* tabPage = currentPage()) {
856         auto dirPath = tabPage->folderView()->path();
857         if(dirPath) {
858             createFileOrFolder(CreateNewTextFile, dirPath, nullptr, this);
859         }
860     }
861 }
862 
on_actionCloseTab_triggered()863 void MainWindow::on_actionCloseTab_triggered() {
864     closeTab(activeViewFrame_->getTabBar()->currentIndex());
865 }
866 
on_actionCloseWindow_triggered()867 void MainWindow::on_actionCloseWindow_triggered() {
868     // FIXME: should we save state here?
869     close();
870     // the window will be deleted automatically on close
871 }
872 
on_actionFileProperties_triggered()873 void MainWindow::on_actionFileProperties_triggered() {
874     TabPage* page = currentPage();
875     if(page) {
876         auto files = page->selectedFiles();
877         if(!files.empty()) {
878             Fm::FilePropsDialog::showForFiles(files);
879         }
880     }
881 }
882 
on_actionFolderProperties_triggered()883 void MainWindow::on_actionFolderProperties_triggered() {
884     TabPage* page = currentPage();
885     if(page) {
886         auto folder = page->folder();
887         if(folder) {
888             auto info = folder->info();
889             if(info) {
890                 Fm::FilePropsDialog::showForFile(info);
891             }
892         }
893     }
894 }
895 
on_actionShowHidden_triggered(bool checked)896 void MainWindow::on_actionShowHidden_triggered(bool checked) {
897     currentPage()->setShowHidden(checked);
898     // visibility of hidden folders in directory tree is toggled by onTabPageSortFilterChanged()
899 }
900 
on_actionShowThumbnails_triggered(bool checked)901 void MainWindow::on_actionShowThumbnails_triggered(bool checked) {
902     currentPage()->setShowThumbnails(checked);
903 }
904 
on_actionByFileName_triggered(bool)905 void MainWindow::on_actionByFileName_triggered(bool /*checked*/) {
906     currentPage()->sort(Fm::FolderModel::ColumnFileName, currentPage()->sortOrder());
907 }
908 
on_actionByMTime_triggered(bool)909 void MainWindow::on_actionByMTime_triggered(bool /*checked*/) {
910     currentPage()->sort(Fm::FolderModel::ColumnFileMTime, currentPage()->sortOrder());
911 }
912 
on_actionByCrTime_triggered(bool)913 void MainWindow::on_actionByCrTime_triggered(bool /*checked*/) {
914     currentPage()->sort(Fm::FolderModel::ColumnFileCrTime, currentPage()->sortOrder());
915 }
916 
on_actionByDTime_triggered(bool)917 void MainWindow::on_actionByDTime_triggered(bool /*checked*/) {
918     currentPage()->sort(Fm::FolderModel::ColumnFileDTime, currentPage()->sortOrder());
919 }
920 
on_actionByOwner_triggered(bool)921 void MainWindow::on_actionByOwner_triggered(bool /*checked*/) {
922     currentPage()->sort(Fm::FolderModel::ColumnFileOwner, currentPage()->sortOrder());
923 }
924 
on_actionByGroup_triggered(bool)925 void MainWindow::on_actionByGroup_triggered(bool /*checked*/) {
926     currentPage()->sort(Fm::FolderModel::ColumnFileGroup, currentPage()->sortOrder());
927 }
928 
on_actionByFileSize_triggered(bool)929 void MainWindow::on_actionByFileSize_triggered(bool /*checked*/) {
930     currentPage()->sort(Fm::FolderModel::ColumnFileSize, currentPage()->sortOrder());
931 }
932 
on_actionByFileType_triggered(bool)933 void MainWindow::on_actionByFileType_triggered(bool /*checked*/) {
934     currentPage()->sort(Fm::FolderModel::ColumnFileType, currentPage()->sortOrder());
935 }
936 
on_actionAscending_triggered(bool)937 void MainWindow::on_actionAscending_triggered(bool /*checked*/) {
938     currentPage()->sort(currentPage()->sortColumn(), Qt::AscendingOrder);
939 }
940 
on_actionDescending_triggered(bool)941 void MainWindow::on_actionDescending_triggered(bool /*checked*/) {
942     currentPage()->sort(currentPage()->sortColumn(), Qt::DescendingOrder);
943 }
944 
on_actionCaseSensitive_triggered(bool checked)945 void MainWindow::on_actionCaseSensitive_triggered(bool checked) {
946     currentPage()->setSortCaseSensitive(checked);
947 }
948 
on_actionFolderFirst_triggered(bool checked)949 void MainWindow::on_actionFolderFirst_triggered(bool checked) {
950     currentPage()->setSortFolderFirst(checked);
951 }
952 
on_actionHiddenLast_triggered(bool checked)953 void MainWindow::on_actionHiddenLast_triggered(bool checked) {
954     currentPage()->setSortHiddenLast(checked);
955 }
956 
on_actionPreserveView_triggered(bool checked)957 void MainWindow::on_actionPreserveView_triggered(bool checked) {
958     TabPage* page = currentPage();
959     page->setCustomizedView(checked);
960     if(checked) {
961         ui.actionPreserveViewRecursive->setChecked(false);
962     }
963     ui.actionGoToCustomizedViewSource->setVisible(page->hasInheritedCustomizedView());
964     setTabIcon(page);
965 }
966 
on_actionPreserveViewRecursive_triggered(bool checked)967 void MainWindow::on_actionPreserveViewRecursive_triggered(bool checked) {
968     TabPage* page = currentPage();
969     page->setCustomizedView(checked, true);
970     if(checked) {
971         ui.actionPreserveView->setChecked(false);
972     }
973     ui.actionGoToCustomizedViewSource->setVisible(page->hasInheritedCustomizedView());
974     setTabIcon(page);
975 }
976 
on_actionGoToCustomizedViewSource_triggered()977 void MainWindow::on_actionGoToCustomizedViewSource_triggered() {
978     currentPage()->goToCustomizedViewSource();
979     updateUIForCurrentPage();
980 }
981 
on_actionFilter_triggered(bool checked)982 void MainWindow::on_actionFilter_triggered(bool checked) {
983     static_cast<Application*>(qApp)->settings().setShowFilter(checked);
984     // show/hide filter-bars and disable/enable their transience for all tabs
985     // (of all view frames) in all windows because this is a global setting
986     QWidgetList windows = static_cast<Application*>(qApp)->topLevelWidgets();
987     QWidgetList::iterator it;
988     for(it = windows.begin(); it != windows.end(); ++it) {
989         QWidget* window = *it;
990         if(window->inherits("PCManFM::MainWindow")) {
991             MainWindow* mainWindow = static_cast<MainWindow*>(window);
992             mainWindow->ui.actionFilter->setChecked(checked); // doesn't call this function
993             for(int i = 0; i < mainWindow->ui.viewSplitter->count(); ++i) {
994                 if(ViewFrame* viewFrame = qobject_cast<ViewFrame*>(mainWindow->ui.viewSplitter->widget(i))) {
995                     int n = viewFrame->getStackedWidget()->count();
996                     for(int j = 0; j < n; ++j) {
997                         if(TabPage* page = static_cast<TabPage*>(viewFrame->getStackedWidget()->widget(j))) {
998                             page->transientFilterBar(!checked);
999                         }
1000                     }
1001                 }
1002             }
1003         }
1004     }
1005 }
1006 
on_actionUnfilter_triggered()1007 void MainWindow::on_actionUnfilter_triggered() {
1008     // clear filters for all tabs (of all view frames)
1009     for(int i = 0; i < ui.viewSplitter->count(); ++i) {
1010         if(ViewFrame* viewFrame = qobject_cast<ViewFrame*>(ui.viewSplitter->widget(i))) {
1011             int n = viewFrame->getStackedWidget()->count();
1012             for(int j = 0; j < n; ++j) {
1013                 if(TabPage* page = static_cast<TabPage*>(viewFrame->getStackedWidget()->widget(j))) {
1014                     page->clearFilter();
1015                 }
1016             }
1017         }
1018     }
1019 }
1020 
on_actionShowFilter_triggered()1021 void MainWindow::on_actionShowFilter_triggered() {
1022     if(TabPage* page = currentPage()) {
1023         page->showFilterBar();
1024     }
1025 }
1026 
on_actionLocationBar_triggered(bool checked)1027 void MainWindow::on_actionLocationBar_triggered(bool checked) {
1028     if(checked) {
1029         // show current path in a location bar entry
1030         createPathBar(false);
1031         static_cast<Application*>(qApp)->settings().setPathBarButtons(false);
1032     }
1033 }
1034 
on_actionPathButtons_triggered(bool checked)1035 void MainWindow::on_actionPathButtons_triggered(bool checked) {
1036     if(checked) {
1037         // show current path as buttons
1038         createPathBar(true);
1039         static_cast<Application*>(qApp)->settings().setPathBarButtons(true);
1040     }
1041 }
1042 
on_actionComputer_triggered()1043 void MainWindow::on_actionComputer_triggered() {
1044     chdir(Fm::FilePath::fromUri("computer:///"));
1045 }
1046 
on_actionApplications_triggered()1047 void MainWindow::on_actionApplications_triggered() {
1048     chdir(Fm::FilePath::fromUri("menu://applications/"));
1049 }
1050 
on_actionTrash_triggered()1051 void MainWindow::on_actionTrash_triggered() {
1052     chdir(Fm::FilePath::fromUri("trash:///"));
1053 }
1054 
on_actionNetwork_triggered()1055 void MainWindow::on_actionNetwork_triggered() {
1056     chdir(Fm::FilePath::fromUri("network:///"));
1057 }
1058 
on_actionDesktop_triggered()1059 void MainWindow::on_actionDesktop_triggered() {
1060     auto desktop = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toLocal8Bit();
1061     chdir(Fm::FilePath::fromLocalPath(desktop.constData()));
1062 }
1063 
on_actionAddToBookmarks_triggered()1064 void MainWindow::on_actionAddToBookmarks_triggered() {
1065     TabPage* page = currentPage();
1066     if(page) {
1067         auto cwd = page->path();
1068         if(cwd) {
1069             QString bookmarkName;
1070             auto parent = cwd.parent();
1071             if(!parent.isValid() || parent == cwd) { // a root path
1072                 bookmarkName = QString::fromUtf8(cwd.displayName().get());
1073                 auto parts = bookmarkName.split(QLatin1Char('/'), Qt::SkipEmptyParts);
1074                 if(!parts.isEmpty()) {
1075                     bookmarkName = parts.last();
1076                 }
1077             }
1078             else {
1079                 bookmarkName = QString::fromUtf8(cwd.baseName().get());
1080             }
1081             bookmarks_->insert(cwd, bookmarkName, -1);
1082         }
1083     }
1084 }
1085 
on_actionEditBookmarks_triggered()1086 void MainWindow::on_actionEditBookmarks_triggered() {
1087     Application* app = static_cast<Application*>(qApp);
1088     app->editBookmarks();
1089 }
1090 
on_actionAbout_triggered()1091 void MainWindow::on_actionAbout_triggered() {
1092     // the about dialog
1093     class AboutDialog : public QDialog {
1094     public:
1095         explicit AboutDialog(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()) : QDialog(parent, f) {
1096             ui.setupUi(this);
1097             ui.version->setText(tr("Version: %1").arg(QStringLiteral(PCMANFM_QT_VERSION)));
1098         }
1099     private:
1100         Ui::AboutDialog ui;
1101     };
1102     AboutDialog dialog(this);
1103     dialog.exec();
1104 }
1105 
on_actionHiddenShortcuts_triggered()1106 void MainWindow::on_actionHiddenShortcuts_triggered() {
1107     class HiddenShortcutsDialog : public QDialog {
1108     public:
1109         explicit HiddenShortcutsDialog(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()) : QDialog(parent, f) {
1110             ui.setupUi(this);
1111             ui.treeWidget->setRootIsDecorated(false);
1112             ui.treeWidget->header()->setSectionResizeMode(QHeaderView::Stretch);
1113             ui.treeWidget->header()->setSectionsClickable(true);
1114             ui.treeWidget->sortByColumn(0, Qt::AscendingOrder);
1115             ui.treeWidget->setSortingEnabled(true);
1116         }
1117     private:
1118         Ui::HiddenShortcutsDialog ui;
1119     };
1120     HiddenShortcutsDialog dialog(this);
1121     dialog.exec();
1122 }
1123 
on_actionIconView_triggered()1124 void MainWindow::on_actionIconView_triggered() {
1125     TabPage* page = currentPage();
1126     page->setViewMode(Fm::FolderView::IconMode);
1127     setTabIcon(page);
1128 }
1129 
on_actionCompactView_triggered()1130 void MainWindow::on_actionCompactView_triggered() {
1131     TabPage* page = currentPage();
1132     page->setViewMode(Fm::FolderView::CompactMode);
1133     setTabIcon(page);
1134 }
1135 
on_actionDetailedList_triggered()1136 void MainWindow::on_actionDetailedList_triggered() {
1137     TabPage* page = currentPage();
1138     page->setViewMode(Fm::FolderView::DetailedListMode);
1139     setTabIcon(page);
1140 }
1141 
on_actionThumbnailView_triggered()1142 void MainWindow::on_actionThumbnailView_triggered() {
1143     TabPage* page = currentPage();
1144     page->setViewMode(Fm::FolderView::ThumbnailMode);
1145     setTabIcon(page);
1146 }
1147 
onTabBarCloseRequested(int index)1148 void MainWindow::onTabBarCloseRequested(int index) {
1149     TabBar* tabBar = static_cast<TabBar*>(sender());
1150     if(ViewFrame* viewFrame = qobject_cast<ViewFrame*>(tabBar->parentWidget())) {
1151         closeTab(index, viewFrame);
1152     }
1153 }
1154 
onResetFocus()1155 void MainWindow::onResetFocus() {
1156     if(TabPage* page = currentPage()) {
1157         page->folderView()->childView()->setFocus();
1158     }
1159 }
1160 
onTabBarTabMoved(int from,int to)1161 void MainWindow::onTabBarTabMoved(int from, int to) {
1162     TabBar* tabBar = static_cast<TabBar*>(sender());
1163     if(ViewFrame* viewFrame = qobject_cast<ViewFrame*>(tabBar->parentWidget())) {
1164         // a tab in the tab bar is moved by the user, so we have to move the
1165         //  corredponding tab page in the stacked widget to the new position, too.
1166         QWidget* page = viewFrame->getStackedWidget()->widget(from);
1167         if(page) {
1168             // we're not going to delete the tab page, so here we block signals
1169             // to avoid calling the slot onStackedWidgetWidgetRemoved() before
1170             // removing the page. Otherwise the page widget will be destroyed.
1171             viewFrame->getStackedWidget()->blockSignals(true);
1172             viewFrame->getStackedWidget()->removeWidget(page);
1173             viewFrame->getStackedWidget()->insertWidget(to, page); // insert the page to the new position
1174             viewFrame->getStackedWidget()->blockSignals(false); // unblock signals
1175             viewFrame->getStackedWidget()->setCurrentWidget(page);
1176         }
1177     }
1178 }
1179 
onFolderUnmounted()1180 void MainWindow::onFolderUnmounted() {
1181     TabPage* tabPage = static_cast<TabPage*>(sender());
1182     if(ViewFrame* viewFrame = viewFrameForTabPage(tabPage)) {
1183         const QList<MountOperation*> ops = ui.sidePane->findChildren<MountOperation*>();
1184         if(ops.isEmpty()) { // unmounting is done somewhere else
1185             Settings& settings = static_cast<Application*>(qApp)->settings();
1186             if(settings.closeOnUnmount()) {
1187                 viewFrame->getStackedWidget()->removeWidget(tabPage);
1188                 // NOTE: Since Fm::Folder queues a folder reload after emitting the unmount signal,
1189                 // pending events may be waiting to be delivered at this very moment. Therefore,
1190                 // if the tab page is deleted immediately, a crash will be imminent for various reasons.
1191                 tabPage->deleteLater();
1192             }
1193             else {
1194                 tabPage->chdir(Fm::FilePath::homeDir(), true);
1195                 setTabIcon(tabPage);
1196                 updateUIForCurrentPage();
1197             }
1198         }
1199         else { // wait for all (un-)mount operations to be finished (otherwise, they might be cancelled)
1200             for(const MountOperation* op : ops) {
1201                 connect(op, &QObject::destroyed, tabPage, [this, tabPage, viewFrame] {
1202                     if(ui.sidePane->findChildren<MountOperation*>().isEmpty()) {
1203                         Settings& settings = static_cast<Application*>(qApp)->settings();
1204                         if(settings.closeOnUnmount()) {
1205                             viewFrame->getStackedWidget()->removeWidget(tabPage);
1206                             tabPage->deleteLater();
1207                         }
1208                         else {
1209                             tabPage->chdir(Fm::FilePath::homeDir(), true);
1210                             setTabIcon(tabPage);
1211                             updateUIForCurrentPage();
1212                         }
1213                     }
1214                 });
1215             }
1216         }
1217     }
1218 }
1219 
closeTab(int index,ViewFrame * viewFrame)1220 void MainWindow::closeTab(int index, ViewFrame* viewFrame) {
1221     QWidget* page = viewFrame->getStackedWidget()->widget(index);
1222     if(page) {
1223         viewFrame->getStackedWidget()->removeWidget(page); // this does not delete the page widget
1224         delete page;
1225         // NOTE: we do not remove the tab here.
1226         // it'll be done in onStackedWidgetWidgetRemoved()
1227     }
1228 }
1229 
resizeEvent(QResizeEvent * event)1230 void MainWindow::resizeEvent(QResizeEvent* event) {
1231     QMainWindow::resizeEvent(event);
1232     Settings& settings = static_cast<Application*>(qApp)->settings();
1233     if(settings.rememberWindowSize()) {
1234         settings.setLastWindowMaximized(isMaximized());
1235 
1236         if(!isMaximized()) {
1237             settings.setLastWindowWidth(width());
1238             settings.setLastWindowHeight(height());
1239         }
1240     }
1241 }
1242 
closeEvent(QCloseEvent * event)1243 void MainWindow::closeEvent(QCloseEvent* event) {
1244     if(lastActive_ == this) {
1245         lastActive_ = nullptr;
1246     }
1247 
1248     QWidget::closeEvent(event);
1249     Settings& settings = static_cast<Application*>(qApp)->settings();
1250     if(settings.rememberWindowSize()) {
1251         settings.setLastWindowMaximized(isMaximized());
1252 
1253         if(!isMaximized()) {
1254             settings.setLastWindowWidth(width());
1255             settings.setLastWindowHeight(height());
1256         }
1257     }
1258 
1259     // remember last tab paths only if this is the last window
1260     QStringList tabPaths;
1261     if(lastActive_ == nullptr && settings.reopenLastTabs()) {
1262         for(int i = 0; i < ui.viewSplitter->count(); ++i) {
1263             if(ViewFrame* viewFrame = qobject_cast<ViewFrame*>(ui.viewSplitter->widget(i))) {
1264                 int n = viewFrame->getStackedWidget()->count();
1265                 for(int j = 0; j < n; ++j) {
1266                     if(TabPage* page = static_cast<TabPage*>(viewFrame->getStackedWidget()->widget(j))) {
1267                         tabPaths.append(QString::fromUtf8(page->path().toString().get()));
1268                     }
1269                 }
1270             }
1271         }
1272         tabPaths.removeDuplicates();
1273     }
1274     settings.setTabPaths(tabPaths);
1275 }
1276 
onTabBarCurrentChanged(int index)1277 void MainWindow::onTabBarCurrentChanged(int index) {
1278     TabBar* tabBar = static_cast<TabBar*>(sender());
1279     if(ViewFrame* viewFrame = qobject_cast<ViewFrame*>(tabBar->parentWidget())) {
1280         viewFrame->getStackedWidget()->setCurrentIndex(index);
1281         if(viewFrame == activeViewFrame_) {
1282             updateUIForCurrentPage();
1283         }
1284         else {
1285             if(TabPage* page = currentPage(viewFrame)) {
1286                 if(Fm::PathBar* pathBar = qobject_cast<Fm::PathBar*>(viewFrame->getTopBar())) {
1287                     pathBar->setPath(page->path());
1288                 }
1289                 else if(Fm::PathEdit* pathEntry = qobject_cast<Fm::PathEdit*>(viewFrame->getTopBar())) {
1290                     pathEntry->setText(page->pathName());
1291                 }
1292             }
1293         }
1294     }
1295 }
1296 
updateStatusBarForCurrentPage()1297 void MainWindow::updateStatusBarForCurrentPage() {
1298     TabPage* tabPage = currentPage();
1299     QString text = tabPage->statusText(TabPage::StatusTextSelectedFiles);
1300     if(text.isEmpty()) {
1301         text = tabPage->statusText(TabPage::StatusTextNormal);
1302     }
1303     ui.statusbar->showMessage(text);
1304 
1305     text = tabPage->statusText(TabPage::StatusTextFSInfo);
1306     fsInfoLabel_->setText(text);
1307     fsInfoLabel_->setVisible(!text.isEmpty());
1308 }
1309 
updateViewMenuForCurrentPage()1310 void MainWindow::updateViewMenuForCurrentPage() {
1311     if(updatingViewMenu_) { // prevent recursive calls
1312         return;
1313     }
1314     updatingViewMenu_ = true;
1315     TabPage* tabPage = currentPage();
1316     if(tabPage) {
1317         // update menus. FIXME: should we move this to another method?
1318         ui.actionShowHidden->setChecked(tabPage->showHidden());
1319         ui.actionPreserveView->setChecked(tabPage->hasCustomizedView() && !tabPage->hasRecursiveCustomizedView());
1320         ui.actionPreserveViewRecursive->setChecked(tabPage->hasRecursiveCustomizedView());
1321         ui.actionGoToCustomizedViewSource->setVisible(tabPage->hasInheritedCustomizedView());
1322 
1323         // view mode
1324         QAction* modeAction = nullptr;
1325 
1326         switch(tabPage->viewMode()) {
1327         case Fm::FolderView::IconMode:
1328             modeAction = ui.actionIconView;
1329             break;
1330 
1331         case Fm::FolderView::CompactMode:
1332             modeAction = ui.actionCompactView;
1333             break;
1334 
1335         case Fm::FolderView::DetailedListMode:
1336             modeAction = ui.actionDetailedList;
1337             break;
1338 
1339         case Fm::FolderView::ThumbnailMode:
1340             modeAction = ui.actionThumbnailView;
1341             break;
1342         }
1343 
1344         Q_ASSERT(modeAction != nullptr);
1345         modeAction->setChecked(true);
1346 
1347         // sort menu
1348         // WARNING: Since libfm-qt may have a column that is not handled here,
1349         // we should prevent a crash by setting all actions to null first and
1350         // check their action group later.
1351         QAction* sortActions[Fm::FolderModel::NumOfColumns];
1352         for(int i = 0; i < Fm::FolderModel::NumOfColumns; ++i) {
1353             sortActions[i] = nullptr;
1354         }
1355         sortActions[Fm::FolderModel::ColumnFileName] = ui.actionByFileName;
1356         sortActions[Fm::FolderModel::ColumnFileMTime] = ui.actionByMTime;
1357         sortActions[Fm::FolderModel::ColumnFileCrTime] = ui.actionByCrTime;
1358         sortActions[Fm::FolderModel::ColumnFileDTime] = ui.actionByDTime;
1359         sortActions[Fm::FolderModel::ColumnFileSize] = ui.actionByFileSize;
1360         sortActions[Fm::FolderModel::ColumnFileType] = ui.actionByFileType;
1361         sortActions[Fm::FolderModel::ColumnFileOwner] = ui.actionByOwner;
1362         sortActions[Fm::FolderModel::ColumnFileGroup] = ui.actionByGroup;
1363         if (auto group = ui.actionByFileName->actionGroup()) {
1364             const auto actions = group->actions();
1365             auto action = sortActions[tabPage->sortColumn()];
1366             if(actions.contains(action)) {
1367                 action->setChecked(true);
1368             }
1369             else {
1370                 for(auto a : actions) {
1371                     a->setChecked(false);
1372                 }
1373             }
1374         }
1375 
1376         if(auto path = tabPage->path()) {
1377             ui.actionByDTime->setVisible(strcmp(path.toString().get(), "trash:///") == 0);
1378         }
1379 
1380         if(tabPage->sortOrder() == Qt::AscendingOrder) {
1381             ui.actionAscending->setChecked(true);
1382         }
1383         else {
1384             ui.actionDescending->setChecked(true);
1385         }
1386         ui.actionCaseSensitive->setChecked(tabPage->sortCaseSensitive());
1387         ui.actionFolderFirst->setChecked(tabPage->sortFolderFirst());
1388         ui.actionHiddenLast->setChecked(tabPage->sortHiddenLast());
1389     }
1390     updatingViewMenu_ = false;
1391 }
1392 
1393 // Update the enabled state of Edit actions for selected files
updateEditSelectedActions()1394 void MainWindow::updateEditSelectedActions() {
1395     bool hasAccessible(false);
1396     bool hasDeletable(false);
1397     int renamable(0);
1398     if(TabPage* page = currentPage()) {
1399         auto files = page->selectedFiles();
1400         for(auto& file: files) {
1401             if(file->isAccessible()) {
1402                 hasAccessible = true;
1403             }
1404             if(file->isDeletable()) {
1405                 hasDeletable = true;
1406             }
1407             if(file->canSetName()) {
1408                 ++renamable;
1409             }
1410             if (hasAccessible && hasDeletable && renamable > 1) {
1411                 break;
1412             }
1413         }
1414         ui.actionCopyFullPath->setEnabled(files.size() == 1);
1415     }
1416     ui.actionCopy->setEnabled(hasAccessible);
1417     ui.actionCut->setEnabled(hasDeletable);
1418     ui.actionDelete->setEnabled(hasDeletable);
1419     ui.actionRename->setEnabled(renamable > 0);
1420     ui.actionBulkRename->setEnabled(renamable > 1);
1421 }
1422 
updateUIForCurrentPage(bool setFocus)1423 void MainWindow::updateUIForCurrentPage(bool setFocus) {
1424     TabPage* tabPage = currentPage();
1425 
1426     if(tabPage) {
1427         setWindowTitle(tabPage->title());
1428         if(splitView_) {
1429             if(Fm::PathBar* pathBar = qobject_cast<Fm::PathBar*>(activeViewFrame_->getTopBar())) {
1430                 pathBar->setPath(tabPage->path());
1431             }
1432             else if(Fm::PathEdit* pathEntry = qobject_cast<Fm::PathEdit*>(activeViewFrame_->getTopBar())) {
1433                 pathEntry->setText(tabPage->pathName());
1434             }
1435         }
1436         else {
1437             if(pathEntry_ != nullptr) {
1438                 pathEntry_->setText(tabPage->pathName());
1439             }
1440             else if(pathBar_ != nullptr) {
1441                 pathBar_->setPath(tabPage->path());
1442             }
1443         }
1444         ui.statusbar->showMessage(tabPage->statusText());
1445         fsInfoLabel_->setText(tabPage->statusText(TabPage::StatusTextFSInfo));
1446         if(setFocus) {
1447             tabPage->folderView()->childView()->setFocus();
1448         }
1449 
1450         // update side pane
1451         ui.sidePane->setCurrentPath(tabPage->path());
1452         ui.sidePane->setShowHidden(tabPage->showHidden());
1453 
1454         // update back/forward/up toolbar buttons
1455         ui.actionGoUp->setEnabled(tabPage->canUp());
1456         ui.actionGoBack->setEnabled(tabPage->canBackward());
1457         ui.actionGoForward->setEnabled(tabPage->canForward());
1458 
1459         ui.actionOpenAsAdmin->setEnabled(tabPage->path() && tabPage->path().isNative());
1460 
1461         updateViewMenuForCurrentPage();
1462         updateStatusBarForCurrentPage();
1463     }
1464 
1465     // also update the enabled state of Edit actions
1466     updateEditSelectedActions();
1467     bool isWritable(false);
1468     bool isNative(false);
1469     if(tabPage && tabPage->folder()) {
1470         if(auto info = tabPage->folder()->info()) {
1471             isWritable = info->isWritable();
1472             isNative = info->isNative();
1473         }
1474     }
1475     ui.actionPaste->setEnabled(isWritable);
1476     ui.menuCreateNew->setEnabled(isWritable);
1477     // disable creation shortcuts too
1478     ui.actionNewFolder->setEnabled(isWritable);
1479     ui.actionNewBlankFile->setEnabled(isWritable);
1480     ui.actionCreateLauncher->setEnabled(isWritable && isNative);
1481 }
1482 
onStackedWidgetWidgetRemoved(int index)1483 void MainWindow::onStackedWidgetWidgetRemoved(int index) {
1484     QStackedWidget* sw = static_cast<QStackedWidget*>(sender());
1485     if(ViewFrame* viewFrame = qobject_cast<ViewFrame*>(sw->parentWidget())) {
1486         // qDebug("onStackedWidgetWidgetRemoved: %d", index);
1487         // need to remove associated tab from tabBar
1488         viewFrame->getTabBar()->removeTab(index);
1489         if(viewFrame->getTabBar()->count() == 0) { // this is the last one
1490             if(!splitView_) {
1491                 deleteLater(); // destroy the whole window
1492                 // qDebug("delete window");
1493             }
1494             else {
1495                 // if we are in the split mode and the last tab of a view frame is closed,
1496                 // remove that view frame and go to the simple mode
1497                 for(int i = 0; i < ui.viewSplitter->count(); ++i) {
1498                     // first find and activate the next view frame
1499                     if(ViewFrame* thisViewFrame = qobject_cast<ViewFrame*>(ui.viewSplitter->widget(i))) {
1500                         if(thisViewFrame == viewFrame) {
1501                             int n = i < ui.viewSplitter->count() - 1 ? i + 1 : 0;
1502                             if(ViewFrame* nextViewFrame = qobject_cast<ViewFrame*>(ui.viewSplitter->widget(n))) {
1503                                 if(activeViewFrame_ != nextViewFrame) {
1504                                     activeViewFrame_ = nextViewFrame;
1505                                     updateUIForCurrentPage();
1506                                     // if the window isn't active, eventFilter() won't be called,
1507                                     // so we should revert to the main palette here
1508                                     if(activeViewFrame_->palette().color(QPalette::Base)
1509                                        != qApp->palette().color(QPalette::Base)) {
1510                                         activeViewFrame_->setPalette(qApp->palette());
1511                                     }
1512                                 }
1513                                 break;
1514                             }
1515                         }
1516                     }
1517                 }
1518                 ui.actionSplitView->setChecked(false);
1519                 on_actionSplitView_triggered(false);
1520             }
1521         }
1522         else {
1523             Settings& settings = static_cast<Application*>(qApp)->settings();
1524             if(!settings.alwaysShowTabs() && viewFrame->getTabBar()->count() == 1) {
1525                 viewFrame->getTabBar()->setVisible(false);
1526             }
1527         }
1528     }
1529 }
1530 
onTabPageTitleChanged()1531 void MainWindow::onTabPageTitleChanged() {
1532     TabPage* tabPage = static_cast<TabPage*>(sender());
1533     if(ViewFrame* viewFrame = viewFrameForTabPage(tabPage)) {
1534         int index = viewFrame->getStackedWidget()->indexOf(tabPage);
1535         if(index >= 0) {
1536             QString tabText = tabPage->title();
1537             // remove newline and distinguish ampersand from mnemonic
1538             tabText.replace(QLatin1Char('\n'), QLatin1Char(' '))
1539                    .replace(QLatin1Char('&'), QLatin1String("&&"));
1540             viewFrame->getTabBar()->setTabText(index, tabText);
1541         }
1542 
1543         if(viewFrame == activeViewFrame_) {
1544             if(tabPage == currentPage()) {
1545                 setWindowTitle(tabPage->title());
1546 
1547                 // Since TabPage::titleChanged is emitted on changing directory,
1548                 // the enabled state of some actions should be updated here
1549                 bool isNative(tabPage->path() && tabPage->path().isNative());
1550                 ui.actionOpenAsAdmin->setEnabled(isNative);
1551                 bool isWritable(false);
1552                 if(tabPage && tabPage->folder()) {
1553                     if(auto info = tabPage->folder()->info()) {
1554                         isWritable = info->isWritable();
1555                     }
1556                 }
1557                 ui.actionPaste->setEnabled(isWritable);
1558                 ui.menuCreateNew->setEnabled(isWritable);
1559                 ui.actionNewFolder->setEnabled(isWritable);
1560                 ui.actionNewBlankFile->setEnabled(isWritable);
1561                 ui.actionCreateLauncher->setEnabled(isWritable && isNative);
1562             }
1563         }
1564     }
1565 }
1566 
onTabPageStatusChanged(int type,QString statusText)1567 void MainWindow::onTabPageStatusChanged(int type, QString statusText) {
1568     TabPage* tabPage = static_cast<TabPage*>(sender());
1569     if(tabPage == currentPage()) {
1570         switch(type) {
1571         case TabPage::StatusTextNormal:
1572         case TabPage::StatusTextSelectedFiles: {
1573             // although the status text may change very frequently,
1574             // the text of PCManFM::StatusBar is updated with a delay
1575             QString text = tabPage->statusText(TabPage::StatusTextSelectedFiles);
1576             if(text.isEmpty()) {
1577                 ui.statusbar->showMessage(tabPage->statusText(TabPage::StatusTextNormal));
1578             }
1579             else {
1580                 ui.statusbar->showMessage(text);
1581             }
1582             break;
1583         }
1584         case TabPage::StatusTextFSInfo:
1585             fsInfoLabel_->setText(tabPage->statusText(TabPage::StatusTextFSInfo));
1586             fsInfoLabel_->setVisible(!statusText.isEmpty());
1587             break;
1588         }
1589     }
1590 
1591     // Since TabPage::statusChanged is always emitted after View::selChanged,
1592     // there is no need to connect a separate slot to the latter signal
1593     updateEditSelectedActions();
1594 }
1595 
onTabPageSortFilterChanged()1596 void MainWindow::onTabPageSortFilterChanged() { // NOTE: This may be called from context menu too.
1597     TabPage* tabPage = static_cast<TabPage*>(sender());
1598     if(tabPage == currentPage()) {
1599         updateViewMenuForCurrentPage();
1600         ui.sidePane->setShowHidden(tabPage->showHidden());
1601         if(!tabPage->hasCustomizedView() && !tabPage->hasInheritedCustomizedView()) { // remember sort settings globally
1602             Settings& settings = static_cast<Application*>(qApp)->settings();
1603             settings.setSortColumn(static_cast<Fm::FolderModel::ColumnId>(tabPage->sortColumn()));
1604             settings.setSortOrder(tabPage->sortOrder());
1605             settings.setSortFolderFirst(tabPage->sortFolderFirst());
1606             settings.setSortHiddenLast(tabPage->sortHiddenLast());
1607             settings.setSortCaseSensitive(tabPage->sortCaseSensitive());
1608             settings.setShowHidden(tabPage->showHidden());
1609         }
1610     }
1611 }
1612 
1613 
onSidePaneChdirRequested(int type,const Fm::FilePath & path)1614 void MainWindow::onSidePaneChdirRequested(int type, const Fm::FilePath &path) {
1615     // FIXME: use enum for type value or change it to button.
1616     if(type == 0) { // left button (default)
1617         chdir(path);
1618     }
1619     else if(type == 1) { // middle button
1620         addTab(path);
1621     }
1622     else if(type == 2) { // new window
1623         (new MainWindow(path))->show();
1624     }
1625 }
1626 
onSidePaneOpenFolderInNewWindowRequested(const Fm::FilePath & path)1627 void MainWindow::onSidePaneOpenFolderInNewWindowRequested(const Fm::FilePath &path) {
1628     (new MainWindow(path))->show();
1629 }
1630 
onSidePaneOpenFolderInNewTabRequested(const Fm::FilePath & path)1631 void MainWindow::onSidePaneOpenFolderInNewTabRequested(const Fm::FilePath &path) {
1632     addTab(path);
1633 }
1634 
onSidePaneOpenFolderInTerminalRequested(const Fm::FilePath & path)1635 void MainWindow::onSidePaneOpenFolderInTerminalRequested(const Fm::FilePath &path) {
1636     Application* app = static_cast<Application*>(qApp);
1637     app->openFolderInTerminal(path);
1638 }
1639 
onSidePaneCreateNewFolderRequested(const Fm::FilePath & path)1640 void MainWindow::onSidePaneCreateNewFolderRequested(const Fm::FilePath &path) {
1641     createFileOrFolder(CreateNewFolder, path, nullptr, this);
1642 }
1643 
onSidePaneModeChanged(Fm::SidePane::Mode mode)1644 void MainWindow::onSidePaneModeChanged(Fm::SidePane::Mode mode) {
1645     static_cast<Application*>(qApp)->settings().setSidePaneMode(mode);
1646 }
1647 
onSettingHiddenPlace(const QString & str,bool hide)1648 void MainWindow::onSettingHiddenPlace(const QString& str, bool hide) {
1649     static_cast<Application*>(qApp)->settings().setHiddenPlace(str, hide);
1650 }
1651 
on_actionSidePane_triggered(bool checked)1652 void MainWindow::on_actionSidePane_triggered(bool checked) {
1653     Application* app = static_cast<Application*>(qApp);
1654     app->settings().showSidePane(checked);
1655     ui.sidePane->setVisible(checked);
1656 }
1657 
onSplitterMoved(int pos,int)1658 void MainWindow::onSplitterMoved(int pos, int /*index*/) {
1659     Application* app = static_cast<Application*>(qApp);
1660     app->settings().setSplitterPos(pos);
1661 }
1662 
loadBookmarksMenu()1663 void MainWindow::loadBookmarksMenu() {
1664     QAction* before = ui.actionAddToBookmarks;
1665     for(auto& item: bookmarks_->items()) {
1666         BookmarkAction* action = new BookmarkAction(item, ui.menu_Bookmarks);
1667         connect(action, &QAction::triggered, this, &MainWindow::onBookmarkActionTriggered);
1668         ui.menu_Bookmarks->insertAction(before, action);
1669     }
1670 
1671     ui.menu_Bookmarks->insertSeparator(before);
1672 }
1673 
onBookmarksChanged()1674 void MainWindow::onBookmarksChanged() {
1675     // delete existing items
1676     QList<QAction*> actions = ui.menu_Bookmarks->actions();
1677     QList<QAction*>::const_iterator it = actions.constBegin();
1678     QList<QAction*>::const_iterator last_it = actions.constEnd() - 2;
1679 
1680     while(it != last_it) {
1681         QAction* action = *it;
1682         ++it;
1683         ui.menu_Bookmarks->removeAction(action);
1684     }
1685 
1686     loadBookmarksMenu();
1687 }
1688 
onBookmarkActionTriggered()1689 void MainWindow::onBookmarkActionTriggered() {
1690     BookmarkAction* action = static_cast<BookmarkAction*>(sender());
1691     auto path = action->path();
1692     if(path) {
1693         Application* app = static_cast<Application*>(qApp);
1694         Settings& settings = app->settings();
1695         switch(settings.bookmarkOpenMethod()) {
1696         case OpenInCurrentTab: /* current tab */
1697         default:
1698             chdir(path);
1699             break;
1700         case OpenInNewTab: /* new tab */
1701             addTab(path);
1702             break;
1703         case OpenInNewWindow: /* new window */
1704             (new MainWindow(path))->show();
1705             break;
1706         }
1707     }
1708 }
1709 
on_actionCopy_triggered()1710 void MainWindow::on_actionCopy_triggered() {
1711     TabPage* page = currentPage();
1712     auto paths = page->selectedFilePaths();
1713     copyFilesToClipboard(paths);
1714 }
1715 
on_actionCut_triggered()1716 void MainWindow::on_actionCut_triggered() {
1717     TabPage* page = currentPage();
1718     auto paths = page->selectedFilePaths();
1719     cutFilesToClipboard(paths);
1720 }
1721 
on_actionPaste_triggered()1722 void MainWindow::on_actionPaste_triggered() {
1723     pasteFilesFromClipboard(currentPage()->path());
1724 }
1725 
on_actionDelete_triggered()1726 void MainWindow::on_actionDelete_triggered() {
1727     Application* app = static_cast<Application*>(qApp);
1728     Settings& settings = app->settings();
1729     TabPage* page = currentPage();
1730     auto paths = page->selectedFilePaths();
1731     auto path_it = paths.cbegin();
1732     bool trashed(path_it != paths.cend() && (*path_it).hasUriScheme("trash"));
1733 
1734     bool shiftPressed = (qApp->keyboardModifiers() & Qt::ShiftModifier ? true : false);
1735     if(settings.useTrash() && !shiftPressed
1736        // trashed files should be deleted
1737        && !trashed) {
1738         FileOperation::trashFiles(paths, settings.confirmTrash(), this);
1739     }
1740     else {
1741         FileOperation::deleteFiles(paths, settings.confirmDelete(), this);
1742     }
1743 }
1744 
on_actionRename_triggered()1745 void MainWindow::on_actionRename_triggered() {
1746     // do inline renaming if only one item is selected,
1747     // otherwise use the renaming dialog
1748     TabPage* page = currentPage();
1749     auto files = page->selectedFiles();
1750     if(files.size() == 1) {
1751         QAbstractItemView* view = page->folderView()->childView();
1752         QModelIndexList selIndexes = view->selectionModel()->selectedIndexes();
1753         if(selIndexes.size() > 1) { // in the detailed list mode, only the first index is editable
1754             view->setCurrentIndex(selIndexes.at(0));
1755         }
1756         QModelIndex cur = view->currentIndex();
1757         if (cur.isValid()) {
1758             view->scrollTo(cur);
1759             view->edit(cur);
1760             return;
1761         }
1762     }
1763     if(!files.empty()) {
1764         for(auto& file: files) {
1765             if(!Fm::renameFile(file, nullptr)) {
1766                 break;
1767             }
1768         }
1769     }
1770 }
1771 
on_actionBulkRename_triggered()1772 void MainWindow::on_actionBulkRename_triggered() {
1773     BulkRenamer(currentPage()->selectedFiles(), this);
1774 }
1775 
on_actionSelectAll_triggered()1776 void MainWindow::on_actionSelectAll_triggered() {
1777     currentPage()->selectAll();
1778 }
1779 
on_actionInvertSelection_triggered()1780 void MainWindow::on_actionInvertSelection_triggered() {
1781     currentPage()->invertSelection();
1782 }
1783 
on_actionPreferences_triggered()1784 void MainWindow::on_actionPreferences_triggered() {
1785     Application* app = reinterpret_cast<Application*>(qApp);
1786     app->preferences(QString());
1787 }
1788 
1789 // change some icons according to layout direction
setRTLIcons(bool isRTL)1790 void MainWindow::setRTLIcons(bool isRTL) {
1791     QIcon nxtIcn = QIcon::fromTheme(QStringLiteral("go-next"));
1792     QIcon prevIcn = QIcon::fromTheme(QStringLiteral("go-previous"));
1793     if(isRTL) {
1794         ui.actionGoBack->setIcon(nxtIcn);
1795         ui.actionCloseLeft->setIcon(nxtIcn);
1796         ui.actionGoForward->setIcon(prevIcn);
1797         ui.actionCloseRight->setIcon(prevIcn);
1798     }
1799     else {
1800         ui.actionGoBack->setIcon(prevIcn);
1801         ui.actionCloseLeft->setIcon(prevIcn);
1802         ui.actionGoForward->setIcon(nxtIcn);
1803         ui.actionCloseRight->setIcon(nxtIcn);
1804     }
1805 }
1806 
event(QEvent * event)1807 bool MainWindow::event(QEvent* event) {
1808     switch(event->type()) {
1809     case QEvent::WindowActivate:
1810         lastActive_ = this;
1811     default:
1812         break;
1813     }
1814     return QMainWindow::event(event);
1815 }
1816 
changeEvent(QEvent * event)1817 void MainWindow::changeEvent(QEvent* event) {
1818     switch(event->type()) {
1819     case QEvent::LayoutDirectionChange:
1820         setRTLIcons(QApplication::layoutDirection() == Qt::RightToLeft);
1821         break;
1822     default:
1823         break;
1824     }
1825     QWidget::changeEvent(event);
1826 }
1827 
onBackForwardContextMenu(QPoint pos)1828 void MainWindow::onBackForwardContextMenu(QPoint pos) {
1829     // show a popup menu for browsing history here.
1830     QToolButton* btn = static_cast<QToolButton*>(sender());
1831     TabPage* page = currentPage();
1832     Fm::BrowseHistory& history = page->browseHistory();
1833     int current = history.currentIndex();
1834     QMenu menu;
1835     for(size_t i = 0; i < history.size(); ++i) {
1836         const BrowseHistoryItem& item = history.at(i);
1837         auto path = item.path();
1838         auto name = path.displayName();
1839         QAction* action = menu.addAction(QString::fromUtf8(name.get()));
1840         if(i == static_cast<size_t>(current)) {
1841             // make the current path bold and checked
1842             action->setCheckable(true);
1843             action->setChecked(true);
1844             QFont font = menu.font();
1845             font.setBold(true);
1846             action->setFont(font);
1847         }
1848     }
1849     QAction* selectedAction = menu.exec(btn->mapToGlobal(pos));
1850     if(selectedAction) {
1851         int index = menu.actions().indexOf(selectedAction);
1852         page->jumpToHistory(index);
1853         setTabIcon(page);
1854         updateUIForCurrentPage();
1855     }
1856 }
1857 
onTabBarClicked(int)1858 void MainWindow::onTabBarClicked(int /*index*/) {
1859     TabBar* tabBar = static_cast<TabBar*>(sender());
1860     if(ViewFrame* viewFrame = qobject_cast<ViewFrame*>(tabBar->parentWidget())) {
1861         // focus the view on clicking the tab bar
1862         if(TabPage* page = currentPage(viewFrame)) {
1863             page->folderView()->childView()->setFocus();
1864         }
1865     }
1866 }
1867 
tabContextMenu(const QPoint & pos)1868 void MainWindow::tabContextMenu(const QPoint& pos) {
1869     TabBar* tabBar = static_cast<TabBar*>(sender());
1870     if(ViewFrame* viewFrame = qobject_cast<ViewFrame*>(tabBar->parentWidget())) {
1871         int tabNum = viewFrame->getTabBar()->count();
1872         if(tabNum < 1) {
1873             return;
1874         }
1875 
1876         rightClickIndex_ = viewFrame->getTabBar()->tabAt(pos);
1877         if(rightClickIndex_ < 0) {
1878             return;
1879         }
1880 
1881         QMenu menu;
1882 
1883         // tab closing actions
1884         if(rightClickIndex_ > 0) {
1885             menu.addAction(ui.actionCloseLeft);
1886         }
1887         if(rightClickIndex_ < tabNum - 1) {
1888             menu.addAction(ui.actionCloseRight);
1889             if(rightClickIndex_ > 0) {
1890                 menu.addSeparator();
1891                 menu.addAction(ui.actionCloseOther);
1892             }
1893         }
1894 
1895         // per-folder actions for the current tab
1896         if(viewFrame->getTabBar()->currentIndex() == rightClickIndex_) {
1897             menu.addSeparator();
1898             QWidgetAction* labelAction = new QWidgetAction(&menu);
1899             QLabel *label = new QLabel(QStringLiteral("<center><b>")
1900                                     + tr("Customized View Settings")
1901                                     + QStringLiteral("</b></center>"));
1902             label->setMargin(5);
1903             labelAction->setDefaultWidget(label);
1904             menu.addAction(labelAction);
1905             menu.addAction(ui.actionPreserveView);
1906             menu.addAction(ui.actionPreserveViewRecursive);
1907             menu.addSeparator();
1908             menu.addAction(ui.actionGoToCustomizedViewSource);
1909             menu.addAction(ui.actionCleanPerFolderConfig);
1910         }
1911 
1912         menu.exec(viewFrame->getTabBar()->mapToGlobal(pos));
1913     }
1914 }
1915 
closeLeftTabs()1916 void MainWindow::closeLeftTabs() {
1917     while(rightClickIndex_ > 0) {
1918         closeTab(rightClickIndex_ - 1);
1919         --rightClickIndex_;
1920     }
1921 }
1922 
closeRightTabs()1923 void MainWindow::closeRightTabs() {
1924     if(rightClickIndex_ < 0) {
1925         return;
1926     }
1927     while(rightClickIndex_ < activeViewFrame_->getTabBar()->count() - 1) {
1928         closeTab(rightClickIndex_ + 1);
1929     }
1930 }
1931 
focusPathEntry()1932 void MainWindow::focusPathEntry() {
1933     // use text entry for the path bar
1934     if(splitView_) {
1935         if(Fm::PathBar* pathBar = qobject_cast<Fm::PathBar*>(activeViewFrame_->getTopBar())) {
1936             pathBar->openEditor();
1937         }
1938         else if(Fm::PathEdit* pathEntry = qobject_cast<Fm::PathEdit*>(activeViewFrame_->getTopBar())) {
1939             pathEntry->setFocus();
1940             pathEntry->selectAll();
1941         }
1942     }
1943     else{
1944         if(pathEntry_ != nullptr) {
1945             pathEntry_->setFocus();
1946             pathEntry_->selectAll();
1947         }
1948         else if(pathBar_ != nullptr) {  // use button-style path bar
1949             pathBar_->openEditor();
1950         }
1951     }
1952 }
1953 
dragEnterEvent(QDragEnterEvent * event)1954 void MainWindow::dragEnterEvent(QDragEnterEvent* event) {
1955     if(event->mimeData()->hasFormat(QStringLiteral("application/pcmanfm-qt-tab"))
1956        // ensure that the tab drag source is ours (and not a root window, for example)
1957        && event->source() != nullptr) {
1958         event->acceptProposedAction();
1959     }
1960 }
1961 
dropEvent(QDropEvent * event)1962 void MainWindow::dropEvent(QDropEvent* event) {
1963     if(event->mimeData()->hasFormat(QStringLiteral("application/pcmanfm-qt-tab"))) {
1964         dropTab(event->source());
1965     }
1966     event->acceptProposedAction();
1967 }
1968 
dropTab(QObject * source)1969 void MainWindow::dropTab(QObject* source) {
1970     QWidget* w = qobject_cast<QWidget*>(source);
1971     MainWindow* dragSource = (w == nullptr ? nullptr : qobject_cast<MainWindow*>(w->window()));
1972     if (dragSource == this // drop on itself
1973         || dragSource == nullptr) {
1974         activeViewFrame_->getTabBar()->finishMouseMoveEvent();
1975         return;
1976     }
1977 
1978     // first close the tab in the drag window;
1979     // then add its page to a new tab in the drop window
1980     TabPage* dropPage = dragSource->currentPage();
1981     if(dropPage) {
1982         disconnect(dropPage, nullptr, dragSource, nullptr);
1983 
1984         // release mouse before tab removal because otherwise, the source tabbar
1985         // might not be updated properly with tab reordering during a fast drag-and-drop
1986         dragSource->activeViewFrame_->getTabBar()->releaseMouse();
1987 
1988         dragSource->activeViewFrame_->getStackedWidget()->removeWidget(dropPage);
1989         int index = addTabWithPage(dropPage, activeViewFrame_);
1990         activeViewFrame_->getTabBar()->setCurrentIndex(index);
1991     }
1992     else {
1993         activeViewFrame_->getTabBar()->finishMouseMoveEvent(); // impossible
1994     }
1995 }
1996 
detachTab()1997 void MainWindow::detachTab() {
1998     if (activeViewFrame_->getStackedWidget()->count() == 1 // don't detach a single tab
1999         || static_cast<Application*>(qApp)->settings().splitView()) { // may have changed elsewhere
2000         activeViewFrame_->getTabBar()->finishMouseMoveEvent();
2001         return;
2002     }
2003 
2004     // close the tab and move its page to a new window
2005     TabPage* dropPage = currentPage();
2006     if(dropPage) {
2007         disconnect(dropPage, nullptr, this, nullptr);
2008 
2009         activeViewFrame_->getTabBar()->releaseMouse(); // as in dropTab()
2010         activeViewFrame_->getStackedWidget()->removeWidget(dropPage);
2011         MainWindow* newWin = new MainWindow();
2012         newWin->addTabWithPage(dropPage, newWin->activeViewFrame_);
2013         newWin->show();
2014     }
2015     else {
2016         activeViewFrame_->getTabBar()->finishMouseMoveEvent(); // impossible
2017     }
2018 }
2019 
setTabIcon(TabPage * tabPage)2020 void MainWindow::setTabIcon(TabPage* tabPage) {
2021     ViewFrame* viewFrame = viewFrameForTabPage(tabPage);
2022     if(viewFrame == nullptr) {
2023         return;
2024     }
2025     bool isCustomized = tabPage->hasCustomizedView() || tabPage->hasInheritedCustomizedView();
2026     int index = viewFrame->getStackedWidget()->indexOf(tabPage);
2027     auto tabBar = viewFrame->getTabBar();
2028 
2029     if(!isCustomized) {
2030         if(!tabBar->tabIcon(index).isNull()) {
2031             tabBar->setTabIcon(index, QIcon());
2032         }
2033         return;
2034     }
2035 
2036     // set the tab icon of a customized folder to its view mode
2037     switch(tabPage->viewMode()) {
2038     case Fm::FolderView::IconMode:
2039         tabBar->setTabIcon(index, QIcon::fromTheme(QLatin1String("view-list-icons"), style()->standardIcon(QStyle::SP_FileDialogContentsView)));
2040         break;
2041     case Fm::FolderView::CompactMode:
2042         tabBar->setTabIcon(index, QIcon::fromTheme(QLatin1String("view-list-text"), style()->standardIcon(QStyle::SP_FileDialogListView)));
2043         break;
2044     case Fm::FolderView::DetailedListMode:
2045         tabBar->setTabIcon(index, QIcon::fromTheme(QLatin1String("view-list-details"), style()->standardIcon(QStyle::SP_FileDialogDetailedView)));
2046         break;
2047     case Fm::FolderView::ThumbnailMode:
2048         tabBar->setTabIcon(index, QIcon::fromTheme(QLatin1String("view-preview"), style()->standardIcon(QStyle::SP_FileDialogInfoView)));
2049         break;
2050     }
2051 }
2052 
updateFromSettings(Settings & settings)2053 void MainWindow::updateFromSettings(Settings& settings) {
2054     // apply settings
2055 
2056     // menu
2057     ui.actionDelete->setText(settings.useTrash() ? tr("&Move to Trash") : tr("&Delete"));
2058     ui.actionDelete->setIcon(settings.useTrash() ? QIcon::fromTheme(QStringLiteral("user-trash")) : QIcon::fromTheme(QStringLiteral("edit-delete")));
2059 
2060     // side pane
2061     ui.sidePane->setIconSize(QSize(settings.sidePaneIconSize(), settings.sidePaneIconSize()));
2062 
2063     // tabs
2064     for(int i = 0; i < ui.viewSplitter->count(); ++i) {
2065         if(ViewFrame* viewFrame = qobject_cast<ViewFrame*>(ui.viewSplitter->widget(i))) {
2066             viewFrame->getTabBar()->setTabsClosable(settings.showTabClose());
2067             viewFrame->getTabBar()->setVisible(settings.alwaysShowTabs() || (viewFrame->getTabBar()->count() > 1));
2068 
2069             // all tab pages
2070             int n = viewFrame->getStackedWidget()->count();
2071 
2072             for(int j = 0; j < n; ++j) {
2073                 TabPage* page = static_cast<TabPage*>(viewFrame->getStackedWidget()->widget(j));
2074                 page->updateFromSettings(settings);
2075             }
2076         }
2077     }
2078 }
2079 
on_actionOpenAsAdmin_triggered()2080 void MainWindow::on_actionOpenAsAdmin_triggered() {
2081     if(TabPage* page = currentPage()) {
2082         if(auto path = page->path()) {
2083             if(path.isNative()) {
2084                 CStrPtr admin{g_strconcat("admin://", path.localPath().get(), nullptr)};
2085                 chdir(Fm::FilePath::fromPathStr(admin.get()));
2086             }
2087         }
2088     }
2089 }
2090 
on_actionOpenAsRoot_triggered()2091 void MainWindow::on_actionOpenAsRoot_triggered() {
2092     TabPage* page = currentPage();
2093 
2094     if(page) {
2095         Application* app = static_cast<Application*>(qApp);
2096         Settings& settings = app->settings();
2097 
2098         if(!settings.suCommand().isEmpty()) {
2099             // run the su command
2100             // FIXME: it's better to get the filename of the current process rather than hard-code pcmanfm-qt here.
2101             QByteArray suCommand = settings.suCommand().toLocal8Bit();
2102             QByteArray programCommand = app->applicationFilePath().toLocal8Bit();
2103             programCommand += " %U";
2104 
2105             // if %s exists in the su command, substitute it with the program
2106             int substPos = suCommand.indexOf("%s");
2107             if(substPos != -1) {
2108                 // replace %s with program
2109                 suCommand.replace(substPos, 2, programCommand);
2110             }
2111             else {
2112                 /* no %s found so just append to it */
2113                 suCommand += programCommand;
2114             }
2115 
2116             Fm::GAppInfoPtr appInfo{g_app_info_create_from_commandline(suCommand.constData(), nullptr, GAppInfoCreateFlags(0), nullptr), false};
2117 
2118             if(appInfo) {
2119                 auto cwd = page->path();
2120                 Fm::GErrorPtr err;
2121                 auto uri = cwd.uri();
2122                 GList* uris = g_list_prepend(nullptr, uri.get());
2123 
2124                 if(!g_app_info_launch_uris(appInfo.get(), uris, nullptr, &err)) {
2125                     QMessageBox::critical(this, tr("Error"), QString::fromUtf8(err->message));
2126                 }
2127 
2128                 g_list_free(uris);
2129             }
2130         }
2131         else {
2132             // show an error message and ask the user to set the command
2133             QMessageBox::critical(this, tr("Error"), tr("Switch user command is not set."));
2134             app->preferences(QStringLiteral("advanced"));
2135         }
2136     }
2137 }
2138 
on_actionFindFiles_triggered()2139 void MainWindow::on_actionFindFiles_triggered() {
2140     Application* app = static_cast<Application*>(qApp);
2141     const auto files = currentPage()->selectedFiles();
2142     QStringList paths;
2143     if(!files.empty()) {
2144         for(const auto& file: files) {
2145             // FIXME: is it ok to use display name here?
2146             // This might be broken on filesystems with non-UTF-8 filenames.
2147             if(file->isDir()) {
2148                 paths.append(QString::fromUtf8(file->path().displayName().get()));
2149             }
2150         }
2151     }
2152     if(paths.isEmpty()) {
2153         paths.append(currentPage()->pathName());
2154     }
2155     app->findFiles(paths);
2156 }
2157 
on_actionOpenTerminal_triggered()2158 void MainWindow::on_actionOpenTerminal_triggered() {
2159     TabPage* page = currentPage();
2160     if(page) {
2161         Application* app = static_cast<Application*>(qApp);
2162         app->openFolderInTerminal(page->path());
2163     }
2164 }
2165 
on_actionCreateLauncher_triggered()2166 void MainWindow::on_actionCreateLauncher_triggered() {
2167     TabPage* page = currentPage();
2168     if(page) {
2169         page->ceateShortcut();
2170     }
2171 }
2172 
on_actionCopyFullPath_triggered()2173 void MainWindow::on_actionCopyFullPath_triggered() {
2174     TabPage* page = currentPage();
2175     if(page) {
2176         auto paths = page->selectedFilePaths();
2177         if(paths.size() == 1) {
2178             QApplication::clipboard()->setText(QString::fromUtf8(paths.front().toString().get()), QClipboard::Clipboard);
2179         }
2180     }
2181 }
2182 
onShortcutNextTab()2183 void MainWindow::onShortcutNextTab() {
2184     int current = activeViewFrame_->getTabBar()->currentIndex();
2185     if(current < activeViewFrame_->getTabBar()->count() - 1) {
2186         activeViewFrame_->getTabBar()->setCurrentIndex(current + 1);
2187     }
2188     else {
2189         activeViewFrame_->getTabBar()->setCurrentIndex(0);
2190     }
2191 }
2192 
onShortcutPrevTab()2193 void MainWindow::onShortcutPrevTab() {
2194     int current = activeViewFrame_->getTabBar()->currentIndex();
2195     if(current > 0) {
2196         activeViewFrame_->getTabBar()->setCurrentIndex(current - 1);
2197     }
2198     else {
2199         activeViewFrame_->getTabBar()->setCurrentIndex(activeViewFrame_->getTabBar()->count() - 1);
2200     }
2201 }
2202 
2203 // Switch to nth tab when Alt+n or Ctrl+n is pressed
onShortcutJumpToTab()2204 void MainWindow::onShortcutJumpToTab() {
2205     QShortcut* shortcut = reinterpret_cast<QShortcut*>(sender());
2206     QKeySequence seq = shortcut->key();
2207     int keyValue = seq[0];
2208     // See the source code of QKeySequence and refer to the method:
2209     // QString QKeySequencePrivate::encodeString(int key, QKeySequence::SequenceFormat format).
2210     // Then we know how to test if a key sequence contains a modifier.
2211     // It's a shame that Qt has no API for this task.
2212 
2213     if((keyValue & Qt::ALT) == Qt::ALT) { // test if we have Alt key pressed
2214         keyValue -= Qt::ALT;
2215     }
2216     else if((keyValue & Qt::CTRL) == Qt::CTRL) { // test if we have Ctrl key pressed
2217         keyValue -= Qt::CTRL;
2218     }
2219 
2220     // now keyValue should contains '0' - '9' only
2221     int index;
2222     if(keyValue == '0') {
2223         index = 9;
2224     }
2225     else {
2226         index = keyValue - '1';
2227     }
2228     if(index < activeViewFrame_->getTabBar()->count()) {
2229         activeViewFrame_->getTabBar()->setCurrentIndex(index);
2230     }
2231 }
2232 
on_actionCleanPerFolderConfig_triggered()2233 void MainWindow::on_actionCleanPerFolderConfig_triggered() {
2234     QMessageBox::StandardButton r = QMessageBox::question(this,
2235                                     tr("Cleaning Folder Settings"),
2236                                     tr("Do you want to remove settings of nonexistent folders?\nThey might be useful if those folders are created again."),
2237                                     QMessageBox::Yes | QMessageBox::No,
2238                                     QMessageBox::No);
2239     if(r == QMessageBox::Yes) {
2240         Application* app = static_cast<Application*>(qApp);
2241         app->cleanPerFolderConfig();
2242     }
2243 }
2244 
2245 }
2246