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