File indexing completed on 2024-05-19 04:29:22

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