diff --git a/autotests/mainshelltest.cpp b/autotests/mainshelltest.cpp index 7e1cb0697..a9941e9e0 100644 --- a/autotests/mainshelltest.cpp +++ b/autotests/mainshelltest.cpp @@ -1,688 +1,695 @@ /*************************************************************************** * Copyright (C) 2014 by Albert Astals Cid * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include "../shell/okular_main.h" #include "../shell/shell.h" #include "../shell/shellutils.h" #include "../core/document_p.h" #include "../ui/findbar.h" #include "../ui/presentationwidget.h" #include "../part.h" #include "../settings.h" #include "closedialoghelper.h" #include #ifndef Q_OS_WIN #include #else #include #endif namespace Okular { class PartTest { public: Okular::Document *partDocument(Okular::Part *part) const { return part->m_document; } QWidget *presentationWidget(Okular::Part *part) const { return part->m_presentationWidget; } FindBar *findWidget(Okular::Part *part) const { return part->m_findBar; } }; } class ClosePrintDialogHelper : public QObject { Q_OBJECT public: ClosePrintDialogHelper(int expectedTab) : foundDialog(false), m_expectedTab(expectedTab) { } bool foundDialog; private slots: void closePrintDialog(); private: int m_expectedTab; }; class MainShellTest : public QObject, public Okular::PartTest { Q_OBJECT public: static QTabWidget* tabWidget(Shell *s) { return s->m_tabWidget; } private slots: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); void testShell_data(); void testShell(); void testFileRemembersPagePosition_data(); void testFileRemembersPagePosition(); void test2FilesError_data(); void test2FilesError(); void testMiddleButtonCloseUndo(); void testSessionRestore_data(); void testSessionRestore(); void testOpenInvalidFiles_data(); void testOpenInvalidFiles(); private: }; QList getShells() { QList shells; const QList< KMainWindow * > mainWindows = KMainWindow::memberList(); for ( KMainWindow* kmw : mainWindows ) { Shell* shell = qobject_cast( kmw ); if( shell ) { shells.append( shell ); } } return shells; } Shell *findShell(Shell *ignore = nullptr) { const QWidgetList wList = QApplication::topLevelWidgets(); for (QWidget *widget : wList ) { Shell *s = qobject_cast(widget); if (s && s != ignore) return s; } return nullptr; } void MainShellTest::initTestCase() { QStandardPaths::setTestModeEnabled(true); // Don't pollute people's okular settings Okular::Settings::instance( QStringLiteral("mainshelltest") ); // Register in bus as okular QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface(); const QString myPid = QString::number( getpid() ); const QString serviceName = QStringLiteral("org.kde.okular-")+ myPid; QVERIFY( bus->registerService(serviceName) == QDBusConnectionInterface::ServiceRegistered ); // Tell the presentationWidget and queryClose to not be annoying KSharedConfigPtr c = KSharedConfig::openConfig(); KConfigGroup cg = c->group("Notification Messages"); cg.writeEntry("presentationInfo", false); cg.writeEntry("ShowTabWarning", false); } void MainShellTest::cleanupTestCase() { } void MainShellTest::init() { // Default settings for every test Okular::Settings::self()->setDefaults(); // Clean docdatas const QList urls = { QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/file1.pdf")) , QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/tocreload.pdf")) , QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/contents.epub")) }; for (const QUrl &url : urls) { QFileInfo fileReadTest( url.toLocalFile() ); const QString docDataPath = Okular::DocumentPrivate::docDataFileName(url, fileReadTest.size()); QFile::remove(docDataPath); } } void MainShellTest::cleanup() { Shell *s; while ((s = findShell())) { delete s; } } void MainShellTest::testShell_data() { QTest::addColumn("paths"); QTest::addColumn("serializedOptions"); QTest::addColumn("useTabs"); QTest::addColumn("externalProcessPath"); QTest::addColumn("expectedPage"); QTest::addColumn("expectPresentation"); QTest::addColumn("expectPrintDialog"); QTest::addColumn("unique"); QTest::addColumn("externalProcessExpectedPage"); QTest::addColumn("externalProcessExpectPresentation"); QTest::addColumn("externalProcessExpectPrintDialog"); QTest::addColumn("externalProcessExpectFind"); const QStringList contentsEpub = QStringList(QStringLiteral(KDESRCDIR "data/contents.epub")); const QStringList file1 = QStringList(QStringLiteral(KDESRCDIR "data/file1.pdf")); QStringList file1AndToc; file1AndToc << QStringLiteral(KDESRCDIR "data/file1.pdf"); file1AndToc << QStringLiteral(KDESRCDIR "data/tocreload.pdf"); const QString tocReload = QStringLiteral(KDESRCDIR "data/tocreload.pdf"); const QString optionsPage2 = ShellUtils::serializeOptions(false, false, false, false, false, QStringLiteral("2"), QString()); const QString optionsPage2Presentation = ShellUtils::serializeOptions(true, false, false, false, false, QStringLiteral("2"), QString()); const QString optionsPrint = ShellUtils::serializeOptions(false, true, false, false, false, QString(), QString()); const QString optionsUnique = ShellUtils::serializeOptions(false, false, false, true, false, QString(), QString()); const QString optionsFind = ShellUtils::serializeOptions(false, false, false, false ,false , QString(), QStringLiteral("si:next-testing parameters!")); QTest::newRow("just show shell") << QStringList() << QString() << false << QString() << 0u << false << false << false << 0u << false << false << QString(); QTest::newRow("open file") << file1 << QString() << false << QString() << 0u << false << false << false << 0u << false << false << QString(); QTest::newRow("two files no tabs") << file1AndToc << QString() << false << QString() << 0u << false << false << false << 0u << false << false << QString(); QTest::newRow("two files with tabs") << file1AndToc << QString() << true << QString() << 0u << false << false << false << 0u << false << false << QString(); QTest::newRow("two files sequence no tabs") << file1 << QString() << false << tocReload << 0u << false << false << false << 0u << false << false << QString(); QTest::newRow("two files sequence with tabs") << file1 << QString() << true << tocReload << 0u << false << false << false << 0u << false << false << QString(); QTest::newRow("open file page number") << contentsEpub << optionsPage2 << false << QString() << 1u << false << false << false << 0u << false << false << QString(); QTest::newRow("open file page number and presentation") << contentsEpub << optionsPage2Presentation << false << QString() << 1u << true << false << false << 0u << false << false << QString(); QTest::newRow("open file find") << file1 << optionsFind << false << QString() << 0u << false << false << false << 0u << false << false << QStringLiteral("si:next-testing parameters!"); QTest::newRow("open file print") << file1 << optionsPrint << false << QString() << 0u << false << true << false << 0u << false << false << QString(); QTest::newRow("open two files unique") << file1 << optionsUnique << false << tocReload << 0u << false << false << true << 0u << false << false << QString(); QTest::newRow("open two files unique tabs") << file1 << optionsUnique << true << tocReload << 0u << false << false << true << 0u << false << false << QString(); QTest::newRow("page number attach tabs") << file1 << QString() << true << contentsEpub[0] << 0u << false << false << false << 2u << false << false << QString(); QTest::newRow("presentation attach tabs") << file1 << QString() << true << contentsEpub[0] << 0u << false << false << false << 2u << true << false << QString(); QTest::newRow("print attach tabs") << file1 << QString() << true << contentsEpub[0] << 0u << false << true << false << 2u << false << true << QString(); QTest::newRow("page number attach unique") << file1 << optionsUnique << false << contentsEpub[0] << 0u << false << false << true << 3u << false << false << QString(); QTest::newRow("presentation attach unique") << file1 << optionsUnique << false << contentsEpub[0] << 0u << false << false << true << 2u << true << false << QString(); QTest::newRow("print attach unique") << file1 << optionsUnique << false << contentsEpub[0] << 0u << false << false << true << 2u << false << true << QString(); QTest::newRow("page number attach unique tabs") << file1 << optionsUnique << true << contentsEpub[0] << 0u << false << false << true << 3u << false << false << QString(); QTest::newRow("presentation attach unique tabs") << file1 << optionsUnique << true << contentsEpub[0] << 0u << false << false << true << 2u << true << false << QString(); QTest::newRow("print attach unique tabs") << file1 << optionsUnique << true << contentsEpub[0] << 0u << false << false << true << 2u << false << true << QString(); } void MainShellTest::testShell() { QFETCH(QStringList, paths); QFETCH(QString, serializedOptions); QFETCH(bool, useTabs); QFETCH(QString, externalProcessPath); QFETCH(uint, expectedPage); QFETCH(bool, expectPresentation); QFETCH(bool, expectPrintDialog); QFETCH(bool, unique); QFETCH(uint, externalProcessExpectedPage); QFETCH(bool, externalProcessExpectPresentation); QFETCH(bool, externalProcessExpectPrintDialog); QFETCH(QString, externalProcessExpectFind); QScopedPointer helper; Okular::Settings::self()->setShellOpenFileInTabs(useTabs); if (expectPrintDialog || externalProcessExpectPrintDialog) { - const int expectedTab = 0; + const int expectedTab = externalProcessExpectPrintDialog && !unique ? 1 : 0; helper.reset(new ClosePrintDialogHelper(expectedTab)); QTimer::singleShot(0, helper.data(), SLOT(closePrintDialog())); } Okular::Status status = Okular::main(paths, serializedOptions); QCOMPARE(status, Okular::Success); Shell *s = findShell(); QVERIFY(s); if (paths.count() == 1) { QCOMPARE(s->m_tabs.count(), 1); Okular::Part *part = s->findChild(); QVERIFY(part); QCOMPARE(part->url().url(), QStringLiteral("file://%1").arg(paths[0])); QCOMPARE(partDocument(part)->currentPage(), expectedPage); // Testing if the bar is shown or hidden as expected QCOMPARE(findWidget(part)->isHidden(), externalProcessExpectFind.isEmpty()); QCOMPARE(findWidget(part)->findChild()->text(), externalProcessExpectFind); // Checking if the encryption/decryption worked QCOMPARE(externalProcessExpectFind, ShellUtils::find(serializedOptions)); } else if (paths.count() == 2) { if (useTabs) { Shell *s = findShell(); QVERIFY(s); Okular::Part *part = dynamic_cast(s->m_tabs[0].part); Okular::Part *part2 = dynamic_cast(s->m_tabs[1].part); QCOMPARE(s->m_tabs.count(), 2); QCOMPARE(part->url().url(), QStringLiteral("file://%1").arg(paths[0])); QCOMPARE(part2->url().url(), QStringLiteral("file://%1").arg(paths[1])); QCOMPARE(partDocument(part)->currentPage(), expectedPage); QCOMPARE(partDocument(part2)->currentPage(), expectedPage); } else { QSet openUrls; Shell *s = findShell(); QVERIFY(s); QCOMPARE(s->m_tabs.count(), 1); Okular::Part *part = s->findChild(); QVERIFY(part); QCOMPARE(partDocument(part)->currentPage(), expectedPage); openUrls << part->url().url(); Shell *s2 = findShell(s); QVERIFY(s2); QCOMPARE(s2->m_tabs.count(), 1); Okular::Part *part2 = s2->findChild(); QVERIFY(part2); QCOMPARE(partDocument(part2)->currentPage(), expectedPage); openUrls << part2->url().url(); for (const QString &path : qAsConst(paths)) { QVERIFY(openUrls.contains(QStringLiteral("file://%1").arg(path))); } } } if (!externalProcessPath.isEmpty()) { Okular::Part *part = s->findChild(); QProcess p; QStringList args; args << externalProcessPath; if (unique) args << QStringLiteral("-unique"); if (externalProcessExpectedPage != 0) args << QStringLiteral("-page") << QString::number(externalProcessExpectedPage + 1); if (externalProcessExpectPresentation) args << QStringLiteral("-presentation"); if (externalProcessExpectPrintDialog) args << QStringLiteral("-print"); p.start(QStringLiteral(OKULAR_BINARY), args); p.waitForStarted(); QCOMPARE(p.state(), QProcess::Running); if (useTabs || unique) { // It is attaching to us, so will eventually stop QTRY_COMPARE_WITH_TIMEOUT(p.state(), QProcess::NotRunning, 20000); QCOMPARE(p.exitStatus(), QProcess::NormalExit); QCOMPARE(p.exitCode(), 0); if (unique) { // It is unique so part got "overwritten" QCOMPARE(s->m_tabs.count(), 1); QCOMPARE(part->url().url(), QStringLiteral("file://%1").arg(externalProcessPath)); QCOMPARE(partDocument(part)->currentPage(), externalProcessExpectedPage); } else { // It is attaching to us so a second tab is there QCOMPARE(s->m_tabs.count(), 2); Okular::Part *part2 = dynamic_cast(s->m_tabs[1].part); QCOMPARE(part2->url().url(), QStringLiteral("file://%1").arg(externalProcessPath)); QCOMPARE(partDocument(part2)->currentPage(), externalProcessExpectedPage); } } else { QTest::qWait(750); // It opened on a new process, so it is still running, we need to kill it QCOMPARE(p.state(), QProcess::Running); p.terminate(); p.waitForFinished(); QVERIFY(p.state() != QProcess::Running); // It opened on a new process, so no change for us QCOMPARE(s->m_tabs.count(), 1); QCOMPARE(part->url().url(), QStringLiteral("file://%1").arg(paths[0])); QCOMPARE(partDocument(part)->currentPage(), externalProcessExpectedPage); } } if (expectPresentation) { QCOMPARE(paths.count(), 1); Okular::Part *part = s->findChild(); QTRY_VERIFY(presentationWidget(part) != nullptr); } if (externalProcessExpectPresentation) { Okular::Part *part; if (unique) { QCOMPARE(s->m_tabs.count(), 1); part = dynamic_cast(s->m_tabs[0].part); } else { QCOMPARE(s->m_tabs.count(), 2); part = dynamic_cast(s->m_tabs[1].part); } QTRY_VERIFY(presentationWidget(part) != nullptr); } if (helper) { QVERIFY(helper->foundDialog); } } void ClosePrintDialogHelper::closePrintDialog() { Shell *s = findShell(); QPrintDialog *dialog = s->findChild(); if (!dialog) { QTimer::singleShot(0, this, &ClosePrintDialogHelper::closePrintDialog); return; } QVERIFY(dialog); QCOMPARE(MainShellTest::tabWidget(s)->currentIndex(), m_expectedTab); dialog->close(); foundDialog = true; } void MainShellTest::testFileRemembersPagePosition_data() { QTest::addColumn("mode"); QTest::newRow("normal") << 1; QTest::newRow("unique") << 2; QTest::newRow("tabs") << 3; } void MainShellTest::testFileRemembersPagePosition() { QFETCH(int, mode); const QStringList paths = QStringList(QStringLiteral(KDESRCDIR "data/contents.epub")); QString serializedOptions; if (mode == 1 || mode == 3) serializedOptions = ShellUtils::serializeOptions(false, false, false, false, false, QString(), QString()); else serializedOptions = ShellUtils::serializeOptions(false, false, false, true, false, QString(), QString()); Okular::Settings::self()->setShellOpenFileInTabs(mode == 3); Okular::Status status = Okular::main(paths, serializedOptions); QCOMPARE(status, Okular::Success); Shell *s = findShell(); QVERIFY(s); Okular::Part *part = s->findChild(); QVERIFY(part); QCOMPARE(part->url().url(), QStringLiteral("file://%1").arg(paths[0])); QCOMPARE(partDocument(part)->currentPage(), 0u); partDocument(part)->setViewportPage(3); QCOMPARE(partDocument(part)->currentPage(), 3u); s->closeUrl(); QCOMPARE(part->url().url(), QString()); if (mode == 1) { delete s; status = Okular::main(paths, serializedOptions); QCOMPARE(status, Okular::Success); } else { QProcess p; QStringList args; args << paths[0]; if (mode == 2) args << QStringLiteral("-unique"); p.start(QStringLiteral(OKULAR_BINARY), args); p.waitForStarted(); QCOMPARE(p.state(), QProcess::Running); // It is attaching to us, so will eventually stop QTRY_COMPARE_WITH_TIMEOUT((int)p.state(), (int)QProcess::NotRunning, 20000); QCOMPARE((int)p.exitStatus(), (int)QProcess::NormalExit); QCOMPARE(p.exitCode(), 0); } s = findShell(); QVERIFY(s); part = s->findChild(); QVERIFY(part); QCOMPARE(part->url().url(), QStringLiteral("file://%1").arg(paths[0])); QCOMPARE(partDocument(part)->currentPage(), 3u); } void MainShellTest::test2FilesError_data() { QTest::addColumn("serializedOptions"); QTest::newRow("startInPresentation") << ShellUtils::serializeOptions(true, false, false, false, false, QString(), QString()); QTest::newRow("showPrintDialog") << ShellUtils::serializeOptions(false, true, false, false, false, QString(), QString()); QTest::newRow("unique") << ShellUtils::serializeOptions(false, false, false, true, false, QString(), QString()); QTest::newRow("pageNumber") << ShellUtils::serializeOptions(false, false, false, false, false, QStringLiteral("3"), QString()); QTest::newRow("find") << ShellUtils::serializeOptions(false, false, false, false, false, QString(), QStringLiteral("silly")); } void MainShellTest::test2FilesError() { QFETCH(QString, serializedOptions); QStringList paths; paths << QStringLiteral(KDESRCDIR "data/file1.pdf") << QStringLiteral(KDESRCDIR "data/tocreload.pdf"); Okular::Status status = Okular::main(paths, serializedOptions); QCOMPARE(status, Okular::Error); Shell *s = findShell(); QVERIFY(!s); } void MainShellTest::testSessionRestore_data() { QTest::addColumn("paths"); QTest::addColumn("options"); QTest::addColumn("useTabsOpen"); QTest::addColumn("useTabsRestore"); QStringList oneDocPaths(QStringLiteral( KDESRCDIR "data/file1.pdf" ) ); QStringList twoDocPaths( oneDocPaths ); twoDocPaths << QStringLiteral(KDESRCDIR "data/formSamples.pdf"); const QString options = ShellUtils::serializeOptions(false, false, false, false, false, QString(), QString()); QTest::newRow("1 doc, 1 window, tabs") << oneDocPaths << options << true << true; QTest::newRow("2 docs, 1 window, tabs") << twoDocPaths << options << true << true; QTest::newRow("2 docs, 2 windows, tabs") << twoDocPaths << options << false << true; QTest::newRow("2 docs, 2 windows, no tabs") << twoDocPaths << options << false << false; QTest::newRow("2 docs, 1 window, no tabs") << twoDocPaths << options << true << false; } void MainShellTest::testSessionRestore() { QFETCH( QStringList, paths ); QFETCH( QString, options ); QFETCH( bool, useTabsOpen ); QFETCH( bool, useTabsRestore ); Okular::Settings::self()->setShellOpenFileInTabs( useTabsOpen ); Okular::Status status = Okular::main( paths, options ); QCOMPARE( status, Okular::Success ); // Gather some information about the state // Verify that the correct number of windows/tabs were opened QList shells = getShells(); QVERIFY( !shells.isEmpty() ); int numDocs = 0; for ( Shell *shell : qAsConst(shells) ) { QVERIFY( QTest::qWaitForWindowExposed( shell ) ); numDocs += shell->m_tabs.size(); } QCOMPARE( numDocs, paths.size() ); QCOMPARE( shells.size(), useTabsOpen ? 1 : paths.size() ); QTest::qWait( 100 ); // Simulate session shutdown. The actual shutdown path comes through // QSessionManager XSMP handlers, then KApplication::commitData/saveState, // then KMWSessionManager::commitData/saveState. Without simulating an X // session manager, the best we can do here is to make a temporary Config // and call KMainWindows save functions directly. QTemporaryFile configFile; QVERIFY( configFile.open() ); int numWindows = 0; { // Scope for config so that we can reconstruct from file KConfig config( configFile.fileName(), KConfig::SimpleConfig ); for ( Shell *shell : qAsConst(shells) ) { shell->savePropertiesInternal( &config, ++numWindows ); // Windows aren't necessarily closed on shutdown, but we'll use // this as a way to trigger the destructor code, which is normally // connected to the aboutToQuit signal shell->close(); } } // Wait for shells to delete themselves. QTest::qWait doesn't do deferred // deletions so we'll set up a full event loop to do that. QEventLoop eventLoop; QTimer::singleShot( 100, &eventLoop, &QEventLoop::quit ); eventLoop.exec( QEventLoop::AllEvents ); shells = getShells(); QVERIFY( shells.isEmpty() ); Okular::Settings::self()->setShellOpenFileInTabs( useTabsRestore ); // Simulate session restore. We can't call KMainWindow::restore() directly // because it asks for info from the session manager, which doesn't know // about our temporary config. But the logic here mostly mirrors restore(). KConfig config( configFile.fileName(), KConfig::SimpleConfig ); for( int i = 1; i <= numWindows; ++i ) { Shell* shell = new Shell; shell->readPropertiesInternal( &config, i ); shell->show(); } // Verify that the restore state is reasonable shells = getShells(); QVERIFY( !shells.isEmpty() ); numDocs = 0; for ( Shell* shell : qAsConst(shells) ) { QVERIFY( QTest::qWaitForWindowExposed( shell ) ); numDocs += shell->m_tabs.size(); } QCOMPARE( numDocs, paths.size() ); QCOMPARE( shells.size(), useTabsRestore ? numWindows : paths.size() ); } void MainShellTest::testOpenInvalidFiles_data() { QTest::addColumn>("files"); QTest::addColumn("options"); QString options = ShellUtils::serializeOptions( false, false, false, false, false, QString(), QString() ); - QUrl validFile = ShellUtils::urlFromArg( QStringLiteral( KDESRCDIR "data/file2.pdf" ), ShellUtils::qfileExistFunc(), QString() ); + QUrl validFile1 = ShellUtils::urlFromArg( QStringLiteral( KDESRCDIR "data/file1.pdf" ), ShellUtils::qfileExistFunc(), QString() ); + QUrl validFile2 = ShellUtils::urlFromArg( QStringLiteral( KDESRCDIR "data/file2.pdf" ), ShellUtils::qfileExistFunc(), QString() ); QUrl invalidFile = ShellUtils::urlFromArg( QStringLiteral( KDESRCDIR "data/non-existing-doc.pdf" ), ShellUtils::qfileExistFunc(), QString() ); - QList firstCase { invalidFile, validFile }; - QList secondCase { validFile, invalidFile }; + QList firstCase { invalidFile, validFile1, validFile2 }; + QList secondCase { validFile1, validFile2, invalidFile }; QTest::newRow( "opening the invalid file first" ) << firstCase << options; - QTest::newRow( "opening the valid file first" ) << secondCase << options; + QTest::newRow( "opening the valids file first" ) << secondCase << options; } void MainShellTest::testOpenInvalidFiles() { QFETCH( QList, files ); QFETCH( QString, options ); /* * The purpose of this test is to verify that when we open an invalid file, no tab is created in the * shell. * */ Okular::Settings::self()->setShellOpenFileInTabs( true ); Okular::Status status = Okular::main( QStringList(), options ); QCOMPARE( status, Okular::Success ); Shell *shell = findShell(); QVERIFY( shell ); /* * We need to make sure that the KrecentFilesAction is empty before starting, because we will also test that * the file gets removed from the recent documents * */ shell->m_recent->clear(); QScopedPointer closeDialogHelper { new TestingUtils::CloseDialogHelper( QDialogButtonBox::StandardButton::Ok ) }; for (const QUrl& file: files) { shell->openUrl( file ); } QList recentFiles = shell->m_recent->urls(); - QVERIFY( shell->m_tabs.size() == 1 ); - QVERIFY( recentFiles.size() == 1 ); - QVERIFY( recentFiles.first().toString().contains( QStringLiteral( "data/file2.pdf" ) ) ); + QVERIFY( shell->m_tabs.size() == 2 ); + QVERIFY( shell->m_tabWidget->tabBar()->isVisible() ); + + QVERIFY( ! shell->m_tabWidget->tabIcon(0).isNull() ); + QVERIFY( ! shell->m_tabWidget->tabIcon(1).isNull() ); + + + QVERIFY( recentFiles.size() == 2 ); + } void MainShellTest::testMiddleButtonCloseUndo() { const QStringList paths = { QStringLiteral(KDESRCDIR "data/file1.pdf"), QStringLiteral(KDESRCDIR "data/file2.pdf") }; QString serializedOptions; serializedOptions = ShellUtils::serializeOptions(false, false, false, false, false, QString(), QString()); Okular::Settings::self()->setShellOpenFileInTabs(true); Okular::Status status = Okular::main(paths, serializedOptions); QCOMPARE(status, Okular::Success); Shell *s = findShell(); QVERIFY(s); QCOMPARE(s->m_tabWidget->count(), paths.size()); // Close a tab using middle key QWidget *firstTab = s->m_tabWidget->tabBar()->tabButton(0, QTabBar::RightSide); QVERIFY(firstTab); QTest::mouseClick(firstTab, Qt::MiddleButton); QCOMPARE(s->m_tabWidget->count(), paths.size() - 1); // Undo tab close s->undoCloseTab(); QCOMPARE(s->m_tabWidget->count(), paths.size()); } QTEST_MAIN( MainShellTest ) #include "mainshelltest.moc" diff --git a/shell/shell.cpp b/shell/shell.cpp index d1d6cf688..1cedc89f2 100644 --- a/shell/shell.cpp +++ b/shell/shell.cpp @@ -1,845 +1,844 @@ /*************************************************************************** * Copyright (C) 2002 by Wilco Greven * * Copyright (C) 2002 by Chris Cheney * * Copyright (C) 2003 by Benjamin Meyer * * Copyright (C) 2003-2004 by Christophe Devriese * * * * Copyright (C) 2003 by Laurent Montel * * Copyright (C) 2003-2004 by Albert Astals Cid * * Copyright (C) 2003 by Luboš Luňák * * Copyright (C) 2003 by Malcolm Hunter * * Copyright (C) 2004 by Dominique Devriese * * Copyright (C) 2004 by Dirk Mueller * * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "shell.h" // qt/kde includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WITH_KACTIVITIES #include #endif // local includes #include "kdocumentviewer.h" #include "../interfaces/viewerinterface.h" #include "shellutils.h" static const char *shouldShowMenuBarComingFromFullScreen = "shouldShowMenuBarComingFromFullScreen"; static const char *shouldShowToolBarComingFromFullScreen = "shouldShowToolBarComingFromFullScreen"; static const char* const SESSION_URL_KEY = "Urls"; static const char* const SESSION_TAB_KEY = "ActiveTab"; Shell::Shell( const QString &serializedOptions ) : KParts::MainWindow(), m_menuBarWasShown(true), m_toolBarWasShown(true) #ifndef Q_OS_WIN , m_activityResource(nullptr) #endif , m_isValid(true) { setObjectName( QStringLiteral( "okular::Shell#" ) ); setContextMenuPolicy( Qt::NoContextMenu ); // otherwise .rc file won't be found by unit test setComponentName(QStringLiteral("okular"), QString()); // set the shell's ui resource file setXMLFile(QStringLiteral("shell.rc")); m_fileformatsscanned = false; m_showMenuBarAction = nullptr; // this routine will find and load our Part. it finds the Part by // name which is a bad idea usually.. but it's alright in this // case since our Part is made for this Shell KPluginLoader loader(QStringLiteral("okularpart")); m_partFactory = loader.factory(); if (!m_partFactory) { // if we couldn't find our Part, we exit since the Shell by // itself can't do anything useful m_isValid = false; KMessageBox::error(this, i18n("Unable to find the Okular component: %1", loader.errorString())); return; } // now that the Part plugin is loaded, create the part KParts::ReadWritePart* const firstPart = m_partFactory->create< KParts::ReadWritePart >( this ); if (firstPart) { // Setup tab bar m_tabWidget = new QTabWidget( this ); m_tabWidget->setTabsClosable( true ); m_tabWidget->setElideMode( Qt::ElideRight ); m_tabWidget->tabBar()->hide(); m_tabWidget->setDocumentMode( true ); m_tabWidget->setMovable( true ); m_tabWidget->setAcceptDrops(true); m_tabWidget->installEventFilter(this); connect( m_tabWidget, &QTabWidget::currentChanged, this, &Shell::setActiveTab ); connect( m_tabWidget, &QTabWidget::tabCloseRequested, this, &Shell::closeTab ); connect( m_tabWidget->tabBar(), &QTabBar::tabMoved, this, &Shell::moveTabData ); setCentralWidget( m_tabWidget ); // then, setup our actions setupActions(); connect( QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater ); // and integrate the part's GUI with the shell's setupGUI(Keys | ToolBar | Save); createGUI(firstPart); connectPart( firstPart ); m_tabs.append( firstPart ); m_tabWidget->addTab( firstPart->widget(), QString() ); readSettings(); m_unique = ShellUtils::unique(serializedOptions); if (m_unique) { m_unique = QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.okular")); if (!m_unique) KMessageBox::information(this, i18n("There is already a unique Okular instance running. This instance won't be the unique one.")); } else { QString serviceName = QStringLiteral("org.kde.okular-") + QString::number(qApp->applicationPid()); QDBusConnection::sessionBus().registerService(serviceName); } if (ShellUtils::noRaise(serializedOptions)) { setAttribute(Qt::WA_ShowWithoutActivating); } QDBusConnection::sessionBus().registerObject(QStringLiteral("/okularshell"), this, QDBusConnection::ExportScriptableSlots); } else { m_isValid = false; KMessageBox::error(this, i18n("Unable to find the Okular component.")); } } bool Shell::eventFilter(QObject *obj, QEvent *event) { Q_UNUSED(obj); QDragMoveEvent* dmEvent = dynamic_cast(event); if (dmEvent) { bool accept = dmEvent->mimeData()->hasUrls(); event->setAccepted(accept); return accept; } QDropEvent* dEvent = dynamic_cast(event); if (dEvent) { const QList list = KUrlMimeData::urlsFromMimeData(dEvent->mimeData()); handleDroppedUrls(list); dEvent->setAccepted(true); return true; } // Handle middle button click events on the tab bar if (obj == m_tabWidget && event->type() == QEvent::MouseButtonRelease) { QMouseEvent* mEvent = static_cast(event); if (mEvent->button() == Qt::MiddleButton) { int tabIndex = m_tabWidget->tabBar()->tabAt(mEvent->pos()); if (tabIndex != -1) { closeTab(tabIndex); return true; } } } return false; } bool Shell::isValid() const { return m_isValid; } void Shell::showOpenRecentMenu() { m_recent->menu()->popup(QCursor::pos()); } Shell::~Shell() { if( !m_tabs.empty() ) { writeSettings(); for( const TabState &tab : qAsConst( m_tabs ) ) { tab.part->closeUrl( false ); } m_tabs.clear(); } if (m_unique) QDBusConnection::sessionBus().unregisterService(QStringLiteral("org.kde.okular")); delete m_tabWidget; } // Open a new document if we have space for it // This can hang if called on a unique instance and openUrl pops a messageBox bool Shell::openDocument( const QUrl& url, const QString &serializedOptions ) { if( m_tabs.size() <= 0 ) return false; KParts::ReadWritePart* const part = m_tabs[0].part; // Return false if we can't open new tabs and the only part is occupied if ( !dynamic_cast(part)->openNewFilesInTabs() && !part->url().isEmpty() && !ShellUtils::unique(serializedOptions)) { return false; } openUrl( url, serializedOptions ); return true; } bool Shell::openDocument( const QString& urlString, const QString &serializedOptions ) { return openDocument(QUrl(urlString), serializedOptions); } bool Shell::canOpenDocs( int numDocs, int desktop ) { if( m_tabs.size() <= 0 || numDocs <= 0 || m_unique ) return false; KParts::ReadWritePart* const part = m_tabs[0].part; const bool allowTabs = dynamic_cast(part)->openNewFilesInTabs(); if( !allowTabs && (numDocs > 1 || !part->url().isEmpty()) ) return false; const KWindowInfo winfo( window()->effectiveWinId(), KWindowSystem::WMDesktop ); if( winfo.desktop() != desktop ) return false; return true; } void Shell::openUrl( const QUrl & url, const QString &serializedOptions ) { const int activeTab = m_tabWidget->currentIndex(); if ( activeTab < m_tabs.size() ) { KParts::ReadWritePart* const activePart = m_tabs[activeTab].part; if( !activePart->url().isEmpty() ) { if( m_unique ) { applyOptionsToPart( activePart, serializedOptions ); activePart->openUrl( url ); } else { if( dynamic_cast(activePart)->openNewFilesInTabs() ) { openNewTab( url, serializedOptions ); } else { Shell* newShell = new Shell( serializedOptions ); newShell->show(); newShell->openUrl( url, serializedOptions ); } } } else { m_tabWidget->setTabText( activeTab, url.fileName() ); applyOptionsToPart( activePart, serializedOptions ); bool openOk = activePart->openUrl( url ); const bool isstdin = url.fileName() == QLatin1String( "-" ) || url.scheme() == QLatin1String( "fd" ); if ( !isstdin ) { if ( openOk ) { #ifdef WITH_KACTIVITIES if ( !m_activityResource ) m_activityResource = new KActivities::ResourceInstance( window()->winId(), this ); m_activityResource->setUri( url ); #endif m_recent->addUrl( url ); } else { m_recent->removeUrl( url ); closeTab( activeTab ); } } } } } void Shell::closeUrl() { closeTab( m_tabWidget->currentIndex() ); } void Shell::readSettings() { m_recent->loadEntries( KSharedConfig::openConfig()->group( "Recent Files" ) ); m_recent->setEnabled( true ); // force enabling const KConfigGroup group = KSharedConfig::openConfig()->group( "Desktop Entry" ); bool fullScreen = group.readEntry( "FullScreen", false ); setFullScreen( fullScreen ); if (fullScreen) { m_menuBarWasShown = group.readEntry( shouldShowMenuBarComingFromFullScreen, true ); m_toolBarWasShown = group.readEntry( shouldShowToolBarComingFromFullScreen, true ); } } void Shell::writeSettings() { m_recent->saveEntries( KSharedConfig::openConfig()->group( "Recent Files" ) ); KConfigGroup group = KSharedConfig::openConfig()->group( "Desktop Entry" ); group.writeEntry( "FullScreen", m_fullScreenAction->isChecked() ); if (m_fullScreenAction->isChecked()) { group.writeEntry( shouldShowMenuBarComingFromFullScreen, m_menuBarWasShown ); group.writeEntry( shouldShowToolBarComingFromFullScreen, m_toolBarWasShown ); } KSharedConfig::openConfig()->sync(); } void Shell::setupActions() { KStandardAction::open(this, SLOT(fileOpen()), actionCollection()); m_recent = KStandardAction::openRecent( this, SLOT(openUrl(QUrl)), actionCollection() ); m_recent->setToolBarMode( KRecentFilesAction::MenuMode ); connect( m_recent, &QAction::triggered, this, &Shell::showOpenRecentMenu ); m_recent->setToolTip( i18n("Click to open a file\nClick and hold to open a recent file") ); m_recent->setWhatsThis( i18n( "Click to open a file or Click and hold to select a recent file" ) ); m_printAction = KStandardAction::print( this, SLOT(print()), actionCollection() ); m_printAction->setEnabled( false ); m_closeAction = KStandardAction::close( this, SLOT(closeUrl()), actionCollection() ); m_closeAction->setEnabled( false ); KStandardAction::quit(this, SLOT(close()), actionCollection()); setStandardToolBarMenuEnabled(true); m_showMenuBarAction = KStandardAction::showMenubar( this, SLOT(slotShowMenubar()), actionCollection()); m_fullScreenAction = KStandardAction::fullScreen( this, SLOT(slotUpdateFullScreen()), this,actionCollection() ); m_nextTabAction = actionCollection()->addAction(QStringLiteral("tab-next")); m_nextTabAction->setText( i18n("Next Tab") ); actionCollection()->setDefaultShortcuts(m_nextTabAction, KStandardShortcut::tabNext()); m_nextTabAction->setEnabled( false ); connect( m_nextTabAction, &QAction::triggered, this, &Shell::activateNextTab ); m_prevTabAction = actionCollection()->addAction(QStringLiteral("tab-previous")); m_prevTabAction->setText( i18n("Previous Tab") ); actionCollection()->setDefaultShortcuts(m_prevTabAction, KStandardShortcut::tabPrev()); m_prevTabAction->setEnabled( false ); connect( m_prevTabAction, &QAction::triggered, this, &Shell::activatePrevTab ); m_undoCloseTab = actionCollection()->addAction(QStringLiteral("undo-close-tab")); m_undoCloseTab->setText( i18n("Undo close tab") ); actionCollection()->setDefaultShortcut(m_undoCloseTab, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_T)); m_undoCloseTab->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo"))); m_undoCloseTab->setEnabled( false ); connect( m_undoCloseTab, &QAction::triggered, this, &Shell::undoCloseTab ); } void Shell::saveProperties(KConfigGroup &group) { if ( !m_isValid ) // part couldn't be loaded, nothing to save return; // Gather lists of settings to preserve QStringList urls; for( const TabState &tab : qAsConst( m_tabs ) ) { urls.append( tab.part->url().url() ); } group.writePathEntry( SESSION_URL_KEY, urls ); group.writeEntry( SESSION_TAB_KEY, m_tabWidget->currentIndex() ); } void Shell::readProperties(const KConfigGroup &group) { // Reopen documents based on saved settings QStringList urls = group.readPathEntry( SESSION_URL_KEY, QStringList() ); int desiredTab = group.readEntry( SESSION_TAB_KEY, 0 ); while( !urls.isEmpty() ) { openUrl( QUrl(urls.takeFirst()) ); } if( desiredTab < m_tabs.size() ) { setActiveTab( desiredTab ); } } QStringList Shell::fileFormats() const { QStringList supportedPatterns; QString constraint( QStringLiteral("(Library == 'okularpart')") ); QLatin1String basePartService( "KParts/ReadOnlyPart" ); KService::List offers = KServiceTypeTrader::self()->query( basePartService, constraint ); KService::List::ConstIterator it = offers.constBegin(), itEnd = offers.constEnd(); for ( ; it != itEnd; ++it ) { KService::Ptr service = *it; QStringList mimeTypes = service->mimeTypes(); supportedPatterns += mimeTypes; } return supportedPatterns; } void Shell::fileOpen() { // this slot is called whenever the File->Open menu is selected, // the Open shortcut is pressed (usually CTRL+O) or the Open toolbar // button is clicked const int activeTab = m_tabWidget->currentIndex(); if ( !m_fileformatsscanned ) { const KDocumentViewer* const doc = qobject_cast(m_tabs[activeTab].part); if ( doc ) m_fileformats = doc->supportedMimeTypes(); if ( m_fileformats.isEmpty() ) m_fileformats = fileFormats(); m_fileformatsscanned = true; } QUrl startDir; const KParts::ReadWritePart* const curPart = m_tabs[activeTab].part; if ( curPart->url().isLocalFile() ) startDir = KIO::upUrl(curPart->url()); QPointer dlg( new QFileDialog( this )); dlg->setDirectoryUrl( startDir ); dlg->setAcceptMode( QFileDialog::AcceptOpen ); dlg->setOption( QFileDialog::HideNameFilterDetails, true ); dlg->setFileMode( QFileDialog::ExistingFiles ); // Allow selection of more than one file QMimeDatabase mimeDatabase; QSet globPatterns; QMap namedGlobs; for ( const QString &mimeName : qAsConst(m_fileformats) ) { QMimeType mimeType = mimeDatabase.mimeTypeForName( mimeName ); const QStringList globs( mimeType.globPatterns() ); if ( globs.isEmpty() ) { continue; } globPatterns.unite( globs.toSet() ) ; namedGlobs[ mimeType.comment() ].append( globs ); } QStringList namePatterns; for ( auto it = namedGlobs.cbegin(); it != namedGlobs.cend(); ++it ) { namePatterns.append( it.key() + QLatin1String(" (") + it.value().join( QLatin1Char(' ') ) + QLatin1Char(')') ); } namePatterns.prepend( i18n("All files (*)") ); namePatterns.prepend( i18n("All supported files (%1)", globPatterns.values().join( QLatin1Char(' ') ) ) ); dlg->setNameFilters( namePatterns ); dlg->setWindowTitle( i18n("Open Document") ); if ( dlg->exec() && dlg ) { const QList urlList = dlg->selectedUrls(); for (const QUrl &url : urlList) { openUrl( url ); } } if ( dlg ) { delete dlg.data(); } } void Shell::tryRaise() { KWindowSystem::forceActiveWindow( window()->effectiveWinId() ); } // only called when starting the program void Shell::setFullScreen( bool useFullScreen ) { if( useFullScreen ) setWindowState( windowState() | Qt::WindowFullScreen ); // set else setWindowState( windowState() & ~Qt::WindowFullScreen ); // reset } void Shell::setCaption( const QString &caption ) { bool modified = false; const int activeTab = m_tabWidget->currentIndex(); if ( activeTab >= 0 && activeTab < m_tabs.size() ) { KParts::ReadWritePart* const activePart = m_tabs[activeTab].part; QString tabCaption = activePart->url().fileName(); if ( activePart->isModified() ) { modified = true; if ( !tabCaption.isEmpty() ) { tabCaption.append( QStringLiteral( " *" ) ); } } m_tabWidget->setTabText( activeTab, tabCaption ); } setCaption( caption, modified ); } void Shell::showEvent(QShowEvent *e) { if (!menuBar()->isNativeMenuBar() && m_showMenuBarAction) m_showMenuBarAction->setChecked( menuBar()->isVisible() ); KParts::MainWindow::showEvent(e); } void Shell::slotUpdateFullScreen() { if(m_fullScreenAction->isChecked()) { m_menuBarWasShown = !menuBar()->isHidden(); menuBar()->hide(); m_toolBarWasShown = !toolBar()->isHidden(); toolBar()->hide(); KToggleFullScreenAction::setFullScreen(this, true); } else { if (m_menuBarWasShown) { menuBar()->show(); } if (m_toolBarWasShown) { toolBar()->show(); } KToggleFullScreenAction::setFullScreen(this, false); } } void Shell::slotShowMenubar() { if ( menuBar()->isHidden() ) menuBar()->show(); else menuBar()->hide(); } QSize Shell::sizeHint() const { return QApplication::desktop()->availableGeometry( this ).size() * 0.75; } bool Shell::queryClose() { if (m_tabs.count() > 1) { const QString dontAskAgainName = QStringLiteral("ShowTabWarning"); KMessageBox::ButtonCode dummy = KMessageBox::Yes; if (shouldBeShownYesNo(dontAskAgainName, dummy)) { QDialog *dialog = new QDialog(this); dialog->setWindowTitle(i18n("Confirm Close")); QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog); buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Yes), KGuiItem(i18n("Close Tabs"), QStringLiteral("tab-close"))); KGuiItem::assign(buttonBox->button(QDialogButtonBox::No), KStandardGuiItem::cancel()); bool checkboxResult = true; const int result = KMessageBox::createKMessageBox(dialog, buttonBox, QMessageBox::Question, i18n("You are about to close %1 tabs. Are you sure you want to continue?", m_tabs.count()), QStringList(), i18n("Warn me when I attempt to close multiple tabs"), &checkboxResult, KMessageBox::Notify); if (!checkboxResult) { saveDontShowAgainYesNo(dontAskAgainName, dummy); } if (result != QDialogButtonBox::Yes) { return false; } } } for( int i = 0; i < m_tabs.size(); ++i ) { KParts::ReadWritePart* const part = m_tabs[i].part; // To resolve confusion about multiple modified docs, switch to relevant tab if( part->isModified() ) setActiveTab( i ); if( !part->queryClose() ) return false; } return true; } void Shell::setActiveTab( int tab ) { m_tabWidget->setCurrentIndex( tab ); createGUI( m_tabs[tab].part ); m_printAction->setEnabled( m_tabs[tab].printEnabled ); m_closeAction->setEnabled( m_tabs[tab].closeEnabled ); } void Shell::closeTab( int tab ) { KParts::ReadWritePart* const part = m_tabs[tab].part; QUrl url = part->url(); if( part->closeUrl() && m_tabs.count() > 1 ) { if( part->factory() ) part->factory()->removeClient( part ); part->disconnect(); part->deleteLater(); m_tabs.removeAt( tab ); m_tabWidget->removeTab( tab ); m_undoCloseTab->setEnabled( true ); m_closedTabUrls.append( url ); if( m_tabWidget->count() == 1 ) { m_tabWidget->tabBar()->hide(); m_nextTabAction->setEnabled( false ); m_prevTabAction->setEnabled( false ); } } } void Shell::openNewTab( const QUrl& url, const QString &serializedOptions ) { + // Tabs are hidden when there's only one, so show it + if( m_tabs.size() == 1 ) + { + m_tabWidget->tabBar()->show(); + m_nextTabAction->setEnabled( true ); + m_prevTabAction->setEnabled( true ); + } const int newIndex = m_tabs.size(); // Make new part m_tabs.append( m_partFactory->create(this) ); connectPart( m_tabs[newIndex].part ); + // Update GUI KParts::ReadWritePart* const part = m_tabs[newIndex].part; + m_tabWidget->addTab( part->widget(), url.fileName() ); + applyOptionsToPart(part, serializedOptions); int previousActiveTab = m_tabWidget->currentIndex(); setActiveTab( m_tabs.size() - 1 ); if( part->openUrl(url) ) { - // Update GUI - m_tabWidget->addTab( part->widget(), url.fileName() ); - - // Tabs are hidden when there's only one, so show it - if( m_tabs.size() == 1 ) - { - m_tabWidget->tabBar()->show(); - m_nextTabAction->setEnabled( true ); - m_prevTabAction->setEnabled( true ); - } - m_recent->addUrl( url ); } else { setActiveTab( previousActiveTab ); closeTab( m_tabs.size() - 1 ); m_recent->removeUrl( url ); } } void Shell::applyOptionsToPart( QObject* part, const QString &serializedOptions ) { KDocumentViewer* const doc = qobject_cast(part); const QString find = ShellUtils::find(serializedOptions); if ( ShellUtils::startInPresentation(serializedOptions) ) doc->startPresentation(); if ( ShellUtils::showPrintDialog(serializedOptions) ) QMetaObject::invokeMethod( part, "enableStartWithPrint" ); if ( ShellUtils::showPrintDialogAndExit(serializedOptions) ) QMetaObject::invokeMethod( part, "enableExitAfterPrint" ); if(!find.isEmpty()) QMetaObject::invokeMethod( part, "enableStartWithFind", Q_ARG( const QString &, find )); } void Shell::connectPart( QObject* part ) { connect( this, SIGNAL(moveSplitter(int)), part, SLOT(moveSplitter(int)) ); connect( part, SIGNAL(enablePrintAction(bool)), this, SLOT(setPrintEnabled(bool))); connect( part, SIGNAL(enableCloseAction(bool)), this, SLOT(setCloseEnabled(bool))); connect( part, SIGNAL(mimeTypeChanged(QMimeType)), this, SLOT(setTabIcon(QMimeType))); connect( part, SIGNAL(urlsDropped(QList)), this, SLOT(handleDroppedUrls(QList)) ); connect( part, SIGNAL(fitWindowToPage(QSize,QSize)), this, SLOT(slotFitWindowToPage(QSize,QSize)) ); } void Shell::print() { QMetaObject::invokeMethod( m_tabs[m_tabWidget->currentIndex()].part, "slotPrint" ); } void Shell::setPrintEnabled( bool enabled ) { int i = findTabIndex( sender() ); if( i != -1 ) { m_tabs[i].printEnabled = enabled; if( i == m_tabWidget->currentIndex() ) m_printAction->setEnabled( enabled ); } } void Shell::setCloseEnabled( bool enabled ) { int i = findTabIndex( sender() ); if( i != -1 ) { m_tabs[i].closeEnabled = enabled; if( i == m_tabWidget->currentIndex() ) m_closeAction->setEnabled( enabled ); } } void Shell::activateNextTab() { if( m_tabs.size() < 2 ) return; const int activeTab = m_tabWidget->currentIndex(); const int nextTab = (activeTab == m_tabs.size()-1) ? 0 : activeTab+1; setActiveTab( nextTab ); } void Shell::activatePrevTab() { if( m_tabs.size() < 2 ) return; const int activeTab = m_tabWidget->currentIndex(); const int prevTab = (activeTab == 0) ? m_tabs.size()-1 : activeTab-1; setActiveTab( prevTab ); } void Shell::undoCloseTab() { if ( m_closedTabUrls.isEmpty() ) { return; } const QUrl lastTabUrl = m_closedTabUrls.takeLast(); if ( m_closedTabUrls.isEmpty() ) { m_undoCloseTab->setEnabled(false); } openUrl(lastTabUrl); } void Shell::setTabIcon( const QMimeType& mimeType ) { int i = findTabIndex( sender() ); if( i != -1 ) { m_tabWidget->setTabIcon( i, QIcon::fromTheme(mimeType.iconName()) ); } } int Shell::findTabIndex( QObject* sender ) { for( int i = 0; i < m_tabs.size(); ++i ) { if( m_tabs[i].part == sender ) { return i; } } return -1; } void Shell::handleDroppedUrls( const QList& urls ) { for ( const QUrl &url : urls ) { openUrl( url ); } } void Shell::moveTabData( int from, int to ) { m_tabs.move( from, to ); } void Shell::slotFitWindowToPage(const QSize& pageViewSize, const QSize& pageSize ) { const int xOffset = pageViewSize.width() - pageSize.width(); const int yOffset = pageViewSize.height() - pageSize.height(); showNormal(); resize( width() - xOffset, height() - yOffset); moveSplitter(pageSize.width()); } /* kate: replace-tabs on; indent-width 4; */