"))+1);
+ uint firstParaEndBr = (uint) html.indexOf(QLatin1String(" request().url();
+ requestUrl = source.toString();
+ m_streamReader.setDevice(reply);
+
+ return parseStream(m_streamReader);
+}
+
+RssItemList KisRssReader::parse(QFile &file) {
+ requestUrl = file.fileName();
+ file.open(QIODevice::ReadOnly);
+ m_streamReader.setDevice(&file);
+
+ RssItemList itemList(parseStream(m_streamReader));
+
+ file.close();
+ return itemList;
+}
diff --git a/libs/ui/KisMultiFeedRSSModel.h b/libs/ui/KisRssReader.h
similarity index 51%
copy from libs/ui/KisMultiFeedRSSModel.h
copy to libs/ui/KisRssReader.h
index 7575013244..17c1868297 100644
--- a/libs/ui/KisMultiFeedRSSModel.h
+++ b/libs/ui/KisRssReader.h
@@ -1,108 +1,87 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**************************************************************************/
-#ifndef MULTIFEEDRSSMODEL_H
-#define MULTIFEEDRSSMODEL_H
+#ifndef KISRSSREADER_H
+#define KISRSSREADER_H
-#include
-#include
+#include
#include
+#include
+#include
+#include
#include
-class QThread;
-class QNetworkReply;
-class QNetworkAccessManager;
-
struct RssItem {
QString source;
QString title;
QString link;
QString description;
QString category;
QString blogName;
QString blogIcon;
QDateTime pubDate;
};
typedef QList RssItemList;
-class KisNetworkAccessManager;
+Q_DECLARE_METATYPE(RssItem);
-enum RssRoles { TitleRole = Qt::UserRole + 1, DescriptionRole, LinkRole,
- PubDateRole, CategoryRole, BlogNameRole, BlogIconRole
- };
-class KRITAUI_EXPORT MultiFeedRssModel : public QAbstractListModel
+class KRITAUI_EXPORT KisRssReader
{
- Q_OBJECT
- Q_PROPERTY(int articleCount READ articleCount WRITE setArticleCount NOTIFY articleCountChanged)
public:
- explicit MultiFeedRssModel(QObject *parent = 0);
- ~MultiFeedRssModel() override;
- QHash roleNames() const override;
- void addFeed(const QString& feed);
- void removeFeed(const QString& feed);
-
- int rowCount(const QModelIndex &parent = QModelIndex()) const override;
- QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
-
- int articleCount() const {
- return m_articleCount;
- }
-
-public Q_SLOTS:
- void setArticleCount(int arg) {
- if (m_articleCount != arg) {
- m_articleCount = arg;
- emit articleCountChanged(arg);
- }
- }
-
-Q_SIGNALS:
- void articleCountChanged(int arg);
- void feedDataChanged();
-
-private Q_SLOTS:
- void appendFeedData(QNetworkReply *reply);
+ KisRssReader();
+
+ enum RssRoles {
+ TitleRole = Qt::UserRole + 1,
+ DescriptionRole,
+ LinkRole,
+ PubDateRole,
+ CategoryRole,
+ BlogNameRole,
+ BlogIconRole
+ };
+
+ RssItem parseItem();
+ RssItemList parseStream(QXmlStreamReader& streamReader);
+ RssItemList parse(QNetworkReply *reply);
+ RssItemList parse(QFile& file);
private:
- QStringList m_sites;
- RssItemList m_aggregatedFeed;
- QNetworkAccessManager *m_networkAccessManager;
- QThread *m_namThread;
- int m_articleCount;
+ QXmlStreamReader m_streamReader;
+ QString requestUrl;
+ QString blogIcon;
+ QString blogName;
};
-#endif // MULTIFEEDRSSMODEL_H
-
-
+#endif // KISRSSREADER_H
diff --git a/libs/ui/KisWelcomePageWidget.cpp b/libs/ui/KisWelcomePageWidget.cpp
index 652bdb46bf..bb80b6c96f 100644
--- a/libs/ui/KisWelcomePageWidget.cpp
+++ b/libs/ui/KisWelcomePageWidget.cpp
@@ -1,460 +1,634 @@
/* This file is part of the KDE project
* Copyright (C) 2018 Scott Petrovic
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisWelcomePageWidget.h"
-#include
#include
#include
#include
#include
#include
+#include
#include
#include "kis_action_manager.h"
#include "kactioncollection.h"
#include "kis_action.h"
#include "KConfigGroup"
#include "KSharedConfig"
#include
#include
#include "kis_icon_utils.h"
#include "krita_utils.h"
#include "KoStore.h"
#include "kis_config.h"
#include "KisDocument.h"
#include
#include
#include
+#include
+
+#include
+#include
+#include
+
+#include "config-updaters.h"
+
+#ifdef ENABLE_UPDATERS
+#ifdef Q_OS_LINUX
+#include
+#endif
+
+#include
+#endif
+
+#include
+#include
+
+#include
KisWelcomePageWidget::KisWelcomePageWidget(QWidget *parent)
: QWidget(parent)
{
setupUi(this);
recentDocumentsListView->setDragEnabled(false);
recentDocumentsListView->viewport()->setAutoFillBackground(false);
recentDocumentsListView->setSpacing(2);
// set up URLs that go to web browser
manualLink->setTextFormat(Qt::RichText);
manualLink->setTextInteractionFlags(Qt::TextBrowserInteraction);
manualLink->setOpenExternalLinks(true);
gettingStartedLink->setTextFormat(Qt::RichText);
gettingStartedLink->setTextInteractionFlags(Qt::TextBrowserInteraction);
gettingStartedLink->setOpenExternalLinks(true);
supportKritaLink->setTextFormat(Qt::RichText);
supportKritaLink->setTextInteractionFlags(Qt::TextBrowserInteraction);
supportKritaLink->setOpenExternalLinks(true);
userCommunityLink->setTextFormat(Qt::RichText);
userCommunityLink->setTextInteractionFlags(Qt::TextBrowserInteraction);
userCommunityLink->setOpenExternalLinks(true);
kritaWebsiteLink->setTextFormat(Qt::RichText);
kritaWebsiteLink->setTextInteractionFlags(Qt::TextBrowserInteraction);
kritaWebsiteLink->setOpenExternalLinks(true);
sourceCodeLink->setTextFormat(Qt::RichText);
sourceCodeLink->setTextInteractionFlags(Qt::TextBrowserInteraction);
sourceCodeLink->setOpenExternalLinks(true);
poweredByKDELink->setTextFormat(Qt::RichText);
poweredByKDELink->setTextInteractionFlags(Qt::TextBrowserInteraction);
poweredByKDELink->setOpenExternalLinks(true);
kdeIcon->setIconSize(QSize(20, 20));
kdeIcon->setIcon(KisIconUtils::loadIcon(QStringLiteral("kde")).pixmap(20));
versionNotificationLabel->setTextFormat(Qt::RichText);
versionNotificationLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
versionNotificationLabel->setOpenExternalLinks(true);
+ devBuildIcon->setIcon(KisIconUtils::loadIcon("warning"));
+
+ devBuildLabel->setTextFormat(Qt::RichText);
+ devBuildLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
+ devBuildLabel->setOpenExternalLinks(true);
+ devBuildLabel->setVisible(false);
+
+ updaterFrame->setVisible(false);
+ versionNotificationLabel->setVisible(false);
+ bnVersionUpdate->setVisible(false);
+ bnErrorDetails->setVisible(false);
+
connect(chkShowNews, SIGNAL(toggled(bool)), newsWidget, SLOT(toggleNews(bool)));
- connect(newsWidget, SIGNAL(newsDataChanged()), this, SLOT(slotUpdateVersionMessage()));
+#ifdef ENABLE_UPDATERS
+ connect(chkShowNews, SIGNAL(toggled(bool)), this, SLOT(slotToggleUpdateChecks(bool)));
+#endif
#ifdef Q_OS_ANDROID
// checking this widgets crashes the app, so it is better for it to be hidden for now
newsWidget->hide();
helpTitleLabel_2->hide();
chkShowNews->hide();
#endif
-
// configure the News area
KisConfig cfg(true);
- bool m_getNews = cfg.readEntry("FetchNews", false);
- chkShowNews->setChecked(m_getNews);
+ m_checkUpdates = cfg.readEntry("FetchNews", false);
+
+
+#ifdef ENABLE_UPDATERS
+#ifndef Q_OS_ANDROID
+ // Setup version updater, but do not check for them, unless the user explicitely
+ // wants to check for updates.
+ // * No updater is created for Linux/Steam, Windows/Steam and Windows/Store distributions,
+ // as those stores have their own updating mechanism.
+ // * STEAMAPPID(Windows)/SteamAppId(Linux) environment variable is set when Krita is run from Steam.
+ // The environment variables are not public API.
+ // * AppxManifest.xml file in the installation directory indicates MS Store version
+#if defined Q_OS_LINUX
+ if (!qEnvironmentVariableIsSet("SteamAppId")) { // do not create updater for linux/steam
+ if (qEnvironmentVariableIsSet("APPIMAGE")) {
+ m_versionUpdater.reset(new KisAppimageUpdater());
+ } else {
+ m_versionUpdater.reset(new KisManualUpdater());
+ }
+ }
+#elif defined Q_OS_WIN
+ QString appxManifestFilePath = QString("%1/../AppxManifest.xml").arg(QCoreApplication::applicationDirPath());
+ QFileInfo appxManifestFileInfo(appxManifestFilePath);
- setAcceptDrops(true);
+ if (!appxManifestFileInfo.exists() && !qEnvironmentVariableIsSet("STEAMAPPID")) {
+ m_versionUpdater.reset(new KisManualUpdater());
+ KisUsageLogger::log("Non-store package - creating updater");
+ } else {
+ KisUsageLogger::log("detected appx or steam package - not creating the updater");
+ }
+
+#else
+ // always create updater for MacOS
+ m_versionUpdater.reset(new KisManualUpdater());
+#endif // Q_OS_*
+
+ if (!m_versionUpdater.isNull()) {
+ connect(bnVersionUpdate, SIGNAL(clicked()), this, SLOT(slotRunVersionUpdate()));
+ connect(bnErrorDetails, SIGNAL(clicked()), this, SLOT(slotShowUpdaterErrorDetails()));
+ connect(m_versionUpdater.data(), SIGNAL(sigUpdateCheckStateChange(KisUpdaterStatus)),
+ this, SLOT(slotSetUpdateStatus(const KisUpdaterStatus&)));
+ if (m_checkUpdates) { // only if the user wants them
+ m_versionUpdater->checkForUpdate();
+ }
+ }
+#endif // ifndef Q_OS_ANDROID
+#endif // ENABLE_UPDATERS
+
+ chkShowNews->setChecked(m_checkUpdates);
+
+ setAcceptDrops(true);
}
KisWelcomePageWidget::~KisWelcomePageWidget()
{
}
void KisWelcomePageWidget::setMainWindow(KisMainWindow* mainWin)
{
if (mainWin) {
m_mainWindow = mainWin;
// set the shortcut links from actions (only if a shortcut exists)
if ( mainWin->viewManager()->actionManager()->actionByName("file_new")->shortcut().toString() != "") {
newFileLinkShortcut->setText(QString("(") + mainWin->viewManager()->actionManager()->actionByName("file_new")->shortcut().toString() + QString(")"));
}
if (mainWin->viewManager()->actionManager()->actionByName("file_open")->shortcut().toString() != "") {
openFileShortcut->setText(QString("(") + mainWin->viewManager()->actionManager()->actionByName("file_open")->shortcut().toString() + QString(")"));
}
connect(recentDocumentsListView, SIGNAL(clicked(QModelIndex)), this, SLOT(recentDocumentClicked(QModelIndex)));
// we need the view manager to actually call actions, so don't create the connections
// until after the view manager is set
connect(newFileLink, SIGNAL(clicked(bool)), this, SLOT(slotNewFileClicked()));
connect(openFileLink, SIGNAL(clicked(bool)), this, SLOT(slotOpenFileClicked()));
connect(clearRecentFilesLink, SIGNAL(clicked(bool)), mainWin, SLOT(clearRecentFiles()));
slotUpdateThemeColors();
// allows RSS news items to apply analytics tracking.
newsWidget->setAnalyticsTracking("?" + analyticsString);
}
}
void KisWelcomePageWidget::showDropAreaIndicator(bool show)
{
if (!show) {
QString dropFrameStyle = "QFrame#dropAreaIndicator { border: 0px }";
dropFrameBorder->setStyleSheet(dropFrameStyle);
} else {
QColor textColor = qApp->palette().color(QPalette::Text);
QColor backgroundColor = qApp->palette().color(QPalette::Background);
QColor blendedColor = KritaUtils::blendColors(textColor, backgroundColor, 0.8);
// QColor.name() turns it into a hex/web format
QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 2px dotted ").append(blendedColor.name()).append(" }") ;
dropFrameBorder->setStyleSheet(dropFrameStyle);
}
}
void KisWelcomePageWidget::slotUpdateThemeColors()
{
textColor = qApp->palette().color(QPalette::Text);
backgroundColor = qApp->palette().color(QPalette::Background);
// make the welcome screen labels a subtle color so it doesn't clash with the main UI elements
blendedColor = KritaUtils::blendColors(textColor, backgroundColor, 0.8);
blendedStyle = QString("color: ").append(blendedColor.name());
// what labels to change the color...
startTitleLabel->setStyleSheet(blendedStyle);
recentDocumentsLabel->setStyleSheet(blendedStyle);
helpTitleLabel->setStyleSheet(blendedStyle);
newFileLinkShortcut->setStyleSheet(blendedStyle);
openFileShortcut->setStyleSheet(blendedStyle);
clearRecentFilesLink->setStyleSheet(blendedStyle);
recentDocumentsListView->setStyleSheet(blendedStyle);
newFileLink->setStyleSheet(blendedStyle);
openFileLink->setStyleSheet(blendedStyle);
// giving the drag area messaging a dotted border
QString dottedBorderStyle = QString("border: 2px dotted ").append(blendedColor.name()).append("; color:").append(blendedColor.name()).append( ";");
dragImageHereLabel->setStyleSheet(dottedBorderStyle);
// make drop area QFrame have a dotted line
dropFrameBorder->setObjectName("dropAreaIndicator");
QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 4px dotted ").append(blendedColor.name()).append("}");
dropFrameBorder->setStyleSheet(dropFrameStyle);
// only show drop area when we have a document over the empty area
showDropAreaIndicator(false);
// add icons for new and open settings to make them stand out a bit more
openFileLink->setIconSize(QSize(30, 30));
newFileLink->setIconSize(QSize(30, 30));
openFileLink->setIcon(KisIconUtils::loadIcon("document-open"));
newFileLink->setIcon(KisIconUtils::loadIcon("document-new"));
kdeIcon->setIcon(KisIconUtils::loadIcon(QStringLiteral("kde")).pixmap(20));
// HTML links seem to be a bit more stubborn with theme changes... setting inline styles to help with color change
userCommunityLink->setText(QString("")
.append(i18n("User Community")).append(""));
gettingStartedLink->setText(QString("")
.append(i18n("Getting Started")).append(""));
manualLink->setText(QString("")
.append(i18n("User Manual")).append(""));
supportKritaLink->setText(QString("")
.append(i18n("Support Krita")).append(""));
kritaWebsiteLink->setText(QString("")
.append(i18n("Krita Website")).append(""));
sourceCodeLink->setText(QString("")
.append(i18n("Source Code")).append(""));
poweredByKDELink->setText(QString("")
.append(i18n("Powered by KDE")).append(""));
- slotUpdateVersionMessage(); // text set from RSS feed
+ // show the dev version labels, if dev version is detected
+ showDevVersionHighlight();
+
+#ifdef ENABLE_UPDATERS
+ updateVersionUpdaterFrame(); // updater frame
+#endif
// re-populate recent files since they might have themed icons
populateRecentDocuments();
}
void KisWelcomePageWidget::populateRecentDocuments()
{
m_recentFilesModel.clear(); // clear existing data before it gets re-populated
// grab recent files data
int numRecentFiles = m_mainWindow->recentFilesUrls().length() > 5 ? 5 : m_mainWindow->recentFilesUrls().length(); // grab at most 5
for (int i = 0; i < numRecentFiles; i++ ) {
QStandardItem *recentItem = new QStandardItem(1,2); // 1 row, 1 column
recentItem->setIcon(KisIconUtils::loadIcon("document-export"));
QString recentFileUrlPath = m_mainWindow->recentFilesUrls().at(i).toLocalFile();
QString fileName = QFileInfo(recentFileUrlPath).fileName();
QList brokenUrls;
if (m_thumbnailMap.contains(recentFileUrlPath)) {
recentItem->setIcon(m_thumbnailMap[recentFileUrlPath]);
}
else {
QFileInfo fi(recentFileUrlPath);
if (fi.exists()) {
QString mimeType = KisMimeDatabase::mimeTypeForFile(recentFileUrlPath);
if (mimeType == KisDocument::nativeFormatMimeType()
|| mimeType == "image/openraster") {
QScopedPointer store(KoStore::createStore(recentFileUrlPath, KoStore::Read));
if (store) {
QString thumbnailpath;
if (store->hasFile(QString("Thumbnails/thumbnail.png"))){
thumbnailpath = QString("Thumbnails/thumbnail.png");
} else if (store->hasFile(QString("preview.png"))) {
thumbnailpath = QString("preview.png");
}
if (!thumbnailpath.isEmpty()) {
if (store->open(thumbnailpath)) {
QByteArray bytes = store->read(store->size());
store->close();
QImage img;
img.loadFromData(bytes);
img.setDevicePixelRatio(devicePixelRatioF());
recentItem->setIcon(QIcon(QPixmap::fromImage(img)));
}
}
}
else {
brokenUrls << m_mainWindow->recentFilesUrls().at(i);
}
}
else if (mimeType == "image/tiff" || mimeType == "image/x-tiff") {
// Workaround for a bug in Qt tiff QImageIO plugin
QScopedPointer doc;
doc.reset(KisPart::instance()->createTemporaryDocument());
doc->setFileBatchMode(true);
bool r = doc->openUrl(QUrl::fromLocalFile(recentFileUrlPath), KisDocument::DontAddToRecent);
if (r) {
KisPaintDeviceSP projection = doc->image()->projection();
recentItem->setIcon(QIcon(QPixmap::fromImage(projection->createThumbnail(48, 48, projection->exactBounds()))));
}
else {
brokenUrls << m_mainWindow->recentFilesUrls().at(i);
}
}
else {
QImage img;
img.setDevicePixelRatio(devicePixelRatioF());
img.load(recentFileUrlPath);
if (!img.isNull()) {
recentItem->setIcon(QIcon(QPixmap::fromImage(img.scaledToWidth(48))));
}
else {
brokenUrls << m_mainWindow->recentFilesUrls().at(i);
}
}
if (brokenUrls.size() == 0 || brokenUrls.last().toLocalFile() != recentFileUrlPath) {
m_thumbnailMap[recentFileUrlPath] = recentItem->icon();
}
}
}
Q_FOREACH(const QUrl &url, brokenUrls) {
m_mainWindow->removeRecentUrl(url);
}
// set the recent object with the data
if (brokenUrls.isEmpty() || brokenUrls.last().toLocalFile() != recentFileUrlPath) {
recentItem->setText(fileName); // what to display for the item
recentItem->setToolTip(recentFileUrlPath);
m_recentFilesModel.appendRow(recentItem);
}
}
// hide clear and Recent files title if there are none
bool hasRecentFiles = m_mainWindow->recentFilesUrls().length() > 0;
recentDocumentsLabel->setVisible(hasRecentFiles);
clearRecentFilesLink->setVisible(hasRecentFiles);
recentDocumentsListView->setIconSize(QSize(48, 48));
recentDocumentsListView->setModel(&m_recentFilesModel);
}
-void KisWelcomePageWidget::slotUpdateVersionMessage()
-{
- alertIcon->setIcon(KisIconUtils::loadIcon("warning"));
- alertIcon->setVisible(false);
-
- // find out if we need an update...or if this is a development version:
- // dev builds contain GIT hash in it and the word git
- // stable versions do not contain this.
- if (qApp->applicationVersion().contains("git")) {
- // Development build
- QString versionLabelText = QString("")
- .append(i18n("DEV BUILD")).append("");
-
- versionNotificationLabel->setText(versionLabelText);
- alertIcon->setVisible(true);
- versionNotificationLabel->setVisible(true);
-
- } else if (newsWidget->hasUpdateAvailable()) {
-
- // build URL for label
- QString versionLabelText = QString("versionLink() + "?" +
- analyticsString + "version-update" + "\">")
- .append(i18n("New Version Available!")).append("");
-
- versionNotificationLabel->setVisible(true);
- versionNotificationLabel->setText(versionLabelText);
- alertIcon->setVisible(true);
-
- } else {
- // no message needed... exit
- versionNotificationLabel->setVisible(false);
- return;
- }
-
- if (!blendedStyle.isNull()) {
- versionNotificationLabel->setStyleSheet(blendedStyle);
- }
-
-}
void KisWelcomePageWidget::dragEnterEvent(QDragEnterEvent *event)
{
//qDebug() << "dragEnterEvent formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage();
showDropAreaIndicator(true);
if (event->mimeData()->hasUrls() ||
event->mimeData()->hasFormat("application/x-krita-node") ||
event->mimeData()->hasFormat("application/x-qt-image")) {
event->accept();
}
}
void KisWelcomePageWidget::dropEvent(QDropEvent *event)
{
//qDebug() << "KisWelcomePageWidget::dropEvent() formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage();
showDropAreaIndicator(false);
if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) {
Q_FOREACH (const QUrl &url, event->mimeData()->urls()) {
if (url.toLocalFile().endsWith(".bundle")) {
bool r = m_mainWindow->installBundle(url.toLocalFile());
if (!r) {
qWarning() << "Could not install bundle" << url.toLocalFile();
}
}
else {
m_mainWindow->openDocument(url, KisMainWindow::None);
}
}
}
}
void KisWelcomePageWidget::dragMoveEvent(QDragMoveEvent *event)
{
//qDebug() << "dragMoveEvent";
m_mainWindow->dragMoveEvent(event);
if (event->mimeData()->hasUrls() ||
event->mimeData()->hasFormat("application/x-krita-node") ||
event->mimeData()->hasFormat("application/x-qt-image")) {
event->accept();
}
}
void KisWelcomePageWidget::dragLeaveEvent(QDragLeaveEvent */*event*/)
{
//qDebug() << "dragLeaveEvent";
showDropAreaIndicator(false);
m_mainWindow->dragLeave();
}
+void KisWelcomePageWidget::showDevVersionHighlight()
+{
+ // always flag developement version
+ if (isDevelopmentBuild()) {
+ QString devBuildLabelText = QString("")
+ .append(i18n("DEV BUILD")).append("");
+
+ devBuildLabel->setText(devBuildLabelText);
+ devBuildIcon->setVisible(true);
+ devBuildLabel->setVisible(true);
+ } else {
+ devBuildIcon->setVisible(false);
+ devBuildLabel->setVisible(false);
+ }
+}
+
void KisWelcomePageWidget::recentDocumentClicked(QModelIndex index)
{
QString fileUrl = index.data(Qt::ToolTipRole).toString();
m_mainWindow->openDocument(QUrl::fromLocalFile(fileUrl), KisMainWindow::None );
}
+bool KisWelcomePageWidget::isDevelopmentBuild()
+{
+ QString versionString = KritaVersionWrapper::versionString(true);
+
+ if (versionString.contains("git")) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
void KisWelcomePageWidget::slotNewFileClicked()
{
m_mainWindow->slotFileNew();
}
void KisWelcomePageWidget::slotOpenFileClicked()
{
m_mainWindow->slotFileOpen();
}
+#ifdef ENABLE_UPDATERS
+void KisWelcomePageWidget::slotToggleUpdateChecks(bool state)
+{
+ if (m_versionUpdater.isNull()) {
+ return;
+ }
+
+ m_checkUpdates = state;
+
+ if (m_checkUpdates) {
+ m_versionUpdater->checkForUpdate();
+ }
+
+ updateVersionUpdaterFrame();
+}
+
+void KisWelcomePageWidget::slotRunVersionUpdate()
+{
+ if (m_versionUpdater.isNull()) {
+ return;
+ }
+
+ if (m_checkUpdates) {
+ m_versionUpdater->doUpdate();
+ }
+}
+
+void KisWelcomePageWidget::slotSetUpdateStatus(KisUpdaterStatus updateStatus)
+{
+ m_updaterStatus = updateStatus;
+ updateVersionUpdaterFrame();
+}
+
+void KisWelcomePageWidget::slotShowUpdaterErrorDetails()
+{
+ QMessageBox::warning(0, i18nc("@title:window", "Krita"), m_updaterStatus.updaterOutput());
+}
+
+void KisWelcomePageWidget::updateVersionUpdaterFrame()
+{
+ updaterFrame->setVisible(false);
+ versionNotificationLabel->setVisible(false);
+ bnVersionUpdate->setVisible(false);
+ bnErrorDetails->setVisible(false);
+
+ if (!m_checkUpdates || m_versionUpdater.isNull()) {
+ return;
+ }
+
+ QString versionLabelText;
+
+ if (m_updaterStatus.status() == UpdaterStatus::StatusID::UPDATE_AVAILABLE) {
+ updaterFrame->setVisible(true);
+ updaterFrame->setEnabled(true);
+ versionLabelText = i18n("New version of Krita is available.");
+ versionNotificationLabel->setVisible(true);
+ updateIcon->setIcon(KisIconUtils::loadIcon("update-medium"));
+
+ if (m_versionUpdater->hasUpdateCapability()) {
+ bnVersionUpdate->setVisible(true);
+// bnVersionUpdate->setEnabled(true);
+ } else {
+ // build URL for label
+ QString downloadLink = QString(" Download Krita %4")
+ .arg(blendedColor.name())
+ .arg(m_updaterStatus.downloadLink())
+ .arg(analyticsString + "version-update")
+ .arg(m_updaterStatus.availableVersion());
+
+ versionLabelText.append(downloadLink);
+ }
+
+ } else if (
+ (m_updaterStatus.status() == UpdaterStatus::StatusID::UPTODATE)
+ || (m_updaterStatus.status() == UpdaterStatus::StatusID::CHECK_ERROR)
+ || (m_updaterStatus.status() == UpdaterStatus::StatusID::IN_PROGRESS)
+ ){
+ // no notifications, if uptodate
+ // also, stay silent on check error - we do not want to generate lots of user support issues
+ // because of failing wifis and proxies over the world
+ updaterFrame->setVisible(false);
+
+ } else if (m_updaterStatus.status() == UpdaterStatus::StatusID::UPDATE_ERROR) {
+ updaterFrame->setVisible(true);
+ versionLabelText = i18n("An error occurred during the update");
+ versionNotificationLabel->setVisible(true);
+ bnErrorDetails->setVisible(true);
+ updateIcon->setIcon(KisIconUtils::loadIcon("warning"));
+
+// bnErrorDetails->setEnabled(true);
+
+ } else if (m_updaterStatus.status() == UpdaterStatus::StatusID::RESTART_REQUIRED) {
+ updaterFrame->setVisible(true);
+ versionLabelText = QString("%1 %2").arg(i18n("Restart is required.")).arg(m_updaterStatus.details());
+ versionNotificationLabel->setVisible(true);
+ updateIcon->setIcon(KisIconUtils::loadIcon("view-refresh"));
+ }
+
+ versionNotificationLabel->setText(versionLabelText);
+ if (!blendedStyle.isNull()) {
+ versionNotificationLabel->setStyleSheet(blendedStyle);
+ }
+}
+#endif
diff --git a/libs/ui/KisWelcomePageWidget.h b/libs/ui/KisWelcomePageWidget.h
index 51a4fafa62..1e29394dfc 100644
--- a/libs/ui/KisWelcomePageWidget.h
+++ b/libs/ui/KisWelcomePageWidget.h
@@ -1,93 +1,117 @@
/* This file is part of the KDE project
* Copyright (C) 2018 Scott Petrovic
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KISWELCOMEPAGEWIDGET_H
#define KISWELCOMEPAGEWIDGET_H
#include "kritaui_export.h"
#include "KisViewManager.h"
#include "KisMainWindow.h"
+#include
#include
#include "ui_KisWelcomePage.h"
#include
+#include
+
+#include "config-updaters.h"
/// A widget for displaying if no documents are open. This will display in the MDI area
class KRITAUI_EXPORT KisWelcomePageWidget : public QWidget, public Ui::KisWelcomePage
{
Q_OBJECT
public:
explicit KisWelcomePageWidget(QWidget *parent);
~KisWelcomePageWidget() override;
void setMainWindow(KisMainWindow* m_mainWindow);
public Q_SLOTS:
/// if a document is placed over this area, a dotted line will appear as an indicator
/// that it is a droppable area. KisMainwindow is what triggers this
void showDropAreaIndicator(bool show);
void slotUpdateThemeColors();
/// this could be called multiple times. If a recent document doesn't
/// have a preview, an icon is used that needs to be updated
void populateRecentDocuments();
- void slotUpdateVersionMessage();
+#ifdef ENABLE_UPDATERS
+ void slotSetUpdateStatus(KisUpdaterStatus updateStatus);
+ void slotShowUpdaterErrorDetails();
+#endif
protected:
// QWidget overrides
void dragEnterEvent(QDragEnterEvent * event) override;
void dropEvent(QDropEvent * event) override;
void dragMoveEvent(QDragMoveEvent * event) override;
void dragLeaveEvent(QDragLeaveEvent * event) override;
private:
+ void showDevVersionHighlight();
+
+#ifdef ENABLE_UPDATERS
+ void updateVersionUpdaterFrame();
+#endif
+
KisMainWindow *m_mainWindow;
QStandardItemModel m_recentFilesModel;
QMap m_thumbnailMap;
/// help us see how many people are clicking startup screen links
/// you can see the results in Matomo (stats.kde.org)
/// this will be listed in the "Acquisition" section of Matomo
/// just append some text to this to associate it with an event/page
const QString analyticsString = "pk_campaign=startup-sceen&pk_kwd=";
// keeping track of link colors with theme change
QColor textColor;
QColor backgroundColor;
QColor blendedColor;
QString blendedStyle;
-
+#ifdef ENABLE_UPDATERS
+ QScopedPointer m_versionUpdater;
+ KisUpdaterStatus m_updaterStatus;
+#endif
+ bool m_checkUpdates {false};
private Q_SLOTS:
void slotNewFileClicked();
void slotOpenFileClicked();
void recentDocumentClicked(QModelIndex index);
+
+#ifdef ENABLE_UPDATERS
+ void slotRunVersionUpdate();
+ void slotToggleUpdateChecks(bool state);
+#endif
+
+ bool isDevelopmentBuild();
};
#endif // KISWELCOMEPAGEWIDGET_H
diff --git a/libs/ui/forms/KisWelcomePage.ui b/libs/ui/forms/KisWelcomePage.ui
index 16ea785892..3cadb26c80 100644
--- a/libs/ui/forms/KisWelcomePage.ui
+++ b/libs/ui/forms/KisWelcomePage.ui
@@ -1,851 +1,992 @@
KisWelcomePage00
- 788
- 476
+ 1118
+ 59300falseQFrame::BoxQFrame::Plain40
-
+
+
+
+ Qt::Vertical
+
+
+
+ 5
+ 5
+
+
+
+
+ 50030018Community00trueUser ManualQt::HorizontalQSizePolicy::Expanding4050trueGetting StartedQt::Horizontal405trueSupport KritaQt::Horizontal4020trueUser CommunityQt::Horizontal4020trueKrita WebsiteQt::Horizontal402000trueSource CodeQt::Horizontal40202020202020truetruePowered by KDEQt::Horizontal402000QFrame::Box2Drag Image in window to openQt::AlignCentertrue30Qt::Horizontal4020Qt::Vertical2020
-
-
-
- Qt::Horizontal
-
-
-
- 5
- 5
-
-
-
-
-
+ Qt::Vertical55
-
-
-
- Qt::Vertical
-
-
-
- 5
- 5
-
-
-
-
-
-
-
- Qt::Horizontal
-
-
-
- 5
- 5
-
-
-
-
-
+ 50200070018Start
-
+ 0016161616true
-
-
-
- 0
- 0
-
-
-
-
- true
-
-
+
- Krita X.X.X Update Available
-
-
- 2
-
-
- false
+ DEV BUILDQt::Horizontal40200trueQt::NoFocusNew Filefalsetrue200falseQt::Horizontal4020030trueQt::NoFocusOpen Filefalsetrue200falseQt::Horizontal402018Recent Documents00trueQt::NoFocusClearfalsetrueQt::Horizontal52000300250truefalseQt::NoFocusfalseQFrame::NoFrameQFrame::Plain0falsefalseQAbstractItemView::SelectItemsQListView::SnapQListView::Fixed0QListView::ListModetruefalseQt::Vertical55
-
+
+
+
+ Qt::Horizontal
+
+
+
+ 5
+ 5
+
+
+
+
+
+
+
+ Qt::Horizontal
+
+
+
+ 5
+ 5
+
+
+
+
+ 0018News0true00Show news about Krita: this needs internet to retrieve information from the krita.org websiteCheck for updates
-
+
+
+ false
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 200
+
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+
+
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 32
+
+
+
+
+ 50
+ 50
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+ true
+
+
+
+
+
+
+ false
+
+
+
+ 0
+ 0
+
+
+
+
+ false
+
+
+
+ Krita Version X.X.X Available
+
+
+ false
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+
+
+ true
+
+
+ 2
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 50
+ 20
+
+
+
+
+
+
+
+ Update now!
+
+
+
+
+
+
+ Show details
+
+
+
+
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+ KisNewsWidgetQWidgetwidgets/KisNewsWidget.h1
diff --git a/libs/ui/tests/CMakeLists.txt b/libs/ui/tests/CMakeLists.txt
index 215bfaeea9..c5cd5d132e 100644
--- a/libs/ui/tests/CMakeLists.txt
+++ b/libs/ui/tests/CMakeLists.txt
@@ -1,169 +1,191 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include_directories(${CMAKE_SOURCE_DIR}/libs/image/metadata
${CMAKE_SOURCE_DIR}/sdk/tests )
include(ECMAddTests)
macro_add_unittest_definitions()
ecm_add_tests(
kis_image_view_converter_test.cpp
kis_shape_selection_test.cpp
kis_doc2_test.cpp
kis_coordinates_converter_test.cpp
kis_grid_config_test.cpp
kis_stabilized_events_sampler_test.cpp
kis_brush_hud_properties_config_test.cpp
kis_shape_commands_test.cpp
kis_stop_gradient_editor_test.cpp
kis_file_layer_test.cpp
kis_multinode_property_test.cpp
KisFrameSerializerTest.cpp
KisFrameCacheStoreTest.cpp
kis_animation_exporter_test.cpp
kis_prescaled_projection_test.cpp
kis_animation_importer_test.cpp
KisSpinBoxSplineUnitConverterTest.cpp
KisDocumentReplaceTest.cpp
+ KisRssReaderTest.cpp
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-"
)
+if (ENABLE_UPDATERS)
+if (UNIX)
+ ecm_add_test( KisAppimageUpdaterTest.cpp
+ TEST_NAME KisAppimageUpdaterTest
+ LINK_LIBRARIES kritaui Qt5::Test
+ NAME_PREFIX "libs-ui-")
+endif()
+
+ecm_add_test( KisManualUpdaterTest.cpp ../../../sdk/tests/testutil.cpp MockMultiFeedRssModel.cpp
+ TEST_NAME KisManualUpdaterTest
+ LINK_LIBRARIES kritaui Qt5::Test
+ NAME_PREFIX "libs-ui-")
+endif()
+
+
+ecm_add_test( KisMultiFeedRssModelTest.cpp ../../../sdk/tests/testutil.cpp MockNetworkAccessManager.cpp
+ TEST_NAME KisMultiFeedRssModelTest
+ LINK_LIBRARIES kritaui Qt5::Test
+ NAME_PREFIX "libs-ui-")
+
+
ecm_add_test( kis_selection_decoration_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp
TEST_NAME KisSelectionDecorationTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test( kis_node_dummies_graph_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisNodeDummiesGraphTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test( kis_node_shapes_graph_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisNodeShapesGraphTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test( kis_model_index_converter_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisModelIndexConverterTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test( kis_categorized_list_model_test.cpp modeltest.cpp
TEST_NAME KisCategorizedListModelTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test( kis_node_juggler_compressed_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisNodeJugglerCompressedTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test(
kis_input_manager_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisInputManagerTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test(
kis_node_model_test.cpp modeltest.cpp
TEST_NAME kis_node_model_test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
##### Tests that currently fail and should be fixed #####
include(KritaAddBrokenUnitTest)
krita_add_broken_unit_test( kis_shape_controller_test.cpp kis_dummies_facade_base_test.cpp
TEST_NAME kis_shape_controller_test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test( kis_exiv2_test.cpp
TEST_NAME KisExiv2Test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test( kis_clipboard_test.cpp
TEST_NAME KisClipboardTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test( freehand_stroke_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
TEST_NAME FreehandStrokeTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test( FreehandStrokeBenchmark.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
TEST_NAME FreehandStrokeBenchmark
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test( KisPaintOnTransparencyMaskTest.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
TEST_NAME KisPaintOnTransparencyMaskTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test( fill_processing_visitor_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
TEST_NAME
FillProcessingVisitorTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test( filter_stroke_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp
TEST_NAME FilterStrokeTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test( kis_selection_manager_test.cpp
TEST_NAME KisSelectionManagerTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
#set_tests_properties(libs-ui-KisSelectionManagerTest PROPERTIES TIMEOUT 300)
krita_add_broken_unit_test( kis_node_manager_test.cpp
TEST_NAME KisNodeManagerTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test( kis_dummies_facade_test.cpp kis_dummies_facade_base_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisDummiesFacadeTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test( kis_zoom_and_pan_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisZoomAndPanTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
#set_tests_properties(libs-ui-KisZoomAndPanTest PROPERTIES TIMEOUT 300)
krita_add_broken_unit_test( kis_action_manager_test.cpp
TEST_NAME KisActionManagerTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test( kis_categories_mapper_test.cpp testing_categories_mapper.cpp
TEST_NAME KisCategoriesMapperTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test( kis_animation_frame_cache_test.cpp
TEST_NAME kis_animation_frame_cache_test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test( kis_derived_resources_test.cpp
TEST_NAME kis_derived_resources_test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test( kis_shape_layer_test.cpp
TEST_NAME KisShapeLayerTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
diff --git a/libs/ui/tests/KisAppimageUpdaterTest.cpp b/libs/ui/tests/KisAppimageUpdaterTest.cpp
new file mode 100644
index 0000000000..1bdb3197bb
--- /dev/null
+++ b/libs/ui/tests/KisAppimageUpdaterTest.cpp
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * #
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisAppimageUpdaterTest.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+KisAppimageUpdaterTest::KisAppimageUpdaterTest(QObject *parent) : QObject(parent)
+{
+
+}
+
+void KisAppimageUpdaterTest::testCheckForUpdate()
+{
+ QFETCH(QString, controlValue);
+ QFETCH(UpdaterStatus::StatusID, resultStatus);
+
+ QString updaterDummyPath = QString("%1%2/AppImageUpdateDummy")
+ .arg(QString(FILES_DATA_DIR))
+ .arg(QDir::separator());
+
+
+ qputenv("APPIMAGEUPDATE_DUMMY_STATE", controlValue.toLocal8Bit());
+
+ QScopedPointer updater(new KisAppimageUpdater(updaterDummyPath));
+
+ QSignalSpy spy(updater.data(), SIGNAL(sigUpdateCheckStateChange(KisUpdaterStatus)));
+
+ QVERIFY(spy.isValid());
+
+ updater->checkForUpdate();
+
+ QTest::qWait(1000);
+
+ QList arguments = spy.takeFirst();
+ KisUpdaterStatus firstStatus = arguments.at(0).value();
+
+ QCOMPARE(firstStatus.status(), UpdaterStatus::StatusID::IN_PROGRESS);
+
+ arguments = spy.takeFirst();
+ KisUpdaterStatus secondStatus = arguments.at(0).value();
+
+ QCOMPARE(secondStatus.status(), resultStatus);
+}
+
+void KisAppimageUpdaterTest::testCheckForUpdate_data()
+{
+ QTest::addColumn("controlValue");
+ QTest::addColumn("resultStatus");
+
+ QTest::addRow("uptodate")
+ << QString("check_uptodate")
+ << UpdaterStatus::StatusID::UPTODATE;
+
+ QTest::addRow("update available")
+ << QString("check_update_avail")
+ << UpdaterStatus::StatusID::UPDATE_AVAILABLE;
+
+ QTest::addRow("error")
+ << QString("check_error")
+ << UpdaterStatus::StatusID::CHECK_ERROR;
+
+ QTest::addRow("empty update info")
+ << QString("check_updinfo_empty")
+ << UpdaterStatus::StatusID::CHECK_ERROR;
+}
+
+void KisAppimageUpdaterTest::testDoUpdate()
+{
+ QFETCH(QString, controlValue);
+ QFETCH(UpdaterStatus::StatusID, resultStatus);
+
+ QString updaterDummyPath = QString("%1%2/AppImageUpdateDummy")
+ .arg(QString(FILES_DATA_DIR))
+ .arg(QDir::separator());
+
+
+ qputenv("APPIMAGEUPDATE_DUMMY_STATE", controlValue.toLocal8Bit());
+
+ QScopedPointer updater(new KisAppimageUpdater(updaterDummyPath));
+
+ QSignalSpy spy(updater.data(), SIGNAL(sigUpdateCheckStateChange(KisUpdaterStatus)));
+
+ QVERIFY(spy.isValid());
+
+ updater->doUpdate();
+
+ QTest::qWait(1000);
+
+ QList arguments = spy.takeFirst();
+ KisUpdaterStatus secondStatus = arguments.at(0).value();
+
+ QCOMPARE(secondStatus.status(), resultStatus);
+
+ // for error also check for output
+ if (secondStatus.status() == UpdaterStatus::StatusID::UPDATE_ERROR) {
+ QCOMPARE(secondStatus.updaterOutput(), QString("DUMMY: an error occured\n"));
+ }
+}
+
+void KisAppimageUpdaterTest::testDoUpdate_data()
+{
+ QTest::addColumn("controlValue");
+ QTest::addColumn("resultStatus");
+
+ QTest::addRow("update ok")
+ << QString("update_ok")
+ << UpdaterStatus::StatusID::RESTART_REQUIRED;
+
+ QTest::addRow("error")
+ << QString("update_error")
+ << UpdaterStatus::StatusID::UPDATE_ERROR;
+
+ QTest::addRow("runtime error")
+ << QString("runtime_error")
+ << UpdaterStatus::StatusID::UPDATE_ERROR;
+}
+
+QTEST_MAIN(KisAppimageUpdaterTest);
diff --git a/libs/ui/tests/KisAppimageUpdaterTest.h b/libs/ui/tests/KisAppimageUpdaterTest.h
new file mode 100644
index 0000000000..e850d2f099
--- /dev/null
+++ b/libs/ui/tests/KisAppimageUpdaterTest.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KISAPPIMAGEUPDATERTEST_H
+#define KISAPPIMAGEUPDATERTEST_H
+
+#include
+
+class KisAppimageUpdaterTest : public QObject
+{
+ Q_OBJECT
+public:
+ explicit KisAppimageUpdaterTest(QObject *parent = nullptr);
+
+private Q_SLOTS:
+ void testCheckForUpdate();
+ void testCheckForUpdate_data();
+ void testDoUpdate();
+ void testDoUpdate_data();
+};
+
+#endif // KISAPPIMAGEUPDATERTEST_H
diff --git a/libs/ui/tests/KisManualUpdaterTest.cpp b/libs/ui/tests/KisManualUpdaterTest.cpp
new file mode 100644
index 0000000000..aca329a258
--- /dev/null
+++ b/libs/ui/tests/KisManualUpdaterTest.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#include "KisManualUpdaterTest.h"
+
+#include
+#include
+#include
+#include
+#include
+
+KisManualUpdaterTest::KisManualUpdaterTest(QObject *parent) : QObject(parent)
+{
+
+}
+
+void KisManualUpdaterTest::testAvailableVersionIsHigher()
+{
+ QFETCH(QString, currentVersion);
+ QFETCH(QString, availableVersion);
+ QFETCH(bool, expectedReturnValue);
+
+ QScopedPointer updater(new KisManualUpdater());
+ bool actualReturnValue = updater->availableVersionIsHigher(currentVersion, availableVersion);
+ QCOMPARE(actualReturnValue, expectedReturnValue);
+}
+
+void KisManualUpdaterTest::testAvailableVersionIsHigher_data()
+{
+ QTest::addColumn("currentVersion");
+ QTest::addColumn("availableVersion");
+ QTest::addColumn("expectedReturnValue");
+
+ QTest::addRow("equal") << "4.2.5" << "4.2.5" << false;
+
+ QTest::addRow("higher major") << "3.0.0" << "4.2.5" << true;
+ QTest::addRow("higher minor") << "4.2.4" << "4.3.4" << true;
+ QTest::addRow("higher rev") << "4.2.4" << "4.2.5" << true;
+
+ QTest::addRow("lower major") << "4.2.5" << "3.2.5" << false;
+ QTest::addRow("lower minor") << "4.2.5" << "4.1.5" << false;
+ QTest::addRow("lower rev") << "4.2.5" << "4.2.4" << false;
+
+ QTest::addRow("current is git, available lower stable") << "4.3.0-d8ea4b" << "4.2.5" << false;
+ QTest::addRow("current is beta, available lower stable") << "4.2.6-beta1" << "4.2.5" << false;
+
+ QTest::addRow("current is git, available higher stable") << "4.3.0-d8ea4b" << "4.4.2" << true;
+ QTest::addRow("current is beta, available lower stable") << "4.2.6-beta1" << "4.3.1" << true;
+
+ QTest::addRow("current is git, available equal version release") << "4.3.0-d8ea4b" << "4.3.0" << true;
+ QTest::addRow("current is beta, available equal version release") << "4.2.6-beta1" << "4.2.6" << true;
+
+ QTest::addRow("available is git, current equal version release") << "4.3.0" << "4.3.0-d8ea4b" << false;
+ QTest::addRow("available is beta, current equal version release") << "4.2.6" << "4.2.6-beta1" << false;
+
+// this is not solved in the function, as it is unlikely to have such versions in the RSS feed
+// QTest::addRow("current is git, available equal version beta") << "4.3.0-d8ea4b" << "4.3.0-beta1" << true;
+ // QTest::addRow("available is beta, current equal version git") << "4.2.6-beta1" << "4.2.6-d8ea4b" << false;
+
+ QTest::addRow("current is empty") << QString() << "4.2.5" << false;
+ QTest::addRow("available is empty") << "4.2.6" << QString() << false;
+}
+
+void KisManualUpdaterTest::testCheckForUpdate()
+{
+ QFETCH(RssItemList, feed);
+ QFETCH(QString, currentVersion);
+ QFETCH(QString, availableVersion);
+ QFETCH(QString, downloadLink);
+ QFETCH(UpdaterStatus::StatusID, resultStatus);
+
+ MockMultiFeedRssModel* mockRssModel(new MockMultiFeedRssModel());
+ mockRssModel->loadFeedData(feed);
+
+ QScopedPointer updater(new KisManualUpdater(mockRssModel, currentVersion));
+
+ QSignalSpy spy(updater.data(), SIGNAL(sigUpdateCheckStateChange(KisUpdaterStatus)));
+
+ QVERIFY(spy.isValid());
+
+ updater->checkForUpdate();
+
+ QTest::qWait(1000);
+
+ QList arguments = spy.takeFirst();
+ KisUpdaterStatus updaterStatus = arguments.at(0).value();
+
+ QCOMPARE(updaterStatus.status(), resultStatus);
+ QCOMPARE(updaterStatus.availableVersion(), availableVersion);
+ QCOMPARE(updaterStatus.downloadLink(), downloadLink);
+}
+
+void KisManualUpdaterTest::testCheckForUpdate_data()
+{
+ QTest::addColumn("feed");
+ QTest::addColumn("currentVersion");
+ QTest::addColumn("availableVersion");
+ QTest::addColumn("downloadLink");
+ QTest::addColumn("resultStatus");
+
+ RssItem item1 = RssItem();
+ item1.title = "Krita 4.3.0 Released";
+ item1.link = "https://krita.org/en/item/krita-4-3-0/";
+ item1.category = "Official Release";
+ item1.pubDate = QDateTime::fromString(QString("Wed, 10 Sep 2019 09:42:15 +0000"), Qt::RFC2822Date);
+
+ RssItem item2 = RssItem();
+ item2.title = "Krita 4.2.4 Released";
+ item2.link = "https://krita.org/en/item/krita-4-2-4/";
+ item2.category = "Official Release";
+ item2.pubDate = QDateTime::fromString(QString("Wed, 10 Sep 2018 09:42:15 +0000"), Qt::RFC2822Date);
+
+ RssItem item3 = RssItem();
+ item3.title = "Interview with The Artist";
+ item3.link = "https://krita.org/en/item/interview-with-the-artist/";
+ item3.category = "Artist Interview";
+ item3.pubDate = QDateTime::fromString(QString("Wed, 15 Sep 2019 09:42:15 +0000"), Qt::RFC2822Date);
+
+
+ RssItemList feed = RssItemList() << item1 << item2 << item3;
+
+ QTest::addRow("uptodate")
+ << feed
+ << QString("4.3.0")
+ << QString("4.3.0")
+ << QString("https://krita.org/en/item/krita-4-3-0/")
+ << UpdaterStatus::StatusID::UPTODATE;
+
+ QTest::addRow("update available")
+ << feed
+ << QString("4.2.4")
+ << QString("4.3.0")
+ << QString("https://krita.org/en/item/krita-4-3-0/")
+ << UpdaterStatus::StatusID::UPDATE_AVAILABLE;
+}
+
+QTEST_MAIN(KisManualUpdaterTest);
diff --git a/libs/ui/tests/KisManualUpdaterTest.h b/libs/ui/tests/KisManualUpdaterTest.h
new file mode 100644
index 0000000000..e3febaeea4
--- /dev/null
+++ b/libs/ui/tests/KisManualUpdaterTest.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef KISMANUALUPDATERTEST_H
+#define KISMANUALUPDATERTEST_H
+
+#include
+
+class KisManualUpdaterTest : public QObject
+{
+ Q_OBJECT
+public:
+ explicit KisManualUpdaterTest(QObject *parent = nullptr);
+
+private Q_SLOTS:
+ void testAvailableVersionIsHigher();
+ void testAvailableVersionIsHigher_data();
+
+ void testCheckForUpdate();
+ void testCheckForUpdate_data();
+};
+
+#endif // KISMANUALUPDATERTEST_H
diff --git a/libs/ui/tests/KisMultiFeedRssModelTest.cpp b/libs/ui/tests/KisMultiFeedRssModelTest.cpp
new file mode 100644
index 0000000000..5aa7b4cec0
--- /dev/null
+++ b/libs/ui/tests/KisMultiFeedRssModelTest.cpp
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * #
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisMultiFeedRssModelTest.h"
+
+#include
+#include
+
+#include
+#include
+#include
+
+
+KisMultiFeedRssModelTest::KisMultiFeedRssModelTest(QObject *parent) : QObject(parent)
+{
+
+}
+
+void KisMultiFeedRssModelTest::testAddFeed()
+{
+ QFETCH(QList, replyDataList);
+ QFETCH(int, resultArticleCount);
+
+
+ MockNetworkAccessManager* nam(new MockNetworkAccessManager());
+
+ QStringList feedList;
+ for(FakeReplyData& replyData: replyDataList) {
+ feedList << replyData.url.toString();
+ nam->setReplyData(replyData);
+ }
+
+ MultiFeedRssModel* rssModel(new MultiFeedRssModel(nam));
+ QSignalSpy spy(rssModel, SIGNAL(feedDataChanged()));
+ QVERIFY(spy.isValid());
+
+ for(QString& feedUrl: feedList) {
+ rssModel->addFeed(feedUrl);
+ }
+
+ // wait for signal
+ QVERIFY(spy.wait());
+
+ QCOMPARE(rssModel->articleCount(), resultArticleCount);
+}
+
+void KisMultiFeedRssModelTest::testAddFeed_data()
+{
+ QTest::addColumn>("replyDataList");
+ QTest::addColumn("resultArticleCount");
+
+ // setup feed1
+ QFile rssFile1(TestUtil::fetchDataFileLazy("rss_feeds/feed.xml"));
+ bool fileOpened = rssFile1.open(QIODevice::ReadOnly | QIODevice::Text);
+ QVERIFY(fileOpened);
+
+ QString urlFeed1("https://krita.org/en/feed/");
+
+ // create reply data
+ FakeReplyData replyFeed1;
+ replyFeed1.url = QUrl(urlFeed1);
+ replyFeed1.statusCode = 200;
+ replyFeed1.requestMethod = QNetworkAccessManager::GetOperation;
+ replyFeed1.contentType = "application/atom+xml";
+ replyFeed1.responseBody = rssFile1.readAll();
+
+
+ // setup feed2
+ QFile rssFile2(TestUtil::fetchDataFileLazy("rss_feeds/feed.xml"));
+ fileOpened = rssFile2.open(QIODevice::ReadOnly | QIODevice::Text);
+ QVERIFY(fileOpened);
+
+ QString urlFeed2("https://krita.org/en/another_feed/");
+
+ FakeReplyData replyFeed2;
+ replyFeed2.url = QUrl(urlFeed2);
+ replyFeed2.statusCode = 200;
+ replyFeed2.requestMethod = QNetworkAccessManager::GetOperation;
+ replyFeed2.contentType = "application/atom+xml";
+ replyFeed2.responseBody = rssFile2.readAll();
+
+
+ // test with 1 feed
+ QList listTest1 = { replyFeed1 };
+ QTest::addRow("1 feed")
+ << listTest1
+ << 10;
+
+ // test with 2 feeds
+ QList listTest2 = { replyFeed1, replyFeed2 };
+ QTest::addRow("2 feeds")
+ << listTest2
+ << 20;
+}
+
+void KisMultiFeedRssModelTest::testRemoveFeed()
+{
+ QFETCH(QList, replyDataList);
+ QFETCH(QString, removeFeedUrl);
+ QFETCH(int, resultArticleCount);
+
+
+ MockNetworkAccessManager* nam(new MockNetworkAccessManager());
+
+ QStringList feedList;
+ for(FakeReplyData& replyData: replyDataList) {
+ feedList << replyData.url.toString();
+ nam->setReplyData(replyData);
+ }
+
+ MultiFeedRssModel* rssModel(new MultiFeedRssModel(nam));
+ QSignalSpy spyAdd(rssModel, SIGNAL(feedDataChanged()));
+ QVERIFY(spyAdd.isValid());
+
+ for(QString& feedUrl: feedList) {
+ rssModel->addFeed(feedUrl);
+ }
+
+ // wait for signal
+ QVERIFY(spyAdd.wait());
+
+ // the remove test itself
+ rssModel->removeFeed(removeFeedUrl);
+
+ QCOMPARE(rssModel->articleCount(), resultArticleCount);
+}
+
+void KisMultiFeedRssModelTest::testRemoveFeed_data()
+{
+ QTest::addColumn>("replyDataList");
+ QTest::addColumn("removeFeedUrl");
+ QTest::addColumn("resultArticleCount");
+
+ // setup feed1
+ QFile rssFile1(TestUtil::fetchDataFileLazy("rss_feeds/feed.xml"));
+ bool fileOpened = rssFile1.open(QIODevice::ReadOnly | QIODevice::Text);
+ QVERIFY(fileOpened);
+
+ QString urlFeed1("https://krita.org/en/feed/");
+
+ // create reply data
+ FakeReplyData replyFeed1;
+ replyFeed1.url = QUrl(urlFeed1);
+ replyFeed1.statusCode = 200;
+ replyFeed1.requestMethod = QNetworkAccessManager::GetOperation;
+ replyFeed1.contentType = "application/atom+xml";
+ replyFeed1.responseBody = rssFile1.readAll();
+
+ // setup feed2
+ QFile rssFile2(TestUtil::fetchDataFileLazy("rss_feeds/feed.xml"));
+ fileOpened = rssFile2.open(QIODevice::ReadOnly | QIODevice::Text);
+ QVERIFY(fileOpened);
+
+ QString urlFeed2("https://krita.org/en/another_feed/");
+
+ FakeReplyData replyFeed2;
+ replyFeed2.url = QUrl(urlFeed2);
+ replyFeed2.statusCode = 200;
+ replyFeed2.requestMethod = QNetworkAccessManager::GetOperation;
+ replyFeed2.contentType = "application/atom+xml";
+ replyFeed2.responseBody = rssFile2.readAll();
+
+
+ // test with 1 feed
+ QList listTest1 = { replyFeed1 };
+ QTest::addRow("1 feed")
+ << listTest1
+ << urlFeed1
+ << 0;
+
+ // test with 2 feeds
+ QList listTest2 = { replyFeed1, replyFeed2 };
+ QTest::addRow("2 feeds")
+ << listTest2
+ << urlFeed1
+ << 10;
+}
+
+QTEST_MAIN(KisMultiFeedRssModelTest);
+
diff --git a/libs/ui/tests/KisMultiFeedRssModelTest.h b/libs/ui/tests/KisMultiFeedRssModelTest.h
new file mode 100644
index 0000000000..d0e024a5d3
--- /dev/null
+++ b/libs/ui/tests/KisMultiFeedRssModelTest.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * #
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef KISMULTIFEEDRSSMODELTEST_H
+#define KISMULTIFEEDRSSMODELTEST_H
+
+#include
+
+class KisMultiFeedRssModelTest : public QObject
+{
+ Q_OBJECT
+public:
+ explicit KisMultiFeedRssModelTest(QObject *parent = nullptr);
+
+private Q_SLOTS:
+ void testAddFeed();
+ void testAddFeed_data();
+
+ void testRemoveFeed();
+ void testRemoveFeed_data();
+};
+
+#endif // KISMULTIFEEDRSSMODELTEST_H
diff --git a/libs/ui/tests/KisRssReaderTest.cpp b/libs/ui/tests/KisRssReaderTest.cpp
new file mode 100644
index 0000000000..6afb845752
--- /dev/null
+++ b/libs/ui/tests/KisRssReaderTest.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisRssReaderTest.h"
+#include
+#include
+#include
+#include
+
+KisRssReaderTest::KisRssReaderTest(QObject *parent) : QObject(parent)
+{
+
+}
+
+void KisRssReaderTest::testParseData()
+{
+ KisRssReader reader;
+ QFile rssFile(TestUtil::fetchDataFileLazy("rss_feeds/feed.xml"));
+
+ RssItemList itemList = reader.parse(rssFile);
+
+ QCOMPARE(itemList.count(), 10);
+
+}
+
+QTEST_MAIN(KisRssReaderTest);
diff --git a/libs/ui/tests/KisRssReaderTest.h b/libs/ui/tests/KisRssReaderTest.h
new file mode 100644
index 0000000000..7c43412271
--- /dev/null
+++ b/libs/ui/tests/KisRssReaderTest.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KISRSSREADERTEST_H
+#define KISRSSREADERTEST_H
+
+#include
+#include
+
+class KisRssReaderTest : public QObject
+{
+ Q_OBJECT
+public:
+ explicit KisRssReaderTest(QObject *parent = nullptr);
+
+private Q_SLOTS:
+ void testParseData();
+};
+
+#endif // KISRSSREADERTEST_H
diff --git a/libs/ui/tests/MockMultiFeedRssModel.cpp b/libs/ui/tests/MockMultiFeedRssModel.cpp
new file mode 100644
index 0000000000..8b3761e3cd
--- /dev/null
+++ b/libs/ui/tests/MockMultiFeedRssModel.cpp
@@ -0,0 +1,28 @@
+#include "MockMultiFeedRssModel.h"
+
+#include
+#include
+#include
+#include
+
+MockMultiFeedRssModel::MockMultiFeedRssModel(QObject *parent)
+ : MultiFeedRssModel(parent)
+{
+
+}
+
+void MockMultiFeedRssModel::addFeed(const QString &feed)
+{
+ Q_UNUSED(feed);
+
+ emit feedDataChanged();
+}
+
+void MockMultiFeedRssModel::loadFeedData(const RssItemList& feed)
+{
+ m_aggregatedFeed = feed;
+ sortAggregatedFeed();
+ setArticleCount(m_aggregatedFeed.size());
+ beginResetModel();
+ endResetModel();
+}
diff --git a/libs/ui/tests/MockMultiFeedRssModel.h b/libs/ui/tests/MockMultiFeedRssModel.h
new file mode 100644
index 0000000000..c3416fde8c
--- /dev/null
+++ b/libs/ui/tests/MockMultiFeedRssModel.h
@@ -0,0 +1,25 @@
+#ifndef MOCKMULTIFEEDRSSMODEL_H
+#define MOCKMULTIFEEDRSSMODEL_H
+
+#include
+
+#include
+#include "kritaui_export.h"
+
+class KRITAUI_EXPORT MockMultiFeedRssModel : public MultiFeedRssModel
+{
+ Q_OBJECT
+
+public:
+ explicit MockMultiFeedRssModel(QObject *parent = 0);
+
+ void addFeed(const QString& feed);
+
+ /**
+ * @brief to be called in the setup phase of the unittest, before call to addFeed
+ * @param rssFile
+ */
+ void loadFeedData(const RssItemList& feed);
+};
+
+#endif // MOCKMULTIFEEDRSSMODEL_H
diff --git a/libs/ui/tests/MockNetworkAccessManager.cpp b/libs/ui/tests/MockNetworkAccessManager.cpp
new file mode 100644
index 0000000000..ead092efea
--- /dev/null
+++ b/libs/ui/tests/MockNetworkAccessManager.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * #
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "MockNetworkAccessManager.h"
+
+#include
+#include
+#include
+
+
+FakeNetworkReply::FakeNetworkReply(const QNetworkRequest& originalRequest, FakeReplyData &replyData)
+ : QNetworkReply()
+{
+ setRequest(originalRequest);
+
+ setUrl(replyData.url);
+ setOperation(replyData.requestMethod);
+ setAttribute(QNetworkRequest::HttpStatusCodeAttribute, replyData.statusCode);
+ setHeader(QNetworkRequest::ContentTypeHeader, replyData.contentType);
+ setHeader(QNetworkRequest::ContentLengthHeader, replyData.responseBody.size());
+
+ m_data.setData(replyData.responseBody);
+
+ m_data.open(QIODevice::ReadOnly);
+
+ open(QIODevice::ReadOnly);
+ setFinished(true);
+ QMetaObject::invokeMethod(this, "readyRead", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
+}
+
+void FakeNetworkReply::abort()
+{
+ return;
+}
+
+bool FakeNetworkReply::atEnd() const
+{
+ return m_data.atEnd();
+}
+
+qint64 FakeNetworkReply::bytesAvailable() const
+{
+ return m_data.bytesAvailable();
+}
+
+bool FakeNetworkReply::canReadLine() const
+{
+ return m_data.canReadLine();
+}
+
+void FakeNetworkReply::close()
+{
+ return m_data.close();
+}
+
+qint64 FakeNetworkReply::size() const
+{
+ return m_data.size();
+}
+
+qint64 FakeNetworkReply::pos() const
+{
+ return m_data.pos();
+}
+
+qint64 FakeNetworkReply::readData(char *data, qint64 maxLen)
+{
+ return m_data.read(data, maxLen);
+}
+
+qint64 FakeNetworkReply::writeData(const char *data, qint64 maxLen)
+{
+ return m_data.write(data, maxLen);
+}
+
+MockNetworkAccessManager::MockNetworkAccessManager(QObject *parent)
+ : KisNetworkAccessManager(parent)
+{
+}
+
+void MockNetworkAccessManager::setReplyData(FakeReplyData &replyData)
+{
+ m_replyData = replyData;
+}
+
+QNetworkReply *MockNetworkAccessManager::createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData)
+{
+ Q_UNUSED(op);
+ Q_UNUSED(outgoingData);
+
+ return new FakeNetworkReply(request, m_replyData);
+}
diff --git a/libs/ui/tests/MockNetworkAccessManager.h b/libs/ui/tests/MockNetworkAccessManager.h
new file mode 100644
index 0000000000..5a0b94e14c
--- /dev/null
+++ b/libs/ui/tests/MockNetworkAccessManager.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * #
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef MOCKNETWORKACCESSMANAGER_H
+#define MOCKNETWORKACCESSMANAGER_H
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include "kritaui_export.h"
+
+
+struct FakeReplyData {
+ QUrl url;
+ QNetworkAccessManager::Operation requestMethod;
+ int statusCode;
+ QString contentType;
+ QByteArray responseBody;
+};
+Q_DECLARE_METATYPE(FakeReplyData);
+
+class FakeNetworkReply : public QNetworkReply
+{
+ Q_OBJECT
+
+public:
+ explicit FakeNetworkReply(const QNetworkRequest &originalRequest, FakeReplyData& replyData);
+
+ void abort() override;
+ bool atEnd() const override;
+ qint64 bytesAvailable() const override;
+ bool canReadLine() const override;
+ void close() override;
+ qint64 size() const override;
+ qint64 pos() const override;
+
+protected:
+ qint64 readData(char *data, qint64 maxLen) override;
+ qint64 writeData(const char *data, qint64 maxLen) override;
+
+private:
+ QBuffer m_data;
+};
+
+class KRITAUI_EXPORT MockNetworkAccessManager : public KisNetworkAccessManager
+{
+ Q_OBJECT
+
+public:
+ explicit MockNetworkAccessManager(QObject *parent = 0);
+
+ void setReplyData(FakeReplyData& replyData);
+
+ QNetworkReply* createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData);
+
+private:
+ FakeReplyData m_replyData;
+};
+
+#endif // MOCKNETWORKACCESSMANAGER_H
diff --git a/libs/ui/tests/data/AppImageUpdateDummy b/libs/ui/tests/data/AppImageUpdateDummy
new file mode 100755
index 0000000000..215a0289d7
--- /dev/null
+++ b/libs/ui/tests/data/AppImageUpdateDummy
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import argparse
+import os.path
+
+EXIT_RUNTIME_ERROR = 255
+EXIT_OK = 0
+EXIT_ERROR = 1
+
+EXIT_CHECK_UPTODATE = 0
+EXIT_CHECK_ERROR = 2
+EXIT_CHECK_UPDATE_AVAIL = 1
+
+EXIT_INVALID_USAGE = 100
+
+# files that govern the output of this dummy
+states = {
+ "check_uptodate": EXIT_CHECK_UPTODATE,
+ "check_update_avail": EXIT_CHECK_UPDATE_AVAIL,
+ "check_error": EXIT_CHECK_ERROR,
+ "check_updinfo_empty": EXIT_RUNTIME_ERROR,
+ "update_ok": EXIT_OK,
+ "update_error": EXIT_ERROR,
+ "runtime_error": EXIT_RUNTIME_ERROR
+}
+
+parser = argparse.ArgumentParser()
+parser.add_argument('--check-for-update', dest='check_for_update', action='store_true',
+ help='only check if updates are available')
+parser.add_argument('appimage_path', metavar='APPIMAGE_PATH', type=str, help='path to the appimage')
+
+args = parser.parse_args()
+
+
+if "APPIMAGEUPDATE_DUMMY_STATE" not in os.environ.keys():
+ sys.exit(EXIT_INVALID_USAGE)
+
+state_string = str(os.environ["APPIMAGEUPDATE_DUMMY_STATE"])
+
+# if we want to test the update, return update available for --check-for-update
+if (args.check_for_update):
+ if state_string in [ 'update_ok', 'update_error', 'runtime_error' ]:
+ sys.exit(EXIT_CHECK_UPDATE_AVAIL)
+
+# else lookup the state in the dict
+if (state_string in states.keys()):
+ state = states[state_string]
+
+ if state_string in ['update_error', 'runtime_error']:
+ print("DUMMY: an error occured")
+
+ sys.exit(states[state_string])
+
+else:
+ sys.exit(EXIT_INVALID_USAGE)
diff --git a/libs/ui/tests/data/rss_feeds/feed.xml b/libs/ui/tests/data/rss_feeds/feed.xml
new file mode 100644
index 0000000000..10d1e8f269
--- /dev/null
+++ b/libs/ui/tests/data/rss_feeds/feed.xml
@@ -0,0 +1,661 @@
+
+
+
+ Krita
+
+ https://krita.org
+ Digital Painting. Creative Freedom.
+ Fri, 04 Oct 2019 13:43:35 +0000
+ en-US
+
+ hourly
+
+ 1
+ https://wordpress.org/?v=5.2.3
+
+ Krita 4.2.7 Released
+ https://krita.org/en/item/krita-4-2-7-released/
+ Thu, 03 Oct 2019 08:42:40 +0000
+
+
+
+ https://krita.org/?p=9790
+
+ Today, we’re releasing the sixth bug fix release of Krita 4.2. As discussed in our development update, we intend to release a few more monthly 4.2 bug fix releases before releasing Krita 4.3. There are a lot of bug fixes!
Note: Linux distributions building Krita against Qt 5.13 have to be aware that a regression in Qt crashes Krita on exit. There’s a workaround for that, but that was not ready for this release.
Krita is a free and open source project. Please consider supporting the project with donations or by buying training videos or the artbook! With your support, we can keep the core team working on Krita full-time.
+
+]]>
+
+
+ September Development Update
+ https://krita.org/en/item/september-development-update/
+ Mon, 30 Sep 2019 13:50:22 +0000
+
+
+
+ https://krita.org/?p=9779
+
+ We’re about to do another bug-fix release of Krita: that’ll be 4.2.7. We’re seeing that the number of new bug reports is dropping a bit, week over week: even though most bug reports can be closed instantly as duplicates or not bugs at all, that’s probably a sign that our stable releases are getting more and more stable. (See here to learn how to make good bug reports.) Though the total number of open bugs remains high, we worked really hard all September to make Krita better:
+
+
We also managed make 538 changes to the code in September with 23 developers — and that excludes translations, since those aren’t in our code repository.
+
We also went back to the Coverity Static Code analyzer and started analyzing Krita again. That was good for at least a hundred potential bug fixes, and it’s something that’s ongoing. We hadn’t done that for quite some time! There is still plenty to do, but the average defect density for projects the size of Krita is 0.7, so we’re not that bad.
+
+
Of course, focus on fixing bugs means that there’s less time for cool new features, or extending existing features. We did merge Sharaf Zaman’s Android port, though, which means that pretty soon we should have signed nightly Android APK’s!
+
Today during our weekly contributors meeting (notes are here), we decided we wanted to continue and do another couple of montly bug fix releases in the 4.2 series. In what’s currently our master branch, there are quite a few nice new features we’d like to share, though, and we’ll start making alpha builds of master in October, and invite everyone to help test what will become Krita 4.3.0.
+
We had originally wanted to release 4.3.0 in October, and have a fundraiser to celebrate the release. Part of the reason we’re postponing 4.3.0 is that we’d like to continue for a while with the stable 4.2 releases. Another reason is that we want 4.3.0 to have a completely reworked system for loading, handling and saving things like brush presets. We’ve been working on that for a long time now, and things are coming together, but there’s still a lot that needs to be done.
Oh I’m terrible at talking about myself. I guess the easiest thing would be that I’m 26 years old and a mother. I identify as genderfluid and pansexual, I freaking love Dungeons and Dragons, and I also am a super avid knitter/fiber artist. I mainly just love doing things with my hands and gravitate to any hobby or craft that lets me do that.
+
Do you paint professionally, as a hobby artist, or both?
+
A complete hobbyist. I actually only started drawing maybe a year ago, so I’m still learning a lot and growing. I’d love to do this professionally some day, but right now I just really enjoy drawing for my own self.
+
What genre(s) do you work in?
+
I’m so new to this I don’t even know if I know how to answer this. I mean I guess I mostly work in portraits? I’m trying to get better at drawing buildings and scenes. I’d love to get into comics at some point but my skill level just isn’t quite there yet.
+
+
Whose work inspires you most — who are your role models as an artist?
+
I love seeing all sorts of art on twitter, I follow a lot of digital and traditional artists and a lot of them draw Dungeons and Dragons characters and I just love seeing people’s imaginations come to life in that way. I also love Jen Bartel and This Is Angle (This Is Angle writes a comic that I absolutely love the art style of, The Devil Is A Handsome Man) But both of them are just amazing artists and if I could draw half as well as them I would feel accomplished.
+
How and when did you get to try digital painting for the first time?
+
About a year ago I decided I really wanted to get into drawing. It was always something I wanted to do but just never took that leap. But the time I got there, I realized doing traditional art would be hard simply because we don’t have a lot of space in our home. The house is filled with yarn, fabric, leather, pets, and kid toys. There just wasn’t space for another hobby so I realized digital art would be the only way for me to do this thing. So my partner found me a refurbished Wacom pen tablet and I just fell in love with it.
+
+
What makes you choose digital over traditional painting?
+
The biggest thing for me is the space it saves. But also, boy, do I absolutely love the undo function. I make a LOT of mistakes and so having the ability to just ctrl+z a bad stroke is phenomenal. I also love the freedom it gives, you can do so much with it that I feel like you just can’t accomplish with traditional. I guess another thing too would be how easy it is to share, I can just upload the finished work to wherever and don’t have to worry about trying to scan traditional art into the computer in order to show people.
+
+
How did you find out about Krita?
+
So when I first got my pen tablet, I was looking at a few programs. I knew I wasn’t going to mess with anything expensive like PS, but I still wanted a good program. Krita was actually the first thing that I found and it just worked perfectly for everything I needed and it was super user friendly. I had people recommend other programs but honestly I just fell in love with Krita and didn’t even bother looking at anything else.
+
+
What was your first impression?
+
Sheer joy. It was the first thing I found and it was just perfect, everything I wanted in a drawing program.
+
What do you love about Krita?
+
I love the quality of it, It’s just as good as the pricier programs and it’s free. It’s also got an amazing community of people around it.
+
What do you think needs improvement in Krita? Is there anything that really annoys you?
+
I think the biggest issue I run into is just wanting to undo more than the allotted undo’s. Sometimes I just zone out and do a bunch of strokes and then realize I’m on the wrong layer and I can’t undo all of my mistakes. But I suppose that’s more of a user error.
+
What sets Krita apart from the other tools that you use?
+
It’s layout is very user friendly and intuitive. I never have to look long for something, it’s always right where I expected it would be. So I didn’t have to waste a bunch of time learning some complicated program, it’s just so well designed and supported that even if I couldn’t figure something out, there’s a great community that is prepared to help.
+
If you had to pick one favourite of all your work done in Krita so far, what would it be, and why?
+
+
Oh that’s a hard one. I don’t know that I have a favorite one, per say, but I do love all of the demons I draw. I think I just like drawing horns on people and blacking out their eyes. My body of work though is small so it’s hard to pick a favorite when my style keeps changing and evolving.
+
What techniques and brushes did you use in it?
+
I like using the sketch tools and then going over the sketch with the basic paint brush. I also have some of David Revoy’s brushes and I love using them for fun backgrounds and effects. But honestly I mainly use the basic brushes, they can accomplish so much.
I think I’d like to just say that it’s never too late for you to do the art thing. You don’t have to be one of those people that drew constantly in high school to be someone who’s good at art later in life. Even if it isn’t drawing, maybe it’s writing or wood carving, maybe you really want to try sewing. I’m telling you to do it, it’s so rewarding and you get to watch yourself get better with everything that you do. Don’t let anyone stop you from doing the thing that you want to do, especially yourself. I’m a stay at home mom with no time and sometimes no energy to do it. But just take a breath and understand that it’s okay to go at your own pace, you don’t owe anyone anything and you’re doing this for you. Yeah, I think that’s it. Just do the thing and you’ll be so proud of yourself, I believe in you.
+]]>
+
+
+ Let’s Test Krita 4.2.7 Beta!
+ https://krita.org/en/item/lets-test-krita-4-2-7-beta/
+ Wed, 25 Sep 2019 10:52:21 +0000
+
+
+
+ https://krita.org/?p=9751
+
+ We’re almost ready for a new release of Krita, with lots of bug fixes! So, please help with testing. As with 4.2.6, there’s a link on the welcome screen to a survey. Filling out the survey helps us figure out regressions and other problems. You can safely use any beta build on any operating system next to your production version of Krita. Settings and resources will be shared, but you won’t overwrite you existing installation by using one of the portable beta downloads.
We also have a bunch of great patches by new Krita hackers: Karl Ove Lufthammer, Rebecca Breu, Matthias Wein, Jasper Hartog, Krysztof Kurek and Guo Yunhe. Yay!
+
If all is well, we will release 4.2.7 in a week.
+
Bugs Fixed in 4.2.7
+
+
Improve the layout and functionality of the color selector dialog and make it perform much better. (BUG:381529). Patches by Mathias Wein.
+
Do not crash when trying to merge an invisible group layer (BUG:411124)
+
Make it possible to save group layers to file layers even if they are empty (BUG:411101)
+
Make the initial location of the OCIO profile selector sensible
+
Fix possible crashes when a broken file ends up in the Recent Documents List (BUG:411416)
+
Use locale-based formatting of numbers in the measure tool and other places. Patch by Karl Ove Lufthammer.
+
Make HTML markup in the Search Field tooltips work. Patch by Karl Ove Lufthammer.
+
Fix a crash when moving multiple vector shapes (BUG:409872)
+
Fix the sort order of images imported as frames if they are not numbered with prefix 0’s (BUG:375885)
+
Make it possible again to run the Python Scripting Debugger on Linux (BUG:410807) Patch by Rebecca Breu.
+
Cache ICC profiles when loading layers: this speeds up loading images with thousands of layers (BUG:411532)
+
Fix file layer and comics manager page updating on Windows (BUG:410409, BUG:389544)
+
Use LittleCMS’ copy alpha channel flag to speed up color transformations
Fix a hang in the text shape if an UTF-8 Line Break character is used (BUG:410402)
+
Fix a random crash if there is not enough space in the swapfile location for AMD Ryzen 3500 CPU’s (BUG:411081)
+
Fix checking whether the swapfile location is actually writable on Windows (BUG:411129, BUG:411081)
+
Fix another random crash when painting (BUG:411280)
+
Fix artifacts when moving control points of a path shape (BUG:411334)
+
Fix a crash when cropping a particular image (BUG:411536)
+
Fix move action in the bezier selection tool (BUG:398294)
+
Fix artifacts in Gaussian Blur on transparent layers (BUG:411719)
+
Fix a crash when the Liquify Transform is started too quickly (BUG:411703)
+
Fix a bad memory leak in the jpeg converter (BUG:410864)
+
Fix a crash when loading a JPEG image with a broken color profile (BUG:410864)
+
Fix problems when zooming with a touchpad (BUG:410940)
+
Fix issues when using the calculation capabilities of the specific color selector’s spin boxes (BUG:409818). Patch by Jasper Hartog
+
Make sure all layers are shown in the animation timeline by default
+
Fix a crash when the colorize tool is active on closing Krita
+
Fix a crash when converting a colorspace with OCIO enabled (BUG:411045)
+
Fix the Strength parameter not being used in Rotation – Fuzzy Dab (BUG:376179)
+
Fix a crash when using the mouse wheel while an image is opening
+
Re-add error messages lost when refactoring the error messages for loading images
+
Do not crash if libjpeg encounters any kind of error (BUG:364350)
+
Fix presets with random offset of texture being marked dirty all the time (BUG:406427)
+
Fix curves changing randomly with sensors with Use Same Curve enabled (BUG:383909)
+
Add a simple progress bar when saving .kra files
+
Ensure that the temporary folder isn’t suggested as a save-location as this can result in lost work.
+
Make sure toolbars don’t get enabled after editing the toolbar buttons (BUG:402679)
+
Do not crash when loading a tiled TIFF file with planar color data. (BUG:407171)
+
Fix freezes when changing some brush properties or curves (BUG:410158)
+
Fix wrong borders in the Edge Detection and Height To Normal Map Filters (BUG:411922)
+
Fix outline of Group Layers in Move Tool and Transform Tool (BUG:392717)
+
Fix preview of shape layers in Transform Tool and Move Tool (BUG:392717)
+
Raise the maximum FPS limit to 300 fps from 100 fps
+
Do not allow clone layers from pass-through group layers (BUG:409949)
+
Fix the color of a selected shape being synchronized with the color selectors (BUG:381784)
+
Fix updating the current shape color when doing undo/redo (BUG:404975)
+
Fix the broken TestKisSwatchGroup test (BUG:410387) Patch by Krysztof Kurek.
+
Make the splash render pixel-perfect on fractionally scaled displays. Patch by Guo Yunhe.
+
Fix a crash in Feather Selection, Wavelets, Blur and Edge Detection (BUG:412057)
+
Include reference images in the screen color picker (BUG:411816) Patch by Matthias Wein.
+
Clean up the SVG files used for icons and license the SVG files properly. Patch by Raghavendra Kamath.
+
Fix updating the assistants when moving the handles. Patch by Matthias Wein.
+
Fix a bad memory corruption error color handling. Patch by Matthias Wein.
+
+
Download
+
Windows
+
For the beta, you should only only use the portable zip files. Just open the zip file in Explorer and drag the folder somewhere convenient, then double-click on the krita icon in the folder. This will not impact an installed version of Krita, though it will share your settings and custom resources with your regular installed version of Krita. For reporting crashes, also get the debug symbols folder.
Krita is a free and open source project. Please consider supporting the project with donations or by buying training videos or the artbook! With your support, we can keep the core team working on Krita full-time.
Sure. I’m Julius Grels, and “I like to call myself an artist whenever I’m wasted enough”. In all seriousness though, I think I’d describe myself more as a self-taught caricaturist or illustrator. I usually like to take some existing premise from real life or history, e.g. painting a picture of a (famous) person, depicting wildlife, et cetera. I’m also very fond of making comics, music and video games whenever I have the time (if only?).
+
Do you paint professionally, as a hobby artist, or both?
+
I’m most definitely a hobbyist, since I haven’t done any professional commissions (apart from some miniscule design work in the past), and what I do for living right now isn’t even remotely connected to art! That said, I’d most certainly would love to work as an illustrator, comic artist, or anything alike! My biggest wish would be to illustrate (and/or write) a children’s book someday.
+
+
What genre(s) do you work in?
+
I’m most comfortable when doing caricatures and comics; in the latter I can also infuse my story-telling abilities, the little there are. I prefer to illustrate living things; people, animals and nature itself. I’m not exactly keen on drawing in-animate objects, though I’m learning to force myself out of this comfort zone. I like to keep things simple and clean, or at least I try my best not to get lost in time-consuming detailing. Guess that’s one argument I can use as to why I prefer using simple black background on most of my works…
+
Whose work inspires you most — who are your role models as an artist?
+
I mostly draw (clever, eh?) influence and inspiration from animation, comics and video game art. I’m hesitant to drop any names because I don’t really have role models per se, and I tend to find pretty much any artist’s work interesting and inspiring. I guess as a Finn I could mention Tove Jansson and Mauri Kunnas. Jansson is of course famous for creating the Moomins, but she was also an accomplished artist and writer in her own right. Kunnas is a well-loved artist best known for his children’s books – pretty much every child in Finland has read at least one of his stories. In my opinion he’s also one of the greatest illustrators this country has to offer.
+
In addition to the aforementioned, I basically inhaled Franco-Belgian comics as a child; I loved reading Astérix, Lucky Luke, Iznogoud and the rest, so artists like Uderzo, Tabary and Tardi have also had a huge influence on me. Whenever possible, I browse through concept art of different video games. Video games are an interesting subject anyway because they not only combine art, design and technology but have to make all three work together even-handedly to create an enjoyable interactive experience.
+
+
How and when did you get to try digital painting for the first time?
+
If we don’t count MS Paint doodles, I think my first real try at digital painting was at elementary school somewhere around the late 90’s where we were introduced to Paint Shop Pro as a part of some “build your own website” -course. We were only able to use mouse for drawing, which was extremely clunky and made me think the whole idea of drawing and painting with a computer was just insane; I’d rather stick to my pencils and brushes, thank you very much. It wasn’t until later when I realised there are equipment specifically made for digital artwork, and once I got my hands on a Wacom tablet, I was sold.
+
What makes you choose digital over traditional painting?
+
Nothing? I mean, they are completely different working methods, and I still paint traditionally. Nevertheless, I’ve started to slowly leer towards digital painting, since it’s much easier to control your work and you can experiment more without the fear of ruining something irreversibly (especially when it comes to inking comics and other drawings). While it’s arguable whether digital painting is more cost-efficient than traditional methods in the long run, I think it’s at least less painful to start working with; you only need to set up your computer and programs ready, while with traditional painting you need to take out easel, canvas, gesso, colours, brushes, pencils? you get the idea. Furthermore, in my case where I don’t have a separate studio I have to find and clear a space in my apartment to set all that stuff up. Hassle, hassle!
+
+
How did you find out about Krita?
+
At one point I started to search for open source alternatives for the myriad number of programs I was using, and Krita was a recommendation somewhere to replace Photoshop, with high ratings from users.
+
What was your first impression?
+
I guess I’m still languishing in my first impression, because I haven’t been able to use Krita as much as I have wanted. In any case, my very first impression upon opening the program was a relieved “this looks familiar” sigh, and it was incredibly easy to start using Krita from there on.
+
What do you love about Krita?
+
Like I mentioned above, I find the interface very easy to use. I also love the fact the community is so alive, and you can find answers to just about any dilemma. All in all, Krita is a magnificent tool for making 2d artwork.
+
+
What do you think needs improvement in Krita? Is there anything that really annoys you?
+
Majority of my problems with Krita are due to the fact I’ve yet to learn most of its nuances, so I can’t really say about improvements that much. Krita seems to be quite a memory-hog, which can cause lot of lag and freezing especially when working with bigger canvases. That, and I’m not too happy with the text tool/editor either and prefer not to use it at all. It’s the one thing in Krita that’s needlessly complicated in my opinion.
+
What sets Krita apart from the other tools that you use?
+
Krita is the only digital painting software I use. Being open source is probably what makes it stand out the most. I use other open source programs as well, e.g. Blender, OpenToonz and Aseprite, but they obviously aren’t that much similar to Krita?
+
If you had to pick one favourite of all your work done in Krita so far, what would it be, and why?
+
+
Not much to choose from, but nevertheless, I’d say Megantereon, which was my first serious Krita artwork that I actually managed to finish. The main reason why I like it so much is simply because I had no initial planning; I took my Wacom, opened Krita and started doodling “something tiger-like”. After an hour or so, I began to realise there might be more to it, and continued working. The first version had plain fur, simplistic ear and lifeless green eye. I published it on deviantart.com, but wasn’t happy with the result, and later on decided to tweak the cat a bit. The fur got more detailed with stripes and spots, and I completely overhauled both the ear and the eye. The final result is what you see here, and I quickly replaced my previous attempt with this better version. I also like Megantereon as it neatly represents my interests (wildlife, history) and my preferred style (stylized, semi-realistic).
+
What techniques and brushes did you use in it?
+
Every brush I used is a Krita default. I used Basic and Fill Circle for outlining and some detail, Bristle Texture and Square for texturing and Inkpens for smaller detailing. There might be some Smudge tool I used too, but I honestly can’t remember.
+I made a rough outlining on one layer against a black background and constructed the beast from bones to muscles to fur et cetera from there on. In the end I had laid out 23 layers with such genius descriptions as “skull”, “Layer 17”, “BLOOD SALIVA”, “hideTheBeard” and “washing”. In retrospect, I highly recommend people to use layers more scarcely if possible – they slow down the program, and the whole working process ends up confusing. At least name your layers better than I did!
+I have two versions of the final work; with and without Noise Effect, which I used to achieve a more horror-esque vibe. I send the noiseless version here so the details aren’t obstructed too much.
+
Where can people see more of your work?
+
At the moment, my most recent work will be published at https://www.deviantart.com/jgrels . Don’t hold your breath, though; I publish work at a snail’s pace. You can also follow me on Twitter @JuliusGrels if you so desire, where I’m giving more information about my possible future projects, like a couple of webcomics I’ve planned to do.
+
Anything else you’d like to share?
+
Maybe just some general advice to any aspiring artist: never stop honing your skills, get out of your comfort zone, don’t fear experimenting, and most importantly; have confidence. Believe in yourself. Even if you don’t think you’re that great of an artist but love doing art, just keep working and publishing your work for everyone to see. You can doubt your talent, but never doubt your passion.
+
Finally, I want to send my thanks to the Krita Foundation and give my highest appreciation for the great work you’ve done. Thank you!
+]]>
+
+
+ Krita 4.2.6 released
+ https://krita.org/en/item/krita-4-2-6-released/
+ Tue, 10 Sep 2019 10:55:39 +0000
+
+
+
+
+ https://krita.org/?p=9735
+
+ A bit later than expected, because of a regression found during beta testing, we’re releasing Krita 4.2.6. Over 120 people have participated in the beta test survey, so this is something we’ll repeat for the next release.
+
This release also contains an important workaround for users with an AMD Ryzen 5 3500 CPU. This CPU has a bug in its hardware random generator that caused crashes.
+
New features:
+
+
Add new layer from visible to layer right-click context menu.
+
When running Krita for the first time on Windows, Angle is now the default renderer. Note that if you have an NVidia GPU and Krita’s window is transparent, you need to select Angle manually in Krita’s settings; if you have another GPU and you have problems with the canvas not updating, you might need to manually select OpenGL in the same window.
+
+
We want to especially thank Karl Ove Hufthammer for his extensive work on polishing the translatable string.
+
Bugs fixed
+
+
Allow selection overlay to be reset to default. (BUG:410470)
+
Set date for bundle creation to use ISO-Date. (BUG:410490)
+
Fix freeze with 32bit float tiff by using our own tiff reader for the thumbnails. (BUG:408731)
+
Ensure filter mask button is disabled appropriately depending on whether the filter supports it. (BUG:410374)
+
Enable the small color selector if opengles is available as well (BUG:410602)
The Linux appimage and the source .tar.gz and .tar.xz tarballs are signed. You can retrieve the public key over https here: 0x58b9596c722ea3bd.asc. The signatures are here (filenames ending in .sig).
+
Support Krita
+
Krita is a free and open source project. Please consider supporting the project with donations or by buying training videos or the artbook! With your support, we can keep the core team working on Krita full-time.
My name is Wojtek Trybus and I publish artworks as wojtryb. I’m in the last year of Computer Science studies in Poland. I’m also a pianist since 6, and happen to be a hobbyist and self-taught illustrator for something like 8 years now.
+
Do you paint professionally, as a hobby artist, or both?
+
Drawing was always something that, despite being quite hard to learn, was making me happy and fulfilled. I guess this counts as a hobby then. I don’t make any money from it right now and I still haven’t decided if I will be more of a programmer or a graphic designer in the nearest future.
+
+
What genre(s) do you work in?
+
My artistic journey started with some simple and minimalistic illustrations – by keeping them so simple, I never got discouraged, as most beginners do, jumping straight into complex artworks. As my knowledge and experience grew, it all became more and more complicated, though the need to keep this feeling of simplicity in my artworks still remains.
+
Whose work inspires you most — who are your role models as an artist?
+
Vlad Gerasimov, founder of Vladstudio, was my first great inspiration – my minimalistic illustrations originated from his way of seeing the world. I think it is still a bit visible in my artworks, even though our ways parted long time ago, as I switched to a much different approach. Currently, I guess I’m more into David Revoy – his open source ideology – and Nathan Fowkes http://www.nathanfowkesart.com/, whose work, especially for the “How to train your dragon” movie, is for me the absolute level cap that an artist can achieve. Julia Blattman is also worth mentioning, as her artworks full of imagination remind me of what I felt looking at those Vladstudio illustrations long time ago.
+
How and when did you get to try digital painting for the first time?
+
As a kid, as many other kids, I used to love computer games – when I grew up a bit, I noticed that it was not the games I liked that much, but the worlds they took me to. When in 2011 I found out how you can use GIMP and a digital tablet to create something similar, it all began.
+
+
What makes you choose digital over traditional painting?
+
Making a lot of mess was always something that bothered me. I still remember how I hated art classes in primary school with kids covering every single thing in the room with paint. In digital painting you can always elegantly save and quit with your hands clean. While I still really enjoy sketching with pencil and pen, I’m afraid I will have a tough job to convince myself to use real paint again (as I know traditional media can give you a lot).
+
How did you find out about Krita?
+
It was 2015, I guess, when I read about it on David Revoy’s blog for the first time – I suppose many artists switched from GIMP to Krita somewhere in that time because of his support to the program.
+
What was your first impression?
+
Insanely laggy It took me one year to accidentally switch something in settings, that suddenly made it work normally. Luckily, with each update, it’s less likely for someone to have such a problem again. After I made my first artwork in 2016, Krita happened to exceed GIMP in any way possible. I fell in love instantly.
+
+
What do you love about Krita?
+
I love how Krita makes it possible for everyone to learn how to draw, while not wanting anything in exchange. It kind of restores my faith in humanity every time I think about it. It’s even harder to believe that developers manage to constantly update and improve it, while their budget and human resources are hard to compare with those that other art programs have.
+
What do you think needs improvement in Krita? Is there anything that really annoys you?
+
I don’t think there are too many flaws right now in the program itself. All the bugs I noticed in 2016 have been fixed by now. It also seems really stable on Kubuntu – the crashes are happening less often than in the past. Developers did a gorgeous job again and I guess, and with this whole “squash the bugs” campaign they really meant it.
+
The thing that bothers me though, is how Krita is sometimes perceived across the internet – some people think it is still laggy or full of bugs – maybe it used to be that way, but in my opinion changed a lot in recent years. Some others may underappreciate it, as Photoshop is still the standard required in the industry.
+
I’m really grateful for each and every artist who already helps to promote the program on social media and art-related websites, but I believe it still deserves more – more recognition, professional users, more mind-blowing, “Made in Krita” artworks that would inspire beginners, finally maybe even more donations for further development.
+
I try to promote it the best I can and it even encourages me to practice and improve as an artist, but I still think we lack some PR – come on guys. Let’s show what we can do with Krita
+
What sets Krita apart from the other tools that you use?
+
Actually I don’t use any other drawing application as I simply don’t need one. I think that says a lot about it. Krita still allows me to constantly develop, and I’m sure that the only thing now that limits me is my current lack of skills. But I’m constantly working on that.
+
If you had to pick one favourite of all your work done in Krita so far, what would it be, and why?
+
+
One of my most recent illustrations is “festival preparation” – it indicates the direction I want to go with my art – to improve in art foundations, while keeping it fun and full of imagination. A lot of things I studied recently were used here, and it seems it all went particularly well.
+
What techniques and brushes did you use in it?
+
By coincidence, this artwork was actually a final test of brushes I use, that recently had some serious modifications to them. As I like things simple, they don’t mess that much with brush tips and textures as most of presets available on the internet do, but are quite tricky in terms of dynamics – they can randomize rotation and colors to differentiate the strokes. You can actually check them out, as I made them available to use freely some time ago: [Link to brushes]
+
Where can people see more of your work?
+
I use artstation as my more refined portfolio and facebook for loose updates. Some of my older artworks can still be found on deviantArt.
Soon I’ll be graduating from college and there are lots of changes and decisions ahead of me for sure – keep your fingers crossed in case I need them to still make progress at the current pace and create the art I love
+]]>
+
+
+ Help Beta Test Krita 4.2.6!
+ https://krita.org/en/item/help-beta-test-krita-4-2-6/
+ Sat, 31 Aug 2019 10:00:56 +0000
+
+
+
+ https://krita.org/?p=9667
+
+ This will be the first Krita release since the big sprint. We’re aiming to do monthly bugfix releases again from now on! But we also want to cut down on the regressions that come with rapid development so we’re making beta releases again. Please help the team out and check these beta releases for bugs and regressions. Right there in the welcome screen is a link to a survey where you can give your feedback:
+
The survey idea is new — we want to get your impressions of what we’re about to release. If it works, we’ll be refining the surveys.
+
What’s new in 4.2.6?
+
New features:
+
+
Add new layer from visible to layer right-click context menu.
+
When running Krita for the first time on Windows, Angle is now the default renderer. Note that if you have an NVidia GPU and Krita’s window is transparent, you need to select Angle manually in Krita’s settings; if you have another GPU and you have problems with the canvas not updating, you might need to manually select OpenGL in the same window.
+
+
We want to especially thank Karl Ove Hufthammer for his extensive work on polishing the translatable string.
+
Bugs fixed
+
+
Allow selection overlay to be reset to default. (BUG:410470)
+
Set date for bundle creation to use ISO-Date. (BUG:410490)
+
Fix freeze with 32bit float tiff by using our own tiff reader for the thumbnails. (BUG:408731)
+
Ensure filter mask button is disabled appropriately depending on whether the filter supports it. (BUG:410374)
+
Enable the small color selector if opengles is available as well (BUG:410602)
Fix a deadlock when using broken Wacom drivers on Linux (BUG:410797)
+
Fix absolute brush rotation on rotated canvas (BUG:292726)
+
Fix deadlock when removing reference image (BUG:411212)
+
Fix a deadlock in handling of vector objects (BUG:411365)
+
+
Download
+
Windows
+
For the beta, only portable zip files are available. Just open the zip file in Explorer and drag the folder somewhere convenient, then double-click on the krita icon in the folder. This will not impact an installed version of Krita, though it will share your settings and custom resources with your regular installed version of Krita. For reporting crashes, also get the debug symbols folder.
Krita is a free and open source project. Please consider supporting the project with donations or by buying training videos or the artbook! With your support, we can keep the core team working on Krita full-time.
+
+]]>
+
+
+ Mounamnamat Médias Teaches Animation using Krita
+ https://krita.org/en/item/mounamnamat-medias-teaches-animation-using-krita/
+ Thu, 29 Aug 2019 09:20:27 +0000
+
+
+
+ https://krita.org/?p=9689
+
+ Amine Sossi Alaoui and Sonia Didier write to tell us about their experience teaching children 2D animation using Krita, with some very cool results:
+
We’re a Moroccan animation studio, created 6 months ago and based in Rabat, Morocco. Before that we worked in animation studios in France during 10 years. Our goal now is to develop animation industry in Morocco and Africa. It’s a long way to go, and for the moment, we’re just beginning with 2d animation. Krita is a great tool for that, and we’re very happy to use it, and to share the knowledge we have about it.
+
So, this summer, we wanted children to learn about 2D animation, so we created an one-week animation course for children from 8 to 14 years old. It was 2 hours per day during 5 days, for a group of 8 to 12 children. The goal was to create an one-minute animated shortfilm in 2D, from the writing of the story, storyboard, background, animation, colorisation and compositing.
+
For that we chose to use Krita (and Shotcut for the final compositing and sound). It’s great software, very complete and fun to work with. And as it’s free, we’re sure that the children could use it at home if they like, to make their own projects.
+
Finally, four groups of children have worked on Krita this summer, and the result is on Youtube for anyone to watch on our youtube channel. Here are the first three films; the fourth film is not yet on youtube. Should be shortly, when it’s finalized.
+
We hope you’ll all like it !
+
+
+
+
Now we hope that we’ll have others projects with Krita, and we’ll be happy
+to share them with you.
Hi, my name is Chayse Goodall. I am 14 years old. I just draw for fun!
+
Do you paint professionally, as a hobby artist, or both?
+
I do animate and draw for a hobby. My artwork improves little by little, but I don’t think it’ll be good enough for a profession!
+
What genre(s) do you work in?
+
I don’t know what my style is, but people tell me that I have an anime style.
+
Whose work inspires you most — who are your role models as an artist?
+
I have many inspirers, two of which was my art teachers, and one of which was my math tutor. They both motivate me to keep going with my progress.
+
How and when did you get to try digital painting for the first time?
+
I tried digital art in 2015. I only had a phone at the time, so it was hard at first. It’s a million times better now, and I have a touch screen computer, so I’ve been trying different art programs!
+
What makes you choose digital over traditional painting?
+
There are more tools, and there is an undo button!
+
How did you find out about Krita?
+
I was looking up free art programs, and stumbled across this program.
+
What was your first impression?
+
My experience was great! It was easy to follow once you get the hang of it!
+
What do you love about Krita?
+
All the brushes and how easy it is to save.
+
What do you think needs improvement in Krita? Is there anything that really annoys you?
+
It is a bit confusing and it looks complex to newcomers.
+
What sets Krita apart from the other tools that you use?
+
It has amazing features and it doesn’t require me to “sign up” or pay for anything!
+
If you had to pick one favourite of all your work done in Krita so far, what would it be, and why?
+
I only made one picture. She doesn’t have a name, she was just a test character to see how good the program was. (It came out great!)
+
What techniques and brushes did you use in it?
+
I normally draw the sketch first in a dark red color. Then I draw the plain body in a light green. I sketch the clothes, hair, and accessories on in a neon color.
+]]>
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/ui/utils/KisAppimageUpdater.cpp b/libs/ui/utils/KisAppimageUpdater.cpp
new file mode 100644
index 0000000000..845c923f93
--- /dev/null
+++ b/libs/ui/utils/KisAppimageUpdater.cpp
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#include "KisAppimageUpdater.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+
+KisAppimageUpdater::KisAppimageUpdater()
+ : m_checkProcess(new QProcess(this))
+ , m_updateProcess(new QProcess(this))
+{
+ QString updaterPath;
+
+#if defined(KRITA_GIT_SHA1_STRING)
+ if (qEnvironmentVariableIsSet("KRITA_APPIMAGEUPDATER_USE_DUMMY")) {
+ updaterPath = QString("%1%2AppImageUpdateDummy")
+ .arg(QCoreApplication::applicationDirPath())
+ .arg(QDir::separator());
+ } else {
+ updaterPath = QString("%1%2AppImageUpdate")
+ .arg(QCoreApplication::applicationDirPath())
+ .arg(QDir::separator());
+ }
+#else
+ updaterPath = QString("%1%2AppImageUpdate")
+ .arg(QCoreApplication::applicationDirPath())
+ .arg(QDir::separator());
+#endif
+
+ initialize(updaterPath);
+}
+
+#if defined(KRITA_GIT_SHA1_STRING)
+KisAppimageUpdater::KisAppimageUpdater(QString dummyUpdaterPath)
+ : m_checkProcess(new QProcess(this))
+ , m_updateProcess(new QProcess(this))
+{
+ initialize(dummyUpdaterPath);
+}
+#endif
+
+void KisAppimageUpdater::checkForUpdate()
+{
+ if (m_updaterInProgress) {
+ return;
+ }
+
+ if (!m_updateCapability) {
+ return;
+ }
+
+ // reset output for subsequent checks, is this needed?
+ m_checkOutput = QString();
+ m_updateOutput = QString();
+ m_updaterStatus.setUpdaterOutput(QString());
+
+ QStringList args = QStringList() << "--check-for-update" << m_appimagePath;
+
+ m_checkProcess->start(m_updaterBinary, args);
+ m_updaterInProgress = true;
+}
+
+bool KisAppimageUpdater::hasUpdateCapability()
+{
+ return m_updateCapability;
+}
+
+void KisAppimageUpdater::doUpdate()
+{
+ QStringList args = QStringList() << m_appimagePath;
+ m_updateProcess->start(m_updaterBinary, args);
+}
+
+void KisAppimageUpdater::slotUpdateCheckFinished(int result, QProcess::ExitStatus exitStatus)
+{
+ KisUsageLogger::log(
+ QString("KisAppimageUpdater: update check finished. Result: %1 Exit status: %2\npath: %3\noutput: %4")
+ .arg(result)
+ .arg(exitStatus)
+ .arg(m_appimagePath)
+ .arg(m_updateOutput)
+ );
+
+ UpdaterStatus::StatusID updateStatus;
+
+ if (exitStatus == QProcess::CrashExit) {
+ updateStatus = UpdaterStatus::StatusID::CHECK_ERROR;
+
+ } else {
+ switch (result) {
+ case 0:
+ updateStatus = UpdaterStatus::StatusID::UPTODATE;
+ break;
+ case 1:
+ updateStatus = UpdaterStatus::StatusID::UPDATE_AVAILABLE;
+ break;
+ case 2:
+ updateStatus = UpdaterStatus::StatusID::CHECK_ERROR;
+ break;
+ default:
+ // some errors have exit code of 255 (modified by system, when AppImageUpdate returns -1)
+ // one source of 255 is when the AppImage does not contain update information
+ updateStatus = UpdaterStatus::StatusID::CHECK_ERROR;
+ break;
+ }
+ }
+
+ m_updaterInProgress = false;
+
+ m_updaterStatus.setStatus(updateStatus);
+ m_updaterStatus.setUpdaterOutput(m_updateOutput);
+
+ emit sigUpdateCheckStateChange(m_updaterStatus);
+}
+
+void KisAppimageUpdater::slotUpdateCheckStarted()
+{
+ m_updaterStatus.setStatus(UpdaterStatus::StatusID::IN_PROGRESS);
+ emit sigUpdateCheckStateChange(m_updaterStatus);
+}
+
+void KisAppimageUpdater::slotUpdateCheckErrorOccurred(QProcess::ProcessError error)
+{
+ KisUsageLogger::log(
+ QString("KisAppimageUpdater: error occurred during update check: %1\npath: %2\noutput: %3")
+ .arg(error)
+ .arg(m_appimagePath)
+ .arg(m_checkOutput)
+ );
+
+ m_updaterInProgress = false;
+
+ m_updaterStatus.setStatus(UpdaterStatus::StatusID::CHECK_ERROR);
+
+ emit sigUpdateCheckStateChange(m_updaterStatus);
+}
+
+void KisAppimageUpdater::slotUpdateFinished(int result, QProcess::ExitStatus exitStatus)
+{
+ KisUsageLogger::log(
+ QString("KisAppimageUpdater: update finished. Result: %1\nExit status: %2\npath: %3\noutput: %4")
+ .arg(result)
+ .arg(exitStatus)
+ .arg(m_appimagePath)
+ .arg(m_updateOutput)
+ );
+
+ UpdaterStatus::StatusID updateStatus;
+ QFileInfo finfoAppImagePath(m_appimagePath);
+ QString statusDetails;
+
+ if (exitStatus == QProcess::CrashExit) {
+ updateStatus = UpdaterStatus::StatusID::UPDATE_ERROR;
+
+ } else {
+ switch (result) {
+ case 0:
+ updateStatus = UpdaterStatus::StatusID::RESTART_REQUIRED;
+ statusDetails = i18n("New AppImage was downloaded to %1. To complete the update, close Krita and run the new AppImage.", finfoAppImagePath.path());
+ break;
+ default:
+ // some errors have exit code of 255 (modified by system, when AppImageUpdate returns -1)
+ updateStatus = UpdaterStatus::StatusID::UPDATE_ERROR;
+ break;
+ }
+ }
+
+ m_updaterInProgress = false;
+
+ m_updaterStatus.setStatus(updateStatus);
+ m_updaterStatus.setUpdaterOutput(m_updateOutput);
+ m_updaterStatus.setDetails(statusDetails);
+
+ emit sigUpdateCheckStateChange(m_updaterStatus);
+}
+
+void KisAppimageUpdater::slotUpdateErrorOccurred(QProcess::ProcessError error)
+{
+ KisUsageLogger::log(
+ QString("KisAppimageUpdater: error occurred during update: %1\npath: %2\noutput: %3")
+ .arg(error)
+ .arg(m_appimagePath)
+ .arg(m_updateOutput)
+ );
+
+ m_updaterInProgress = false;
+
+ m_updaterStatus.setStatus(UpdaterStatus::StatusID::UPDATE_ERROR);
+ m_updaterStatus.setUpdaterOutput(m_updateOutput);
+
+ emit sigUpdateCheckStateChange(m_updaterStatus);
+}
+
+void KisAppimageUpdater::slotAppendCheckOutput()
+{
+ m_checkOutput.append(m_checkProcess->readAllStandardOutput());
+}
+
+void KisAppimageUpdater::slotAppendUpdateOutput()
+{
+ m_updateOutput.append(m_updateProcess->readAllStandardOutput());
+}
+
+void KisAppimageUpdater::initialize(QString& updaterPath)
+{
+ m_appimagePath = qEnvironmentVariable("APPIMAGE");
+ m_updaterBinary = updaterPath;
+
+ m_updateCapability = findUpdaterBinary();
+
+ m_checkProcess->setProcessChannelMode(QProcess::MergedChannels);
+ m_updateProcess->setProcessChannelMode(QProcess::MergedChannels);
+
+ connect(m_checkProcess.data(), SIGNAL(started()), this, SLOT(slotUpdateCheckStarted()));
+ connect(m_checkProcess.data(), SIGNAL(errorOccurred(QProcess::ProcessError)),
+ this, SLOT(slotUpdateCheckErrorOccurred(QProcess::ProcessError)));
+ connect(m_checkProcess.data(), SIGNAL(finished(int, QProcess::ExitStatus)),
+ this, SLOT(slotUpdateCheckFinished(int, QProcess::ExitStatus)));
+ connect(m_checkProcess.data(), SIGNAL(readyReadStandardOutput()),
+ this, SLOT(slotAppendCheckOutput()));
+
+
+ connect(m_updateProcess.data(), SIGNAL(errorOccurred(QProcess::ProcessError)),
+ this, SLOT(slotUpdateErrorOccurred(QProcess::ProcessError)));
+ connect(m_updateProcess.data(), SIGNAL(finished(int, QProcess::ExitStatus)),
+ this, SLOT(slotUpdateFinished(int, QProcess::ExitStatus)));
+ connect(m_updateProcess.data(), SIGNAL(readyReadStandardOutput()),
+ this, SLOT(slotAppendUpdateOutput()));
+}
+
+bool KisAppimageUpdater::findUpdaterBinary()
+{
+ QFileInfo finfo(m_updaterBinary);
+ if (finfo.isExecutable()) {
+ return true;
+ } else {
+ KisUsageLogger::log(
+ QString("KisAppimageUpdater: AppImageUpdate (%1) was not found within the Krita appimage, or is not executable")
+ .arg(m_updaterBinary)
+ );
+ return false;
+ }
+}
diff --git a/libs/ui/utils/KisAppimageUpdater.h b/libs/ui/utils/KisAppimageUpdater.h
new file mode 100644
index 0000000000..1c8947ca34
--- /dev/null
+++ b/libs/ui/utils/KisAppimageUpdater.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef KISAPPIMAGEUPDATER_H
+#define KISAPPIMAGEUPDATER_H
+
+#include
+#include
+#include
+
+#include
+
+#include "kritaui_export.h"
+
+
+class QString;
+
+class KRITAUI_EXPORT KisAppimageUpdater : public KisUpdaterBase
+{
+ Q_OBJECT
+
+public:
+ KisAppimageUpdater();
+#ifdef KRITA_GIT_SHA1_STRING
+ explicit KisAppimageUpdater(QString dummyUpdaterPath);
+#endif
+
+ void checkForUpdate() override;
+ bool hasUpdateCapability() override;
+ void doUpdate() override;
+
+private Q_SLOTS:
+ void slotUpdateCheckFinished(int result, QProcess::ExitStatus exitStatus);
+ void slotUpdateCheckStarted();
+ void slotUpdateCheckErrorOccurred(QProcess::ProcessError error);
+
+ void slotUpdateFinished(int result, QProcess::ExitStatus exitStatus);
+ void slotUpdateErrorOccurred(QProcess::ProcessError error);
+
+ void slotAppendCheckOutput();
+ void slotAppendUpdateOutput();
+
+private:
+ void initialize(QString& updaterPath);
+
+ bool findUpdaterBinary();
+ QString m_updaterBinary;
+ QString m_appimagePath;
+ bool m_updateCapability;
+ bool m_updaterInProgress {false};
+ QString m_checkOutput;
+ QString m_updateOutput;
+
+ QScopedPointer m_checkProcess;
+ QScopedPointer m_updateProcess;
+};
+
+#endif // KISAPPIMAGEUPDATER_H
diff --git a/libs/ui/utils/KisManualUpdater.cpp b/libs/ui/utils/KisManualUpdater.cpp
new file mode 100644
index 0000000000..982bc6e7af
--- /dev/null
+++ b/libs/ui/utils/KisManualUpdater.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#include "KisManualUpdater.h"
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+
+
+KisManualUpdater::KisManualUpdater()
+ : m_currentVersion(KritaVersionWrapper::versionString())
+{
+ m_rssModel.reset(new MultiFeedRssModel());
+}
+
+KisManualUpdater::KisManualUpdater(MultiFeedRssModel* rssModel, QString ¤tVersion)
+ : m_currentVersion(currentVersion)
+{
+ m_rssModel.reset(rssModel);
+}
+
+void KisManualUpdater::checkForUpdate()
+{
+// m_rssModel->addFeed(QLatin1String("https://krita.org/en/feed/"));
+ m_rssModel->addFeed(QLatin1String("http://localhost/feed.xml"));
+ connect(m_rssModel.data(), SIGNAL(feedDataChanged()), this, SLOT(rssDataChanged()));
+}
+
+void KisManualUpdater::rssDataChanged()
+{
+ // grab the latest release post and URL for reference later
+ // if we need to update
+ QString availableVersion;
+ QString downloadLink;
+
+ for (int i = 0; i < m_rssModel->rowCount(); i++) {
+ const QModelIndex &idx = m_rssModel->index(i);
+
+ if (idx.isValid()) {
+ // only use official release announcements to get version number
+ if ( idx.data(KisRssReader::RssRoles::CategoryRole).toString() != "Official Release") {
+ continue;
+ }
+
+ QString linkTitle = idx.data(KisRssReader::RssRoles::TitleRole).toString();
+
+ // regex to capture version number
+ QRegularExpression versionRegex("\\d\\.\\d\\.?\\d?\\.?\\d");
+ QRegularExpressionMatch matched = versionRegex.match(linkTitle);
+
+ // only take the top match for release version since that is the newest
+ if (matched.hasMatch()) {
+ availableVersion = matched.captured(0);
+ downloadLink = idx.data(KisRssReader::RssRoles::LinkRole).toString();
+ break;
+ }
+ }
+ }
+
+ UpdaterStatus::StatusID status;
+
+ if (availableVersionIsHigher(m_currentVersion, availableVersion)) {
+ status = UpdaterStatus::StatusID::UPDATE_AVAILABLE;
+ } else {
+ status = UpdaterStatus::StatusID::UPTODATE;
+ }
+
+ m_updaterStatus.setStatus(status);
+ m_updaterStatus.setAvailableVersion(availableVersion);
+ m_updaterStatus.setDownloadLink(downloadLink);
+
+ emit sigUpdateCheckStateChange(m_updaterStatus);
+}
+
+bool KisManualUpdater::availableVersionIsHigher(QString currentVersion, QString availableVersion)
+{
+ if (currentVersion.isEmpty() || availableVersion.isEmpty()) {
+ return false;
+ }
+
+ int currentSuffixIndex {5};
+ int availableSuffixIndex {5};
+
+ QVersionNumber currentVersionNumber = QVersionNumber::fromString(currentVersion, ¤tSuffixIndex);
+ QVersionNumber availableVersionNumber = QVersionNumber::fromString(availableVersion, &availableSuffixIndex);
+
+ QString currentSuffix = currentVersion.mid(currentSuffixIndex);
+ QString availableSuffix = availableVersion.mid(availableSuffixIndex);
+
+ if (currentVersionNumber.normalized() == availableVersionNumber.normalized()) {
+ if (!currentSuffix.isEmpty() && availableSuffix.isEmpty()) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return (currentVersionNumber.normalized() < availableVersionNumber.normalized());
+ }
+}
diff --git a/libs/ui/utils/KisManualUpdater.h b/libs/ui/utils/KisManualUpdater.h
new file mode 100644
index 0000000000..c29847aaeb
--- /dev/null
+++ b/libs/ui/utils/KisManualUpdater.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef KISMANUALUPDATER_H
+#define KISMANUALUPDATER_H
+
+#include
+
+#include
+#include
+#include "kritaui_export.h"
+
+
+class KRITAUI_EXPORT KisManualUpdater : public KisUpdaterBase
+{
+ Q_OBJECT
+
+public:
+ KisManualUpdater();
+ /**
+ * @brief KisManualUpdater - constructor for testing
+ * @param rssModel
+ * @param currentVersion
+ */
+ explicit KisManualUpdater(MultiFeedRssModel* rssModel, QString& currentVersion);
+
+ void checkForUpdate() override;
+
+ // this updater can only check for updates
+ /**
+ * @brief the manual updater can only check for available versions
+ * @return false
+ */
+ inline bool hasUpdateCapability() override { return false; }
+ inline void doUpdate() override { return; }
+
+public Q_SLOTS:
+ void rssDataChanged();
+
+private:
+ bool availableVersionIsHigher(QString currentVersion, QString availableVersion);
+
+ QScopedPointer m_rssModel;
+ QString m_currentVersion;
+
+ friend class KisManualUpdaterTest;
+};
+
+#endif // KISMANUALUPDATER_H
diff --git a/libs/ui/utils/KisUpdaterBase.cpp b/libs/ui/utils/KisUpdaterBase.cpp
new file mode 100644
index 0000000000..cd4daf4e04
--- /dev/null
+++ b/libs/ui/utils/KisUpdaterBase.cpp
@@ -0,0 +1,6 @@
+#include "KisUpdaterBase.h"
+
+KisUpdaterBase::KisUpdaterBase()
+{
+
+}
diff --git a/libs/ui/utils/KisUpdaterBase.h b/libs/ui/utils/KisUpdaterBase.h
new file mode 100644
index 0000000000..c79c85fda0
--- /dev/null
+++ b/libs/ui/utils/KisUpdaterBase.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef KISUPDATERBASE_H
+#define KISUPDATERBASE_H
+
+#include
+#include
+
+class QString;
+
+class KisUpdaterBase : public QObject
+{
+ Q_OBJECT
+
+public:
+ KisUpdaterBase();
+
+ /**
+ * @brief start the process checking whether there is an update available or not
+ * When the check is done, the updater emits sigUpdateCheckStateChange with the check result
+ */
+ virtual void checkForUpdate() = 0;
+
+ /**
+ * @brief Returns true if this updater can actually perform an update.
+ * If it can only check for new versions, return false.
+ * @return bool
+ */
+ virtual bool hasUpdateCapability() = 0;
+
+ /**
+ * @brief if the updater has update capability, start the update process
+ * When the update is done, the updater emits sigUpdateCheckStateChange with the check result
+ */
+ virtual void doUpdate() = 0;
+
+Q_SIGNALS:
+ void sigUpdateCheckStateChange(KisUpdaterStatus);
+
+protected:
+ KisUpdaterStatus m_updaterStatus;
+};
+
+#endif // KISUPDATERBASE_H
diff --git a/libs/ui/utils/KisUpdaterStatus.cpp b/libs/ui/utils/KisUpdaterStatus.cpp
new file mode 100644
index 0000000000..93ead4e51e
--- /dev/null
+++ b/libs/ui/utils/KisUpdaterStatus.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#include "KisUpdaterStatus.h"
+#include
+
+KisUpdaterStatus::KisUpdaterStatus()
+{
+
+}
+
+KisUpdaterStatus::KisUpdaterStatus(const KisUpdaterStatus& rhs)
+ : QObject(0)
+ , m_status(rhs.m_status)
+ , m_availableVersion(rhs.m_availableVersion)
+ , m_downloadLink(rhs.m_downloadLink)
+ , m_updaterOutput(rhs.m_updaterOutput)
+ , m_details(rhs.m_details)
+{
+
+}
+
+KisUpdaterStatus::~KisUpdaterStatus()
+{
+
+}
+
+UpdaterStatus::StatusID KisUpdaterStatus::status() {
+ return m_status;
+}
+
+QString KisUpdaterStatus::availableVersion() {
+ return m_availableVersion;
+}
+
+QString KisUpdaterStatus::downloadLink() {
+ return m_downloadLink;
+}
+
+QString KisUpdaterStatus::updaterOutput() {
+ return m_updaterOutput;
+}
+
+QString KisUpdaterStatus::details()
+{
+ return m_details;
+}
+
+void KisUpdaterStatus::setStatus(const UpdaterStatus::StatusID& status)
+{
+ m_status = status;
+}
+
+void KisUpdaterStatus::setAvailableVersion(const QString& availableVersion)
+{
+ m_availableVersion = availableVersion;
+}
+
+void KisUpdaterStatus::setDownloadLink(const QString& downloadLink)
+{
+ m_downloadLink = downloadLink;
+}
+
+void KisUpdaterStatus::setUpdaterOutput(const QString& updaterOutput)
+{
+ m_updaterOutput = updaterOutput;
+}
+
+void KisUpdaterStatus::setDetails(const QString& details)
+{
+ m_details = details;
+}
+
+KisUpdaterStatus& KisUpdaterStatus::operator=(KisUpdaterStatus& secondArg)
+{
+ m_status = secondArg.status();
+ m_availableVersion = secondArg.availableVersion();
+ m_downloadLink = secondArg.downloadLink();
+ m_updaterOutput = secondArg.updaterOutput();
+ m_details = secondArg.details();
+
+ return *this;
+}
+
+bool KisUpdaterStatus::operator==(KisUpdaterStatus& secondArg)
+{
+ if (m_status == secondArg.status()) {
+ return true;
+ } else {
+ return false;
+ }
+}
diff --git a/libs/ui/utils/KisUpdaterStatus.h b/libs/ui/utils/KisUpdaterStatus.h
new file mode 100644
index 0000000000..3492675f52
--- /dev/null
+++ b/libs/ui/utils/KisUpdaterStatus.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef KISUPDATERSTATUS_H
+#define KISUPDATERSTATUS_H
+
+#include
+#include
+#include
+
+#include "kritaui_export.h"
+
+
+namespace UpdaterStatus {
+
+ enum class KRITAUI_EXPORT StatusID {
+ UPTODATE,
+ UPDATE_AVAILABLE,
+ CHECK_ERROR,
+ UPDATE_ERROR,
+ IN_PROGRESS,
+ RESTART_REQUIRED,
+ INITIALIZED
+ };
+
+}
+
+Q_DECLARE_METATYPE(UpdaterStatus::StatusID);
+
+
+class KRITAUI_EXPORT KisUpdaterStatus : public QObject
+{
+ Q_OBJECT
+
+public:
+ KisUpdaterStatus();
+ KisUpdaterStatus(const KisUpdaterStatus& rhs);
+ ~KisUpdaterStatus();
+
+ UpdaterStatus::StatusID status();
+ QString availableVersion();
+ QString downloadLink();
+ QString updaterOutput();
+ QString details();
+
+ void setStatus(const UpdaterStatus::StatusID& status);
+ void setAvailableVersion(const QString& availableVersion);
+ void setDownloadLink(const QString& downloadLink);
+ void setUpdaterOutput(const QString& updaterOutput);
+ void setDetails(const QString& details);
+
+ KisUpdaterStatus& operator=(KisUpdaterStatus& secondArg);
+ bool operator==(KisUpdaterStatus& secondArg);
+
+private:
+ UpdaterStatus::StatusID m_status { UpdaterStatus::StatusID::INITIALIZED };
+ QString m_availableVersion;
+ QString m_downloadLink;
+ QString m_updaterOutput;
+ QString m_details;
+};
+
+Q_DECLARE_METATYPE(KisUpdaterStatus);
+
+#endif // KISUPDATERSTATUS_H
diff --git a/libs/ui/widgets/KisNewsWidget.cpp b/libs/ui/widgets/KisNewsWidget.cpp
index bf9207aa84..f35c7f680e 100644
--- a/libs/ui/widgets/KisNewsWidget.cpp
+++ b/libs/ui/widgets/KisNewsWidget.cpp
@@ -1,229 +1,144 @@
/*
* Copyright (c) 2018 boud
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "KisNewsWidget.h"
#include
#include
#include
#include
#include
#include
#include
#include "kis_config.h"
#include "KisMultiFeedRSSModel.h"
#include "QRegularExpression"
KisNewsDelegate::KisNewsDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
}
void KisNewsDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
painter->save();
QStyleOptionViewItem optionCopy = option;
initStyleOption(&optionCopy, index);
QStyle *style = optionCopy.widget? optionCopy.widget->style() : QApplication::style();
QTextDocument doc;
doc.setHtml(optionCopy.text);
doc.setDocumentMargin(10);
/// Painting item without text
optionCopy.text = QString();
style->drawControl(QStyle::CE_ItemViewItem, &optionCopy, painter);
QAbstractTextDocumentLayout::PaintContext ctx;
// Highlighting text if item is selected
if (optionCopy.state & QStyle::State_Selected) {
ctx.palette.setColor(QPalette::Text, optionCopy.palette.color(QPalette::Active, QPalette::HighlightedText));
}
painter->translate(optionCopy.rect.left(), optionCopy.rect.top());
QRect clip(0, 0, optionCopy.rect.width(), optionCopy.rect.height());
doc.setPageSize(clip.size());
doc.drawContents(painter, clip);
painter->restore();
}
QSize KisNewsDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItem optionCopy = option;
initStyleOption(&optionCopy, index);
QTextDocument doc;
doc.setHtml(optionCopy.text);
doc.setTextWidth(optionCopy.rect.width());
return QSize(doc.idealWidth(), doc.size().height());
}
KisNewsWidget::KisNewsWidget(QWidget *parent)
: QWidget(parent)
, m_getNews(false)
, m_rssModel(0)
- , m_needsVersionUpdate(false)
{
setupUi(this);
m_rssModel = new MultiFeedRssModel(this);
connect(m_rssModel, SIGNAL(feedDataChanged()), this, SLOT(rssDataChanged()));
setCursor(Qt::PointingHandCursor);
listNews->setModel(m_rssModel);
listNews->setItemDelegate(new KisNewsDelegate(listNews));
connect(listNews, SIGNAL(clicked(QModelIndex)), this, SLOT(itemSelected(QModelIndex)));
}
void KisNewsWidget::setAnalyticsTracking(QString text)
{
m_analyticsTrackingParameters = text;
}
-bool KisNewsWidget::hasUpdateAvailable()
-{
- return m_needsVersionUpdate;
-}
-
-QString KisNewsWidget::versionNumber()
-{
- return m_newVersionNumber;
-}
-
-QString KisNewsWidget::versionLink()
-{
- return m_newVersionLink;
-}
-
void KisNewsWidget::toggleNews(bool toggle)
{
KisConfig cfg(false);
cfg.writeEntry("FetchNews", toggle);
if (toggle) {
- m_rssModel->addFeed(QLatin1String("https://krita.org/en/feed/"));
+// m_rssModel->addFeed(QLatin1String("https://krita.org/en/feed/"));
+ m_rssModel->addFeed(QLatin1String("http://localhost/feed.xml"));
}
else {
- m_rssModel->removeFeed(QLatin1String("https://krita.org/en/feed/"));
+// m_rssModel->removeFeed(QLatin1String("https://krita.org/en/feed/"));
+ m_rssModel->removeFeed(QLatin1String("http://localhost/feed.xml"));
}
}
void KisNewsWidget::itemSelected(const QModelIndex &idx)
{
if (idx.isValid()) {
- QString link = idx.data(RssRoles::LinkRole).toString();
+ QString link = idx.data(KisRssReader::RssRoles::LinkRole).toString();
// append query string for analytics tracking if we set it
if (m_analyticsTrackingParameters != "") {
// use title in analytics query string
- QString linkTitle = idx.data(RssRoles::TitleRole).toString();
+ QString linkTitle = idx.data(KisRssReader::RssRoles::TitleRole).toString();
linkTitle = linkTitle.simplified(); // trims and makes 1 white space
linkTitle = linkTitle.replace(" ", "");
m_analyticsTrackingParameters = m_analyticsTrackingParameters.append(linkTitle);
QDesktopServices::openUrl(QUrl(link.append(m_analyticsTrackingParameters)));
} else {
QDesktopServices::openUrl(QUrl(link));
}
}
}
void KisNewsWidget::rssDataChanged()
{
-
- // grab the latest release post and URL for reference later
- // if we need to update
- for (int i = 0; i < m_rssModel->rowCount(); i++)
- {
- const QModelIndex &idx = m_rssModel->index(i);
-
- if (idx.isValid()) {
-
- // only use official release announcements to get version number
- if ( idx.data(RssRoles::CategoryRole).toString() != "Official Release") {
- continue;
- }
-
- QString linkTitle = idx.data(RssRoles::TitleRole).toString();
-
- // regex to capture version number
- QRegularExpression versionRegex("\\d\\.\\d\\.?\\d?\\.?\\d");
- QRegularExpressionMatch matched = versionRegex.match(linkTitle);
-
- // only take the top match for release version since that is the newest
- if (matched.hasMatch()) {
- m_newVersionNumber = matched.captured(0);
- m_newVersionLink = idx.data(RssRoles::LinkRole).toString();
- break;
- }
- }
- }
-
- // see if we need to update our version, or we are on a dev version
- calculateVersionUpdateStatus();
-
emit newsDataChanged();
}
-
-void KisNewsWidget::calculateVersionUpdateStatus()
-{
- // do nothing if we are in dev version.
- QString currentVersionString = qApp->applicationVersion();
- if (currentVersionString.contains("git")) {
- return;
- }
-
- QList currentVersionParts;
- Q_FOREACH (QString number, currentVersionString.split(".")) {
- currentVersionParts.append(number.toInt());
- }
-
- QList onlineReleaseAnnouncement;
- Q_FOREACH (QString number, m_newVersionNumber.split(".")) {
- onlineReleaseAnnouncement.append(number.toInt());
- }
-
- while (onlineReleaseAnnouncement.size() < 4) {
- onlineReleaseAnnouncement.append(0);
- }
-
- while (currentVersionParts.size() < 4) {
- currentVersionParts.append(0);
- }
-
- // Check versions from mayor to minor
- // We don't assume onlineRelease version is always equal or higher.
- bool makeUpdate = true;
- for (int i = 0; i <= 3; i++) {
- if (onlineReleaseAnnouncement.at(i) > currentVersionParts.at(i)) {
- m_needsVersionUpdate = (true & makeUpdate);
- return;
- } else if (onlineReleaseAnnouncement.at(i) < currentVersionParts.at(i)) {
- makeUpdate &= false;
- }
- }
-}
diff --git a/libs/ui/widgets/KisNewsWidget.h b/libs/ui/widgets/KisNewsWidget.h
index 71dba10506..46aa55e8c4 100644
--- a/libs/ui/widgets/KisNewsWidget.h
+++ b/libs/ui/widgets/KisNewsWidget.h
@@ -1,79 +1,63 @@
/*
* Copyright (c) 2018 boud
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KISNEWSWIDGET_H
#define KISNEWSWIDGET_H
#include
#include
#include
#include
class MultiFeedRssModel;
class KisNewsDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
KisNewsDelegate(QObject *parent = 0);
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
};
/**
* @brief The KisNewsWidget class shows the latest news from Krita.org
*/
class KisNewsWidget : public QWidget, public Ui::KisNewsPage
{
Q_OBJECT
public:
explicit KisNewsWidget(QWidget *parent = nullptr);
void setAnalyticsTracking(QString text);
- bool hasUpdateAvailable();
- QString versionNumber();
- QString versionLink();
-
Q_SIGNALS:
void newsDataChanged();
private Q_SLOTS:
void toggleNews(bool toggle);
void itemSelected(const QModelIndex &idx);
void rssDataChanged();
private:
- // do version compare to see if there is a new version available
- void calculateVersionUpdateStatus();
-
-private:
- bool m_getNews;
- MultiFeedRssModel *m_rssModel;
+ bool m_getNews {false};
+ MultiFeedRssModel *m_rssModel {0};
QString m_analyticsTrackingParameters;
-
- /// for new Krita version notification
- QString m_newVersionNumber;
- QString m_newVersionLink;
-
- // version checking logic tells us we need to update our Krita
- bool m_needsVersionUpdate;
-
};
#endif // KISNEWSWIDGET_H
diff --git a/packaging/linux/appimage/build-image.sh b/packaging/linux/appimage/build-image.sh
index 345dc4ace9..d70cc23564 100755
--- a/packaging/linux/appimage/build-image.sh
+++ b/packaging/linux/appimage/build-image.sh
@@ -1,110 +1,155 @@
#!/bin/bash
# Halt on errors and be verbose about what we are doing
-set -e
+#set -e
set -x
# Read in our parameters
export BUILD_PREFIX=$1
export KRITA_SOURCES=$2
# Save some frequently referenced locations in variables for ease of use / updating
export APPDIR=$BUILD_PREFIX/krita.appdir
export PLUGINS=$APPDIR/usr/lib/kritaplugins/
-# qjsonparser, used to add metadata to the plugins needs to work in a en_US.UTF-8 environment.
+# qjsonparser, used to add metadata to the plugins needs to work in a en_US.UTF-8 environment.
# That's not always the case, so make sure it is
export LC_ALL=en_US.UTF-8
export LANG=en_us.UTF-8
# We want to use $prefix/deps/usr/ for all our dependencies
export DEPS_INSTALL_PREFIX=$BUILD_PREFIX/deps/usr/
export DOWNLOADS_DIR=$BUILD_PREFIX/downloads/
# Setup variables needed to help everything find what we built
export LD_LIBRARY_PATH=$DEPS_INSTALL_PREFIX/lib/:$DEPS_INSTALL_PREFIX/lib/x86_64-linux-gnu/:$APPDIR/usr/lib/:$LD_LIBRARY_PATH
export PATH=$DEPS_INSTALL_PREFIX/bin/:$PATH
export PKG_CONFIG_PATH=$DEPS_INSTALL_PREFIX/share/pkgconfig/:$DEPS_INSTALL_PREFIX/lib/pkgconfig/:/usr/lib/pkgconfig/:$PKG_CONFIG_PATH
export CMAKE_PREFIX_PATH=$DEPS_INSTALL_PREFIX:$CMAKE_PREFIX_PATH
export PYTHONPATH=$DEPS_INSTALL_PREFIX/sip/:$DEPS_INSTALL_PREFIX/lib/python3.8/site-packages/:$DEPS_INSTALL_PREFIX/lib/python3.8/
export PYTHONHOME=$DEPS_INSTALL_PREFIX
+# download
+mkdir -p $DOWNLOADS_DIR
+cd $DOWNLOADS_DIR
+wget "https://files.kde.org/krita/build/AppImageUpdate-x86_64.AppImage" -O AppImageUpdate
+echo -n "ebc4763e8eac6aa7b9dfcbea77ec07d2e01fa1b9f10a38d4af0fc040bc965c1f AppImageUpdate" | sha256sum -c -
+
# Switch over to our build prefix
cd $BUILD_PREFIX
#
# Now we can get the process started!
#
# Step 0: place the translations where ki18n and Qt look for them
if [ -d $APPDIR/usr/share/locale ] ; then
mv $APPDIR/usr/share/locale $APPDIR/usr/share/krita
fi
-# Step 1: Copy over all the resources provided by dependencies that we need
+# Step 1: Copy over all the resources provided by dependencies that we need
cp -r $DEPS_INSTALL_PREFIX/share/locale $APPDIR/usr/share/krita
cp -r $DEPS_INSTALL_PREFIX/share/kf5 $APPDIR/usr/share
cp -r $DEPS_INSTALL_PREFIX/share/mime $APPDIR/usr/share
cp -r $DEPS_INSTALL_PREFIX/lib/python3.8 $APPDIR/usr/lib
cp -r $DEPS_INSTALL_PREFIX/share/sip $APPDIR/usr/share
cp -r $DEPS_INSTALL_PREFIX/translations $APPDIR/usr/
# Step 2: Relocate x64 binaries from the architecture specific directory as required for Appimages
mv $APPDIR/usr/lib/x86_64-linux-gnu/* $APPDIR/usr/lib
rm -rf $APPDIR/usr/lib/x86_64-linux-gnu/
# Step 3: Update the rpath in the various plugins we have to make sure they'll be loadable in an Appimage context
for lib in $PLUGINS/*.so*; do
- patchelf --set-rpath '$ORIGIN/..' $lib;
+ patchelf --set-rpath '$ORIGIN/..' $lib;
done
for lib in $APPDIR/usr/lib/python3.8/site-packages/PyQt5/*.so*; do
patchelf --set-rpath '$ORIGIN/../..' $lib;
done
for lib in $APPDIR/usr/lib/python3.8/lib-dynload/*.so*; do
patchelf --set-rpath '$ORIGIN/../..' $lib;
done
patchelf --set-rpath '$ORIGIN/../../../..' $APPDIR/usr/lib/qml/org/krita/draganddrop/libdraganddropplugin.so
patchelf --set-rpath '$ORIGIN/../../../..' $APPDIR/usr/lib/qml/org/krita/sketch/libkritasketchplugin.so
patchelf --set-rpath '$ORIGIN/../..' $APPDIR/usr/lib/krita-python-libs/PyKrita/krita.so
patchelf --set-rpath '$ORIGIN/../..' $APPDIR/usr/lib/python3.8/site-packages/PyQt5/sip.so
+# Step 4: Install AppImageUpdate
+if [ -f $DOWNLOADS_DIR/AppImageUpdate ]; then
+ cp $DOWNLOADS_DIR/AppImageUpdate $APPDIR/usr/bin/
+ chmod +x $APPDIR/usr/bin/AppImageUpdate
+fi
+
# Step 5: Find out what version of Krita we built and give the Appimage a proper name
cd $BUILD_PREFIX/krita-build
KRITA_VERSION=$(grep "#define KRITA_VERSION_STRING" libs/version/kritaversion.h | cut -d '"' -f 2)
-
# Also find out the revision of Git we built
# Then use that to generate a combined name we'll distribute
cd $KRITA_SOURCES
if [[ -d .git ]]; then
GIT_REVISION=$(git rev-parse --short HEAD)
- VERSION=$KRITA_VERSION-$GIT_REVISION
+ export VERSION=$KRITA_VERSION-$GIT_REVISION
+ VERSION_TYPE="developement"
+ BRANCH="$(git rev-parse --abbrev-ref HEAD)"
+ if [ "$BRANCH" = "master" ]; then
+ CHANNEL="Next"
+ else
+ CHANNEL="Plus"
+ fi
else
- VERSION=$KRITA_VERSION
+ export VERSION=$KRITA_VERSION
+
+ #if KRITA_BETA is set, set channel to Beta, otherwise set it to stable
+ grep "define KRITA_BETA 1" libs/version/kritaversion.h;
+ is_beta=$?
+ if [ is_beta -eq 0 ]; then
+ VERSION_TYPE="developement"
+ CHANNEL="Beta"
+ else
+ VERSION_TYPE="stable"
+ CHANNEL="Stable"
+ fi
fi
+DATE=$(git log -1 --format="%ct" | xargs -I{} date -d @{} +%Y-%m-%d)
+if [ "$DATE" = "" ] ; then
+ DATE=$(date +%Y-%m-%d)
+fi
+
+sed -e "s|||" -i $APPDIR/usr/share/metainfo/org.kde.krita.appdata.xml
+
+# set zsync url for linuxdeployqt
+if [ "$CHANNEL" = "Next" ]; then
+ ZSYNC_URL="zsync|https://binary-factory.kde.org/job/Krita_Nightly_Appimage_Build/lastSuccessfulBuild/artifact/Krita-${CHANNEL}-x86_64.AppImage.zsync"
+elif [ "$CHANNEL" = "Plus" ]; then
+ ZSYNC_URL="zsync|https://binary-factory.kde.org/job/Krita_Stable_Appimage_Build/lastSuccessfulBuild/artifact/Krita-${CHANNEL}-x86_64.AppImage.zsync"
+elif [ "$CHANNEL" = "Stable" ]; then
+ ZSYNC_URL="zsync|https://download.kde.org/stable/krita/updates/Krita-${CHANNEL}-x86_64.AppImage.zsync"
+elif [ "$CHANNEL" = "Beta" ]; then
+ ZSYNC_URL="zsync|https://download.kde.org/unstable/krita/updates/Krita-${CHANNEL}-x86_64.AppImage.zsync"
+fi
# Return to our build root
cd $BUILD_PREFIX
# place the icon where linuxdeployqt seems to expect it
find $APPDIR -name krita.png
cp $APPDIR/usr/share/icons/hicolor/256x256/apps/krita.png $APPDIR
ls $APPDIR
# Step 4: Build the image!!!
linuxdeployqt $APPDIR/usr/share/applications/org.kde.krita.desktop \
-executable=$APPDIR/usr/bin/krita \
-qmldir=$DEPS_INSTALL_PREFIX/qml \
-verbose=2 \
-bundle-non-qt-libs \
-extra-plugins=$PLUGINS,$APPDIR/usr/lib/krita-python-libs/PyKrita/krita.so,$APPDIR/usr/lib//qml/org/krita/sketch/libkritasketchplugin.so,$APPDIR/usr/lib/qml/org/krita/draganddrop/libdraganddropplugin.so \
- -appimage
-
-# Generate a new name for the Appimage file and rename it accordingly
-APPIMAGE=krita-"$VERSION"-x86_64.appimage
+ -updateinformation="${ZSYNC_URL}" \
+ -appimage
-mv Krita*x86_64.AppImage $APPIMAGE
+# the zsync will be regenerated after signing
+rm Krita-${VERSION}-x86_64.AppImage.zsync
diff --git a/packaging/linux/appimage/build-krita.sh b/packaging/linux/appimage/build-krita.sh
index f69b53b3ac..146f8279c2 100755
--- a/packaging/linux/appimage/build-krita.sh
+++ b/packaging/linux/appimage/build-krita.sh
@@ -1,52 +1,76 @@
#!/bin/bash
# Halt on errors and be verbose about what we are doing
set -e
set -x
# Read in our parameters
export BUILD_PREFIX=$1
export KRITA_SOURCES=$2
-# qjsonparser, used to add metadata to the plugins needs to work in a en_US.UTF-8 environment.
+# qjsonparser, used to add metadata to the plugins needs to work in a en_US.UTF-8 environment.
# That's not always the case, so make sure it is
export LC_ALL=en_US.UTF-8
export LANG=en_us.UTF-8
# We want to use $prefix/deps/usr/ for all our dependencies
export DEPS_INSTALL_PREFIX=$BUILD_PREFIX/deps/usr/
export DOWNLOADS_DIR=$BUILD_PREFIX/downloads/
# Setup variables needed to help everything find what we build
export LD_LIBRARY_PATH=$DEPS_INSTALL_PREFIX/lib:$LD_LIBRARY_PATH
export PATH=$DEPS_INSTALL_PREFIX/bin:$PATH
export PKG_CONFIG_PATH=$DEPS_INSTALL_PREFIX/share/pkgconfig:$DEPS_INSTALL_PREFIX/lib/pkgconfig:/usr/lib/pkgconfig:$PKG_CONFIG_PATH
export CMAKE_PREFIX_PATH=$DEPS_INSTALL_PREFIX:$CMAKE_PREFIX_PATH
export PYTHONPATH=$DEPS_INSTALL_PREFIX/sip:$DEPS_INSTALL_PREFIX/lib/python3.8/site-packages:$DEPS_INSTALL_PREFIX/lib/python3.8
export PYTHONHOME=$DEPS_INSTALL_PREFIX
+cd $KRITA_SOURCES
+
+# determine the channel for branding
+if [[ -d .git ]]; then
+ BRANCH="$(git rev-parse --abbrev-ref HEAD)"
+ if [ "$BRANCH" = "master" ]; then
+ BRANDING="Next"
+ else
+ BRANDING="Plus"
+ fi
+else
+ #if KRITA_BETA is set, set channel to Beta, otherwise set it to stable
+ grep "define KRITA_BETA 1" libs/version/kritaversion.h;
+ is_beta=$?
+ if [ is_beta -eq 0 ]; then
+ BRANDING="Beta"
+ else
+ BRANDING="default"
+ fi
+fi
+
+BUILD_TYPE="Release"
+
# Make sure our build directory exists
if [ ! -d $BUILD_PREFIX/krita-build/ ] ; then
mkdir -p $BUILD_PREFIX/krita-build/
fi
# Now switch to it
cd $BUILD_PREFIX/krita-build/
# Determine how many CPUs we have
CPU_COUNT=`grep processor /proc/cpuinfo | wc -l`
+
# Configure Krita
cmake $KRITA_SOURCES \
-DCMAKE_INSTALL_PREFIX:PATH=$BUILD_PREFIX/krita.appdir/usr \
-DDEFINE_NO_DEPRECATED=1 \
- -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
-DFOUNDATION_BUILD=1 \
-DHIDE_SAFE_ASSERTS=ON \
- -DBUILD_TESTING=FALSE \
+ -DBUILD_TESTING=TRUE \
-DPYQT_SIP_DIR_OVERRIDE=$DEPS_INSTALL_PREFIX/share/sip/ \
- -DHAVE_MEMORY_LEAK_TRACKER=FALSE
-
+ -DHAVE_MEMORY_LEAK_TRACKER=FALSE \
+ -DBRANDING="${BRANDING}"
+
# Build and Install Krita (ready for the next phase)
make -j$CPU_COUNT install
-
diff --git a/packaging/linux/appimage/generate_zsync.sh b/packaging/linux/appimage/generate_zsync.sh
new file mode 100644
index 0000000000..a826110b99
--- /dev/null
+++ b/packaging/linux/appimage/generate_zsync.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+
+set -x
+set -e
+
+APPIMAGE_PATH="${1}"
+CHANNEL="${2}"
+VERSION="${3}"
+
+if [ -z $APPIMAGE_PATH ]; then
+ echo "path to appimage (arg1) is not set"
+ exit 1
+fi
+
+if [ -z $CHANNEL ]; then
+ echo "channel (arg2) is not set"
+ exit 1
+fi
+
+if [ -z $VERSION ]; then
+ echo "version (arg3) is not set"
+ exit 1
+fi
+
+# regenerate zsync file
+if [ "$CHANNEL" = "Next" ]; then
+ URL="${BUILD_URL}/artifact/"
+elif [ "$CHANNEL" = "Plus" ]; then
+ URL="${BUILD_URL}/artifact/"
+elif [ "$CHANNEL" = "Stable" ]; then
+ URL="https://download.kde.org/stable/krita/${VERSION}"
+elif [ "$CHANNEL" = "Beta" ]; then
+ URL="https://download.kde.org/unstable/krita/${VERSION}"
+fi
+
+zsyncmake -u "${URL}/$(basename ${APPIMAGE_PATH})" -o $APPIMAGE_PATH.zsync.new $APPIMAGE_PATH
+ret=$?
+if [ $ret -eq 0 ]; then
+ mv $APPIMAGE_PATH.zsync.new Krita-${CHANNEL}-x86_64.AppImage.zsync
+fi
+
diff --git a/packaging/linux/appimage/rewrite_appimage_updinfo.sh b/packaging/linux/appimage/rewrite_appimage_updinfo.sh
new file mode 100755
index 0000000000..78452e5a87
--- /dev/null
+++ b/packaging/linux/appimage/rewrite_appimage_updinfo.sh
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+
+set -x
+set -e
+
+APPIMAGE_PATH="${1}"
+UPDURL="${2}"
+
+if [ -z $APPIMAGE_PATH ]; then
+ echo "path to appimage (arg1) is not set"
+ exit 1
+fi
+
+if [ -z $UPDURL ]; then
+ echo "update url (arg2) is not set"
+ exit 1
+fi
+
+
+tempdir="$(mktemp rewrite_appimage_updinfo.XXXXXX -d -p /tmp)"
+
+destination=$(basename $APPIMAGE_PATH)
+updinfo_file="${tempdir}/upd_info"
+
+echo -n "zsync|${UPDURL}" > $updinfo_file
+
+# get offsets and lengths of .upd_info section of the AppImage
+OFFSET=$(objdump -h "${APPIMAGE_PATH}" | grep .upd_info | awk '{print $6}')
+LENGTH=$(objdump -h "${APPIMAGE_PATH}" | grep .upd_info | awk '{print $3}')
+
+# Null the section
+dd if=/dev/zero bs=1 seek=$(($(echo 0x$OFFSET))) count=$(($(echo 0x$LENGTH))) of="${APPIMAGE_PATH}" conv=notrunc
+
+# Embed the updinfo
+dd if=${updinfo_file} bs=1 seek=$(($(echo 0x$OFFSET))) count=$(($(echo 0x$LENGTH))) of="${APPIMAGE_PATH}" conv=notrunc
+
+# cleanup
+rm -rf $tempdir
diff --git a/packaging/linux/appimage/sign_appimage.sh b/packaging/linux/appimage/sign_appimage.sh
new file mode 100755
index 0000000000..e3c0c72bb8
--- /dev/null
+++ b/packaging/linux/appimage/sign_appimage.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+
+set -x
+set -e
+
+APPIMAGE_PATH="${1}"
+GPG_KEY="${2}"
+
+if [ -z $APPIMAGE_PATH ]; then
+ echo "path to appimage (arg1) is not set"
+ exit 1
+fi
+
+if [ -z $GPG_KEY ]; then
+ echo "gpg key id (arg3) is not set"
+ exit 1
+fi
+
+tempdir="$(mktemp sign_appimage.XXXXXX -d -p /tmp)"
+
+destination=$(basename $APPIMAGE_PATH)
+
+ascfile="${tempdir}/${destination}.digest.asc"
+digestfile="${tempdir}/${destination}.digest"
+sigkeyfile="${tempdir}/sig_pubkey"
+
+if [ -f $digestfile ]; then rm $digestfile; fi
+if [ -f $ascfile ]; then rm $ascfile; fi
+if [ -f $sigkeyfile ]; then rm $sigkeyfile; fi
+
+# get offsets and lengths of .sha256_sig and .sig_key sections of the AppImage
+SIG_OFFSET=$(objdump -h "${APPIMAGE_PATH}" | grep .sha256_sig | awk '{print $6}')
+SIG_LENGTH=$(objdump -h "${APPIMAGE_PATH}" | grep .sha256_sig | awk '{print $3}')
+
+KEY_OFFSET=$(objdump -h "${APPIMAGE_PATH}" | grep .sig_key | awk '{print $6}')
+KEY_LENGTH=$(objdump -h "${APPIMAGE_PATH}" | grep .sig_key | awk '{print $3}')
+
+# Null the sections
+dd if=/dev/zero bs=1 seek=$(($(echo 0x$SIG_OFFSET))) count=$(($(echo 0x$SIG_LENGTH))) of="${APPIMAGE_PATH}" conv=notrunc
+dd if=/dev/zero bs=1 seek=$(($(echo 0x$KEY_OFFSET))) count=$(($(echo 0x$KEY_LENGTH))) of="${APPIMAGE_PATH}" conv=notrunc
+
+# generate sha256sum
+# BEWARE THE NEWLINE! if it is not stripped, AppImageUpdate validaton will fail
+sha256sum $APPIMAGE_PATH | cut -d " " -f 1 | tr -d '\n' > $digestfile
+
+#sign the sha256sum
+gpg2 --detach-sign --armor -u $GPG_KEY -o $ascfile $digestfile
+gpg2 --export --armor $GPG_KEY > $sigkeyfile
+
+# Embed the signature
+dd if=${ascfile} bs=1 seek=$(($(echo 0x$SIG_OFFSET))) count=$(($(echo 0x$SIG_LENGTH))) of="${APPIMAGE_PATH}" conv=notrunc
+# Embed the public part of the signing key
+dd if=${sigkeyfile} bs=1 seek=$(($(echo 0x$KEY_OFFSET))) count=$(($(echo 0x$KEY_LENGTH))) of="${APPIMAGE_PATH}" conv=notrunc
+
+# cleanup
+rm -rf $tempdir
diff --git a/packaging/linux/appimage/strip_appimage_signature.sh b/packaging/linux/appimage/strip_appimage_signature.sh
new file mode 100755
index 0000000000..518bc8dbf2
--- /dev/null
+++ b/packaging/linux/appimage/strip_appimage_signature.sh
@@ -0,0 +1,67 @@
+#!/usr/bin/env bash
+
+set -x
+set -e
+
+APPIMAGE_PATH="${1}"
+CHANNEL="${2}"
+GPG_KEY="${3}"
+
+if [ -z $APPIMAGE_PATH ]; then
+ echo "path to appimage (arg1) is not set"
+ exit 1
+fi
+
+if [ -z $CHANNEL ]; then
+ echo "channel (arg2) is not set"
+ exit 1
+fi
+
+if [ -z $GPG_KEY ]; then
+ echo "gpg key id (arg3) is not set"
+ exit 1
+fi
+
+tempdir="$(mktemp strip_appimage_signature.XXXXXX -d -p /tmp)"
+
+destination=$(basename $APPIMAGE_PATH)
+
+ascfile="${tempdir}/${destination}.digest.asc"
+digestfile="${tempdir}/${destination}.digest"
+sigkeyfile="${tempdir}/sig_pubkey"
+
+if [ -f $digestfile ]; then rm $digestfile; fi
+if [ -f $ascfile ]; then rm $ascfile; fi
+if [ -f $sigkeyfile ]; then rm $sigkeyfile; fi
+
+# get offsets and lengths of .sha256_sig and .sig_key sections of the AppImage
+SIG_OFFSET=$(objdump -h "${APPIMAGE_PATH}" | grep .sha256_sig | awk '{print $6}')
+SIG_LENGTH=$(objdump -h "${APPIMAGE_PATH}" | grep .sha256_sig | awk '{print $3}')
+
+KEY_OFFSET=$(objdump -h "${APPIMAGE_PATH}" | grep .sig_key | awk '{print $6}')
+KEY_LENGTH=$(objdump -h "${APPIMAGE_PATH}" | grep .sig_key | awk '{print $3}')
+
+# Null the sections
+dd if=/dev/zero bs=1 seek=$(($(echo 0x$SIG_OFFSET))) count=$(($(echo 0x$SIG_LENGTH))) of="${APPIMAGE_PATH}" conv=notrunc
+dd if=/dev/zero bs=1 seek=$(($(echo 0x$KEY_OFFSET))) count=$(($(echo 0x$KEY_LENGTH))) of="${APPIMAGE_PATH}" conv=notrunc
+
+# regenerate zsync file
+# TODO: replace with real urls
+if [ "$CHANNEL" = "Next" ]; then
+ URL="http://localhost:8000/nightly"
+elif [ "$CHANNEL" = "Plus" ]; then
+ URL="http://localhost:8000/nightly"
+elif [ "$CHANNEL" = "Stable" ]; then
+ URL="http://localhost:8000/stable"
+elif [ "$CHANNEL" = "Beta" ]; then
+ URL="http://localhost:8000/unstable"
+fi
+
+zsyncmake -u "${URL}/$(basename ${APPIMAGE_PATH})" -o $APPIMAGE_PATH.zsync.new $APPIMAGE_PATH
+ret=$?
+if [ $ret -eq 0 ]; then
+ mv $APPIMAGE_PATH.zsync.new Krita-${CHANNEL}-x86_64.AppImage.zsync
+fi
+
+# cleanup
+rm -rf $tempdir
diff --git a/packaging/linux/appimage/validate_appimage_signature.sh b/packaging/linux/appimage/validate_appimage_signature.sh
new file mode 100755
index 0000000000..5511723f7d
--- /dev/null
+++ b/packaging/linux/appimage/validate_appimage_signature.sh
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+
+set -x
+set -e
+
+APPIMAGE_PATH="${1}"
+
+if [ -z $APPIMAGE_PATH ]; then
+ echo "path to appimage (arg1) is not set"
+ exit 1
+fi
+
+tempdir="$(mktemp validate_appimage_signature.XXXXXX -d -p /tmp)"
+
+destination=$(basename $APPIMAGE_PATH)
+
+ascfile="${tempdir}/${destination}.digest.asc"
+digestfile="${tempdir}/${destination}.digest"
+sigkeyfile="${tempdir}/sig_pubkey"
+tempkeyringpath="${tempdir}/keyring"
+tmpappimage="$tempdir/tmp.AppImage"
+
+# get offsets and lengths of .sha256_sig and .sig_key sections of the AppImage
+SIG_OFFSET=$(objdump -h "${APPIMAGE_PATH}" | grep .sha256_sig | awk '{print $6}')
+SIG_LENGTH=$(objdump -h "${APPIMAGE_PATH}" | grep .sha256_sig | awk '{print $3}')
+
+KEY_OFFSET=$(objdump -h "${APPIMAGE_PATH}" | grep .sig_key | awk '{print $6}')
+KEY_LENGTH=$(objdump -h "${APPIMAGE_PATH}" | grep .sig_key | awk '{print $3}')
+
+cp $APPIMAGE_PATH $tmpappimage
+
+# restore the original, for generating checksum
+dd if=/dev/zero bs=1 seek=$(($(echo 0x$SIG_OFFSET))) count=$(($(echo 0x$SIG_LENGTH))) of="${tmpappimage}" conv=notrunc
+dd if=/dev/zero bs=1 seek=$(($(echo 0x$KEY_OFFSET))) count=$(($(echo 0x$KEY_LENGTH))) of="${tmpappimage}" conv=notrunc
+
+sha256sum $tmpappimage | cut -d " " -f 1 | tr -d '\n' > $digestfile
+
+# extract signature
+dd if="${APPIMAGE_PATH}" bs=1 skip=$(($(echo 0x$SIG_OFFSET))) count=$(($(echo 0x$SIG_LENGTH))) of="${ascfile}"
+# extract the public part of the signing key
+dd if=${APPIMAGE_PATH} bs=1 skip=$(($(echo 0x$KEY_OFFSET))) count=$(($(echo 0x$KEY_LENGTH))) of="${sigkeyfile}"
+
+cat $sigkeyfile | gpg2 --no-default-keyring --keyring $tempkeyringpath --import
+gpg2 --no-default-keyring --keyring $tempkeyringpath --verify $ascfile $digestfile
+
+# cleanup
+rm -rf $tempdir