File indexing completed on 2024-05-12 05:52:09
0001 /* 0002 SPDX-FileCopyrightText: 2022 Jiří Wolker <woljiri@gmail.com> 0003 SPDX-FileCopyrightText: 2022 Eugene Popov <popov895@ukr.net> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "welcomeview.h" 0009 0010 #include "kateapp.h" 0011 #include "katemainwindow.h" 0012 #include "kateviewmanager.h" 0013 #include "recentitemsmodel.h" 0014 #include "savedsessionsmodel.h" 0015 0016 #include <KAboutData> 0017 #include <KConfigGroup> 0018 #include <KIO/OpenFileManagerWindowJob> 0019 #include <KIconLoader> 0020 #include <KRecentFilesAction> 0021 #include <KSharedConfig> 0022 #include <QTimer> 0023 0024 #include <QClipboard> 0025 #include <QDesktopServices> 0026 #include <QFileInfo> 0027 #include <QGraphicsOpacityEffect> 0028 #include <QLabel> 0029 #include <QMenu> 0030 0031 class Placeholder : public QLabel 0032 { 0033 public: 0034 explicit Placeholder(QWidget *parent = nullptr) 0035 : QLabel(parent) 0036 { 0037 setAlignment(Qt::AlignCenter); 0038 setMargin(20); 0039 setWordWrap(true); 0040 // Match opacity of QML placeholder label component 0041 QGraphicsOpacityEffect *opacityEffect = new QGraphicsOpacityEffect; 0042 opacityEffect->setOpacity(0.5); 0043 setGraphicsEffect(opacityEffect); 0044 } 0045 }; 0046 0047 WelcomeView::WelcomeView(KateViewManager *viewManager, QWidget *parent) 0048 : QScrollArea(parent) 0049 , m_viewManager(viewManager) 0050 { 0051 setupUi(this); 0052 0053 listViewRecentItems->setSelectionMode(QAbstractItemView::MultiSelection); 0054 0055 const KAboutData aboutData = KAboutData::applicationData(); 0056 labelTitle->setText(i18n("Welcome to %1", aboutData.displayName())); 0057 labelDescription->setText(aboutData.shortDescription()); 0058 labelIcon->setPixmap(aboutData.programLogo().value<QIcon>().pixmap(KIconLoader::SizeEnormous)); 0059 0060 labelRecentItems->setText(KateApp::isKate() ? i18n("Recent Documents and Projects") : i18n("Recent Documents")); 0061 0062 m_placeholderRecentItems = new Placeholder; 0063 m_placeholderRecentItems->setText(KateApp::isKate() ? i18n("No recent documents or projects") : i18n("No recent documents")); 0064 0065 QVBoxLayout *layoutPlaceholderRecentItems = new QVBoxLayout; 0066 layoutPlaceholderRecentItems->addWidget(m_placeholderRecentItems); 0067 listViewRecentItems->setLayout(layoutPlaceholderRecentItems); 0068 0069 m_recentItemsModel = new RecentItemsModel(this); 0070 connect(m_recentItemsModel, &RecentItemsModel::modelReset, this, [this]() { 0071 const bool noRecentItems = m_recentItemsModel->rowCount() == 0; 0072 buttonClearRecentItems->setDisabled(noRecentItems); 0073 m_placeholderRecentItems->setVisible(noRecentItems); 0074 }); 0075 0076 KRecentFilesAction *recentFilesAction = m_viewManager->mainWindow()->recentFilesAction(); 0077 m_recentItemsModel->refresh(recentFilesAction->urls()); 0078 recentFilesAction->menu()->installEventFilter(this); 0079 0080 listViewRecentItems->setModel(m_recentItemsModel); 0081 connect(listViewRecentItems, &QListView::customContextMenuRequested, this, &WelcomeView::onRecentItemsContextMenuRequested); 0082 connect(listViewRecentItems, &QListView::activated, this, [this](const QModelIndex &index) { 0083 if (index.isValid()) { 0084 const QUrl url = m_recentItemsModel->url(index); 0085 Q_ASSERT(url.isValid()); 0086 m_viewManager->openUrlOrProject(url); 0087 } 0088 }); 0089 0090 connect(buttonNewFile, &QPushButton::clicked, m_viewManager, &KateViewManager::slotDocumentNew); 0091 connect(buttonOpenFile, &QPushButton::clicked, m_viewManager, &KateViewManager::slotDocumentOpen); 0092 connect(buttonClearRecentItems, &QPushButton::clicked, this, [recentFilesAction]() { 0093 recentFilesAction->clear(); 0094 }); 0095 0096 connect(labelHomepage, qOverload<>(&KUrlLabel::leftClickedUrl), this, [aboutData]() { 0097 QDesktopServices::openUrl(QUrl(aboutData.homepage())); 0098 }); 0099 connect(labelContribute, qOverload<>(&KUrlLabel::leftClickedUrl), this, []() { 0100 QDesktopServices::openUrl(QUrl(QStringLiteral("https://kate-editor.org/join-us"))); 0101 }); 0102 connect(labelHandbook, qOverload<>(&KUrlLabel::leftClickedUrl), this, [this]() { 0103 m_viewManager->mainWindow()->appHelpActivated(); 0104 }); 0105 0106 onPluginViewChanged(); 0107 0108 const KTextEditor::MainWindow *mainWindow = m_viewManager->mainWindow()->wrapper(); 0109 connect(mainWindow, &KTextEditor::MainWindow::pluginViewCreated, this, &WelcomeView::onPluginViewChanged); 0110 connect(mainWindow, &KTextEditor::MainWindow::pluginViewDeleted, this, &WelcomeView::onPluginViewChanged); 0111 0112 if (KateApp::isKWrite()) { 0113 widgetSavedSessions->hide(); 0114 } else { 0115 m_placeholderSavedSessions = new Placeholder; 0116 m_placeholderSavedSessions->setText(i18n("No saved sessions")); 0117 0118 QVBoxLayout *layoutPlaceholderSavedSessions = new QVBoxLayout; 0119 layoutPlaceholderSavedSessions->addWidget(m_placeholderSavedSessions); 0120 listViewSavedSessions->setLayout(layoutPlaceholderSavedSessions); 0121 0122 m_savedSessionsModel = new SavedSessionsModel(this); 0123 connect(m_savedSessionsModel, &SavedSessionsModel::modelReset, this, [this]() { 0124 const bool noSavedSession = m_savedSessionsModel->rowCount() == 0; 0125 m_placeholderSavedSessions->setVisible(noSavedSession); 0126 }); 0127 0128 KateSessionManager *sessionManager = KateApp::self()->sessionManager(); 0129 m_savedSessionsModel->refresh(sessionManager->sessionList()); 0130 connect(sessionManager, &KateSessionManager::sessionListChanged, this, [this, sessionManager]() { 0131 m_savedSessionsModel->refresh(sessionManager->sessionList()); 0132 }); 0133 0134 listViewSavedSessions->setModel(m_savedSessionsModel); 0135 connect(listViewSavedSessions, &QListView::activated, this, [sessionManager](const QModelIndex &index) { 0136 if (index.isValid()) { 0137 const QString sessionName = index.data().toString(); 0138 Q_ASSERT(!sessionName.isEmpty()); 0139 sessionManager->activateSession(sessionName); 0140 } 0141 }); 0142 0143 connect(buttonNewSession, &QPushButton::clicked, sessionManager, &KateSessionManager::sessionNew); 0144 connect(buttonManageSessions, &QPushButton::clicked, this, []() { 0145 KateSessionManager::sessionManage(); 0146 }); 0147 } 0148 0149 static const char showForNewWindowKey[] = "Show welcome view for new window"; 0150 KConfigGroup configGroup = KSharedConfig::openConfig()->group(QStringLiteral("General")); 0151 checkBoxShowForNewWindow->setChecked(configGroup.readEntry(showForNewWindowKey, true)); 0152 connect(checkBoxShowForNewWindow, &QCheckBox::toggled, this, [configGroup](bool checked) mutable { 0153 configGroup.writeEntry(showForNewWindowKey, checked); 0154 }); 0155 0156 connect(KateApp::self(), &KateApp::configurationChanged, this, [this, configGroup]() { 0157 checkBoxShowForNewWindow->setChecked(configGroup.readEntry(showForNewWindowKey, true)); 0158 }); 0159 0160 updateFonts(); 0161 updateButtons(); 0162 } 0163 0164 bool WelcomeView::event(QEvent *event) 0165 { 0166 switch (event->type()) { 0167 case QEvent::FontChange: 0168 updateFonts(); 0169 updateButtons(); 0170 break; 0171 case QEvent::Resize: 0172 if (updateLayout()) { 0173 return true; 0174 } 0175 break; 0176 default: 0177 break; 0178 } 0179 0180 return QScrollArea::event(event); 0181 } 0182 0183 void WelcomeView::resizeEvent(QResizeEvent *event) 0184 { 0185 QScrollArea::resizeEvent(event); 0186 0187 updateLayout(); 0188 } 0189 0190 bool WelcomeView::eventFilter(QObject *watched, QEvent *event) 0191 { 0192 KRecentFilesAction *recentFilesAction = m_viewManager->mainWindow()->recentFilesAction(); 0193 if (watched == recentFilesAction->menu()) { 0194 switch (event->type()) { 0195 case QEvent::ActionAdded: 0196 case QEvent::ActionRemoved: 0197 // since the KRecentFilesAction doesn't notify about adding or 0198 // deleting items, we should use this dirty trick to find out 0199 // the KRecentFilesAction has changed 0200 QTimer::singleShot(0, this, [this, recentFilesAction]() { 0201 m_recentItemsModel->refresh(recentFilesAction->urls()); 0202 }); 0203 break; 0204 default: 0205 break; 0206 } 0207 } 0208 0209 return QScrollArea::eventFilter(watched, event); 0210 } 0211 0212 void WelcomeView::onPluginViewChanged(const QString &pluginName) 0213 { 0214 static const QString projectPluginName = QStringLiteral("kateprojectplugin"); 0215 if (pluginName.isEmpty() || pluginName == projectPluginName) { 0216 QObject *projectPluginView = m_viewManager->mainWindow()->pluginView(projectPluginName); 0217 if (projectPluginView) { 0218 connect(buttonOpenFolder, SIGNAL(clicked()), projectPluginView, SLOT(openDirectoryOrProject())); 0219 buttonOpenFolder->show(); 0220 } else { 0221 buttonOpenFolder->hide(); 0222 } 0223 } 0224 } 0225 0226 void WelcomeView::onRecentItemsContextMenuRequested(const QPoint &pos) 0227 { 0228 const QModelIndex index = listViewRecentItems->indexAt(pos); 0229 if (!index.isValid()) { 0230 return; 0231 } 0232 0233 const QUrl url = m_recentItemsModel->url(index); 0234 Q_ASSERT(url.isValid()); 0235 0236 QMenu contextMenu(listViewRecentItems); 0237 0238 const auto selectedIndexes = listViewRecentItems->selectionModel()->selectedIndexes(); 0239 auto allSelectedAreFiles = [this, selectedIndexes] { 0240 return std::all_of(selectedIndexes.begin(), selectedIndexes.end(), [model = m_recentItemsModel](const QModelIndex &index) { 0241 const QUrl url = model->url(index); 0242 return !url.isLocalFile() || QFileInfo(url.toLocalFile()).isFile(); 0243 }); 0244 }; 0245 if (selectedIndexes.size() > 1 && allSelectedAreFiles()) { 0246 QAction *action = new QAction(i18n("Open Selected Files..."), this); 0247 action->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); 0248 connect(action, &QAction::triggered, this, [this, selectedIndexes]() { 0249 for (const auto &index : selectedIndexes) { 0250 const auto url = m_recentItemsModel->url(index); 0251 if (url.isValid()) { 0252 m_viewManager->openUrl(url); 0253 } 0254 } 0255 }); 0256 contextMenu.addAction(action); 0257 } 0258 0259 QAction *action = new QAction(i18n("Copy &Location"), this); 0260 action->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy-path"))); 0261 connect(action, &QAction::triggered, this, [url]() { 0262 qApp->clipboard()->setText(url.toString(QUrl::PreferLocalFile)); 0263 }); 0264 contextMenu.addAction(action); 0265 0266 action = new QAction(i18n("&Open Containing Folder"), this); 0267 action->setEnabled(url.isLocalFile()); 0268 action->setIcon(QIcon::fromTheme(QStringLiteral("document-open-folder"))); 0269 connect(action, &QAction::triggered, this, [url]() { 0270 KIO::highlightInFileManager({url}); 0271 }); 0272 contextMenu.addAction(action); 0273 0274 action = new QAction(i18n("&Remove"), this); 0275 action->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); 0276 connect(action, &QAction::triggered, this, [this, url]() { 0277 KRecentFilesAction *recentFilesAction = m_viewManager->mainWindow()->recentFilesAction(); 0278 recentFilesAction->removeUrl(url); 0279 m_recentItemsModel->refresh(recentFilesAction->urls()); 0280 }); 0281 contextMenu.addAction(action); 0282 0283 contextMenu.exec(listViewRecentItems->mapToGlobal(pos)); 0284 } 0285 0286 void WelcomeView::updateButtons() 0287 { 0288 std::vector<QPushButton *> buttons{buttonNewFile, buttonOpenFile, buttonOpenFolder}; 0289 if (KateApp::isKate()) { 0290 buttons.push_back(buttonNewSession); 0291 buttons.push_back(buttonManageSessions); 0292 } 0293 const int maxWidth = std::accumulate(buttons.cbegin(), buttons.cend(), 0, [](int maxWidth, const QPushButton *button) { 0294 return std::max(maxWidth, button->sizeHint().width()); 0295 }); 0296 for (QPushButton *button : std::as_const(buttons)) { 0297 button->setFixedWidth(maxWidth); 0298 } 0299 } 0300 0301 void WelcomeView::updateFonts() 0302 { 0303 QFont titleFont = font(); 0304 titleFont.setPointSize(titleFont.pointSize() + 6); 0305 titleFont.setWeight(QFont::Bold); 0306 labelTitle->setFont(titleFont); 0307 0308 QFont panelTitleFont = font(); 0309 panelTitleFont.setPointSize(panelTitleFont.pointSize() + 2); 0310 labelRecentItems->setFont(panelTitleFont); 0311 labelSavedSessions->setFont(panelTitleFont); 0312 labelHelp->setFont(panelTitleFont); 0313 0314 QFont placeholderFont = font(); 0315 placeholderFont.setPointSize(qRound(placeholderFont.pointSize() * 1.3)); 0316 m_placeholderRecentItems->setFont(placeholderFont); 0317 if (m_placeholderSavedSessions) { 0318 m_placeholderSavedSessions->setFont(placeholderFont); 0319 } 0320 } 0321 0322 bool WelcomeView::updateLayout() 0323 { 0324 // Align labelHelp with labelRecentItems 0325 labelHelp->setMinimumHeight(labelRecentItems->height()); 0326 0327 bool result = false; 0328 0329 // show/hide widgetHeader depending on the view height 0330 if (widgetHeader->isVisible()) { 0331 if (height() <= frameContent->height()) { 0332 widgetHeader->hide(); 0333 result = true; 0334 } 0335 } else { 0336 const int implicitHeight = frameContent->height() + widgetHeader->height() + layoutContent->spacing(); 0337 if (height() > implicitHeight) { 0338 widgetHeader->show(); 0339 result = true; 0340 } 0341 } 0342 0343 // show/hide widgetHelp depending on the view height 0344 if (widgetHelp->isVisible()) { 0345 if (width() <= frameContent->width()) { 0346 widgetHelp->hide(); 0347 result = true; 0348 } 0349 } else { 0350 const int implicitWidth = frameContent->width() + widgetHelp->width() + layoutPanels->horizontalSpacing(); 0351 if (width() > implicitWidth) { 0352 widgetHelp->show(); 0353 return true; 0354 } 0355 } 0356 0357 return result; 0358 } 0359 0360 #include "moc_welcomeview.cpp"