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 "cppfilesettingspage.h"
27 
28 #include "cpptoolsconstants.h"
29 #include "cpptoolsplugin.h"
30 #include <ui_cppfilesettingspage.h>
31 
32 #include <app/app_version.h>
33 
34 #include <coreplugin/icore.h>
35 #include <coreplugin/editormanager/editormanager.h>
36 #include <cppeditor/cppeditorconstants.h>
37 
38 #include <utils/environment.h>
39 #include <utils/fileutils.h>
40 #include <utils/hostosinfo.h>
41 #include <utils/mimetypes/mimedatabase.h>
42 #include <utils/stringutils.h>
43 
44 #include <QCoreApplication>
45 #include <QDate>
46 #include <QDebug>
47 #include <QFile>
48 #include <QFileDialog>
49 #include <QLocale>
50 #include <QSettings>
51 #include <QTextCodec>
52 #include <QTextStream>
53 
54 namespace CppTools {
55 namespace Internal {
56 
57 const char headerPrefixesKeyC[] = "HeaderPrefixes";
58 const char sourcePrefixesKeyC[] = "SourcePrefixes";
59 const char headerSuffixKeyC[] = "HeaderSuffix";
60 const char sourceSuffixKeyC[] = "SourceSuffix";
61 const char headerSearchPathsKeyC[] = "HeaderSearchPaths";
62 const char sourceSearchPathsKeyC[] = "SourceSearchPaths";
63 const char headerPragmaOnceC[] = "HeaderPragmaOnce";
64 const char licenseTemplatePathKeyC[] = "LicenseTemplate";
65 
66 const char *licenseTemplateTemplate = QT_TRANSLATE_NOOP("CppTools::Internal::CppFileSettingsWidget",
67 "/**************************************************************************\n"
68 "** %1 license header template\n"
69 "**   Special keywords: %USER% %DATE% %YEAR%\n"
70 "**   Environment variables: %$VARIABLE%\n"
71 "**   To protect a percent sign, use '%%'.\n"
72 "**************************************************************************/\n");
73 
toSettings(QSettings * s) const74 void CppFileSettings::toSettings(QSettings *s) const
75 {
76     using Utils::QtcSettings;
77     const CppFileSettings def;
78     s->beginGroup(Constants::CPPTOOLS_SETTINGSGROUP);
79     QtcSettings::setValueWithDefault(s, headerPrefixesKeyC, headerPrefixes, def.headerPrefixes);
80     QtcSettings::setValueWithDefault(s, sourcePrefixesKeyC, sourcePrefixes, def.sourcePrefixes);
81     QtcSettings::setValueWithDefault(s, headerSuffixKeyC, headerSuffix, def.headerSuffix);
82     QtcSettings::setValueWithDefault(s, sourceSuffixKeyC, sourceSuffix, def.sourceSuffix);
83     QtcSettings::setValueWithDefault(s,
84                                      headerSearchPathsKeyC,
85                                      headerSearchPaths,
86                                      def.headerSearchPaths);
87     QtcSettings::setValueWithDefault(s,
88                                      sourceSearchPathsKeyC,
89                                      sourceSearchPaths,
90                                      def.sourceSearchPaths);
91     QtcSettings::setValueWithDefault(s,
92                                      Constants::LOWERCASE_CPPFILES_KEY,
93                                      lowerCaseFiles,
94                                      def.lowerCaseFiles);
95     QtcSettings::setValueWithDefault(s, headerPragmaOnceC, headerPragmaOnce, def.headerPragmaOnce);
96     QtcSettings::setValueWithDefault(s,
97                                      licenseTemplatePathKeyC,
98                                      licenseTemplatePath,
99                                      def.licenseTemplatePath);
100     s->endGroup();
101 }
102 
fromSettings(QSettings * s)103 void CppFileSettings::fromSettings(QSettings *s)
104 {
105     const CppFileSettings def;
106     s->beginGroup(Constants::CPPTOOLS_SETTINGSGROUP);
107     headerPrefixes = s->value(headerPrefixesKeyC, def.headerPrefixes).toStringList();
108     sourcePrefixes = s->value(sourcePrefixesKeyC, def.sourcePrefixes).toStringList();
109     headerSuffix = s->value(headerSuffixKeyC, def.headerSuffix).toString();
110     sourceSuffix = s->value(sourceSuffixKeyC, def.sourceSuffix).toString();
111     headerSearchPaths = s->value(headerSearchPathsKeyC, def.headerSearchPaths).toStringList();
112     sourceSearchPaths = s->value(sourceSearchPathsKeyC, def.sourceSearchPaths).toStringList();
113     lowerCaseFiles = s->value(Constants::LOWERCASE_CPPFILES_KEY, def.lowerCaseFiles).toBool();
114     headerPragmaOnce = s->value(headerPragmaOnceC, def.headerPragmaOnce).toBool();
115     licenseTemplatePath = s->value(licenseTemplatePathKeyC, def.licenseTemplatePath).toString();
116     s->endGroup();
117 }
118 
applySuffixesToMimeDB()119 bool CppFileSettings::applySuffixesToMimeDB()
120 {
121     Utils::MimeType mt;
122     mt = Utils::mimeTypeForName(QLatin1String(CppTools::Constants::CPP_SOURCE_MIMETYPE));
123     if (!mt.isValid())
124         return false;
125     mt.setPreferredSuffix(sourceSuffix);
126     mt = Utils::mimeTypeForName(QLatin1String(CppTools::Constants::CPP_HEADER_MIMETYPE));
127     if (!mt.isValid())
128         return false;
129     mt.setPreferredSuffix(headerSuffix);
130     return true;
131 }
132 
equals(const CppFileSettings & rhs) const133 bool CppFileSettings::equals(const CppFileSettings &rhs) const
134 {
135     return lowerCaseFiles == rhs.lowerCaseFiles
136            && headerPragmaOnce == rhs.headerPragmaOnce
137            && headerPrefixes == rhs.headerPrefixes
138            && sourcePrefixes == rhs.sourcePrefixes
139            && headerSuffix == rhs.headerSuffix
140            && sourceSuffix == rhs.sourceSuffix
141            && headerSearchPaths == rhs.headerSearchPaths
142            && sourceSearchPaths == rhs.sourceSearchPaths
143            && licenseTemplatePath == rhs.licenseTemplatePath;
144 }
145 
146 // Replacements of special license template keywords.
keyWordReplacement(const QString & keyWord,QString * value)147 static bool keyWordReplacement(const QString &keyWord,
148                                QString *value)
149 {
150     if (keyWord == QLatin1String("%YEAR%")) {
151         *value = QLatin1String("%{CurrentDate:yyyy}");
152         return true;
153     }
154     if (keyWord == QLatin1String("%MONTH%")) {
155         *value = QLatin1String("%{CurrentDate:M}");
156         return true;
157     }
158     if (keyWord == QLatin1String("%DAY%")) {
159         *value = QLatin1String("%{CurrentDate:d}");
160         return true;
161     }
162     if (keyWord == QLatin1String("%CLASS%")) {
163         *value = QLatin1String("%{Cpp:License:ClassName}");
164         return true;
165     }
166     if (keyWord == QLatin1String("%FILENAME%")) {
167         *value = QLatin1String("%{Cpp:License:FileName}");
168         return true;
169     }
170     if (keyWord == QLatin1String("%DATE%")) {
171         static QString format;
172         // ensure a format with 4 year digits. Some have locales have 2.
173         if (format.isEmpty()) {
174             QLocale loc;
175             format = loc.dateFormat(QLocale::ShortFormat);
176             const QChar ypsilon = QLatin1Char('y');
177             if (format.count(ypsilon) == 2)
178                 format.insert(format.indexOf(ypsilon), QString(2, ypsilon));
179             format.replace('/', "\\/");
180         }
181         *value = QString::fromLatin1("%{CurrentDate:") + format + QLatin1Char('}');
182         return true;
183     }
184     if (keyWord == QLatin1String("%USER%")) {
185         *value = Utils::HostOsInfo::isWindowsHost() ? QLatin1String("%{Env:USERNAME}")
186                                                     : QLatin1String("%{Env:USER}");
187         return true;
188     }
189     // Environment variables (for example '%$EMAIL%').
190     if (keyWord.startsWith(QLatin1String("%$"))) {
191         const QString varName = keyWord.mid(2, keyWord.size() - 3);
192         *value = QString::fromLatin1("%{Env:") + varName + QLatin1Char('}');
193         return true;
194     }
195     return false;
196 }
197 
198 // Parse a license template, scan for %KEYWORD% and replace if known.
199 // Replace '%%' by '%'.
parseLicenseTemplatePlaceholders(QString * t)200 static void parseLicenseTemplatePlaceholders(QString *t)
201 {
202     int pos = 0;
203     const QChar placeHolder = QLatin1Char('%');
204     do {
205         const int placeHolderPos = t->indexOf(placeHolder, pos);
206         if (placeHolderPos == -1)
207             break;
208         const int endPlaceHolderPos = t->indexOf(placeHolder, placeHolderPos + 1);
209         if (endPlaceHolderPos == -1)
210             break;
211         if (endPlaceHolderPos == placeHolderPos + 1) { // '%%' -> '%'
212             t->remove(placeHolderPos, 1);
213             pos = placeHolderPos + 1;
214         } else {
215             const QString keyWord = t->mid(placeHolderPos, endPlaceHolderPos + 1 - placeHolderPos);
216             QString replacement;
217             if (keyWordReplacement(keyWord, &replacement)) {
218                 t->replace(placeHolderPos, keyWord.size(), replacement);
219                 pos = placeHolderPos + replacement.size();
220             } else {
221                 // Leave invalid keywords as is.
222                 pos = endPlaceHolderPos + 1;
223             }
224         }
225     } while (pos < t->size());
226 
227 }
228 
229 // Convenience that returns the formatted license template.
licenseTemplate()230 QString CppFileSettings::licenseTemplate()
231 {
232     const QSettings *s = Core::ICore::settings();
233     QString key = QLatin1String(Constants::CPPTOOLS_SETTINGSGROUP);
234     key += QLatin1Char('/');
235     key += QLatin1String(licenseTemplatePathKeyC);
236     const QString path = s->value(key, QString()).toString();
237     if (path.isEmpty())
238         return QString();
239     QFile file(path);
240     if (!file.open(QIODevice::ReadOnly|QIODevice::Text)) {
241         qWarning("Unable to open the license template %s: %s", qPrintable(path), qPrintable(file.errorString()));
242         return QString();
243     }
244 
245     QTextStream licenseStream(&file);
246     licenseStream.setAutoDetectUnicode(true);
247     QString license = licenseStream.readAll();
248 
249     parseLicenseTemplatePlaceholders(&license);
250 
251     // Ensure at least one newline at the end of the license template to separate it from the code
252     const QChar newLine = QLatin1Char('\n');
253     if (!license.endsWith(newLine))
254         license += newLine;
255 
256     return license;
257 }
258 
259 // ------------------ CppFileSettingsWidget
260 
261 class CppFileSettingsWidget final : public Core::IOptionsPageWidget
262 {
263     Q_DECLARE_TR_FUNCTIONS(CppTools::Internal::CppFileSettingsWidget)
264 
265 public:
266     explicit CppFileSettingsWidget(CppFileSettings *settings);
267 
268     void apply() final;
269 
270     void setSettings(const CppFileSettings &s);
271 
272 private:
273     void slotEdit();
274     QString licenseTemplatePath() const;
275     void setLicenseTemplatePath(const QString &);
276 
277     Ui::CppFileSettingsPage m_ui;
278     CppFileSettings *m_settings = nullptr;
279 };
280 
CppFileSettingsWidget(CppFileSettings * settings)281 CppFileSettingsWidget::CppFileSettingsWidget(CppFileSettings *settings)
282     : m_settings(settings)
283 {
284     m_ui.setupUi(this);
285     // populate suffix combos
286     const Utils::MimeType sourceMt = Utils::mimeTypeForName(QLatin1String(CppTools::Constants::CPP_SOURCE_MIMETYPE));
287     if (sourceMt.isValid()) {
288         foreach (const QString &suffix, sourceMt.suffixes())
289             m_ui.sourceSuffixComboBox->addItem(suffix);
290     }
291 
292     const Utils::MimeType headerMt = Utils::mimeTypeForName(QLatin1String(CppTools::Constants::CPP_HEADER_MIMETYPE));
293     if (headerMt.isValid()) {
294         foreach (const QString &suffix, headerMt.suffixes())
295             m_ui.headerSuffixComboBox->addItem(suffix);
296     }
297     m_ui.licenseTemplatePathChooser->setExpectedKind(Utils::PathChooser::File);
298     m_ui.licenseTemplatePathChooser->setHistoryCompleter(QLatin1String("Cpp.LicenseTemplate.History"));
299     m_ui.licenseTemplatePathChooser->addButton(tr("Edit..."), this, [this] { slotEdit(); });
300 
301     setSettings(*m_settings);
302 }
303 
licenseTemplatePath() const304 QString CppFileSettingsWidget::licenseTemplatePath() const
305 {
306     return m_ui.licenseTemplatePathChooser->filePath().toString();
307 }
308 
setLicenseTemplatePath(const QString & lp)309 void CppFileSettingsWidget::setLicenseTemplatePath(const QString &lp)
310 {
311     m_ui.licenseTemplatePathChooser->setPath(lp);
312 }
313 
trimmedPaths(const QString & paths)314 static QStringList trimmedPaths(const QString &paths)
315 {
316     QStringList res;
317     foreach (const QString &path, paths.split(QLatin1Char(','), Qt::SkipEmptyParts))
318         res << path.trimmed();
319     return res;
320 }
321 
apply()322 void CppFileSettingsWidget::apply()
323 {
324     CppFileSettings rc;
325     rc.lowerCaseFiles = m_ui.lowerCaseFileNamesCheckBox->isChecked();
326     rc.headerPragmaOnce = m_ui.headerPragmaOnceCheckBox->isChecked();
327     rc.headerPrefixes = trimmedPaths(m_ui.headerPrefixesEdit->text());
328     rc.sourcePrefixes = trimmedPaths(m_ui.sourcePrefixesEdit->text());
329     rc.headerSuffix = m_ui.headerSuffixComboBox->currentText();
330     rc.sourceSuffix = m_ui.sourceSuffixComboBox->currentText();
331     rc.headerSearchPaths = trimmedPaths(m_ui.headerSearchPathsEdit->text());
332     rc.sourceSearchPaths = trimmedPaths(m_ui.sourceSearchPathsEdit->text());
333     rc.licenseTemplatePath = licenseTemplatePath();
334 
335     if (rc == *m_settings)
336         return;
337 
338     *m_settings = rc;
339     m_settings->toSettings(Core::ICore::settings());
340     m_settings->applySuffixesToMimeDB();
341     CppToolsPlugin::clearHeaderSourceCache();
342 }
343 
setComboText(QComboBox * cb,const QString & text,int defaultIndex=0)344 static inline void setComboText(QComboBox *cb, const QString &text, int defaultIndex = 0)
345 {
346     const int index = cb->findText(text);
347     cb->setCurrentIndex(index == -1 ? defaultIndex: index);
348 }
349 
setSettings(const CppFileSettings & s)350 void CppFileSettingsWidget::setSettings(const CppFileSettings &s)
351 {
352     const QChar comma = QLatin1Char(',');
353     m_ui.lowerCaseFileNamesCheckBox->setChecked(s.lowerCaseFiles);
354     m_ui.headerPragmaOnceCheckBox->setChecked(s.headerPragmaOnce);
355     m_ui.headerPrefixesEdit->setText(s.headerPrefixes.join(comma));
356     m_ui.sourcePrefixesEdit->setText(s.sourcePrefixes.join(comma));
357     setComboText(m_ui.headerSuffixComboBox, s.headerSuffix);
358     setComboText(m_ui.sourceSuffixComboBox, s.sourceSuffix);
359     m_ui.headerSearchPathsEdit->setText(s.headerSearchPaths.join(comma));
360     m_ui.sourceSearchPathsEdit->setText(s.sourceSearchPaths.join(comma));
361     setLicenseTemplatePath(s.licenseTemplatePath);
362 }
363 
slotEdit()364 void CppFileSettingsWidget::slotEdit()
365 {
366     QString path = licenseTemplatePath();
367     if (path.isEmpty()) {
368         // Pick a file name and write new template, edit with C++
369         path = QFileDialog::getSaveFileName(this, tr("Choose Location for New License Template File"));
370         if (path.isEmpty())
371             return;
372         Utils::FileSaver saver(Utils::FilePath::fromString(path), QIODevice::Text);
373         saver.write(tr(licenseTemplateTemplate).arg(Core::Constants::IDE_DISPLAY_NAME).toUtf8());
374         if (!saver.finalize(this))
375             return;
376         setLicenseTemplatePath(path);
377     }
378     // Edit (now) existing file with C++
379     Core::EditorManager::openEditor(path, CppEditor::Constants::CPPEDITOR_ID);
380 }
381 
382 // --------------- CppFileSettingsPage
383 
CppFileSettingsPage(CppFileSettings * settings)384 CppFileSettingsPage::CppFileSettingsPage(CppFileSettings *settings)
385 {
386     setId(Constants::CPP_FILE_SETTINGS_ID);
387     setDisplayName(QCoreApplication::translate("CppTools", Constants::CPP_FILE_SETTINGS_NAME));
388     setCategory(Constants::CPP_SETTINGS_CATEGORY);
389     setWidgetCreator([settings] { return new CppFileSettingsWidget(settings); });
390 }
391 
392 } // namespace Internal
393 } // namespace CppTools
394