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