From f24aa63a97b0220a69682655e2326fe66ac6a519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Wolker?= Date: Fri, 7 May 2021 19:07:44 +0200 Subject: [PATCH] Create welcome screen This screen is similar to the one in Ark, but also shows you a list of recently-opened documents. --- autotests/CMakeLists.txt | 4 +- shell/CMakeLists.txt | 7 +- shell/recentitemsmodel.cpp | 125 +++++++++ shell/recentitemsmodel.h | 64 +++++ shell/shell.cpp | 69 ++++- shell/shell.h | 12 + shell/welcomescreen.cpp | 189 +++++++++++++ shell/welcomescreen.h | 49 ++++ shell/welcomescreen.ui | 526 +++++++++++++++++++++++++++++++++++++ 9 files changed, 1038 insertions(+), 7 deletions(-) create mode 100644 shell/recentitemsmodel.cpp create mode 100644 shell/recentitemsmodel.h create mode 100644 shell/welcomescreen.cpp create mode 100644 shell/welcomescreen.h create mode 100644 shell/welcomescreen.ui diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 47b350989..098af33d9 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -110,7 +110,7 @@ ecm_add_test(calculatetexttest.cpp ) if(KF5Activities_FOUND AND BUILD_DESKTOP) - ecm_add_test(mainshelltest.cpp ../shell/okular_main.cpp ../shell/shellutils.cpp ../shell/shell.cpp closedialoghelper.cpp + ecm_add_test(mainshelltest.cpp ../shell/okular_main.cpp ../shell/shellutils.cpp ../shell/shell.cpp ../shell/welcomescreen.cpp ../shell/recentitemsmodel.cpp closedialoghelper.cpp TEST_NAME "mainshelltest" LINK_LIBRARIES Qt5::Test KF5::Activities okularpart okularcore ) @@ -118,7 +118,7 @@ if(KF5Activities_FOUND AND BUILD_DESKTOP) endif() if(BUILD_DESKTOP) - ecm_add_test(annotationtoolbartest.cpp ../shell/okular_main.cpp ../shell/shellutils.cpp ../shell/shell.cpp closedialoghelper.cpp + ecm_add_test(annotationtoolbartest.cpp ../shell/okular_main.cpp ../shell/shellutils.cpp ../shell/shell.cpp ../shell/welcomescreen.cpp ../shell/recentitemsmodel.cpp closedialoghelper.cpp TEST_NAME "annotationtoolbartest" LINK_LIBRARIES Qt5::Test okularpart ) diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt index 4259a5e1c..fbd460322 100644 --- a/shell/CMakeLists.txt +++ b/shell/CMakeLists.txt @@ -11,14 +11,19 @@ set(okular_SRCS okular_main.cpp shell.cpp shellutils.cpp + recentitemsmodel.cpp + welcomescreen.cpp ) +ki18n_wrap_ui(okular_SRCS + welcomescreen.ui) + file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/../icons/*-apps-okular.png") ecm_add_app_icon(okular_SRCS ICONS ${ICONS_SRCS}) add_executable(okular ${okular_SRCS}) -target_link_libraries(okular KF5::I18n KF5::Parts KF5::WindowSystem KF5::Crash Qt5::DBus) +target_link_libraries(okular KF5::I18n KF5::Parts KF5::WindowSystem KF5::Crash KF5::IconThemes Qt5::DBus) if(TARGET KF5::Activities) target_compile_definitions(okular PUBLIC -DWITH_KACTIVITIES=1) diff --git a/shell/recentitemsmodel.cpp b/shell/recentitemsmodel.cpp new file mode 100644 index 000000000..965792c0b --- /dev/null +++ b/shell/recentitemsmodel.cpp @@ -0,0 +1,125 @@ +/* + SPDX-FileCopyrightText: 2021 Jiří Wolker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "recentitemsmodel.h" + +#include +#include +#include +#include + +#include + +RecentItemsModel::RecentItemsModel() +{ +} + +RecentItemsModel::~RecentItemsModel() +{ +} + +void RecentItemsModel::loadEntries(const KConfigGroup &cg) +{ + clearEntries(); + + // Based on implementation of KRecentFilesAction::loadEntries. + + QString key; + QString value; + QString nameKey; + QString nameValue; + QUrl url; + + // read file list + for (int i = 1; i <= maxItems(); i++) { + key = QStringLiteral("File%1").arg(i); + value = cg.readPathEntry(key, QString()); + if (value.isEmpty()) { + continue; + } + url = QUrl::fromUserInput(value); + + // Don't restore if file doesn't exist anymore + if (url.isLocalFile() && !QFile::exists(url.toLocalFile())) { + continue; + } + + nameKey = QStringLiteral("Name%1").arg(i); + nameValue = cg.readPathEntry(nameKey, url.fileName()); + m_recentItems.append(RecentItem {.name = nameValue, .url = url}); + } + + Q_EMIT layoutChanged(); +} + +void RecentItemsModel::clearEntries() +{ + m_recentItems.clear(); + + Q_EMIT layoutChanged(); +} + +int RecentItemsModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + + return m_recentItems.size(); +} + +QVariant RecentItemsModel::data(const QModelIndex &index, int role) const +{ + const RecentItemsModel::RecentItem *item = getItem(index); + + if (item != nullptr) { + switch (role) { + case Qt::ItemDataRole::DisplayRole: + if (item->name.isEmpty()) { + if (item->url.isLocalFile()) { + return item->url.toLocalFile(); + } else { + return item->url.toString(); + } + } else { + return item->name; + } + + case Qt::ItemDataRole::ToolTipRole: + if (item->url.isLocalFile()) { + return item->url.toLocalFile(); + } else { + return item->url.toString(); + } + + case Qt::ItemDataRole::DecorationRole: + if (item->url.isLocalFile()) { + return m_iconProvider.icon(QFileInfo(item->url.toLocalFile())); + } else { + // Fallback icon for remote files. + return QIcon::fromTheme(QStringLiteral("network-server")); + } + + default: + return QVariant(); + } + } else { + return QVariant(); + } +} + +RecentItemsModel::RecentItem const *RecentItemsModel::getItem(int index) const +{ + if (m_recentItems.size() > index && index >= 0) { + // We reverse the order of items. + return &m_recentItems[m_recentItems.size() - index - 1]; + } else { + return nullptr; + } +} + +RecentItemsModel::RecentItem const *RecentItemsModel::getItem(const QModelIndex &index) const +{ + return getItem(index.row()); +} diff --git a/shell/recentitemsmodel.h b/shell/recentitemsmodel.h new file mode 100644 index 000000000..0725ca629 --- /dev/null +++ b/shell/recentitemsmodel.h @@ -0,0 +1,64 @@ +/* + SPDX-FileCopyrightText: 2021 Jiří Wolker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef RECENTITEMSMODEL_H +#define RECENTITEMSMODEL_H + +#include +#include +#include +#include +#include +#include + +class KConfigGroup; + +/** + * @todo write docs + */ +class RecentItemsModel : public QAbstractListModel +{ + Q_OBJECT + +public: + struct RecentItem { + QString name; + QUrl url; + }; + + /** + * Default constructor + */ + RecentItemsModel(); + + /** + * Destructor + */ + ~RecentItemsModel() override; + + void loadEntries(const KConfigGroup &cg); + void clearEntries(); + + int maxItems() const + { + return m_maxItems; + } + + RecentItemsModel::RecentItem const *getItem(const QModelIndex &) const; + RecentItemsModel::RecentItem const *getItem(int index) const; + + // Model implementation: + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::ItemDataRole::DisplayRole) const override; + +private: + QList m_recentItems; + + int m_maxItems = 20; + QFileIconProvider m_iconProvider; +}; + +#endif // RECENTITEMSMODEL_H diff --git a/shell/shell.cpp b/shell/shell.cpp index 5a0badf0a..fbc8d02ef 100644 --- a/shell/shell.cpp +++ b/shell/shell.cpp @@ -95,6 +95,20 @@ Shell::Shell(const QString &serializedOptions) // now that the Part plugin is loaded, create the part KParts::ReadWritePart *const firstPart = m_partFactory->create(this); if (firstPart) { + // Setup the central widget + m_centralStackedWidget = new QStackedWidget(); + setCentralWidget(m_centralStackedWidget); + + // Setup the welcome screen + m_welcomeScreen = new WelcomeScreen(this); + connect(m_welcomeScreen, &WelcomeScreen::openClicked, this, &Shell::fileOpen); + connect(m_welcomeScreen, &WelcomeScreen::closeClicked, this, &Shell::hideWelcomeScreen); + connect(m_welcomeScreen, &WelcomeScreen::recentItemClicked, this, [this](const QUrl &url) { openUrl(url); }); + connect(m_welcomeScreen, &WelcomeScreen::forgetRecentItem, this, &Shell::forgetRecentItem); + m_centralStackedWidget->addWidget(m_welcomeScreen); + + m_welcomeScreen->installEventFilter(this); + // Setup tab bar m_tabWidget = new QTabWidget(this); m_tabWidget->setTabsClosable(true); @@ -106,12 +120,12 @@ Shell::Shell(const QString &serializedOptions) m_tabWidget->setAcceptDrops(true); m_tabWidget->tabBar()->installEventFilter(this); + m_centralStackedWidget->addWidget(m_tabWidget); + 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); @@ -146,6 +160,9 @@ Shell::Shell(const QString &serializedOptions) } QDBusConnection::sessionBus().registerObject(QStringLiteral("/okularshell"), this, QDBusConnection::ExportScriptableSlots); + + // Make sure that the welcome scren is visible on startup. + showWelcomeScreen(); } else { m_isValid = false; KMessageBox::error(this, i18n("Unable to find the Okular component.")); @@ -233,6 +250,8 @@ bool Shell::openDocument(const QUrl &url, const QString &serializedOptions) if (m_tabs.size() <= 0) return false; + hideWelcomeScreen(); + KParts::ReadWritePart *const part = m_tabs[0].part; // Return false if we can't open new tabs and the only part is occupied @@ -270,6 +289,8 @@ bool Shell::canOpenDocs(int numDocs, int desktop) void Shell::openUrl(const QUrl &url, const QString &serializedOptions) { + hideWelcomeScreen(); + const int activeTab = m_tabWidget->currentIndex(); if (activeTab < m_tabs.size()) { KParts::ReadWritePart *const activePart = m_tabs[activeTab].part; @@ -343,7 +364,7 @@ void Shell::readSettings() void Shell::writeSettings() { - m_recent->saveEntries(KSharedConfig::openConfig()->group("Recent Files")); + saveRecents(); KConfigGroup group = KSharedConfig::openConfig()->group("Desktop Entry"); group.writeEntry("FullScreen", m_fullScreenAction->isChecked()); if (m_fullScreenAction->isChecked()) { @@ -353,12 +374,19 @@ void Shell::writeSettings() KSharedConfig::openConfig()->sync(); } +void Shell::saveRecents() +{ + m_recent->saveEntries(KSharedConfig::openConfig()->group("Recent Files")); +} + 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); + connect(m_recent, &KRecentFilesAction::recentListCleared, this, &Shell::refreshRecentsOnWelcomeScreen); + connect(m_welcomeScreen, &WelcomeScreen::forgetAllRecents, m_recent, &KRecentFilesAction::clear); 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()); @@ -652,7 +680,8 @@ void Shell::closeTab(int tab) { KParts::ReadWritePart *const part = m_tabs[tab].part; QUrl url = part->url(); - if (part->closeUrl() && m_tabs.count() > 1) { + bool closeSuccess = part->closeUrl(); + if (closeSuccess && m_tabs.count() > 1) { if (part->factory()) part->factory()->removeClient(part); part->disconnect(); @@ -667,6 +696,10 @@ void Shell::closeTab(int tab) m_nextTabAction->setEnabled(false); m_prevTabAction->setEnabled(false); } + } else if (closeSuccess && m_tabs.count() == 1) { + // Show welcome screen when the last tab is closed. + + showWelcomeScreen(); } } @@ -675,6 +708,8 @@ void Shell::openNewTab(const QUrl &url, const QString &serializedOptions) const int previousActiveTab = m_tabWidget->currentIndex(); KParts::ReadWritePart *const activePart = m_tabs[previousActiveTab].part; + hideWelcomeScreen(); + bool activateTabIfAlreadyOpen; QMetaObject::invokeMethod(activePart, "activateTabIfAlreadyOpenFile", Q_RETURN_ARG(bool, activateTabIfAlreadyOpen)); @@ -853,4 +888,30 @@ void Shell::slotFitWindowToPage(const QSize pageViewSize, const QSize pageSize) emit moveSplitter(pageSize.width()); } +void Shell::hideWelcomeScreen() +{ + m_centralStackedWidget->setCurrentWidget(m_tabWidget); +} + +void Shell::showWelcomeScreen() +{ + m_centralStackedWidget->setCurrentWidget(m_welcomeScreen); + refreshRecentsOnWelcomeScreen(); +} + +void Shell::refreshRecentsOnWelcomeScreen() +{ + saveRecents(); + m_welcomeScreen->loadRecents(); +} + +void Shell::forgetRecentItem(QUrl const &url) +{ + if (m_recent != nullptr) { + m_recent->removeUrl(url); + saveRecents(); + refreshRecentsOnWelcomeScreen(); + } +} + /* kate: replace-tabs on; indent-width 4; */ diff --git a/shell/shell.h b/shell/shell.h index 139058bf3..2aec58bd1 100644 --- a/shell/shell.h +++ b/shell/shell.h @@ -19,8 +19,11 @@ #include #include +#include #include // krazy:exclude=includes +#include "welcomescreen.h" + class KRecentFilesAction; class KToggleAction; class QTabWidget; @@ -137,10 +140,17 @@ private Q_SLOTS: void slotFitWindowToPage(const QSize pageViewSize, const QSize pageSize); + void hideWelcomeScreen(); + void showWelcomeScreen(); + void refreshRecentsOnWelcomeScreen(); + + void forgetRecentItem(QUrl const &url); + Q_SIGNALS: void moveSplitter(int sideWidgetSize); private: + void saveRecents(); void setupAccel(); void setupActions(); void openNewTab(const QUrl &url, const QString &serializedOptions); @@ -165,6 +175,8 @@ private: bool m_unique; QTabWidget *m_tabWidget; KToggleAction *m_openInTab; + WelcomeScreen *m_welcomeScreen; + QStackedWidget *m_centralStackedWidget; struct TabState { explicit TabState(KParts::ReadWritePart *p) diff --git a/shell/welcomescreen.cpp b/shell/welcomescreen.cpp new file mode 100644 index 000000000..f0863d516 --- /dev/null +++ b/shell/welcomescreen.cpp @@ -0,0 +1,189 @@ +/* + SPDX-FileCopyrightText: 2021 Jiří Wolker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "welcomescreen.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "recentitemsmodel.h" + +class RecentsListItemDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + explicit RecentsListItemDelegate(WelcomeScreen *welcomeScreen) + : m_welcomeScreen(welcomeScreen) + { + } + + WelcomeScreen *welcomeScreen() const + { + return m_welcomeScreen; + } + + bool editorEvent(QEvent *event, QAbstractItemModel *aModel, const QStyleOptionViewItem &styleOptionViewItem, const QModelIndex &index) override + { + const RecentItemsModel *model = static_cast(aModel); + const RecentItemsModel::RecentItem *item = model->getItem(index); + + bool willOpenMenu = false; + QPoint menuPosition; + + if (item != nullptr) { + if (event->type() == QEvent::ContextMenu) { + willOpenMenu = true; + menuPosition = static_cast(event)->globalPos(); + } + if (event->type() == QEvent::MouseButtonPress) { + if (static_cast(event)->button() == Qt::MouseButton::RightButton) { + willOpenMenu = true; + menuPosition = static_cast(event)->globalPos(); + } + } + + if (willOpenMenu) { + event->accept(); + + QMenu menu; + + QAction *copyPathAction = new QAction(i18n("&Copy Path")); + copyPathAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); + connect(copyPathAction, &QAction::triggered, this, [item]() { + QString path; + if (item->url.isLocalFile()) { + path = item->url.toLocalFile(); + } else { + path = item->url.toString(); + } + QGuiApplication::clipboard()->setText(path); + }); + menu.addAction(copyPathAction); + + QAction *showDirectoryAction = new QAction(i18n("&Open Containing Folder")); + showDirectoryAction->setIcon(QIcon::fromTheme(QStringLiteral("document-open-folder"))); + connect(showDirectoryAction, &QAction::triggered, this, [item]() { + if (item->url.isLocalFile()) { + QFileInfo fileInfo(item->url.toLocalFile()); + QDir parentDir = fileInfo.dir(); + QUrl parentDirUrl = QUrl::fromLocalFile(parentDir.absolutePath()); + QDesktopServices::openUrl(parentDirUrl); + } + }); + menu.addAction(showDirectoryAction); + if (!item->url.isLocalFile()) { + showDirectoryAction->setEnabled(false); + } + + QAction *forgetItemAction = new QAction(i18nc("recent items context menu", "&Forget This Item")); + forgetItemAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history"))); + connect(forgetItemAction, &QAction::triggered, this, [this, item]() { Q_EMIT welcomeScreen()->forgetRecentItem(item->url); }); + menu.addAction(forgetItemAction); + + menu.exec(menuPosition); + + return true; + } + } + + return QStyledItemDelegate::editorEvent(event, aModel, styleOptionViewItem, index); + } + +private: + WelcomeScreen *m_welcomeScreen; +}; + +WelcomeScreen::WelcomeScreen(QWidget *parent) + : QWidget(parent) + , m_recentsModel(new RecentItemsModel) + , m_recentsItemDelegate(new RecentsListItemDelegate(this)) +{ + Q_ASSERT(parent); + + setupUi(this); + + KIconLoader loader; + + appIcon->setPixmap(loader.loadIcon(QStringLiteral("okular"), KIconLoader::Group::Desktop, KIconLoader::SizeEnormous)); + + connect(openButton, &QPushButton::clicked, this, &WelcomeScreen::openClicked); + connect(closeButton, &QPushButton::clicked, this, &WelcomeScreen::closeClicked); + + recentsListView->setContextMenuPolicy(Qt::DefaultContextMenu); + recentsListView->setModel(m_recentsModel); + recentsListView->setItemDelegate(m_recentsItemDelegate); + connect(recentsListView, &QListView::activated, this, &WelcomeScreen::recentsItemActivated); + + connect(m_recentsModel, &RecentItemsModel::layoutChanged, this, &WelcomeScreen::recentListChanged); + + QVBoxLayout *noRecentsLayout = new QVBoxLayout(recentsListView); + recentsListView->setLayout(noRecentsLayout); + m_noRecentsLabel = new QLabel(recentsListView); + QFont placeholderLabelFont; + // To match the size of a level 2 Heading/KTitleWidget + placeholderLabelFont.setPointSize(qRound(placeholderLabelFont.pointSize() * 1.3)); + noRecentsLayout->addWidget(m_noRecentsLabel); + m_noRecentsLabel->setFont(placeholderLabelFont); + m_noRecentsLabel->setTextInteractionFlags(Qt::NoTextInteraction); + m_noRecentsLabel->setWordWrap(true); + m_noRecentsLabel->setAlignment(Qt::AlignCenter); + m_noRecentsLabel->setText(i18nc("on welcome screen", "No recent documents")); + // Match opacity of QML placeholder label component + auto *effect = new QGraphicsOpacityEffect(m_noRecentsLabel); + effect->setOpacity(0.5); + m_noRecentsLabel->setGraphicsEffect(effect); + + connect(forgetAllButton, &QToolButton::clicked, this, &WelcomeScreen::forgetAllRecents); +} + +WelcomeScreen::~WelcomeScreen() +{ + delete m_recentsModel; + delete m_recentsItemDelegate; +} + +void WelcomeScreen::loadRecents() +{ + m_recentsModel->loadEntries(KSharedConfig::openConfig()->group("Recent Files")); +} + +int WelcomeScreen::recentsCount() +{ + return m_recentsModel->rowCount(); +} + +void WelcomeScreen::recentsItemActivated(const QModelIndex &index) +{ + const RecentItemsModel::RecentItem *item = m_recentsModel->getItem(index); + if (item != nullptr) { + Q_EMIT recentItemClicked(item->url); + } +} + +void WelcomeScreen::recentListChanged() +{ + if (recentsCount() == 0) { + m_noRecentsLabel->show(); + forgetAllButton->setEnabled(false); + } else { + m_noRecentsLabel->hide(); + forgetAllButton->setEnabled(true); + } +} + +#include "welcomescreen.moc" diff --git a/shell/welcomescreen.h b/shell/welcomescreen.h new file mode 100644 index 000000000..073645e00 --- /dev/null +++ b/shell/welcomescreen.h @@ -0,0 +1,49 @@ +/* + SPDX-FileCopyrightText: 2021 Jiří Wolker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef WELCOMESCREEN_H +#define WELCOMESCREEN_H + +#include "shell/ui_welcomescreen.h" + +#include +#include + +class KRecentFilesAction; +class QListWidgetItem; +class RecentItemsModel; +class RecentsListItemDelegate; + +class WelcomeScreen : public QWidget, Ui::WelcomeScreen +{ + Q_OBJECT +public: + explicit WelcomeScreen(QWidget *parent = nullptr); + ~WelcomeScreen() override; + + void loadRecents(); + +Q_SIGNALS: + void openClicked(); + void closeClicked(); + void recentItemClicked(QUrl const &url); + void forgetAllRecents(); + void forgetRecentItem(QUrl const &url); + +private Q_SLOTS: + void recentsItemActivated(QModelIndex const &index); + void recentListChanged(); + +private: + int recentsCount(); + + RecentItemsModel *m_recentsModel; + RecentsListItemDelegate *m_recentsItemDelegate; + + QLabel *m_noRecentsLabel; +}; + +#endif // WELCOMESCREEN_H diff --git a/shell/welcomescreen.ui b/shell/welcomescreen.ui new file mode 100644 index 000000000..2af3cfadc --- /dev/null +++ b/shell/welcomescreen.ui @@ -0,0 +1,526 @@ + + + + WelcomeScreen + + + + 0 + 0 + 1423 + 850 + + + + true + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 48 + 20 + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 48 + + + + + + + + + 0 + 0 + + + + + 0 + 256 + + + + + 700 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 1 + 0 + + + + + 192 + 16777215 + + + + QFrame::NoFrame + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + 12 + 75 + true + + + + Welcome to Okular + + + Qt::AlignCenter + + + 0 + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 12 + 12 + + + + + + + + + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 12 + + + + + + + + QLayout::SetMinimumSize + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 48 + 0 + + + + + + + + Open Document... + + + + .. + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 48 + 0 + + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 48 + 20 + + + + + + + + + 0 + 0 + + + + + 0 + + + + + + 0 + 0 + + + + Qt::Vertical + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 48 + 20 + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + 0 + + + + + + 0 + 0 + + + + + 12 + + + + Recent documents + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Forget All + + + + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + + + + + 0 + 1 + + + + + 200 + 128 + + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAsNeeded + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Hide welcome screen + + + + + + + .. + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 48 + + + + + + + + openButton + + + +