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 }