1 /**
2  * UGENE - Integrated Bioinformatics Tools.
3  * Copyright (C) 2008-2021 UniPro <ugene@unipro.ru>
4  * http://ugene.net
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  */
21 
22 #include "ColorSchemaDialogController.h"
23 
24 #include <QColorDialog>
25 #include <QMessageBox>
26 #include <QPainter>
27 
28 #include <U2Algorithm/ColorSchemeUtils.h>
29 
30 #include <U2Core/AppContext.h>
31 #include <U2Core/FileAndDirectoryUtils.h>
32 #include <U2Core/GUrlUtils.h>
33 #include <U2Core/L10n.h>
34 #include <U2Core/QObjectScopedPointer.h>
35 #include <U2Core/Theme.h>
36 #include <U2Core/U2OpStatusUtils.h>
37 #include <U2Core/U2SafePoints.h>
38 
39 #include <U2Gui/HelpButton.h>
40 #include <U2Gui/U2FileDialog.h>
41 
42 namespace U2 {
43 
ColorSchemaDialogController(QMap<char,QColor> & colors)44 ColorSchemaDialogController::ColorSchemaDialogController(QMap<char, QColor> &colors)
45     : QDialog(),
46       alphabetColorsView(nullptr),
47       newColors(colors),
48       storedColors(colors) {
49 }
50 
adjustAlphabetColors()51 int ColorSchemaDialogController::adjustAlphabetColors() {
52     setupUi(this);
53     new HelpButton(this, buttonBox, "65929623");
54     buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK"));
55     buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
56     alphabetColorsView = new QPixmap(alphabetColorsFrame->size());
57     connect(clearButton, SIGNAL(clicked()), SLOT(sl_onClear()));
58     connect(restoreButton, SIGNAL(clicked()), SLOT(sl_onRestore()));
59 
60     update();
61 
62     return exec();
63 }
64 
~ColorSchemaDialogController()65 ColorSchemaDialogController::~ColorSchemaDialogController() {
66     delete alphabetColorsView;
67 }
68 
paintEvent(QPaintEvent *)69 void ColorSchemaDialogController::paintEvent(QPaintEvent *) {
70     QPainter dialogPainter(this);
71     const int columns = 6;
72 
73     const int rect_width = static_cast<double>(alphabetColorsFrame->size().width()) / columns;
74     if (rect_width == 0) {
75         return;
76     }
77 
78     const int rows = (newColors.size() / columns) + ((newColors.size() % columns) ? 1 : 0);
79     const int rect_height = static_cast<double>(alphabetColorsFrame->size().height()) / rows;
80     if (rect_height == 0) {
81         return;
82     }
83     int rect_height_rest = alphabetColorsFrame->size().height() % rows;
84 
85     delete alphabetColorsView;
86     alphabetColorsView = new QPixmap(alphabetColorsFrame->size());
87 
88     QPainter painter(alphabetColorsView);
89     QFont font;
90     font.setFamily("Verdana");
91     font.setPointSize(qMin(rect_width, rect_height) / 2);
92     painter.setFont(font);
93 
94     QMapIterator<char, QColor> it(newColors);
95 
96     int hLineY = 0;
97     for (int i = 0; i < rows; ++i) {
98         int rh = rect_height;
99         int rect_width_rest = alphabetColorsFrame->size().width() % columns;
100         if (rect_height_rest > 0) {
101             rh++;
102             rect_height_rest--;
103         }
104         painter.drawLine(0, hLineY, alphabetColorsView->size().width(), hLineY);
105         int vLineX = 0;
106         for (int j = 0; j < columns; ++j) {
107             if (!it.hasNext()) {
108                 break;
109             }
110 
111             it.next();
112             int rw = rect_width;
113             if (rect_width_rest > 0) {
114                 rw++;
115                 rect_width_rest--;
116             }
117             QRect nextRect(vLineX, hLineY + 1, rw, rh - 1);
118             painter.fillRect(nextRect, it.value());
119             painter.drawText(nextRect, Qt::AlignCenter, QString(it.key()));
120             painter.drawLine(vLineX, hLineY, vLineX, hLineY + rh);
121             painter.drawLine(vLineX + rw, hLineY, vLineX + rw, hLineY + rh);
122             vLineX += rw;
123             charsPlacement[it.key()] = nextRect;
124         }
125         hLineY += rh;
126         if (!it.hasNext()) {
127             painter.fillRect(vLineX + 1, hLineY - rh + 1, alphabetColorsView->size().width() - vLineX - 1, rh - 1, dialogPainter.background());
128             break;
129         }
130     }
131 
132     painter.drawLine(2, alphabetColorsView->size().height() - 2, alphabetColorsView->size().width() - 2, alphabetColorsView->size().height() - 2);
133     painter.drawLine(alphabetColorsView->size().width() - 2, 2, alphabetColorsView->size().width() - 2, alphabetColorsView->size().height() - 2);
134 
135     painter.drawLine(alphabetColorsView->size().width() - 2, alphabetColorsView->size().height() - 2, 2, alphabetColorsView->size().height() - 2);
136     painter.drawLine(alphabetColorsView->size().width() - 2, alphabetColorsView->size().height() - 2, alphabetColorsView->size().width() - 2, 2);
137 
138     dialogPainter.drawPixmap(alphabetColorsFrame->geometry().x(), alphabetColorsFrame->geometry().y(), *alphabetColorsView);
139 }
140 
sl_onClear()141 void ColorSchemaDialogController::sl_onClear() {
142     storedColors = newColors;
143 
144     QMapIterator<char, QColor> it(newColors);
145     while (it.hasNext()) {
146         it.next();
147         newColors[it.key()] = QColor(Qt::white);
148     }
149 
150     update();
151 }
152 
sl_onRestore()153 void ColorSchemaDialogController::sl_onRestore() {
154     newColors = storedColors;
155     update();
156 }
157 
mouseReleaseEvent(QMouseEvent * event)158 void ColorSchemaDialogController::mouseReleaseEvent(QMouseEvent *event) {
159     QMapIterator<char, QRect> it(charsPlacement);
160 
161     while (it.hasNext()) {
162         it.next();
163         if (it.value().contains(event->pos().x() - alphabetColorsFrame->geometry().x(), event->pos().y() - alphabetColorsFrame->geometry().y())) {
164             QObjectScopedPointer<QColorDialog> d = new QColorDialog(this);
165 #ifdef Q_OS_DARWIN
166             // A workaround because of UGENE-2263
167             // Another way should be found.
168             // I suppose, that it is bug in the Qt libraries (Qt-4.8.5 for mac)
169             d->setOption(QColorDialog::DontUseNativeDialog);
170 #endif
171             const int res = d->exec();
172             CHECK(!d.isNull(), );
173 
174             if (res == QDialog::Accepted) {
175                 newColors[it.key()] = d->selectedColor();
176             }
177             break;
178         }
179     }
180 
181     update();
182 }
183 
184 /*Create MSA scheme dialog*/
185 
CreateColorSchemaDialog(ColorSchemeData * _newSchema,QStringList _usedNames)186 CreateColorSchemaDialog::CreateColorSchemaDialog(ColorSchemeData *_newSchema, QStringList _usedNames)
187     : usedNames(_usedNames), newSchema(_newSchema) {
188     setupUi(this);
189     new HelpButton(this, buttonBox, "65929623");
190     buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Create"));
191     buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
192 
193     alphabetComboBox->insertItem(0, QString(tr("Amino acid")), DNAAlphabet_AMINO);
194     alphabetComboBox->insertItem(1, QString(tr("Nucleotide")), DNAAlphabet_NUCL);
195 
196     connect(alphabetComboBox, SIGNAL(currentIndexChanged(int)), SLOT(sl_alphabetChanged(int)));
197     extendedModeBox->setVisible(false);
198     validLabel->setStyleSheet("color: " + Theme::errorColorLabelStr() + "; font: bold;");
199     validLabel->setVisible(false);
200     adjustSize();
201 
202     connect(schemeName, SIGNAL(textEdited(const QString &)), SLOT(sl_schemaNameEdited(const QString &)));
203 
204     buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK"));
205     buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
206     QPushButton *createButton = buttonBox->button(QDialogButtonBox::Ok);
207     QPushButton *cancelButton = buttonBox->button(QDialogButtonBox::Cancel);
208 
209     connect(createButton, SIGNAL(clicked()), this, SLOT(sl_createSchema()));
210     connect(cancelButton, SIGNAL(clicked()), this, SLOT(sl_cancel()));
211 
212     QSet<QString> excluded;
213     foreach (const QString &usedName, usedNames) {
214         excluded.insert(usedName);
215     }
216     schemeName->setText(GUrlUtils::rollFileName("Custom color scheme", excluded));
217 }
218 
isNameExist(const QString & text)219 bool CreateColorSchemaDialog::isNameExist(const QString &text) {
220     foreach (const QString &usedName, usedNames) {
221         if (usedName == text) {
222             return true;
223         }
224     }
225     return false;
226 }
227 
isSchemaNameValid(const QString & text,QString & description)228 bool CreateColorSchemaDialog::isSchemaNameValid(const QString &text, QString &description) {
229     if (text.isEmpty()) {
230         description = tr("Name of scheme is empty.");
231         return false;
232     }
233     int spaces = 0;
234     for (int i = 0; i < text.length(); i++) {
235         if (text[i] == ' ') {
236             spaces++;
237         }
238     }
239     if (spaces == text.length()) {
240         description = tr("Name can't contain only spaces.");
241         return false;
242     }
243     for (int i = 0; i < text.length(); ++i) {
244         if (!text[i].isDigit() && !text[i].isLetter() && text[i] != QChar('_') && !text[i].isSpace()) {
245             description = tr("Name has to consist of letters, digits, spaces") + "<br>" + tr("or underscore symbols only.");
246             return false;
247         }
248     }
249     if (isNameExist(text)) {
250         description = tr("Color scheme with the same name already exists.");
251         return false;
252     }
253 
254     return true;
255 }
256 
sl_schemaNameEdited(const QString & text)257 void CreateColorSchemaDialog::sl_schemaNameEdited(const QString &text) {
258     QString description;
259     const bool isNameValid = isSchemaNameValid(text, description);
260     validLabel->setVisible(!isNameValid);
261     adjustSize();
262     if (isNameValid) {
263         validLabel->clear();
264     } else {
265         validLabel->setText("Warning: " + description);
266     }
267 }
268 
sl_alphabetChanged(int index)269 void CreateColorSchemaDialog::sl_alphabetChanged(int index) {
270     if (0 > index || index >= alphabetComboBox->count()) {
271         return;
272     }
273 
274     if (DNAAlphabet_AMINO == static_cast<DNAAlphabetType>(
275                                  alphabetComboBox->itemData(index).toInt())) {
276         extendedModeBox->setVisible(false);
277         extendedModeBox->setChecked(false);
278     } else {
279         extendedModeBox->setVisible(true);
280     }
281     adjustSize();
282 }
283 
createNewScheme()284 int CreateColorSchemaDialog::createNewScheme() {
285     return exec();
286 }
287 
sl_createSchema()288 void CreateColorSchemaDialog::sl_createSchema() {
289     QString description;
290     if (!isSchemaNameValid(schemeName->text(), description)) {
291         return;
292     }
293 
294     int index = alphabetComboBox->currentIndex();
295     if (index < 0 || index >= alphabetComboBox->count()) {
296         return;
297     }
298 
299     DNAAlphabetType type = DNAAlphabet_AMINO;
300     bool defaultAlpType = true;
301 
302     if (static_cast<DNAAlphabetType>(alphabetComboBox->itemData(index).toInt()) == DNAAlphabet_NUCL) {
303         type = DNAAlphabet_NUCL;
304         if (extendedModeBox->isChecked()) {
305             defaultAlpType = false;
306         }
307     }
308 
309     QMap<char, QColor> alpColors = ColorSchemeUtils::getDefaultSchemaColors(type, defaultAlpType);
310 
311     QObjectScopedPointer<ColorSchemaDialogController> controller = new ColorSchemaDialogController(alpColors);
312     const int r = controller->adjustAlphabetColors();
313     CHECK(!controller.isNull(), );
314 
315     if (r == QDialog::Rejected) {
316         return;
317     }
318 
319     newSchema->name = schemeName->text();
320     newSchema->type = type;
321     newSchema->defaultAlpType = defaultAlpType;
322 
323     QMapIterator<char, QColor> it(alpColors);
324     while (it.hasNext()) {
325         it.next();
326         newSchema->alpColors[it.key()] = it.value();
327     }
328     accept();
329 }
330 
sl_cancel()331 void CreateColorSchemaDialog::sl_cancel() {
332     reject();
333 }
334 
ColorSchemaSettingsPageWidget(ColorSchemaSettingsPageController *)335 ColorSchemaSettingsPageWidget::ColorSchemaSettingsPageWidget(ColorSchemaSettingsPageController *) {
336     setupUi(this);
337 
338     connect(colorsDirButton, SIGNAL(clicked()), SLOT(sl_onColorsDirButton()));
339     connect(changeSchemaButton, SIGNAL(clicked()), SLOT(sl_onChangeColorSchema()));
340     connect(addSchemaButton, SIGNAL(clicked()), SLOT(sl_onAddColorSchema()));
341     connect(deleteSchemaButton, SIGNAL(clicked()), SLOT(sl_onDeleteColorSchema()));
342     connect(colorSchemas, SIGNAL(currentRowChanged(int)), SLOT(sl_schemaChanged(int)));
343 
344     sl_schemaChanged(colorSchemas->currentRow());
345 }
346 
setState(AppSettingsGUIPageState * s)347 void ColorSchemaSettingsPageWidget::setState(AppSettingsGUIPageState *s) {
348     ColorSchemaSettingsPageState *state = qobject_cast<ColorSchemaSettingsPageState *>(s);
349     colorsDirEdit->setText(state->colorsDir);
350     customSchemas = state->customSchemas;
351     removedCustomSchemas = state->removedCustomSchemas;
352     colorSchemas->clear();
353 
354     foreach (const ColorSchemeData &customSchema, customSchemas) {
355         colorSchemas->addItem(new QListWidgetItem(customSchema.name, colorSchemas));
356     }
357     update();
358 }
359 
getState(QString & err) const360 AppSettingsGUIPageState *ColorSchemaSettingsPageWidget::getState(QString &err) const {
361     QString colorsDir = colorsDirEdit->text();
362     U2OpStatusImpl os;
363     GUrlUtils::prepareDirLocation(colorsDir, os);
364     if (os.hasError()) {
365         err = os.getError();
366         return nullptr;
367     }
368     ColorSchemaSettingsPageState *state = new ColorSchemaSettingsPageState();
369     state->colorsDir = colorsDir;
370     state->customSchemas = customSchemas;
371     state->removedCustomSchemas = removedCustomSchemas;
372     return state;
373 }
374 
sl_schemaChanged(int index)375 void ColorSchemaSettingsPageWidget::sl_schemaChanged(int index) {
376     if (index < 0 || index >= colorSchemas->count()) {
377         changeSchemaButton->setDisabled(true);
378         deleteSchemaButton->setDisabled(true);
379     } else {
380         changeSchemaButton->setEnabled(true);
381         deleteSchemaButton->setEnabled(true);
382     }
383 }
384 
sl_onColorsDirButton()385 void ColorSchemaSettingsPageWidget::sl_onColorsDirButton() {
386     QString path = colorsDirEdit->text();
387     QString dir = U2FileDialog::getExistingDirectory(this, tr("Choose Folder"), path, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
388     if (dir.isEmpty()) {
389         return;
390     }
391     if (!FileAndDirectoryUtils::isDirectoryWritable(dir)) {
392         QMessageBox::warning(this, L10N::warningTitle(), tr("You don't have permissions to write in selected folder."));
393         return;
394     }
395     colorsDirEdit->setText(dir);
396     ColorSchemeUtils::setColorsDir(dir);
397     customSchemas.clear();
398     colorSchemas->clear();
399     customSchemas = ColorSchemeUtils::getSchemas();
400     foreach (ColorSchemeData schema, customSchemas) {
401         colorSchemas->addItem(new QListWidgetItem(schema.name, colorSchemas));
402     }
403 }
404 
sl_onAddColorSchema()405 void ColorSchemaSettingsPageWidget::sl_onAddColorSchema() {
406     QStringList usedNames;
407     foreach (const ColorSchemeData &customScheme, customSchemas) {
408         usedNames << customScheme.name;
409     }
410     ColorSchemeData schema;
411 
412     QObjectScopedPointer<CreateColorSchemaDialog> d = new CreateColorSchemaDialog(&schema, usedNames);
413     const int r = d->createNewScheme();
414     CHECK(!d.isNull(), );
415 
416     if (r == QDialog::Rejected) {
417         return;
418     }
419 
420     customSchemas.append(schema);
421     colorSchemas->addItem(new QListWidgetItem(schema.name, colorSchemas));
422 }
423 
sl_onChangeColorSchema()424 void ColorSchemaSettingsPageWidget::sl_onChangeColorSchema() {
425     QMap<char, QColor> alpColors;
426 
427     QListWidgetItem *item = colorSchemas->currentItem();
428     if (item == nullptr) {
429         return;
430     }
431 
432     QString schemaName = item->text();
433     for (int i = 0; i < customSchemas.size(); ++i) {
434         ColorSchemeData &customSchema = customSchemas[i];
435         if (customSchema.name == schemaName) {
436             alpColors = customSchema.alpColors;
437             QObjectScopedPointer<ColorSchemaDialogController> controller = new ColorSchemaDialogController(alpColors);
438             const int r = controller->adjustAlphabetColors();
439             CHECK(!controller.isNull(), );
440 
441             if (r == QDialog::Rejected) {
442                 return;
443             }
444 
445             QMapIterator<char, QColor> it(alpColors);
446             while (it.hasNext()) {
447                 it.next();
448                 customSchema.alpColors[it.key()] = it.value();
449             }
450             break;
451         }
452     }
453 }
454 
sl_onDeleteColorSchema()455 void ColorSchemaSettingsPageWidget::sl_onDeleteColorSchema() {
456     QListWidgetItem *item = colorSchemas->currentItem();
457     SAFE_POINT(item != nullptr, "current item for deletion is NULL", );
458 
459     QString schemaName = item->text();
460     for (int i = 0; i < customSchemas.size(); ++i) {
461         ColorSchemeData &customSchema = customSchemas[i];
462         if (customSchema.name == schemaName) {
463             removedCustomSchemas.append(customSchemas[i]);
464             customSchemas.removeAt(i);
465             colorSchemas->removeItemWidget(item);
466             delete item;
467             return;
468         }
469     }
470     FAIL("something wrong causes color scheme deletion, this code must be unreacheble", );
471 }
472 
473 }  // namespace U2
474