mirror of
https://invent.kde.org/graphics/okular
synced 2024-10-02 14:14:10 +00:00
Create welcome screen
This screen is similar to the one in Ark, but also shows you a list of recently-opened documents.
This commit is contained in:
parent
293337d994
commit
f24aa63a97
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
125
shell/recentitemsmodel.cpp
Normal file
125
shell/recentitemsmodel.cpp
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2021 Jiří Wolker <woljiri@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "recentitemsmodel.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileIconProvider>
|
||||
#include <QFileInfo>
|
||||
#include <QIcon>
|
||||
|
||||
#include <KConfigGroup>
|
||||
|
||||
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());
|
||||
}
|
64
shell/recentitemsmodel.h
Normal file
64
shell/recentitemsmodel.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2021 Jiří Wolker <woljiri@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef RECENTITEMSMODEL_H
|
||||
#define RECENTITEMSMODEL_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QFileIconProvider>
|
||||
#include <QList>
|
||||
#include <QModelIndex>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
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<RecentItemsModel::RecentItem> m_recentItems;
|
||||
|
||||
int m_maxItems = 20;
|
||||
QFileIconProvider m_iconProvider;
|
||||
};
|
||||
|
||||
#endif // RECENTITEMSMODEL_H
|
|
@ -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<KParts::ReadWritePart>(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("<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());
|
||||
|
@ -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; */
|
||||
|
|
|
@ -19,8 +19,11 @@
|
|||
#include <kparts/mainwindow.h>
|
||||
#include <kparts/readwritepart.h>
|
||||
|
||||
#include <QStackedWidget>
|
||||
#include <QtDBus> // 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)
|
||||
|
|
189
shell/welcomescreen.cpp
Normal file
189
shell/welcomescreen.cpp
Normal file
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2021 Jiří Wolker <woljiri@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "welcomescreen.h"
|
||||
|
||||
#include <KConfigGroup>
|
||||
#include <KIconLoader>
|
||||
#include <KSharedConfig>
|
||||
|
||||
#include <QAction>
|
||||
#include <QClipboard>
|
||||
#include <QDesktopServices>
|
||||
#include <QDir>
|
||||
#include <QGraphicsOpacityEffect>
|
||||
#include <QGuiApplication>
|
||||
#include <QMenu>
|
||||
#include <QResizeEvent>
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
#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<RecentItemsModel *>(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<QContextMenuEvent *>(event)->globalPos();
|
||||
}
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
if (static_cast<QMouseEvent *>(event)->button() == Qt::MouseButton::RightButton) {
|
||||
willOpenMenu = true;
|
||||
menuPosition = static_cast<QMouseEvent *>(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"
|
49
shell/welcomescreen.h
Normal file
49
shell/welcomescreen.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2021 Jiří Wolker <woljiri@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef WELCOMESCREEN_H
|
||||
#define WELCOMESCREEN_H
|
||||
|
||||
#include "shell/ui_welcomescreen.h"
|
||||
|
||||
#include <QFrame>
|
||||
#include <QUrl>
|
||||
|
||||
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
|
526
shell/welcomescreen.ui
Normal file
526
shell/welcomescreen.ui
Normal file
|
@ -0,0 +1,526 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2021 Jiří Wolker <woljiri@gmail.com>
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
-->
|
||||
<ui version="4.0">
|
||||
<class>WelcomeScreen</class>
|
||||
<widget class="QWidget" name="WelcomeScreen">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1423</width>
|
||||
<height>850</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="acceptDrops">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout" rowstretch="3,5,0,3" columnstretch="1,4,1">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="1" column="0" rowspan="3">
|
||||
<spacer name="horizontalSpacer_8">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<spacer name="verticalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="1" rowspan="2">
|
||||
<widget class="QWidget" name="horizontalWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>256</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>700</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QFrame" name="verticalFrame">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Expanding">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>192</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Welcome to Okular</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>12</width>
|
||||
<height>12</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="appIcon">
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>12</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMinimumSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="openButton">
|
||||
<property name="text">
|
||||
<string comment="on welcome screen">Open Document...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-open">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Maximum</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="verticalWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="1">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Maximum</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="recentsArea" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3" stretch="0,1">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QWidget" name="horizontalWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string comment="on welcome screen">Recent documents</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="forgetAllButton">
|
||||
<property name="text">
|
||||
<string comment="on welcome screen (recent document list)">Forget All</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-clear-history"/>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextBesideIcon</enum>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListView" name="recentsListView">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>128</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<spacer name="horizontalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="closeButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Hide welcome screen</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string comment="on welcome screen"/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="window-close">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="2" rowspan="3">
|
||||
<spacer name="horizontalSpacer_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<spacer name="verticalSpacer_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>openButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Loading…
Reference in a new issue