KisWelcomePageWidget.cpp 17.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/* This file is part of the KDE project
 * Copyright (C) 2018 Scott Petrovic <scottpetrovic@gmail.com>
 *
 * 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 <QDebug>
22
#include <QDesktopServices>
23
#include <QFileInfo>
24
#include <QMimeData>
25 26
#include <QPixmap>
#include <QImage>
27

28 29 30 31
#include "kis_action_manager.h"
#include "kactioncollection.h"
#include "kis_action.h"

32 33 34 35 36
#include "KConfigGroup"
#include "KSharedConfig"

#include <QListWidget>
#include <QListWidgetItem>
37

38
#include "kis_icon_utils.h"
39
#include "krita_utils.h"
40
#include "KoStore.h"
41
#include "kis_config.h"
42 43 44 45
#include "KisDocument.h"
#include <kis_image.h>
#include <kis_paint_device.h>
#include <KisPart.h>
46

47

48
KisWelcomePageWidget::KisWelcomePageWidget(QWidget *parent)
49
    : QWidget(parent)
50
{
51 52 53 54
    setupUi(this);
    recentDocumentsListView->setDragEnabled(false);
    recentDocumentsListView->viewport()->setAutoFillBackground(false);
    recentDocumentsListView->setSpacing(2);
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72

    // 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);

73

74 75 76 77
    kritaWebsiteLink->setTextFormat(Qt::RichText);
    kritaWebsiteLink->setTextInteractionFlags(Qt::TextBrowserInteraction);
    kritaWebsiteLink->setOpenExternalLinks(true);

78

79 80 81 82 83 84 85 86 87 88 89
    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));


90 91 92 93
    versionNotificationLabel->setTextFormat(Qt::RichText);
    versionNotificationLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
    versionNotificationLabel->setOpenExternalLinks(true);

94
    connect(chkShowNews, SIGNAL(toggled(bool)), newsWidget, SLOT(toggleNews(bool)));
95

96 97 98
    connect(newsWidget, SIGNAL(newsDataChanged()), this, SLOT(slotUpdateVersionMessage()));


99 100 101 102
    // configure the News area
    KisConfig cfg(true);
    bool m_getNews = cfg.readEntry<bool>("FetchNews", false);
    chkShowNews->setChecked(m_getNews);
103

104 105
    setAcceptDrops(true);

106
}
107

108 109 110
KisWelcomePageWidget::~KisWelcomePageWidget()
{
}
111

112 113 114
void KisWelcomePageWidget::setMainWindow(KisMainWindow* mainWin)
{
    if (mainWin) {
115
        m_mainWindow = mainWin;
116

117 118 119 120 121 122 123
        // 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(")"));
        }
124 125 126 127 128
        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()));
129
        connect(clearRecentFilesLink, SIGNAL(clicked(bool)), this, SLOT(slotClearRecentFiles()));
130

131
        slotUpdateThemeColors();
132 133 134 135

        // allows RSS news items to apply analytics tracking.
        newsWidget->setAnalyticsTracking("?" + analyticsString);

136 137
    }
}
138

139 140 141 142 143 144 145
bool KisWelcomePageWidget::isDevelopmentBuild()
{
    // dev builds contain GIT hash in it and the word git
    // stable versions do not contain this
    return qApp->applicationVersion().contains("git");
}

146 147 148 149 150 151 152 153 154
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);
155

156 157 158 159
        // QColor.name() turns it into a hex/web format
        QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 2px dotted ").append(blendedColor.name()).append(" }") ;
        dropFrameBorder->setStyleSheet(dropFrameStyle);
    }
160 161
}

162
void KisWelcomePageWidget::slotUpdateThemeColors()
163
{
164

165 166
    textColor = qApp->palette().color(QPalette::Text);
    backgroundColor = qApp->palette().color(QPalette::Background);
167 168

    // make the welcome screen labels a subtle color so it doesn't clash with the main UI elements
169 170
    blendedColor = KritaUtils::blendColors(textColor, backgroundColor, 0.8);
    blendedStyle = QString("color: ").append(blendedColor.name());
171 172 173 174 175 176


    // what labels to change the color...
    startTitleLabel->setStyleSheet(blendedStyle);
    recentDocumentsLabel->setStyleSheet(blendedStyle);
    helpTitleLabel->setStyleSheet(blendedStyle);
177 178
    newFileLinkShortcut->setStyleSheet(blendedStyle);
    openFileShortcut->setStyleSheet(blendedStyle);
179
    clearRecentFilesLink->setStyleSheet(blendedStyle);
180 181 182 183
    recentDocumentsListView->setStyleSheet(blendedStyle);

    newFileLink->setStyleSheet(blendedStyle);
    openFileLink->setStyleSheet(blendedStyle);
184

185 186

    // giving the drag area messaging a dotted border
187
    QString dottedBorderStyle = QString("border: 2px dotted ").append(blendedColor.name()).append("; color:").append(blendedColor.name()).append( ";");
188 189 190 191 192
    dragImageHereLabel->setStyleSheet(dottedBorderStyle);


    // make drop area QFrame have a dotted line
    dropFrameBorder->setObjectName("dropAreaIndicator");
193
    QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 4px dotted ").append(blendedColor.name()).append("}");
194 195 196 197 198 199 200 201 202 203
    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"));
204 205 206 207 208


    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
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
    userCommunityLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://forum.kde.org/viewforum.php?f=136&" + analyticsString + "user-community" + "\">")
                               .append(i18n("User Community")).append("</a>"));

    gettingStartedLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://docs.krita.org/en/user_manual/getting_started.html?" + analyticsString + "getting-started" + "\">")
                                .append(i18n("Getting Started")).append("</a>"));

    manualLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://docs.krita.org?" + analyticsString + "documentation-site" + "\">")
                        .append(i18n("User Manual")).append("</a>"));

    supportKritaLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://krita.org/en/support-us/donations?" + analyticsString + "donations" + "\">")
                              .append(i18n("Support Krita")).append("</a>"));

    kritaWebsiteLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://www.krita.org?" + analyticsString + "marketing-site" + "\">")
                              .append(i18n("Krita Website")).append("</a>"));

    sourceCodeLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://invent.kde.org/kde/krita?" + analyticsString + "source-code" + "\">")
                            .append(i18n("Source Code")).append("</a>"));

    poweredByKDELink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://userbase.kde.org/What_is_KDE?" + analyticsString + "what-is-kde" + "\">")
                              .append(i18n("Powered by KDE")).append("</a>"));
229

230 231 232 233

    slotUpdateVersionMessage(); // text set from RSS feed


234 235 236
    // re-populate recent files since they might have themed icons
    populateRecentDocuments();

237
}
238

239
void KisWelcomePageWidget::populateRecentDocuments()
240
{
241 242
    m_recentFilesModel.clear(); // clear existing data before it gets re-populated

243
    // grab recent files data
244
    int numRecentFiles = m_mainWindow->recentFilesUrls().length() > 5 ? 5 : m_mainWindow->recentFilesUrls().length(); // grab at most 5
245

246
    for (int i = 0; i < numRecentFiles; i++ ) {
247

248 249
        QStandardItem *recentItem = new QStandardItem(1,2); // 1 row, 1 column
        recentItem->setIcon(KisIconUtils::loadIcon("document-export"));
250

251
        QString recentFileUrlPath = m_mainWindow->recentFilesUrls().at(i).toLocalFile();
252
        QString fileName = recentFileUrlPath.split("/").last();
253

254 255
        if (m_thumbnailMap.contains(recentFileUrlPath)) {
            recentItem->setIcon(m_thumbnailMap[recentFileUrlPath]);
256 257
        }
        else {
258 259 260 261
            QFileInfo fi(recentFileUrlPath);

            if (fi.exists()) {
                if (fi.suffix() == "ora" || fi.suffix() == "kra") {
262

263
                    QScopedPointer<KoStore> store(KoStore::createStore(recentFileUrlPath, KoStore::Read));
264
                    if (store) {
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
                        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)));
                            }
281
                        }
282 283
                    }
                }
284
                else if (fi.suffix() == "tiff" || fi.suffix() == "tif") {
285 286 287 288 289 290 291 292 293
                    // Workaround for a bug in Qt tiff QImageIO plugin
                    QScopedPointer<KisDocument> doc;
                    doc.reset(KisPart::instance()->createDocument());
                    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()))));
                    }
                }
294
                else {
295
                    QImage img;
296
                    img.setDevicePixelRatio(devicePixelRatioF());
297
                    img.load(recentFileUrlPath);
298 299 300
                    if (!img.isNull()) {
                        recentItem->setIcon(QIcon(QPixmap::fromImage(img.scaledToWidth(48))));
                    }
301
                }
302
                m_thumbnailMap[recentFileUrlPath] = recentItem->icon();
303 304
            }
        }
305

306 307 308
        // set the recent object with the data
        recentItem->setText(fileName); // what to display for the item
        recentItem->setToolTip(recentFileUrlPath);
309
        m_recentFilesModel.appendRow(recentItem);
310
    }
311

312
    // hide clear and Recent files title if there are none
313
    bool hasRecentFiles = m_mainWindow->recentFilesUrls().length() > 0;
314

315 316 317
    recentDocumentsLabel->setVisible(hasRecentFiles);
    clearRecentFilesLink->setVisible(hasRecentFiles);

318 319
    recentDocumentsListView->setIconSize(QSize(48, 48));
    recentDocumentsListView->setModel(&m_recentFilesModel);
320 321
}

322 323 324 325 326
void KisWelcomePageWidget::slotUpdateVersionMessage()
{
    // find out if we need an update...or if this is a development version
    // TODO: show app version if dev version. Show version number available and provide link
    QString versionText = "";
327 328
    if (isDevelopmentBuild()) {
        // Development build
329 330
        versionText = i18n("DEV BUILD");
        versionNotificationLabel->setVisible(true);
331
        versionNotificationLabel->setText(versionText); // no link
332
    } else if (newsWidget->hasUpdateAvailable()) {
333 334 335
        // Version update available
        versionText = QString("Krita " + newsWidget->versionNumber() + " ")
                .append(i18n("Available!"));
336
        versionNotificationLabel->setVisible(true);
337 338 339 340 341 342 343
        versionNotificationLabel->setText(QString("<a style=\"color: " +
                                                  blendedColor.name() +
                                                  " \" href=\"  " +
                                                  newsWidget->versionLink() +
                                                  analyticsString + "version-update" + "\">")
                                  .append(versionText).append("</a>"));

344
    } else {
345
        // message needed... exit
346 347 348 349 350 351 352 353 354 355
        versionNotificationLabel->setVisible(false);
        return;
    }

    if (!blendedStyle.isNull()) {
        versionNotificationLabel->setStyleSheet(blendedStyle);
    }

}

356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407
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();
}
408

409 410 411
void KisWelcomePageWidget::recentDocumentClicked(QModelIndex index)
{
    QString fileUrl = index.data(Qt::ToolTipRole).toString();
412
    m_mainWindow->openDocument(QUrl::fromLocalFile(fileUrl), KisMainWindow::None );
413
}
414 415 416 417


void KisWelcomePageWidget::slotNewFileClicked()
{
418
    m_mainWindow->slotFileNew();
419 420 421 422
}

void KisWelcomePageWidget::slotOpenFileClicked()
{
423
    m_mainWindow->slotFileOpen();
424 425
}

426 427
void KisWelcomePageWidget::slotClearRecentFiles()
{
428
    m_mainWindow->clearRecentFiles();
429 430
    populateRecentDocuments();
}