File indexing completed on 2024-04-28 05:47:34

0001 /*
0002     SPDX-FileCopyrightText: 2002-2003 Georg Robbers <Georg.Robbers@urz.uni-hd.de>
0003     SPDX-FileCopyrightText: 2003 Helio Chissini de Castro <helio@conectiva.com>
0004     SPDX-FileCopyrightText: 2007 Henrique Pinto <henrique.pinto@kdemail.net>
0005     SPDX-FileCopyrightText: 2008 Harald Hvaal <haraldhv@stud.ntnu.no>
0006     SPDX-FileCopyrightText: 2021 Jiří Wolker <woljiri@gmail.com>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "mainwindow.h"
0012 #include "ark_debug.h"
0013 #include "createdialog.h"
0014 #include "interface.h"
0015 #include "pluginmanager.h"
0016 #include "settingsdialog.h"
0017 #include "settingspage.h"
0018 
0019 #include <KActionCollection>
0020 #include <KConfigDialog>
0021 #include <KConfigSkeleton>
0022 #include <KLocalizedString>
0023 #include <KMessageBox>
0024 #include <KParts/ReadWritePart>
0025 #include <KPluginFactory>
0026 #include <KRecentFilesMenu>
0027 #include <KSharedConfig>
0028 #include <KStandardAction>
0029 #include <KToolBar>
0030 #include <KWindowSystem>
0031 #include <KXMLGUIFactory>
0032 
0033 #include <QApplication>
0034 #include <QDockWidget>
0035 #include <QDragEnterEvent>
0036 #include <QDragMoveEvent>
0037 #include <QFileDialog>
0038 #include <QMenuBar>
0039 #include <QMimeData>
0040 #include <QPointer>
0041 #include <QStatusBar>
0042 
0043 static constexpr char SIDEBAR_LOCKED_KEY[] = "LockSidebar";
0044 static constexpr char SIDEBAR_VISIBLE_KEY[] = "ShowSidebar";
0045 
0046 static bool isValidArchiveDrag(const QMimeData *data)
0047 {
0048     return ((data->hasUrls()) && (data->urls().count() == 1));
0049 }
0050 
0051 class Sidebar : public QDockWidget
0052 {
0053     Q_OBJECT
0054 
0055 public:
0056     explicit Sidebar(QWidget *parent = nullptr)
0057         : QDockWidget(parent)
0058     {
0059         setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
0060         setFeatures(defaultFeatures());
0061     }
0062 
0063     bool isLocked() const
0064     {
0065         return features().testFlag(NoDockWidgetFeatures);
0066     }
0067 
0068     void setLocked(bool locked)
0069     {
0070         setFeatures(locked ? NoDockWidgetFeatures : defaultFeatures());
0071 
0072         // show titlebar only if not locked
0073         if (locked) {
0074             if (!m_dumbTitleWidget) {
0075                 m_dumbTitleWidget = new QWidget;
0076             }
0077             setTitleBarWidget(m_dumbTitleWidget);
0078         } else {
0079             setTitleBarWidget(nullptr);
0080         }
0081     }
0082 
0083 private:
0084     static DockWidgetFeatures defaultFeatures()
0085     {
0086         DockWidgetFeatures dockFeatures = DockWidgetClosable | DockWidgetMovable;
0087         if (!KWindowSystem::isPlatformWayland()) { // TODO : Remove this check when QTBUG-87332 is fixed
0088             dockFeatures |= DockWidgetFloatable;
0089         }
0090 
0091         return dockFeatures;
0092     }
0093 
0094     QWidget *m_dumbTitleWidget = nullptr;
0095 };
0096 
0097 MainWindow::MainWindow(QWidget *)
0098     : KParts::MainWindow()
0099     , m_windowContents(new QStackedWidget(this))
0100 {
0101     setAcceptDrops(true);
0102     // Ark doesn't provide a fullscreen mode; remove the corresponding window button
0103     setWindowFlags(windowFlags() & ~Qt::WindowFullscreenButtonHint);
0104 }
0105 
0106 MainWindow::~MainWindow()
0107 {
0108     guiFactory()->removeClient(m_part);
0109     delete m_part;
0110     m_part = nullptr;
0111     m_welcomeView = nullptr;
0112 }
0113 
0114 void MainWindow::dragEnterEvent(QDragEnterEvent *event)
0115 {
0116     qCDebug(ARK) << event;
0117 
0118     Interface *iface = qobject_cast<Interface *>(m_part);
0119     if (iface->isBusy()) {
0120         return;
0121     }
0122 
0123     const bool partAcceptsDrops = !m_part->url().isEmpty() && m_part->isReadWrite();
0124     if (!event->source() && isValidArchiveDrag(event->mimeData()) && !partAcceptsDrops) {
0125         event->acceptProposedAction();
0126     }
0127     return;
0128 }
0129 
0130 void MainWindow::dropEvent(QDropEvent *event)
0131 {
0132     qCDebug(ARK) << event;
0133 
0134     Interface *iface = qobject_cast<Interface *>(m_part);
0135     if (iface->isBusy()) {
0136         return;
0137     }
0138 
0139     if ((event->source() == nullptr) && (isValidArchiveDrag(event->mimeData()))) {
0140         event->acceptProposedAction();
0141     }
0142 
0143     // TODO: if this call provokes a message box the drag will still be going
0144     // while the box is onscreen. looks buggy, do something about it
0145     openUrl(event->mimeData()->urls().at(0));
0146 }
0147 
0148 void MainWindow::dragMoveEvent(QDragMoveEvent *event)
0149 {
0150     qCDebug(ARK) << event;
0151 
0152     Interface *iface = qobject_cast<Interface *>(m_part);
0153     if (iface->isBusy()) {
0154         return;
0155     }
0156 
0157     if ((event->source() == nullptr) && (isValidArchiveDrag(event->mimeData()))) {
0158         event->acceptProposedAction();
0159     }
0160 }
0161 
0162 bool MainWindow::loadPart()
0163 {
0164     m_part = KPluginFactory::instantiatePlugin<KParts::ReadWritePart>(KPluginMetaData(QStringLiteral("kf6/parts/arkpart"))).plugin;
0165 
0166     if (!m_part) {
0167         KMessageBox::error(this, i18n("Unable to find Ark's KPart component, please check your installation."));
0168         qCWarning(ARK) << "Error loading Ark KPart.";
0169         return false;
0170     }
0171 
0172     m_part->setObjectName(QStringLiteral("ArkPart"));
0173 
0174     Interface *iface = qobject_cast<Interface *>(m_part);
0175     Q_ASSERT(iface);
0176     QWidget *infoPanel = iface->infoPanel();
0177     infoPanel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
0178 
0179     m_sidebar = new Sidebar;
0180     m_sidebar->setObjectName(QStringLiteral("ark_sidebar"));
0181     m_sidebar->setContextMenuPolicy(Qt::ActionsContextMenu);
0182     m_sidebar->setWindowTitle(i18n("Sidebar"));
0183     connect(m_sidebar, &QDockWidget::visibilityChanged, this, [this](bool visible) {
0184         // sync sidebar visibility with the m_showSidebarAction only if welcome screen is hidden
0185         if (m_showSidebarAction && m_windowContents->currentWidget() != m_welcomeView) {
0186             m_showSidebarAction->setChecked(visible);
0187         }
0188     });
0189     m_sidebar->setWidget(infoPanel);
0190     addDockWidget(Qt::RightDockWidgetArea, m_sidebar);
0191 
0192     setupActions();
0193 
0194     m_welcomeView = new WelcomeView(this);
0195     m_windowContents->addWidget(m_welcomeView);
0196 
0197     QWidget *partwidget = m_part->widget();
0198     m_windowContents->addWidget(partwidget);
0199     m_windowContents->setCurrentWidget(partwidget);
0200 
0201     setCentralWidget(m_windowContents);
0202 
0203     // needs to be above createGUI()
0204     KHamburgerMenu *const hamburgerMenu = KStandardAction::hamburgerMenu(nullptr, nullptr, m_part->actionCollection());
0205     connect(hamburgerMenu, &KHamburgerMenu::aboutToShowMenu, this, &MainWindow::updateHamburgerMenu);
0206     hamburgerMenu->setMenuBar(menuBar());
0207 
0208     QAction *const showMenuBarAction = actionCollection()->action(KStandardAction::name(KStandardAction::ShowMenubar));
0209     hamburgerMenu->setShowMenuBarAction(showMenuBarAction);
0210 
0211     setXMLFile(QStringLiteral("arkui.rc"));
0212     setupGUI(ToolBar | Keys | Save);
0213 
0214     // NOTE : apply default sidebar width only after calling setupGUI(...)
0215     // and before calling createGUI(...)
0216     resizeDocks({m_sidebar}, {m_sidebar->sizeHint().width()}, Qt::Horizontal);
0217 
0218     createGUI(m_part);
0219 
0220     // FIXME: workaround for BUG 171080
0221     showMenuBarAction->setChecked(!menuBar()->isHidden());
0222 
0223     statusBar()->hide();
0224 
0225     KConfigGroup configGroup = KSharedConfig::openConfig()->group(QStringLiteral("General"));
0226     m_sidebar->setLocked(configGroup.readEntry(SIDEBAR_LOCKED_KEY, true));
0227     m_sidebar->setVisible(configGroup.readEntry(SIDEBAR_VISIBLE_KEY, true));
0228 
0229     m_showSidebarAction->setChecked(m_sidebar->isVisibleTo(this));
0230     m_lockSidebarAction->setChecked(m_sidebar->isLocked());
0231 
0232     connect(m_part, SIGNAL(ready()), this, SLOT(updateActions()));
0233     connect(m_part, SIGNAL(ready()), this, SLOT(hideWelcomeScreen()));
0234     connect(m_part, SIGNAL(quit()), this, SLOT(quit()));
0235     // #365200: this will disable m_recentFilesAction, while openUrl() will enable it.
0236     // So updateActions() needs to be called after openUrl() returns.
0237     connect(m_part, SIGNAL(busy()), this, SLOT(updateActions()), Qt::QueuedConnection);
0238     connect(m_part, QOverload<>::of(&KParts::ReadOnlyPart::completed), this, &MainWindow::addPartUrl);
0239 
0240     updateActions();
0241 
0242     configGroup = KSharedConfig::openConfig()->group(QStringLiteral("General"));
0243     if (configGroup.readEntry("ShowWelcomeScreenOnStartup", true)) {
0244         showWelcomeScreen();
0245     }
0246 
0247     return true;
0248 }
0249 
0250 KRecentFilesMenu *MainWindow::recentFilesMenu() const
0251 {
0252     return m_recentFilesMenu;
0253 }
0254 
0255 void MainWindow::showWelcomeScreen()
0256 {
0257     m_showSidebarAction->setEnabled(false);
0258     m_windowContents->setCurrentWidget(m_welcomeView);
0259     m_sidebar->setVisible(false);
0260 }
0261 
0262 void MainWindow::hideWelcomeScreen()
0263 {
0264     Q_ASSERT(m_part->widget());
0265     m_sidebar->setVisible(m_showSidebarAction->isChecked());
0266     m_windowContents->setCurrentWidget(m_part->widget());
0267     m_showSidebarAction->setEnabled(true);
0268 }
0269 
0270 void MainWindow::setupActions()
0271 {
0272     m_newAction = KStandardAction::openNew(this, &MainWindow::newArchive, this);
0273     actionCollection()->addAction(QStringLiteral("ark_file_new"), m_newAction);
0274     m_openAction = KStandardAction::open(this, &MainWindow::openArchive, this);
0275     actionCollection()->addAction(QStringLiteral("ark_file_open"), m_openAction);
0276     auto quitAction = KStandardAction::quit(this, &MainWindow::quit, this);
0277     actionCollection()->addAction(QStringLiteral("ark_quit"), quitAction);
0278     m_recentFilesMenu = new KRecentFilesMenu(this);
0279     actionCollection()->addAction(QStringLiteral("ark_file_open_recent"), m_recentFilesMenu->menuAction());
0280     connect(m_recentFilesMenu, &KRecentFilesMenu::urlTriggered, this, &MainWindow::openUrl);
0281 
0282     KStandardAction::preferences(this, &MainWindow::showSettings, actionCollection());
0283 
0284     QAction *a = actionCollection()->addAction(QStringLiteral("help_welcome_page"));
0285     a->setText(i18n("Welcome Page"));
0286     a->setIcon(qApp->windowIcon());
0287     a->setWhatsThis(i18n("Show the welcome page"));
0288     connect(a, &QAction::triggered, this, [this]() {
0289         showWelcomeScreen();
0290     });
0291 
0292     // add Menubar toggle to 'Settings' menu
0293     KToggleAction *showMenuBar = KStandardAction::showMenubar(nullptr, nullptr, actionCollection());
0294     showMenuBar->setWhatsThis(xi18nc("@info:whatsthis",
0295                                      "This switches between having a <emphasis>Menubar</emphasis> "
0296                                      "and having a <interface>Hamburger Menu</interface> button. Both "
0297                                      "contain mostly the same commands and configuration options."));
0298     connect(
0299         showMenuBar,
0300         &KToggleAction::triggered, // Fixes #286822
0301         this,
0302         [this] {
0303             menuBar()->setVisible(!menuBar()->isVisible());
0304         },
0305         Qt::QueuedConnection);
0306 
0307     m_showSidebarAction = m_part->actionCollection()->action(QStringLiteral("show-infopanel"));
0308     m_showSidebarAction->setIcon(QIcon::fromTheme(QStringLiteral("sidebar-show-symbolic")));
0309     m_showSidebarAction->disconnect();
0310     connect(m_showSidebarAction, &QAction::triggered, m_sidebar, &Sidebar::setVisible);
0311 
0312     m_lockSidebarAction = actionCollection()->addAction(QStringLiteral("ark_lock_sidebar"));
0313     m_lockSidebarAction->setCheckable(true);
0314     m_lockSidebarAction->setIcon(QIcon::fromTheme(QStringLiteral("lock")));
0315     m_lockSidebarAction->setText(i18n("Lock Sidebar"));
0316     connect(m_lockSidebarAction, &QAction::triggered, m_sidebar, &Sidebar::setLocked);
0317     m_sidebar->addAction(m_lockSidebarAction);
0318 }
0319 
0320 void MainWindow::updateHamburgerMenu()
0321 {
0322     const KActionCollection *ac = m_part->actionCollection();
0323     auto hamburgerMenu = static_cast<KHamburgerMenu *>(ac->action(KStandardAction::name(KStandardAction::HamburgerMenu)));
0324     auto menu = hamburgerMenu->menu();
0325     if (!menu) {
0326         menu = new QMenu(this);
0327         hamburgerMenu->setMenu(menu);
0328     } else {
0329         menu->clear();
0330     }
0331 
0332     if (!toolBar()->isVisible()) {
0333         // If neither the menu bar nor the toolbar are visible, these actions should be available.
0334         menu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::ShowMenubar)));
0335         menu->addAction(toolBarMenuAction());
0336         menu->addSeparator();
0337     }
0338 
0339     menu->addAction(m_newAction);
0340     menu->addAction(m_openAction);
0341     menu->addMenu(m_recentFilesMenu);
0342     menu->addSeparator();
0343 
0344     menu->addAction(ac->action(QStringLiteral("extract")));
0345     menu->addAction(ac->action(QStringLiteral("add")));
0346     menu->addAction(ac->action(QStringLiteral("edit_find")));
0347     menu->addSeparator();
0348 
0349     menu->addMenu(static_cast<QMenu *>(factory()->container(QStringLiteral("ark_file"), m_part)));
0350     menu->addSeparator();
0351 
0352     menu->addMenu(static_cast<QMenu *>(factory()->container(QStringLiteral("settings"), this)));
0353 }
0354 
0355 void MainWindow::updateActions()
0356 {
0357     Interface *iface = qobject_cast<Interface *>(m_part);
0358     Kerfuffle::PluginManager pluginManager;
0359     m_newAction->setEnabled(!iface->isBusy() && !pluginManager.availableWritePlugins().isEmpty());
0360     m_openAction->setEnabled(!iface->isBusy());
0361     m_recentFilesMenu->setEnabled(!iface->isBusy());
0362 }
0363 
0364 void MainWindow::openArchive()
0365 {
0366     Interface *iface = qobject_cast<Interface *>(m_part);
0367     Q_ASSERT(iface);
0368     Q_UNUSED(iface);
0369 
0370     Kerfuffle::PluginManager pluginManager;
0371     auto dlg = new QFileDialog(this, i18nc("to open an archive", "Open Archive"));
0372 
0373     dlg->setMimeTypeFilters(pluginManager.supportedMimeTypes(Kerfuffle::PluginManager::SortByComment));
0374     dlg->setFileMode(QFileDialog::ExistingFile);
0375     dlg->setAcceptMode(QFileDialog::AcceptOpen);
0376 
0377     connect(dlg, &QDialog::finished, this, [this, dlg](int result) {
0378         if (result == QDialog::Accepted) {
0379             openUrl(dlg->selectedUrls().at(0));
0380         }
0381         dlg->deleteLater();
0382     });
0383 
0384     dlg->open();
0385 }
0386 
0387 void MainWindow::openUrl(const QUrl &url)
0388 {
0389     if (url.isEmpty()) {
0390         return;
0391     }
0392 
0393     hideWelcomeScreen();
0394     m_part->setArguments(m_openArgs);
0395     m_part->openUrl(url);
0396 }
0397 
0398 void MainWindow::setShowExtractDialog(bool option)
0399 {
0400     if (option) {
0401         m_openArgs.metaData()[QStringLiteral("showExtractDialog")] = QStringLiteral("true");
0402     } else {
0403         m_openArgs.metaData().remove(QStringLiteral("showExtractDialog"));
0404     }
0405 }
0406 
0407 void MainWindow::closeEvent(QCloseEvent *event)
0408 {
0409     KConfigGroup configGroup = KSharedConfig::openConfig()->group(QStringLiteral("General"));
0410     configGroup.writeEntry(SIDEBAR_LOCKED_KEY, m_sidebar->isLocked());
0411     // NOTE : Consider whether the m_showSidebarAction is checked, because
0412     // the sidebar can be forcibly hidden if the welcome screen is displayed
0413     configGroup.writeEntry(SIDEBAR_VISIBLE_KEY, m_sidebar->isVisibleTo(this) || m_showSidebarAction->isChecked());
0414 
0415     // Preview windows don't have a parent, so we need to manually close them.
0416     const auto topLevelWidgets = qApp->topLevelWidgets();
0417     for (QWidget *widget : topLevelWidgets) {
0418         if (widget->isVisible()) {
0419             widget->close();
0420         }
0421     }
0422 
0423     KParts::MainWindow::closeEvent(event);
0424 }
0425 
0426 // Set a sane default window size
0427 QSize MainWindow::sizeHint() const
0428 {
0429     return QSize(950, 600);
0430 }
0431 
0432 void MainWindow::quit()
0433 {
0434     close();
0435 }
0436 
0437 void MainWindow::showSettings()
0438 {
0439     if (KConfigDialog::showDialog(QStringLiteral("settings"))) {
0440         return;
0441     }
0442 
0443     Interface *iface = qobject_cast<Interface *>(m_part);
0444     Q_ASSERT(iface);
0445 
0446     auto dialog = new Kerfuffle::SettingsDialog(this, QStringLiteral("settings"), iface->config());
0447 
0448     const auto pages = iface->settingsPages(this);
0449     for (Kerfuffle::SettingsPage *page : pages) {
0450         dialog->addPage(page, page->name(), page->iconName());
0451         connect(dialog, &KConfigDialog::settingsChanged, page, &Kerfuffle::SettingsPage::slotSettingsChanged);
0452         connect(dialog, &Kerfuffle::SettingsDialog::defaultsButtonClicked, page, &Kerfuffle::SettingsPage::slotDefaultsButtonClicked);
0453     }
0454     // Hide the icons list if only one page has been added.
0455     dialog->setFaceType(KPageDialog::Auto);
0456     dialog->setModal(true);
0457 
0458     connect(dialog, &KConfigDialog::settingsChanged, this, &MainWindow::writeSettings);
0459     connect(dialog, &KConfigDialog::settingsChanged, this, &MainWindow::updateActions);
0460     dialog->show();
0461 }
0462 
0463 void MainWindow::writeSettings()
0464 {
0465     Interface *iface = qobject_cast<Interface *>(m_part);
0466     Q_ASSERT(iface);
0467     iface->config()->save();
0468 }
0469 
0470 void MainWindow::addPartUrl()
0471 {
0472     m_recentFilesMenu->addUrl(m_part->url());
0473 }
0474 
0475 void MainWindow::newArchive()
0476 {
0477     qCDebug(ARK) << "Creating new archive";
0478 
0479     Interface *iface = qobject_cast<Interface *>(m_part);
0480     Q_ASSERT(iface);
0481     Q_UNUSED(iface);
0482 
0483     QPointer<Kerfuffle::CreateDialog> dialog = new Kerfuffle::CreateDialog(nullptr, // parent
0484                                                                            i18n("Create New Archive"), // caption
0485                                                                            QUrl()); // startDir
0486 
0487     if (dialog.data()->exec()) {
0488         const QUrl saveFileUrl = dialog.data()->selectedUrl();
0489         const QString password = dialog.data()->password();
0490         const QString fixedMimeType = dialog.data()->currentMimeType().name();
0491 
0492         qCDebug(ARK) << "CreateDialog returned URL:" << saveFileUrl.toString();
0493         qCDebug(ARK) << "CreateDialog returned mime:" << fixedMimeType;
0494 
0495         m_openArgs.metaData()[QStringLiteral("createNewArchive")] = QStringLiteral("true");
0496         m_openArgs.metaData()[QStringLiteral("fixedMimeType")] = fixedMimeType;
0497         if (dialog.data()->compressionLevel() > -1) {
0498             m_openArgs.metaData()[QStringLiteral("compressionLevel")] = QString::number(dialog.data()->compressionLevel());
0499         }
0500         if (dialog.data()->volumeSize() > 0) {
0501             qCDebug(ARK) << "Setting volume size:" << QString::number(dialog.data()->volumeSize());
0502             m_openArgs.metaData()[QStringLiteral("volumeSize")] = QString::number(dialog.data()->volumeSize());
0503         }
0504         if (!dialog.data()->compressionMethod().isEmpty()) {
0505             m_openArgs.metaData()[QStringLiteral("compressionMethod")] = dialog.data()->compressionMethod();
0506         }
0507         if (!dialog.data()->encryptionMethod().isEmpty()) {
0508             m_openArgs.metaData()[QStringLiteral("encryptionMethod")] = dialog.data()->encryptionMethod();
0509         }
0510 
0511         m_openArgs.metaData()[QStringLiteral("encryptionPassword")] = password;
0512 
0513         if (dialog.data()->isHeaderEncryptionEnabled()) {
0514             m_openArgs.metaData()[QStringLiteral("encryptHeader")] = QStringLiteral("true");
0515         }
0516 
0517         openUrl(saveFileUrl);
0518 
0519         m_openArgs.metaData().remove(QStringLiteral("showExtractDialog"));
0520         m_openArgs.metaData().remove(QStringLiteral("createNewArchive"));
0521         m_openArgs.metaData().remove(QStringLiteral("fixedMimeType"));
0522         m_openArgs.metaData().remove(QStringLiteral("compressionLevel"));
0523         m_openArgs.metaData().remove(QStringLiteral("encryptionPassword"));
0524         m_openArgs.metaData().remove(QStringLiteral("encryptHeader"));
0525     }
0526 
0527     delete dialog.data();
0528 }
0529 
0530 #include "mainwindow.moc"
0531 #include "moc_mainwindow.cpp"