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