1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "basefilewizard.h"
36 #include "coreconstants.h"
38 #include "ifilewizardextension.h"
39 #include "mimedatabase.h"
40 #include "editormanager/editormanager.h"
42 #include <extensionsystem/pluginmanager.h>
43 #include <utils/filewizarddialog.h>
44 #include <utils/qtcassert.h>
45 #include <utils/stringutils.h>
47 #include <QtCore/QDir>
48 #include <QtCore/QFile>
49 #include <QtCore/QFileInfo>
50 #include <QtCore/QVector>
51 #include <QtCore/QDebug>
52 #include <QtCore/QSharedData>
53 #include <QtCore/QEventLoop>
54 #include <QtCore/QSharedPointer>
55 #include <QtCore/QScopedPointer>
57 #include <QtGui/QMessageBox>
58 #include <QtGui/QWizard>
59 #include <QtGui/QMainWindow>
60 #include <QtGui/QIcon>
62 enum { debugWizard = 0 };
66 class GeneratedFilePrivate : public QSharedData
69 GeneratedFilePrivate() : binary(false) {}
70 explicit GeneratedFilePrivate(const QString &p);
75 GeneratedFile::Attributes attributes;
78 GeneratedFilePrivate::GeneratedFilePrivate(const QString &p) :
79 path(QDir::cleanPath(p)),
85 GeneratedFile::GeneratedFile() :
86 m_d(new GeneratedFilePrivate)
90 GeneratedFile::GeneratedFile(const QString &p) :
91 m_d(new GeneratedFilePrivate(p))
95 GeneratedFile::GeneratedFile(const GeneratedFile &rhs) :
100 GeneratedFile &GeneratedFile::operator=(const GeneratedFile &rhs)
103 m_d.operator=(rhs.m_d);
107 GeneratedFile::~GeneratedFile()
111 QString GeneratedFile::path() const
116 void GeneratedFile::setPath(const QString &p)
118 m_d->path = QDir::cleanPath(p);
121 QString GeneratedFile::contents() const
123 return QString::fromUtf8(m_d->contents);
126 void GeneratedFile::setContents(const QString &c)
128 m_d->contents = c.toUtf8();
131 QByteArray GeneratedFile::binaryContents() const
133 return m_d->contents;
136 void GeneratedFile::setBinaryContents(const QByteArray &c)
141 bool GeneratedFile::isBinary() const
146 void GeneratedFile::setBinary(bool b)
151 QString GeneratedFile::editorId() const
153 return m_d->editorId;
156 void GeneratedFile::setEditorId(const QString &k)
161 bool GeneratedFile::write(QString *errorMessage) const
163 // Ensure the directory
164 const QFileInfo info(m_d->path);
165 const QDir dir = info.absoluteDir();
167 if (!dir.mkpath(dir.absolutePath())) {
168 *errorMessage = BaseFileWizard::tr("Unable to create the directory %1.").arg(dir.absolutePath());
173 QFile file(m_d->path);
175 QIODevice::OpenMode flags = QIODevice::WriteOnly|QIODevice::Truncate;
177 flags |= QIODevice::Text;
179 if (!file.open(flags)) {
180 *errorMessage = BaseFileWizard::tr("Unable to open %1 for writing: %2").arg(m_d->path, file.errorString());
183 if (file.write(m_d->contents) == -1) {
184 *errorMessage = BaseFileWizard::tr("Error while writing to %1: %2").arg(m_d->path, file.errorString());
191 GeneratedFile::Attributes GeneratedFile::attributes() const
193 return m_d->attributes;
196 void GeneratedFile::setAttributes(Attributes a)
201 // ------------ BaseFileWizardParameterData
202 class BaseFileWizardParameterData : public QSharedData
205 explicit BaseFileWizardParameterData(IWizard::WizardKind kind = IWizard::FileWizard);
208 IWizard::WizardKind kind;
214 QString displayCategory;
217 BaseFileWizardParameterData::BaseFileWizardParameterData(IWizard::WizardKind k) :
222 void BaseFileWizardParameterData::clear()
224 kind = IWizard::FileWizard;
230 displayCategory.clear();
233 BaseFileWizardParameters::BaseFileWizardParameters(IWizard::WizardKind kind) :
234 m_d(new BaseFileWizardParameterData(kind))
238 BaseFileWizardParameters::BaseFileWizardParameters(const BaseFileWizardParameters &rhs) :
243 BaseFileWizardParameters &BaseFileWizardParameters::operator=(const BaseFileWizardParameters &rhs)
246 m_d.operator=(rhs.m_d);
250 BaseFileWizardParameters::~BaseFileWizardParameters()
254 void BaseFileWizardParameters::clear()
259 CORE_EXPORT QDebug operator<<(QDebug d, const BaseFileWizardParameters &p)
261 d.nospace() << "Kind: " << p.kind() << " Id: " << p.id()
262 << " Category: " << p.category()
263 << " DisplayName: " << p.displayName()
264 << " Description: " << p.description()
265 << " DisplayCategory: " << p.displayCategory();
269 IWizard::WizardKind BaseFileWizardParameters::kind() const
274 void BaseFileWizardParameters::setKind(IWizard::WizardKind k)
279 QIcon BaseFileWizardParameters::icon() const
284 void BaseFileWizardParameters::setIcon(const QIcon &icon)
289 QString BaseFileWizardParameters::description() const
291 return m_d->description;
294 void BaseFileWizardParameters::setDescription(const QString &v)
296 m_d->description = v;
299 QString BaseFileWizardParameters::displayName() const
301 return m_d->displayName;
304 void BaseFileWizardParameters::setDisplayName(const QString &v)
306 m_d->displayName = v;
309 QString BaseFileWizardParameters::id() const
314 void BaseFileWizardParameters::setId(const QString &v)
319 QString BaseFileWizardParameters::category() const
321 return m_d->category;
324 void BaseFileWizardParameters::setCategory(const QString &v)
329 QString BaseFileWizardParameters::displayCategory() const
331 return m_d->displayCategory;
334 void BaseFileWizardParameters::setDisplayCategory(const QString &v)
336 m_d->displayCategory = v;
339 /* WizardEventLoop: Special event loop that runs a QWizard and terminates if the page changes.
342 Wizard wizard(parent);
343 WizardEventLoop::WizardResult wr;
345 wr = WizardEventLoop::execWizardPage(wizard);
346 } while (wr == WizardEventLoop::PageChanged);
349 class WizardEventLoop : public QEventLoop
352 Q_DISABLE_COPY(WizardEventLoop)
353 WizardEventLoop(QObject *parent);
356 enum WizardResult { Accepted, Rejected , PageChanged };
358 static WizardResult execWizardPage(QWizard &w);
361 void pageChanged(int);
366 WizardResult execWizardPageI();
368 WizardResult m_result;
371 WizardEventLoop::WizardEventLoop(QObject *parent) :
377 WizardEventLoop::WizardResult WizardEventLoop::execWizardPage(QWizard &wizard)
379 /* Install ourselves on the wizard. Main trick is here to connect
380 * to the page changed signal and quit() on it. */
381 WizardEventLoop *eventLoop = wizard.findChild<WizardEventLoop *>();
383 eventLoop = new WizardEventLoop(&wizard);
384 connect(&wizard, SIGNAL(currentIdChanged(int)), eventLoop, SLOT(pageChanged(int)));
385 connect(&wizard, SIGNAL(accepted()), eventLoop, SLOT(accepted()));
386 connect(&wizard, SIGNAL(rejected()), eventLoop, SLOT(rejected()));
387 wizard.setAttribute(Qt::WA_ShowModal, true);
390 const WizardResult result = eventLoop->execWizardPageI();
392 if (result != PageChanged)
395 qDebug() << "WizardEventLoop::runWizard" << wizard.pageIds() << " returns " << result;
400 WizardEventLoop::WizardResult WizardEventLoop::execWizardPageI()
403 exec(QEventLoop::DialogExec);
407 void WizardEventLoop::pageChanged(int /*page*/)
409 m_result = PageChanged;
413 void WizardEventLoop::accepted()
419 void WizardEventLoop::rejected()
425 // ---------------- BaseFileWizardPrivate
426 struct BaseFileWizardPrivate
428 explicit BaseFileWizardPrivate(const Core::BaseFileWizardParameters ¶meters)
429 : m_parameters(parameters), m_wizardDialog(0)
432 const Core::BaseFileWizardParameters m_parameters;
433 QWizard *m_wizardDialog;
436 // ---------------- Wizard
437 BaseFileWizard::BaseFileWizard(const BaseFileWizardParameters ¶meters,
440 m_d(new BaseFileWizardPrivate(parameters))
444 BaseFileWizard::~BaseFileWizard()
449 IWizard::WizardKind BaseFileWizard::kind() const
451 return m_d->m_parameters.kind();
454 QIcon BaseFileWizard::icon() const
456 return m_d->m_parameters.icon();
459 QString BaseFileWizard::description() const
461 return m_d->m_parameters.description();
464 QString BaseFileWizard::displayName() const
466 return m_d->m_parameters.displayName();
469 QString BaseFileWizard::id() const
471 return m_d->m_parameters.id();
474 QString BaseFileWizard::category() const
476 return m_d->m_parameters.category();
479 QString BaseFileWizard::displayCategory() const
481 return m_d->m_parameters.displayCategory();
484 void BaseFileWizard::runWizard(const QString &path, QWidget *parent)
486 QTC_ASSERT(!path.isEmpty(), return);
488 typedef QList<IFileWizardExtension*> ExtensionList;
490 QString errorMessage;
491 // Compile extension pages, purge out unused ones
492 ExtensionList extensions = ExtensionSystem::PluginManager::instance()->getObjects<IFileWizardExtension>();
493 WizardPageList allExtensionPages;
494 for (ExtensionList::iterator it = extensions.begin(); it != extensions.end(); ) {
495 const WizardPageList extensionPages = (*it)->extensionPages(this);
496 if (extensionPages.empty()) {
497 it = extensions.erase(it);
499 allExtensionPages += extensionPages;
505 qDebug() << Q_FUNC_INFO << path << parent << "exs" << extensions.size() << allExtensionPages.size();
507 QWizardPage *firstExtensionPage = 0;
508 if (!allExtensionPages.empty())
509 firstExtensionPage = allExtensionPages.front();
511 // Create dialog and run it. Ensure that the dialog is deleted when
512 // leaving the func, but not before the IFileWizardExtension::process
514 const QScopedPointer<QWizard> wizard(createWizardDialog(parent, path, allExtensionPages));
515 QTC_ASSERT(!wizard.isNull(), return);
517 GeneratedFiles files;
518 // Run the wizard: Call generate files on switching to the first extension
519 // page is OR after 'Accepted' if there are no extension pages
521 const WizardEventLoop::WizardResult wr = WizardEventLoop::execWizardPage(*wizard);
522 if (wr == WizardEventLoop::Rejected) {
526 const bool accepted = wr == WizardEventLoop::Accepted;
527 const bool firstExtensionPageHit = wr == WizardEventLoop::PageChanged
528 && wizard->page(wizard->currentId()) == firstExtensionPage;
529 const bool needGenerateFiles = firstExtensionPageHit || (accepted && allExtensionPages.empty());
530 if (needGenerateFiles) {
531 QString errorMessage;
532 files = generateFiles(wizard.data(), &errorMessage);
534 QMessageBox::critical(0, tr("File Generation Failure"), errorMessage);
538 if (firstExtensionPageHit)
539 foreach (IFileWizardExtension *ex, extensions)
540 ex->firstExtensionPageShown(files);
546 // Compile result list and prompt for overwrite
548 foreach (const GeneratedFile &generatedFile, files)
549 result.push_back(generatedFile.path());
551 switch (promptOverwrite(result, &errorMessage)) {
552 case OverwriteCanceled:
555 QMessageBox::critical(0, tr("Existing files"), errorMessage);
562 if (!writeFiles(files, &errorMessage)) {
563 QMessageBox::critical(parent, tr("File Generation Failure"), errorMessage);
567 bool removeOpenProjectAttribute = false;
568 // Run the extensions
569 foreach (IFileWizardExtension *ex, extensions) {
571 if (!ex->process(files, &remove, &errorMessage)) {
572 QMessageBox::critical(parent, tr("File Generation Failure"), errorMessage);
575 removeOpenProjectAttribute |= remove;
578 if (removeOpenProjectAttribute) {
579 for (int i = 0; i < files.count(); i++) {
580 if (files[i].attributes() & Core::GeneratedFile::OpenProjectAttribute)
581 files[i].setAttributes(Core::GeneratedFile::OpenEditorAttribute);
585 // Post generation handler
586 if (!postGenerateFiles(wizard.data(), files, &errorMessage))
587 QMessageBox::critical(0, tr("File Generation Failure"), errorMessage);
591 bool BaseFileWizard::writeFiles(const GeneratedFiles &files, QString *errorMessage)
593 foreach (const GeneratedFile &generatedFile, files)
594 if (!(generatedFile.attributes() & GeneratedFile::CustomGeneratorAttribute))
595 if (!generatedFile.write(errorMessage))
601 void BaseFileWizard::setupWizard(QWizard *w)
603 w->setOption(QWizard::NoCancelButton, false);
604 w->setOption(QWizard::NoDefaultButton, false);
605 w->setOption(QWizard::NoBackButtonOnStartPage, true);
608 void BaseFileWizard::applyExtensionPageShortTitle(Utils::Wizard *wizard, int pageId)
612 QWizardPage *p = wizard->page(pageId);
615 Utils::WizardProgressItem *item = wizard->wizardProgress()->item(pageId);
618 const QString shortTitle = p->property("shortTitle").toString();
619 if (!shortTitle.isEmpty())
620 item->setTitle(shortTitle);
623 bool BaseFileWizard::postGenerateFiles(const QWizard *, const GeneratedFiles &l, QString *errorMessage)
625 return BaseFileWizard::postGenerateOpenEditors(l, errorMessage);
628 bool BaseFileWizard::postGenerateOpenEditors(const GeneratedFiles &l, QString *errorMessage)
630 Core::EditorManager *em = Core::EditorManager::instance();
631 foreach(const Core::GeneratedFile &file, l) {
632 if (file.attributes() & Core::GeneratedFile::OpenEditorAttribute) {
633 if (!em->openEditor(file.path(), file.editorId(), Core::EditorManager::ModeSwitch )) {
635 *errorMessage = tr("Failed to open an editor for '%1'.").arg(QDir::toNativeSeparators(file.path()));
643 BaseFileWizard::OverwriteResult BaseFileWizard::promptOverwrite(const QStringList &files,
644 QString *errorMessage) const
647 qDebug() << Q_FUNC_INFO << files;
649 QStringList existingFiles;
650 bool oddStuffFound = false;
652 static const QString readOnlyMsg = tr(" [read only]");
653 static const QString directoryMsg = tr(" [directory]");
654 static const QString symLinkMsg = tr(" [symbolic link]");
656 foreach (const QString &fileName, files) {
657 const QFileInfo fi(fileName);
659 existingFiles.append(fileName);
661 // Note: Generated files are using native separators, no need to convert.
662 const QString commonExistingPath = Utils::commonPath(existingFiles);
663 // Format a file list message as ( "<file1> [readonly], <file2> [directory]").
664 QString fileNamesMsgPart;
665 foreach (const QString &fileName, existingFiles) {
666 const QFileInfo fi(fileName);
668 if (!fileNamesMsgPart.isEmpty())
669 fileNamesMsgPart += QLatin1String(", ");
670 fileNamesMsgPart += QDir::toNativeSeparators(fileName.mid(commonExistingPath.size() + 1));
673 oddStuffFound = true;
674 fileNamesMsgPart += directoryMsg;
677 if (fi.isSymLink()) {
678 oddStuffFound = true;
679 fileNamesMsgPart += symLinkMsg;
682 if (!fi.isWritable()) {
683 oddStuffFound = true;
684 fileNamesMsgPart += readOnlyMsg;
690 if (existingFiles.isEmpty())
694 *errorMessage = tr("The project directory %1 contains files which cannot be overwritten:\n%2.")
695 .arg(QDir::toNativeSeparators(commonExistingPath)).arg(fileNamesMsgPart);
696 return OverwriteError;
699 const QString messageFormat = tr("The following files already exist in the directory %1:\n"
700 "%2.\nWould you like to overwrite them?");
701 const QString message = messageFormat.arg(QDir::toNativeSeparators(commonExistingPath)).arg(fileNamesMsgPart);
702 const bool yes = (QMessageBox::question(Core::ICore::instance()->mainWindow(),
703 tr("Existing files"), message,
704 QMessageBox::Yes | QMessageBox::No,
706 == QMessageBox::Yes);
707 return yes ? OverwriteOk : OverwriteCanceled;
710 QString BaseFileWizard::buildFileName(const QString &path,
711 const QString &baseName,
712 const QString &extension)
715 if (!rc.isEmpty() && !rc.endsWith(QDir::separator()))
716 rc += QDir::separator();
718 // Add extension unless user specified something else
719 const QChar dot = QLatin1Char('.');
720 if (!extension.isEmpty() && !baseName.contains(dot)) {
721 if (!extension.startsWith(dot))
726 qDebug() << Q_FUNC_INFO << rc;
730 QString BaseFileWizard::preferredSuffix(const QString &mimeType)
732 const QString rc = Core::ICore::instance()->mimeDatabase()->preferredSuffixByType(mimeType);
734 qWarning("%s: WARNING: Unable to find a preferred suffix for %s.",
735 Q_FUNC_INFO, mimeType.toUtf8().constData());
739 // ------------- StandardFileWizard
741 StandardFileWizard::StandardFileWizard(const BaseFileWizardParameters ¶meters,
743 BaseFileWizard(parameters, parent)
747 QWizard *StandardFileWizard::createWizardDialog(QWidget *parent,
748 const QString &defaultPath,
749 const WizardPageList &extensionPages) const
751 Utils::FileWizardDialog *standardWizardDialog = new Utils::FileWizardDialog(parent);
752 standardWizardDialog->setWindowTitle(tr("New %1").arg(displayName()));
753 setupWizard(standardWizardDialog);
754 standardWizardDialog->setPath(defaultPath);
755 foreach (QWizardPage *p, extensionPages)
756 BaseFileWizard::applyExtensionPageShortTitle(standardWizardDialog, standardWizardDialog->addPage(p));
757 return standardWizardDialog;
760 GeneratedFiles StandardFileWizard::generateFiles(const QWizard *w,
761 QString *errorMessage) const
763 const Utils::FileWizardDialog *standardWizardDialog = qobject_cast<const Utils::FileWizardDialog *>(w);
764 return generateFilesFromPath(standardWizardDialog->path(),
765 standardWizardDialog->fileName(),
771 #include "basefilewizard.moc"