/* Copyright (C) 2013 Hong Jen Yee (PCMan) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tabpage.h" #include "launcher.h" #include #include #include #include #include #include #include #include #include #include "ui_about.h" #include "application.h" #include "bulkrename.h" using namespace Fm; namespace PCManFM { ViewFrame::ViewFrame(QWidget* parent): QFrame(parent), topBar_(nullptr) { QVBoxLayout* vBox = new QVBoxLayout; vBox->setContentsMargins(0, 0, 0, 0); tabBar_ = new TabBar; tabBar_->setFocusPolicy(Qt::NoFocus); stackedWidget_ = new QStackedWidget; vBox->addWidget(tabBar_); vBox->addWidget(stackedWidget_, 1); setLayout(vBox); // tabbed browsing interface tabBar_->setDocumentMode(true); tabBar_->setElideMode(Qt::ElideRight); tabBar_->setExpanding(false); tabBar_->setMovable(true); // reorder the tabs by dragging #if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0) // switch to the tab under the cursor during dnd. tabBar_->setChangeCurrentOnDrag(true); tabBar_->setAcceptDrops(true); #endif tabBar_->setContextMenuPolicy(Qt::CustomContextMenu); } void ViewFrame::createTopBar(bool usePathButtons) { if(QVBoxLayout* vBox = qobject_cast(layout())) { if(usePathButtons) { if (qobject_cast(topBar_)) { delete topBar_; topBar_ = nullptr; } if(topBar_ == nullptr) { topBar_ = new Fm::PathBar(); vBox->insertWidget(0, topBar_); } } else { if(qobject_cast(topBar_)) { delete topBar_; topBar_ = nullptr; } if(topBar_ == nullptr) { topBar_ = new Fm::PathEdit(); vBox->insertWidget(0, topBar_); } } } } void ViewFrame::removeTopBar() { if(topBar_ != nullptr) { if(QVBoxLayout* vBox = qobject_cast(layout())) { vBox->removeWidget(topBar_); delete topBar_; topBar_ = nullptr; } } } //====================================================================== // static MainWindow* MainWindow::lastActive_ = nullptr; MainWindow::MainWindow(Fm::FilePath path): QMainWindow(), pathEntry_(nullptr), pathBar_(nullptr), bookmarks_{Fm::Bookmarks::globalInstance()}, fileLauncher_(this), rightClickIndex_(-1), updatingViewMenu_(false), menuSpacer_(nullptr), activeViewFrame_(nullptr) { Settings& settings = static_cast(qApp)->settings(); setAttribute(Qt::WA_DeleteOnClose); // setup user interface ui.setupUi(this); // add a warning label to the root instance if(geteuid() == 0) { QLabel *warningLabel = new QLabel(tr("Root Instance")); warningLabel->setAlignment(Qt::AlignCenter); warningLabel->setTextInteractionFlags(Qt::NoTextInteraction); warningLabel->setStyleSheet(QLatin1String("QLabel {background-color: #7d0000; color: white; font-weight:bold; border-radius: 3px; margin: 2px; padding: 5px;}")); ui.verticalLayout->addWidget(warningLabel); ui.verticalLayout->setStretch(0, 1); } splitView_ = settings.splitView(); // hide menu items that are not usable //if(!uriExists("computer:///")) // ui.actionComputer->setVisible(false); if(!settings.supportTrash()) { ui.actionTrash->setVisible(false); } // add a context menu for showing browse history to back and forward buttons QToolButton* forwardButton = static_cast(ui.toolBar->widgetForAction(ui.actionGoForward)); forwardButton->setContextMenuPolicy(Qt::CustomContextMenu); connect(forwardButton, &QToolButton::customContextMenuRequested, this, &MainWindow::onBackForwardContextMenu); QToolButton* backButton = static_cast(ui.toolBar->widgetForAction(ui.actionGoBack)); backButton->setContextMenuPolicy(Qt::CustomContextMenu); connect(backButton, &QToolButton::customContextMenuRequested, this, &MainWindow::onBackForwardContextMenu); connect(ui.actionCloseRight, &QAction::triggered, this, &MainWindow::closeRightTabs); connect(ui.actionCloseLeft, &QAction::triggered, this, &MainWindow::closeLeftTabs); connect(ui.actionCloseOther, &QAction::triggered, this, &MainWindow::closeOtherTabs); ui.actionFilter->setChecked(settings.showFilter()); // side pane ui.sidePane->setIconSize(QSize(settings.sidePaneIconSize(), settings.sidePaneIconSize())); ui.sidePane->setMode(settings.sidePaneMode()); ui.sidePane->restoreHiddenPlaces(settings.getHiddenPlaces()); connect(ui.sidePane, &Fm::SidePane::chdirRequested, this, &MainWindow::onSidePaneChdirRequested); connect(ui.sidePane, &Fm::SidePane::openFolderInNewWindowRequested, this, &MainWindow::onSidePaneOpenFolderInNewWindowRequested); connect(ui.sidePane, &Fm::SidePane::openFolderInNewTabRequested, this, &MainWindow::onSidePaneOpenFolderInNewTabRequested); connect(ui.sidePane, &Fm::SidePane::openFolderInTerminalRequested, this, &MainWindow::onSidePaneOpenFolderInTerminalRequested); connect(ui.sidePane, &Fm::SidePane::createNewFolderRequested, this, &MainWindow::onSidePaneCreateNewFolderRequested); connect(ui.sidePane, &Fm::SidePane::modeChanged, this, &MainWindow::onSidePaneModeChanged); connect(ui.sidePane, &Fm::SidePane::hiddenPlaceSet, this, &MainWindow::onSettingHiddenPlace); // detect change of splitter position connect(ui.splitter, &QSplitter::splitterMoved, this, &MainWindow::onSplitterMoved); // add filesystem info to status bar fsInfoLabel_ = new QLabel(ui.statusbar); ui.statusbar->addPermanentWidget(fsInfoLabel_); // setup the splitter ui.splitter->setStretchFactor(1, 1); // only the right pane can be stretched QList sizes; sizes.append(settings.splitterPos()); sizes.append(300); ui.splitter->setSizes(sizes); // load bookmark menu connect(bookmarks_.get(), &Fm::Bookmarks::changed, this, &MainWindow::onBookmarksChanged); loadBookmarksMenu(); // use generic icons for view actions only if theme icons don't exist ui.actionIconView->setIcon(QIcon::fromTheme(QLatin1String("view-list-icons"), style()->standardIcon(QStyle::SP_FileDialogContentsView))); ui.actionThumbnailView->setIcon(QIcon::fromTheme(QLatin1String("dialog-information"), style()->standardIcon(QStyle::SP_FileDialogInfoView))); ui.actionCompactView->setIcon(QIcon::fromTheme(QLatin1String("view-list-text"), style()->standardIcon(QStyle::SP_FileDialogListView))); ui.actionDetailedList->setIcon(QIcon::fromTheme(QLatin1String("view-list-details"), style()->standardIcon(QStyle::SP_FileDialogDetailedView))); // Fix the menu groups which is not done by Qt designer // To my suprise, this was supported in Qt designer 3 :-( QActionGroup* group = new QActionGroup(ui.menu_View); group->setExclusive(true); group->addAction(ui.actionIconView); group->addAction(ui.actionCompactView); group->addAction(ui.actionThumbnailView); group->addAction(ui.actionDetailedList); group = new QActionGroup(ui.menuSorting); group->setExclusive(true); group->addAction(ui.actionByFileName); group->addAction(ui.actionByMTime); group->addAction(ui.actionByFileSize); group->addAction(ui.actionByFileType); group->addAction(ui.actionByOwner); group->addAction(ui.actionByGroup); group = new QActionGroup(ui.menuSorting); group->setExclusive(true); group->addAction(ui.actionAscending); group->addAction(ui.actionDescending); group = new QActionGroup(ui.menuPathBarStyle); group->setExclusive(true); group->addAction(ui.actionLocationBar); group->addAction(ui.actionPathButtons); // Add menubar actions to the main window this is necessary so that actions // shortcuts are still working when the menubar is hidden. addActions(ui.menubar->actions()); // Show or hide the menu bar QMenu* menu = new QMenu(ui.toolBar); menu->addMenu(ui.menu_File); menu->addMenu(ui.menu_Edit); menu->addMenu(ui.menu_View); menu->addMenu(ui.menu_Go); menu->addMenu(ui.menu_Bookmarks); menu->addMenu(ui.menu_Tool); menu->addMenu(ui.menu_Help); ui.actionMenu->setMenu(menu); if(ui.actionMenu->icon().isNull()) { ui.actionMenu->setIcon(QIcon::fromTheme("applications-system")); } QToolButton* menuBtn = static_cast(ui.toolBar->widgetForAction(ui.actionMenu)); menuBtn->setPopupMode(QToolButton::InstantPopup); menuSep_ = ui.toolBar->insertSeparator(ui.actionMenu); menuSep_->setVisible(!settings.showMenuBar() && !splitView_); ui.actionMenu->setVisible(!settings.showMenuBar()); ui.menubar->setVisible(settings.showMenuBar()); ui.actionMenu_bar->setChecked(settings.showMenuBar()); connect(ui.actionMenu_bar, &QAction::triggered, this, &MainWindow::toggleMenuBar); // create shortcuts QShortcut* shortcut; shortcut = new QShortcut(QKeySequence(Qt::Key_Escape), this); connect(shortcut, &QShortcut::activated, this, &MainWindow::onResetFocus); shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_L), this); connect(shortcut, &QShortcut::activated, this, &MainWindow::focusPathEntry); shortcut = new QShortcut(Qt::ALT + Qt::Key_D, this); connect(shortcut, &QShortcut::activated, this, &MainWindow::focusPathEntry); shortcut = new QShortcut(Qt::CTRL + Qt::Key_Tab, this); connect(shortcut, &QShortcut::activated, this, &MainWindow::onShortcutNextTab); shortcut = new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Tab, this); connect(shortcut, &QShortcut::activated, this, &MainWindow::onShortcutPrevTab); // Add Ctrl+PgUp and Ctrl+PgDown as well, because they are common in Firefox // , Opera, Google Chromium/Google Chrome and most other tab-using // applications. shortcut = new QShortcut(Qt::CTRL + Qt::Key_PageDown, this); connect(shortcut, &QShortcut::activated, this, &MainWindow::onShortcutNextTab); shortcut = new QShortcut(Qt::CTRL + Qt::Key_PageUp, this); connect(shortcut, &QShortcut::activated, this, &MainWindow::onShortcutPrevTab); int i; for(i = 0; i < 10; ++i) { shortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::Key_0 + i), this); connect(shortcut, &QShortcut::activated, this, &MainWindow::onShortcutJumpToTab); shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_0 + i), this); connect(shortcut, &QShortcut::activated, this, &MainWindow::onShortcutJumpToTab); } shortcut = new QShortcut(QKeySequence(Qt::Key_Backspace), this); connect(shortcut, &QShortcut::activated, [this, &settings] { // pass Backspace to current page if it has a visible, transient filter-bar if(!settings.showFilter() && currentPage() && currentPage()->isFilterBarVisible()) { currentPage()->backspacePressed(); return; } on_actionGoUp_triggered(); }); shortcut = new QShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Delete), this); connect(shortcut, &QShortcut::activated, this, &MainWindow::on_actionDelete_triggered); // in addition to F3, for convenience shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F), this); connect(shortcut, &QShortcut::activated, ui.actionFindFiles, &QAction::trigger); addViewFrame(path); if(splitView_) { // put the menu button on the right (there's no path bar/entry on the toolbar) QWidget* w = new QWidget(this); w->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); menuSpacer_ = ui.toolBar->insertWidget(ui.actionMenu, w); ui.actionSplitView->setChecked(true); addViewFrame(path); qApp->removeEventFilter(this); // precaution qApp->installEventFilter(this); } else { ui.actionSplitView->setChecked(false); setAcceptDrops(true); // we want tab dnd in the simple mode } createPathBar(settings.pathBarButtons()); if(settings.pathBarButtons()) { ui.actionPathButtons->setChecked(true); } else { ui.actionLocationBar->setChecked(true); } // size from settings resize(settings.windowWidth(), settings.windowHeight()); if(settings.rememberWindowSize() && settings.windowMaximized()) { setWindowState(windowState() | Qt::WindowMaximized); } if(QApplication::layoutDirection() == Qt::RightToLeft) { setRTLIcons(true); } } MainWindow::~MainWindow() { } // Activate a view frame appropriately and give a special style to the inactive one(s). // NOTE: This function is called only with the split mode. bool MainWindow::eventFilter(QObject* watched, QEvent* event) { if(qobject_cast(watched)) { if(event->type() == QEvent::FocusIn // the event has happened inside the splitter && ui.viewSplitter->isAncestorOf(qobject_cast(watched))) { for(int i = 0; i < ui.viewSplitter->count(); ++i) { if(ViewFrame* viewFrame = qobject_cast(ui.viewSplitter->widget(i))) { if(viewFrame->isAncestorOf(qobject_cast(watched))) { // a widget inside this view frame has gained focus; ensure the view is active if(activeViewFrame_ != viewFrame) { activeViewFrame_ = viewFrame; updateUIForCurrentPage(false); // WARNING: never set focus here! } if(viewFrame->palette().color(QPalette::Base) != qApp->palette().color(QPalette::Base)) { viewFrame->setPalette(qApp->palette()); // restore the main palette } } else if (viewFrame->palette().color(QPalette::Base) == qApp->palette().color(QPalette::Base)) { // Change the text and base palettes of an inactive view frame a little. // NOTE: Style-sheets aren't used because they can interfere with QStyle. QPalette palette = viewFrame->palette(); QColor txtCol = palette.color(QPalette::Text); txtCol.setAlphaF(txtCol.alphaF() * 0.7); palette.setColor(QPalette::Text, txtCol); palette.setColor(QPalette::WindowText, txtCol); // tabs // the disabled text color of path-bars shouldn't change because it may be used by arrows palette.setColor(QPalette::Active, QPalette::ButtonText, txtCol); palette.setColor(QPalette::Inactive, QPalette::ButtonText, txtCol); // There are various ways of getting a distinct color near the base color // but this one gives the best results with almost all palettes: QColor baseCol = palette.color(QPalette::Base); baseCol.setRgbF(0.9 * baseCol.redF() + 0.1 * txtCol.redF(), 0.9 * baseCol.greenF() + 0.1 * txtCol.greenF(), 0.9 * baseCol.blueF() + 0.1 * txtCol.blueF(), baseCol.alphaF()); palette.setColor(QPalette::Base, baseCol); viewFrame->setPalette(palette); } } } } // Use the Tab key for switching between view frames else if (event->type() == QEvent::KeyPress) { if(QKeyEvent *ke = static_cast(event)) { if(ke->key() == Qt::Key_Tab && ke->modifiers() == Qt::NoModifier) { if(!qobject_cast(watched) // not during inline renaming && ui.viewSplitter->isAncestorOf(qobject_cast(watched))) { // wrap the focus for(int i = 0; i < ui.viewSplitter->count(); ++i) { if(ViewFrame* viewFrame = qobject_cast(ui.viewSplitter->widget(i))) { if(activeViewFrame_ == viewFrame) { int n = i < ui.viewSplitter->count() - 1 ? i + 1 : 0; activeViewFrame_ = qobject_cast(ui.viewSplitter->widget(n)); updateUIForCurrentPage(); // focuses the view and calls this function again return true; } } } } } } } } return QMainWindow::eventFilter(watched, event); } void MainWindow::addViewFrame(const Fm::FilePath& path) { ui.actionGo->setVisible(false); Settings& settings = static_cast(qApp)->settings(); ViewFrame* viewFrame = new ViewFrame(); viewFrame->getTabBar()->setDetachable(!splitView_); // no tab DND with the split view viewFrame->getTabBar()->setTabsClosable(settings.showTabClose()); ui.viewSplitter->addWidget(viewFrame); // the splitter takes ownership of viewFrame if(ui.viewSplitter->count() == 1) { activeViewFrame_ = viewFrame; } else { // give equal widths to all view frames QTimer::singleShot(0, this, [this] { QList sizes; for(int i = 0; i < ui.viewSplitter->count(); ++i) { sizes << ui.viewSplitter->width() / ui.viewSplitter->count(); } ui.viewSplitter->setSizes(sizes); }); } connect(viewFrame->getTabBar(), &QTabBar::currentChanged, this, &MainWindow::onTabBarCurrentChanged); connect(viewFrame->getTabBar(), &QTabBar::tabCloseRequested, this, &MainWindow::onTabBarCloseRequested); connect(viewFrame->getTabBar(), &QTabBar::tabMoved, this, &MainWindow::onTabBarTabMoved); connect(viewFrame->getTabBar(), &QTabBar::tabBarClicked, this, &MainWindow::onTabBarClicked); connect(viewFrame->getTabBar(), &QTabBar::customContextMenuRequested, this, &MainWindow::tabContextMenu); connect(viewFrame->getTabBar(), &TabBar::tabDetached, this, &MainWindow::detachTab); connect(viewFrame->getStackedWidget(), &QStackedWidget::widgetRemoved, this, &MainWindow::onStackedWidgetWidgetRemoved); if(path) { addTab(path, viewFrame); } } void MainWindow::on_actionSplitView_triggered(bool checked) { if(splitView_ == checked) { return; } Settings& settings = static_cast(qApp)->settings(); splitView_ = checked; settings.setSplitView(splitView_); if(splitView_) { // split the view // remove the path bar/entry from the toolbar ui.actionGo->setVisible(false); menuSep_->setVisible(false); if(pathBar_ != nullptr) { delete pathBar_; pathBar_ = nullptr; } else if(pathEntry_ != nullptr) { delete pathEntry_; pathEntry_ = nullptr; } // add a spacer before the menu action if not exisitng if(menuSpacer_ == nullptr) { QWidget* w = new QWidget(this); w->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); menuSpacer_ = ui.toolBar->insertWidget(ui.actionMenu, w); } menuSpacer_->setVisible(true); // disable tab DND activeViewFrame_->getTabBar()->setDetachable(false); setAcceptDrops(false); // add the current path to a new view frame Fm::FilePath path; TabPage* page = currentPage(); if(page) { path = page->path(); } addViewFrame(path); qApp->removeEventFilter(this); // precaution qApp->installEventFilter(this); createPathBar(settings.pathBarButtons()); // reset the focus for the inactive view frame(s) to be styled by MainWindow::eventFilter() if(page) { page->folderView()->childView()->clearFocus(); page->folderView()->childView()->setFocus(); } } else { // remove splitting menuSep_->setVisible(!settings.showMenuBar()); qApp->removeEventFilter(this); for(int i = 0; i < ui.viewSplitter->count(); ++i) { if(ViewFrame* viewFrame = qobject_cast(ui.viewSplitter->widget(i))) { if(viewFrame != activeViewFrame_) { viewFrame->deleteLater(); // this may be called by onStackedWidgetWidgetRemoved() } } } // enable tab DND activeViewFrame_->getTabBar()->setDetachable(true); setAcceptDrops(true); activeViewFrame_->removeTopBar(); if(menuSpacer_ != nullptr) { menuSpacer_->setVisible(false); } createPathBar(settings.pathBarButtons()); } } ViewFrame* MainWindow::viewFrameForTabPage(TabPage* page) { if(page) { if(QStackedWidget* sw = qobject_cast(page->parentWidget())) { if(ViewFrame* viewFrame = qobject_cast(sw->parentWidget())) { return viewFrame; } } } return nullptr; } void MainWindow::chdir(Fm::FilePath path, ViewFrame* viewFrame) { // wait until queued events are processed QTimer::singleShot(0, viewFrame, [this, path, viewFrame] { if(TabPage* page = currentPage(viewFrame)) { page->chdir(path, true); if(viewFrame == activeViewFrame_) { updateUIForCurrentPage(); } else { if(Fm::PathBar* pathBar = qobject_cast(viewFrame->getTopBar())) { pathBar->setPath(page->path()); } else if(Fm::PathEdit* pathEntry = qobject_cast(viewFrame->getTopBar())) { pathEntry->setText(page->pathName()); } } } }); } void MainWindow::createPathBar(bool usePathButtons) { // NOTE: Path bars/entries may be created after tab pages; so, their paths/texts should be set. if(splitView_) { for(int i = 0; i < ui.viewSplitter->count(); ++i) { if(ViewFrame* viewFrame = qobject_cast(ui.viewSplitter->widget(i))) { viewFrame->createTopBar(usePathButtons); TabPage* curPage = currentPage(viewFrame); if(Fm::PathBar* pathBar = qobject_cast(viewFrame->getTopBar())) { connect(pathBar, &Fm::PathBar::chdir, this, &MainWindow::onPathBarChdir); connect(pathBar, &Fm::PathBar::middleClickChdir, this, &MainWindow::onPathBarMiddleClickChdir); connect(pathBar, &Fm::PathBar::editingFinished, this, &MainWindow::onResetFocus); if(curPage) { pathBar->setPath(curPage->path()); } } else if(Fm::PathEdit* pathEntry = qobject_cast(viewFrame->getTopBar())) { connect(pathEntry, &Fm::PathEdit::returnPressed, this, &MainWindow::onPathEntryReturnPressed); if(curPage) { pathEntry->setText(curPage->pathName()); } } } } } else { QWidget* bar = nullptr; TabPage* curPage = currentPage(); if(usePathButtons) { if(pathEntry_ != nullptr) { delete pathEntry_; pathEntry_ = nullptr; } if(pathBar_ == nullptr) { bar = pathBar_ = new Fm::PathBar(this); connect(pathBar_, &Fm::PathBar::chdir, this, &MainWindow::onPathBarChdir); connect(pathBar_, &Fm::PathBar::middleClickChdir, this, &MainWindow::onPathBarMiddleClickChdir); connect(pathBar_, &Fm::PathBar::editingFinished, this, &MainWindow::onResetFocus); if(curPage) { pathBar_->setPath(currentPage()->path()); } } } else { if(pathBar_ != nullptr) { delete pathBar_; pathBar_ = nullptr; } if(pathEntry_ == nullptr) { bar = pathEntry_ = new Fm::PathEdit(this); connect(pathEntry_, &Fm::PathEdit::returnPressed, this, &MainWindow::onPathEntryReturnPressed); if(curPage) { pathEntry_->setText(curPage->pathName()); } } } if(bar != nullptr) { ui.toolBar->insertWidget(ui.actionGo, bar); ui.actionGo->setVisible(!usePathButtons); } } } int MainWindow::addTabWithPage(TabPage* page, ViewFrame* viewFrame, Fm::FilePath path) { if(page == nullptr || viewFrame == nullptr) { return -1; } page->setFileLauncher(&fileLauncher_); int index = viewFrame->getStackedWidget()->addWidget(page); connect(page, &TabPage::titleChanged, this, &MainWindow::onTabPageTitleChanged); connect(page, &TabPage::statusChanged, this, &MainWindow::onTabPageStatusChanged); connect(page, &TabPage::openDirRequested, this, &MainWindow::onTabPageOpenDirRequested); connect(page, &TabPage::sortFilterChanged, this, &MainWindow::onTabPageSortFilterChanged); connect(page, &TabPage::backwardRequested, this, &MainWindow::on_actionGoBack_triggered); connect(page, &TabPage::forwardRequested, this, &MainWindow::on_actionGoForward_triggered); connect(page, &TabPage::folderUnmounted, this, &MainWindow::onFolderUnmounted); if(path) { page->chdir(path, true); } viewFrame->getTabBar()->insertTab(index, page->windowTitle()); Settings& settings = static_cast(qApp)->settings(); if(!settings.alwaysShowTabs()) { viewFrame->getTabBar()->setVisible(viewFrame->getTabBar()->count() > 1); } return index; } // add a new tab int MainWindow::addTab(Fm::FilePath path, ViewFrame* viewFrame) { TabPage* newPage = new TabPage(this); return addTabWithPage(newPage, viewFrame, path); } void MainWindow::toggleMenuBar(bool /*checked*/) { Settings& settings = static_cast(qApp)->settings(); bool showMenuBar = !settings.showMenuBar(); if(!showMenuBar) { if(QMessageBox::Cancel == QMessageBox::warning(this, tr("Hide menu bar"), tr("This will hide the menu bar completely, use Ctrl+M to show it again."), QMessageBox::Ok | QMessageBox::Cancel)) { ui.actionMenu_bar->setChecked(true); return; } } ui.menubar->setVisible(showMenuBar); ui.actionMenu_bar->setChecked(showMenuBar); menuSep_->setVisible(!showMenuBar); ui.actionMenu->setVisible(!showMenuBar); settings.setShowMenuBar(showMenuBar); } void MainWindow::onPathEntryReturnPressed() { Fm::PathEdit* pathEntry = pathEntry_; if(pathEntry == nullptr) { pathEntry = static_cast(sender()); } if(pathEntry != nullptr) { QString text = pathEntry->text(); QByteArray utext = text.toLocal8Bit(); chdir(Fm::FilePath::fromPathStr(utext.constData())); } } void MainWindow::onPathBarChdir(const Fm::FilePath& dirPath) { TabPage* page = nullptr; ViewFrame* viewFrame = nullptr; if(pathBar_ != nullptr) { page = currentPage(); viewFrame = activeViewFrame_; } else { Fm::PathBar* pathBar = static_cast(sender()); viewFrame = qobject_cast(pathBar->parentWidget()); if(viewFrame != nullptr) { page = currentPage(viewFrame); } } if(page && dirPath != page->path()) { chdir(dirPath, viewFrame); } } void MainWindow::onPathBarMiddleClickChdir(const Fm::FilePath& dirPath) { ViewFrame* viewFrame = nullptr; if(pathBar_ != nullptr) { viewFrame = activeViewFrame_; } else { Fm::PathBar* pathBar = static_cast(sender()); viewFrame = qobject_cast(pathBar->parentWidget()); } if(viewFrame) { addTab(dirPath, viewFrame); } } void MainWindow::on_actionGoUp_triggered() { QTimer::singleShot(0, this, [this] { if(TabPage* page = currentPage()) { page->up(); updateUIForCurrentPage(); } }); } void MainWindow::on_actionGoBack_triggered() { QTimer::singleShot(0, this, [this] { if(TabPage* page = currentPage()) { page->backward(); updateUIForCurrentPage(); } }); } void MainWindow::on_actionGoForward_triggered() { QTimer::singleShot(0, this, [this] { if(TabPage* page = currentPage()) { page->forward(); updateUIForCurrentPage(); } }); } void MainWindow::on_actionHome_triggered() { chdir(Fm::FilePath::homeDir()); } void MainWindow::on_actionReload_triggered() { currentPage()->reload(); if(pathEntry_ != nullptr) { pathEntry_->setText(currentPage()->pathName()); } } void MainWindow::on_actionConnectToServer_triggered() { Application* app = static_cast(qApp); app->connectToServer(); } void MainWindow::on_actionGo_triggered() { onPathEntryReturnPressed(); } void MainWindow::on_actionNewTab_triggered() { auto path = currentPage()->path(); int index = addTab(path); activeViewFrame_->getTabBar()->setCurrentIndex(index); } void MainWindow::on_actionNewWin_triggered() { auto path = currentPage()->path(); (new MainWindow(path))->show(); } void MainWindow::on_actionNewFolder_triggered() { if(TabPage* tabPage = currentPage()) { auto dirPath = tabPage->folderView()->path(); if(dirPath) { createFileOrFolder(CreateNewFolder, dirPath, nullptr, this); } } } void MainWindow::on_actionNewBlankFile_triggered() { if(TabPage* tabPage = currentPage()) { auto dirPath = tabPage->folderView()->path(); if(dirPath) { createFileOrFolder(CreateNewTextFile, dirPath, nullptr, this); } } } void MainWindow::on_actionCloseTab_triggered() { closeTab(activeViewFrame_->getTabBar()->currentIndex()); } void MainWindow::on_actionCloseWindow_triggered() { // FIXME: should we save state here? close(); // the window will be deleted automatically on close } void MainWindow::on_actionFileProperties_triggered() { TabPage* page = currentPage(); if(page) { auto files = page->selectedFiles(); if(!files.empty()) { Fm::FilePropsDialog::showForFiles(files); } } } void MainWindow::on_actionFolderProperties_triggered() { TabPage* page = currentPage(); if(page) { auto folder = page->folder(); if(folder) { auto info = folder->info(); if(info) { Fm::FilePropsDialog::showForFile(info); } } } } void MainWindow::on_actionShowHidden_triggered(bool checked) { currentPage()->setShowHidden(checked); ui.sidePane->setShowHidden(checked); } void MainWindow::on_actionByFileName_triggered(bool /*checked*/) { currentPage()->sort(Fm::FolderModel::ColumnFileName, currentPage()->sortOrder()); } void MainWindow::on_actionByMTime_triggered(bool /*checked*/) { currentPage()->sort(Fm::FolderModel::ColumnFileMTime, currentPage()->sortOrder()); } void MainWindow::on_actionByOwner_triggered(bool /*checked*/) { currentPage()->sort(Fm::FolderModel::ColumnFileOwner, currentPage()->sortOrder()); } void MainWindow::on_actionByGroup_triggered(bool /*checked*/) { currentPage()->sort(Fm::FolderModel::ColumnFileGroup, currentPage()->sortOrder()); } void MainWindow::on_actionByFileSize_triggered(bool /*checked*/) { currentPage()->sort(Fm::FolderModel::ColumnFileSize, currentPage()->sortOrder()); } void MainWindow::on_actionByFileType_triggered(bool /*checked*/) { currentPage()->sort(Fm::FolderModel::ColumnFileType, currentPage()->sortOrder()); } void MainWindow::on_actionAscending_triggered(bool /*checked*/) { currentPage()->sort(currentPage()->sortColumn(), Qt::AscendingOrder); } void MainWindow::on_actionDescending_triggered(bool /*checked*/) { currentPage()->sort(currentPage()->sortColumn(), Qt::DescendingOrder); } void MainWindow::on_actionCaseSensitive_triggered(bool checked) { currentPage()->setSortCaseSensitive(checked); } void MainWindow::on_actionFolderFirst_triggered(bool checked) { currentPage()->setSortFolderFirst(checked); } void MainWindow::on_actionPreserveView_triggered(bool /*checked*/) { TabPage* page = currentPage(); page->setCustomizedView(!page->hasCustomizedView()); } void MainWindow::on_actionFilter_triggered(bool checked) { static_cast(qApp)->settings().setShowFilter(checked); // show/hide filter-bars and disable/enable their transience for all tabs // (of all view frames) in all windows because this is a global setting QWidgetList windows = static_cast(qApp)->topLevelWidgets(); QWidgetList::iterator it; for(it = windows.begin(); it != windows.end(); ++it) { QWidget* window = *it; if(window->inherits("PCManFM::MainWindow")) { MainWindow* mainWindow = static_cast(window); mainWindow->ui.actionFilter->setChecked(checked); // doesn't call this function for(int i = 0; i < mainWindow->ui.viewSplitter->count(); ++i) { if(ViewFrame* viewFrame = qobject_cast(mainWindow->ui.viewSplitter->widget(i))) { int n = viewFrame->getStackedWidget()->count(); for(int j = 0; j < n; ++j) { if(TabPage* page = static_cast(viewFrame->getStackedWidget()->widget(j))) { page->transientFilterBar(!checked); } } } } } } } void MainWindow::on_actionUnfilter_triggered() { // clear filters for all tabs (of all view frames) for(int i = 0; i < ui.viewSplitter->count(); ++i) { if(ViewFrame* viewFrame = qobject_cast(ui.viewSplitter->widget(i))) { int n = viewFrame->getStackedWidget()->count(); for(int j = 0; j < n; ++j) { if(TabPage* page = static_cast(viewFrame->getStackedWidget()->widget(j))) { page->clearFilter(); } } } } } void MainWindow::on_actionShowFilter_triggered() { if(TabPage* page = currentPage()) { page->showFilterBar(); } } void MainWindow::on_actionLocationBar_triggered(bool checked) { if(checked) { // show current path in a location bar entry createPathBar(false); static_cast(qApp)->settings().setPathBarButtons(false); } } void MainWindow::on_actionPathButtons_triggered(bool checked) { if(checked) { // show current path as buttons createPathBar(true); static_cast(qApp)->settings().setPathBarButtons(true); } } void MainWindow::on_actionComputer_triggered() { chdir(Fm::FilePath::fromUri("computer:///")); } void MainWindow::on_actionApplications_triggered() { chdir(Fm::FilePath::fromUri("menu://applications/")); } void MainWindow::on_actionTrash_triggered() { chdir(Fm::FilePath::fromUri("trash:///")); } void MainWindow::on_actionNetwork_triggered() { chdir(Fm::FilePath::fromUri("network:///")); } void MainWindow::on_actionDesktop_triggered() { auto desktop = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toLocal8Bit(); chdir(Fm::FilePath::fromLocalPath(desktop.constData())); } void MainWindow::on_actionAddToBookmarks_triggered() { TabPage* page = currentPage(); if(page) { auto cwd = page->path(); if(cwd) { auto dispName = cwd.baseName(); bookmarks_->insert(cwd, dispName.get(), -1); } } } void MainWindow::on_actionEditBookmarks_triggered() { Application* app = static_cast(qApp); app->editBookmarks(); } void MainWindow::on_actionAbout_triggered() { // the about dialog class AboutDialog : public QDialog { public: explicit AboutDialog(QWidget* parent = 0, Qt::WindowFlags f = 0) : QDialog(parent, f) { ui.setupUi(this); ui.version->setText(tr("Version: %1").arg(PCMANFM_QT_VERSION)); } private: Ui::AboutDialog ui; }; AboutDialog dialog(this); dialog.exec(); } void MainWindow::on_actionIconView_triggered() { currentPage()->setViewMode(Fm::FolderView::IconMode); } void MainWindow::on_actionCompactView_triggered() { currentPage()->setViewMode(Fm::FolderView::CompactMode); } void MainWindow::on_actionDetailedList_triggered() { currentPage()->setViewMode(Fm::FolderView::DetailedListMode); } void MainWindow::on_actionThumbnailView_triggered() { currentPage()->setViewMode(Fm::FolderView::ThumbnailMode); } void MainWindow::onTabBarCloseRequested(int index) { TabBar* tabBar = static_cast(sender()); if(ViewFrame* viewFrame = qobject_cast(tabBar->parentWidget())) { closeTab(index, viewFrame); } } void MainWindow::onResetFocus() { if(TabPage* page = currentPage()) { page->folderView()->childView()->setFocus(); } } void MainWindow::onTabBarTabMoved(int from, int to) { TabBar* tabBar = static_cast(sender()); if(ViewFrame* viewFrame = qobject_cast(tabBar->parentWidget())) { // a tab in the tab bar is moved by the user, so we have to move the // corredponding tab page in the stacked widget to the new position, too. QWidget* page = viewFrame->getStackedWidget()->widget(from); if(page) { // we're not going to delete the tab page, so here we block signals // to avoid calling the slot onStackedWidgetWidgetRemoved() before // removing the page. Otherwise the page widget will be destroyed. viewFrame->getStackedWidget()->blockSignals(true); viewFrame->getStackedWidget()->removeWidget(page); viewFrame->getStackedWidget()->insertWidget(to, page); // insert the page to the new position viewFrame->getStackedWidget()->blockSignals(false); // unblock signals viewFrame->getStackedWidget()->setCurrentWidget(page); } } } void MainWindow::onFolderUnmounted() { TabPage* tabPage = static_cast(sender()); if(ViewFrame* viewFrame = viewFrameForTabPage(tabPage)) { const QList ops = ui.sidePane->findChildren(); if(ops.isEmpty()) { // unmounting is done somewhere else Settings& settings = static_cast(qApp)->settings(); if(settings.closeOnUnmount()) { viewFrame->getStackedWidget()->removeWidget(tabPage); // NOTE: Since Fm::Folder queues a folder reload after emitting the unmount signal, // pending events may be waiting to be delivered at this very moment. Therefore, // if the tab page is deleted immediately, a crash will be imminent for various reasons. tabPage->deleteLater(); } else { tabPage->chdir(Fm::FilePath::homeDir(), viewFrame); updateUIForCurrentPage(); } } else { // wait for all (un-)mount operations to be finished (otherwise, they might be cancelled) for(const MountOperation* op : ops) { connect(op, &QObject::destroyed, tabPage, [this, tabPage, viewFrame] { if(ui.sidePane->findChildren().isEmpty()) { Settings& settings = static_cast(qApp)->settings(); if(settings.closeOnUnmount()) { viewFrame->getStackedWidget()->removeWidget(tabPage); tabPage->deleteLater(); } else { tabPage->chdir(Fm::FilePath::homeDir(), viewFrame); updateUIForCurrentPage(); } } }); } } } } void MainWindow::closeTab(int index, ViewFrame* viewFrame) { QWidget* page = viewFrame->getStackedWidget()->widget(index); if(page) { viewFrame->getStackedWidget()->removeWidget(page); // this does not delete the page widget delete page; // NOTE: we do not remove the tab here. // it'll be done in onStackedWidgetWidgetRemoved() } } void MainWindow::resizeEvent(QResizeEvent* event) { QMainWindow::resizeEvent(event); Settings& settings = static_cast(qApp)->settings(); if(settings.rememberWindowSize()) { settings.setLastWindowMaximized(isMaximized()); if(!isMaximized()) { settings.setLastWindowWidth(width()); settings.setLastWindowHeight(height()); } } } void MainWindow::closeEvent(QCloseEvent* event) { if(lastActive_ == this) { lastActive_ = nullptr; } QWidget::closeEvent(event); Settings& settings = static_cast(qApp)->settings(); if(settings.rememberWindowSize()) { settings.setLastWindowMaximized(isMaximized()); if(!isMaximized()) { settings.setLastWindowWidth(width()); settings.setLastWindowHeight(height()); } } } void MainWindow::onTabBarCurrentChanged(int index) { TabBar* tabBar = static_cast(sender()); if(ViewFrame* viewFrame = qobject_cast(tabBar->parentWidget())) { viewFrame->getStackedWidget()->setCurrentIndex(index); if(viewFrame == activeViewFrame_) { updateUIForCurrentPage(); } else { if(TabPage* page = currentPage(viewFrame)) { if(Fm::PathBar* pathBar = qobject_cast(viewFrame->getTopBar())) { pathBar->setPath(page->path()); } else if(Fm::PathEdit* pathEntry = qobject_cast(viewFrame->getTopBar())) { pathEntry->setText(page->pathName()); } } } } } void MainWindow::updateStatusBarForCurrentPage() { TabPage* tabPage = currentPage(); QString text = tabPage->statusText(TabPage::StatusTextSelectedFiles); if(text.isEmpty()) { text = tabPage->statusText(TabPage::StatusTextNormal); } ui.statusbar->showMessage(text); text = tabPage->statusText(TabPage::StatusTextFSInfo); fsInfoLabel_->setText(text); fsInfoLabel_->setVisible(!text.isEmpty()); } void MainWindow::updateViewMenuForCurrentPage() { if(updatingViewMenu_) { // prevent recursive calls return; } updatingViewMenu_ = true; TabPage* tabPage = currentPage(); if(tabPage) { // update menus. FIXME: should we move this to another method? ui.actionShowHidden->setChecked(tabPage->showHidden()); ui.actionPreserveView->setChecked(tabPage->hasCustomizedView()); // view mode QAction* modeAction = nullptr; switch(tabPage->viewMode()) { case Fm::FolderView::IconMode: modeAction = ui.actionIconView; break; case Fm::FolderView::CompactMode: modeAction = ui.actionCompactView; break; case Fm::FolderView::DetailedListMode: modeAction = ui.actionDetailedList; break; case Fm::FolderView::ThumbnailMode: modeAction = ui.actionThumbnailView; break; } Q_ASSERT(modeAction != nullptr); modeAction->setChecked(true); // sort menu QAction* sortActions[Fm::FolderModel::NumOfColumns]; sortActions[Fm::FolderModel::ColumnFileName] = ui.actionByFileName; sortActions[Fm::FolderModel::ColumnFileMTime] = ui.actionByMTime; sortActions[Fm::FolderModel::ColumnFileSize] = ui.actionByFileSize; sortActions[Fm::FolderModel::ColumnFileType] = ui.actionByFileType; sortActions[Fm::FolderModel::ColumnFileOwner] = ui.actionByOwner; sortActions[Fm::FolderModel::ColumnFileGroup] = ui.actionByGroup; sortActions[tabPage->sortColumn()]->setChecked(true); if(tabPage->sortOrder() == Qt::AscendingOrder) { ui.actionAscending->setChecked(true); } else { ui.actionDescending->setChecked(true); } ui.actionCaseSensitive->setChecked(tabPage->sortCaseSensitive()); ui.actionFolderFirst->setChecked(tabPage->sortFolderFirst()); } updatingViewMenu_ = false; } // Update the enabled state of Edit actions for selected files void MainWindow::updateEditSelectedActions() { bool hasAccessible(false); bool hasDeletable(false); int renamable(0); if(TabPage* page = currentPage()) { auto files = page->selectedFiles(); for(auto& file: files) { if(file->isAccessible()) { hasAccessible = true; } if(file->isDeletable()) { hasDeletable = true; } if(file->canSetName()) { ++renamable; } if (hasAccessible && hasDeletable && renamable > 1) { break; } } ui.actionCopyFullPath->setEnabled(files.size() == 1); } ui.actionCopy->setEnabled(hasAccessible); ui.actionCut->setEnabled(hasDeletable); ui.actionDelete->setEnabled(hasDeletable); ui.actionRename->setEnabled(renamable > 0); ui.actionBulkRename->setEnabled(renamable > 1); } void MainWindow::updateUIForCurrentPage(bool setFocus) { TabPage* tabPage = currentPage(); if(tabPage) { setWindowTitle(tabPage->windowTitle()); if(splitView_) { if(Fm::PathBar* pathBar = qobject_cast(activeViewFrame_->getTopBar())) { pathBar->setPath(tabPage->path()); } else if(Fm::PathEdit* pathEntry = qobject_cast(activeViewFrame_->getTopBar())) { pathEntry->setText(tabPage->pathName()); } } else { if(pathEntry_ != nullptr) { pathEntry_->setText(tabPage->pathName()); } else if(pathBar_ != nullptr) { pathBar_->setPath(tabPage->path()); } } ui.statusbar->showMessage(tabPage->statusText()); fsInfoLabel_->setText(tabPage->statusText(TabPage::StatusTextFSInfo)); if(setFocus) { tabPage->folderView()->childView()->setFocus(); } // update side pane ui.sidePane->setCurrentPath(tabPage->path()); ui.sidePane->setShowHidden(tabPage->showHidden()); // update back/forward/up toolbar buttons ui.actionGoUp->setEnabled(tabPage->canUp()); ui.actionGoBack->setEnabled(tabPage->canBackward()); ui.actionGoForward->setEnabled(tabPage->canForward()); updateViewMenuForCurrentPage(); updateStatusBarForCurrentPage(); } // also update the enabled state of Edit actions updateEditSelectedActions(); bool isWritable(false); if(tabPage && tabPage->folder()) { if(auto info = tabPage->folder()->info()) { isWritable = info->isWritable(); } } ui.actionPaste->setEnabled(isWritable); } void MainWindow::onStackedWidgetWidgetRemoved(int index) { QStackedWidget* sw = static_cast(sender()); if(ViewFrame* viewFrame = qobject_cast(sw->parentWidget())) { // qDebug("onStackedWidgetWidgetRemoved: %d", index); // need to remove associated tab from tabBar viewFrame->getTabBar()->removeTab(index); if(viewFrame->getTabBar()->count() == 0) { // this is the last one if(!splitView_) { deleteLater(); // destroy the whole window // qDebug("delete window"); } else { // if we are in the split mode and the last tab of a view frame is closed, // remove that view frame and go to the simple mode for(int i = 0; i < ui.viewSplitter->count(); ++i) { // first find and activate the next view frame if(ViewFrame* thisViewFrame = qobject_cast(ui.viewSplitter->widget(i))) { if(thisViewFrame == viewFrame) { int n = i < ui.viewSplitter->count() - 1 ? i + 1 : 0; if(ViewFrame* nextViewFrame = qobject_cast(ui.viewSplitter->widget(n))) { if(activeViewFrame_ != nextViewFrame) { activeViewFrame_ = nextViewFrame; updateUIForCurrentPage(); // if the window isn't active, eventFilter() won't be called, // so we should revert to the main palette here if(activeViewFrame_->palette().color(QPalette::Base) != qApp->palette().color(QPalette::Base)) { activeViewFrame_->setPalette(qApp->palette()); } } break; } } } } ui.actionSplitView->setChecked(false); on_actionSplitView_triggered(false); } } else { Settings& settings = static_cast(qApp)->settings(); if(!settings.alwaysShowTabs() && viewFrame->getTabBar()->count() == 1) { viewFrame->getTabBar()->setVisible(false); } } } } void MainWindow::onTabPageTitleChanged(QString title) { TabPage* tabPage = static_cast(sender()); if(ViewFrame* viewFrame = viewFrameForTabPage(tabPage)) { int index = viewFrame->getStackedWidget()->indexOf(tabPage); if(index >= 0) { viewFrame->getTabBar()->setTabText(index, title); } if(viewFrame == activeViewFrame_) { if(tabPage == currentPage()) { setWindowTitle(title); // Since TabPage::titleChanged is emitted on changing directory, // the enabled state of Paste action should be updated here bool isWritable(false); if(tabPage && tabPage->folder()) { if(auto info = tabPage->folder()->info()) { isWritable = info->isWritable(); } } ui.actionPaste->setEnabled(isWritable); } } } } void MainWindow::onTabPageStatusChanged(int type, QString statusText) { TabPage* tabPage = static_cast(sender()); if(tabPage == currentPage()) { switch(type) { case TabPage::StatusTextNormal: case TabPage::StatusTextSelectedFiles: { // although the status text may change very frequently, // the text of PCManFM::StatusBar is updated with a delay QString text = tabPage->statusText(TabPage::StatusTextSelectedFiles); if(text.isEmpty()) { ui.statusbar->showMessage(tabPage->statusText(TabPage::StatusTextNormal)); } else { ui.statusbar->showMessage(text); } break; } case TabPage::StatusTextFSInfo: fsInfoLabel_->setText(tabPage->statusText(TabPage::StatusTextFSInfo)); fsInfoLabel_->setVisible(!statusText.isEmpty()); break; } } // Since TabPage::statusChanged is always emitted after View::selChanged, // there is no need to connect a separate slot to the latter signal updateEditSelectedActions(); } void MainWindow::onTabPageOpenDirRequested(const Fm::FilePath& path, int target) { TabPage* tabPage = static_cast(sender()); if(ViewFrame* viewFrame = viewFrameForTabPage(tabPage)) { switch(target) { case OpenInCurrentTab: chdir(path, viewFrame); break; case OpenInNewTab: addTab(path, viewFrame); break; case OpenInNewWindow: (new MainWindow(path))->show(); break; } } } void MainWindow::onTabPageSortFilterChanged() { // NOTE: This may be called from context menu too. TabPage* tabPage = static_cast(sender()); if(tabPage == currentPage()) { updateViewMenuForCurrentPage(); if(!tabPage->hasCustomizedView()) { // remember sort settings globally Settings& settings = static_cast(qApp)->settings(); settings.setSortColumn(static_cast(tabPage->sortColumn())); settings.setSortOrder(tabPage->sortOrder()); settings.setSortFolderFirst(tabPage->sortFolderFirst()); settings.setSortCaseSensitive(tabPage->sortCaseSensitive()); settings.setShowHidden(tabPage->showHidden()); } } } void MainWindow::onSidePaneChdirRequested(int type, const Fm::FilePath &path) { // FIXME: use enum for type value or change it to button. if(type == 0) { // left button (default) chdir(path); } else if(type == 1) { // middle button addTab(path); } else if(type == 2) { // new window (new MainWindow(path))->show(); } } void MainWindow::onSidePaneOpenFolderInNewWindowRequested(const Fm::FilePath &path) { (new MainWindow(path))->show(); } void MainWindow::onSidePaneOpenFolderInNewTabRequested(const Fm::FilePath &path) { addTab(path); } void MainWindow::onSidePaneOpenFolderInTerminalRequested(const Fm::FilePath &path) { Application* app = static_cast(qApp); app->openFolderInTerminal(path); } void MainWindow::onSidePaneCreateNewFolderRequested(const Fm::FilePath &path) { createFileOrFolder(CreateNewFolder, path, nullptr, this); } void MainWindow::onSidePaneModeChanged(Fm::SidePane::Mode mode) { static_cast(qApp)->settings().setSidePaneMode(mode); } void MainWindow::onSettingHiddenPlace(const QString& str, bool hide) { static_cast(qApp)->settings().setHiddenPlace(str, hide); } void MainWindow::onSplitterMoved(int pos, int /*index*/) { Application* app = static_cast(qApp); app->settings().setSplitterPos(pos); } void MainWindow::loadBookmarksMenu() { QAction* before = ui.actionAddToBookmarks; for(auto& item: bookmarks_->items()) { BookmarkAction* action = new BookmarkAction(item, ui.menu_Bookmarks); connect(action, &QAction::triggered, this, &MainWindow::onBookmarkActionTriggered); ui.menu_Bookmarks->insertAction(before, action); } ui.menu_Bookmarks->insertSeparator(before); } void MainWindow::onBookmarksChanged() { // delete existing items QList actions = ui.menu_Bookmarks->actions(); QList::const_iterator it = actions.constBegin(); QList::const_iterator last_it = actions.constEnd() - 2; while(it != last_it) { QAction* action = *it; ++it; ui.menu_Bookmarks->removeAction(action); } loadBookmarksMenu(); } void MainWindow::onBookmarkActionTriggered() { BookmarkAction* action = static_cast(sender()); auto path = action->path(); if(path) { Application* app = static_cast(qApp); Settings& settings = app->settings(); switch(settings.bookmarkOpenMethod()) { case OpenInCurrentTab: /* current tab */ default: chdir(path); break; case OpenInNewTab: /* new tab */ addTab(path); break; case OpenInNewWindow: /* new window */ (new MainWindow(path))->show(); break; } } } void MainWindow::on_actionCopy_triggered() { TabPage* page = currentPage(); auto paths = page->selectedFilePaths(); copyFilesToClipboard(paths); } void MainWindow::on_actionCut_triggered() { TabPage* page = currentPage(); auto paths = page->selectedFilePaths(); cutFilesToClipboard(paths); } void MainWindow::on_actionPaste_triggered() { pasteFilesFromClipboard(currentPage()->path(), this); } void MainWindow::on_actionDelete_triggered() { Application* app = static_cast(qApp); Settings& settings = app->settings(); TabPage* page = currentPage(); auto paths = page->selectedFilePaths(); auto path_it = paths.cbegin(); bool trashed(path_it != paths.cend() && (*path_it).hasUriScheme("trash")); bool shiftPressed = (qApp->keyboardModifiers() & Qt::ShiftModifier ? true : false); if(settings.useTrash() && !shiftPressed // trashed files should be deleted && !trashed) { FileOperation::trashFiles(paths, settings.confirmTrash(), this); } else { FileOperation::deleteFiles(paths, settings.confirmDelete(), this); } } void MainWindow::on_actionRename_triggered() { // do inline renaming if only one item is selected, // otherwise use the renaming dialog TabPage* page = currentPage(); auto files = page->selectedFiles(); if(files.size() == 1) { QAbstractItemView* view = page->folderView()->childView(); QModelIndexList selIndexes = view->selectionModel()->selectedIndexes(); if(selIndexes.size() > 1) { // in the detailed list mode, only the first index is editable view->setCurrentIndex(selIndexes.at(0)); } QModelIndex cur = view->currentIndex(); if (cur.isValid()) { view->scrollTo(cur); view->edit(cur); return; } } if(!files.empty()) { for(auto& file: files) { if(!Fm::renameFile(file, nullptr)) { break; } } } } void MainWindow::on_actionBulkRename_triggered() { BulkRenamer(currentPage()->selectedFiles(), this); } void MainWindow::on_actionSelectAll_triggered() { currentPage()->selectAll(); } void MainWindow::on_actionInvertSelection_triggered() { currentPage()->invertSelection(); } void MainWindow::on_actionPreferences_triggered() { Application* app = reinterpret_cast(qApp); app->preferences(QString()); } // change some icons according to layout direction void MainWindow::setRTLIcons(bool isRTL) { QIcon nxtIcn = QIcon::fromTheme("go-next"); QIcon prevIcn = QIcon::fromTheme("go-previous"); if(isRTL) { ui.actionGoBack->setIcon(nxtIcn); ui.actionCloseLeft->setIcon(nxtIcn); ui.actionGoForward->setIcon(prevIcn); ui.actionCloseRight->setIcon(prevIcn); } else { ui.actionGoBack->setIcon(prevIcn); ui.actionCloseLeft->setIcon(prevIcn); ui.actionGoForward->setIcon(nxtIcn); ui.actionCloseRight->setIcon(nxtIcn); } } bool MainWindow::event(QEvent* event) { switch(event->type()) { case QEvent::WindowActivate: lastActive_ = this; default: break; } return QMainWindow::event(event); } void MainWindow::changeEvent(QEvent* event) { switch(event->type()) { case QEvent::LayoutDirectionChange: setRTLIcons(QApplication::layoutDirection() == Qt::RightToLeft); break; default: break; } QWidget::changeEvent(event); } void MainWindow::onBackForwardContextMenu(QPoint pos) { // show a popup menu for browsing history here. QToolButton* btn = static_cast(sender()); TabPage* page = currentPage(); Fm::BrowseHistory& history = page->browseHistory(); int current = history.currentIndex(); QMenu menu; for(size_t i = 0; i < history.size(); ++i) { const BrowseHistoryItem& item = history.at(i); auto path = item.path(); auto name = path.displayName(); QAction* action = menu.addAction(name.get()); if(i == static_cast(current)) { // make the current path bold and checked action->setCheckable(true); action->setChecked(true); QFont font = menu.font(); font.setBold(true); action->setFont(font); } } QAction* selectedAction = menu.exec(btn->mapToGlobal(pos)); if(selectedAction) { int index = menu.actions().indexOf(selectedAction); page->jumpToHistory(index); updateUIForCurrentPage(); } } void MainWindow::onTabBarClicked(int /*index*/) { TabBar* tabBar = static_cast(sender()); if(ViewFrame* viewFrame = qobject_cast(tabBar->parentWidget())) { // focus the view on clicking the tab bar if(TabPage* page = currentPage(viewFrame)) { page->folderView()->childView()->setFocus(); } } } void MainWindow::tabContextMenu(const QPoint& pos) { TabBar* tabBar = static_cast(sender()); if(ViewFrame* viewFrame = qobject_cast(tabBar->parentWidget())) { int tabNum = viewFrame->getTabBar()->count(); if(tabNum <= 1) { return; } rightClickIndex_ = viewFrame->getTabBar()->tabAt(pos); if(rightClickIndex_ < 0) { return; } QMenu menu; if(rightClickIndex_ > 0) { menu.addAction(ui.actionCloseLeft); } if(rightClickIndex_ < tabNum - 1) { menu.addAction(ui.actionCloseRight); if(rightClickIndex_ > 0) { menu.addSeparator(); menu.addAction(ui.actionCloseOther); } } menu.exec(viewFrame->getTabBar()->mapToGlobal(pos)); } } void MainWindow::closeLeftTabs() { while(rightClickIndex_ > 0) { closeTab(rightClickIndex_ - 1); --rightClickIndex_; } } void MainWindow::closeRightTabs() { if(rightClickIndex_ < 0) { return; } while(rightClickIndex_ < activeViewFrame_->getTabBar()->count() - 1) { closeTab(rightClickIndex_ + 1); } } void MainWindow::focusPathEntry() { // use text entry for the path bar if(splitView_) { if(Fm::PathBar* pathBar = qobject_cast(activeViewFrame_->getTopBar())) { pathBar->openEditor(); } else if(Fm::PathEdit* pathEntry = qobject_cast(activeViewFrame_->getTopBar())) { pathEntry->setFocus(); pathEntry->selectAll(); } } else{ if(pathEntry_ != nullptr) { pathEntry_->setFocus(); pathEntry_->selectAll(); } else if(pathBar_ != nullptr) { // use button-style path bar pathBar_->openEditor(); } } } void MainWindow::dragEnterEvent(QDragEnterEvent* event) { if(event->mimeData()->hasFormat("application/pcmanfm-qt-tab") // ensure that the tab drag source is ours (and not a root window, for example) && lastActive_ && lastActive_->isActiveWindow()) { event->acceptProposedAction(); } } void MainWindow::dropEvent(QDropEvent* event) { if(event->mimeData()->hasFormat("application/pcmanfm-qt-tab")) { dropTab(); } event->acceptProposedAction(); } void MainWindow::dropTab() { if(lastActive_ == nullptr // impossible || lastActive_ == this) { // don't drop on the same window activeViewFrame_->getTabBar()->finishMouseMoveEvent(); return; } // close the tab in the first window and add // its page to a new tab in the second window TabPage* dropPage = lastActive_->currentPage(); if(dropPage) { disconnect(dropPage, nullptr, lastActive_, nullptr); // release mouse before tab removal because otherwise, the source tabbar // might not be updated properly with tab reordering during a fast drag-and-drop lastActive_->activeViewFrame_->getTabBar()->releaseMouse(); QWidget* page = lastActive_->activeViewFrame_->getStackedWidget()->currentWidget(); lastActive_->activeViewFrame_->getStackedWidget()->removeWidget(page); int index = addTabWithPage(dropPage, activeViewFrame_); activeViewFrame_->getTabBar()->setCurrentIndex(index); } else { activeViewFrame_->getTabBar()->finishMouseMoveEvent(); // impossible } } void MainWindow::detachTab() { if (activeViewFrame_->getStackedWidget()->count() == 1) { // don't detach a single tab activeViewFrame_->getTabBar()->finishMouseMoveEvent(); return; } // close the tab and move its page to a new window TabPage* dropPage = currentPage(); if(dropPage) { disconnect(dropPage, nullptr, this, nullptr); activeViewFrame_->getTabBar()->releaseMouse(); // as in dropTab() QWidget* page = activeViewFrame_->getStackedWidget()->currentWidget(); activeViewFrame_->getStackedWidget()->removeWidget(page); MainWindow* newWin = new MainWindow(); newWin->addTabWithPage(dropPage, newWin->activeViewFrame_); newWin->show(); } else { activeViewFrame_->getTabBar()->finishMouseMoveEvent(); // impossible } } void MainWindow::updateFromSettings(Settings& settings) { // apply settings // menu ui.actionDelete->setText(settings.useTrash() ? tr("&Move to Trash") : tr("&Delete")); ui.actionDelete->setIcon(settings.useTrash() ? QIcon::fromTheme("user-trash") : QIcon::fromTheme("edit-delete")); // side pane ui.sidePane->setIconSize(QSize(settings.sidePaneIconSize(), settings.sidePaneIconSize())); // tabs for(int i = 0; i < ui.viewSplitter->count(); ++i) { if(ViewFrame* viewFrame = qobject_cast(ui.viewSplitter->widget(i))) { viewFrame->getTabBar()->setTabsClosable(settings.showTabClose()); viewFrame->getTabBar()->setVisible(settings.alwaysShowTabs() || (viewFrame->getTabBar()->count() > 1)); // all tab pages int n = viewFrame->getStackedWidget()->count(); for(int j = 0; j < n; ++j) { TabPage* page = static_cast(viewFrame->getStackedWidget()->widget(j)); page->updateFromSettings(settings); } } } } void MainWindow::on_actionOpenAsRoot_triggered() { TabPage* page = currentPage(); if(page) { Application* app = static_cast(qApp); Settings& settings = app->settings(); if(!settings.suCommand().isEmpty()) { // run the su command // FIXME: it's better to get the filename of the current process rather than hard-code pcmanfm-qt here. QByteArray suCommand = settings.suCommand().toLocal8Bit(); QByteArray programCommand = app->applicationFilePath().toLocal8Bit(); programCommand += " %U"; // if %s exists in the su command, substitute it with the program int substPos = suCommand.indexOf("%s"); if(substPos != -1) { // replace %s with program suCommand.replace(substPos, 2, programCommand); } else { /* no %s found so just append to it */ suCommand += programCommand; } Fm::GAppInfoPtr appInfo{g_app_info_create_from_commandline(suCommand.constData(), nullptr, GAppInfoCreateFlags(0), nullptr), false}; if(appInfo) { auto cwd = page->path(); Fm::GErrorPtr err; auto uri = cwd.uri(); GList* uris = g_list_prepend(nullptr, uri.get()); if(!g_app_info_launch_uris(appInfo.get(), uris, nullptr, &err)) { QMessageBox::critical(this, tr("Error"), QString::fromUtf8(err->message)); } g_list_free(uris); } } else { // show an error message and ask the user to set the command QMessageBox::critical(this, tr("Error"), tr("Switch user command is not set.")); app->preferences("advanced"); } } } void MainWindow::on_actionFindFiles_triggered() { Application* app = static_cast(qApp); auto selectedPaths = currentPage()->selectedFilePaths(); QStringList paths; if(!selectedPaths.empty()) { for(auto& path: selectedPaths) { // FIXME: is it ok to use display name here? // This might be broken on filesystems with non-UTF-8 filenames. paths.append(path.displayName().get()); } } else { paths.append(currentPage()->pathName()); } app->findFiles(paths); } void MainWindow::on_actionOpenTerminal_triggered() { TabPage* page = currentPage(); if(page) { Application* app = static_cast(qApp); app->openFolderInTerminal(page->path()); } } void MainWindow::on_actionCopyFullPath_triggered() { TabPage* page = currentPage(); if(page) { auto paths = page->selectedFilePaths(); if(paths.size() == 1) { QApplication::clipboard()->setText(QString(paths.front().toString().get()), QClipboard::Clipboard); } } } void MainWindow::onShortcutNextTab() { int current = activeViewFrame_->getTabBar()->currentIndex(); if(current < activeViewFrame_->getTabBar()->count() - 1) { activeViewFrame_->getTabBar()->setCurrentIndex(current + 1); } else { activeViewFrame_->getTabBar()->setCurrentIndex(0); } } void MainWindow::onShortcutPrevTab() { int current = activeViewFrame_->getTabBar()->currentIndex(); if(current > 0) { activeViewFrame_->getTabBar()->setCurrentIndex(current - 1); } else { activeViewFrame_->getTabBar()->setCurrentIndex(activeViewFrame_->getTabBar()->count() - 1); } } // Switch to nth tab when Alt+n or Ctrl+n is pressed void MainWindow::onShortcutJumpToTab() { QShortcut* shortcut = reinterpret_cast(sender()); QKeySequence seq = shortcut->key(); int keyValue = seq[0]; // See the source code of QKeySequence and refer to the method: // QString QKeySequencePrivate::encodeString(int key, QKeySequence::SequenceFormat format). // Then we know how to test if a key sequence contains a modifier. // It's a shame that Qt has no API for this task. if((keyValue & Qt::ALT) == Qt::ALT) { // test if we have Alt key pressed keyValue -= Qt::ALT; } else if((keyValue & Qt::CTRL) == Qt::CTRL) { // test if we have Ctrl key pressed keyValue -= Qt::CTRL; } // now keyValue should contains '0' - '9' only int index; if(keyValue == '0') { index = 9; } else { index = keyValue - '1'; } if(index < activeViewFrame_->getTabBar()->count()) { activeViewFrame_->getTabBar()->setCurrentIndex(index); } } }