gettingstartedwelcomepage.cpp 14.9 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
Eike Ziller's avatar
Eike Ziller committed
3
4
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8
9
10
11
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
Eike Ziller's avatar
Eike Ziller committed
12
13
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
Eike Ziller's avatar
Eike Ziller committed
14
** use the contact form at http://www.qt.io/contact-us.
15
16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18
19
20
21
22
23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
**
Eike Ziller's avatar
Eike Ziller committed
25
26
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
27
28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
30
31
32
33

#include "gettingstartedwelcomepage.h"

#include "exampleslistmodel.h"
34
#include "screenshotcropper.h"
35

36
#include <utils/pathchooser.h>
37
38
#include <utils/winutils.h>

39
#include <coreplugin/coreconstants.h>
40
#include <coreplugin/documentmanager.h>
41
#include <coreplugin/icore.h>
42
#include <coreplugin/helpmanager.h>
43
#include <coreplugin/modemanager.h>
44
#include <projectexplorer/projectexplorer.h>
45
#include <projectexplorer/project.h>
46

47
48
49
#include <QMutex>
#include <QThread>
#include <QMutexLocker>
50
#include <QPointer>
51
52
53
54
55
56
57
58
59
60
61
#include <QWaitCondition>
#include <QDir>
#include <QBuffer>
#include <QImage>
#include <QImageReader>
#include <QGridLayout>
#include <QLabel>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QMessageBox>
#include <QApplication>
62
63
64
#include <QQuickImageProvider>
#include <QQmlEngine>
#include <QQmlContext>
65
#include <QDesktopServices>
66

67
68
using namespace Utils;

69
70
71
namespace QtSupport {
namespace Internal {

72
73
74
75
76
77
78
79
80
81
82
class ExampleDialog : public QDialog
{
    Q_OBJECT
 public:
    enum ResultCode { Copy = QDialog::Accepted + 1, Keep };
    ExampleDialog(QWidget *parent = 0) : QDialog(parent) {};
 private slots:
    void handleCopyClicked() { done(Copy); };
    void handleKeepClicked() { done(Keep); };
};

83
84
const char C_FALLBACK_ROOT[] = "ProjectsFallbackRoot";

85
QPointer<ExamplesListModel> &examplesModelStatic()
86
{
87
    static QPointer<ExamplesListModel> s_examplesModel;
88
89
    return s_examplesModel;
}
90

91
92
93
class Fetcher : public QObject
{
    Q_OBJECT
hjk's avatar
hjk committed
94

95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
public:
    Fetcher() : QObject(),  m_shutdown(false)
    {
        connect(Core::ICore::instance(), SIGNAL(coreAboutToClose()), this, SLOT(shutdown()));
    }

    void wait()
    {
        if (QThread::currentThread() == QApplication::instance()->thread())
            return;
        if (m_shutdown)
            return;

        m_waitcondition.wait(&m_mutex, 4000);
    }

    QByteArray data()
    {
        QMutexLocker lock(&m_dataMutex);
        return m_fetchedData;
    }

    void clearData()
    {
        QMutexLocker lock(&m_dataMutex);
        m_fetchedData.clear();
    }

    bool asynchronousFetchData(const QUrl &url)
    {
        QMutexLocker lock(&m_mutex);

        if (!QMetaObject::invokeMethod(this,
                                       "fetchData",
                                       Qt::AutoConnection,
                                       Q_ARG(QUrl, url))) {
            return false;
        }

        wait();
        return true;
    }


139
140
141
public slots:
    void fetchData(const QUrl &url)
    {
142
143
144
145
146
147
148
        if (m_shutdown)
            return;

        QMutexLocker lock(&m_mutex);

        if (Core::HelpManager::instance()) {
            QMutexLocker dataLock(&m_dataMutex);
149
            m_fetchedData = Core::HelpManager::fileData(url);
150
151
152
153
154
155
156
157
        }
        m_waitcondition.wakeAll();
    }

private slots:
    void shutdown()
    {
        m_shutdown = true;
158
159
160
    }

public:
161
162
163
164
165
166
167
168
    QByteArray m_fetchedData;
    QWaitCondition m_waitcondition;
    QMutex m_mutex;     //This mutex synchronises the wait() and wakeAll() on the wait condition.
                        //We have to ensure that wakeAll() is called always after wait().

    QMutex m_dataMutex; //This mutex synchronises the access of m_fectedData.
                        //If the wait condition timeouts we otherwise get a race condition.
    bool m_shutdown;
169
170
};

171
class HelpImageProvider : public QQuickImageProvider
172
173
174
{
public:
    HelpImageProvider()
175
        : QQuickImageProvider(QQuickImageProvider::Image)
176
177
178
    {
    }

179
    // gets called by declarative in separate thread
180
181
    QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize)
    {
182
        QMutexLocker lock(&m_mutex);
183

184
        QUrl url = QUrl::fromEncoded(id.toLatin1());
185
186


187
188
189
190
191
        if (!m_fetcher.asynchronousFetchData(url) || m_fetcher.data().isEmpty()) {
            if (size) {
                size->setWidth(0);
                size->setHeight(0);
            }
192
            return QImage();
193
194
        }

195
196
        QByteArray data = m_fetcher.data();
        QBuffer imgBuffer(&data);
197
198
199
        imgBuffer.open(QIODevice::ReadOnly);
        QImageReader reader(&imgBuffer);
        QImage img = reader.read();
200
201

        m_fetcher.clearData();
202
203
204
205
206
        img = ScreenshotCropper::croppedImage(img, id, requestedSize);
        if (size)
            *size = img.size();
        return img;

207
    }
208
209
210
private:
    Fetcher m_fetcher;
    QMutex m_mutex;
211
212
};

213
ExamplesWelcomePage::ExamplesWelcomePage()
214
215
216
217
    : m_engine(0),  m_showExamples(false)
{
}

218
void ExamplesWelcomePage::setShowExamples(bool showExamples)
219
220
221
222
{
    m_showExamples = showExamples;
}

223
QString ExamplesWelcomePage::title() const
224
{
225
    if (m_showExamples)
226
        return tr("Examples");
227
    else
228
        return tr("Tutorials");
229
230
}

231
 int ExamplesWelcomePage::priority() const
232
233
234
235
 {
     if (m_showExamples)
         return 30;
     else
236
         return 40;
237
238
 }

239
 bool ExamplesWelcomePage::hasSearchBar() const
240
241
242
243
244
245
246
 {
     if (m_showExamples)
         return true;
     else
         return false;
 }

247
QUrl ExamplesWelcomePage::pageLocation() const
248
{
249
    // normalize paths so QML doesn't freak out if it's wrongly capitalized on Windows
Orgad Shaneh's avatar
Orgad Shaneh committed
250
    const QString resourcePath = Utils::FileUtils::normalizePathName(Core::ICore::resourcePath());
251
    if (m_showExamples)
252
        return QUrl::fromLocalFile(resourcePath + QLatin1String("/welcomescreen/examples.qml"));
253
    else
254
        return QUrl::fromLocalFile(resourcePath + QLatin1String("/welcomescreen/tutorials.qml"));
255
256
}

257
void ExamplesWelcomePage::facilitateQml(QQmlEngine *engine)
258
259
{
    m_engine = engine;
260
    m_engine->addImageProvider(QLatin1String("helpimage"), new HelpImageProvider);
261
    ExamplesListModelFilter *proxy = new ExamplesListModelFilter(examplesModel(), this);
262

263
264
265
266
    proxy->setDynamicSortFilter(true);
    proxy->sort(0);
    proxy->setFilterCaseSensitivity(Qt::CaseInsensitive);

267
    QQmlContext *rootContenxt = m_engine->rootContext();
268
269
270
    if (m_showExamples) {
        proxy->setShowTutorialsOnly(false);
        rootContenxt->setContextProperty(QLatin1String("examplesModel"), proxy);
271
        rootContenxt->setContextProperty(QLatin1String("exampleSetModel"), proxy->exampleSetModel());
272
273
274
    } else {
        rootContenxt->setContextProperty(QLatin1String("tutorialsModel"), proxy);
    }
275
    rootContenxt->setContextProperty(QLatin1String("gettingStarted"), this);
276
277
}

278
Core::Id ExamplesWelcomePage::id() const
279
{
280
    return m_showExamples ? "Examples" : "Tutorials";
281
282
}

283
void ExamplesWelcomePage::openHelpInExtraWindow(const QUrl &help)
284
{
285
    Core::HelpManager::handleHelpRequest(help, Core::HelpManager::ExternalHelpAlways);
286
287
}

288
289
void ExamplesWelcomePage::openHelp(const QUrl &help)
{
290
    Core::HelpManager::handleHelpRequest(help, Core::HelpManager::HelpModeAlways);
291
292
293
294
295
296
297
}

void ExamplesWelcomePage::openUrl(const QUrl &url)
{
    QDesktopServices::openUrl(url);
}

298
QString ExamplesWelcomePage::copyToAlternativeLocation(const QFileInfo& proFileInfo, QStringList &filesToOpen, const QStringList& dependencies)
299
300
{
    const QString projectDir = proFileInfo.canonicalPath();
301
    ExampleDialog d(Core::ICore::mainWindow());
302
303
304
305
    QGridLayout *lay = new QGridLayout(&d);
    QLabel *descrLbl = new QLabel;
    d.setWindowTitle(tr("Copy Project to writable Location?"));
    descrLbl->setTextFormat(Qt::RichText);
306
307
308
309
    descrLbl->setWordWrap(false);
    const QString nativeProjectDir = QDir::toNativeSeparators(projectDir);
    descrLbl->setText(QString::fromLatin1("<blockquote>%1</blockquote>").arg(nativeProjectDir));
    descrLbl->setMinimumWidth(descrLbl->sizeHint().width());
310
311
312
313
314
315
316
    descrLbl->setWordWrap(true);
    descrLbl->setText(tr("<p>The project you are about to open is located in the "
                         "write-protected location:</p><blockquote>%1</blockquote>"
                         "<p>Please select a writable location below and click \"Copy Project and Open\" "
                         "to open a modifiable copy of the project or click \"Keep Project and Open\" "
                         "to open the project in location.</p><p><b>Note:</b> You will not "
                         "be able to alter or compile your project in the current location.</p>")
317
                      .arg(nativeProjectDir));
318
319
    lay->addWidget(descrLbl, 0, 0, 1, 2);
    QLabel *txt = new QLabel(tr("&Location:"));
320
    PathChooser *chooser = new PathChooser;
321
    txt->setBuddy(chooser);
322
    chooser->setExpectedKind(PathChooser::ExistingDirectory);
323
    chooser->setHistoryCompleter(QLatin1String("Qt.WritableExamplesDir.History"));
hjk's avatar
hjk committed
324
    QSettings *settings = Core::ICore::settings();
325
326
    chooser->setPath(settings->value(QString::fromLatin1(C_FALLBACK_ROOT),
                                     Core::DocumentManager::projectsDirectory()).toString());
327
328
329
330
    lay->addWidget(txt, 1, 0);
    lay->addWidget(chooser, 1, 1);
    QDialogButtonBox *bb = new QDialogButtonBox;
    QPushButton *copyBtn = bb->addButton(tr("&Copy Project and Open"), QDialogButtonBox::AcceptRole);
331
    connect(copyBtn, SIGNAL(released()), &d, SLOT(handleCopyClicked()));
332
    copyBtn->setDefault(true);
333
334
    QPushButton *keepBtn = bb->addButton(tr("&Keep Project and Open"), QDialogButtonBox::RejectRole);
    connect(keepBtn, SIGNAL(released()), &d, SLOT(handleKeepClicked()));
335
336
    lay->addWidget(bb, 2, 0, 1, 2);
    connect(chooser, SIGNAL(validChanged(bool)), copyBtn, SLOT(setEnabled(bool)));
337
338
    int code = d.exec();
    if (code == ExampleDialog::Copy) {
339
340
341
342
343
344
        QString exampleDirName = proFileInfo.dir().dirName();
        QString destBaseDir = chooser->path();
        settings->setValue(QString::fromLatin1(C_FALLBACK_ROOT), destBaseDir);
        QDir toDirWithExamplesDir(destBaseDir);
        if (toDirWithExamplesDir.cd(exampleDirName)) {
            toDirWithExamplesDir.cdUp(); // step out, just to not be in the way
hjk's avatar
hjk committed
345
            QMessageBox::warning(Core::ICore::mainWindow(), tr("Cannot Use Location"),
346
347
348
349
350
351
                                 tr("The specified location already exists. "
                                    "Please specify a valid location."),
                                 QMessageBox::Ok, QMessageBox::NoButton);
            return QString();
        } else {
            QString error;
352
            QString targetDir = destBaseDir + QLatin1Char('/') + exampleDirName;
353
354
            if (FileUtils::copyRecursively(FileName::fromString(projectDir),
                    FileName::fromString(targetDir), &error)) {
355
                // set vars to new location
356
357
                const QStringList::Iterator end = filesToOpen.end();
                for (QStringList::Iterator it = filesToOpen.begin(); it != end; ++it)
358
359
                    it->replace(projectDir, targetDir);

360
                foreach (const QString &dependency, dependencies) {
361
362
363
364
                    FileName targetFile = FileName::fromString(targetDir);
                    targetFile.appendPath(QDir(dependency).dirName());
                    if (!FileUtils::copyRecursively(FileName::fromString(dependency), targetFile,
                            &error)) {
hjk's avatar
hjk committed
365
                        QMessageBox::warning(Core::ICore::mainWindow(), tr("Cannot Copy Project"), error);
366
367
368
369
370
                        // do not fail, just warn;
                    }
                }


371
                return targetDir + QLatin1Char('/') + proFileInfo.fileName();
372
            } else {
hjk's avatar
hjk committed
373
                QMessageBox::warning(Core::ICore::mainWindow(), tr("Cannot Copy Project"), error);
374
375
376
377
            }

        }
    }
378
379
    if (code == ExampleDialog::Keep)
        return proFileInfo.absoluteFilePath();
380
381
382
383
    return QString();

}

384
385
386
387
388
389
void ExamplesWelcomePage::openProject(const QString &projectFile,
                                      const QStringList &additionalFilesToOpen,
                                      const QString &mainFile,
                                      const QUrl &help,
                                      const QStringList &dependencies,
                                      const QStringList &)
390
{
391
392
393
394
395
    QString proFile = projectFile;
    if (proFile.isEmpty())
        return;

    QStringList filesToOpen = additionalFilesToOpen;
396
397
398
399
400
401
    if (!mainFile.isEmpty()) {
        // ensure that the main file is opened on top (i.e. opened last)
        filesToOpen.removeAll(mainFile);
        filesToOpen.append(mainFile);
    }

402
    QFileInfo proFileInfo(proFile);
403
404
405
    if (!proFileInfo.exists())
        return;

406
    QFileInfo pathInfo(proFileInfo.path());
407
    // If the Qt is a distro Qt on Linux, it will not be writable, hence compilation will fail
408
409
410
    if (!proFileInfo.isWritable()
            || !pathInfo.isWritable() /* path of .pro file */
            || !QFileInfo(pathInfo.path()).isWritable() /* shadow build directory */) {
411
        proFile = copyToAlternativeLocation(proFileInfo, filesToOpen, dependencies);
412
    }
413

414
    // don't try to load help and files if loading the help request is being cancelled
415
    QString errorMessage;
416
417
    if (proFile.isEmpty())
        return;
418
    if (ProjectExplorer::ProjectExplorerPlugin::instance()->openProject(proFile, &errorMessage)) {
hjk's avatar
hjk committed
419
        Core::ICore::openFiles(filesToOpen);
Eike Ziller's avatar
Eike Ziller committed
420
        Core::ModeManager::activateMode(Core::Constants::MODE_EDIT);
421
        if (help.isValid())
422
            openHelpInExtraWindow(help.toString());
423
        Core::ModeManager::activateMode(ProjectExplorer::Constants::MODE_SESSION);
424
    }
425
    if (!errorMessage.isEmpty())
Friedemann Kleint's avatar
Friedemann Kleint committed
426
        QMessageBox::critical(Core::ICore::mainWindow(), tr("Failed to Open Project"), errorMessage);
427
428
}

429
ExamplesListModel *ExamplesWelcomePage::examplesModel() const
430
431
432
433
{
    if (examplesModelStatic())
        return examplesModelStatic().data();

434
    examplesModelStatic() = new ExamplesListModel(const_cast<ExamplesWelcomePage*>(this));
435
436
437
    return examplesModelStatic().data();
}

438
439
440
} // namespace Internal
} // namespace QtSupport

441
#include "gettingstartedwelcomepage.moc"