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 }