shell.cpp 28.1 KB
Newer Older
1 2 3 4 5 6 7
/***************************************************************************
 *   Copyright (C) 2002 by Wilco Greven <greven@kde.org>                   *
 *   Copyright (C) 2002 by Chris Cheney <ccheney@cheney.cx>                *
 *   Copyright (C) 2003 by Benjamin Meyer <benjamin@csh.rit.edu>           *
 *   Copyright (C) 2003-2004 by Christophe Devriese                        *
 *                         <Christophe.Devriese@student.kuleuven.ac.be>    *
 *   Copyright (C) 2003 by Laurent Montel <montel@kde.org>                 *
Albert Astals Cid's avatar
Albert Astals Cid committed
8
 *   Copyright (C) 2003-2004 by Albert Astals Cid <aacid@kde.org>          *
9 10 11 12
 *   Copyright (C) 2003 by Luboš Luňák <l.lunak@kde.org>                   *
 *   Copyright (C) 2003 by Malcolm Hunter <malcolm.hunter@gmx.co.uk>       *
 *   Copyright (C) 2004 by Dominique Devriese <devriese@kde.org>           *
 *   Copyright (C) 2004 by Dirk Mueller <mueller@kde.org>                  *
13 14 15
 *   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             *
16 17 18 19 20 21
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/
Christophe Devriese's avatar
Christophe Devriese committed
22

Albert Astals Cid's avatar
Albert Astals Cid committed
23 24
#include "shell.h"

25
// qt/kde includes
Albert Astals Cid's avatar
Albert Astals Cid committed
26 27 28 29
#include <KActionCollection>
#include <KConfigGroup>
#include <KIO/Global>
#include <KLocalizedString>
30
#include <KMessageBox>
Albert Astals Cid's avatar
Albert Astals Cid committed
31
#include <KPluginLoader>
32 33
#include <KRecentFilesAction>
#include <KServiceTypeTrader>
Albert Astals Cid's avatar
Albert Astals Cid committed
34 35
#include <KSharedConfig>
#include <KStandardAction>
36
#include <KToggleFullScreenAction>
Albert Astals Cid's avatar
Albert Astals Cid committed
37 38
#include <KToolBar>
#include <KUrlMimeData>
39 40
#include <KWindowSystem>
#include <KXMLGUIFactory>
Albert Astals Cid's avatar
Albert Astals Cid committed
41 42
#include <QApplication>
#include <QDBusConnection>
43
#include <QDragMoveEvent>
Albert Astals Cid's avatar
Albert Astals Cid committed
44 45 46 47
#include <QFileDialog>
#include <QMenuBar>
#include <QMimeDatabase>
#include <QMimeType>
David Hurka's avatar
David Hurka committed
48
#include <QScreen>
Jonathan Doman's avatar
Jonathan Doman committed
49
#include <QTabBar>
Albert Astals Cid's avatar
Albert Astals Cid committed
50 51
#include <QTabWidget>
#include <QTimer>
52
#ifdef WITH_KACTIVITIES
Ivan Čukić's avatar
Ivan Čukić committed
53 54 55
#include <KActivities/ResourceInstance>
#endif

56 57
#include <kio_version.h>

58
// local includes
59
#include "../interfaces/viewerinterface.h"
Albert Astals Cid's avatar
Albert Astals Cid committed
60
#include "kdocumentviewer.h"
61
#include "shellutils.h"
62

63 64 65
static const char *shouldShowMenuBarComingFromFullScreen = "shouldShowMenuBarComingFromFullScreen";
static const char *shouldShowToolBarComingFromFullScreen = "shouldShowToolBarComingFromFullScreen";

Albert Astals Cid's avatar
Albert Astals Cid committed
66 67
static const char *const SESSION_URL_KEY = "Urls";
static const char *const SESSION_TAB_KEY = "ActiveTab";
68

Albert Astals Cid's avatar
Albert Astals Cid committed
69 70 71 72
Shell::Shell(const QString &serializedOptions)
    : KParts::MainWindow()
    , m_menuBarWasShown(true)
    , m_toolBarWasShown(true)
73
#ifndef Q_OS_WIN
74
    , m_activityResource(nullptr)
Ivan Čukić's avatar
Ivan Čukić committed
75
#endif
76
    , m_isValid(true)
77
{
Albert Astals Cid's avatar
Albert Astals Cid committed
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
    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;
97
    }
98

Albert Astals Cid's avatar
Albert Astals Cid committed
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
    // 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);

        m_tabs.append(firstPart);
        m_tabWidget->addTab(firstPart->widget(), QString()); // triggers setActiveTab that calls createGUI( part )

        connectPart(firstPart);

        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."));
    }
150 151
}

152 153
bool Shell::eventFilter(QObject *obj, QEvent *event)
{
154 155
    Q_UNUSED(obj);

Albert Astals Cid's avatar
Albert Astals Cid committed
156
    QDragMoveEvent *dmEvent = dynamic_cast<QDragMoveEvent *>(event);
157 158 159 160 161 162
    if (dmEvent) {
        bool accept = dmEvent->mimeData()->hasUrls();
        event->setAccepted(accept);
        return accept;
    }

Albert Astals Cid's avatar
Albert Astals Cid committed
163
    QDropEvent *dEvent = dynamic_cast<QDropEvent *>(event);
164 165 166 167 168 169
    if (dEvent) {
        const QList<QUrl> list = KUrlMimeData::urlsFromMimeData(dEvent->mimeData());
        handleDroppedUrls(list);
        dEvent->setAccepted(true);
        return true;
    }
Bob Bai's avatar
Bob Bai committed
170 171 172

    // Handle middle button click events on the tab bar
    if (obj == m_tabWidget && event->type() == QEvent::MouseButtonRelease) {
Albert Astals Cid's avatar
Albert Astals Cid committed
173
        QMouseEvent *mEvent = static_cast<QMouseEvent *>(event);
Bob Bai's avatar
Bob Bai committed
174 175 176 177 178 179 180 181
        if (mEvent->button() == Qt::MiddleButton) {
            int tabIndex = m_tabWidget->tabBar()->tabAt(mEvent->pos());
            if (tabIndex != -1) {
                closeTab(tabIndex);
                return true;
            }
        }
    }
182
    return KParts::MainWindow::eventFilter(obj, event);
183 184
}

185 186 187 188 189
bool Shell::isValid() const
{
    return m_isValid;
}

190 191 192 193 194
void Shell::showOpenRecentMenu()
{
    m_recent->menu()->popup(QCursor::pos());
}

195 196
Shell::~Shell()
{
Albert Astals Cid's avatar
Albert Astals Cid committed
197
    if (!m_tabs.empty()) {
198
        writeSettings();
Albert Astals Cid's avatar
Albert Astals Cid committed
199 200
        for (const TabState &tab : qAsConst(m_tabs)) {
            tab.part->closeUrl(false);
Jonathan Doman's avatar
Jonathan Doman committed
201
        }
202
        m_tabs.clear();
203
    }
204
    if (m_unique)
Laurent Montel's avatar
Laurent Montel committed
205
        QDBusConnection::sessionBus().unregisterService(QStringLiteral("org.kde.okular"));
David Rosca's avatar
David Rosca committed
206 207

    delete m_tabWidget;
208 209
}

210 211
// Open a new document if we have space for it
// This can hang if called on a unique instance and openUrl pops a messageBox
Albert Astals Cid's avatar
Albert Astals Cid committed
212
bool Shell::openDocument(const QUrl &url, const QString &serializedOptions)
213
{
Albert Astals Cid's avatar
Albert Astals Cid committed
214 215
    if (m_tabs.size() <= 0)
        return false;
216

Albert Astals Cid's avatar
Albert Astals Cid committed
217
    KParts::ReadWritePart *const part = m_tabs[0].part;
218 219

    // Return false if we can't open new tabs and the only part is occupied
Albert Astals Cid's avatar
Albert Astals Cid committed
220
    if (!dynamic_cast<Okular::ViewerInterface *>(part)->openNewFilesInTabs() && !part->url().isEmpty() && !ShellUtils::unique(serializedOptions)) {
221
        return false;
222
    }
223

Albert Astals Cid's avatar
Albert Astals Cid committed
224
    openUrl(url, serializedOptions);
225 226 227 228

    return true;
}

Albert Astals Cid's avatar
Albert Astals Cid committed
229
bool Shell::openDocument(const QString &urlString, const QString &serializedOptions)
230 231 232 233
{
    return openDocument(QUrl(urlString), serializedOptions);
}

Albert Astals Cid's avatar
Albert Astals Cid committed
234
bool Shell::canOpenDocs(int numDocs, int desktop)
235
{
Albert Astals Cid's avatar
Albert Astals Cid committed
236 237
    if (m_tabs.size() <= 0 || numDocs <= 0 || m_unique)
        return false;
238

Albert Astals Cid's avatar
Albert Astals Cid committed
239 240
    KParts::ReadWritePart *const part = m_tabs[0].part;
    const bool allowTabs = dynamic_cast<Okular::ViewerInterface *>(part)->openNewFilesInTabs();
241

Albert Astals Cid's avatar
Albert Astals Cid committed
242 243
    if (!allowTabs && (numDocs > 1 || !part->url().isEmpty()))
        return false;
244

Albert Astals Cid's avatar
Albert Astals Cid committed
245 246 247
    const KWindowInfo winfo(window()->effectiveWinId(), KWindowSystem::WMDesktop);
    if (winfo.desktop() != desktop)
        return false;
248

Albert Astals Cid's avatar
Albert Astals Cid committed
249
    return true;
250 251
}

Albert Astals Cid's avatar
Albert Astals Cid committed
252
void Shell::openUrl(const QUrl &url, const QString &serializedOptions)
Laurent Montel's avatar
Laurent Montel committed
253
{
Jonathan Doman's avatar
Jonathan Doman committed
254
    const int activeTab = m_tabWidget->currentIndex();
Albert Astals Cid's avatar
Albert Astals Cid committed
255 256 257 258 259 260 261 262 263 264 265
    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<Okular::ViewerInterface *>(activePart)->openNewFilesInTabs()) {
                    openNewTab(url, serializedOptions);
                } else {
                    Shell *newShell = new Shell(serializedOptions);
Jonathan Doman's avatar
Jonathan Doman committed
266
                    newShell->show();
Albert Astals Cid's avatar
Albert Astals Cid committed
267
                    newShell->openUrl(url, serializedOptions);
Jonathan Doman's avatar
Jonathan Doman committed
268
                }
269
            }
Albert Astals Cid's avatar
Albert Astals Cid committed
270 271 272 273 274 275 276
        } 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) {
277
#ifdef WITH_KACTIVITIES
Albert Astals Cid's avatar
Albert Astals Cid committed
278 279
                    if (!m_activityResource)
                        m_activityResource = new KActivities::ResourceInstance(window()->winId(), this);
Ivan Čukić's avatar
Ivan Čukić committed
280

Albert Astals Cid's avatar
Albert Astals Cid committed
281
                    m_activityResource->setUri(url);
Ivan Čukić's avatar
Ivan Čukić committed
282
#endif
Albert Astals Cid's avatar
Albert Astals Cid committed
283 284 285 286
                    m_recent->addUrl(url);
                } else {
                    m_recent->removeUrl(url);
                    closeTab(activeTab);
287
                }
288
            }
289
        }
290
    }
Laurent Montel's avatar
Laurent Montel committed
291 292
}

293 294
void Shell::closeUrl()
{
Albert Astals Cid's avatar
Albert Astals Cid committed
295
    closeTab(m_tabWidget->currentIndex());
296
}
Laurent Montel's avatar
Laurent Montel committed
297 298

void Shell::readSettings()
299
{
Albert Astals Cid's avatar
Albert Astals Cid committed
300 301
    m_recent->loadEntries(KSharedConfig::openConfig()->group("Recent Files"));
    m_recent->setEnabled(true); // force enabling
302

Albert Astals Cid's avatar
Albert Astals Cid committed
303 304 305
    const KConfigGroup group = KSharedConfig::openConfig()->group("Desktop Entry");
    bool fullScreen = group.readEntry("FullScreen", false);
    setFullScreen(fullScreen);
306

Albert Astals Cid's avatar
Albert Astals Cid committed
307 308 309
    if (fullScreen) {
        m_menuBarWasShown = group.readEntry(shouldShowMenuBarComingFromFullScreen, true);
        m_toolBarWasShown = group.readEntry(shouldShowToolBarComingFromFullScreen, true);
310
    }
311 312
}

Laurent Montel's avatar
Laurent Montel committed
313 314
void Shell::writeSettings()
{
Albert Astals Cid's avatar
Albert Astals Cid committed
315 316 317 318 319 320
    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);
321
    }
322
    KSharedConfig::openConfig()->sync();
323 324
}

325
void Shell::setupActions()
326
{
Albert Astals Cid's avatar
Albert Astals Cid committed
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
    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("<b>Click</b> to open a file or <b>Click and hold</b> 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);
362 363
}

Tobias Koenig's avatar
Tobias Koenig committed
364
void Shell::saveProperties(KConfigGroup &group)
365
{
Albert Astals Cid's avatar
Albert Astals Cid committed
366
    if (!m_isValid) // part couldn't be loaded, nothing to save
367 368
        return;

369 370
    // Gather lists of settings to preserve
    QStringList urls;
Albert Astals Cid's avatar
Albert Astals Cid committed
371 372
    for (const TabState &tab : qAsConst(m_tabs)) {
        urls.append(tab.part->url().url());
373
    }
Albert Astals Cid's avatar
Albert Astals Cid committed
374 375
    group.writePathEntry(SESSION_URL_KEY, urls);
    group.writeEntry(SESSION_TAB_KEY, m_tabWidget->currentIndex());
376 377
}

Pino Toscano's avatar
Pino Toscano committed
378
void Shell::readProperties(const KConfigGroup &group)
379
{
380
    // Reopen documents based on saved settings
Albert Astals Cid's avatar
Albert Astals Cid committed
381 382
    QStringList urls = group.readPathEntry(SESSION_URL_KEY, QStringList());
    int desiredTab = group.readEntry<int>(SESSION_TAB_KEY, 0);
383

Albert Astals Cid's avatar
Albert Astals Cid committed
384 385
    while (!urls.isEmpty()) {
        openUrl(QUrl(urls.takeFirst()));
386 387
    }

Albert Astals Cid's avatar
Albert Astals Cid committed
388 389
    if (desiredTab < m_tabs.size()) {
        setActiveTab(desiredTab);
390
    }
391 392
}

Pino Toscano's avatar
Pino Toscano committed
393
QStringList Shell::fileFormats() const
394
{
Pino Toscano's avatar
Pino Toscano committed
395
    QStringList supportedPatterns;
396

Albert Astals Cid's avatar
Albert Astals Cid committed
397 398 399
    QString constraint(QStringLiteral("(Library == 'okularpart')"));
    QLatin1String basePartService("KParts/ReadOnlyPart");
    KService::List offers = KServiceTypeTrader::self()->query(basePartService, constraint);
Laurent Montel's avatar
Laurent Montel committed
400
    KService::List::ConstIterator it = offers.constBegin(), itEnd = offers.constEnd();
Albert Astals Cid's avatar
Albert Astals Cid committed
401
    for (; it != itEnd; ++it) {
402
        KService::Ptr service = *it;
403
        QStringList mimeTypes = service->mimeTypes();
404 405

        supportedPatterns += mimeTypes;
406 407
    }

Pino Toscano's avatar
Pino Toscano committed
408
    return supportedPatterns;
409 410 411 412
}

void Shell::fileOpen()
{
Albert Astals Cid's avatar
Albert Astals Cid committed
413 414 415
    // 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
Jonathan Doman's avatar
Jonathan Doman committed
416
    const int activeTab = m_tabWidget->currentIndex();
Albert Astals Cid's avatar
Albert Astals Cid committed
417 418 419
    if (!m_fileformatsscanned) {
        const KDocumentViewer *const doc = qobject_cast<KDocumentViewer *>(m_tabs[activeTab].part);
        if (doc)
Jonathan Doman's avatar
Jonathan Doman committed
420
            m_fileformats = doc->supportedMimeTypes();
421

Albert Astals Cid's avatar
Albert Astals Cid committed
422
        if (m_fileformats.isEmpty())
423
            m_fileformats = fileFormats();
424

425
        m_fileformatsscanned = true;
426
    }
427

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
428
    QUrl startDir;
Albert Astals Cid's avatar
Albert Astals Cid committed
429 430
    const KParts::ReadWritePart *const curPart = m_tabs[activeTab].part;
    if (curPart->url().isLocalFile())
431
        startDir = KIO::upUrl(curPart->url());
432

Albert Astals Cid's avatar
Albert Astals Cid committed
433 434 435 436 437
    QPointer<QFileDialog> 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
438 439

    QMimeDatabase mimeDatabase;
440 441 442 443 444 445
#if KIO_VERSION >= QT_VERSION_CHECK(5, 73, 0)
    QStringList mimetypes;
    for (const QString &mimeName : qAsConst(m_fileformats)) {
        QMimeType mimeType = mimeDatabase.mimeTypeForName(mimeName);
        mimetypes << mimeType.name();
    }
Albert Astals Cid's avatar
Albert Astals Cid committed
446
    mimetypes << QStringLiteral("application/octet-stream");
447 448
    dlg->setMimeTypeFilters(mimetypes);
#else
449
    QSet<QString> globPatterns;
450
    QMap<QString, QStringList> namedGlobs;
Albert Astals Cid's avatar
Albert Astals Cid committed
451 452 453 454
    for (const QString &mimeName : qAsConst(m_fileformats)) {
        QMimeType mimeType = mimeDatabase.mimeTypeForName(mimeName);
        const QStringList globs(mimeType.globPatterns());
        if (globs.isEmpty()) {
455 456
            continue;
        }
457

Albert Astals Cid's avatar
Albert Astals Cid committed
458
        globPatterns.unite(globs.toSet());
459

Albert Astals Cid's avatar
Albert Astals Cid committed
460
        namedGlobs[mimeType.comment()].append(globs);
461
    }
462
    QStringList namePatterns;
Albert Astals Cid's avatar
Albert Astals Cid committed
463 464
    for (auto it = namedGlobs.cbegin(); it != namedGlobs.cend(); ++it) {
        namePatterns.append(it.key() + QLatin1String(" (") + it.value().join(QLatin1Char(' ')) + QLatin1Char(')'));
465
    }
466

467
    const QStringList allGlobPatterns = globPatterns.values();
Albert Astals Cid's avatar
Albert Astals Cid committed
468 469 470
    namePatterns.prepend(i18n("All files (*)"));
    namePatterns.prepend(i18n("All supported files (%1)", allGlobPatterns.join(QLatin1Char(' '))));
    dlg->setNameFilters(namePatterns);
471
#endif
472

Albert Astals Cid's avatar
Albert Astals Cid committed
473 474
    dlg->setWindowTitle(i18n("Open Document"));
    if (dlg->exec() && dlg) {
475
        const QList<QUrl> urlList = dlg->selectedUrls();
Albert Astals Cid's avatar
Albert Astals Cid committed
476 477
        for (const QUrl &url : urlList) {
            openUrl(url);
478 479
        }
    }
480

Albert Astals Cid's avatar
Albert Astals Cid committed
481
    if (dlg) {
482
        delete dlg.data();
483
    }
Laurent Montel's avatar
Laurent Montel committed
484
}
485

486 487
void Shell::tryRaise()
{
Albert Astals Cid's avatar
Albert Astals Cid committed
488
    KWindowSystem::forceActiveWindow(window()->effectiveWinId());
489 490
}

Albert Astals Cid's avatar
Albert Astals Cid committed
491
// only called when starting the program
Albert Astals Cid's avatar
Albert Astals Cid committed
492
void Shell::setFullScreen(bool useFullScreen)
493
{
Albert Astals Cid's avatar
Albert Astals Cid committed
494 495
    if (useFullScreen)
        setWindowState(windowState() | Qt::WindowFullScreen); // set
496
    else
Albert Astals Cid's avatar
Albert Astals Cid committed
497
        setWindowState(windowState() & ~Qt::WindowFullScreen); // reset
498 499
}

Albert Astals Cid's avatar
Albert Astals Cid committed
500
void Shell::setCaption(const QString &caption)
501 502 503 504
{
    bool modified = false;

    const int activeTab = m_tabWidget->currentIndex();
Albert Astals Cid's avatar
Albert Astals Cid committed
505 506
    if (activeTab >= 0 && activeTab < m_tabs.size()) {
        KParts::ReadWritePart *const activePart = m_tabs[activeTab].part;
507
        QString tabCaption = activePart->url().fileName();
Albert Astals Cid's avatar
Albert Astals Cid committed
508
        if (activePart->isModified()) {
509
            modified = true;
Albert Astals Cid's avatar
Albert Astals Cid committed
510 511
            if (!tabCaption.isEmpty()) {
                tabCaption.append(QStringLiteral(" *"));
512 513 514
            }
        }

Albert Astals Cid's avatar
Albert Astals Cid committed
515
        m_tabWidget->setTabText(activeTab, tabCaption);
516 517
    }

Albert Astals Cid's avatar
Albert Astals Cid committed
518
    setCaption(caption, modified);
519 520
}

521 522
void Shell::showEvent(QShowEvent *e)
{
523
    if (!menuBar()->isNativeMenuBar() && m_showMenuBarAction)
Albert Astals Cid's avatar
Albert Astals Cid committed
524
        m_showMenuBarAction->setChecked(menuBar()->isVisible());
525 526 527 528

    KParts::MainWindow::showEvent(e);
}

529 530
void Shell::slotUpdateFullScreen()
{
Albert Astals Cid's avatar
Albert Astals Cid committed
531 532 533
    if (m_fullScreenAction->isChecked()) {
        m_menuBarWasShown = !menuBar()->isHidden();
        menuBar()->hide();
534

Albert Astals Cid's avatar
Albert Astals Cid committed
535 536
        m_toolBarWasShown = !toolBar()->isHidden();
        toolBar()->hide();
537

Albert Astals Cid's avatar
Albert Astals Cid committed
538 539 540 541 542 543 544 545 546
        KToggleFullScreenAction::setFullScreen(this, true);
    } else {
        if (m_menuBarWasShown) {
            menuBar()->show();
        }
        if (m_toolBarWasShown) {
            toolBar()->show();
        }
        KToggleFullScreenAction::setFullScreen(this, false);
547 548 549
    }
}

550 551
void Shell::slotShowMenubar()
{
Albert Astals Cid's avatar
Albert Astals Cid committed
552
    if (menuBar()->isHidden())
553 554 555 556 557
        menuBar()->show();
    else
        menuBar()->hide();
}

558 559
QSize Shell::sizeHint() const
{
David Hurka's avatar
David Hurka committed
560
    return QApplication::primaryScreen()->availableSize() * 0.75;
561 562
}

563 564
bool Shell::queryClose()
{
Albert Astals Cid's avatar
Albert Astals Cid committed
565
    if (m_tabs.count() > 1) {
Laurent Montel's avatar
Laurent Montel committed
566
        const QString dontAskAgainName = QStringLiteral("ShowTabWarning");
567
        KMessageBox::ButtonCode dummy = KMessageBox::Yes;
Albert Astals Cid's avatar
Albert Astals Cid committed
568
        if (shouldBeShownYesNo(dontAskAgainName, dummy)) {
569 570 571 572 573
            QDialog *dialog = new QDialog(this);
            dialog->setWindowTitle(i18n("Confirm Close"));

            QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog);
            buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No);
Laurent Montel's avatar
Laurent Montel committed
574
            KGuiItem::assign(buttonBox->button(QDialogButtonBox::Yes), KGuiItem(i18n("Close Tabs"), QStringLiteral("tab-close")));
575 576 577
            KGuiItem::assign(buttonBox->button(QDialogButtonBox::No), KStandardGuiItem::cancel());

            bool checkboxResult = true;
Albert Astals Cid's avatar
Albert Astals Cid committed
578 579 580 581 582 583 584 585 586 587
            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) {
588 589 590
                saveDontShowAgainYesNo(dontAskAgainName, dummy);
            }

Albert Astals Cid's avatar
Albert Astals Cid committed
591
            if (result != QDialogButtonBox::Yes) {
592 593 594 595 596
                return false;
            }
        }
    }

Albert Astals Cid's avatar
Albert Astals Cid committed
597 598
    for (int i = 0; i < m_tabs.size(); ++i) {
        KParts::ReadWritePart *const part = m_tabs[i].part;
Jonathan Doman's avatar
Jonathan Doman committed
599 600

        // To resolve confusion about multiple modified docs, switch to relevant tab
Albert Astals Cid's avatar
Albert Astals Cid committed
601 602
        if (part->isModified())
            setActiveTab(i);
Jonathan Doman's avatar
Jonathan Doman committed
603

Albert Astals Cid's avatar
Albert Astals Cid committed
604 605
        if (!part->queryClose())
            return false;
Jonathan Doman's avatar
Jonathan Doman committed
606 607 608 609
    }
    return true;
}

Albert Astals Cid's avatar
Albert Astals Cid committed
610
void Shell::setActiveTab(int tab)
Jonathan Doman's avatar
Jonathan Doman committed
611
{
Albert Astals Cid's avatar
Albert Astals Cid committed
612 613 614 615
    m_tabWidget->setCurrentIndex(tab);
    createGUI(m_tabs[tab].part);
    m_printAction->setEnabled(m_tabs[tab].printEnabled);
    m_closeAction->setEnabled(m_tabs[tab].closeEnabled);
Jonathan Doman's avatar
Jonathan Doman committed
616 617
}

Albert Astals Cid's avatar
Albert Astals Cid committed
618
void Shell::closeTab(int tab)
Jonathan Doman's avatar
Jonathan Doman committed
619
{
Albert Astals Cid's avatar
Albert Astals Cid committed
620
    KParts::ReadWritePart *const part = m_tabs[tab].part;
Bob Bai's avatar
Bob Bai committed
621
    QUrl url = part->url();
Albert Astals Cid's avatar
Albert Astals Cid committed
622 623 624
    if (part->closeUrl() && m_tabs.count() > 1) {
        if (part->factory())
            part->factory()->removeClient(part);
Jonathan Doman's avatar
Jonathan Doman committed
625 626
        part->disconnect();
        part->deleteLater();
Albert Astals Cid's avatar
Albert Astals Cid committed
627 628 629 630
        m_tabs.removeAt(tab);
        m_tabWidget->removeTab(tab);
        m_undoCloseTab->setEnabled(true);
        m_closedTabUrls.append(url);
Jonathan Doman's avatar
Jonathan Doman committed
631

Albert Astals Cid's avatar
Albert Astals Cid committed
632
        if (m_tabWidget->count() == 1) {
633
            m_tabWidget->tabBar()->hide();
Albert Astals Cid's avatar
Albert Astals Cid committed
634 635
            m_nextTabAction->setEnabled(false);
            m_prevTabAction->setEnabled(false);
Jonathan Doman's avatar
Jonathan Doman committed
636 637 638 639
        }
    }
}

Albert Astals Cid's avatar
Albert Astals Cid committed
640
void Shell::openNewTab(const QUrl &url, const QString &serializedOptions)
Jonathan Doman's avatar
Jonathan Doman committed
641
{
642
    const int previousActiveTab = m_tabWidget->currentIndex();
Albert Astals Cid's avatar
Albert Astals Cid committed
643
    KParts::ReadWritePart *const activePart = m_tabs[previousActiveTab].part;
644 645

    bool activateTabIfAlreadyOpen;
Albert Astals Cid's avatar
Albert Astals Cid committed
646
    QMetaObject::invokeMethod(activePart, "activateTabIfAlreadyOpenFile", Q_RETURN_ARG(bool, activateTabIfAlreadyOpen));
647

Albert Astals Cid's avatar
Albert Astals Cid committed
648 649
    if (activateTabIfAlreadyOpen) {
        const int tabIndex = findTabIndex(url);
650

Albert Astals Cid's avatar
Albert Astals Cid committed
651 652 653
        if (tabIndex >= 0) {
            setActiveTab(tabIndex);
            m_recent->addUrl(url);
654 655 656 657
            return;
        }
    }

658
    // Tabs are hidden when there's only one, so show it
Albert Astals Cid's avatar
Albert Astals Cid committed
659
    if (m_tabs.size() == 1) {
660
        m_tabWidget->tabBar()->show();
Albert Astals Cid's avatar
Albert Astals Cid committed
661 662
        m_nextTabAction->setEnabled(true);
        m_prevTabAction->setEnabled(true);
663
    }
Jonathan Doman's avatar
Jonathan Doman committed
664 665 666 667

    const int newIndex = m_tabs.size();

    // Make new part
Albert Astals Cid's avatar
Albert Astals Cid committed
668 669
    m_tabs.append(m_partFactory->create<KParts::ReadWritePart>(this));
    connectPart(m_tabs[newIndex].part);
Jonathan Doman's avatar
Jonathan Doman committed
670

671
    // Update GUI
Albert Astals Cid's avatar
Albert Astals Cid committed
672 673
    KParts::ReadWritePart *const part = m_tabs[newIndex].part;
    m_tabWidget->addTab(part->widget(), url.fileName());
674

675 676
    applyOptionsToPart(part, serializedOptions);

Albert Astals Cid's avatar
Albert Astals Cid committed
677
    setActiveTab(m_tabs.size() - 1);
678

Albert Astals Cid's avatar
Albert Astals Cid committed
679 680 681 682 683 684
    if (part->openUrl(url)) {
        m_recent->addUrl(url);
    } else {
        setActiveTab(previousActiveTab);
        closeTab(m_tabs.size() - 1);
        m_recent->removeUrl(url);
685
    }
686 687
}

Albert Astals Cid's avatar
Albert Astals Cid committed
688
void Shell::applyOptionsToPart(QObject *part, const QString &serializedOptions)
689
{
Albert Astals Cid's avatar
Albert Astals Cid committed
690
    KDocumentViewer *const doc = qobject_cast<KDocumentViewer *>(part);
691
    const QString find = ShellUtils::find(serializedOptions);
Albert Astals Cid's avatar
Albert Astals Cid committed
692
    if (ShellUtils::startInPresentation(serializedOptions))
693
        doc->startPresentation();
Albert Astals Cid's avatar
Albert Astals Cid committed
694 695 696 697 698 699
    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(QString, find));
Jonathan Doman's avatar
Jonathan Doman committed
700 701
}

Albert Astals Cid's avatar
Albert Astals Cid committed
702
void Shell::connectPart(QObject *part)
Jonathan Doman's avatar
Jonathan Doman committed
703
{
704
    // We're abusing the fact we know the part is our part here
Albert Astals Cid's avatar
Albert Astals Cid committed
705 706 707 708 709
    connect(this, SIGNAL(moveSplitter(int)), part, SLOT(moveSplitter(int)));                     // clazy:exclude=old-style-connect
    connect(part, SIGNAL(enablePrintAction(bool)), this, SLOT(setPrintEnabled(bool)));           // clazy:exclude=old-style-connect
    connect(part, SIGNAL(enableCloseAction(bool)), this, SLOT(setCloseEnabled(bool)));           // clazy:exclude=old-style-connect
    connect(part, SIGNAL(mimeTypeChanged(QMimeType)), this, SLOT(setTabIcon(QMimeType)));        // clazy:exclude=old-style-connect
    connect(part, SIGNAL(urlsDropped(QList<QUrl>)), this, SLOT(handleDroppedUrls(QList<QUrl>))); // clazy:exclude=old-style-connect
710 711 712 713
    // clang-format off
    // Otherwise the QSize,QSize gets turned into QSize, QSize that is not normalized signals and is slightly slower
    connect(part, SIGNAL(fitWindowToPage(QSize,QSize)), this, SLOT(slotFitWindowToPage(QSize,QSize)));   // clazy:exclude=old-style-connect
    // clang-format on
Jonathan Doman's avatar
Jonathan Doman committed
714 715 716 717
}

void Shell::print()
{
Albert Astals Cid's avatar
Albert Astals Cid committed
718
    QMetaObject::invokeMethod(m_tabs[m_tabWidget->currentIndex()].part, "slotPrint");
Jonathan Doman's avatar
Jonathan Doman committed
719 720
}

Albert Astals Cid's avatar
Albert Astals Cid committed
721
void Shell::setPrintEnabled(bool enabled)
Jonathan Doman's avatar
Jonathan Doman committed
722
{
Albert Astals Cid's avatar
Albert Astals Cid committed
723 724
    int i = findTabIndex(sender());
    if (i != -1) {
Jonathan Doman's avatar
Jonathan Doman committed
725
        m_tabs[i].printEnabled = enabled;
Albert Astals Cid's avatar
Albert Astals Cid committed
726 727
        if (i == m_tabWidget->currentIndex())
            m_printAction->setEnabled(enabled);
Jonathan Doman's avatar
Jonathan Doman committed
728 729 730
    }
}

Albert Astals Cid's avatar
Albert Astals Cid committed
731
void Shell::setCloseEnabled(bool enabled)
Jonathan Doman's avatar
Jonathan Doman committed
732
{
Albert Astals Cid's avatar
Albert Astals Cid committed
733 734
    int i = findTabIndex(sender());
    if (i != -1) {
Jonathan Doman's avatar
Jonathan Doman committed
735
        m_tabs[i].closeEnabled = enabled;
Albert Astals Cid's avatar
Albert Astals Cid committed
736 737
        if (i == m_tabWidget->currentIndex())
            m_closeAction->setEnabled(enabled);
Jonathan Doman's avatar
Jonathan Doman committed
738 739 740 741 742
    }
}

void Shell::activateNextTab()
{
Albert Astals Cid's avatar
Albert Astals Cid committed
743
    if (m_tabs.size() < 2)
Jonathan Doman's avatar
Jonathan Doman committed
744 745 746
        return;

    const int activeTab = m_tabWidget->currentIndex();
Albert Astals Cid's avatar
Albert Astals Cid committed
747
    const int nextTab = (activeTab == m_tabs.size() - 1) ? 0 : activeTab + 1;
Jonathan Doman's avatar
Jonathan Doman committed
748

Albert Astals Cid's avatar
Albert Astals Cid committed
749
    setActiveTab(nextTab);
Jonathan Doman's avatar
Jonathan Doman committed
750 751 752 753
}

void Shell::activatePrevTab()
{
Albert Astals Cid's avatar
Albert Astals Cid committed
754
    if (m_tabs.size() < 2)
Jonathan Doman's avatar
Jonathan Doman committed
755 756 757
        return;

    const int activeTab = m_tabWidget->currentIndex();
Albert Astals Cid's avatar
Albert Astals Cid committed
758
    const int prevTab = (activeTab == 0) ? m_tabs.size() - 1 : activeTab - 1;
Jonathan Doman's avatar
Jonathan Doman committed
759

Albert Astals Cid's avatar
Albert Astals Cid committed
760
    setActiveTab(prevTab);
Jonathan Doman's avatar
Jonathan Doman committed
761 762
}

Bob Bai's avatar
Bob Bai committed
763 764
void Shell::undoCloseTab()
{
Albert Astals Cid's avatar
Albert Astals Cid committed
765
    if (m_closedTabUrls.isEmpty()) {
Bob Bai's avatar
Bob Bai committed
766 767 768 769 770
        return;
    }

    const QUrl lastTabUrl = m_closedTabUrls.takeLast();

Albert Astals Cid's avatar
Albert Astals Cid committed
771
    if (m_closedTabUrls.isEmpty()) {
Bob Bai's avatar
Bob Bai committed
772 773 774 775 776 777
        m_undoCloseTab->setEnabled(false);
    }

    openUrl(lastTabUrl);
}

Albert Astals Cid's avatar
Albert Astals Cid committed
778
void Shell::setTabIcon(const QMimeType &mimeType)
Jonathan Doman's avatar
Jonathan Doman committed
779
{
Albert Astals Cid's avatar
Albert Astals Cid committed
780 781 782
    int i = findTabIndex(sender());
    if (i != -1) {
        m_tabWidget->setTabIcon(i, QIcon::fromTheme(mimeType.iconName()));
Jonathan Doman's avatar
Jonathan Doman committed
783 784 785
    }
}

Albert Astals Cid's avatar
Albert Astals Cid committed
786
int Shell::findTabIndex(QObject *sender) const
Jonathan Doman's avatar
Jonathan Doman committed
787
{
Albert Astals Cid's avatar
Albert Astals Cid committed
788 789
    for (int i = 0; i < m_tabs.size(); ++i) {
        if (m_tabs[i].part == sender) {
Jonathan Doman's avatar
Jonathan Doman committed
790 791 792 793
            return i;
        }
    }
    return -1;
794 795
}

Albert Astals Cid's avatar
Albert Astals Cid committed
796
int Shell::findTabIndex(const QUrl &url) const
797
{
Albert Astals Cid's avatar
Albert Astals Cid committed
798 799
    auto it = std::find_if(m_tabs.begin(), m_tabs.end(), [&url](const TabState state) { return state.part->url() == url; });
    return (it != m_tabs.end()) ? std::distance(m_tabs.begin(), it) : -1;
800 801
}

Albert Astals Cid's avatar
Albert Astals Cid committed
802
void Shell::handleDroppedUrls(const QList<QUrl> &urls)
803
{
Albert Astals Cid's avatar
Albert Astals Cid committed
804 805
    for (const QUrl &url : urls) {
        openUrl(url);
806 807 808
    }
}

Albert Astals Cid's avatar
Albert Astals Cid committed
809
void Shell::moveTabData(int from, int to)
Jonathan Doman's avatar
Jonathan Doman committed
810
{
Albert Astals Cid's avatar
Albert Astals Cid committed
811
    m_tabs.move(from, to);
Jonathan Doman's avatar
Jonathan Doman committed
812 813
}

Albert Astals Cid's avatar
Albert Astals Cid committed
814
void Shell::slotFitWindowToPage(const QSize pageViewSize, const QSize pageSize)
815 816 817 818
{
    const int xOffset = pageViewSize.width() - pageSize.width();
    const int yOffset = pageViewSize.height() - pageSize.height();
    showNormal();
Albert Astals Cid's avatar
Albert Astals Cid committed
819
    resize(width() - xOffset, height() - yOffset);
820
    emit moveSplitter(pageSize.width());
821
}
822 823

/* kate: replace-tabs on; indent-width 4; */