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 }