uicodemodelsupport.cpp 15.2 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
con's avatar
con committed
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 34
#include "uicodemodelsupport.h"

#include "qtkitinformation.h"

Tobias Hunger's avatar
Tobias Hunger committed
35 36
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
37
#include <coreplugin/idocument.h>
38
#include <cpptools/cppmodelmanager.h>
39
#include <projectexplorer/buildconfiguration.h>
Tobias Hunger's avatar
Tobias Hunger committed
40
#include <projectexplorer/buildmanager.h>
41
#include <projectexplorer/project.h>
Tobias Hunger's avatar
Tobias Hunger committed
42 43
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/session.h>
44
#include <projectexplorer/target.h>
45
#include <utils/algorithm.h>
Tobias Hunger's avatar
Tobias Hunger committed
46
#include <utils/qtcassert.h>
Friedemann Kleint's avatar
Friedemann Kleint committed
47

48 49
#include <QFile>
#include <QFileInfo>
50
#include <QLoggingCategory>
51

hjk's avatar
hjk committed
52
using namespace ProjectExplorer;
53
using namespace CPlusPlus;
54

Tobias Hunger's avatar
Tobias Hunger committed
55 56 57 58 59 60 61 62 63 64 65 66 67 68
// Test for form editor (loosely coupled)
static inline bool isFormWindowDocument(const QObject *o)
{
    return o && !qstrcmp(o->metaObject()->className(), "Designer::Internal::FormWindowFile");
}

// Return contents of form editor (loosely coupled)
static inline QString formWindowEditorContents(const QObject *editor)
{
    const QVariant contentV = editor->property("contents");
    QTC_ASSERT(contentV.isValid(), return QString());
    return contentV.toString();
}

69 70
namespace QtSupport {

71
UiCodeModelSupport::UiCodeModelSupport(CppTools::CppModelManager *modelmanager,
72
                                       ProjectExplorer::Project *project,
Tobias Hunger's avatar
Tobias Hunger committed
73
                                       const QString &uiFile,
74
                                       const QString &uiHeaderFile)
75 76
    : CppTools::AbstractEditorSupport(modelmanager),
      m_project(project),
Tobias Hunger's avatar
Tobias Hunger committed
77 78
      m_uiFileName(uiFile),
      m_headerFileName(uiHeaderFile),
79
      m_state(BARE)
80
{
81 82
    QLoggingCategory log("qtc.qtsupport.uicodemodelsupport");
    qCDebug(log) << "ctor UiCodeModelSupport for" << m_uiFileName << uiHeaderFile;
83 84
    connect(&m_process, SIGNAL(finished(int)),
            this, SLOT(finishProcess()));
85
    init();
86 87 88 89
}

UiCodeModelSupport::~UiCodeModelSupport()
{
90 91 92 93 94 95
    disconnect(&m_process, SIGNAL(finished(int)),
               this, SLOT(finishProcess()));
    m_process.kill();
    CppTools::CppModelManager::instance()->emitAbstractEditorSupportRemoved(m_headerFileName);
    QLoggingCategory log("qtc.qtsupport.uicodemodelsupport");
    qCDebug(log) << "dtor ~UiCodeModelSupport for" << m_uiFileName;
96 97
}

dt's avatar
dt committed
98
void UiCodeModelSupport::init() const
99
{
100
    QLoggingCategory log("qtc.qtsupport.uicodemodelsupport");
101 102
    if (m_state != BARE)
        return;
Tobias Hunger's avatar
Tobias Hunger committed
103 104
    QDateTime sourceTime = QFileInfo(m_uiFileName).lastModified();
    QFileInfo uiHeaderFileInfo(m_headerFileName);
105 106
    QDateTime uiHeaderTime = uiHeaderFileInfo.exists() ? uiHeaderFileInfo.lastModified() : QDateTime();
    if (uiHeaderTime.isValid() && (uiHeaderTime > sourceTime)) {
Tobias Hunger's avatar
Tobias Hunger committed
107
        QFile file(m_headerFileName);
108
        if (file.open(QFile::ReadOnly | QFile::Text)) {
109
            qCDebug(log) << "init: ui*h file is more recent then source file, using information from ui*h file" << m_headerFileName;
110 111 112
            QTextStream stream(&file);
            m_contents = stream.readAll().toUtf8();
            m_cacheTime = uiHeaderTime;
113
            m_state = FINISHED;
114
            notifyAboutUpdatedContents();
115 116 117 118
            return;
        }
    }

119
    qCDebug(log) << "ui*h file not found, or not recent enough, trying to create it on the fly";
Tobias Hunger's avatar
Tobias Hunger committed
120
    QFile file(m_uiFileName);
121
    if (file.open(QFile::ReadOnly | QFile::Text)) {
122 123 124
        QTextStream stream(&file);
        const QString contents = stream.readAll();
        if (runUic(contents)) {
125
            qCDebug(log) << "created on the fly";
126 127 128
            return;
        } else {
            // uic run was unsuccesfull
129
            qCDebug(log) << "uic run wasn't succesfull";
dt's avatar
dt committed
130
            m_cacheTime = QDateTime ();
131
            m_contents.clear();
132
            m_state = FINISHED;
133
            notifyAboutUpdatedContents();
134 135 136
            return;
        }
    } else {
137
        qCDebug(log) << "Could not open " << m_uiFileName << "needed for the cpp model";
138
        m_contents.clear();
139
        m_state = FINISHED;
140
        notifyAboutUpdatedContents();
141 142 143 144 145 146 147 148
    }
}

QByteArray UiCodeModelSupport::contents() const
{
    return m_contents;
}

Tobias Hunger's avatar
Tobias Hunger committed
149 150 151 152 153
QString UiCodeModelSupport::uiFileName() const
{
    return m_uiFileName;
}

154 155
QString UiCodeModelSupport::fileName() const
{
Tobias Hunger's avatar
Tobias Hunger committed
156
    return m_headerFileName;
157 158
}

Tobias Hunger's avatar
Tobias Hunger committed
159
void UiCodeModelSupport::setHeaderFileName(const QString &name)
160
{
Tobias Hunger's avatar
Tobias Hunger committed
161
    if (m_headerFileName == name && m_cacheTime.isValid())
162 163
        return;

164 165 166 167 168
    if (m_state == RUNNING) {
        m_state = ABORTING;
        m_process.kill();
        m_process.waitForFinished(3000);
    }
169

170 171
    QLoggingCategory log("qtc.qtsupport.uicodemodelsupport");
    qCDebug(log) << "UiCodeModelSupport::setFileName" << name;
172

Tobias Hunger's avatar
Tobias Hunger committed
173
    m_headerFileName = name;
174 175
    m_contents.clear();
    m_cacheTime = QDateTime();
176
    m_state = BARE;
177
    init();
178 179 180 181 182
}

bool UiCodeModelSupport::runUic(const QString &ui) const
{
    const QString uic = uicCommand();
183 184
    if (uic.isEmpty())
        return false;
185
    QLoggingCategory log("qtc.qtsupport.uicodemodelsupport");
Daniel Teske's avatar
Daniel Teske committed
186
    m_process.setEnvironment(environment());
187

188
    qCDebug(log) << "  UiCodeModelSupport::runUic " << uic << " on " << ui.size() << " bytes";
Daniel Teske's avatar
Daniel Teske committed
189 190
    m_process.start(uic, QStringList(), QIODevice::ReadWrite);
    if (!m_process.waitForStarted())
191
        return false;
Daniel Teske's avatar
Daniel Teske committed
192 193
    m_process.write(ui.toUtf8());
    if (!m_process.waitForBytesWritten(3000))
194
        goto error;
Daniel Teske's avatar
Daniel Teske committed
195
    m_process.closeWriteChannel();
196
    m_state = RUNNING;
197 198 199
    return true;

error:
200
    qCDebug(log) << "failed" << m_process.readAllStandardError();
Daniel Teske's avatar
Daniel Teske committed
201
    m_process.kill();
202
    m_state = FINISHED;
203 204 205 206 207
    return false;
}

void UiCodeModelSupport::updateFromEditor(const QString &formEditorContents)
{
208 209 210 211 212 213 214 215
    QLoggingCategory log("qtc.qtsupport.uicodemodelsupport");
    qCDebug(log) << "updating from editor" << m_uiFileName;
    if (m_state == RUNNING) {
        m_state = ABORTING;
        m_process.kill();
        m_process.waitForFinished(3000);
    }
    runUic(formEditorContents);
Daniel Teske's avatar
Daniel Teske committed
216 217
}

218 219
void UiCodeModelSupport::updateFromBuild()
{
220 221 222
    QLoggingCategory log("qtc.qtsupport.uicodemodelsupport");
    qCDebug(log) << "UiCodeModelSupport::updateFromBuild() for " << m_uiFileName;

223 224
    // This is mostly a fall back for the cases when uic couldn't be run
    // it pays special attention to the case where a ui_*h was newly created
Tobias Hunger's avatar
Tobias Hunger committed
225
    QDateTime sourceTime = QFileInfo(m_uiFileName).lastModified();
226
    if (m_cacheTime.isValid() && m_cacheTime >= sourceTime) {
227
        qCDebug(log) << "Cache is still more recent then source";
228 229
        return;
    } else {
Tobias Hunger's avatar
Tobias Hunger committed
230
        QFileInfo fi(m_headerFileName);
231 232 233 234
        QDateTime uiHeaderTime = fi.exists() ? fi.lastModified() : QDateTime();
        if (uiHeaderTime.isValid() && (uiHeaderTime > sourceTime)) {
            if (m_cacheTime >= uiHeaderTime)
                return;
235
            qCDebug(log) << "found ui*h updating from it";
236

Tobias Hunger's avatar
Tobias Hunger committed
237
            QFile file(m_headerFileName);
238
            if (file.open(QFile::ReadOnly | QFile::Text)) {
239 240 241
                QTextStream stream(&file);
                m_contents = stream.readAll().toUtf8();
                m_cacheTime = uiHeaderTime;
242
                notifyAboutUpdatedContents();
243 244 245 246
                updateDocument();
                return;
            }
        }
247
        qCDebug(log) << "ui*h not found or not more recent then source not changing anything";
248 249 250
    }
}

251 252 253 254
QString UiCodeModelSupport::uicCommand() const
{
    QtSupport::BaseQtVersion *version;
    if (m_project->needsConfiguration()) {
255
        version = QtSupport::QtKitInformation::qtVersion(ProjectExplorer::KitManager::defaultKit());
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
    } else {
        ProjectExplorer::Target *target = m_project->activeTarget();
        version = QtSupport::QtKitInformation::qtVersion(target->kit());
    }
    return version ? version->uicCommand() : QString();
}

QStringList UiCodeModelSupport::environment() const
{
    if (m_project->needsConfiguration()) {
        return Utils::Environment::systemEnvironment().toStringList();
    } else {
        ProjectExplorer::Target *target = m_project->activeTarget();
        if (!target)
            return QStringList();
        ProjectExplorer::BuildConfiguration *bc = target->activeBuildConfiguration();
        return bc ? bc->environment().toStringList() : QStringList();
    }
}

276
bool UiCodeModelSupport::finishProcess()
277 278 279
{
    if (m_state != RUNNING)
        return false;
280
    QLoggingCategory log("qtc.qtsupport.uicodemodelsupport");
281 282 283 284
    if (!m_process.waitForFinished(3000)
            && m_process.exitStatus() != QProcess::NormalExit
            && m_process.exitCode() != 0) {

285
        qCDebug(log) << "finish process: failed" << m_process.readAllStandardError();
286 287 288 289 290
        m_process.kill();
        m_state = FINISHED;
        return false;
    }

291 292 293 294
    // As far as I can discover in the UIC sources, it writes out local 8-bit encoding. The
    // conversion below is to normalize both the encoding, and the line terminators.
    QString normalized = QString::fromLocal8Bit(m_process.readAllStandardOutput());
    m_contents = normalized.toUtf8();
295
    m_cacheTime = QDateTime::currentDateTime();
296
    qCDebug(log) << "finish process: ok" << m_contents.size() << "bytes.";
297
    m_state = FINISHED;
298 299
    notifyAboutUpdatedContents();
    updateDocument();
300 301 302
    return true;
}

Tobias Hunger's avatar
Tobias Hunger committed
303 304 305 306 307 308 309
UiCodeModelManager *UiCodeModelManager::m_instance = 0;

UiCodeModelManager::UiCodeModelManager() :
    m_lastEditor(0),
    m_dirty(false)
{
    m_instance = this;
hjk's avatar
hjk committed
310
    connect(BuildManager::instance(), SIGNAL(buildStateChanged(ProjectExplorer::Project*)),
Tobias Hunger's avatar
Tobias Hunger committed
311
            this, SLOT(buildStateHasChanged(ProjectExplorer::Project*)));
hjk's avatar
hjk committed
312
    connect(SessionManager::instance(),
Tobias Hunger's avatar
Tobias Hunger committed
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
            SIGNAL(projectRemoved(ProjectExplorer::Project*)),
            this, SLOT(projectWasRemoved(ProjectExplorer::Project*)));

    connect(Core::EditorManager::instance(), SIGNAL(editorAboutToClose(Core::IEditor*)),
            this, SLOT(editorIsAboutToClose(Core::IEditor*)));
    connect(Core::EditorManager::instance(), SIGNAL(currentEditorChanged(Core::IEditor*)),
            this, SLOT(editorWasChanged(Core::IEditor*)));
}

UiCodeModelManager::~UiCodeModelManager()
{
    m_instance = 0;
}

static UiCodeModelSupport *findUiFile(const QList<UiCodeModelSupport *> &range, const QString &uiFile)
{
329
    return Utils::findOrDefault(range, Utils::equal(&UiCodeModelSupport::uiFileName, uiFile));
Tobias Hunger's avatar
Tobias Hunger committed
330 331 332 333
}

void UiCodeModelManager::update(ProjectExplorer::Project *project, QHash<QString, QString> uiHeaders)
{
334
    CppTools::CppModelManager *mm = CppTools::CppModelManager::instance();
Tobias Hunger's avatar
Tobias Hunger committed
335 336

    // Find support to add/update:
337
    QList<UiCodeModelSupport *> oldSupport = m_instance->m_projectUiSupport.value(project);
Tobias Hunger's avatar
Tobias Hunger committed
338 339 340 341 342 343 344 345 346 347
    QList<UiCodeModelSupport *> newSupport;
    QHash<QString, QString>::const_iterator it;
    for (it = uiHeaders.constBegin(); it != uiHeaders.constEnd(); ++it) {
        if (UiCodeModelSupport *support = findUiFile(oldSupport, it.key())) {
            support->setHeaderFileName(it.value());
            oldSupport.removeOne(support);
            newSupport.append(support);
        } else {
            UiCodeModelSupport *cms = new UiCodeModelSupport(mm, project, it.key(), it.value());
            newSupport.append(cms);
348
            mm->addExtraEditorSupport(cms);
Tobias Hunger's avatar
Tobias Hunger committed
349 350 351 352 353
        }
    }

    // Remove old:
    foreach (UiCodeModelSupport *support, oldSupport) {
354
        mm->removeExtraEditorSupport(support);
Tobias Hunger's avatar
Tobias Hunger committed
355 356 357 358
        delete support;
    }

    // Update state:
359
    m_instance->m_projectUiSupport.insert(project, newSupport);
Tobias Hunger's avatar
Tobias Hunger committed
360 361 362 363
}

void UiCodeModelManager::updateContents(const QString &uiFileName, const QString &contents)
{
hjk's avatar
hjk committed
364
    QHash<Project *, QList<UiCodeModelSupport *> >::iterator i;
365 366
    for (i = m_instance->m_projectUiSupport.begin();
         i != m_instance->m_projectUiSupport.end(); ++i) {
Tobias Hunger's avatar
Tobias Hunger committed
367 368 369 370 371 372 373
        foreach (UiCodeModelSupport *support, i.value()) {
            if (support->uiFileName() == uiFileName)
                support->updateFromEditor(contents);
        }
    }
}

hjk's avatar
hjk committed
374
void UiCodeModelManager::buildStateHasChanged(Project *project)
Tobias Hunger's avatar
Tobias Hunger committed
375
{
hjk's avatar
hjk committed
376
    if (BuildManager::isBuilding(project))
Tobias Hunger's avatar
Tobias Hunger committed
377 378 379 380 381 382 383
        return;

    QList<UiCodeModelSupport *> projectSupport = m_projectUiSupport.value(project);
    foreach (UiCodeModelSupport *const i, projectSupport)
        i->updateFromBuild();
}

hjk's avatar
hjk committed
384
void UiCodeModelManager::projectWasRemoved(Project *project)
Tobias Hunger's avatar
Tobias Hunger committed
385
{
386
    CppTools::CppModelManager *mm = CppTools::CppModelManager::instance();
Tobias Hunger's avatar
Tobias Hunger committed
387 388 389

    QList<UiCodeModelSupport *> projectSupport = m_projectUiSupport.value(project);
    foreach (UiCodeModelSupport *const i, projectSupport) {
390
        mm->removeExtraEditorSupport(i);
Tobias Hunger's avatar
Tobias Hunger committed
391 392 393 394 395 396 397 398 399 400 401 402 403 404
        delete i;
    }

    m_projectUiSupport.remove(project);
}

void UiCodeModelManager::editorIsAboutToClose(Core::IEditor *editor)
{
    if (m_lastEditor == editor) {
        // Oh no our editor is going to be closed
        // get the content first
        if (isFormWindowDocument(m_lastEditor->document())) {
            disconnect(m_lastEditor->document(), SIGNAL(changed()), this, SLOT(uiDocumentContentsHasChanged()));
            if (m_dirty) {
405
                updateContents(m_lastEditor->document()->filePath().toString(),
Tobias Hunger's avatar
Tobias Hunger committed
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
                               formWindowEditorContents(m_lastEditor));
                m_dirty = false;
            }
        }
        m_lastEditor = 0;
    }
}

void UiCodeModelManager::editorWasChanged(Core::IEditor *editor)
{
    // Handle old editor
    if (m_lastEditor && isFormWindowDocument(m_lastEditor->document())) {
        disconnect(m_lastEditor->document(), SIGNAL(changed()), this, SLOT(uiDocumentContentsHasChanged()));

        if (m_dirty) {
421
            updateContents(m_lastEditor->document()->filePath().toString(),
Tobias Hunger's avatar
Tobias Hunger committed
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
                           formWindowEditorContents(m_lastEditor));
            m_dirty = false;
        }
    }

    m_lastEditor = editor;

    // Handle new editor
    if (m_lastEditor && isFormWindowDocument(m_lastEditor->document()))
        connect(m_lastEditor->document(), SIGNAL(changed()), this, SLOT(uiDocumentContentsHasChanged()));
}

void UiCodeModelManager::uiDocumentContentsHasChanged()
{
    QTC_ASSERT(isFormWindowDocument(sender()), return);
    m_dirty = true;
}

440
} // namespace QtSupport