gettingstartedwelcomepage.cpp 15.2 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8
9
10
11
12
13
14
** 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
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15
16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
18
19
20
21
22
23
24
25
** Alternatively, 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, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
26
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
29
30
31
32

#include "gettingstartedwelcomepage.h"

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

35
36
#include "qtsupportconstants.h"

37
38
39
#include <utils/pathchooser.h>
#include <utils/fileutils.h>

40
41
42
43
#ifdef Q_OS_WIN
#include <utils/winutils.h>
#endif

44
#include <coreplugin/coreconstants.h>
45
#include <coreplugin/coreplugin.h>
46
#include <coreplugin/documentmanager.h>
47
#include <coreplugin/icore.h>
48
#include <coreplugin/helpmanager.h>
49
#include <coreplugin/modemanager.h>
50
#include <projectexplorer/projectexplorer.h>
51
52
53
#include <projectexplorer/session.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectnodes.h>
54

55
56
57
#include <QMutex>
#include <QThread>
#include <QMutexLocker>
58
#include <QPointer>
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <QWaitCondition>
#include <QDir>
#include <QBuffer>
#include <QGraphicsProxyWidget>
#include <QScrollBar>
#include <QSortFilterProxyModel>
#include <QImage>
#include <QImageReader>
#include <QGridLayout>
#include <QLabel>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QMessageBox>
#include <QApplication>
#include <QDeclarativeImageProvider>
#include <QDeclarativeEngine>
#include <QDeclarativeContext>
#include <QDesktopServices>
77

78
79
using namespace Utils;

80
81
82
namespace QtSupport {
namespace Internal {

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
149
150
151
152
153
154
155
156
157
        if (m_shutdown)
            return;

        QMutexLocker lock(&m_mutex);

        if (Core::HelpManager::instance()) {
            QMutexLocker dataLock(&m_dataMutex);
            m_fetchedData = Core::HelpManager::instance()->fileData(url);
        }
        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
172
173
174
175
176
177
178
class HelpImageProvider : public QDeclarativeImageProvider
{
public:
    HelpImageProvider()
        : QDeclarativeImageProvider(QDeclarativeImageProvider::Image)
    {
    }

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

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

187
188
        if (!m_fetcher.asynchronousFetchData(url))
            return QImage();
189
190

        if (m_fetcher.data().isEmpty())
191
            return QImage();
192
193
        QByteArray data = m_fetcher.data();
        QBuffer imgBuffer(&data);
194
195
196
        imgBuffer.open(QIODevice::ReadOnly);
        QImageReader reader(&imgBuffer);
        QImage img = reader.read();
197
198

        m_fetcher.clearData();
199
        return ScreenshotCropper::croppedImage(img, id, requestedSize);
200
    }
201
202
203
private:
    Fetcher m_fetcher;
    QMutex m_mutex;
204
205
};

206
207
208
209
210
211
212
GettingStartedWelcomePage::GettingStartedWelcomePage() : m_engine(0)
{

}

QUrl GettingStartedWelcomePage::pageLocation() const
{
213
214
215
216
217
218
219
    QString resourcePath = Core::ICore::resourcePath();
#ifdef Q_OS_WIN
    // normalize paths so QML doesn't freak out if it's wrongly capitalized on Windows
    resourcePath = Utils::normalizePathName(resourcePath);
#endif

    return QUrl::fromLocalFile(resourcePath + QLatin1String("/welcomescreen/gettingstarted.qml"));
220
221
222
223
224
225
226
227
228
}

QString GettingStartedWelcomePage::title() const
{
    return tr("Getting Started");
}

int GettingStartedWelcomePage::priority() const
{
229
    return 4;
230
231
232
233
234
235
236
}

void GettingStartedWelcomePage::facilitateQml(QDeclarativeEngine *engine)
{
    m_engine = engine;
}

237
238
239
240
241
GettingStartedWelcomePage::Id GettingStartedWelcomePage::id() const
{
    return GettingStarted;
}

242
ExamplesWelcomePage::ExamplesWelcomePage()
243
244
245
246
    : m_engine(0),  m_showExamples(false)
{
}

247
void ExamplesWelcomePage::setShowExamples(bool showExamples)
248
249
250
251
{
    m_showExamples = showExamples;
}

252
QString ExamplesWelcomePage::title() const
253
{
254
    if (m_showExamples)
255
        return tr("Examples");
256
    else
257
        return tr("Tutorials");
258
259
}

260
 int ExamplesWelcomePage::priority() const
261
262
263
264
 {
     if (m_showExamples)
         return 30;
     else
265
         return 40;
266
267
 }

268
 bool ExamplesWelcomePage::hasSearchBar() const
269
270
271
272
273
274
275
 {
     if (m_showExamples)
         return true;
     else
         return false;
 }

276
QUrl ExamplesWelcomePage::pageLocation() const
277
{
278
279
280
281
282
    QString resourcePath = Core::ICore::resourcePath();
#ifdef Q_OS_WIN
    // normalize paths so QML doesn't freak out if it's wrongly capitalized on Windows
    resourcePath = Utils::normalizePathName(resourcePath);
#endif
283
    if (m_showExamples)
284
        return QUrl::fromLocalFile(resourcePath + QLatin1String("/welcomescreen/examples.qml"));
285
    else
286
        return QUrl::fromLocalFile(resourcePath + QLatin1String("/welcomescreen/tutorials.qml"));
287
288
}

289
void ExamplesWelcomePage::facilitateQml(QDeclarativeEngine *engine)
290
291
{
    m_engine = engine;
292
    m_engine->addImageProvider(QLatin1String("helpimage"), new HelpImageProvider);
293
    connect (examplesModel(), SIGNAL(tagsUpdated()), SLOT(updateTagsModel()));
294
    ExamplesListModelFilter *proxy = new ExamplesListModelFilter(examplesModel(), this);
295

296
297
298
299
300
    proxy->setDynamicSortFilter(true);
    proxy->sort(0);
    proxy->setFilterCaseSensitivity(Qt::CaseInsensitive);

    QDeclarativeContext *rootContenxt = m_engine->rootContext();
301
302
303
304
305
306
    if (m_showExamples) {
        proxy->setShowTutorialsOnly(false);
        rootContenxt->setContextProperty(QLatin1String("examplesModel"), proxy);
    } else {
        rootContenxt->setContextProperty(QLatin1String("tutorialsModel"), proxy);
    }
307
    rootContenxt->setContextProperty(QLatin1String("gettingStarted"), this);
308
309
}

310
311
312
313
314
ExamplesWelcomePage::Id ExamplesWelcomePage::id() const
{
    return m_showExamples ? Examples : Tutorials;
}

315
void ExamplesWelcomePage::openSplitHelp(const QUrl &help)
316
{
hjk's avatar
hjk committed
317
    Core::ICore::helpManager()->handleHelpRequest(help.toString()+QLatin1String("?view=split"));
318
319
}

320
321
void ExamplesWelcomePage::openHelp(const QUrl &help)
{
hjk's avatar
hjk committed
322
    Core::ICore::helpManager()->handleHelpRequest(help.toString());
323
324
325
326
327
328
329
330
}

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

QStringList ExamplesWelcomePage::tagList() const
331
{
332
    return examplesModel()->tags();
333
334
}

335
QString ExamplesWelcomePage::copyToAlternativeLocation(const QFileInfo& proFileInfo, QStringList &filesToOpen, const QStringList& dependencies)
336
337
{
    const QString projectDir = proFileInfo.canonicalPath();
hjk's avatar
hjk committed
338
    QDialog d(Core::ICore::mainWindow());
339
340
341
342
    QGridLayout *lay = new QGridLayout(&d);
    QLabel *descrLbl = new QLabel;
    d.setWindowTitle(tr("Copy Project to writable Location?"));
    descrLbl->setTextFormat(Qt::RichText);
343
344
345
346
    descrLbl->setWordWrap(false);
    const QString nativeProjectDir = QDir::toNativeSeparators(projectDir);
    descrLbl->setText(QString::fromLatin1("<blockquote>%1</blockquote>").arg(nativeProjectDir));
    descrLbl->setMinimumWidth(descrLbl->sizeHint().width());
347
348
349
350
351
352
353
    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>")
354
                      .arg(nativeProjectDir));
355
356
    lay->addWidget(descrLbl, 0, 0, 1, 2);
    QLabel *txt = new QLabel(tr("&Location:"));
357
    PathChooser *chooser = new PathChooser;
358
    txt->setBuddy(chooser);
359
    chooser->setExpectedKind(PathChooser::ExistingDirectory);
hjk's avatar
hjk committed
360
    QSettings *settings = Core::ICore::settings();
361
362
    chooser->setPath(settings->value(QString::fromLatin1(C_FALLBACK_ROOT),
                                     Core::DocumentManager::projectsDirectory()).toString());
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
    lay->addWidget(txt, 1, 0);
    lay->addWidget(chooser, 1, 1);
    QDialogButtonBox *bb = new QDialogButtonBox;
    connect(bb, SIGNAL(accepted()), &d, SLOT(accept()));
    connect(bb, SIGNAL(rejected()), &d, SLOT(reject()));
    QPushButton *copyBtn = bb->addButton(tr("&Copy Project and Open"), QDialogButtonBox::AcceptRole);
    copyBtn->setDefault(true);
    bb->addButton(tr("&Keep Project and Open"), QDialogButtonBox::RejectRole);
    lay->addWidget(bb, 2, 0, 1, 2);
    connect(chooser, SIGNAL(validChanged(bool)), copyBtn, SLOT(setEnabled(bool)));
    if (d.exec() == QDialog::Accepted) {
        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
380
            QMessageBox::warning(Core::ICore::mainWindow(), tr("Cannot Use Location"),
381
382
383
384
385
386
                                 tr("The specified location already exists. "
                                    "Please specify a valid location."),
                                 QMessageBox::Ok, QMessageBox::NoButton);
            return QString();
        } else {
            QString error;
387
            QString targetDir = destBaseDir + QLatin1Char('/') + exampleDirName;
388
389
            if (FileUtils::copyRecursively(FileName::fromString(projectDir),
                    FileName::fromString(targetDir), &error)) {
390
                // set vars to new location
391
392
                const QStringList::Iterator end = filesToOpen.end();
                for (QStringList::Iterator it = filesToOpen.begin(); it != end; ++it)
393
394
                    it->replace(projectDir, targetDir);

395
                foreach (const QString &dependency, dependencies) {
396
397
398
399
                    FileName targetFile = FileName::fromString(targetDir);
                    targetFile.appendPath(QDir(dependency).dirName());
                    if (!FileUtils::copyRecursively(FileName::fromString(dependency), targetFile,
                            &error)) {
hjk's avatar
hjk committed
400
                        QMessageBox::warning(Core::ICore::mainWindow(), tr("Cannot Copy Project"), error);
401
402
403
404
405
                        // do not fail, just warn;
                    }
                }


406
                return targetDir + QLatin1Char('/') + proFileInfo.fileName();
407
            } else {
hjk's avatar
hjk committed
408
                QMessageBox::warning(Core::ICore::mainWindow(), tr("Cannot Copy Project"), error);
409
410
411
412
413
414
415
416
            }

        }
    }
    return QString();

}

417
void ExamplesWelcomePage::openProject(const QString &projectFile, const QStringList &additionalFilesToOpen,
418
                                            const QUrl &help, const QStringList &dependencies, const QStringList &platforms)
419
{
420
421
422
423
424
425
    QString proFile = projectFile;
    if (proFile.isEmpty())
        return;

    QStringList filesToOpen = additionalFilesToOpen;
    QFileInfo proFileInfo(proFile);
426
427
428
    if (!proFileInfo.exists())
        return;

429
430
    // If the Qt is a distro Qt on Linux, it will not be writable, hence compilation will fail
    if (!proFileInfo.isWritable())
431
        proFile = copyToAlternativeLocation(proFileInfo, filesToOpen, dependencies);
432

433
    // don't try to load help and files if loading the help request is being cancelled
434
    QString errorMessage;
435
    ProjectExplorer::ProjectExplorerPlugin *peplugin = ProjectExplorer::ProjectExplorerPlugin::instance();
436
437
438
    if (proFile.isEmpty())
        return;
    if (ProjectExplorer::Project *project = peplugin->openProject(proFile, &errorMessage)) {
hjk's avatar
hjk committed
439
        Core::ICore::openFiles(filesToOpen);
440
441
        if (project->needsConfiguration())
            project->configureAsExampleProject(platforms);
442
        Core::ModeManager::activateModeType(Core::Constants::MODE_EDIT_TYPE);
443
444
        if (help.isValid())
            Core::ICore::helpManager()->handleHelpRequest(help.toString() + QLatin1String("?view=split"));
445
    }
446
    if (!errorMessage.isEmpty())
Friedemann Kleint's avatar
Friedemann Kleint committed
447
        QMessageBox::critical(Core::ICore::mainWindow(), tr("Failed to Open Project"), errorMessage);
448
449
}

450
void ExamplesWelcomePage::updateTagsModel()
451
{
452
    m_engine->rootContext()->setContextProperty(QLatin1String("tagsList"), examplesModel()->tags());
453
454
455
    emit tagsUpdated();
}

456
ExamplesListModel *ExamplesWelcomePage::examplesModel() const
457
458
459
460
{
    if (examplesModelStatic())
        return examplesModelStatic().data();

461
    examplesModelStatic() = new ExamplesListModel(const_cast<ExamplesWelcomePage*>(this));
462
463
464
    return examplesModelStatic().data();
}

465
466
467
} // namespace Internal
} // namespace QtSupport

468
#include "gettingstartedwelcomepage.moc"