diff --git a/shell/okular_main.cpp b/shell/okular_main.cpp new file mode 100644 index 000000000..7f583ce9a --- /dev/null +++ b/shell/okular_main.cpp @@ -0,0 +1,159 @@ +/*************************************************************************** + * Copyright (C) 2002 by Wilco Greven * + * Copyright (C) 2003 by Christophe Devriese * + * * + * Copyright (C) 2003 by Laurent Montel * + * Copyright (C) 2003-2007 by Albert Astals Cid * + * Copyright (C) 2004 by Andy Goossens * + * * + * 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 "okular_main.h" + +#include "shell.h" +#include +#include +#include +#include +#include +#include "aboutdata.h" +#include "shellutils.h" + +static bool attachUniqueInstance(const QStringList &paths, const QString &serializedOptions) +{ + if (!ShellUtils::unique(serializedOptions) || paths.count() != 1) + return false; + + QDBusInterface iface("org.kde.okular", "/okularshell", "org.kde.okular"); + if (!iface.isValid()) + return false; + + const QString page = ShellUtils::page(serializedOptions); + iface.call("openDocument", ShellUtils::urlFromArg(paths[0], ShellUtils::qfileExistFunc(), page).url(), serializedOptions); + if (!ShellUtils::noRaise(serializedOptions)) { + iface.call("tryRaise"); + } + + return true; +} + +// Ask an existing non-unique instance to open new tabs +static bool attachExistingInstance(const QStringList &paths, const QString &serializedOptions) +{ + if ( paths.count() < 1 ) + return false; + + const QStringList services = QDBusConnection::sessionBus().interface()->registeredServiceNames().value(); + + // Don't match the service without trailing "-" (unique instance) + const QString pattern = "org.kde.okular-"; + const QString myPid = QString::number( kapp->applicationPid() ); + QScopedPointer bestService; + const int desktop = KWindowSystem::currentDesktop(); + + // Select the first instance that isn't us (metric may change in future) + foreach ( const QString& service, services ) + { + if ( service.startsWith(pattern) && !service.endsWith( myPid ) ) + { + bestService.reset( new QDBusInterface(service, "/okularshell", "org.kde.okular") ); + + // Find a window that can handle our documents + const QDBusReply reply = bestService->call( "canOpenDocs", paths.count(), desktop ); + if( reply.isValid() && reply.value() ) + break; + + bestService.reset(); + } + } + + if ( !bestService ) + return false; + + foreach( const QString &arg, paths ) + { + // Copy stdin to temporary file which can be opened by the existing + // window. The temp file is automatically deleted after it has been + // opened. Not sure if this behavior is safe on all platforms. + QScopedPointer tempFile; + QString path; + if( arg == "-" ) + { + tempFile.reset( new QTemporaryFile ); + QFile stdinFile; + if( !tempFile->open() || !stdinFile.open(stdin,QIODevice::ReadOnly) ) + return false; + + const size_t bufSize = 1024*1024; + QScopedPointer > buf( new char[bufSize] ); + size_t bytes; + do + { + bytes = stdinFile.read( buf.data(), bufSize ); + tempFile->write( buf.data(), bytes ); + } while( bytes != 0 ); + + path = tempFile->fileName(); + } + else + { + // Page only makes sense if we are opening one file + const QString page = paths.count() == 1 ? ShellUtils::page(serializedOptions) : QString(); + path = ShellUtils::urlFromArg(arg, ShellUtils::qfileExistFunc(), page).url(); + } + + // Returns false if it can't fit another document + const QDBusReply reply = bestService->call( "openDocument", path, serializedOptions ); + if( !reply.isValid() || !reply.value() ) + return false; + } + + bestService->call( "tryRaise" ); + + return true; +} + +namespace Okular { + +Status main(const QStringList &paths, const QString &serializedOptions) +{ + if (ShellUtils::unique(serializedOptions) && paths.count() > 1) + { + QTextStream stream(stderr); + stream << i18n( "Error: Can't open more than one document with the --unique switch" ) << endl; + return Error; + } + + // try to attach to existing session, unique or not + if (attachUniqueInstance(paths, serializedOptions) || attachExistingInstance(paths, serializedOptions)) + { + return AttachedOtherProcess; + } + + Shell* shell = new Shell( serializedOptions ); + shell->show(); + for ( int i = 0; i < paths.count(); ) + { + // Page only makes sense if we are opening one file + const QString page = paths.count() == 1 ? ShellUtils::page(serializedOptions) : QString(); + if ( shell->openDocument( ShellUtils::urlFromArg(paths[i], ShellUtils::qfileExistFunc(), page).url(), serializedOptions) ) + { + ++i; + } + else + { + shell = new Shell( serializedOptions ); + shell->show(); + } + } + + return Success; +} + +} + +/* kate: replace-tabs on; indent-width 4; */ diff --git a/shell/okular_main.h b/shell/okular_main.h new file mode 100644 index 000000000..950b18ef0 --- /dev/null +++ b/shell/okular_main.h @@ -0,0 +1,27 @@ +/*************************************************************************** + * Copyright (C) 2002 by Wilco Greven * + * Copyright (C) 2003 by Christophe Devriese * + * * + * Copyright (C) 2003 by Laurent Montel * + * Copyright (C) 2003-2007 by Albert Astals Cid * + * Copyright (C) 2004 by Andy Goossens * + * * + * 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. * + ***************************************************************************/ + +class QString; +class QStringList; + +namespace Okular +{ + +enum Status { Error, AttachedOtherProcess, Success }; + +Status main(const QStringList &paths, const QString &serializedOptions); + +} + +/* kate: replace-tabs on; indent-width 4; */ diff --git a/tests/mainshelltest.cpp b/tests/mainshelltest.cpp new file mode 100644 index 000000000..766f017a6 --- /dev/null +++ b/tests/mainshelltest.cpp @@ -0,0 +1,391 @@ +/*************************************************************************** + * 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 "../shell/okular_main.h" +#include "../shell/shell.h" +#include "../shell/shellutils.h" +#include "../core/document_p.h" +#include "../ui/presentationwidget.h" +#include "../part.h" +#include "../settings.h" + +#include +#include + +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; + } +}; +} + +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 KTabWidget* tabWidget(Shell *s) + { + return s->m_tabWidget; + } + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void testShell_data(); + void testShell(); + void testUnique2FilesError(); + +private: +}; + +Shell *findShell(Shell *ignore = 0) +{ + foreach (QWidget *widget, QApplication::topLevelWidgets()) + { + Shell *s = qobject_cast(widget); + if (s && s != ignore) + return s; + } + return 0; +} + +void MainShellTest::initTestCase() +{ + // Don't pollute people's okular settings + Okular::Settings::instance( "mainshelltest" ); + + // Register in bus as okular + QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface(); + const QString myPid = QString::number( getpid() ); + const QString serviceName = "org.kde.okular-"+ myPid; + QVERIFY( bus->registerService(serviceName) == QDBusConnectionInterface::ServiceRegistered ); + + // Tell the presentationWidget to not be annoying + KSharedConfigPtr c = KGlobal::config(); + KConfigGroup cg = c->group("Notification Messages"); + cg.writeEntry("presentationInfo", false); +} + +void MainShellTest::cleanupTestCase() +{ +} + +void MainShellTest::init() +{ + // Default settings for every test + Okular::Settings::self()->setDefaults(); + + // Clean docdatas + QList urls; + urls << KUrl("file://" KDESRCDIR "data/file1.pdf"); + urls << KUrl("file://" KDESRCDIR "data/tocreload.pdf"); + urls << KUrl("file://" KDESRCDIR "data/contents.epub"); + + foreach (const KUrl &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"); + + const QStringList contentsEpub = QStringList(KDESRCDIR "data/contents.epub"); + const QStringList file1 = QStringList(KDESRCDIR "data/file1.pdf"); + QStringList file1AndToc; + file1AndToc << KDESRCDIR "data/file1.pdf"; + file1AndToc << KDESRCDIR "data/tocreload.pdf"; + const QString tocReload = KDESRCDIR "data/tocreload.pdf"; + + const QString optionsPage2 = ShellUtils::serializeOptions(false, false, false, false, "2"); + const QString optionsPage2Presentation = ShellUtils::serializeOptions(true, false, false, false, "2"); + const QString optionsPrint = ShellUtils::serializeOptions(false, true, false, false, QString()); + const QString optionsUnique = ShellUtils::serializeOptions(false, false, true, false, QString()); + + QTest::newRow("just show shell") << QStringList() << QString() << false << QString() << 0u << false << false << false << 0u << false << false; + QTest::newRow("open file") << file1 << QString() << false << QString() << 0u << false << false << false << 0u << false << false; + QTest::newRow("two files no tabs") << file1AndToc << QString() << false << QString() << 0u << false << false << false << 0u << false << false; + QTest::newRow("two files with tabs") << file1AndToc << QString() << true << QString() << 0u << false << false << false << 0u << false << false; + QTest::newRow("two files sequence no tabs") << file1 << QString() << false << tocReload << 0u << false << false << false << 0u << false << false; + QTest::newRow("two files sequence with tabs") << file1 << QString() << true << tocReload << 0u << false << false << false << 0u << false << false; + QTest::newRow("open file page number") << contentsEpub << optionsPage2 << false << QString() << 1u << false << false << false << 0u << false << false; + QTest::newRow("open file page number and presentation") << contentsEpub << optionsPage2Presentation << false << QString() << 1u << true << false << false << 0u << false << false; + QTest::newRow("open file print") << file1 << optionsPrint << false << QString() << 0u << false << true << false << 0u << false << false; + QTest::newRow("open two files unique") << file1 << optionsUnique << false << tocReload << 0u << false << false << true << 0u << false << false; + QTest::newRow("open two files unique tabs") << file1 << optionsUnique << true << tocReload << 0u << false << false << true << 0u << false << false; + QTest::newRow("page number attach tabs") << file1 << QString() << true << contentsEpub[0] << 0u << false << false << false << 2u << false << false; + QTest::newRow("presentation attach tabs") << file1 << QString() << true << contentsEpub[0] << 0u << false << false << false << 2u << true << false; + QTest::newRow("print attach tabs") << file1 << QString() << true << contentsEpub[0] << 0u << false << true << false << 2u << false << true; + QTest::newRow("page number attach unique") << file1 << optionsUnique << false << contentsEpub[0] << 0u << false << false << true << 3u << false << false; + QTest::newRow("presentation attach unique") << file1 << optionsUnique << false << contentsEpub[0] << 0u << false << false << true << 2u << true << false; + QTest::newRow("print attach unique") << file1 << optionsUnique << false << contentsEpub[0] << 0u << false << false << true << 2u << false << true; + QTest::newRow("page number attach unique tabs") << file1 << optionsUnique << true << contentsEpub[0] << 0u << false << false << true << 3u << false << false; + QTest::newRow("presentation attach unique tabs") << file1 << optionsUnique << true << contentsEpub[0] << 0u << false << false << true << 2u << true << false; + QTest::newRow("print attach unique tabs") << file1 << optionsUnique << true << contentsEpub[0] << 0u << false << false << true << 2u << false << true; +} + +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); + + QScopedPointer helper; + + Okular::Settings::self()->setShellOpenFileInTabs(useTabs); + + if (expectPrintDialog || externalProcessExpectPrintDialog) { + 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(), QString("file://%1").arg(paths[0])); + QCOMPARE(partDocument(part)->currentPage(), expectedPage); + } + else if (paths.count() == 2) + { + if (useTabs) + { + QSet openUrls; + 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(), QString("file://%1").arg(paths[0])); + QCOMPARE(part2->url().url(), QString("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(); + + foreach(const QString &path, paths) + { + QVERIFY(openUrls.contains(QString("file://%1").arg(path))); + } + } + } + + if (!externalProcessPath.isEmpty()) + { + Okular::Part *part = s->findChild(); + + QProcess p; + QString command = "okular " + externalProcessPath; + if (unique) + command += " -unique"; + if (externalProcessExpectedPage != 0) + command += QString(" -page %1").arg(externalProcessExpectedPage + 1); + if (externalProcessExpectPresentation) + command += QString(" -presentation"); + if (externalProcessExpectPrintDialog) + command += QString(" -print"); + p.start(command); + p.waitForStarted(); + QCOMPARE(p.state(), QProcess::Running); + + if (useTabs || unique) + { + // It is attaching to us, so will eventually stop + for (int i = 0; p.state() != QProcess::NotRunning && i < 20; ++i) { + QTest::qWait(100); + } + QCOMPARE(p.state(), QProcess::NotRunning); + QCOMPARE(p.exitStatus(), QProcess::NormalExit); + QCOMPARE(p.exitCode(), 0); + + if (unique) + { + // It is unique so part got "overriten" + QCOMPARE(s->m_tabs.count(), 1); + QCOMPARE(part->url().url(), QString("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(), QString("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(); + QCOMPARE(p.exitCode(), 0); + + // It opened on a new process, so no change for us + QCOMPARE(s->m_tabs.count(), 1); + QCOMPARE(part->url().url(), QString("file://%1").arg(paths[0])); + QCOMPARE(partDocument(part)->currentPage(), externalProcessExpectedPage); + } + } + + if (expectPresentation) + { + QCOMPARE(paths.count(), 1); + Okular::Part *part = s->findChild(); + + // Oh Qt5 i want your QTRY_VERIFY + for (int i = 0; presentationWidget(part) == 0 && i < 20; ++i) { + QTest::qWait(100); + } + QVERIFY(presentationWidget(part) != 0); + } + + 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); + } + + for (int i = 0; presentationWidget(part) == 0 && i < 20; ++i) { + QTest::qWait(100); + } + QVERIFY(presentationWidget(part) != 0); + } + + if (helper) + { + QVERIFY(helper->foundDialog); + } +} + +void ClosePrintDialogHelper::closePrintDialog() +{ + Shell *s = findShell(); + QPrintDialog *dialog = s->findChild(); + if (!dialog) { + QTimer::singleShot(0, this, SLOT(closePrintDialog())); + return; + } + QVERIFY(dialog); + QCOMPARE(MainShellTest::tabWidget(s)->currentIndex(), m_expectedTab); + dialog->close(); + foundDialog = true; +} + +void MainShellTest::testUnique2FilesError() +{ + QStringList paths; + QString serializedOptions = ShellUtils::serializeOptions(false, false, true, false, QString()); + paths << KDESRCDIR "data/file1.pdf" << KDESRCDIR "data/tocreload.pdf"; + Okular::Status status = Okular::main(paths, serializedOptions); + QCOMPARE(status, Okular::Error); + + QSet openUrls; + Shell *s = findShell(); + QVERIFY(!s); +} + + +QTEST_KDEMAIN( MainShellTest, GUI ) +#include "mainshelltest.moc"