1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "simpleprojectwizard.h"
27 
28 #include "projectexplorerconstants.h"
29 
30 #include <app/app_version.h>
31 
32 #include <coreplugin/basefilewizard.h>
33 #include <coreplugin/icore.h>
34 
35 #include <cmakeprojectmanager/cmakeprojectconstants.h>
36 #include <projectexplorer/projectexplorerconstants.h>
37 #include <projectexplorer/customwizard/customwizard.h>
38 #include <projectexplorer/selectablefilesmodel.h>
39 #include <qmakeprojectmanager/qmakeprojectmanagerconstants.h>
40 
41 #include <utils/algorithm.h>
42 #include <utils/fileutils.h>
43 #include <utils/filewizardpage.h>
44 #include <utils/mimetypes/mimedatabase.h>
45 #include <utils/wizard.h>
46 
47 #include <QApplication>
48 #include <QComboBox>
49 #include <QDebug>
50 #include <QDir>
51 #include <QFileInfo>
52 #include <QLineEdit>
53 #include <QPainter>
54 #include <QPixmap>
55 #include <QStyle>
56 #include <QVBoxLayout>
57 #include <QWizardPage>
58 
59 using namespace Core;
60 using namespace Utils;
61 
62 namespace ProjectExplorer {
63 namespace Internal {
64 
65 class SimpleProjectWizardDialog;
66 
67 class FilesSelectionWizardPage : public QWizardPage
68 {
69     Q_OBJECT
70 
71 public:
72     FilesSelectionWizardPage(SimpleProjectWizardDialog *simpleProjectWizard);
isComplete() const73     bool isComplete() const override { return m_filesWidget->hasFilesSelected(); }
74     void initializePage() override;
cleanupPage()75     void cleanupPage() override { m_filesWidget->cancelParsing(); }
selectedFiles() const76     FilePaths selectedFiles() const { return m_filesWidget->selectedFiles(); }
selectedPaths() const77     FilePaths selectedPaths() const { return m_filesWidget->selectedPaths(); }
qtModules() const78     QString qtModules() const { return m_qtModules; }
buildSystem() const79     QString buildSystem() const { return m_buildSystem; }
80 
81 private:
82     SimpleProjectWizardDialog *m_simpleProjectWizardDialog;
83     SelectableFilesWidget *m_filesWidget;
84     QString m_qtModules;
85     QString m_buildSystem;
86 };
87 
FilesSelectionWizardPage(SimpleProjectWizardDialog * simpleProjectWizard)88 FilesSelectionWizardPage::FilesSelectionWizardPage(SimpleProjectWizardDialog *simpleProjectWizard)
89     : m_simpleProjectWizardDialog(simpleProjectWizard),
90       m_filesWidget(new SelectableFilesWidget(this))
91 {
92     auto layout = new QVBoxLayout(this);
93     {
94         auto hlayout = new QHBoxLayout;
95         hlayout->addWidget(new QLabel("Qt modules", this));
96         auto lineEdit = new QLineEdit("core gui widgets", this);
97         connect(lineEdit, &QLineEdit::editingFinished, this, [this, lineEdit]{
98             m_qtModules = lineEdit->text();
99         });
100         m_qtModules = lineEdit->text();
101         hlayout->addWidget(lineEdit);
102         layout->addLayout(hlayout);
103     }
104 
105     {
106         auto hlayout = new QHBoxLayout;
107         hlayout->addWidget(new QLabel("Build system", this));
108         auto comboBox = new QComboBox(this);
109         connect(comboBox, &QComboBox::currentTextChanged, this, [this](const QString &bs){
110             m_buildSystem = bs;
111         });
112         comboBox->addItems(QStringList() << "qmake" << "cmake");
113         comboBox->setEditable(false);
114         comboBox->setCurrentText("qmake");
115         hlayout->addWidget(comboBox);
116         layout->addLayout(hlayout);
117     }
118 
119     layout->addWidget(m_filesWidget);
120     m_filesWidget->setBaseDirEditable(false);
121     m_filesWidget->enableFilterHistoryCompletion
122             (ProjectExplorer::Constants::ADD_FILES_DIALOG_FILTER_HISTORY_KEY);
123     connect(m_filesWidget, &SelectableFilesWidget::selectedFilesChanged,
124             this, &FilesSelectionWizardPage::completeChanged);
125 
126     setProperty(Utils::SHORT_TITLE_PROPERTY, tr("Files"));
127 }
128 
129 class SimpleProjectWizardDialog : public BaseFileWizard
130 {
131     Q_OBJECT
132 
133 public:
SimpleProjectWizardDialog(const BaseFileWizardFactory * factory,QWidget * parent)134     SimpleProjectWizardDialog(const BaseFileWizardFactory *factory, QWidget *parent)
135         : BaseFileWizard(factory, QVariantMap(), parent)
136     {
137         setWindowTitle(tr("Import Existing Project"));
138 
139         m_firstPage = new FileWizardPage;
140         m_firstPage->setTitle(tr("Project Name and Location"));
141         m_firstPage->setFileNameLabel(tr("Project name:"));
142         m_firstPage->setPathLabel(tr("Location:"));
143         addPage(m_firstPage);
144 
145         m_secondPage = new FilesSelectionWizardPage(this);
146         m_secondPage->setTitle(tr("File Selection"));
147         addPage(m_secondPage);
148     }
149 
path() const150     QString path() const { return m_firstPage->path(); }
setPath(const QString & path)151     void setPath(const QString &path) { m_firstPage->setPath(path); }
selectedFiles() const152     FilePaths selectedFiles() const { return m_secondPage->selectedFiles(); }
selectedPaths() const153     FilePaths selectedPaths() const { return m_secondPage->selectedPaths(); }
qtModules() const154     QString qtModules() const { return m_secondPage->qtModules(); }
buildSystem() const155     QString buildSystem() const { return m_secondPage->buildSystem(); }
projectName() const156     QString projectName() const { return m_firstPage->fileName(); }
157 
158     FileWizardPage *m_firstPage;
159     FilesSelectionWizardPage *m_secondPage;
160 };
161 
initializePage()162 void FilesSelectionWizardPage::initializePage()
163 {
164     m_filesWidget->resetModel(FilePath::fromString(m_simpleProjectWizardDialog->path()),
165                               FilePaths());
166 }
167 
SimpleProjectWizard()168 SimpleProjectWizard::SimpleProjectWizard()
169 {
170     setSupportedProjectTypes({QmakeProjectManager::Constants::QMAKEPROJECT_ID,
171                               CMakeProjectManager::Constants::CMAKE_PROJECT_ID});
172     setIcon(QIcon(QLatin1String(":/projectexplorer/images/importasproject.png")));
173     setDisplayName(tr("Import as qmake or cmake Project (Limited Functionality)"));
174     setId("Z.DummyProFile");
175     setDescription(tr("Imports existing projects that do not use qmake, CMake, Qbs, Meson, or Autotools.<p>"
176                       "This creates a project file that allows you to use %1 as a code editor "
177                       "and as a launcher for debugging and analyzing tools. "
178                       "If you want to build the project, you might need to edit the generated project file.")
179                    .arg(Core::Constants::IDE_DISPLAY_NAME));
180     setCategory(ProjectExplorer::Constants::IMPORT_WIZARD_CATEGORY);
181     setDisplayCategory(ProjectExplorer::Constants::IMPORT_WIZARD_CATEGORY_DISPLAY);
182     setFlags(IWizardFactory::PlatformIndependent);
183 }
184 
create(QWidget * parent,const WizardDialogParameters & parameters) const185 BaseFileWizard *SimpleProjectWizard::create(QWidget *parent,
186                                             const WizardDialogParameters &parameters) const
187 {
188     auto wizard = new SimpleProjectWizardDialog(this, parent);
189     wizard->setPath(parameters.defaultPath());
190 
191     for (QWizardPage *p : wizard->extensionPages())
192         wizard->addPage(p);
193 
194     return wizard;
195 }
196 
generateQmakeFiles(const SimpleProjectWizardDialog * wizard,QString * errorMessage)197 GeneratedFiles generateQmakeFiles(const SimpleProjectWizardDialog *wizard,
198                                   QString *errorMessage)
199 {
200     Q_UNUSED(errorMessage)
201     const QString projectPath = wizard->path();
202     const QDir dir(projectPath);
203     const QString projectName = wizard->projectName();
204     const QString proFileName = QFileInfo(dir, projectName + ".pro").absoluteFilePath();
205     const QStringList paths = Utils::transform(wizard->selectedPaths(), &FilePath::toString);
206 
207     MimeType headerType = Utils::mimeTypeForName("text/x-chdr");
208 
209     QStringList nameFilters = headerType.globPatterns();
210 
211     QString proIncludes = "INCLUDEPATH = \\\n";
212     for (const QString &path : paths) {
213         QFileInfo fileInfo(path);
214         QDir thisDir(fileInfo.absoluteFilePath());
215         if (!thisDir.entryList(nameFilters, QDir::Files).isEmpty()) {
216             QString relative = dir.relativeFilePath(path);
217             if (!relative.isEmpty())
218                 proIncludes.append("    $$PWD/" + relative + " \\\n");
219         }
220     }
221 
222     QString proSources = "SOURCES = \\\n";
223     QString proHeaders = "HEADERS = \\\n";
224 
225     for (const FilePath &fileName : wizard->selectedFiles()) {
226         QString source = dir.relativeFilePath(fileName.toString());
227         MimeType mimeType = Utils::mimeTypeForFile(fileName.toFileInfo());
228         if (mimeType.matchesName("text/x-chdr") || mimeType.matchesName("text/x-c++hdr"))
229             proHeaders += "   $$PWD/" + source + " \\\n";
230         else
231             proSources += "   $$PWD/" + source + " \\\n";
232     }
233 
234     proHeaders.chop(3);
235     proSources.chop(3);
236     proIncludes.chop(3);
237 
238     GeneratedFile generatedProFile(proFileName);
239     generatedProFile.setAttributes(Core::GeneratedFile::OpenProjectAttribute);
240     generatedProFile.setContents(
241         "# Created by and for " + QLatin1String(Core::Constants::IDE_DISPLAY_NAME)
242         + " This file was created for editing the project sources only.\n"
243         "# You may attempt to use it for building too, by modifying this file here.\n\n"
244         "#TARGET = " + projectName + "\n\n"
245         "QT = " + wizard->qtModules()  + "\n\n"
246         + proHeaders + "\n\n"
247         + proSources + "\n\n"
248         + proIncludes + "\n\n"
249         "#DEFINES = \n\n"
250         );
251 
252     return GeneratedFiles{generatedProFile};
253 }
254 
generateCmakeFiles(const SimpleProjectWizardDialog * wizard,QString * errorMessage)255 GeneratedFiles generateCmakeFiles(const SimpleProjectWizardDialog *wizard,
256                                   QString *errorMessage)
257 {
258     Q_UNUSED(errorMessage)
259     const QString projectPath = wizard->path();
260     const QDir dir(projectPath);
261     const QString projectName = wizard->projectName();
262     const QString projectFileName = QFileInfo(dir, "CMakeLists.txt").absoluteFilePath();
263     const QStringList paths = Utils::transform(wizard->selectedPaths(), &FilePath::toString);
264 
265     MimeType headerType = Utils::mimeTypeForName("text/x-chdr");
266 
267     QStringList nameFilters = headerType.globPatterns();
268 
269     QString includes = "include_directories(\n";
270     bool haveIncludes = false;
271     for (const QString &path : paths) {
272         QFileInfo fileInfo(path);
273         QDir thisDir(fileInfo.absoluteFilePath());
274         if (!thisDir.entryList(nameFilters, QDir::Files).isEmpty()) {
275             QString relative = dir.relativeFilePath(path);
276             if (!relative.isEmpty()) {
277                 includes.append("    " + relative + "\n");
278                 haveIncludes = true;
279             }
280         }
281     }
282     if (haveIncludes)
283         includes += ")";
284     else
285         includes.clear();
286 
287     QString srcs = "set (SRCS\n";
288     for (const FilePath &fileName : wizard->selectedFiles())
289         srcs += "    " + dir.relativeFilePath(fileName.toString()) + "\n";
290     srcs += ")\n";
291 
292     QString components = "find_package(Qt5 COMPONENTS";
293     QString libs = "target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE";
294     bool haveQtModules = false;
295     for (QString c : wizard->qtModules().split(' ')) {
296         if (c.isEmpty())
297             continue;
298         c[0] = c[0].toUpper();
299         libs += " Qt5::" + c;
300         components += " " + c;
301         haveQtModules = true;
302     }
303     if (haveQtModules) {
304         libs += ")\n";
305         components += " REQUIRED)";
306     } else {
307         libs.clear();
308         components.clear();
309     }
310 
311 
312     GeneratedFile generatedProFile(projectFileName);
313     generatedProFile.setAttributes(Core::GeneratedFile::OpenProjectAttribute);
314     generatedProFile.setContents(
315         "# Created by and for " + QLatin1String(Core::Constants::IDE_DISPLAY_NAME)
316         + " This file was created for editing the project sources only.\n"
317           "# You may attempt to use it for building too, by modifying this file here.\n\n"
318           "cmake_minimum_required(VERSION 3.5)\n"
319           "project("+ projectName +")\n\n"
320           "set(CMAKE_INCLUDE_CURRENT_DIR ON)\n"
321           "set(CMAKE_AUTOUIC ON)\n"
322           "set(CMAKE_AUTOMOC ON)\n"
323           "set(CMAKE_AUTORCC ON)\n"
324           "set(CMAKE_CXX_STANDARD 11)\n"
325           "set(CMAKE_CXX_STANDARD_REQUIRED ON)\n"
326           + components + "\n\n"
327           + includes + "\n\n"
328           + srcs + "\n\n"
329           "add_executable(${CMAKE_PROJECT_NAME} ${SRCS})\n\n"
330           + libs
331         );
332     return GeneratedFiles{generatedProFile};
333 }
334 
generateFiles(const QWizard * w,QString * errorMessage) const335 GeneratedFiles SimpleProjectWizard::generateFiles(const QWizard *w,
336                                                   QString *errorMessage) const
337 {
338     Q_UNUSED(errorMessage)
339 
340     auto wizard = qobject_cast<const SimpleProjectWizardDialog *>(w);
341     if (wizard->buildSystem() == "qmake")
342         return generateQmakeFiles(wizard, errorMessage);
343     else if (wizard->buildSystem() == "cmake")
344         return generateCmakeFiles(wizard, errorMessage);
345 
346     if (errorMessage)
347         *errorMessage = tr("Unknown build system \"%1\"").arg(wizard->buildSystem());
348     return {};
349 }
350 
postGenerateFiles(const QWizard * w,const GeneratedFiles & l,QString * errorMessage) const351 bool SimpleProjectWizard::postGenerateFiles(const QWizard *w, const GeneratedFiles &l,
352                                              QString *errorMessage) const
353 {
354     Q_UNUSED(w)
355     return CustomProjectWizard::postGenerateOpen(l, errorMessage);
356 }
357 
358 } // namespace Internal
359 } // namespace GenericProjectManager
360 
361 #include "simpleprojectwizard.moc"
362