File indexing completed on 2024-05-12 16:01:58

0001 /* This file is part of the KDE project
0002  * SPDX-FileCopyrightText: 2018 Scott Petrovic <scottpetrovic@gmail.com>
0003  * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 
0008 #include "KisWelcomePageWidget.h"
0009 #include "KisRecentDocumentsModelWrapper.h"
0010 #include <QDesktopServices>
0011 #include <QFileInfo>
0012 #include <QMimeData>
0013 #include <QPixmap>
0014 #include <QImage>
0015 #include <QMessageBox>
0016 #include <QTemporaryFile>
0017 
0018 #include "KisRemoteFileFetcher.h"
0019 #include "kactioncollection.h"
0020 #include "kis_action.h"
0021 #include "kis_action_manager.h"
0022 #include <KisMimeDatabase.h>
0023 
0024 #include "KConfigGroup"
0025 #include "KSharedConfig"
0026 
0027 #include <QListWidget>
0028 #include <QListWidgetItem>
0029 #include <QMenu>
0030 #include <QScrollBar>
0031 
0032 #include "kis_icon_utils.h"
0033 #include <kis_painting_tweaks.h>
0034 #include "KoStore.h"
0035 #include "kis_config.h"
0036 #include "KisDocument.h"
0037 #include <kis_image.h>
0038 #include <kis_paint_device.h>
0039 #include <KisPart.h>
0040 #include <KisKineticScroller.h>
0041 #include "KisMainWindow.h"
0042 
0043 #include <utils/KisUpdaterBase.h>
0044 
0045 #include <QCoreApplication>
0046 #include <kis_debug.h>
0047 #include <QDir>
0048 
0049 #include <array>
0050 
0051 #include "config-updaters.h"
0052 
0053 #ifdef ENABLE_UPDATERS
0054 #ifdef Q_OS_LINUX
0055 #include <utils/KisAppimageUpdater.h>
0056 #endif
0057 
0058 #include <utils/KisManualUpdater.h>
0059 #endif
0060 
0061 #include <klocalizedstring.h>
0062 #include <KritaVersionWrapper.h>
0063 
0064 #include <KisUsageLogger.h>
0065 #include <QSysInfo>
0066 #include <kis_config.h>
0067 #include <kis_image_config.h>
0068 #include "opengl/kis_opengl.h"
0069 
0070 #ifdef Q_OS_ANDROID
0071 #include <QtAndroid>
0072 
0073 QPushButton* KisWelcomePageWidget::donationLink {nullptr};
0074 QLabel* KisWelcomePageWidget::donationBannerImage {nullptr};
0075 #endif
0076 
0077 #ifdef Q_OS_WIN
0078 #include <KisWindowsPackageUtils.h>
0079 #endif
0080 
0081 // Used for triggering a QAction::setChecked signal from a QLabel::linkActivated signal
0082 void ShowNewsAction::enableFromLink(QString unused_url)
0083 {
0084     Q_UNUSED(unused_url);
0085     emit setChecked(true);
0086 }
0087 
0088 
0089 // class to override item height for Breeze since qss seems to not work
0090 class RecentItemDelegate : public QStyledItemDelegate
0091 {
0092     int itemHeight = 0;
0093 public:
0094     RecentItemDelegate(QObject *parent = 0)
0095         : QStyledItemDelegate(parent)
0096     {
0097     }
0098 
0099     void setItemHeight(int itemHeight)
0100     {
0101         this->itemHeight = itemHeight;
0102     }
0103 
0104     QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &/*index*/) const override
0105     {
0106         return QSize(option.rect.width(), itemHeight);
0107     }
0108 };
0109 
0110 
0111 KisWelcomePageWidget::KisWelcomePageWidget(QWidget *parent)
0112     : QWidget(parent)
0113 {
0114     setupUi(this);
0115 
0116     // URLs that go to web browser...
0117     devBuildIcon->setIcon(KisIconUtils::loadIcon("warning"));
0118     devBuildLabel->setVisible(false);
0119     updaterFrame->setVisible(false);
0120     versionNotificationLabel->setVisible(false);
0121     bnVersionUpdate->setVisible(false);
0122     bnErrorDetails->setVisible(false);
0123 
0124     // Recent docs...
0125     recentDocumentsListView->setDragEnabled(false);
0126     recentDocumentsListView->viewport()->setAutoFillBackground(false);
0127     recentDocumentsListView->setSpacing(2);
0128     recentDocumentsListView->installEventFilter(this);
0129 
0130     recentItemDelegate.reset(new RecentItemDelegate(this));
0131     recentItemDelegate->setItemHeight(KisRecentDocumentsModelWrapper::ICON_SIZE_LENGTH);
0132     recentDocumentsListView->setItemDelegate(recentItemDelegate.data());
0133     recentDocumentsListView->setIconSize(QSize(KisRecentDocumentsModelWrapper::ICON_SIZE_LENGTH, KisRecentDocumentsModelWrapper::ICON_SIZE_LENGTH));
0134     recentDocumentsListView->setVerticalScrollMode(QListView::ScrollPerPixel);
0135     recentDocumentsListView->verticalScrollBar()->setSingleStep(50);
0136     {
0137         QScroller* scroller = KisKineticScroller::createPreconfiguredScroller(recentDocumentsListView);
0138         if (scroller) {
0139             connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State)));
0140         }
0141     }
0142 
0143     // News widget...
0144     QMenu *newsOptionsMenu = new QMenu(this);
0145     newsOptionsMenu->setToolTipsVisible(true);
0146     ShowNewsAction *showNewsAction = new ShowNewsAction(i18n("Enable news and check for new releases"), newsOptionsMenu);
0147     newsOptionsMenu->addAction(showNewsAction);
0148     showNewsAction->setToolTip(i18n("Show news about Krita: this needs internet to retrieve information from the krita.org website"));
0149     showNewsAction->setCheckable(true);
0150 
0151     newsOptionsMenu->addSection(i18n("Language"));
0152     QAction *newsInfoAction = newsOptionsMenu->addAction(i18n("English news is always up to date."));
0153     newsInfoAction->setEnabled(false);
0154 
0155     setupNewsLangSelection(newsOptionsMenu);
0156     btnNewsOptions->setMenu(newsOptionsMenu);
0157 
0158     labelSupportText->setFont(largerFont());
0159 
0160     connect(showNewsAction, SIGNAL(toggled(bool)), newsWidget, SLOT(setVisible(bool)));
0161     connect(showNewsAction, SIGNAL(toggled(bool)), labelNoFeed, SLOT(setHidden(bool)));
0162     connect(showNewsAction, SIGNAL(toggled(bool)), newsWidget, SLOT(toggleNews(bool)));
0163     connect(labelNoFeed, SIGNAL(linkActivated(QString)), showNewsAction, SLOT(enableFromLink(QString)));
0164 
0165 #ifdef ENABLE_UPDATERS
0166     connect(showNewsAction, SIGNAL(toggled(bool)), this, SLOT(slotToggleUpdateChecks(bool)));
0167 #endif
0168 
0169 #ifdef Q_OS_ANDROID
0170     dragImageHereLabel->hide();
0171     newFileLink->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
0172     openFileLink->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
0173 
0174     donationLink = new QPushButton(dropFrameBorder);
0175     donationLink->setFlat(true);
0176     QFont f = font();
0177     f.setPointSize(15);
0178     f.setUnderline(true);
0179     donationLink->setFont(f);
0180     donationLink->setStyleSheet("padding-left: 5px; padding-right: 5px;");
0181 
0182     connect(donationLink, SIGNAL(clicked(bool)), this, SLOT(slotStartDonationFlow()));
0183 
0184     horizontalSpacer_19->changeSize(10, 0, QSizePolicy::Expanding, QSizePolicy::Expanding);
0185     horizontalSpacer_20->changeSize(10, 0, QSizePolicy::Expanding, QSizePolicy::Expanding);
0186 
0187     horizontalLayout_9->addWidget(donationLink);
0188 
0189     donationBannerImage = new QLabel(dropFrameBorder);
0190     QString bannerPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, "share/krita/donation/banner.png");
0191     donationBannerImage->setPixmap(QPixmap(bannerPath));
0192 
0193     verticalLayout->addWidget(donationBannerImage);
0194 
0195     donationLink->show();
0196     donationBannerImage->hide();
0197 
0198     // this will asynchronously lead to donationSuccessful (i.e if it *is* successful) which will hide the
0199     // link and enable the donation banner.
0200     QAndroidJniObject::callStaticMethod<void>("org/krita/android/DonationHelper", "checkBadgePurchased",
0201                                               "()V");
0202 #endif
0203 
0204     // configure the News area
0205     KisConfig cfg(true);
0206     m_checkUpdates = cfg.readEntry<bool>("FetchNews", false);
0207 
0208 
0209 #ifdef ENABLE_UPDATERS
0210 #ifndef Q_OS_ANDROID
0211     // Setup version updater, but do not check for them, unless the user explicitly
0212     // wants to check for updates.
0213     // * No updater is created for Linux/Steam, Windows/Steam and Windows/Store distributions,
0214     // as those stores have their own updating mechanism.
0215     // * STEAMAPPID(Windows)/SteamAppId(Linux) environment variable is set when Krita is run from Steam.
0216     // The environment variables are not public API.
0217     // * MS Store version runs as a package (though we cannot know if it was
0218     // installed from the Store or manually with the .msix package)
0219 #if defined Q_OS_LINUX
0220     if (!qEnvironmentVariableIsSet("SteamAppId")) { // do not create updater for linux/steam
0221         if (qEnvironmentVariableIsSet("APPIMAGE")) {
0222             m_versionUpdater.reset(new KisAppimageUpdater());
0223         } else {
0224             m_versionUpdater.reset(new KisManualUpdater());
0225         }
0226     }
0227 #elif defined Q_OS_WIN
0228     if (!KisWindowsPackageUtils::isRunningInPackage() && !qEnvironmentVariableIsSet("STEAMAPPID")) {
0229         m_versionUpdater.reset(new KisManualUpdater());
0230         KisUsageLogger::log("Non-store package - creating updater");
0231     } else {
0232         KisUsageLogger::log("detected appx or steam package - not creating the updater");
0233     }
0234 #else
0235     // always create updater for MacOS
0236     m_versionUpdater.reset(new KisManualUpdater());
0237 #endif // Q_OS_*
0238     if (!m_versionUpdater.isNull()) {
0239         connect(bnVersionUpdate, SIGNAL(clicked()), this, SLOT(slotRunVersionUpdate()));
0240         connect(bnErrorDetails, SIGNAL(clicked()), this, SLOT(slotShowUpdaterErrorDetails()));
0241         connect(m_versionUpdater.data(), SIGNAL(sigUpdateCheckStateChange(KisUpdaterStatus)),
0242                 this, SLOT(slotSetUpdateStatus(const KisUpdaterStatus&)));
0243 
0244         if (m_checkUpdates) { // only if the user wants them
0245             m_versionUpdater->checkForUpdate();
0246         }
0247     }
0248 #endif // ifndef Q_OS_ANDROID
0249 #endif // ENABLE_UPDATERS
0250 
0251 
0252     showNewsAction->setChecked(m_checkUpdates);
0253     newsWidget->setVisible(m_checkUpdates);
0254     versionNotificationLabel->setEnabled(m_checkUpdates);
0255 
0256     // Drop area..
0257     setAcceptDrops(true);
0258 }
0259 
0260 KisWelcomePageWidget::~KisWelcomePageWidget()
0261 {
0262 }
0263 
0264 void KisWelcomePageWidget::setMainWindow(KisMainWindow* mainWin)
0265 {
0266     if (mainWin) {
0267         m_mainWindow = mainWin;
0268 
0269         // set the shortcut links from actions (only if a shortcut exists)
0270         if ( mainWin->viewManager()->actionManager()->actionByName("file_new")->shortcut().toString() != "") {
0271             newFileLinkShortcut->setText(
0272                 QString("(") + mainWin->viewManager()->actionManager()->actionByName("file_new")->shortcut().toString(QKeySequence::NativeText) + QString(")"));
0273         }
0274         if (mainWin->viewManager()->actionManager()->actionByName("file_open")->shortcut().toString()  != "") {
0275             openFileShortcut->setText(
0276                 QString("(") + mainWin->viewManager()->actionManager()->actionByName("file_open")->shortcut().toString(QKeySequence::NativeText) + QString(")"));
0277         }
0278         connect(recentDocumentsListView, SIGNAL(clicked(QModelIndex)), this, SLOT(recentDocumentClicked(QModelIndex)));
0279         // we need the view manager to actually call actions, so don't create the connections
0280         // until after the view manager is set
0281         connect(newFileLink, SIGNAL(clicked(bool)), this, SLOT(slotNewFileClicked()));
0282         connect(openFileLink, SIGNAL(clicked(bool)), this, SLOT(slotOpenFileClicked()));
0283         connect(clearRecentFilesLink, SIGNAL(clicked(bool)), mainWin, SLOT(clearRecentFiles()));
0284 
0285         slotUpdateThemeColors();
0286 
0287         // allows RSS news items to apply analytics tracking.
0288         newsWidget->setAnalyticsTracking("?" + analyticsString);
0289 
0290         KisRecentDocumentsModelWrapper *recentFilesModel = KisRecentDocumentsModelWrapper::instance();
0291         connect(recentFilesModel, SIGNAL(sigModelIsUpToDate()),
0292                 this, SLOT(slotRecentFilesModelIsUpToDate()));
0293         recentDocumentsListView->setModel(&recentFilesModel->model());
0294         slotRecentFilesModelIsUpToDate();
0295     }
0296 }
0297 
0298 
0299 void KisWelcomePageWidget::showDropAreaIndicator(bool show)
0300 {
0301     if (!show) {
0302         QString dropFrameStyle = QStringLiteral("QFrame#dropAreaIndicator { border: 2px solid transparent }");
0303         dropFrameBorder->setStyleSheet(dropFrameStyle);
0304     } else {
0305         QColor textColor = qApp->palette().color(QPalette::Text);
0306         QColor backgroundColor = qApp->palette().color(QPalette::Background);
0307         QColor blendedColor = KisPaintingTweaks::blendColors(textColor, backgroundColor, 0.8);
0308 
0309         // QColor.name() turns it into a hex/web format
0310         QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 2px dotted ").append(blendedColor.name()).append(" }") ;
0311         dropFrameBorder->setStyleSheet(dropFrameStyle);
0312     }
0313 }
0314 
0315 void KisWelcomePageWidget::slotUpdateThemeColors()
0316 {
0317     textColor = qApp->palette().color(QPalette::Text);
0318     backgroundColor = qApp->palette().color(QPalette::Background);
0319 
0320     // make the welcome screen labels a subtle color so it doesn't clash with the main UI elements
0321     blendedColor = KisPaintingTweaks::blendColors(textColor, backgroundColor, 0.8);
0322     // only apply color to the widget itself, not to the tooltip or something
0323     blendedStyle = "QWidget{color: " + blendedColor.name() + "}";
0324 
0325     // what labels to change the color...
0326     startTitleLabel->setStyleSheet(blendedStyle);
0327     recentDocumentsLabel->setStyleSheet(blendedStyle);
0328     helpTitleLabel->setStyleSheet(blendedStyle);
0329     newsTitleLabel->setStyleSheet(blendedStyle);
0330     newFileLinkShortcut->setStyleSheet(blendedStyle);
0331     openFileShortcut->setStyleSheet(blendedStyle);
0332     clearRecentFilesLink->setStyleSheet(blendedStyle);
0333     recentDocumentsListView->setStyleSheet(blendedStyle);
0334     newsWidget->setStyleSheet(blendedStyle);
0335 
0336 #ifdef Q_OS_ANDROID
0337     blendedStyle = blendedStyle + "\nQPushButton { padding: 10px }";
0338 #endif
0339 
0340     newFileLink->setStyleSheet(blendedStyle);
0341     openFileLink->setStyleSheet(blendedStyle);
0342 
0343     // giving the drag area messaging a dotted border
0344     QString dottedBorderStyle = QString("border: 2px dotted ").append(blendedColor.name()).append("; color:").append(blendedColor.name()).append( ";");
0345     dragImageHereLabel->setStyleSheet(dottedBorderStyle);
0346 
0347     // make drop area QFrame have a dotted line
0348     dropFrameBorder->setObjectName("dropAreaIndicator");
0349     QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 4px dotted ").append(blendedColor.name()).append("}");
0350     dropFrameBorder->setStyleSheet(dropFrameStyle);
0351 
0352     // only show drop area when we have a document over the empty area
0353     showDropAreaIndicator(false);
0354 
0355     // add icons for new and open settings to make them stand out a bit more
0356     openFileLink->setIconSize(QSize(30, 30));
0357     newFileLink->setIconSize(QSize(30, 30));
0358     openFileLink->setIcon(KisIconUtils::loadIcon("document-open"));
0359     newFileLink->setIcon(KisIconUtils::loadIcon("document-new"));
0360 
0361     btnNewsOptions->setIcon(KisIconUtils::loadIcon("view-choose"));
0362     btnNewsOptions->setFlat(true);
0363 
0364     supportKritaIcon->setIcon(KisIconUtils::loadIcon(QStringLiteral("support-krita")));
0365     const QIcon &linkIcon = KisIconUtils::loadIcon(QStringLiteral("bookmarks"));
0366     userManualIcon->setIcon(linkIcon);
0367     gettingStartedIcon->setIcon(linkIcon);
0368     userCommunityIcon->setIcon(linkIcon);
0369     kritaWebsiteIcon->setIcon(linkIcon);
0370     sourceCodeIcon->setIcon(linkIcon);
0371 
0372     kdeIcon->setIcon(KisIconUtils::loadIcon(QStringLiteral("kde")));
0373 
0374     // HTML links seem to be a bit more stubborn with theme changes... setting inline styles to help with color change
0375     userCommunityLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://krita-artists.org\">")
0376                                .append(i18n("User Community")).append("</a>"));
0377 
0378     gettingStartedLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://docs.krita.org/en/user_manual/getting_started.html\">")
0379                                 .append(i18n("Getting Started")).append("</a>"));
0380 
0381     manualLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://docs.krita.org\">")
0382                         .append(i18n("User Manual")).append("</a>"));
0383 
0384     supportKritaLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://krita.org/en/support-us/donations?" + analyticsString + "donations" + "\">")
0385                               .append(i18n("Support Krita")).append("</a>"));
0386 
0387     kritaWebsiteLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://www.krita.org?" + analyticsString + "marketing-site" + "\">")
0388                               .append(i18n("Krita Website")).append("</a>"));
0389 
0390     sourceCodeLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://invent.kde.org/graphics/krita\">")
0391                             .append(i18n("Source Code")).append("</a>"));
0392 
0393     poweredByKDELink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://userbase.kde.org/What_is_KDE\">")
0394                               .append(i18n("Powered by KDE")).append("</a>"));
0395 
0396     QString translationNoFeed = i18n("You can <a href=\"ignored\" style=\"color: COLOR_PLACEHOLDER; text-decoration: underline;\">enable news</a> from krita.org in various languages with the menu above");
0397     labelNoFeed->setText(translationNoFeed.replace("COLOR_PLACEHOLDER", blendedColor.name()));
0398 
0399     const QColor faintTextColor = KisPaintingTweaks::blendColors(textColor, backgroundColor, 0.4);
0400     const QString &faintTextStyle = "QWidget{color: " + faintTextColor.name() + "}";
0401     labelNoRecentDocs->setStyleSheet(faintTextStyle);
0402     labelNoFeed->setStyleSheet(faintTextStyle);
0403 
0404     const QColor frameColor = KisPaintingTweaks::blendColors(textColor, backgroundColor, 0.1);
0405     const QString &frameQss = "{border: 1px solid " + frameColor.name() + "}";
0406     recentDocsStackedWidget->setStyleSheet("QStackedWidget#recentDocsStackedWidget" + frameQss);
0407     newsFrame->setStyleSheet("QFrame#newsFrame" + frameQss);
0408 
0409     // show the dev version labels, if dev version is detected
0410     showDevVersionHighlight();
0411 
0412 #ifdef ENABLE_UPDATERS
0413     updateVersionUpdaterFrame(); // updater frame
0414 #endif
0415 
0416 #ifdef Q_OS_ANDROID
0417     donationLink->setStyleSheet(blendedStyle);
0418     donationLink->setText(QString(i18n("Get your Krita Supporter Badge here!")));
0419 #endif
0420 }
0421 
0422 #ifdef Q_OS_ANDROID
0423 void KisWelcomePageWidget::slotStartDonationFlow()
0424 {
0425     QAndroidJniObject::callStaticMethod<void>("org/krita/android/DonationHelper", "startBillingFlow", "()V");
0426 }
0427 #endif
0428 
0429 void KisWelcomePageWidget::dragEnterEvent(QDragEnterEvent *event)
0430 {
0431     showDropAreaIndicator(true);
0432     if (event->mimeData()->hasUrls() ||
0433         event->mimeData()->hasFormat("application/x-krita-node-internal-pointer") ||
0434         event->mimeData()->hasFormat("application/x-qt-image")) {
0435         return event->accept();
0436     }
0437 
0438     return event->ignore();
0439 }
0440 
0441 void KisWelcomePageWidget::dropEvent(QDropEvent *event)
0442 {
0443     showDropAreaIndicator(false);
0444 
0445     if (event->mimeData()->hasUrls() && !event->mimeData()->urls().empty()) {
0446         Q_FOREACH (const QUrl &url, event->mimeData()->urls()) {
0447             if (url.toLocalFile().endsWith(".bundle", Qt::CaseInsensitive)) {
0448                 bool r = m_mainWindow->installBundle(url.toLocalFile());
0449                 if (!r) {
0450                     qWarning() << "Could not install bundle" << url.toLocalFile();
0451                 }
0452             } else if (!url.isLocalFile()) {
0453                 QScopedPointer<QTemporaryFile> tmp(new QTemporaryFile());
0454                 tmp->setFileName(url.fileName());
0455 
0456                 KisRemoteFileFetcher fetcher;
0457 
0458                 if (!fetcher.fetchFile(url, tmp.data())) {
0459                     qWarning() << "Fetching" << url << "failed";
0460                     continue;
0461                 }
0462                 const auto localUrl = QUrl::fromLocalFile(tmp->fileName());
0463 
0464                 m_mainWindow->openDocument(localUrl.toLocalFile(), KisMainWindow::None);
0465             } else {
0466                 m_mainWindow->openDocument(url.toLocalFile(), KisMainWindow::None);
0467             }
0468         }
0469     }
0470 }
0471 
0472 void KisWelcomePageWidget::dragMoveEvent(QDragMoveEvent *event)
0473 {
0474     m_mainWindow->dragMoveEvent(event);
0475 
0476     if (event->mimeData()->hasUrls() ||
0477         event->mimeData()->hasFormat("application/x-krita-node-internal-pointer") ||
0478         event->mimeData()->hasFormat("application/x-qt-image")) {
0479         return event->accept();
0480     }
0481 
0482     return event->ignore();
0483 }
0484 
0485 void KisWelcomePageWidget::dragLeaveEvent(QDragLeaveEvent */*event*/)
0486 {
0487     showDropAreaIndicator(false);
0488     m_mainWindow->dragLeave();
0489 }
0490 
0491 void KisWelcomePageWidget::changeEvent(QEvent *event)
0492 {
0493     if (event->type() == QEvent::FontChange) {
0494         labelSupportText->setFont(largerFont());
0495     }
0496 }
0497 
0498 bool KisWelcomePageWidget::eventFilter(QObject *watched, QEvent *event)
0499 {
0500     if (watched == recentDocumentsListView && event->type() == QEvent::Leave) {
0501         recentDocumentsListView->clearSelection();
0502     }
0503     return QWidget::eventFilter(watched, event);
0504 }
0505 
0506 namespace {
0507 
0508 QString mapKi18nLangToNewsLang(const QString &ki18nLang) {
0509     if (ki18nLang == "ja") {
0510         return QString("jp");
0511     }
0512     if (ki18nLang == "zh_CN") {
0513         return QString("zh");
0514     }
0515     if (ki18nLang == "zh_TW") {
0516         return QString("zh-tw");
0517     }
0518     if (ki18nLang == "zh_HK") {
0519         return QString("zh-hk");
0520     }
0521     if (ki18nLang == "en" || ki18nLang == "en_US" || ki18nLang == "en_GB") {
0522         return QString("en");
0523     }
0524     return QString();
0525 };
0526 
0527 QString getAutoNewsLang()
0528 {
0529     // Get current UI languages:
0530     const QStringList uiLangs = KLocalizedString::languages();
0531 
0532     QString autoNewsLang;
0533     // Iterate UI languages including fallback languages for the first
0534     // available news language.
0535     Q_FOREACH(const auto &uiLang, uiLangs) {
0536         autoNewsLang = mapKi18nLangToNewsLang(uiLang);
0537         if (!autoNewsLang.isEmpty()) {
0538             break;
0539         }
0540     }
0541     if (autoNewsLang.isEmpty()) {
0542         // If nothing else, use English.
0543         autoNewsLang = QString("en");
0544     }
0545     return autoNewsLang;
0546 }
0547 
0548 } /* namespace */
0549 
0550 void KisWelcomePageWidget::setupNewsLangSelection(QMenu *newsOptionsMenu)
0551 {
0552     // Hard-coded news language data:
0553     // These are languages in which the news items should be regularly
0554     // translated into as of 2020-11-07.
0555     // The language display names should not be translated. This reflects
0556     // the language selection box on the Krita website.
0557     struct Lang {
0558         const QString siteCode;
0559         const QString name;
0560     };
0561     static const std::array<Lang, 5> newsLangs = {{
0562         {QString("en"), QStringLiteral("English")},
0563         {QString("jp"), QStringLiteral("日本語")},
0564         {QString("zh"), QStringLiteral("中文 (简体)")},
0565         {QString("zh-tw"), QStringLiteral("中文 (台灣正體)")},
0566         {QString("zh-hk"), QStringLiteral("廣東話 (香港)")},
0567     }};
0568 
0569     static const QString newsLangConfigName = QStringLiteral("FetchNewsLanguages");
0570 
0571     QSharedPointer<QSet<QString>> enabledNewsLangs = QSharedPointer<QSet<QString>>::create();
0572     {
0573         // Initialize with the config.
0574         KisConfig cfg(true);
0575         *enabledNewsLangs = cfg.readList<QString>(newsLangConfigName).toSet();
0576     }
0577 
0578     // If no languages are selected in the config, use the automatic selection.
0579     if (enabledNewsLangs->isEmpty()) {
0580         enabledNewsLangs->insert(QString(getAutoNewsLang()));
0581     }
0582 
0583     for (const auto &lang : newsLangs) {
0584         QAction *langItem = newsOptionsMenu->addAction(lang.name);
0585         langItem->setCheckable(true);
0586         // We can copy `code` into the lambda because its backing string is a
0587         // static string literal.
0588         const QString code = lang.siteCode;
0589         connect(langItem, &QAction::toggled, [=](bool checked) {
0590             newsWidget->toggleNewsLanguage(code, checked);
0591         });
0592 
0593         // Set the initial checked state.
0594         if (enabledNewsLangs->contains(code)) {
0595             langItem->setChecked(true);
0596         }
0597 
0598         // Connect this lambda after setting the initial checked state because
0599         // we don't want to overwrite the config when doing the initial setup.
0600         connect(langItem, &QAction::toggled, [=](bool checked) {
0601             KisConfig cfg(false);
0602             // It is safe to modify `enabledNewsLangs` here, because the slots
0603             // are called synchronously on the UI thread so there is no need
0604             // for explicit synchronization.
0605             if (checked) {
0606                 enabledNewsLangs->insert(QString(code));
0607             } else {
0608                 enabledNewsLangs->remove(QString(code));
0609             }
0610             cfg.writeList(newsLangConfigName, enabledNewsLangs->values());
0611         });
0612     }
0613 }
0614 
0615 void KisWelcomePageWidget::showDevVersionHighlight()
0616 {
0617     // always flag development version
0618     if (isDevelopmentBuild()) {
0619         QString devBuildLabelText = QString("<a style=\"color: " +
0620                                            blendedColor.name() +
0621                                            " \" href=\"https://docs.krita.org/en/untranslatable_pages/triaging_bugs.html?"
0622                                            + analyticsString + "dev-build" + "\">")
0623                                   .append(i18n("DEV BUILD")).append("</a>");
0624 
0625         devBuildLabel->setText(devBuildLabelText);
0626         devBuildIcon->setVisible(true);
0627         devBuildLabel->setVisible(true);
0628     } else {
0629         devBuildIcon->setVisible(false);
0630         devBuildLabel->setVisible(false);
0631     }
0632 }
0633 
0634 void KisWelcomePageWidget::recentDocumentClicked(QModelIndex index)
0635 {
0636     QString fileUrl = index.data(Qt::ToolTipRole).toString();
0637     m_mainWindow->openDocument(fileUrl, KisMainWindow::None );
0638 }
0639 
0640 
0641 bool KisWelcomePageWidget::isDevelopmentBuild()
0642 {
0643     QString versionString = KritaVersionWrapper::versionString(true);
0644 
0645     if (versionString.contains("git")) {
0646         return true;
0647     } else {
0648         return false;
0649     }
0650 }
0651 
0652 void KisWelcomePageWidget::slotNewFileClicked()
0653 {
0654     m_mainWindow->slotFileNew();
0655 }
0656 
0657 void KisWelcomePageWidget::slotOpenFileClicked()
0658 {
0659     m_mainWindow->slotFileOpen();
0660 }
0661 
0662 void KisWelcomePageWidget::slotRecentFilesModelIsUpToDate()
0663 {
0664     KisRecentDocumentsModelWrapper *recentFilesModel = KisRecentDocumentsModelWrapper::instance();
0665     const bool modelIsEmpty = recentFilesModel->model().rowCount() == 0;
0666 
0667     if (modelIsEmpty) {
0668         recentDocsStackedWidget->setCurrentWidget(labelNoRecentDocs);
0669     } else {
0670         recentDocsStackedWidget->setCurrentWidget(recentDocumentsListView);
0671     }
0672     clearRecentFilesLink->setVisible(!modelIsEmpty);
0673 }
0674 
0675 #ifdef ENABLE_UPDATERS
0676 void KisWelcomePageWidget::slotToggleUpdateChecks(bool state)
0677 {
0678     if (m_versionUpdater.isNull()) {
0679         return;
0680     }
0681 
0682     m_checkUpdates = state;
0683 
0684     if (m_checkUpdates) {
0685         m_versionUpdater->checkForUpdate();
0686     }
0687 
0688     updateVersionUpdaterFrame();
0689 }
0690 void KisWelcomePageWidget::slotRunVersionUpdate()
0691 {
0692     if (m_versionUpdater.isNull()) {
0693         return;
0694     }
0695 
0696     if (m_checkUpdates) {
0697         m_versionUpdater->doUpdate();
0698     }
0699 }
0700 
0701 void KisWelcomePageWidget::slotSetUpdateStatus(KisUpdaterStatus updateStatus)
0702 {
0703     m_updaterStatus = updateStatus;
0704     updateVersionUpdaterFrame();
0705 }
0706 
0707 void KisWelcomePageWidget::slotShowUpdaterErrorDetails()
0708 {
0709     QMessageBox::warning(qApp->activeWindow(), i18nc("@title:window", "Krita"), m_updaterStatus.updaterOutput());
0710 }
0711 
0712 void KisWelcomePageWidget::updateVersionUpdaterFrame()
0713 {
0714     updaterFrame->setVisible(false);
0715     versionNotificationLabel->setVisible(false);
0716     bnVersionUpdate->setVisible(false);
0717     bnErrorDetails->setVisible(false);
0718 
0719     if (!m_checkUpdates || m_versionUpdater.isNull()) {
0720         return;
0721     }
0722 
0723     QString versionLabelText;
0724 
0725     if (m_updaterStatus.status() == UpdaterStatus::StatusID::UPDATE_AVAILABLE) {
0726         updaterFrame->setVisible(true);
0727         updaterFrame->setEnabled(true);
0728         versionLabelText = i18n("New version of Krita is available.");
0729         versionNotificationLabel->setVisible(true);
0730         updateIcon->setIcon(KisIconUtils::loadIcon("update-medium"));
0731 
0732         if (m_versionUpdater->hasUpdateCapability()) {
0733             bnVersionUpdate->setVisible(true);
0734         } else {
0735             // build URL for label
0736             QString downloadLink = QString(" <a style=\"color: %1; text-decoration: underline\" href=\"%2?%3\">Download Krita %4</a>")
0737                     .arg(blendedColor.name())
0738                     .arg(m_updaterStatus.downloadLink())
0739                     .arg(analyticsString + "version-update")
0740                     .arg(m_updaterStatus.availableVersion());
0741 
0742             versionLabelText.append(downloadLink);
0743         }
0744 
0745     } else if (
0746                (m_updaterStatus.status() == UpdaterStatus::StatusID::UPTODATE)
0747                || (m_updaterStatus.status() == UpdaterStatus::StatusID::CHECK_ERROR)
0748                || (m_updaterStatus.status() == UpdaterStatus::StatusID::IN_PROGRESS)
0749                ){
0750         // no notifications, if uptodate
0751         // also, stay silent on check error - we do not want to generate lots of user support issues
0752         // because of failing wifis and proxies over the world
0753         updaterFrame->setVisible(false);
0754 
0755     } else if (m_updaterStatus.status() == UpdaterStatus::StatusID::UPDATE_ERROR) {
0756         updaterFrame->setVisible(true);
0757         versionLabelText = i18n("An error occurred during the update");
0758         versionNotificationLabel->setVisible(true);
0759         bnErrorDetails->setVisible(true);
0760         updateIcon->setIcon(KisIconUtils::loadIcon("warning"));
0761     } else if (m_updaterStatus.status() == UpdaterStatus::StatusID::RESTART_REQUIRED) {
0762         updaterFrame->setVisible(true);
0763         versionLabelText = QString("<b>%1</b> %2").arg(i18n("Restart is required.")).arg(m_updaterStatus.details());
0764         versionNotificationLabel->setVisible(true);
0765         updateIcon->setIcon(KisIconUtils::loadIcon("view-refresh"));
0766     }
0767 
0768     versionNotificationLabel->setText(versionLabelText);
0769     if (!blendedStyle.isNull()) {
0770         versionNotificationLabel->setStyleSheet(blendedStyle);
0771     }
0772 }
0773 #endif
0774 
0775 #ifdef Q_OS_ANDROID
0776 extern "C" JNIEXPORT void JNICALL
0777 Java_org_krita_android_JNIWrappers_donationSuccessful(JNIEnv* /*env*/,
0778                                                       jobject /*obj*/,
0779                                                       jint    /*n*/)
0780 {
0781     KisWelcomePageWidget::donationLink->hide();
0782     KisWelcomePageWidget::donationBannerImage->show();
0783 }
0784 #endif
0785 
0786 QFont KisWelcomePageWidget::largerFont()
0787 {
0788     QFont larger = font();
0789     larger.setPointSizeF(larger.pointSizeF() * 1.1f);
0790     return larger;
0791 }