1 /**************************************************************************
2 * Otter Browser: Web browser controlled by the user, not vice-versa.
3 * Copyright (C) 2013 - 2018 Michal Dutkiewicz aka Emdek <michal@emdek.pl>
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
20 #include "ConfigurationContentsWidget.h"
21 #include "../../../core/ActionsManager.h"
22 #include "../../../core/NetworkManagerFactory.h"
23 #include "../../../core/SettingsManager.h"
24 #include "../../../core/ThemesManager.h"
25 #include "../../../ui/ColorWidget.h"
26 #include "../../../ui/OptionWidget.h"
27
28 #include "ui_ConfigurationContentsWidget.h"
29
30 #include <QtCore/QMetaEnum>
31 #include <QtGui/QClipboard>
32 #include <QtGui/QKeyEvent>
33 #include <QtGui/QPainter>
34 #include <QtWidgets/QMenu>
35 #include <QtWidgets/QMessageBox>
36
37 namespace Otter
38 {
39
ConfigurationOptionDelegate(QObject * parent)40 ConfigurationOptionDelegate::ConfigurationOptionDelegate(QObject *parent) : ItemDelegate(parent)
41 {
42 }
43
initStyleOption(QStyleOptionViewItem * option,const QModelIndex & index) const44 void ConfigurationOptionDelegate::initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const
45 {
46 ItemDelegate::initStyleOption(option, index);
47
48 const SettingsManager::OptionDefinition definition(SettingsManager::getOptionDefinition(index.data(ConfigurationContentsWidget::IdentifierRole).toInt()));
49
50 option->text = SettingsManager::createDisplayValue(definition.identifier, index.data(Qt::DisplayRole));
51
52 switch (definition.type)
53 {
54 case SettingsManager::ColorType:
55 {
56 QPixmap pixmap(option->fontMetrics.height(), option->fontMetrics.height());
57 pixmap.fill(Qt::transparent);
58
59 QPainter painter(&pixmap);
60 painter.setRenderHints(QPainter::Antialiasing);
61
62 ColorWidget::drawThumbnail(&painter, QColor(index.data(Qt::DisplayRole).toString()), option->palette, pixmap.rect());
63
64 painter.end();
65
66 option->features |= QStyleOptionViewItem::HasDecoration;
67 option->decorationSize = pixmap.size();
68 option->icon = QIcon(pixmap);
69 }
70
71 break;
72 case SettingsManager::EnumerationType:
73 {
74 const QString value(index.data(Qt::DisplayRole).toString());
75
76 if (definition.hasIcons())
77 {
78 option->features |= QStyleOptionViewItem::HasDecoration;
79 }
80
81 for (int i = 0; i < definition.choices.count(); ++i)
82 {
83 if (definition.choices.at(i).value == value)
84 {
85 option->icon = definition.choices.at(i).icon;
86
87 break;
88 }
89 }
90 }
91
92 break;
93 case SettingsManager::FontType:
94 option->font = QFont(index.data(Qt::DisplayRole).toString());
95
96 break;
97 default:
98 break;
99 }
100 }
101
setEditorData(QWidget * editor,const QModelIndex & index) const102 void ConfigurationOptionDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
103 {
104 OptionWidget *widget(qobject_cast<OptionWidget*>(editor));
105
106 if (widget && !index.sibling(index.row(), 0).data(ConfigurationContentsWidget::IsModifiedRole).toBool())
107 {
108 widget->setValue(index.data(Qt::EditRole));
109 }
110 }
111
setModelData(QWidget * editor,QAbstractItemModel * model,const QModelIndex & index) const112 void ConfigurationOptionDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
113 {
114 const OptionWidget *widget(qobject_cast<OptionWidget*>(editor));
115
116 if (widget)
117 {
118 const QModelIndex optionIndex(index.sibling(index.row(), 0));
119 QFont font(optionIndex.data(Qt::FontRole).value<QFont>());
120 font.setBold(widget->getValue() != widget->getDefaultValue());
121
122 model->setData(index, widget->getValue(), Qt::EditRole);
123 model->setData(optionIndex, font, Qt::FontRole);
124 model->setData(optionIndex, true, ConfigurationContentsWidget::IsModifiedRole);
125 }
126 }
127
createEditor(QWidget * parent,const QStyleOptionViewItem & option,const QModelIndex & index) const128 QWidget* ConfigurationOptionDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
129 {
130 Q_UNUSED(option)
131
132 const SettingsManager::OptionDefinition definition(SettingsManager::getOptionDefinition(index.data(ConfigurationContentsWidget::IdentifierRole).toInt()));
133 OptionWidget *widget(new OptionWidget(index.data(Qt::EditRole), definition.type, parent));
134 widget->setDefaultValue(definition.defaultValue);
135 widget->setFocus();
136
137 if (definition.type == SettingsManager::EnumerationType)
138 {
139 widget->setChoices(definition.choices);
140 }
141
142 connect(widget, &OptionWidget::commitData, this, &ConfigurationOptionDelegate::commitData);
143
144 return widget;
145 }
146
ConfigurationContentsWidget(const QVariantMap & parameters,Window * window,QWidget * parent)147 ConfigurationContentsWidget::ConfigurationContentsWidget(const QVariantMap ¶meters, Window *window, QWidget *parent) : ContentsWidget(parameters, window, parent),
148 m_model(new QStandardItemModel(this)),
149 m_ui(new Ui::ConfigurationContentsWidget)
150 {
151 m_ui->setupUi(this);
152 m_ui->filterLineEditWidget->setClearOnEscape(true);
153
154 NetworkManagerFactory::initialize();
155
156 const QMetaEnum metaEnum(SettingsManager::getInstance()->metaObject()->enumerator(SettingsManager::getInstance()->metaObject()->indexOfEnumerator(QLatin1String("OptionType").data())));
157 const QStringList options(SettingsManager::getOptions());
158 QStandardItem *groupItem(nullptr);
159 bool canResetAll(false);
160
161 for (int i = 0; i < options.count(); ++i)
162 {
163 const int identifier(SettingsManager::getOptionIdentifier(options.at(i)));
164 const QStringList option(options.at(i).split(QLatin1Char('/')));
165 const QVariant value(SettingsManager::getOption(identifier));
166 const SettingsManager::OptionDefinition definition(SettingsManager::getOptionDefinition(identifier));
167
168 if (!definition.flags.testFlag(SettingsManager::OptionDefinition::IsEnabledFlag) || !definition.flags.testFlag(SettingsManager::OptionDefinition::IsVisibleFlag))
169 {
170 continue;
171 }
172
173 if (!groupItem || groupItem->text() != option.first())
174 {
175 groupItem = new QStandardItem(ThemesManager::createIcon(QLatin1String("inode-directory")), option.first());
176
177 m_model->appendRow(groupItem);
178 }
179
180 QString type(metaEnum.valueToKey(definition.type));
181 type.chop(4);
182
183 QList<QStandardItem*> optionItems({new QStandardItem(option.last()), new QStandardItem(type.toLower()), new QStandardItem((value.type() == QVariant::StringList) ? value.toStringList().join(QLatin1String(", ")) : value.toString())});
184 optionItems[0]->setFlags(optionItems[0]->flags() | Qt::ItemNeverHasChildren);
185 optionItems[1]->setFlags(optionItems[1]->flags() | Qt::ItemNeverHasChildren);
186 optionItems[2]->setData(QSize(-1, 30), Qt::SizeHintRole);
187 optionItems[2]->setData(identifier, IdentifierRole);
188 optionItems[2]->setData(options.at(i), NameRole);
189 optionItems[2]->setFlags(optionItems[2]->flags() | Qt::ItemNeverHasChildren);
190
191 if (definition.flags.testFlag(SettingsManager::OptionDefinition::RequiresRestartFlag))
192 {
193 optionItems[2]->setData(true, RequiresRestartRole);
194 }
195
196 if (value != definition.defaultValue)
197 {
198 QFont font(optionItems[0]->font());
199 font.setBold(true);
200
201 optionItems[0]->setFont(font);
202
203 if (identifier != SettingsManager::Browser_MigrationsOption)
204 {
205 canResetAll = true;
206 }
207 }
208
209 groupItem->appendRow(optionItems);
210 }
211
212 m_model->setHorizontalHeaderLabels({tr("Name"), tr("Type"), tr("Value")});
213 m_model->sort(0);
214
215 m_ui->configurationViewWidget->setViewMode(ItemViewWidget::TreeView);
216 m_ui->configurationViewWidget->setModel(m_model);
217 m_ui->configurationViewWidget->setLayoutDirection(Qt::LeftToRight);
218 m_ui->configurationViewWidget->setItemDelegateForColumn(2, new ConfigurationOptionDelegate(this));
219 m_ui->configurationViewWidget->setFilterRoles({Qt::DisplayRole, NameRole});
220 m_ui->configurationViewWidget->installEventFilter(this);
221 m_ui->resetAllButton->setEnabled(canResetAll);
222
223 if (isSidebarPanel())
224 {
225 m_ui->detailsWidget->hide();
226 }
227
228 connect(SettingsManager::getInstance(), &SettingsManager::optionChanged, this, &ConfigurationContentsWidget::handleOptionChanged);
229 connect(m_ui->configurationViewWidget, &ItemViewWidget::customContextMenuRequested, this, &ConfigurationContentsWidget::showContextMenu);
230 connect(m_ui->configurationViewWidget, &ItemViewWidget::needsActionsUpdate, this, &ConfigurationContentsWidget::updateActions);
231 connect(m_ui->configurationViewWidget, &ItemViewWidget::clicked, this, &ConfigurationContentsWidget::handleIndexClicked);
232 connect(m_ui->configurationViewWidget, &ItemViewWidget::modified, [&]()
233 {
234 m_ui->resetAllButton->setEnabled(true);
235 m_ui->saveAllButton->setEnabled(true);
236 });
237 connect(m_ui->configurationViewWidget->selectionModel(), &QItemSelectionModel::currentChanged, this, &ConfigurationContentsWidget::handleCurrentIndexChanged);
238 connect(m_ui->filterLineEditWidget, &LineEditWidget::textChanged, m_ui->configurationViewWidget, &ItemViewWidget::setFilterString);
239 connect(m_ui->resetAllButton, &QPushButton::clicked, [&]()
240 {
241 saveAll(true);
242 });
243 connect(m_ui->saveAllButton, &QPushButton::clicked, [&]()
244 {
245 saveAll(false);
246 });
247 }
248
~ConfigurationContentsWidget()249 ConfigurationContentsWidget::~ConfigurationContentsWidget()
250 {
251 delete m_ui;
252 }
253
changeEvent(QEvent * event)254 void ConfigurationContentsWidget::changeEvent(QEvent *event)
255 {
256 ContentsWidget::changeEvent(event);
257
258 if (event->type() == QEvent::LanguageChange)
259 {
260 m_ui->retranslateUi(this);
261
262 m_model->setHorizontalHeaderLabels({tr("Name"), tr("Type"), tr("Value")});
263 }
264 }
265
triggerAction(int identifier,const QVariantMap & parameters,ActionsManager::TriggerType trigger)266 void ConfigurationContentsWidget::triggerAction(int identifier, const QVariantMap ¶meters, ActionsManager::TriggerType trigger)
267 {
268 switch (identifier)
269 {
270 case ActionsManager::FindAction:
271 case ActionsManager::QuickFindAction:
272 m_ui->filterLineEditWidget->setFocus();
273
274 break;
275 case ActionsManager::ActivateContentAction:
276 m_ui->configurationViewWidget->setFocus();
277
278 break;
279 default:
280 ContentsWidget::triggerAction(identifier, parameters, trigger);
281
282 break;
283 }
284 }
285
closeEvent(QCloseEvent * event)286 void ConfigurationContentsWidget::closeEvent(QCloseEvent *event)
287 {
288 if (m_ui->configurationViewWidget->isModified())
289 {
290 const int result(QMessageBox::question(this, tr("Question"), tr("The settings have been changed.\nDo you want to save them?"), QMessageBox::Yes, QMessageBox::No, QMessageBox::Cancel));
291
292 if (result == QMessageBox::Cancel)
293 {
294 event->ignore();
295
296 return;
297 }
298
299 if (result == QMessageBox::Yes)
300 {
301 saveAll(false);
302 }
303 }
304
305 event->accept();
306 }
307
print(QPrinter * printer)308 void ConfigurationContentsWidget::print(QPrinter *printer)
309 {
310 m_ui->configurationViewWidget->render(printer);
311 }
312
copyOptionName()313 void ConfigurationContentsWidget::copyOptionName()
314 {
315 const QModelIndex index(m_ui->configurationViewWidget->currentIndex().sibling(m_ui->configurationViewWidget->currentIndex().row(), 2));
316
317 if (index.isValid())
318 {
319 QApplication::clipboard()->setText(index.data(NameRole).toString());
320 }
321 }
322
copyOptionValue()323 void ConfigurationContentsWidget::copyOptionValue()
324 {
325 const QModelIndex index(m_ui->configurationViewWidget->currentIndex().sibling(m_ui->configurationViewWidget->currentIndex().row(), 2));
326
327 if (index.isValid())
328 {
329 QApplication::clipboard()->setText(index.data(Qt::EditRole).toString());
330 }
331 }
332
resetOption()333 void ConfigurationContentsWidget::resetOption()
334 {
335 const QModelIndex index(m_ui->configurationViewWidget->currentIndex().sibling(m_ui->configurationViewWidget->currentIndex().row(), 2));
336
337 if (index.isValid())
338 {
339 SettingsManager::setOption(index.data(IdentifierRole).toInt(), SettingsManager::getOptionDefinition(index.data(IdentifierRole).toInt()).defaultValue);
340
341 m_model->setData(index.sibling(index.row(), 0), false, IsModifiedRole);
342
343 updateActions();
344 }
345 }
346
saveOption()347 void ConfigurationContentsWidget::saveOption()
348 {
349 const QModelIndex index(m_ui->configurationViewWidget->currentIndex().sibling(m_ui->configurationViewWidget->currentIndex().row(), 2));
350
351 if (index.isValid())
352 {
353 SettingsManager::setOption(index.data(IdentifierRole).toInt(), index.data(Qt::EditRole));
354
355 m_model->setData(index.sibling(index.row(), 0), false, IsModifiedRole);
356
357 updateActions();
358 }
359 }
360
saveAll(bool reset)361 void ConfigurationContentsWidget::saveAll(bool reset)
362 {
363 if (reset && QMessageBox::question(this, tr("Question"), tr("Do you really want to restore default values of all options?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
364 {
365 return;
366 }
367
368 for (int i = 0; i < m_model->rowCount(); ++i)
369 {
370 const QModelIndex groupIndex(m_model->index(i, 0));
371 const int optionAmount(m_model->rowCount(groupIndex));
372
373 if (optionAmount == 0)
374 {
375 continue;
376 }
377
378 for (int j = 0; j < optionAmount; ++j)
379 {
380 const QModelIndex optionIndex(groupIndex.child(j, 0));
381 const bool isModified(optionIndex.data(IsModifiedRole).toBool());
382
383 if (reset || isModified)
384 {
385 const QModelIndex valueIndex(groupIndex.child(j, 2));
386 const int identifier(valueIndex.data(IdentifierRole).toInt());
387 const QVariant defaultValue(SettingsManager::getOptionDefinition(identifier).defaultValue);
388
389 if (reset && identifier != SettingsManager::Browser_MigrationsOption && valueIndex.data(Qt::EditRole) != defaultValue)
390 {
391 SettingsManager::setOption(identifier, defaultValue);
392
393 QFont font(optionIndex.data(Qt::FontRole).isNull() ? m_ui->configurationViewWidget->font() : optionIndex.data(Qt::FontRole).value<QFont>());
394 font.setBold(false);
395
396 m_model->setData(optionIndex, font, Qt::FontRole);
397 m_model->setData(valueIndex, defaultValue, Qt::EditRole);
398 }
399 else if (!reset && isModified)
400 {
401 SettingsManager::setOption(identifier, valueIndex.data(Qt::EditRole));
402 }
403
404 if (isModified)
405 {
406 m_model->setData(optionIndex, false, IsModifiedRole);
407 }
408 }
409 }
410 }
411
412 m_ui->configurationViewWidget->setModified(false);
413 m_ui->saveAllButton->setEnabled(false);
414
415 if (reset)
416 {
417 m_ui->resetAllButton->setEnabled(false);
418 }
419
420 connect(m_ui->configurationViewWidget, &ItemViewWidget::needsActionsUpdate, this, &ConfigurationContentsWidget::updateActions);
421
422 updateActions();
423 }
424
handleOptionChanged(int identifier,const QVariant & value)425 void ConfigurationContentsWidget::handleOptionChanged(int identifier, const QVariant &value)
426 {
427 const QString name(SettingsManager::getOptionName(identifier));
428 const bool wasModified(m_ui->configurationViewWidget->isModified());
429
430 for (int i = 0; i < m_model->rowCount(); ++i)
431 {
432 const QModelIndex groupIndex(m_model->index(i, 0));
433 const QString groupTitle(groupIndex.data(Qt::DisplayRole).toString());
434
435 if (groupTitle.isEmpty() || !name.startsWith(groupTitle))
436 {
437 continue;
438 }
439
440 const int optionAmount(m_model->rowCount(groupIndex));
441
442 for (int j = 0; j < optionAmount; ++j)
443 {
444 const QModelIndex valueIndex(groupIndex.child(j, 2));
445
446 if (valueIndex.data(IdentifierRole).toInt() == identifier)
447 {
448 const QModelIndex optionIndex(groupIndex.child(j, 0));
449
450 if (!optionIndex.data(IsModifiedRole).toBool())
451 {
452 QFont font(optionIndex.data(Qt::FontRole).isNull() ? m_ui->configurationViewWidget->font() : optionIndex.data(Qt::FontRole).value<QFont>());
453 font.setBold(value != SettingsManager::getOptionDefinition(identifier).defaultValue);
454
455 m_model->setData(optionIndex, font, Qt::FontRole);
456 m_model->setData(valueIndex, value, Qt::EditRole);
457 }
458
459 break;
460 }
461 }
462 }
463
464 if (!wasModified)
465 {
466 m_ui->configurationViewWidget->setModified(false);
467 }
468
469 updateActions();
470 }
471
handleCurrentIndexChanged(const QModelIndex & currentIndex,const QModelIndex & previousIndex)472 void ConfigurationContentsWidget::handleCurrentIndexChanged(const QModelIndex ¤tIndex, const QModelIndex &previousIndex)
473 {
474 if (previousIndex.parent().isValid() && previousIndex.column() == 2)
475 {
476 m_ui->configurationViewWidget->closePersistentEditor(previousIndex);
477 }
478
479 if (currentIndex.parent().isValid() && currentIndex.column() == 2)
480 {
481 m_ui->configurationViewWidget->openPersistentEditor(currentIndex);
482 }
483 }
484
handleIndexClicked(const QModelIndex & index)485 void ConfigurationContentsWidget::handleIndexClicked(const QModelIndex &index)
486 {
487 if (index.parent().isValid() && index.column() != 2)
488 {
489 m_ui->configurationViewWidget->setCurrentIndex(index.sibling(index.row(), 2));
490 }
491 }
492
showContextMenu(const QPoint & position)493 void ConfigurationContentsWidget::showContextMenu(const QPoint &position)
494 {
495 const QModelIndex index(m_ui->configurationViewWidget->indexAt(position));
496 QMenu menu(this);
497
498 if (index.isValid() && index.parent() != m_ui->configurationViewWidget->rootIndex())
499 {
500 menu.addAction(tr("Copy Option Name"), this, &ConfigurationContentsWidget::copyOptionName);
501 menu.addAction(tr("Copy Option Value"), this, &ConfigurationContentsWidget::copyOptionValue);
502 menu.addSeparator();
503 menu.addAction(tr("Save Value"), this, &ConfigurationContentsWidget::saveOption)->setEnabled(index.sibling(index.row(), 0).data(IsModifiedRole).toBool());
504 menu.addAction(tr("Restore Default Value"), this, &ConfigurationContentsWidget::resetOption)->setEnabled(index.sibling(index.row(), 2).data(Qt::EditRole) != SettingsManager::getOptionDefinition(index.sibling(index.row(), 2).data(IdentifierRole).toInt()).defaultValue);
505 menu.addSeparator();
506 }
507
508 menu.addAction(tr("Expand All"), m_ui->configurationViewWidget, &ItemViewWidget::expandAll);
509 menu.addAction(tr("Collapse All"), m_ui->configurationViewWidget, &ItemViewWidget::collapseAll);
510 menu.exec(m_ui->configurationViewWidget->mapToGlobal(position));
511 }
512
updateActions()513 void ConfigurationContentsWidget::updateActions()
514 {
515 const QModelIndex index(m_ui->configurationViewWidget->selectionModel()->hasSelection() ? m_ui->configurationViewWidget->currentIndex().sibling(m_ui->configurationViewWidget->currentIndex().row(), 2) : QModelIndex());
516 const int identifier(index.data(IdentifierRole).toInt());
517
518 if (identifier >= 0 && index.parent().isValid())
519 {
520 m_ui->nameLabelWidget->setText(SettingsManager::getOptionName(identifier));
521 m_ui->currentValueLabelWidget->setText(SettingsManager::createDisplayValue(identifier, SettingsManager::getOption(identifier)));
522 m_ui->defaultValueLabelWidget->setText(SettingsManager::createDisplayValue(identifier, SettingsManager::getOptionDefinition(identifier).defaultValue));
523 }
524 else
525 {
526 m_ui->nameLabelWidget->clear();
527 m_ui->currentValueLabelWidget->clear();
528 m_ui->defaultValueLabelWidget->clear();
529 }
530 }
531
getTitle() const532 QString ConfigurationContentsWidget::getTitle() const
533 {
534 return tr("Advanced Configuration");
535 }
536
getType() const537 QLatin1String ConfigurationContentsWidget::getType() const
538 {
539 return QLatin1String("config");
540 }
541
getUrl() const542 QUrl ConfigurationContentsWidget::getUrl() const
543 {
544 return QUrl(QLatin1String("about:config"));
545 }
546
getIcon() const547 QIcon ConfigurationContentsWidget::getIcon() const
548 {
549 return ThemesManager::createIcon(QLatin1String("configuration"), false);
550 }
551
eventFilter(QObject * object,QEvent * event)552 bool ConfigurationContentsWidget::eventFilter(QObject *object, QEvent *event)
553 {
554 if (object == m_ui->configurationViewWidget && event->type() == QEvent::KeyPress)
555 {
556 const QModelIndex index(m_ui->configurationViewWidget->currentIndex());
557
558 if (static_cast<QKeyEvent*>(event)->key() == Qt::Key_Right && index.parent().isValid())
559 {
560 m_ui->configurationViewWidget->setCurrentIndex(index.sibling(index.row(), 2));
561 }
562 }
563
564 return ContentsWidget::eventFilter(object, event);
565 }
566
567 }
568