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