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