OSDN Git Service

828948d44b63902eaff117c1047bd9d4d4a7e153
[qt-creator-jp/qt-creator-jp.git] / src / plugins / projectexplorer / projectfilewizardextension.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 **
10 ** GNU Lesser General Public License Usage
11 **
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 **
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 **
23 ** Other Usage
24 **
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **************************************************************************/
32
33 #include "projectfilewizardextension.h"
34 #include "projectexplorer.h"
35 #include "session.h"
36 #include "projectnodes.h"
37 #include "nodesvisitor.h"
38 #include "projectwizardpage.h"
39
40 #include <utils/qtcassert.h>
41 #include <utils/stringutils.h>
42
43 #include <coreplugin/basefilewizard.h>
44 #include <coreplugin/documentmanager.h>
45 #include <coreplugin/icore.h>
46 #include <coreplugin/iversioncontrol.h>
47 #include <coreplugin/vcsmanager.h>
48 #include <coreplugin/mimedatabase.h>
49 #include <extensionsystem/pluginmanager.h>
50 #include <texteditor/texteditorsettings.h>
51 #include <texteditor/indenter.h>
52 #include <texteditor/icodestylepreferences.h>
53 #include <texteditor/icodestylepreferencesfactory.h>
54 #include <texteditor/normalindenter.h>
55 #include <texteditor/tabsettings.h>
56 #include <projectexplorer/project.h>
57 #include <projectexplorer/editorconfiguration.h>
58
59 #include <QVariant>
60 #include <QtAlgorithms>
61 #include <QDebug>
62 #include <QFileInfo>
63 #include <QMultiMap>
64 #include <QDir>
65 #include <QTextDocument>
66 #include <QTextCursor>
67
68 /*!
69     \class ProjectExplorer::Internal::ProjectFileWizardExtension
70
71     \brief Post-file generating steps of a project wizard.
72
73     Offers:
74     \list
75     \o Add to a project file (*.pri/ *.pro)
76     \o Initialize a version control repository (unless the path is already
77         managed) and do 'add' if the VCS supports it.
78     \endlist
79
80     \sa ProjectExplorer::Internal::ProjectWizardPage
81 */
82
83 enum { debugExtension = 0 };
84
85 namespace ProjectExplorer {
86
87 typedef QList<ProjectNode *> ProjectNodeList;
88
89 namespace Internal {
90
91 // AllProjectNodesVisitor: Retrieve all projects (*.pri/*.pro)
92 // which support adding files
93 class AllProjectNodesVisitor : public NodesVisitor
94 {
95 public:
96     AllProjectNodesVisitor(ProjectNode::ProjectAction action)
97         : m_action(action)
98         {}
99
100     static ProjectNodeList allProjects(ProjectNode::ProjectAction action);
101
102     virtual void visitProjectNode(ProjectNode *node);
103
104 private:
105     ProjectNodeList m_projectNodes;
106     ProjectNode::ProjectAction m_action;
107 };
108
109 ProjectNodeList AllProjectNodesVisitor::allProjects(ProjectNode::ProjectAction action)
110 {
111     AllProjectNodesVisitor visitor(action);
112     ProjectExplorerPlugin::instance()->session()->sessionNode()->accept(&visitor);
113     return visitor.m_projectNodes;
114 }
115
116 void AllProjectNodesVisitor::visitProjectNode(ProjectNode *node)
117 {
118     if (node->supportedActions(node).contains(m_action))
119         m_projectNodes.push_back(node);
120 }
121
122 // ProjectEntry: Context entry for a *.pri/*.pro file. Stores name and path
123 // for quick sort and path search, provides operator<() for maps.
124 struct ProjectEntry {
125     enum Type { ProFile, PriFile }; // Sort order: 'pro' before 'pri'
126
127     ProjectEntry() : node(0), type(ProFile) {}
128     explicit ProjectEntry(ProjectNode *node);
129
130     int compare(const ProjectEntry &rhs) const;
131
132     ProjectNode *node;
133     QString directory; // For matching against wizards' files, which are native.
134     QString fileName;
135     QString baseName;
136     Type type;
137 };
138
139 ProjectEntry::ProjectEntry(ProjectNode *n) :
140     node(n),
141     type(ProFile)
142 {
143     const QFileInfo fi(node->path());
144     fileName = fi.fileName();
145     baseName = fi.baseName();
146     if (fi.suffix() != QLatin1String("pro"))
147         type = PriFile;
148     directory = fi.absolutePath();
149 }
150
151 // Sort helper that sorts by base name and puts '*.pro' before '*.pri'
152 int ProjectEntry::compare(const ProjectEntry &rhs) const
153 {
154     if (const int drc = directory.compare(rhs.directory))
155         return drc;
156     if (const int brc = baseName.compare(rhs.baseName))
157         return brc;
158     if (type < rhs.type)
159         return -1;
160     if (type > rhs.type)
161         return 1;
162     return 0;
163 }
164
165 inline bool operator<(const ProjectEntry &pe1, const ProjectEntry &pe2)
166 {
167     return pe1.compare(pe2) < 0;
168 }
169
170 QDebug operator<<(QDebug d, const ProjectEntry &e)
171 {
172     d.nospace() << e.directory << ' ' << e.fileName << ' ' << e.type;
173     return d;
174 }
175
176 // --------- ProjectWizardContext
177 struct ProjectWizardContext
178 {
179     ProjectWizardContext();
180     void clear();
181
182     QList<Core::IVersionControl*> versionControls;
183     QList<Core::IVersionControl*> activeVersionControls;
184     QList<ProjectEntry> projects;
185     ProjectWizardPage *page;
186     bool repositoryExists; // Is VCS 'add' sufficient, or should a repository be created?
187     QString commonDirectory;
188     const Core::IWizard *wizard;
189 };
190
191 ProjectWizardContext::ProjectWizardContext() :
192     page(0),
193     repositoryExists(false),
194     wizard(0)
195 {
196 }
197
198 void ProjectWizardContext::clear()
199 {
200     activeVersionControls.clear();
201     projects.clear();
202     commonDirectory.clear();
203     page = 0;
204     repositoryExists = false;
205     wizard = 0;
206 }
207
208 // ---- ProjectFileWizardExtension
209 ProjectFileWizardExtension::ProjectFileWizardExtension()
210   : m_context(0)
211 {
212 }
213
214 ProjectFileWizardExtension::~ProjectFileWizardExtension()
215 {
216     delete m_context;
217 }
218
219 static QList<ProjectEntry> findDeployProject(const QList<ProjectEntry> &projects,
220                                  QString &commonPath)
221 {
222     QList<ProjectEntry> filtered;
223     foreach (const ProjectEntry &project, projects)
224         if (project.node->deploysFolder(commonPath))
225             filtered << project;
226     return filtered;
227 }
228
229 // Find the project the new files should be added to given their common
230 // path. Either a direct match on the directory or the directory with
231 // the longest matching path (list containing"/project/subproject1" matching
232 // common path "/project/subproject1/newuserpath").
233 static int findMatchingProject(const QList<ProjectEntry> &projects,
234                                const QString &commonPath)
235 {
236     if (projects.isEmpty() || commonPath.isEmpty())
237         return -1;
238
239     int bestMatch = -1;
240     int bestMatchLength = 0;
241     bool bestMatchIsProFile = false;
242     const int count = projects.size();
243     for (int p = 0; p < count; p++) {
244         // Direct match or better match? (note that the wizards' files are native).
245         const ProjectEntry &entry = projects.at(p);
246         const QString &projectDirectory = entry.directory;
247         const int projectDirectorySize = projectDirectory.size();
248         if (projectDirectorySize == bestMatchLength && bestMatchIsProFile)
249             continue; // prefer first pro file over all other files with same bestMatchLength
250         if (projectDirectorySize == bestMatchLength && entry.type == ProjectEntry::PriFile)
251             continue; // we already have a match with same bestMatchLength that is at least a pri file
252         if (projectDirectorySize >= bestMatchLength
253                 && commonPath.startsWith(projectDirectory)) {
254             bestMatchIsProFile = (entry.type == ProjectEntry::ProFile);
255             bestMatchLength = projectDirectory.size();
256             bestMatch = p;
257         }
258     }
259     return bestMatch;
260 }
261
262 static QString generatedProjectFilePath(const QList<Core::GeneratedFile> &files)
263 {
264     foreach (const Core::GeneratedFile &file, files)
265         if (file.attributes() & Core::GeneratedFile::OpenProjectAttribute)
266             return file.path();
267     return QString();
268 }
269
270 void ProjectFileWizardExtension::firstExtensionPageShown(
271         const QList<Core::GeneratedFile> &files)
272 {
273     initProjectChoices(generatedProjectFilePath(files));
274
275     if (debugExtension)
276         qDebug() << Q_FUNC_INFO << files.size();
277     // Parametrize wizard page: find best project to add to, set up files display and
278     // version control depending on path
279     QStringList fileNames;
280     foreach (const Core::GeneratedFile &f, files)
281         fileNames.push_back(f.path());
282     m_context->commonDirectory = Utils::commonPath(fileNames);
283     m_context->page->setFilesDisplay(m_context->commonDirectory, fileNames);
284     // Find best project (Entry at 0 is 'None').
285
286     int bestProjectIndex = -1;
287
288     QList<ProjectEntry> deployingProjects = findDeployProject(m_context->projects, m_context->commonDirectory);
289     if (!deployingProjects.isEmpty()) {
290         // Oh we do have someone that deploys it
291         // then the best match is NONE
292         // We display a label explaining that and rename <None> to
293         // <Implicitly Add>
294         m_context->page->setNoneLabel(tr("<Implicitly Add>"));
295
296         QString text = tr("The files are implicitly added to the projects:\n");
297         foreach (const ProjectEntry &project, deployingProjects) {
298             text += project.fileName;
299             text += QLatin1Char('\n');
300         }
301
302         m_context->page->setAdditionalInfo(text);
303         bestProjectIndex = -1;
304     } else {
305         bestProjectIndex = findMatchingProject(m_context->projects, m_context->commonDirectory);
306         m_context->page->setNoneLabel(tr("<None>"));
307     }
308
309     if (bestProjectIndex == -1) {
310         m_context->page->setCurrentProjectIndex(0);
311     } else {
312         m_context->page->setCurrentProjectIndex(bestProjectIndex + 1);
313     }
314
315     // Store all version controls for later use:
316     if (m_context->versionControls.isEmpty()) {
317         foreach (Core::IVersionControl *vc, ExtensionSystem::PluginManager::instance()->getObjects<Core::IVersionControl>()) {
318             m_context->versionControls.append(vc);
319             connect(vc, SIGNAL(configurationChanged()), this, SLOT(initializeVersionControlChoices()));
320         }
321     }
322
323     initializeVersionControlChoices();
324 }
325
326 void ProjectFileWizardExtension::initializeVersionControlChoices()
327 {
328     // Figure out version control situation:
329     // 1) Directory is managed and VCS supports "Add" -> List it
330     // 2) Directory is managed and VCS does not support "Add" -> None available
331     // 3) Directory is not managed -> Offer all VCS that support "CreateRepository"
332
333     Core::IVersionControl *currentSelection = 0;
334     int currentIdx = m_context->page->versionControlIndex() - 1;
335     if (currentIdx >= 0 && currentIdx <= m_context->activeVersionControls.size() - 1)
336         currentSelection = m_context->activeVersionControls.at(currentIdx);
337
338     m_context->activeVersionControls.clear();
339
340     QStringList versionControlChoices = QStringList(tr("<None>"));
341     if (!m_context->commonDirectory.isEmpty()) {
342         Core::IVersionControl *managingControl = Core::ICore::vcsManager()->findVersionControlForDirectory(m_context->commonDirectory);
343         if (managingControl) {
344             // Under VCS
345             if (managingControl->supportsOperation(Core::IVersionControl::AddOperation)) {
346                 versionControlChoices.append(managingControl->displayName());
347                 m_context->activeVersionControls.push_back(managingControl);
348                 m_context->repositoryExists = true;
349             }
350         } else {
351             // Create
352             foreach (Core::IVersionControl *vc, m_context->versionControls)
353                 if (vc->supportsOperation(Core::IVersionControl::CreateRepositoryOperation)) {
354                     versionControlChoices.append(vc->displayName());
355                     m_context->activeVersionControls.append(vc);
356                 }
357             m_context->repositoryExists = false;
358         }
359     } // has a common root.
360
361     m_context->page->setVersionControls(versionControlChoices);
362     // Enable adding to version control by default.
363     if (m_context->repositoryExists && versionControlChoices.size() >= 2)
364         m_context->page->setVersionControlIndex(1);
365     if (!m_context->repositoryExists) {
366         int newIdx = m_context->activeVersionControls.indexOf(currentSelection) + 1;
367         m_context->page->setVersionControlIndex(newIdx);
368     }
369 }
370
371 QList<QWizardPage *> ProjectFileWizardExtension::extensionPages(const Core::IWizard *wizard)
372 {
373     if (!m_context) {
374         m_context = new ProjectWizardContext;
375     } else {
376         m_context->clear();
377     }
378     // Init context with page and projects
379     m_context->page = new ProjectWizardPage;
380     m_context->wizard = wizard;
381     return QList<QWizardPage *>() << m_context->page;
382 }
383
384 void ProjectFileWizardExtension::initProjectChoices(const QString &generatedProjectFilePath)
385 {
386     // Set up project list which remains the same over duration of wizard execution
387     // As tooltip, set the directory for disambiguation (should someone have
388     // duplicate base names in differing directories).
389     //: No project selected
390     QStringList projectChoices(tr("<None>"));
391     QStringList projectToolTips((QString()));
392
393     typedef QMap<ProjectEntry, bool> ProjectEntryMap;
394     // Sort by base name and purge duplicated entries (resulting from dependencies)
395     // via Map.
396     ProjectEntryMap entryMap;
397
398     ProjectNode::ProjectAction projectAction =
399             m_context->wizard->kind() == Core::IWizard::ProjectWizard
400             ? ProjectNode::AddSubProject : ProjectNode::AddNewFile;
401     foreach(ProjectNode *n, AllProjectNodesVisitor::allProjects(projectAction)) {
402         if (projectAction == ProjectNode::AddNewFile
403                 || (projectAction == ProjectNode::AddSubProject
404                 && n->canAddSubProject(generatedProjectFilePath)))
405             entryMap.insert(ProjectEntry(n), true);
406     }
407
408     m_context->projects.clear();
409
410     // Collect names
411     const ProjectEntryMap::const_iterator cend = entryMap.constEnd();
412     for (ProjectEntryMap::const_iterator it = entryMap.constBegin(); it != cend; ++it) {
413         m_context->projects.push_back(it.key());
414         projectChoices.push_back(it.key().fileName);
415         projectToolTips.push_back(QDir::toNativeSeparators(it.key().directory));
416     }
417
418     m_context->page->setProjects(projectChoices);
419     m_context->page->setProjectToolTips(projectToolTips);
420     m_context->page->setAddingSubProject(projectAction == ProjectNode::AddSubProject);
421 }
422
423 bool ProjectFileWizardExtension::processFiles(
424         const QList<Core::GeneratedFile> &files,
425         bool *removeOpenProjectAttribute, QString *errorMessage)
426 {
427     return processProject(files, removeOpenProjectAttribute, errorMessage) &&
428            processVersionControl(files, errorMessage);
429 }
430
431 // Add files to project && version control
432 bool ProjectFileWizardExtension::processProject(
433         const QList<Core::GeneratedFile> &files,
434         bool *removeOpenProjectAttribute, QString *errorMessage)
435 {
436     typedef QMultiMap<FileType, QString> TypeFileMap;
437
438     *removeOpenProjectAttribute = false;
439
440     QString generatedProject = generatedProjectFilePath(files);
441
442     // Add files to  project (Entry at 0 is 'None').
443     const int projectIndex = m_context->page->currentProjectIndex() - 1;
444     if (projectIndex < 0 || projectIndex >= m_context->projects.size())
445         return true;
446     ProjectNode *project = m_context->projects.at(projectIndex).node;
447     if (m_context->wizard->kind() == Core::IWizard::ProjectWizard) {
448         if (!project->addSubProjects(QStringList(generatedProject))) {
449             *errorMessage = tr("Failed to add subproject '%1'\nto project '%2'.")
450                             .arg(generatedProject).arg(project->path());
451             return false;
452         }
453         *removeOpenProjectAttribute = true;
454     } else {
455         // Split into lists by file type and bulk-add them.
456         TypeFileMap typeFileMap;
457         const Core::MimeDatabase *mdb = Core::ICore::mimeDatabase();
458         foreach (const Core::GeneratedFile &generatedFile, files) {
459             const QString path = generatedFile.path();
460             typeFileMap.insert(typeForFileName(mdb, path), path);
461         }
462         foreach (FileType type, typeFileMap.uniqueKeys()) {
463             const QStringList typeFiles = typeFileMap.values(type);
464             if (!project->addFiles(type, typeFiles)) {
465                 *errorMessage = tr("Failed to add one or more files to project\n'%1' (%2).").
466                                 arg(project->path(), typeFiles.join(QString(QLatin1Char(','))));
467                 return false;
468             }
469         }
470     }
471     return true;
472 }
473
474 bool ProjectFileWizardExtension::processVersionControl(const QList<Core::GeneratedFile> &files, QString *errorMessage)
475 {
476     // Add files to  version control (Entry at 0 is 'None').
477     const int vcsIndex = m_context->page->versionControlIndex() - 1;
478     if (vcsIndex < 0 || vcsIndex >= m_context->activeVersionControls.size())
479         return true;
480     QTC_ASSERT(!m_context->commonDirectory.isEmpty(), return false);
481     Core::IVersionControl *versionControl = m_context->activeVersionControls.at(vcsIndex);
482     // Create repository?
483     if (!m_context->repositoryExists) {
484         QTC_ASSERT(versionControl->supportsOperation(Core::IVersionControl::CreateRepositoryOperation), return false);
485         if (!versionControl->vcsCreateRepository(m_context->commonDirectory)) {
486             *errorMessage = tr("A version control system repository could not be created in '%1'.").arg(m_context->commonDirectory);
487             return false;
488         }
489     }
490     // Add files if supported.
491     if (versionControl->supportsOperation(Core::IVersionControl::AddOperation)) {
492         foreach (const Core::GeneratedFile &generatedFile, files) {
493             if (!versionControl->vcsAdd(generatedFile.path())) {
494                 *errorMessage = tr("Failed to add '%1' to the version control system.").arg(generatedFile.path());
495                 return false;
496             }
497         }
498     }
499     return true;
500 }
501
502 static TextEditor::ICodeStylePreferences *codeStylePreferences(ProjectExplorer::Project *project, const QString &languageId)
503 {
504     if (languageId.isEmpty())
505         return 0;
506
507     if (project)
508         return project->editorConfiguration()->codeStyle(languageId);
509
510     return TextEditor::TextEditorSettings::instance()->codeStyle(languageId);
511 }
512
513 void ProjectFileWizardExtension::applyCodeStyle(Core::GeneratedFile *file) const
514 {
515     if (file->isBinary() || file->contents().isEmpty())
516         return; // nothing to do
517
518     const Core::MimeDatabase *mdb = Core::ICore::mimeDatabase();
519     Core::MimeType mt = mdb->findByFile(QFileInfo(file->path()));
520     const QString languageId = TextEditor::TextEditorSettings::instance()->languageId(mt.type());
521
522     if (languageId.isEmpty())
523         return; // don't modify files like *.ui *.pro
524
525     ProjectNode *project = 0;
526     const int projectIndex = m_context->page->currentProjectIndex() - 1;
527     if (projectIndex >= 0 && projectIndex < m_context->projects.size())
528         project = m_context->projects.at(projectIndex).node;
529
530     ProjectExplorer::Project *baseProject
531             = ProjectExplorer::ProjectExplorerPlugin::instance()->session()->projectForNode(project);
532
533     TextEditor::ICodeStylePreferencesFactory *factory
534             = TextEditor::TextEditorSettings::instance()->codeStyleFactory(languageId);
535
536     TextEditor::Indenter *indenter = 0;
537     if (factory)
538         indenter = factory->createIndenter();
539     if (!indenter)
540         indenter = new TextEditor::NormalIndenter();
541
542     TextEditor::ICodeStylePreferences *codeStylePrefs = codeStylePreferences(baseProject, languageId);
543     indenter->setCodeStylePreferences(codeStylePrefs);
544
545     QTextDocument doc(file->contents());
546     QTextCursor cursor(&doc);
547     cursor.select(QTextCursor::Document);
548     indenter->indent(&doc, cursor, QChar::Null, codeStylePrefs->currentTabSettings());
549     file->setContents(doc.toPlainText());
550     delete indenter;
551 }
552
553 } // namespace Internal
554 } // namespace ProjectExplorer