1/*
2 * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com)
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17#include "editorcolorschemewidget.h"
18#include "ui_editorcolorschemewidget.h"
19#include "../settings.h"
20#include "../colorscheme.h"
21#include "../mainwindow.h"
22
23#include <QAction>
24#include <QMessageBox>
25#include <QDebug>
26#include <QInputDialog>
27#include <QFileDialog>
28
29EditorColorSchemeWidget::EditorColorSchemeWidget(const QString& name, const QString& group, QWidget *parent) :
30 SettingsWidget(name,group,parent),
31 ui(new Ui::EditorColorSchemeWidget)
32{
33 ui->setupUi(this);
34 mStatementColors = std::make_shared<QHash<StatementKind, std::shared_ptr<ColorSchemeItem> >>();
35
36 mDefaultSchemeComboFont = ui->cbScheme->font();
37 mModifiedSchemeComboFont = mDefaultSchemeComboFont;
38 mModifiedSchemeComboFont.setBold(true);
39 int schemeCount=0;
40 for (QString schemeName: pColorManager->getSchemes()) {
41 PColorScheme scheme = pColorManager->get(schemeName);
42 if (!scheme)
43 return;
44 ui->cbScheme->addItem(schemeName);
45 if (scheme->customed())
46 ui->cbScheme->setItemData(schemeCount,mModifiedSchemeComboFont,Qt::FontRole);
47 schemeCount++;
48 }
49 ui->treeItems->setModel(&mDefinesModel);
50 mDefinesModel.setHorizontalHeaderLabels(QStringList());
51 for (QString defineName : pColorManager->getDefines()) {
52 addDefine(defineName, pColorManager->getDefine(defineName));
53 }
54 ui->treeItems->expandAll();
55 QModelIndex groupIndex = mDefinesModel.index(0,0);
56 QModelIndex index = mDefinesModel.index(0,0,groupIndex);
57 ui->treeItems->setCurrentIndex(index);
58 connect(ui->treeItems->selectionModel(), &QItemSelectionModel::selectionChanged,
59 this, &EditorColorSchemeWidget::onItemSelectionChanged);
60 connect(ui->cbScheme, QOverload<int>::of(&QComboBox::currentIndexChanged),
61 this, &EditorColorSchemeWidget::changeSchemeComboFont);
62 connect(ui->cbScheme, QOverload<int>::of(&QComboBox::currentIndexChanged),
63 this, &EditorColorSchemeWidget::onItemSelectionChanged);
64 connect(this, &SettingsWidget::settingsChanged,this,
65 &EditorColorSchemeWidget::onSettingChanged);
66 ui->editDemo->document()->setText(
67 "#include <iostream>\n"
68 "#include <conio.h>\n"
69 "\n"
70 "int x=10;\n"
71 "\n"
72 "int main(int argc, char **argv)\n"
73 "{\n"
74 " int numbers[20];\n"
75 " float average, total; //breakpoint\n"
76 " for (int i = 0; i <= 19; i++)\n"
77 " { // active breakpoint\n"
78 " numbers[i] = i+x;\n"
79 " Total += i; // error line\n"
80 " }\n"
81 " average = total / 20; // comment\n"
82 " cout << \"total: \" << total << \"\nAverage: \" << average;\n"
83 " getch();\n"
84 "}\n"
85 );
86 ui->editDemo->setReadOnly(true);
87 ui->editDemo->setStatementColors(mStatementColors);
88 onItemSelectionChanged();
89}
90
91void EditorColorSchemeWidget::addDefine(const QString& name, PColorSchemeItemDefine define)
92{
93 QList<QStandardItem*> items = mDefinesModel.findItems(define->group());
94 QStandardItem* pGroupItem;
95 if (items.count() == 0 ) {
96 pGroupItem = new QStandardItem(define->group());
97 pGroupItem->setData("", NameRole);
98 mDefinesModel.appendRow(pGroupItem);
99 } else {
100 pGroupItem = items[0];
101 }
102 QStandardItem* pWidgetItem = new QStandardItem(define->displayName());
103 pWidgetItem->setData(name, NameRole);
104 pGroupItem->appendRow(pWidgetItem);
105}
106
107PColorSchemeItem EditorColorSchemeWidget::getCurrentItem()
108{
109 QItemSelectionModel * selectionModel = ui->treeItems->selectionModel();
110 QString name =mDefinesModel.data(selectionModel->currentIndex(),NameRole).toString();
111 if (name.isEmpty())
112 return PColorSchemeItem();
113 return pColorManager->getItem(ui->cbScheme->currentText(), name);
114}
115
116PColorScheme EditorColorSchemeWidget::getCurrentScheme()
117{
118 return pColorManager->get(ui->cbScheme->currentText());
119}
120
121void EditorColorSchemeWidget::connectModificationSlots()
122{
123 connect(ui->cbBackground,&QCheckBox::stateChanged,
124 this, &EditorColorSchemeWidget::onBackgroundChanged);
125 connect(ui->colorBackground,&ColorEdit::colorChanged,
126 this, &EditorColorSchemeWidget::onBackgroundChanged);
127 connect(ui->cbForeground,&QCheckBox::stateChanged,
128 this, &EditorColorSchemeWidget::onForegroundChanged);
129 connect(ui->colorForeground,&ColorEdit::colorChanged,
130 this, &EditorColorSchemeWidget::onForegroundChanged);
131 connect(ui->cbBold,&QCheckBox::stateChanged,
132 this, &EditorColorSchemeWidget::onFontStyleChanged);
133 connect(ui->cbItalic,&QCheckBox::stateChanged,
134 this, &EditorColorSchemeWidget::onFontStyleChanged);
135 connect(ui->cbStrikeout,&QCheckBox::stateChanged,
136 this, &EditorColorSchemeWidget::onFontStyleChanged);
137 connect(ui->cbUnderlined,&QCheckBox::stateChanged,
138 this, &EditorColorSchemeWidget::onFontStyleChanged);
139}
140
141void EditorColorSchemeWidget::disconnectModificationSlots()
142{
143 disconnect(ui->cbBackground,&QCheckBox::stateChanged,
144 this, &EditorColorSchemeWidget::onBackgroundChanged);
145 disconnect(ui->colorBackground,&ColorEdit::colorChanged,
146 this, &EditorColorSchemeWidget::onBackgroundChanged);
147 disconnect(ui->cbForeground,&QCheckBox::stateChanged,
148 this, &EditorColorSchemeWidget::onForegroundChanged);
149 disconnect(ui->colorForeground,&ColorEdit::colorChanged,
150 this, &EditorColorSchemeWidget::onForegroundChanged);
151 disconnect(ui->cbBold,&QCheckBox::stateChanged,
152 this, &EditorColorSchemeWidget::onFontStyleChanged);
153 disconnect(ui->cbItalic,&QCheckBox::stateChanged,
154 this, &EditorColorSchemeWidget::onFontStyleChanged);
155 disconnect(ui->cbStrikeout,&QCheckBox::stateChanged,
156 this, &EditorColorSchemeWidget::onFontStyleChanged);
157 disconnect(ui->cbUnderlined,&QCheckBox::stateChanged,
158 this, &EditorColorSchemeWidget::onFontStyleChanged);
159}
160
161void EditorColorSchemeWidget::setCurrentSchemeModified()
162{
163 PColorScheme scheme = getCurrentScheme();
164 if (scheme) {
165 scheme->setCustomed(true);
166 }
167// if (mModifiedSchemes.contains(ui->cbScheme->currentText()))
168// return;
169 mModifiedSchemes.insert(ui->cbScheme->currentText());
170 ui->cbScheme->setItemData(ui->cbScheme->currentIndex(),
171 mModifiedSchemeComboFont,Qt::FontRole);
172 ui->cbScheme->setFont(mModifiedSchemeComboFont);
173 //ui->cbScheme->view()->setFont(mDefaultSchemeComboFont);
174 //we must reset the editor here, because this slot is processed after the onSettingChanged
175 onSettingChanged();
176}
177
178EditorColorSchemeWidget::~EditorColorSchemeWidget()
179{
180 delete ui;
181}
182
183static void setColorProp(ColorEdit* ce, QCheckBox* cb, const QColor& color) {
184 if (color.isValid()) {
185 cb->setChecked(true);
186 ce->setColor(color);
187 ce->setVisible(true);
188 } else {
189 cb->setChecked(false);
190 ce->setVisible(false);
191 }
192}
193
194void EditorColorSchemeWidget::onItemSelectionChanged()
195{
196 disconnectModificationSlots();
197 QItemSelectionModel * selectionModel = ui->treeItems->selectionModel();
198 QString name =mDefinesModel.data(selectionModel->currentIndex(),NameRole).toString();
199 bool found = false;
200 if (!name.isEmpty()) {
201 PColorSchemeItemDefine define = pColorManager->getDefine(name);
202 if (define) {
203 found = true;
204 ui->cbBackground->setEnabled(define->hasBackground());
205 ui->colorBackground->setEnabled(define->hasBackground());
206 ui->cbForeground->setEnabled(define->hasForeground());
207 ui->colorForeground->setEnabled(define->hasForeground());
208 ui->grpFontStyles->setEnabled(define->hasFontStyle());
209 PColorSchemeItem item = pColorManager->getItem(ui->cbScheme->currentText(), name);
210 if (!item) {
211 PColorScheme scheme = pColorManager->get(ui->cbScheme->currentText());
212 if (scheme) {
213 scheme->addItem(name);
214 }
215 }
216 if (define->hasBackground() && item) {
217 setColorProp(ui->colorBackground, ui->cbBackground,item->background());
218 } else {
219 setColorProp(ui->colorBackground, ui->cbBackground,QColor());
220 }
221 if (define->hasForeground() && item) {
222 setColorProp(ui->colorForeground, ui->cbForeground,item->foreground());
223 } else {
224 setColorProp(ui->colorForeground, ui->cbForeground,QColor());
225 }
226 if (define->hasFontStyle() && item) {
227 ui->cbBold->setChecked(item->bold());
228 ui->cbItalic->setChecked(item->italic());
229 ui->cbUnderlined->setChecked(item->underlined());
230 ui->cbStrikeout->setChecked(item->strikeout());
231 } else {
232 ui->cbBold->setChecked(false);
233 ui->cbItalic->setChecked(false);
234 ui->cbUnderlined->setChecked(false);
235 ui->cbStrikeout->setChecked(false);
236 }
237 }
238 }
239
240 ui->widgetSchemeItem->setEnabled(found);
241 connectModificationSlots();
242}
243
244void EditorColorSchemeWidget::onSettingChanged()
245{
246 pColorManager->updateStatementColors(mStatementColors,ui->cbScheme->currentText());
247 ui->editDemo->applyColorScheme(ui->cbScheme->currentText());
248}
249
250void EditorColorSchemeWidget::onForegroundChanged()
251{
252 PColorSchemeItem item = getCurrentItem();
253 if (!item)
254 return;
255 ui->colorForeground->setVisible(ui->cbForeground->isChecked());
256 if (ui->cbForeground->isChecked()) {
257 item->setForeground(ui->colorForeground->color());
258 } else {
259 ui->colorForeground->setColor(QColor());
260 item->setForeground(QColor());
261 }
262 setCurrentSchemeModified();
263}
264
265void EditorColorSchemeWidget::onBackgroundChanged()
266{
267 PColorSchemeItem item = getCurrentItem();
268 if (!item)
269 return;
270 ui->colorBackground->setVisible(ui->cbBackground->isChecked());
271 if (ui->cbBackground->isChecked()) {
272 item->setBackground(ui->colorBackground->color());
273 } else {
274 ui->colorBackground->setColor(QColor());
275 item->setBackground(QColor());
276 }
277 setCurrentSchemeModified();
278}
279
280void EditorColorSchemeWidget::onFontStyleChanged()
281{
282 PColorSchemeItem item = getCurrentItem();
283 if (!item)
284 return;
285 item->setBold(ui->cbBold->isChecked());
286 item->setItalic(ui->cbItalic->isChecked());
287 item->setStrikeout(ui->cbStrikeout->isChecked());
288 item->setUnderlined(ui->cbUnderlined->isChecked());
289 setCurrentSchemeModified();
290}
291
292void EditorColorSchemeWidget::changeSchemeComboFont()
293{
294 QString name = ui->cbScheme->currentText();
295 PColorScheme scheme = pColorManager->get(name);
296 if (scheme && scheme->customed()) {
297 ui->cbScheme->setFont(mModifiedSchemeComboFont);
298 } else {
299 ui->cbScheme->setFont(mDefaultSchemeComboFont);
300 }
301 //ui->cbScheme->view()->setFont(mDefaultSchemeComboFont);
302}
303
304void EditorColorSchemeWidget::doLoad()
305{
306 ui->cbScheme->setCurrentText(pSettings->editor().colorScheme());
307 ui->chkRainborParenthesis->setChecked(pSettings->editor().rainbowParenthesis());
308}
309
310void EditorColorSchemeWidget::doSave()
311{
312 try {
313 for (QString name:mModifiedSchemes) {
314 pColorManager->saveScheme(name);
315 }
316 pSettings->editor().setColorScheme(ui->cbScheme->currentText());
317 pSettings->editor().setRainbowParenthesis(ui->chkRainborParenthesis->isChecked());
318 pSettings->editor().save();
319 pMainWindow->updateEditorColorSchemes();
320 } catch (FileError e) {
321 QMessageBox::critical(this,tr("Error"),e.reason());
322 }
323}
324
325void EditorColorSchemeWidget::on_actionCopy_Scheme_triggered()
326{
327 QString newName = pColorManager->copy(ui->cbScheme->currentText());
328 if (newName.isEmpty())
329 return;
330 ui->cbScheme->addItem(newName);
331 ui->cbScheme->setCurrentText(newName);
332}
333
334void EditorColorSchemeWidget::on_btnSchemeMenu_pressed()
335{
336 QMenu menu;
337
338 PColorScheme scheme = getCurrentScheme();
339 if (scheme) {
340 if (scheme->customed()) {
341 menu.addAction(ui->actionReset_Scheme);
342 }
343 if (!scheme->bundled()) {
344 menu.addAction(ui->actionRename_Scheme);
345 menu.addAction(ui->actionDelete_Scheme);
346 }
347 QString name = ui->cbScheme->currentText();
348 if (!pColorManager->exists(name+ " Copy"))
349 menu.addAction(ui->actionCopy_Scheme);
350 menu.addAction(ui->actionExport_Scheme);
351 menu.addSeparator();
352 }
353 menu.addAction(ui->actionImport_Scheme);
354 QPoint p;
355 p.setX(0);
356 p.setY(ui->btnSchemeMenu->height()+2);
357 menu.exec(ui->btnSchemeMenu->mapToGlobal(p));
358}
359
360void EditorColorSchemeWidget::on_actionImport_Scheme_triggered()
361{
362 QString filename = QFileDialog::getOpenFileName(this,
363 tr("Open"), QString(), tr("Color Scheme Files (*.scheme)"));
364 if (filename.isEmpty())
365 return;
366 QFileInfo fileInfo(filename);
367 QString name = fileInfo.fileName();
368 QString suffix = EXT_COLOR_SCHEME;
369 if (!name.toLower().endsWith(suffix))
370 return;
371 name.remove(name.length()-suffix.length(),suffix.length());
372 name.replace('_',' ');
373 if (!pColorManager->isValidName(name)) {
374 QMessageBox::critical(this,tr("Error"),tr("'%1' is not a valid name for color scheme file."));
375 return;
376 }
377 try {
378 PColorScheme scheme = ColorScheme::load(filename);
379 pColorManager->add(name, scheme);
380 ui->cbScheme->addItem(name);
381 ui->cbScheme->setCurrentText(name);
382 } catch (FileError e) {
383 QMessageBox::critical(this,tr("Error"),e.reason());
384 return;
385 }
386}
387
388void EditorColorSchemeWidget::on_actionRename_Scheme_triggered()
389{
390 QString name = ui->cbScheme->currentText();
391 bool isOk;
392 QString newName = QInputDialog::getText(this,tr("New scheme name"),tr("New scheme name"),
393 QLineEdit::Normal,name,&isOk);
394 if (isOk) {
395 if (!pColorManager->isValidName(newName)) {
396 QMessageBox::critical(this,tr("Error"),tr("'%1' is not a valid scheme name!").arg(newName));
397 return;
398 }
399 try {
400 pColorManager->rename(name,newName);
401 ui->cbScheme->setItemText(
402 ui->cbScheme->currentIndex(),
403 newName
404 );
405 if (mModifiedSchemes.contains(name))
406 mModifiedSchemes.remove(name);
407 mModifiedSchemes.insert(newName);
408 } catch(FileError e) {
409 QMessageBox::critical(this,tr("Error"),e.reason());
410 }
411 }
412}
413
414void EditorColorSchemeWidget::on_actionReset_Scheme_triggered()
415{
416 try {
417 if (pColorManager->restoreToDefault(ui->cbScheme->currentText())) {
418 ui->cbScheme->setItemData(
419 ui->cbScheme->currentIndex(),
420 QVariant(),
421 Qt::FontRole);
422 ui->cbScheme->setFont(mDefaultSchemeComboFont);
423 //ui->cbScheme->view()->setFont(mDefaultSchemeComboFont);
424 }
425 } catch (FileError e) {
426 QMessageBox::critical(this,tr("Error"),e.reason());
427 }
428
429}
430
431void EditorColorSchemeWidget::on_actionExport_Scheme_triggered()
432{
433 QString filename = QFileDialog::getSaveFileName(this,
434 tr("Save"), QString(), tr("Color Scheme Files (*.scheme)"));
435 if (filename.isEmpty())
436 return;
437 try {
438 PColorScheme scheme = getCurrentScheme();
439 scheme->save(filename);
440 } catch (FileError e) {
441 QMessageBox::critical(this,tr("Error"),e.reason());
442 return;
443 }
444}
445
446void EditorColorSchemeWidget::on_actionDelete_Scheme_triggered()
447{
448
449 QString name = ui->cbScheme->currentText();
450 if (QMessageBox::warning(this,tr("Confirm Delete Scheme"),
451 tr("Scheme '%1' will be deleted!<br />Do you really want to continue?")
452 .arg(name),
453 QMessageBox::Yes, QMessageBox::No)!=QMessageBox::Yes)
454 return;
455 try {
456 if (pColorManager->remove(name)) {
457 if (mModifiedSchemes.contains(name))
458 mModifiedSchemes.remove(name);
459 ui->cbScheme->removeItem(ui->cbScheme->currentIndex());
460 if (name == pSettings->editor().colorScheme())
461 doSave();
462 }
463 } catch (FileError e) {
464 QMessageBox::critical(this,tr("Error"),e.reason());
465 }
466}
467
468
469