1 /*
2 * Cppcheck - A tool for static C/C++ code analysis
3 * Copyright (C) 2007-2021 Cppcheck team.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "settingsdialog.h"
20
21 #include <QWidget>
22 #include <QList>
23 #include <QFileDialog>
24 #include <QFileInfo>
25 #include <QThread>
26 #include <QSettings>
27 #include "applicationdialog.h"
28 #include "applicationlist.h"
29 #include "translationhandler.h"
30 #include "codeeditorstyle.h"
31 #include "codeeditstyledialog.h"
32 #include "common.h"
33
SettingsDialog(ApplicationList * list,TranslationHandler * translator,QWidget * parent)34 SettingsDialog::SettingsDialog(ApplicationList *list,
35 TranslationHandler *translator,
36 QWidget *parent) :
37 QDialog(parent),
38 mApplications(list),
39 mTempApplications(new ApplicationList(this)),
40 mTranslator(translator)
41 {
42 mUI.setupUi(this);
43 mUI.mPythonPathWarning->setStyleSheet("color: red");
44 QSettings settings;
45 mTempApplications->copy(list);
46
47 mUI.mJobs->setText(settings.value(SETTINGS_CHECK_THREADS, 1).toString());
48 mUI.mForce->setCheckState(boolToCheckState(settings.value(SETTINGS_CHECK_FORCE, false).toBool()));
49 mUI.mShowFullPath->setCheckState(boolToCheckState(settings.value(SETTINGS_SHOW_FULL_PATH, false).toBool()));
50 mUI.mShowNoErrorsMessage->setCheckState(boolToCheckState(settings.value(SETTINGS_SHOW_NO_ERRORS, false).toBool()));
51 mUI.mShowDebugWarnings->setCheckState(boolToCheckState(settings.value(SETTINGS_SHOW_DEBUG_WARNINGS, false).toBool()));
52 mUI.mSaveAllErrors->setCheckState(boolToCheckState(settings.value(SETTINGS_SAVE_ALL_ERRORS, false).toBool()));
53 mUI.mSaveFullPath->setCheckState(boolToCheckState(settings.value(SETTINGS_SAVE_FULL_PATH, false).toBool()));
54 mUI.mInlineSuppressions->setCheckState(boolToCheckState(settings.value(SETTINGS_INLINE_SUPPRESSIONS, false).toBool()));
55 mUI.mEnableInconclusive->setCheckState(boolToCheckState(settings.value(SETTINGS_INCONCLUSIVE_ERRORS, false).toBool()));
56 mUI.mShowStatistics->setCheckState(boolToCheckState(settings.value(SETTINGS_SHOW_STATISTICS, false).toBool()));
57 mUI.mShowErrorId->setCheckState(boolToCheckState(settings.value(SETTINGS_SHOW_ERROR_ID, false).toBool()));
58 mUI.mEditPythonPath->setText(settings.value(SETTINGS_PYTHON_PATH, QString()).toString());
59 validateEditPythonPath();
60 mUI.mEditMisraFile->setText(settings.value(SETTINGS_MISRA_FILE, QString()).toString());
61
62 #ifdef Q_OS_WIN
63 //mUI.mTabClang->setVisible(true);
64 mUI.mEditClangPath->setText(settings.value(SETTINGS_CLANG_PATH, QString()).toString());
65 mUI.mEditVsIncludePaths->setText(settings.value(SETTINGS_VS_INCLUDE_PATHS, QString()).toString());
66 connect(mUI.mBtnBrowseClangPath, &QPushButton::released, this, &SettingsDialog::browseClangPath);
67 #else
68 mUI.mTabClang->setVisible(false);
69 #endif
70 mCurrentStyle = new CodeEditorStyle(CodeEditorStyle::loadSettings(&settings));
71 manageStyleControls();
72
73 connect(mUI.mEditPythonPath, SIGNAL(textEdited(const QString&)),
74 this, SLOT(validateEditPythonPath()));
75
76 connect(mUI.mButtons, &QDialogButtonBox::accepted, this, &SettingsDialog::ok);
77 connect(mUI.mButtons, &QDialogButtonBox::rejected, this, &SettingsDialog::reject);
78 connect(mUI.mBtnAddApplication, SIGNAL(clicked()),
79 this, SLOT(addApplication()));
80 connect(mUI.mBtnRemoveApplication, SIGNAL(clicked()),
81 this, SLOT(removeApplication()));
82 connect(mUI.mBtnEditApplication, SIGNAL(clicked()),
83 this, SLOT(editApplication()));
84 connect(mUI.mBtnDefaultApplication, SIGNAL(clicked()),
85 this, SLOT(defaultApplication()));
86 connect(mUI.mListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
87 this, SLOT(editApplication()));
88
89 connect(mUI.mBtnBrowsePythonPath, &QPushButton::clicked, this, &SettingsDialog::browsePythonPath);
90 connect(mUI.mBtnBrowseMisraFile, &QPushButton::clicked, this, &SettingsDialog::browseMisraFile);
91 connect(mUI.mBtnEditTheme, SIGNAL(clicked()), this, SLOT(editCodeEditorStyle()));
92 connect(mUI.mThemeSystem, SIGNAL(released()), this, SLOT(setCodeEditorStyleDefault()));
93 connect(mUI.mThemeDark, SIGNAL(released()), this, SLOT(setCodeEditorStyleDefault()));
94 connect(mUI.mThemeLight, SIGNAL(released()), this, SLOT(setCodeEditorStyleDefault()));
95 connect(mUI.mThemeCustom, SIGNAL(toggled(bool)), mUI.mBtnEditTheme, SLOT(setEnabled(bool)));
96
97 mUI.mListWidget->setSortingEnabled(false);
98 populateApplicationList();
99
100 const int count = QThread::idealThreadCount();
101 if (count != -1)
102 mUI.mLblIdealThreads->setText(QString::number(count));
103 else
104 mUI.mLblIdealThreads->setText(tr("N/A"));
105
106 loadSettings();
107 initTranslationsList();
108 }
109
~SettingsDialog()110 SettingsDialog::~SettingsDialog()
111 {
112 saveSettings();
113 }
114
initTranslationsList()115 void SettingsDialog::initTranslationsList()
116 {
117 const QString current = mTranslator->getCurrentLanguage();
118 QList<TranslationInfo> translations = mTranslator->getTranslations();
119 foreach (TranslationInfo translation, translations) {
120 QListWidgetItem *item = new QListWidgetItem;
121 item->setText(translation.mName);
122 item->setData(mLangCodeRole, QVariant(translation.mCode));
123 mUI.mListLanguages->addItem(item);
124 if (translation.mCode == current || translation.mCode == current.mid(0, 2))
125 mUI.mListLanguages->setCurrentItem(item);
126 }
127 }
128
boolToCheckState(bool yes)129 Qt::CheckState SettingsDialog::boolToCheckState(bool yes)
130 {
131 if (yes) {
132 return Qt::Checked;
133 }
134 return Qt::Unchecked;
135 }
136
checkStateToBool(Qt::CheckState state)137 bool SettingsDialog::checkStateToBool(Qt::CheckState state)
138 {
139 if (state == Qt::Checked) {
140 return true;
141 }
142 return false;
143 }
144
145
loadSettings()146 void SettingsDialog::loadSettings()
147 {
148 QSettings settings;
149 resize(settings.value(SETTINGS_CHECK_DIALOG_WIDTH, 800).toInt(),
150 settings.value(SETTINGS_CHECK_DIALOG_HEIGHT, 600).toInt());
151 }
152
saveSettings() const153 void SettingsDialog::saveSettings() const
154 {
155 QSettings settings;
156 settings.setValue(SETTINGS_CHECK_DIALOG_WIDTH, size().width());
157 settings.setValue(SETTINGS_CHECK_DIALOG_HEIGHT, size().height());
158 }
159
saveSettingValues() const160 void SettingsDialog::saveSettingValues() const
161 {
162 int jobs = mUI.mJobs->text().toInt();
163 if (jobs <= 0) {
164 jobs = 1;
165 }
166
167 QSettings settings;
168 settings.setValue(SETTINGS_CHECK_THREADS, jobs);
169 saveCheckboxValue(&settings, mUI.mForce, SETTINGS_CHECK_FORCE);
170 saveCheckboxValue(&settings, mUI.mSaveAllErrors, SETTINGS_SAVE_ALL_ERRORS);
171 saveCheckboxValue(&settings, mUI.mSaveFullPath, SETTINGS_SAVE_FULL_PATH);
172 saveCheckboxValue(&settings, mUI.mShowFullPath, SETTINGS_SHOW_FULL_PATH);
173 saveCheckboxValue(&settings, mUI.mShowNoErrorsMessage, SETTINGS_SHOW_NO_ERRORS);
174 saveCheckboxValue(&settings, mUI.mShowDebugWarnings, SETTINGS_SHOW_DEBUG_WARNINGS);
175 saveCheckboxValue(&settings, mUI.mInlineSuppressions, SETTINGS_INLINE_SUPPRESSIONS);
176 saveCheckboxValue(&settings, mUI.mEnableInconclusive, SETTINGS_INCONCLUSIVE_ERRORS);
177 saveCheckboxValue(&settings, mUI.mShowStatistics, SETTINGS_SHOW_STATISTICS);
178 saveCheckboxValue(&settings, mUI.mShowErrorId, SETTINGS_SHOW_ERROR_ID);
179 settings.setValue(SETTINGS_PYTHON_PATH, mUI.mEditPythonPath->text());
180 settings.setValue(SETTINGS_MISRA_FILE, mUI.mEditMisraFile->text());
181
182 #ifdef Q_OS_WIN
183 settings.setValue(SETTINGS_CLANG_PATH, mUI.mEditClangPath->text());
184 settings.setValue(SETTINGS_VS_INCLUDE_PATHS, mUI.mEditVsIncludePaths->text());
185 #endif
186
187 const QListWidgetItem *currentLang = mUI.mListLanguages->currentItem();
188 if (currentLang) {
189 const QString langcode = currentLang->data(mLangCodeRole).toString();
190 settings.setValue(SETTINGS_LANGUAGE, langcode);
191 }
192 CodeEditorStyle::saveSettings(&settings, *mCurrentStyle);
193 }
194
saveCheckboxValue(QSettings * settings,QCheckBox * box,const QString & name)195 void SettingsDialog::saveCheckboxValue(QSettings *settings, QCheckBox *box,
196 const QString &name)
197 {
198 settings->setValue(name, checkStateToBool(box->checkState()));
199 }
200
validateEditPythonPath()201 void SettingsDialog::validateEditPythonPath()
202 {
203 const auto pythonPath = mUI.mEditPythonPath->text();
204 if (pythonPath.isEmpty()) {
205 mUI.mEditPythonPath->setStyleSheet("");
206 mUI.mPythonPathWarning->hide();
207 return;
208 }
209
210 QFileInfo pythonPathInfo(pythonPath);
211 if (!pythonPathInfo.exists() ||
212 !pythonPathInfo.isFile() ||
213 !pythonPathInfo.isExecutable()) {
214 mUI.mEditPythonPath->setStyleSheet("QLineEdit {border: 1px solid red}");
215 mUI.mPythonPathWarning->setText(tr("The executable file \"%1\" is not available").arg(pythonPath));
216 mUI.mPythonPathWarning->show();
217 } else {
218 mUI.mEditPythonPath->setStyleSheet("");
219 mUI.mPythonPathWarning->hide();
220 }
221 }
222
addApplication()223 void SettingsDialog::addApplication()
224 {
225 Application app;
226 ApplicationDialog dialog(tr("Add a new application"), app, this);
227
228 if (dialog.exec() == QDialog::Accepted) {
229 mTempApplications->addApplication(app);
230 mUI.mListWidget->addItem(app.getName());
231 }
232 }
233
removeApplication()234 void SettingsDialog::removeApplication()
235 {
236 QList<QListWidgetItem *> selected = mUI.mListWidget->selectedItems();
237 foreach (QListWidgetItem *item, selected) {
238 const int removeIndex = mUI.mListWidget->row(item);
239 const int currentDefault = mTempApplications->getDefaultApplication();
240 mTempApplications->removeApplication(removeIndex);
241 if (removeIndex == currentDefault)
242 // If default app is removed set default to unknown
243 mTempApplications->setDefault(-1);
244 else if (removeIndex < currentDefault)
245 // Move default app one up if earlier app was removed
246 mTempApplications->setDefault(currentDefault - 1);
247 }
248 mUI.mListWidget->clear();
249 populateApplicationList();
250 }
251
editApplication()252 void SettingsDialog::editApplication()
253 {
254 QList<QListWidgetItem *> selected = mUI.mListWidget->selectedItems();
255 QListWidgetItem *item = nullptr;
256 foreach (item, selected) {
257 int row = mUI.mListWidget->row(item);
258 Application& app = mTempApplications->getApplication(row);
259 ApplicationDialog dialog(tr("Modify an application"), app, this);
260
261 if (dialog.exec() == QDialog::Accepted) {
262 QString name = app.getName();
263 if (mTempApplications->getDefaultApplication() == row)
264 name += tr(" [Default]");
265 item->setText(name);
266 }
267 }
268 }
269
defaultApplication()270 void SettingsDialog::defaultApplication()
271 {
272 QList<QListWidgetItem *> selected = mUI.mListWidget->selectedItems();
273 if (!selected.isEmpty()) {
274 int index = mUI.mListWidget->row(selected[0]);
275 mTempApplications->setDefault(index);
276 mUI.mListWidget->clear();
277 populateApplicationList();
278 }
279 }
280
populateApplicationList()281 void SettingsDialog::populateApplicationList()
282 {
283 const int defapp = mTempApplications->getDefaultApplication();
284 for (int i = 0; i < mTempApplications->getApplicationCount(); i++) {
285 const Application& app = mTempApplications->getApplication(i);
286 QString name = app.getName();
287 if (i == defapp) {
288 name += " ";
289 name += tr("[Default]");
290 }
291 mUI.mListWidget->addItem(name);
292 }
293
294 // Select default application, or if there is no default app then the
295 // first item.
296 if (defapp == -1)
297 mUI.mListWidget->setCurrentRow(0);
298 else {
299 if (mTempApplications->getApplicationCount() > defapp)
300 mUI.mListWidget->setCurrentRow(defapp);
301 else
302 mUI.mListWidget->setCurrentRow(0);
303 }
304 }
305
ok()306 void SettingsDialog::ok()
307 {
308 mApplications->copy(mTempApplications);
309 accept();
310 }
311
showFullPath() const312 bool SettingsDialog::showFullPath() const
313 {
314 return checkStateToBool(mUI.mShowFullPath->checkState());
315 }
316
saveFullPath() const317 bool SettingsDialog::saveFullPath() const
318 {
319 return checkStateToBool(mUI.mSaveFullPath->checkState());
320 }
321
saveAllErrors() const322 bool SettingsDialog::saveAllErrors() const
323 {
324 return checkStateToBool(mUI.mSaveAllErrors->checkState());
325 }
326
showNoErrorsMessage() const327 bool SettingsDialog::showNoErrorsMessage() const
328 {
329 return checkStateToBool(mUI.mShowNoErrorsMessage->checkState());
330 }
331
showErrorId() const332 bool SettingsDialog::showErrorId() const
333 {
334 return checkStateToBool(mUI.mShowErrorId->checkState());
335 }
336
showInconclusive() const337 bool SettingsDialog::showInconclusive() const
338 {
339 return checkStateToBool(mUI.mEnableInconclusive->checkState());
340 }
341
browsePythonPath()342 void SettingsDialog::browsePythonPath()
343 {
344 QString fileName = QFileDialog::getOpenFileName(this, tr("Select python binary"), QDir::rootPath());
345 if (fileName.contains("python", Qt::CaseInsensitive))
346 mUI.mEditPythonPath->setText(fileName);
347 }
348
browseMisraFile()349 void SettingsDialog::browseMisraFile()
350 {
351 const QString fileName = QFileDialog::getOpenFileName(this, tr("Select MISRA File"), QDir::homePath(), "Misra File (*.pdf *.txt)");
352 if (!fileName.isEmpty())
353 mUI.mEditMisraFile->setText(fileName);
354 }
355
356 // Slot to set default light style
setCodeEditorStyleDefault()357 void SettingsDialog::setCodeEditorStyleDefault()
358 {
359 if (mUI.mThemeSystem->isChecked())
360 *mCurrentStyle = CodeEditorStyle::getSystemTheme();
361 if (mUI.mThemeLight->isChecked())
362 *mCurrentStyle = defaultStyleLight;
363 if (mUI.mThemeDark->isChecked())
364 *mCurrentStyle = defaultStyleDark;
365 manageStyleControls();
366 }
367
368 // Slot to edit custom style
editCodeEditorStyle()369 void SettingsDialog::editCodeEditorStyle()
370 {
371 StyleEditDialog dlg(*mCurrentStyle, this);
372 int nResult = dlg.exec();
373 if (nResult == QDialog::Accepted) {
374 *mCurrentStyle = dlg.getStyle();
375 manageStyleControls();
376 }
377 }
378
browseClangPath()379 void SettingsDialog::browseClangPath()
380 {
381 QString selectedDir = QFileDialog::getExistingDirectory(this,
382 tr("Select clang path"),
383 QDir::rootPath());
384
385 if (!selectedDir.isEmpty()) {
386 mUI.mEditClangPath->setText(selectedDir);
387 }
388 }
389
manageStyleControls()390 void SettingsDialog::manageStyleControls()
391 {
392 bool isSystemTheme = mCurrentStyle->isSystemTheme();
393 bool isDefaultLight = !isSystemTheme && *mCurrentStyle == defaultStyleLight;
394 bool isDefaultDark = !isSystemTheme && *mCurrentStyle == defaultStyleDark;
395 mUI.mThemeSystem->setChecked(isSystemTheme);
396 mUI.mThemeLight->setChecked(isDefaultLight && !isDefaultDark);
397 mUI.mThemeDark->setChecked(!isDefaultLight && isDefaultDark);
398 mUI.mThemeCustom->setChecked(!isSystemTheme && !isDefaultLight && !isDefaultDark);
399 mUI.mBtnEditTheme->setEnabled(!isSystemTheme && !isDefaultLight && !isDefaultDark);
400 }
401
402