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"