Commit 2cda9f65 authored by Eric Armbruster's avatar Eric Armbruster 🍁
Browse files

Adapt welcomeview from Okular for Kate

parent 97f28219
......@@ -66,7 +66,7 @@ if (TARGET KUserFeedbackWidgets)
target_compile_definitions(kateprivate PUBLIC -DWITH_KUSERFEEDBACK)
endif()
ki18n_wrap_ui(kateprivate ui/sessionconfigwidget.ui ui/welcome.ui session/katesessionmanagedialog.ui)
ki18n_wrap_ui(kateprivate ui/sessionconfigwidget.ui welcomeview.ui session/katesessionmanagedialog.ui)
target_sources(
kateprivate
......@@ -97,7 +97,6 @@ target_sources(
katesplitter.cpp
kateviewmanager.cpp
kateviewspace.cpp
katewelcomeview.cpp
tabmimedata.cpp
kateoutputview.cpp
......@@ -119,6 +118,9 @@ target_sources(
diff/gitdiff.cpp
filehistorywidget.cpp
recentitemsmodel.cpp
welcomeview.cpp
)
if(BUILD_TESTING)
......
......@@ -75,6 +75,7 @@
#include <QToolButton>
#include "diffwidget.h"
#include "welcomeview.h"
#include <ktexteditor/sessionconfiginterface.h>
......@@ -307,6 +308,8 @@ void KateMainWindow::setupActions()
m_fileOpenRecent->setMaxItems(KateConfigDialog::recentFilesMaxCount());
actionCollection()->addAction(m_fileOpenRecent->objectName(), m_fileOpenRecent);
m_fileOpenRecent->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again."));
connect(m_fileOpenRecent, &KRecentFilesAction::recentListCleared, this, &KateMainWindow::refreshRecentsOnWelcomeView);
connect(m_welcomeView, &WelcomeView::forgetAllRecents, m_fileOpenRecent, &KRecentFilesAction::clear);
a = actionCollection()->addAction(QStringLiteral("file_save_all"));
a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-all")));
......@@ -1491,3 +1494,57 @@ void KateMainWindow::addRecentOpenedFile(const QUrl &url)
// to the global "Recent Document Menu", see bug 420504
// KRecentDocument::add(url);
}
void KateMainWindow::hideWelcomeView()
{
if (auto welcomeView = qobject_cast<WelcomeView *>((viewManager()->activeViewSpace())->currentWidget())) {
QTimer::singleShot(0, welcomeView, [this, welcomeView]() {
removeWidget(welcomeView);
});
}
}
void KateMainWindow::showWelcomeView()
{
// delay the creation, e.g. used on startup
QTimer::singleShot(0, this, [this]() {
if (activeView())
return;
auto vs = viewManager()->activeViewSpace();
if (vs) {
m_welcomeView = new WelcomeView(this);
// todo m_welcomeView->installEventFilter(this);
connect(m_welcomeView, &WelcomeView::openClicked, this, [this]() {
viewManager()->slotDocumentOpen();
});
connect(m_welcomeView, &WelcomeView::recentItemClicked, this, [this](const QUrl &url) {
openUrl(url);
});
connect(m_welcomeView, &WelcomeView::forgetRecentItem, this, &KateMainWindow::forgetRecentItem);
addWidget(m_welcomeView);
refreshRecentsOnWelcomeView();
}
});
}
void KateMainWindow::refreshRecentsOnWelcomeView()
{
saveRecents();
m_welcomeView->loadRecents();
}
void KateMainWindow::forgetRecentItem(QUrl const &url)
{
if (m_fileOpenRecent != nullptr) {
m_fileOpenRecent->removeUrl(url);
saveRecents();
refreshRecentsOnWelcomeView();
}
}
void KateMainWindow::saveRecents()
{
m_fileOpenRecent->saveEntries(KSharedConfig::openConfig()->group("Recent Files"));
}
......@@ -44,6 +44,7 @@ class KRecentFilesAction;
class KateOutputView;
class KateViewManager;
class KateMwModOnHdDialog;
class WelcomeView;
// Helper layout class to always provide minimum size
class KateContainerStackedLayout : public QStackedLayout
......@@ -303,6 +304,9 @@ public:
*/
void addRecentOpenedFile(const QUrl &url);
// TODO
void saveRecents();
//
// KTextEditor::MainWindow interface, get called by invokeMethod from our wrapper object!
//
......@@ -558,6 +562,13 @@ public Q_SLOTS:
*/
void showMessage(const QVariantMap &map);
// TODO
public Q_SLOTS:
void hideWelcomeView();
void showWelcomeView();
void refreshRecentsOnWelcomeView();
void forgetRecentItem(QUrl const &url);
private Q_SLOTS:
void slotUpdateBottomViewBar();
......@@ -672,6 +683,8 @@ private:
*/
KateOutputView *m_outputView = nullptr;
WelcomeView *m_welcomeView = nullptr;
public:
/**
* The available actions for the mouse back button, used as indexing
......
......@@ -13,7 +13,7 @@
#include "katemainwindow.h"
#include "kateupdatedisabler.h"
#include "kateviewspace.h"
#include "katewelcomeview.h"
#include "welcomeview.h"
#include <KTextEditor/Attribute>
#include <KTextEditor/Document>
......@@ -75,7 +75,7 @@ KateViewManager::KateViewManager(QWidget *parentW, KateMainWindow *parent)
connect(KateApp::self()->documentManager(), &KateDocManager::documentsDeleted, this, &KateViewManager::documentsDeleted);
// ensure we have the welcome view if no active view is there
showWelcomeViewIfNeeded();
mainWindow()->showWelcomeView();
}
KateViewManager::~KateViewManager()
......@@ -497,12 +497,7 @@ KTextEditor::View *KateViewManager::createView(KTextEditor::Document *doc, KateV
/**
* ensure the initial welcome view vanishes as soon as we have some real view!
*/
if (auto welcomeView = qobject_cast<KateWelcomeView *>((vs ? vs : activeViewSpace())->currentWidget())) {
// delay the deletion, we might be in event handling of some action from the welcome view itself!
QTimer::singleShot(0, welcomeView, [this, welcomeView]() {
mainWindow()->removeWidget(welcomeView);
});
}
mainWindow()->hideWelcomeView();
/**
* create view, registers its XML gui itself
......@@ -1098,7 +1093,7 @@ void KateViewManager::onViewSpaceEmptied(KateViewSpace *vs)
}
// else we want to trigger showing of the welcome view
showWelcomeViewIfNeeded();
mainWindow()->showWelcomeView();
}
void KateViewManager::setShowUrlNavBar(bool show)
......@@ -1512,13 +1507,3 @@ void KateViewManager::moveSplitter(Qt::Key key, int repeats)
currentSplitter = qobject_cast<KateSplitter *>(currentSplitter->parentWidget());
}
}
void KateViewManager::showWelcomeViewIfNeeded()
{
// delay the creation, e.g. used on startup
QTimer::singleShot(0, this, [this]() {
if (activeView())
return;
mainWindow()->addWidget(new KateWelcomeView());
});
}
......@@ -309,12 +309,6 @@ public Q_SLOTS:
void setShowUrlNavBar(bool show);
bool showUrlNavBar() const;
/**
* Trigger creation of welcome view via timer.
* Will do nothing if we have some view around when the timer triggers.
*/
void showWelcomeViewIfNeeded();
private:
KateMainWindow *m_mainWindow;
......
/*
SPDX-FileCopyrightText: 2021 Jiří Wolker <woljiri@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "recentitemsmodel.h"
#include "kateconfigdialog.h"
#include <QFile>
#include <QFileIconProvider>
#include <QFileInfo>
#include <QIcon>
#include <KConfigGroup>
RecentItemsModel::RecentItemsModel()
{
m_maxItems = KateConfigDialog::recentFilesMaxCount();
}
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());
}
\ No newline at end of file
/*
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;
QFileIconProvider m_iconProvider;
};
#endif // RECENTITEMSMODEL_H
\ No newline at end of file
/*
SPDX-FileCopyrightText: 2021 Jiří Wolker <woljiri@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "welcomeview.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 <QVBoxLayout>
#include <klocalizedstring.h>
#include "kateviewspace.h"
#include "recentitemsmodel.h"
class RecentsListItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit RecentsListItemDelegate(WelcomeView *welcomeView)
: m_welcomeView(welcomeView)
{
}
WelcomeView *welcomeView() const
{
return m_welcomeView;
}
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 welcomeView()->forgetRecentItem(item->url);
});
menu.addAction(forgetItemAction);
menu.exec(menuPosition);
return true;
}
}
return QStyledItemDelegate::editorEvent(event, aModel, styleOptionViewItem, index);
}
private:
WelcomeView *m_welcomeView;
};
WelcomeView::WelcomeView(QWidget *parent)
: QWidget(parent)
, m_recentsModel(new RecentItemsModel)
, m_recentsItemDelegate(new RecentsListItemDelegate(this))
{
Q_ASSERT(parent);
setupUi(this);
setWindowTitle(i18n("Welcome"));
KIconLoader loader;
appIcon->setPixmap(loader.loadIcon(QStringLiteral("kate"), KIconLoader::Group::Desktop, KIconLoader::SizeEnormous));
connect(openButton, &QPushButton::clicked, this, &WelcomeView::openClicked);
recentsListView->setContextMenuPolicy(Qt::DefaultContextMenu);
recentsListView->setModel(m_recentsModel);
recentsListView->setItemDelegate(m_recentsItemDelegate);
connect(recentsListView, &QListView::activated, this, &WelcomeView::recentsItemActivated);
connect(m_recentsModel, &RecentItemsModel::layoutChanged, this, &WelcomeView::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, &WelcomeView::forgetAllRecents);
}
WelcomeView::~WelcomeView()
{
delete m_recentsModel;
delete m_recentsItemDelegate;
}
void WelcomeView::loadRecents()
{
m_recentsModel->loadEntries(KSharedConfig::openConfig()->group("Recent Files"));
}
int WelcomeView::recentsCount()
{
return m_recentsModel->rowCount();
}
void WelcomeView::recentsItemActivated(const QModelIndex &index)
{
const RecentItemsModel::RecentItem *item = m_recentsModel->getItem(index);
if (item != nullptr) {
Q_EMIT recentItemClicked(item->url);
}
}
void WelcomeView::recentListChanged()
{
if (recentsCount() == 0) {
m_noRecentsLabel->show();
forgetAllButton->setEnabled(false);
} else {
m_noRecentsLabel->hide();
forgetAllButton->setEnabled(true);
}
}
#include "welcomeview.moc"
/*
SPDX-FileCopyrightText: 2021 Jiří Wolker <woljiri@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef WELCOMEVIEW_H
#define WELCOMEVIEW_H
#include "ui_welcomeview.h"
#include <QFrame>
#include <QUrl>
class QListWidgetItem;
class QLabel;
class KRecentFilesAction;
class KateViewSpace;
class RecentItemsModel;
class RecentsListItemDelegate;