Krita Source Code Documentation
Loading...
Searching...
No Matches
KisWelcomePageWidget.cpp
Go to the documentation of this file.
1
2/* This file is part of the KDE project
3 * SPDX-FileCopyrightText: 2018 Scott Petrovic <scottpetrovic@gmail.com>
4 * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
5 *
6 * SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8
11#include <QDesktopServices>
12#include <QMimeData>
13#include <QPixmap>
14#include <QMessageBox>
15#include <QTemporaryFile>
16#include <QBuffer>
17#include <QNetworkAccessManager>
18#include <QEventLoop>
19#include <QDomDocument>
20
22#include "kactioncollection.h"
23#include "kis_action.h"
24#include "kis_action_manager.h"
25#include <KisMimeDatabase.h>
26#include <KisApplication.h>
27
28#include "KConfigGroup"
29#include "KSharedConfig"
30
31#include <QListWidget>
32#include <QListWidgetItem>
33#include <QMenu>
34#include <QScrollBar>
35
36#include "kis_icon_utils.h"
37#include <kis_painting_tweaks.h>
38#include "KoStore.h"
39#include "kis_config.h"
40#include "KisDocument.h"
41#include <kis_image.h>
42#include <kis_paint_device.h>
43#include <KisPart.h>
44#include <KisKineticScroller.h>
45#include "KisMainWindow.h"
46
48
49#include <QCoreApplication>
50#include <kis_debug.h>
51#include <QDir>
52
53#include <array>
54
55#include "config-updaters.h"
56
57#ifdef ENABLE_UPDATERS
58#ifdef Q_OS_LINUX
60#endif
61
63#endif
64
65#include <klocalizedstring.h>
66#include <KritaVersionWrapper.h>
67
68#include <KisUsageLogger.h>
69#include <QSysInfo>
70#include <kis_config.h>
71#include <kis_image_config.h>
72#include "opengl/kis_opengl.h"
73
74#ifdef Q_OS_WIN
76#endif
77
78#ifdef Q_OS_MACOS
80#endif
81
82#ifdef Q_OS_ANDROID
83#include "KisAndroidDonations.h"
84#endif
85
86// Used for triggering a QAction::setChecked signal from a QLabel::linkActivated signal
87void ShowNewsAction::enableFromLink(QString unused_url)
88{
89 Q_UNUSED(unused_url);
90 Q_EMIT setChecked(true);
91}
92
93
94// class to override item height for Breeze since qss seems to not work
95class RecentItemDelegate : public QStyledItemDelegate
96{
97 int itemHeight = 0;
98public:
99 RecentItemDelegate(QObject *parent = 0)
100 : QStyledItemDelegate(parent)
101 {
102 }
103
105 {
106 this->itemHeight = itemHeight;
107 }
108
109 QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &/*index*/) const override
110 {
111 return QSize(option.rect.width(), itemHeight);
112 }
113};
114
115
117 : QWidget(parent)
118{
119 setupUi(this);
120
121 // URLs that go to web browser...
122 devBuildIcon->setIcon(KisIconUtils::loadIcon("warning"));
123 devBuildLabel->setVisible(false);
124 updaterFrame->setVisible(false);
125 versionNotificationLabel->setVisible(false);
126 bnVersionUpdate->setVisible(false);
127 bnErrorDetails->setVisible(false);
128
129 // Recent docs...
130 recentDocumentsListView->setDragEnabled(false);
131 recentDocumentsListView->viewport()->setAutoFillBackground(false);
132 recentDocumentsListView->setSpacing(2);
133 recentDocumentsListView->installEventFilter(this);
134 recentDocumentsListView->setViewMode(QListView::IconMode);
135 recentDocumentsListView->setSelectionMode(QAbstractItemView::NoSelection);
136
137// m_recentItemDelegate.reset(new RecentItemDelegate(this));
138// m_recentItemDelegate->setItemHeight(KisRecentDocumentsModelWrapper::ICON_SIZE_LENGTH);
139// recentDocumentsListView->setItemDelegate(m_recentItemDelegate.data());
141 recentDocumentsListView->setVerticalScrollMode(QListView::ScrollPerPixel);
142 recentDocumentsListView->verticalScrollBar()->setSingleStep(50);
143 {
144 QScroller* scroller = KisKineticScroller::createPreconfiguredScroller(recentDocumentsListView);
145 if (scroller) {
146 connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State)));
147 }
148 }
149 recentDocumentsListView->setContextMenuPolicy(Qt::CustomContextMenu);
150 connect(recentDocumentsListView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(slotRecentDocContextMenuRequest(QPoint)));
151
152 // News widget...
153 QMenu *newsOptionsMenu = new QMenu(this);
154 newsOptionsMenu->setToolTipsVisible(true);
155 ShowNewsAction *showNewsAction = new ShowNewsAction(i18n("Enable news and check for new releases"), newsOptionsMenu);
156 newsOptionsMenu->addAction(showNewsAction);
157 showNewsAction->setToolTip(i18n("Show news about Krita: this needs internet to retrieve information from the krita.org website"));
158 showNewsAction->setCheckable(true);
159
160 newsOptionsMenu->addSection(i18n("Language"));
161 QAction *newsInfoAction = newsOptionsMenu->addAction(i18n("English news is always up to date."));
162 newsInfoAction->setEnabled(false);
163
164 setupNewsLangSelection(newsOptionsMenu);
165 btnNewsOptions->setMenu(newsOptionsMenu);
166
167 labelSupportText->setFont(largerFont());
168 donationLink->setFont(largerFont());
169
170 connect(showNewsAction, SIGNAL(toggled(bool)), newsWidget, SLOT(setVisible(bool)));
171 connect(showNewsAction, SIGNAL(toggled(bool)), labelNoFeed, SLOT(setHidden(bool)));
172 connect(showNewsAction, SIGNAL(toggled(bool)), newsWidget, SLOT(toggleNews(bool)));
173 connect(labelNoFeed, SIGNAL(linkActivated(QString)), showNewsAction, SLOT(enableFromLink(QString)));
174
175 labelNoFeed->setDismissable(false);
176
177#ifdef ENABLE_UPDATERS
178 connect(showNewsAction, SIGNAL(toggled(bool)), this, SLOT(slotToggleUpdateChecks(bool)));
179#endif
180
181 donationLink->hide();
182 supporterBadge->hide();
183#ifdef Q_OS_ANDROID
184 initDonations();
185#endif
186
187 // configure the News area
188 KisConfig cfg(true);
189 m_networkIsAllowed = cfg.readEntry<bool>("FetchNews", false);
190
191
192#ifdef ENABLE_UPDATERS
193#ifndef Q_OS_ANDROID
194 // Setup version updater, but do not check for them, unless the user explicitly
195 // wants to check for updates.
196 // * No updater is created for Linux/Steam, Windows/Steam and Windows/Store distributions,
197 // as those stores have their own updating mechanism.
198 // * STEAMAPPID(Windows)/SteamAppId(Linux) environment variable is set when Krita is run from Steam.
199 // The environment variables are not public API.
200 // * MS Store version runs as a package (though we cannot know if it was
201 // installed from the Store or manually with the .msix package)
202#if defined Q_OS_LINUX
203 if (!qEnvironmentVariableIsSet("SteamAppId")) { // do not create updater for linux/steam
204 if (qEnvironmentVariableIsSet("APPIMAGE")) {
205 m_versionUpdater.reset(new KisAppimageUpdater());
206 } else {
207 m_versionUpdater.reset(new KisManualUpdater());
208 }
209 }
210#elif defined Q_OS_WIN
211 if (!KisWindowsPackageUtils::isRunningInPackage() && !qEnvironmentVariableIsSet("STEAMAPPID")) {
212 m_versionUpdater.reset(new KisManualUpdater());
213 KisUsageLogger::log("Non-store package - creating updater");
214 } else {
215 KisUsageLogger::log("detected appx or steam package - not creating the updater");
216 }
217#else
218 // always create updater for MacOS
219 m_versionUpdater.reset(new KisManualUpdater());
220#endif // Q_OS_*
221 if (!m_versionUpdater.isNull()) {
222 connect(bnVersionUpdate, SIGNAL(clicked()), this, SLOT(slotRunVersionUpdate()));
223 connect(bnErrorDetails, SIGNAL(clicked()), this, SLOT(slotShowUpdaterErrorDetails()));
224 connect(m_versionUpdater.data(), SIGNAL(sigUpdateCheckStateChange(KisUpdaterStatus)),
225 this, SLOT(slotSetUpdateStatus(const KisUpdaterStatus&)));
226
227 if (m_networkIsAllowed) { // only if the user wants them
228 m_versionUpdater->checkForUpdate();
229 }
230 }
231#endif // ifndef Q_OS_ANDROID
232#endif // ENABLE_UPDATERS
233
234
235 showNewsAction->setChecked(m_networkIsAllowed);
236 newsWidget->setVisible(m_networkIsAllowed);
237 versionNotificationLabel->setEnabled(m_networkIsAllowed);
238
239 // Drop area..
240 setAcceptDrops(true);
241}
242
246
248{
249 if (mainWin) {
250 m_mainWindow = mainWin;
251
252 // set the shortcut links from actions (only if a shortcut exists)
253 if ( mainWin->viewManager()->actionManager()->actionByName("file_new")->shortcut().toString() != "") {
254 newFileLinkShortcut->setText(
255 QString("(") + mainWin->viewManager()->actionManager()->actionByName("file_new")->shortcut().toString(QKeySequence::NativeText) + QString(")"));
256 }
257 if (mainWin->viewManager()->actionManager()->actionByName("file_open")->shortcut().toString() != "") {
258 openFileShortcut->setText(
259 QString("(") + mainWin->viewManager()->actionManager()->actionByName("file_open")->shortcut().toString(QKeySequence::NativeText) + QString(")"));
260 }
261 connect(recentDocumentsListView, SIGNAL(clicked(QModelIndex)), this, SLOT(recentDocumentClicked(QModelIndex)));
262 // we need the view manager to actually call actions, so don't create the connections
263 // until after the view manager is set
264 connect(newFileLink, SIGNAL(clicked(bool)), this, SLOT(slotNewFileClicked()));
265 connect(openFileLink, SIGNAL(clicked(bool)), this, SLOT(slotOpenFileClicked()));
266 connect(clearRecentFilesLink, SIGNAL(clicked(bool)), mainWin, SLOT(clearRecentFiles()));
267
269
270 // allows RSS news items to apply analytics tracking.
271 newsWidget->setAnalyticsTracking("?" + analyticsString);
272
274 connect(recentFilesModel, SIGNAL(sigModelIsUpToDate()), this, SLOT(slotRecentFilesModelIsUpToDate()));
275 recentDocumentsListView->setModel(&recentFilesModel->model());
277 }
278}
279
280
282{
283 if (!show) {
284 QString dropFrameStyle = QStringLiteral("QFrame#dropAreaIndicator { border: 2px solid transparent }");
285 dropFrameBorder->setStyleSheet(dropFrameStyle);
286 } else {
287 QColor textColor = qApp->palette().color(QPalette::Text);
288 QColor backgroundColor = qApp->palette().color(QPalette::Window);
290
291 // QColor.name() turns it into a hex/web format
292 QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 2px dotted ").append(blendedColor.name()).append(" }") ;
293 dropFrameBorder->setStyleSheet(dropFrameStyle);
294 }
295}
296
298{
299 textColor = qApp->palette().color(QPalette::Text);
300 backgroundColor = qApp->palette().color(QPalette::Window);
301
302 // make the welcome screen labels a subtle color so it doesn't clash with the main UI elements
304 // only apply color to the widget itself, not to the tooltip or something
305 blendedStyle = "QWidget{color: " + blendedColor.name() + "}";
306
307 // what labels to change the color...
308 startTitleLabel->setStyleSheet(blendedStyle);
309 recentDocumentsLabel->setStyleSheet(blendedStyle);
310 helpTitleLabel->setStyleSheet(blendedStyle);
311 newsTitleLabel->setStyleSheet(blendedStyle);
312 newFileLinkShortcut->setStyleSheet(blendedStyle);
313 openFileShortcut->setStyleSheet(blendedStyle);
314 clearRecentFilesLink->setStyleSheet(blendedStyle);
315 recentDocumentsListView->setStyleSheet(blendedStyle);
316 newsWidget->setStyleSheet(blendedStyle);
317
318#ifdef Q_OS_ANDROID
319 blendedStyle = blendedStyle + "\nQPushButton { padding: 10px }";
320#endif
321
322 newFileLink->setStyleSheet(blendedStyle);
323 openFileLink->setStyleSheet(blendedStyle);
324
325 // make drop area QFrame have a dotted line
326 dropFrameBorder->setObjectName("dropAreaIndicator");
327 QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 4px dotted ").append(blendedColor.name()).append("}");
328 dropFrameBorder->setStyleSheet(dropFrameStyle);
329
330 // only show drop area when we have a document over the empty area
332
333 // add icons for new and open settings to make them stand out a bit more
334 openFileLink->setIconSize(QSize(48, 48));
335 newFileLink->setIconSize(QSize(48, 48));
336
337 openFileLink->setIcon(KisIconUtils::loadIcon("document-open"));
338 newFileLink->setIcon(KisIconUtils::loadIcon("document-new"));
339
340 btnNewsOptions->setIcon(KisIconUtils::loadIcon("view-choose"));
341 btnNewsOptions->setFlat(true);
342
343 supportKritaIcon->setIcon(KisIconUtils::loadIcon(QStringLiteral("support-krita")));
344 const QIcon &linkIcon = KisIconUtils::loadIcon(QStringLiteral("bookmarks"));
345 userManualIcon->setIcon(linkIcon);
346 gettingStartedIcon->setIcon(linkIcon);
347 userCommunityIcon->setIcon(linkIcon);
348 kritaWebsiteIcon->setIcon(linkIcon);
349 sourceCodeIcon->setIcon(linkIcon);
350
351 kdeIcon->setIcon(KisIconUtils::loadIcon(QStringLiteral("kde")));
352
353 // HTML links seem to be a bit more stubborn with theme changes... setting inline styles to help with color change
354 userCommunityLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://krita-artists.org\">")
355 .append(i18n("User Community")).append("</a>"));
356
357 gettingStartedLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://docs.krita.org/user_manual/getting_started.html\">")
358 .append(i18n("Getting Started")).append("</a>"));
359
360 manualLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://docs.krita.org\">")
361 .append(i18n("User Manual")).append("</a>"));
362
363 supportKritaLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://krita.org/support-us/donations?" + analyticsString + "donations" + "\">")
364 .append(i18n("Support Krita")).append("</a>"));
365
366 kritaWebsiteLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://www.krita.org?" + analyticsString + "marketing-site" + "\">")
367 .append(i18n("Krita Website")).append("</a>"));
368
369 sourceCodeLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://invent.kde.org/graphics/krita\">")
370 .append(i18n("Source Code")).append("</a>"));
371
372 poweredByKDELink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://userbase.kde.org/What_is_KDE\">")
373 .append(i18n("Powered by KDE")).append("</a>"));
374
375 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");
376 labelNoFeed->setText(translationNoFeed.replace("COLOR_PLACEHOLDER", blendedColor.name()));
377
378 const QColor faintTextColor = KisPaintingTweaks::blendColors(textColor, backgroundColor, 0.4);
379 const QString &faintTextStyle = "QWidget{color: " + faintTextColor.name() + "}";
380 labelNoRecentDocs->setStyleSheet(faintTextStyle);
381 labelNoFeed->setStyleSheet(faintTextStyle);
382
383 const QColor frameColor = KisPaintingTweaks::blendColors(textColor, backgroundColor, 0.1);
384 const QString &frameQss = "{border: 1px solid " + frameColor.name() + "}";
385 recentDocsStackedWidget->setStyleSheet("QStackedWidget#recentDocsStackedWidget" + frameQss);
386 newsFrame->setStyleSheet("QFrame#newsFrame" + frameQss);
387
388 // show the dev version labels, if dev version is detected
390
391#ifdef ENABLE_UPDATERS
392 updateVersionUpdaterFrame(); // updater frame
393#endif
394
395#ifdef Q_OS_ANDROID
396 donationLink->setText(
397 QStringLiteral("<a href=\"#\">%1</a>").arg(QString(i18n("Get your Krita Supporter Badge here!"))));
398#endif
399
400#ifdef Q_OS_MACOS
401 // macOS store version should not contain external links containing donation buttons or forms
402 if (KisMacosEntitlements().sandbox()) {
403 supportKritaLink->hide();
404 supportKritaIcon->hide();
405 labelSupportText->hide();
406 kritaWebsiteLink->hide();
407 kritaWebsiteIcon->hide();
408 donationLink->hide();
409 }
410#endif
411}
412
413void KisWelcomePageWidget::dragEnterEvent(QDragEnterEvent *event)
414{
416 if (event->mimeData()->hasUrls() ||
417 event->mimeData()->hasFormat("application/x-krita-node-internal-pointer") ||
418 event->mimeData()->hasFormat("application/x-qt-image")) {
419 return event->accept();
420 }
421
422 return event->ignore();
423}
424
425void KisWelcomePageWidget::dropEvent(QDropEvent *event)
426{
428
429 if (event->mimeData()->hasUrls() && !event->mimeData()->urls().empty()) {
430 Q_FOREACH (const QUrl &url, event->mimeData()->urls()) {
431 if (url.toLocalFile().endsWith(".bundle", Qt::CaseInsensitive)) {
432 bool r = m_mainWindow->installBundle(url.toLocalFile());
433 if (!r) {
434 qWarning() << "Could not install bundle" << url.toLocalFile();
435 }
436 } else if (!url.isLocalFile()) {
437 QScopedPointer<QTemporaryFile> tmp(new QTemporaryFile());
438 tmp->setFileName(url.fileName());
439
440 KisRemoteFileFetcher fetcher;
441
442 if (!fetcher.fetchFile(url, tmp.data())) {
443 qWarning() << "Fetching" << url << "failed";
444 continue;
445 }
446 const auto localUrl = QUrl::fromLocalFile(tmp->fileName());
447
448 m_mainWindow->openDocument(localUrl.toLocalFile(), KisMainWindow::None);
449 } else {
450 m_mainWindow->openDocument(url.toLocalFile(), KisMainWindow::None);
451 }
452 }
453 }
454}
455
456void KisWelcomePageWidget::dragMoveEvent(QDragMoveEvent *event)
457{
459
460 if (event->mimeData()->hasUrls() ||
461 event->mimeData()->hasFormat("application/x-krita-node-internal-pointer") ||
462 event->mimeData()->hasFormat("application/x-qt-image")) {
463 return event->accept();
464 }
465
466 return event->ignore();
467}
468
469void KisWelcomePageWidget::dragLeaveEvent(QDragLeaveEvent */*event*/)
470{
473}
474
476{
477 if (event->type() == QEvent::FontChange) {
478 labelSupportText->setFont(largerFont());
479 donationLink->setFont(largerFont());
480 }
481}
482
483bool KisWelcomePageWidget::eventFilter(QObject *watched, QEvent *event)
484{
485 if (watched == recentDocumentsListView && event->type() == QEvent::Leave) {
486 recentDocumentsListView->clearSelection();
487 }
488 return QWidget::eventFilter(watched, event);
489}
490
491namespace {
492
493QString getAutoNewsLang()
494{
495 // Get current UI languages:
496 const QStringList uiLangs = KLocalizedString::languages();
497 QString autoNewsLang = uiLangs.first();
498 if (autoNewsLang.isEmpty()) {
499 // If nothing else, use English.
500 autoNewsLang = QString("en");
501 } else if (autoNewsLang == "ja") {
502 return QString("jp");
503 } else if (autoNewsLang == "zh_CN") {
504 return QString("zh");
505 } else if (autoNewsLang == "zh_TW") {
506 return QString("zh-tw");
507 } else if (autoNewsLang == "zh_HK") {
508 return QString("zh-hk");
509 } else if (autoNewsLang == "en" || autoNewsLang == "en_US" || autoNewsLang == "en_GB") {
510 return QString("en");
511 }
512
513 return autoNewsLang;
514}
515
516} /* namespace */
517
519{
520 // Hard-coded news language data:
521 // These are languages in which the news items should be regularly
522 // translated into as of 04-09-2024.
523 // The language display names should not be translated. This reflects
524 // the language selection box on the Krita website.
525 struct Lang {
526 const QString siteCode;
527 const QString name;
528 };
529 static const std::array<Lang, 22> newsLangs = {{
530 {QString("en"), QStringLiteral("English")},
531 {QString("jp"), QStringLiteral("日本語")},
532 {QString("zh"), QStringLiteral("中文 (简体)")},
533 {QString("zh-tw"), QStringLiteral("中文 (台灣正體)")},
534 {QString("zh-hk"), QStringLiteral("廣東話 (香港)")},
535 {QString("ca"), QStringLiteral("Català")},
536 {QString("ca@valencia"), QStringLiteral("Català de Valencia")},
537 {QString("cs"), QStringLiteral("Čeština")},
538 {QString("de"), QStringLiteral("Deutsch")},
539 {QString("eo"), QStringLiteral("Esperanto")},
540 {QString("es"), QStringLiteral("Español")},
541 {QString("eu"), QStringLiteral("Euskara")},
542 {QString("fr"), QStringLiteral("Français")},
543 {QString("it"), QStringLiteral("Italiano")},
544 {QString("lt"), QStringLiteral("lietuvių")},
545 {QString("nl"), QStringLiteral("Nederlands")},
546 {QString("pt"), QStringLiteral("Português")},
547 {QString("sk"), QStringLiteral("Slovenský")},
548 {QString("sl"), QStringLiteral("Slovenski")},
549 {QString("sv"), QStringLiteral("Svenska")},
550 {QString("tr"), QStringLiteral("Türkçe")},
551 {QString("uk"), QStringLiteral("Українська")}
552 }};
553
554 static const QString newsLangConfigName = QStringLiteral("FetchNewsLanguages");
555
556 QSharedPointer<QSet<QString>> enabledNewsLangs = QSharedPointer<QSet<QString>>::create();
557 {
558 // Initialize with the config.
559 KisConfig cfg(true);
560 auto languagesList = cfg.readList<QString>(newsLangConfigName);
561 *enabledNewsLangs = QSet(languagesList.begin(), languagesList.end());
562 }
563
564 // If no languages are selected in the config, use the automatic selection.
565 if (enabledNewsLangs->isEmpty()) {
566 enabledNewsLangs->insert(QString(getAutoNewsLang()));
567 }
568
569 for (const auto &lang : newsLangs) {
570 QAction *langItem = newsOptionsMenu->addAction(lang.name);
571 langItem->setCheckable(true);
572 // We can copy `code` into the lambda because its backing string is a
573 // static string literal.
574 const QString code = lang.siteCode;
575 connect(langItem, &QAction::toggled, newsWidget, [=](bool checked) {
576 newsWidget->toggleNewsLanguage(code, checked);
577 });
578
579 // Set the initial checked state.
580 if (enabledNewsLangs->contains(code)) {
581 langItem->setChecked(true);
582 }
583
584 // Connect this lambda after setting the initial checked state because
585 // we don't want to overwrite the config when doing the initial setup.
586 connect(langItem, &QAction::toggled, [=](bool checked) {
587 KisConfig cfg(false);
588 // It is safe to modify `enabledNewsLangs` here, because the slots
589 // are called synchronously on the UI thread so there is no need
590 // for explicit synchronization.
591 if (checked) {
592 enabledNewsLangs->insert(QString(code));
593 } else {
594 enabledNewsLangs->remove(QString(code));
595 }
596 cfg.writeList(newsLangConfigName, enabledNewsLangs->values());
597 });
598 }
599}
600
602{
603 // always flag development version
604 if (isDevelopmentBuild()) {
605 QString devBuildLabelText = QString("<a style=\"color: " +
606 blendedColor.name() +
607 " \" href=\"https://docs.krita.org/en/untranslatable_pages/triaging_bugs.html?"
608 + analyticsString + "dev-build" + "\">")
609 .append(i18n("DEV BUILD")).append("</a>");
610
611 devBuildLabel->setText(devBuildLabelText);
612 devBuildIcon->setVisible(true);
613 devBuildLabel->setVisible(true);
614 } else {
615 devBuildIcon->setVisible(false);
616 devBuildLabel->setVisible(false);
617 }
618}
619
621{
622 QString fileUrl = index.data(Qt::ToolTipRole).toString();
624}
625
627{
628 QMenu contextMenu;
629 QModelIndex index = recentDocumentsListView->indexAt(pos);
630 QAction *actionForget = 0;
631 if (index.isValid()) {
632 actionForget = new QAction(i18n("Forget \"%1\"", index.data(Qt::DisplayRole).toString()), &contextMenu);
633 contextMenu.addAction(actionForget);
634 }
635 QAction *triggered = contextMenu.exec(recentDocumentsListView->mapToGlobal(pos));
636
637 if (index.isValid() && triggered == actionForget) {
638 m_mainWindow->removeRecentFile(index.data(Qt::ToolTipRole).toString());
639 }
640}
641
646
651
656
658{
660 const bool modelIsEmpty = recentFilesModel->model().rowCount() == 0;
661
662 if (modelIsEmpty) {
663 recentDocsStackedWidget->setCurrentWidget(labelNoRecentDocs);
664 } else {
665 recentDocsStackedWidget->setCurrentWidget(recentDocumentsListView);
666 }
667 clearRecentFilesLink->setVisible(!modelIsEmpty);
668}
669
670#ifdef ENABLE_UPDATERS
671void KisWelcomePageWidget::slotToggleUpdateChecks(bool state)
672{
673 if (m_versionUpdater.isNull()) {
674 return;
675 }
676
677 m_networkIsAllowed = state;
678
679 if (m_networkIsAllowed) {
680 m_versionUpdater->checkForUpdate();
681 }
682
683 updateVersionUpdaterFrame();
684}
685void KisWelcomePageWidget::slotRunVersionUpdate()
686{
687 if (m_versionUpdater.isNull()) {
688 return;
689 }
690
691 if (m_networkIsAllowed) {
692 m_versionUpdater->doUpdate();
693 }
694}
695
696void KisWelcomePageWidget::slotSetUpdateStatus(KisUpdaterStatus updateStatus)
697{
698 m_updaterStatus = updateStatus;
699 updateVersionUpdaterFrame();
700}
701
702void KisWelcomePageWidget::slotShowUpdaterErrorDetails()
703{
704 QMessageBox::warning(qApp->activeWindow(), i18nc("@title:window", "Krita"), m_updaterStatus.updaterOutput());
705}
706
707void KisWelcomePageWidget::updateVersionUpdaterFrame()
708{
709 updaterFrame->setVisible(false);
710 versionNotificationLabel->setVisible(false);
711 bnVersionUpdate->setVisible(false);
712 bnErrorDetails->setVisible(false);
713
714 if (!m_networkIsAllowed || m_versionUpdater.isNull()) {
715 return;
716 }
717
718 QString versionLabelText;
719
720 if (m_updaterStatus.status() == UpdaterStatus::StatusID::UPDATE_AVAILABLE) {
721 updaterFrame->setVisible(true);
722 updaterFrame->setEnabled(true);
723 versionLabelText = i18n("New version of Krita is available.");
724 versionNotificationLabel->setVisible(true);
725 updateIcon->setIcon(KisIconUtils::loadIcon("update-medium"));
726
727 if (m_versionUpdater->hasUpdateCapability()) {
728 bnVersionUpdate->setVisible(true);
729 } else {
730 // build URL for label
731 QString downloadLink = QString(" <a style=\"color: %1; text-decoration: underline\" href=\"%2?%3\">Download Krita %4</a>")
732 .arg(blendedColor.name())
733 .arg(m_updaterStatus.downloadLink())
734 .arg(analyticsString + "version-update")
735 .arg(m_updaterStatus.availableVersion());
736
737 versionLabelText.append(downloadLink);
738 }
739
740 } else if (
741 (m_updaterStatus.status() == UpdaterStatus::StatusID::UPTODATE)
742 || (m_updaterStatus.status() == UpdaterStatus::StatusID::CHECK_ERROR)
743 || (m_updaterStatus.status() == UpdaterStatus::StatusID::IN_PROGRESS)
744 ){
745 // no notifications, if uptodate
746 // also, stay silent on check error - we do not want to generate lots of user support issues
747 // because of failing wifis and proxies over the world
748 updaterFrame->setVisible(false);
749
750 } else if (m_updaterStatus.status() == UpdaterStatus::StatusID::UPDATE_ERROR) {
751 updaterFrame->setVisible(true);
752 versionLabelText = i18n("An error occurred during the update");
753 versionNotificationLabel->setVisible(true);
754 bnErrorDetails->setVisible(true);
755 updateIcon->setIcon(KisIconUtils::loadIcon("warning"));
756 } else if (m_updaterStatus.status() == UpdaterStatus::StatusID::RESTART_REQUIRED) {
757 updaterFrame->setVisible(true);
758 versionLabelText = QString("<b>%1</b> %2").arg(i18n("Restart is required.")).arg(m_updaterStatus.details());
759 versionNotificationLabel->setVisible(true);
760 updateIcon->setIcon(KisIconUtils::loadIcon("view-refresh"));
761 }
762
763 versionNotificationLabel->setText(versionLabelText);
764 if (!blendedStyle.isNull()) {
765 versionNotificationLabel->setStyleSheet(blendedStyle);
766 }
767}
768#endif
769
770#ifdef Q_OS_ANDROID
771void KisWelcomePageWidget::initDonations()
772{
774 if (!androidDonations) {
775 qWarning("KisWelcomePage::initDonations: androidDonations is null");
776 return;
777 }
778
779 connect(donationLink, SIGNAL(linkActivated(QString)), androidDonations, SLOT(slotStartDonationFlow()));
780
781 QString bannerPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, "share/krita/donation/banner.png");
782 QPixmap pixmap(bannerPath);
783 if (pixmap.isNull()) {
784 qWarning("KisWelcomePage::initDonations: failed to load banner from '%s'", qUtf8Printable(bannerPath));
785 } else {
786 supporterBadge->setPixmap(QPixmap(bannerPath));
787 }
788
789 connect(androidDonations, SIGNAL(sigStateChanged()), this, SLOT(slotUpdateDonationState()));
790 slotUpdateDonationState();
791}
792
793void KisWelcomePageWidget::slotUpdateDonationState()
794{
795 bool linkVisible = false;
796 bool badgeVisible = false;
797
799 if (androidDonations) {
800 linkVisible = androidDonations->shouldShowDonationLink();
801 badgeVisible = androidDonations->shouldShowSupporterBadge();
802 } else {
803 qWarning("KisWelcomePageWidget::slotUpdateDonationState: android donations is null");
804 }
805
806 donationLink->setVisible(linkVisible);
807 supporterBadge->setVisible(badgeVisible);
808}
809#endif
810
812{
813 QFont larger = font();
814 larger.setPointSizeF(larger.pointSizeF() * 1.1f);
815 return larger;
816}
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
KisAction * actionByName(const QString &name) const
bool shouldShowDonationLink() const
bool shouldShowSupporterBadge() const
static KisAndroidDonations * instance()
void writeList(const QString &name, const QList< T > &value)
Definition kis_config.h:784
QList< T > readList(const QString &name, const QList< T > &defaultValue=QList< T >())
Definition kis_config.h:794
T readEntry(const QString &name, const T &defaultValue=T())
Definition kis_config.h:789
Main window for Krita.
bool installBundle(const QString &fileName) const
Copy the given file into the bundle directory.
bool openDocument(const QString &path, OpenFlags flags)
KisViewManager * viewManager
void slotFileOpen(bool isImporting=false)
void removeRecentFile(QString url)
void dragMoveEvent(QDragMoveEvent *event) override
static KisRecentDocumentsModelWrapper * instance()
The KisRemoteFileFetcher class can fetch a remote file and blocks until the file is downloaded.
bool fetchFile(const QUrl &remote, QIODevice *io)
fetch the image. Shows a progress dialog
static void log(const QString &message)
Logs with date/time.
KisActionManager * actionManager() const
void recentDocumentClicked(QModelIndex index)
KisWelcomePageWidget(QWidget *parent)
void dragEnterEvent(QDragEnterEvent *event) override
void setupNewsLangSelection(QMenu *newsOptionMenu)
bool eventFilter(QObject *watched, QEvent *event) override
void dropEvent(QDropEvent *event) override
void slotScrollerStateChanged(QScroller::State state)
void dragLeaveEvent(QDragLeaveEvent *event) override
void changeEvent(QEvent *event) override
void setMainWindow(KisMainWindow *m_mainWindow)
void slotRecentDocContextMenuRequest(const QPoint &pos)
void showDropAreaIndicator(bool show)
void dragMoveEvent(QDragMoveEvent *event) override
RecentItemDelegate(QObject *parent=0)
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &) const override
void setItemHeight(int itemHeight)
void enableFromLink(QString unused_url)
QIcon loadIcon(const QString &name)
void updateIcon(QAbstractButton *button)
KRITAWIDGETUTILS_EXPORT QScroller * createPreconfiguredScroller(QAbstractScrollArea *target)
QColor blendColors(const QColor &c1, const QColor &c2, qreal r1)
KRITAVERSION_EXPORT bool isDevelopersBuild()