File indexing completed on 2024-03-24 17:26:06
0001 /* 0002 SPDX-FileCopyrightText: 2000 Shie Erlich <krusader@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2000 Rafi Yanai <krusader@users.sourceforge.net> 0004 SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "krusader.h" 0010 0011 // QtCore 0012 #include <QDateTime> 0013 #include <QDir> 0014 #include <QStandardPaths> 0015 #include <QStringList> 0016 // QtGui 0017 #include <QMoveEvent> 0018 #include <QResizeEvent> 0019 // QtWidgets 0020 #include <QApplication> 0021 #include <QDesktopWidget> 0022 #include <QMenuBar> 0023 // QtDBus 0024 #include <QDBusInterface> 0025 0026 #include <KConfigCore/KSharedConfig> 0027 #include <KConfigGui/KWindowConfig> 0028 #include <KI18n/KLocalizedString> 0029 #include <KWidgetsAddons/KAcceleratorManager> 0030 #include <KWidgetsAddons/KCursor> 0031 #include <KWidgetsAddons/KMessageBox> 0032 #include <KWidgetsAddons/KToggleAction> 0033 #include <KWidgetsAddons/KToolBarPopupAction> 0034 #include <KWindowSystem/KStartupInfo> 0035 #include <KWindowSystem/KWindowSystem> 0036 #include <KXmlGui/KActionCollection> 0037 #include <KXmlGui/KToolBar> 0038 #include <KXmlGui/KXMLGUIFactory> 0039 #include <utility> 0040 0041 #include "defaults.h" 0042 #include "kractions.h" 0043 #include "krarchandler.h" 0044 #include "krglobal.h" 0045 #include "krservices.h" 0046 #include "krslots.h" 0047 #include "krtrashhandler.h" 0048 #include "krusaderversion.h" 0049 #include "krusaderview.h" 0050 #include "panelmanager.h" 0051 #include "tabactions.h" 0052 0053 #include "BookMan/krbookmarkhandler.h" 0054 #include "Dialogs/checksumdlg.h" 0055 #include "Dialogs/krpleasewait.h" 0056 #include "Dialogs/popularurls.h" 0057 #include "FileSystem/fileitem.h" 0058 #include "FileSystem/krpermhandler.h" 0059 #include "GUI/kcmdline.h" 0060 #include "GUI/kfnkeys.h" 0061 #include "GUI/krremoteencodingmenu.h" 0062 #include "GUI/krusaderstatus.h" 0063 #include "GUI/terminaldock.h" 0064 #include "JobMan/jobman.h" 0065 #include "KViewer/krviewer.h" 0066 #include "Konfigurator/kgprotocols.h" 0067 #include "MountMan/kmountman.h" 0068 #include "Panel/PanelView/krview.h" 0069 #include "Panel/PanelView/krviewfactory.h" 0070 #include "Panel/krcolorcache.h" 0071 #include "Panel/krpanel.h" 0072 #include "Panel/listpanelactions.h" 0073 #include "Panel/viewactions.h" 0074 #include "UserAction/expander.h" 0075 // This makes gcc-4.1 happy. Warning about possible problem with KrAction's dtor not called 0076 #include "UserAction/kraction.h" 0077 #include "UserAction/useraction.h" 0078 0079 // define the static members 0080 Krusader *Krusader::App = nullptr; 0081 QString Krusader::AppName; 0082 0083 // construct the views, statusbar and menu bars and prepare Krusader to start 0084 Krusader::Krusader(const QCommandLineParser &parser) 0085 : KParts::MainWindow(nullptr, Qt::Window | Qt::WindowTitleHint | Qt::WindowContextHelpButtonHint) 0086 , _listPanelActions(nullptr) 0087 , isStarting(true) 0088 , _quit(false) 0089 { 0090 // create the "krusader" 0091 App = this; 0092 krMainWindow = this; 0093 SLOTS = new KrSlots(this); 0094 setXMLFile("krusaderui.rc"); // kpart-related xml file 0095 0096 plzWait = new KrPleaseWaitHandler(this); 0097 0098 const bool runKonfig = versionControl(); 0099 0100 QString message; 0101 switch (krConfig->accessMode()) { 0102 case KConfigBase::NoAccess: 0103 message = "Krusader's configuration file can't be found. Default values will be used."; 0104 break; 0105 case KConfigBase::ReadOnly: 0106 message = "Krusader's configuration file is in READ ONLY mode (why is that!?) Changed values will not be saved"; 0107 break; 0108 case KConfigBase::ReadWrite: 0109 message = ""; 0110 break; 0111 } 0112 if (!message.isEmpty()) { 0113 KMessageBox::error(krApp, message); 0114 } 0115 0116 // create an object that manages archives in several parts of the source code 0117 KrGlobal::arcMan = new KrArcHandler(this); 0118 0119 // create MountMan 0120 KrGlobal::mountMan = new KMountMan(this); 0121 connect(KrGlobal::mountMan, &KMountMan::refreshPanel, SLOTS, &KrSlots::refresh); 0122 0123 // create popular URLs container 0124 _popularUrls = new PopularUrls(this); 0125 0126 // create bookman 0127 krBookMan = new KrBookmarkHandler(this); 0128 0129 // create job manager 0130 krJobMan = new JobMan(this); 0131 0132 // create the main view 0133 MAIN_VIEW = new KrusaderView(this); 0134 0135 // setup all the krusader's actions 0136 setupActions(); 0137 0138 // init the permission handler class 0139 KrPermHandler::init(); 0140 0141 // init the protocol handler 0142 KgProtocols::init(); 0143 0144 const KConfigGroup lookFeelGroup(krConfig, "Look&Feel"); 0145 FileItem::loadUserDefinedFolderIcons(lookFeelGroup.readEntry("Load User Defined Folder Icons", _UserDefinedFolderIcons)); 0146 0147 const KConfigGroup startupGroup(krConfig, "Startup"); 0148 QString startProfile = startupGroup.readEntry("Starter Profile Name", QString()); 0149 0150 QList<QUrl> leftTabs; 0151 QList<QUrl> rightTabs; 0152 0153 // get command-line arguments 0154 if (parser.isSet("left")) { 0155 leftTabs = KrServices::toUrlList(parser.value("left").split(',')); 0156 startProfile.clear(); 0157 } 0158 if (parser.isSet("right")) { 0159 rightTabs = KrServices::toUrlList(parser.value("right").split(',')); 0160 startProfile.clear(); 0161 } 0162 if (parser.isSet("profile")) 0163 startProfile = parser.value("profile"); 0164 0165 if (!startProfile.isEmpty()) { 0166 leftTabs.clear(); 0167 rightTabs.clear(); 0168 } 0169 // starting the panels 0170 MAIN_VIEW->start(startupGroup, startProfile.isEmpty(), leftTabs, rightTabs); 0171 0172 // create a status bar 0173 auto *status = new KrusaderStatus(this); 0174 setStatusBar(status); 0175 status->setWhatsThis( 0176 i18n("Statusbar will show basic information " 0177 "about file below mouse pointer.")); 0178 0179 // create tray icon (if needed) 0180 const bool startToTray = startupGroup.readEntry("Start To Tray", _StartToTray); 0181 setTray(startToTray); 0182 0183 setCentralWidget(MAIN_VIEW); 0184 0185 // manage our keyboard short-cuts 0186 // KAcceleratorManager::manage(this,true); 0187 0188 setCursor(Qt::ArrowCursor); 0189 0190 if (!startProfile.isEmpty()) 0191 MAIN_VIEW->profiles(startProfile); 0192 0193 // restore gui settings 0194 { 0195 // now, check if we need to create a konsole_part 0196 // call the XML GUI function to draw the UI 0197 createGUI(MAIN_VIEW->terminalDock()->part()); 0198 0199 // this needs to be called AFTER createGUI() !!! 0200 updateUserActions(); 0201 _listPanelActions->guiUpdated(); 0202 0203 // not using this. See savePosition() 0204 // applyMainWindowSettings(); 0205 0206 const KConfigGroup cfgToolbar(krConfig, "Main Toolbar"); 0207 toolBar()->applySettings(cfgToolbar); 0208 0209 const KConfigGroup cfgJobBar(krConfig, "Job Toolbar"); 0210 toolBar("jobToolBar")->applySettings(cfgJobBar); 0211 0212 const KConfigGroup cfgActionsBar(krConfig, "Actions Toolbar"); 0213 toolBar("actionsToolBar")->applySettings(cfgActionsBar); 0214 0215 // restore toolbars position and visibility 0216 restoreState(startupGroup.readEntry("State", QByteArray())); 0217 0218 statusBar()->setVisible(startupGroup.readEntry("Show status bar", _ShowStatusBar)); 0219 0220 MAIN_VIEW->updateGUI(startupGroup); 0221 0222 // popular urls 0223 _popularUrls->load(); 0224 } 0225 0226 if (runKonfig) 0227 SLOTS->runKonfigurator(true); 0228 0229 KConfigGroup viewerModuleGrp(krConfig, "ViewerModule"); 0230 if (viewerModuleGrp.readEntry("FirstRun", true)) { 0231 KrViewer::configureDeps(); 0232 viewerModuleGrp.writeEntry("FirstRun", false); 0233 } 0234 0235 if (!runKonfig) { 0236 KConfigGroup cfg(krConfig, "Private"); 0237 move(cfg.readEntry("Start Position", _StartPosition)); 0238 resize(cfg.readEntry("Start Size", _StartSize)); 0239 } 0240 0241 // view initialized; show window or only tray 0242 if (!startToTray) { 0243 show(); 0244 } 0245 0246 KrTrashHandler::startWatcher(); 0247 isStarting = false; 0248 0249 // HACK - used by [ListerTextArea|KrSearchDialog|LocateDlg]:keyPressEvent() 0250 KrGlobal::copyShortcut = _listPanelActions->actCopy->shortcut(); 0251 0252 // HACK: make sure the active view becomes focused 0253 // for some reason sometimes the active view cannot be focused immediately at this point, 0254 // so queue it for the main loop 0255 QTimer::singleShot(0, ACTIVE_PANEL->view->widget(), QOverload<>::of(&QWidget::setFocus)); 0256 0257 _openUrlTimer.setSingleShot(true); 0258 connect(&_openUrlTimer, &QTimer::timeout, this, &Krusader::doOpenUrl); 0259 0260 auto *startupInfo = new KStartupInfo(0, this); 0261 connect(startupInfo, &KStartupInfo::gotNewStartup, this, &Krusader::slotGotNewStartup); 0262 connect(startupInfo, &KStartupInfo::gotRemoveStartup, this, &Krusader::slotGotRemoveStartup); 0263 } 0264 0265 Krusader::~Krusader() 0266 { 0267 KrTrashHandler::stopWatcher(); 0268 0269 delete MAIN_VIEW; 0270 MAIN_VIEW = nullptr; 0271 App = nullptr; 0272 } 0273 0274 void Krusader::setTray(bool forceCreation) 0275 { 0276 const bool trayIsNeeded = forceCreation || KConfigGroup(krConfig, "Look&Feel").readEntry("Minimize To Tray", _ShowTrayIcon); 0277 if (!sysTray && trayIsNeeded) { 0278 sysTray = new KStatusNotifierItem(this); 0279 sysTray->setIconByName(appIconName()); 0280 // we have our own "quit" method, re-connect 0281 QAction *quitAction = sysTray->action(QStringLiteral("quit")); 0282 if (quitAction) { 0283 disconnect(quitAction, &QAction::triggered, nullptr, nullptr); 0284 connect(quitAction, &QAction::triggered, this, &Krusader::quit); 0285 } 0286 } else if (sysTray && !trayIsNeeded) { 0287 // user does not want tray anymore :( 0288 sysTray->deleteLater(); 0289 } 0290 } 0291 0292 bool Krusader::versionControl() 0293 { 0294 // create config file 0295 krConfig = KSharedConfig::openConfig().data(); 0296 KConfigGroup nogroup(krConfig, QString()); 0297 const bool firstRun = nogroup.readEntry("First Time", true); 0298 KrGlobal::sCurrentConfigVersion = nogroup.readEntry("Config Version", -1); 0299 0300 // first installation of krusader 0301 if (firstRun) { 0302 KMessageBox::information(krApp, 0303 i18n("<qt><b>Welcome to Krusader.</b><p>As this is your first run, your machine " 0304 "will now be checked for external applications. Then the Konfigurator will " 0305 "be launched where you can customize Krusader to your needs.</p></qt>")); 0306 } 0307 nogroup.writeEntry("Version", VERSION); 0308 nogroup.writeEntry("First Time", false); 0309 krConfig->sync(); 0310 0311 QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/krusader/")); 0312 0313 return firstRun; 0314 } 0315 0316 void Krusader::statusBarUpdate(const QString &mess) 0317 { 0318 // change the message on the statusbar for 5 seconds 0319 if (statusBar()->isVisible()) 0320 statusBar()->showMessage(mess, 5000); 0321 } 0322 0323 bool Krusader::event(QEvent *e) 0324 { 0325 if (e->type() == QEvent::ApplicationPaletteChange) { 0326 KrColorCache::getColorCache().refreshColors(); 0327 } 0328 return KParts::MainWindow::event(e); 0329 } 0330 0331 // <patch> Moving from Pixmap actions to generic filenames - thanks to Carsten Pfeiffer 0332 void Krusader::setupActions() 0333 { 0334 QAction *bringToTopAct = new QAction(i18n("Bring Main Window to Top"), this); 0335 actionCollection()->addAction("bring_main_window_to_top", bringToTopAct); 0336 connect(bringToTopAct, &QAction::triggered, this, &Krusader::moveToTop); 0337 0338 KrActions::setupActions(this); 0339 _krActions = new KrActions(this); 0340 _viewActions = new ViewActions(this, this); 0341 _listPanelActions = new ListPanelActions(this, this); 0342 _tabActions = new TabActions(this, this); 0343 } 0344 0345 /////////////////////////////////////////////////////////////////////////// 0346 //////////////////// implementation of slots ////////////////////////////// 0347 /////////////////////////////////////////////////////////////////////////// 0348 0349 void Krusader::savePosition() 0350 { 0351 KConfigGroup cfg(krConfig, "Private"); 0352 cfg.writeEntry("Start Position", pos()); 0353 cfg.writeEntry("Start Size", size()); 0354 0355 cfg = krConfig->group("Startup"); 0356 MAIN_VIEW->saveSettings(cfg); 0357 0358 // NOTE: this would save current window state/size, statusbar and settings for each toolbar. 0359 // We are not using this and saving everything manually because 0360 // - it does not save window position 0361 // - window size save/restore does sometimes not work (multi-monitor setup) 0362 // - saving the statusbar visibility should be independent from window position and restoring it 0363 // does not work properly. 0364 // KConfigGroup cfg = KConfigGroup(&cfg, "MainWindowSettings"); 0365 // saveMainWindowSettings(cfg); 0366 // statusBar()->setVisible(cfg.readEntry("StatusBar", "Enabled") != "Disabled"); 0367 0368 krConfig->sync(); 0369 } 0370 0371 void Krusader::saveSettings() 0372 { 0373 // avoid that information about closed tabs gets saved 0374 ACTIVE_MNG->delAllClosedTabs(); 0375 0376 // workaround: revert terminal fullscreen mode before saving widget and toolbar visibility 0377 if (MAIN_VIEW->isTerminalEmulatorFullscreen()) { 0378 MAIN_VIEW->setTerminalEmulator(false, true); 0379 } 0380 0381 KConfigGroup noGroup(krConfig, QString()); 0382 noGroup.writeEntry("Config Version", KrGlobal::sConfigVersion); 0383 0384 // save toolbar settings 0385 KConfigGroup cfg(krConfig, "Main Toolbar"); 0386 toolBar()->saveSettings(cfg); 0387 0388 cfg = krConfig->group("Job Toolbar"); 0389 toolBar("jobToolBar")->saveSettings(cfg); 0390 0391 cfg = krConfig->group("Actions Toolbar"); 0392 toolBar("actionsToolBar")->saveSettings(cfg); 0393 0394 cfg = krConfig->group("Startup"); 0395 // save toolbar visibility and position 0396 cfg.writeEntry("State", saveState()); 0397 cfg.writeEntry("Show status bar", statusBar()->isVisible()); 0398 0399 // save panel and window settings 0400 if (cfg.readEntry("Remember Position", _RememberPos)) 0401 savePosition(); 0402 0403 // save the gui components visibility 0404 if (cfg.readEntry("UI Save Settings", _UiSave)) { 0405 cfg.writeEntry("Show FN Keys", KrActions::actToggleFnkeys->isChecked()); 0406 cfg.writeEntry("Show Cmd Line", KrActions::actToggleCmdline->isChecked()); 0407 cfg.writeEntry("Show Terminal Emulator", KrActions::actToggleTerminal->isChecked()); 0408 } 0409 0410 // save popular links 0411 _popularUrls->save(); 0412 0413 krConfig->sync(); 0414 } 0415 0416 void Krusader::closeEvent(QCloseEvent *event) 0417 { 0418 if (sysTray && !_quit && !qApp->isSavingSession()) { 0419 // close to tray instead 0420 event->ignore(); 0421 hide(); 0422 return; 0423 } 0424 0425 _quit = false; // in case quit will be aborted 0426 KParts::MainWindow::closeEvent(event); // (may) quit, continues with queryClose()... 0427 } 0428 0429 void Krusader::showEvent(QShowEvent *event) 0430 { 0431 const KConfigGroup lookFeelGroup(krConfig, "Look&Feel"); 0432 if (sysTray && !lookFeelGroup.readEntry("Minimize To Tray", _ShowTrayIcon)) { 0433 // restoring from "start to tray", tray icon is not needed anymore 0434 sysTray->deleteLater(); 0435 } 0436 0437 KParts::MainWindow::showEvent(event); 0438 } 0439 0440 bool Krusader::queryClose() 0441 { 0442 if (isStarting) 0443 return false; 0444 0445 if (qApp->isSavingSession()) { // KDE is logging out, accept the close 0446 acceptClose(); 0447 return true; 0448 } 0449 0450 const KConfigGroup cfg = krConfig->group("Look&Feel"); 0451 const bool confirmExit = cfg.readEntry("Warn On Exit", _WarnOnExit); 0452 0453 // ask user and wait until all KIO::job operations are terminated. Krusader won't exit before 0454 // that anyway 0455 if (!krJobMan->waitForJobs(confirmExit)) 0456 return false; 0457 0458 /* First try to close the child windows, because it's the safer 0459 way to avoid crashes, then close the main window. 0460 If closing a child is not successful, then we cannot let the 0461 main window close. */ 0462 0463 for (;;) { 0464 QWidgetList list = QApplication::topLevelWidgets(); 0465 QWidget *activeModal = QApplication::activeModalWidget(); 0466 QWidget *w = list.at(0); 0467 0468 if (activeModal && activeModal != this && activeModal != menuBar() && list.contains(activeModal) && !activeModal->isHidden()) { 0469 w = activeModal; 0470 } else { 0471 int i = 1; 0472 for (; i < list.count(); ++i) { 0473 w = list.at(i); 0474 if (!(w && (w == this || w->isHidden() || w == menuBar()))) 0475 break; 0476 } 0477 0478 if (i == list.count()) 0479 w = nullptr; 0480 } 0481 0482 if (!w) 0483 break; 0484 0485 if (!w->close()) { 0486 if (w->inherits("QDialog")) { 0487 fprintf(stderr, "Failed to close: %s\n", w->metaObject()->className()); 0488 } 0489 return false; 0490 } 0491 } 0492 0493 acceptClose(); 0494 return true; 0495 } 0496 0497 void Krusader::acceptClose() 0498 { 0499 saveSettings(); 0500 0501 emit shutdown(); 0502 0503 // Removes the DBUS registration of the application. Single instance mode requires unique appid. 0504 // As Krusader is exiting, we release that unique appid, so new Krusader instances 0505 // can be started. 0506 0507 QDBusConnection dbus = QDBusConnection::sessionBus(); 0508 dbus.unregisterObject("/Instances/" + Krusader::AppName); 0509 } 0510 0511 // the please wait dialog functions 0512 void Krusader::startWaiting(QString msg, int count, bool cancel) 0513 { 0514 plzWait->startWaiting(std::move(msg), count, cancel); 0515 } 0516 0517 bool Krusader::wasWaitingCancelled() const 0518 { 0519 return plzWait->wasCancelled(); 0520 } 0521 0522 void Krusader::stopWait() 0523 { 0524 plzWait->stopWait(); 0525 } 0526 0527 void Krusader::updateUserActions() 0528 { 0529 auto *userActionMenu = qobject_cast<KActionMenu *>(KrActions::actUserMenu); 0530 if (userActionMenu) { 0531 userActionMenu->menu()->clear(); 0532 0533 userActionMenu->addAction(KrActions::actManageUseractions); 0534 userActionMenu->addSeparator(); 0535 krUserAction->populateMenu(userActionMenu, nullptr); 0536 } 0537 } 0538 0539 const char *Krusader::appIconName() 0540 { 0541 if (geteuid()) 0542 return "krusader_user"; 0543 else 0544 return "krusader_root"; 0545 } 0546 0547 void Krusader::quit() 0548 { 0549 _quit = true; // remember that we want to quit and not close to tray 0550 close(); // continues with closeEvent()... 0551 } 0552 0553 void Krusader::moveToTop() 0554 { 0555 if (isHidden()) 0556 show(); 0557 0558 KWindowSystem::forceActiveWindow(winId()); 0559 } 0560 0561 bool Krusader::isRunning() 0562 { 0563 moveToTop(); // FIXME - doesn't belong here 0564 return true; 0565 } 0566 0567 bool Krusader::isLeftActive() 0568 { 0569 return MAIN_VIEW->isLeftActive(); 0570 } 0571 0572 bool Krusader::openUrl(QString url) 0573 { 0574 _urlToOpen = std::move(url); 0575 _openUrlTimer.start(0); 0576 return true; 0577 } 0578 0579 void Krusader::doOpenUrl() 0580 { 0581 QUrl url = QUrl::fromUserInput(_urlToOpen, QDir::currentPath(), QUrl::AssumeLocalFile); 0582 _urlToOpen.clear(); 0583 int tab = ACTIVE_MNG->findTab(url); 0584 if (tab >= 0) 0585 ACTIVE_MNG->setActiveTab(tab); 0586 else if ((tab = OTHER_MNG->findTab(url)) >= 0) { 0587 OTHER_MNG->setActiveTab(tab); 0588 OTHER_MNG->currentPanel()->view->widget()->setFocus(); 0589 } else 0590 ACTIVE_MNG->slotNewTab(url); 0591 } 0592 0593 void Krusader::slotGotNewStartup(const KStartupInfoId &id, const KStartupInfoData &data) 0594 { 0595 Q_UNUSED(id) 0596 Q_UNUSED(data) 0597 // This is here to show busy mouse cursor when _other_ applications are launched, not for krusader itself. 0598 qApp->setOverrideCursor(Qt::BusyCursor); 0599 } 0600 0601 void Krusader::slotGotRemoveStartup(const KStartupInfoId &id, const KStartupInfoData &data) 0602 { 0603 Q_UNUSED(id) 0604 Q_UNUSED(data) 0605 qApp->restoreOverrideCursor(); 0606 } 0607 0608 KrView *Krusader::activeView() 0609 { 0610 return ACTIVE_PANEL->view; 0611 } 0612 0613 AbstractPanelManager *Krusader::activeManager() 0614 { 0615 return MAIN_VIEW->activeManager(); 0616 } 0617 0618 AbstractPanelManager *Krusader::leftManager() 0619 { 0620 return MAIN_VIEW->leftManager(); 0621 } 0622 0623 AbstractPanelManager *Krusader::rightManager() 0624 { 0625 return MAIN_VIEW->rightManager(); 0626 }