diff --git a/contrib/dash-qt.pro b/contrib/dash-qt.pro index cde8c56930a8..708978d1d574 100644 --- a/contrib/dash-qt.pro +++ b/contrib/dash-qt.pro @@ -1,6 +1,7 @@ FORMS += \ ../src/qt/forms/aboutdialog.ui \ ../src/qt/forms/addressbookpage.ui \ + ../src/qt/forms/appearancewidget.ui \ ../src/qt/forms/askpassphrasedialog.ui \ ../src/qt/forms/coincontroldialog.ui \ ../src/qt/forms/debugwindow.ui \ diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index b975e8161342..3c1f09a870ee 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -32,6 +32,7 @@ QT_TS = \ QT_FORMS_UI = \ qt/forms/addressbookpage.ui \ + qt/forms/appearancewidget.ui \ qt/forms/askpassphrasedialog.ui \ qt/forms/coincontroldialog.ui \ qt/forms/editaddressdialog.ui \ @@ -54,6 +55,7 @@ QT_FORMS_UI = \ QT_MOC_CPP = \ qt/moc_addressbookpage.cpp \ qt/moc_addresstablemodel.cpp \ + qt/moc_appearancewidget.cpp \ qt/moc_askpassphrasedialog.cpp \ qt/moc_bantablemodel.cpp \ qt/moc_bitcoinaddressvalidator.cpp \ @@ -126,6 +128,7 @@ PROTOBUF_PROTO = qt/paymentrequest.proto BITCOIN_QT_H = \ qt/addressbookpage.h \ qt/addresstablemodel.h \ + qt/appearancewidget.h \ qt/askpassphrasedialog.h \ qt/bantablemodel.h \ qt/bitcoinaddressvalidator.h \ @@ -238,6 +241,7 @@ RES_ICONS = \ qt/res/icons/network_disabled.png BITCOIN_QT_BASE_CPP = \ + qt/appearancewidget.cpp \ qt/bantablemodel.cpp \ qt/bitcoinaddressvalidator.cpp \ qt/bitcoinamountfield.cpp \ diff --git a/src/qt/appearancewidget.cpp b/src/qt/appearancewidget.cpp new file mode 100644 index 000000000000..59c3e782b869 --- /dev/null +++ b/src/qt/appearancewidget.cpp @@ -0,0 +1,159 @@ +// Copyright (c) 2020 The Dash Core developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include + +#include +#include + +#include + +#include +#include +#include +#include + +AppearanceWidget::AppearanceWidget(QWidget* parent) : + QWidget(parent), + ui(new Ui::AppearanceWidget), + fAcceptChanges(false), + prevTheme(GUIUtil::getActiveTheme()), + prevFontFamily(GUIUtil::getFontFamily()), + prevScale(GUIUtil::getFontScale()), + prevWeightNormal(GUIUtil::getFontWeightNormal()), + prevWeightBold(GUIUtil::getFontWeightBold()) +{ + ui->setupUi(this); + + for (const QString& entry : GUIUtil::listThemes()) { + ui->theme->addItem(entry, QVariant(entry)); + } + + GUIUtil::FontFamily fontSystem = GUIUtil::FontFamily::SystemDefault; + GUIUtil::FontFamily fontMontserrat = GUIUtil::FontFamily::Montserrat; + + ui->fontFamily->addItem(GUIUtil::fontFamilyToString(fontSystem), QVariant(static_cast(fontSystem))); + ui->fontFamily->addItem(GUIUtil::fontFamilyToString(fontMontserrat), QVariant(static_cast(fontMontserrat))); + + updateWeightSlider(); + + mapper = new QDataWidgetMapper(this); + mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit); + mapper->setOrientation(Qt::Vertical); + + connect(ui->theme, SIGNAL(currentTextChanged(const QString&)), this, SLOT(updateTheme(const QString&))); + connect(ui->fontFamily, SIGNAL(activated(int)), this, SLOT(updateFontFamily(int))); + connect(ui->fontScaleSlider, SIGNAL(valueChanged(int)), this, SLOT(updateFontScale(int))); + connect(ui->fontWeightNormalSlider, SIGNAL(valueChanged(int)), this, SLOT(updateFontWeightNormal(int))); + connect(ui->fontWeightBoldSlider, SIGNAL(valueChanged(int)), this, SLOT(updateFontWeightBold(int))); +} + +AppearanceWidget::~AppearanceWidget() +{ + if (fAcceptChanges) { + mapper->submit(); + } else { + if (prevTheme != GUIUtil::getActiveTheme()) { + updateTheme(prevTheme); + } + if (prevFontFamily != GUIUtil::getFontFamily()) { + GUIUtil::setFontFamily(prevFontFamily); + } + if (prevScale != GUIUtil::getFontScale()) { + GUIUtil::setFontScale(prevScale); + } + if (prevWeightNormal != GUIUtil::getFontWeightNormal()) { + GUIUtil::setFontWeightNormal(prevWeightNormal); + } + if (prevWeightBold != GUIUtil::getFontWeightBold()) { + GUIUtil::setFontWeightBold(prevWeightBold); + } + } + delete ui; +} + +void AppearanceWidget::setModel(OptionsModel* _model) +{ + this->model = _model; + + if (_model) { + mapper->setModel(_model); + mapper->addMapping(ui->theme, OptionsModel::Theme); + mapper->addMapping(ui->fontFamily, OptionsModel::FontFamily); + mapper->addMapping(ui->fontScaleSlider, OptionsModel::FontScale); + mapper->addMapping(ui->fontWeightNormalSlider, OptionsModel::FontWeightNormal); + mapper->addMapping(ui->fontWeightBoldSlider, OptionsModel::FontWeightBold); + mapper->toFirst(); + } +} + +void AppearanceWidget::accept() +{ + fAcceptChanges = true; +} + +void AppearanceWidget::updateTheme(const QString& theme) +{ + QString newValue = theme.isEmpty() ? ui->theme->currentData().toString() : theme; + if (GUIUtil::getActiveTheme() != newValue) { + QSettings().setValue("theme", newValue); + GUIUtil::loadTheme(); + } +} + +void AppearanceWidget::updateFontFamily(int index) +{ + GUIUtil::setFontFamily(static_cast(ui->fontFamily->itemData(index).toInt())); + updateWeightSlider(); +} + +void AppearanceWidget::updateFontScale(int nScale) +{ + GUIUtil::setFontScale(nScale); +} + +void AppearanceWidget::updateFontWeightNormal(int nValue, bool fForce) +{ + int nSliderValue = nValue; + if (nValue > ui->fontWeightBoldSlider->value() && !fForce) { + nSliderValue = ui->fontWeightBoldSlider->value(); + } + const QSignalBlocker blocker(ui->fontWeightNormalSlider); + ui->fontWeightNormalSlider->setValue(nSliderValue); + GUIUtil::setFontWeightNormal(GUIUtil::supportedWeightFromIndex(ui->fontWeightNormalSlider->value())); +} + +void AppearanceWidget::updateFontWeightBold(int nValue, bool fForce) +{ + int nSliderValue = nValue; + if (nValue < ui->fontWeightNormalSlider->value() && !fForce) { + nSliderValue = ui->fontWeightNormalSlider->value(); + } + const QSignalBlocker blocker(ui->fontWeightBoldSlider); + ui->fontWeightBoldSlider->setValue(nSliderValue); + GUIUtil::setFontWeightBold(GUIUtil::supportedWeightFromIndex(ui->fontWeightBoldSlider->value())); +} + +void AppearanceWidget::updateWeightSlider() +{ + int nMaximum = GUIUtil::getSupportedWeights().size() - 1; + + ui->fontWeightNormalSlider->setMinimum(0); + ui->fontWeightNormalSlider->setMaximum(nMaximum); + + ui->fontWeightBoldSlider->setMinimum(0); + ui->fontWeightBoldSlider->setMaximum(nMaximum); + + if (nMaximum < 4) { + updateFontWeightNormal(0, true); + updateFontWeightBold(nMaximum, true); + } else { + updateFontWeightNormal(1, true); + updateFontWeightBold(4, true); + } +} diff --git a/src/qt/appearancewidget.h b/src/qt/appearancewidget.h new file mode 100644 index 000000000000..b97496ced83c --- /dev/null +++ b/src/qt/appearancewidget.h @@ -0,0 +1,56 @@ +// Copyright (c) 2020 The Dash Core developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef DASH_QT_APPEARANCE_WIDGET_H +#define DASH_QT_APPEARANCE_WIDGET_H + +#include + +#include + +namespace Ui { +class AppearanceWidget; +} + +class OptionsModel; + +class QDataWidgetMapper; +class QSlider; +class QComboBox; + +class AppearanceWidget : public QWidget +{ + Q_OBJECT + +public: + explicit AppearanceWidget(QWidget* parent = 0); + ~AppearanceWidget(); + + void setModel(OptionsModel* model); + +public Q_SLOTS: + void accept(); + +private Q_SLOTS: + void updateTheme(const QString& toTheme = QString()); + void updateFontFamily(int index); + void updateFontScale(int nScale); + void updateFontWeightNormal(int nValue, bool fForce = false); + void updateFontWeightBold(int nValue, bool fForce = false); + +private: + Ui::AppearanceWidget* ui; + QDataWidgetMapper* mapper; + OptionsModel* model; + bool fAcceptChanges; + QString prevTheme; + int prevScale; + GUIUtil::FontFamily prevFontFamily; + QFont::Weight prevWeightNormal; + QFont::Weight prevWeightBold; + + void updateWeightSlider(); +}; + +#endif // DASH_QT_APPEARANCE_WIDGET_H diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 63886e2346ae..7735be536310 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -842,7 +842,6 @@ void BitcoinGUI::optionsClicked() OptionsDialog dlg(this, enableWallet); dlg.setModel(clientModel->getOptionsModel()); - connect(&dlg, &OptionsDialog::themeChanged, [=]() { GUIUtil::loadTheme(); }); dlg.exec(); } diff --git a/src/qt/dash.cpp b/src/qt/dash.cpp index 29d90bb47434..ba2288af174c 100644 --- a/src/qt/dash.cpp +++ b/src/qt/dash.cpp @@ -515,6 +515,9 @@ void BitcoinApplication::initializeResult(bool success) } Q_EMIT splashFinished(window); + // Let the users setup their prefered appearance if there are no settings for it defined yet. + GUIUtil::setupAppearance(window, clientModel->getOptionsModel()); + #ifdef ENABLE_WALLET // Now that initialization/startup is done, process any command-line // dash: URIs or payment requests: diff --git a/src/qt/forms/appearancewidget.ui b/src/qt/forms/appearancewidget.ui new file mode 100644 index 000000000000..d8d42ad007a6 --- /dev/null +++ b/src/qt/forms/appearancewidget.ui @@ -0,0 +1,486 @@ + + + AppearanceWidget + + + + 0 + 0 + 445 + 156 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QLayout::SetDefaultConstraint + + + 0 + + + + + QLayout::SetFixedSize + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + Lighter + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + 0 + + + 5 + + + 0 + + + Qt::Horizontal + + + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + Bolder + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + 0 + 0 + + + + Font Weight Normal: + + + + + + + QLayout::SetFixedSize + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + Smaller + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + -30 + + + 30 + + + 10 + + + 0 + + + Qt::Horizontal + + + QSlider::NoTicks + + + 3 + + + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + Bigger + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + 0 + 0 + + + + Font Scale: + + + + + + + QLayout::SetFixedSize + + + + + + 282 + 0 + + + + + 282 + 16777215 + + + + + + + + + + + 0 + 0 + + + + Font Family: + + + + + + + QLayout::SetFixedSize + + + + + + 282 + 0 + + + + + 282 + 16777215 + + + + + + + + + + + 0 + 0 + + + + Theme: + + + + + + + + 0 + 0 + + + + Font Weight Bold: + + + + + + + QLayout::SetFixedSize + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + Lighter + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + 3 + + + 8 + + + 3 + + + Qt::Horizontal + + + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + Bolder + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + 2 + 20 + + + + + + + + Qt::Horizontal + + + + 2 + 20 + + + + + + + + Qt::Horizontal + + + + 2 + 20 + + + + + + + + Qt::Horizontal + + + + 2 + 20 + + + + + + + + Qt::Horizontal + + + + 2 + 20 + + + + + + + + + + + diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index c507210394a8..43f67725d869 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -6,10 +6,16 @@ 0 0 - 576 - 402 + 583 + 537 + + + 0 + 0 + + Options @@ -75,6 +81,16 @@ + + + + &Appearance + + + true + + + @@ -86,7 +102,7 @@ - 0 + 5 @@ -777,20 +793,6 @@ https://www.transifex.com/projects/p/dash/ - - - - - - User Interface Theme: - - - - - - - - @@ -875,6 +877,26 @@ https://www.transifex.com/projects/p/dash/ + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index f688a9a9531a..409687b45ac1 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -5,8 +5,10 @@ #include +#include #include #include +#include #include #include @@ -46,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +61,7 @@ #include #include #include +#include static fs::detail::utf8_codecvt_facet utf8; @@ -101,6 +105,8 @@ static const std::map mapStyleToTheme{ {"scrollbars.css", ""} }; +/** loadFonts stores the SystemDefault font in osDefaultFont to be able to reference it later again */ +static std::unique_ptr osDefaultFont; /** Font related default values. */ static const FontFamily defaultFontFamily = FontFamily::SystemDefault; static const int defaultFontSize = 12; @@ -131,6 +137,8 @@ static std::map> mapNormalFontUpdates; static std::set setFixedPitchFontUpdates; // Contains all widgets where a non-default fontsize has been seet with GUIUtil::setFont static std::map mapFontSizeUpdates; +// Contains a list of supported font weights for all members of GUIUtil::FontFamily +static std::map> mapSupportedWeights; #ifdef Q_OS_MAC // Contains all widgets where the macOS focus rect has been disabled. @@ -258,6 +266,55 @@ void setupAmountWidget(QLineEdit *widget, QWidget *parent) widget->setAlignment(Qt::AlignRight|Qt::AlignVCenter); } +void setupAppearance(QWidget* parent, OptionsModel* model) +{ + if (!QSettings().value("fAppearanceSetupDone", false).toBool()) { + // First make sure SystemDefault has reasonable default values if it does not support the full range of weights. + if (fontFamily == FontFamily::SystemDefault && getSupportedWeights().size() < 4) { + fontWeightNormal = mapSupportedWeights[FontFamily::SystemDefault].front(); + fontWeightBold = mapSupportedWeights[FontFamily::SystemDefault].back(); + QSettings().setValue("fontWeightNormal", weightToArg(fontWeightNormal)); + QSettings().setValue("fontWeightBold", weightToArg(fontWeightBold)); + } + // Create the dialog + QDialog dlg(parent); + dlg.setObjectName("AppearanceSetup"); + dlg.setWindowTitle(QObject::tr("Appearance Setup")); + dlg.setWindowIcon(QIcon(":icons/bitcoin")); + // And the widgets we add to it + QLabel lblHeading(QObject::tr("Please choose your prefered settings for the appearance of %1").arg(QObject::tr(PACKAGE_NAME)), &dlg); + lblHeading.setObjectName("lblHeading"); + lblHeading.setWordWrap(true); + QLabel lblSubHeading(QObject::tr("This can also be adjusted later in the \"Appearance\" tab of the preferences."), &dlg); + lblSubHeading.setObjectName("lblSubHeading"); + lblSubHeading.setWordWrap(true); + AppearanceWidget appearance(&dlg); + appearance.setModel(model); + QFrame line(&dlg); + line.setFrameShape(QFrame::HLine); + QDialogButtonBox buttonBox(QDialogButtonBox::Save); + // Put them into a vbox and add the vbox to the dialog + QVBoxLayout layout; + layout.addWidget(&lblHeading); + layout.addWidget(&lblSubHeading); + layout.addWidget(&line); + layout.addWidget(&appearance); + layout.addWidget(&buttonBox); + dlg.setLayout(&layout); + // Adjust the headings + setFont({&lblHeading}, FontWeight::Bold, 16); + setFont({&lblSubHeading}, FontWeight::Normal, 14, true); + // Make sure the dialog closes and accepts the settings if save has been pressed + QObject::connect(&buttonBox, &QDialogButtonBox::accepted, [&]() { + QSettings().setValue("fAppearanceSetupDone", true); + appearance.accept(); + dlg.accept(); + }); + // And fire it! + dlg.exec(); + } +} + bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out) { // return if URI is not valid or is no dash: URI @@ -1258,6 +1315,9 @@ double getScaledFontSize(int nSize) bool loadFonts() { + // Before any font changes store the applications default font to use it as SystemDefault. + osDefaultFont = std::make_unique(QApplication::font()); + QString family = fontFamilyToString(FontFamily::Montserrat); QString italic = "Italic"; @@ -1314,15 +1374,60 @@ bool loadFonts() } } + // Load font related settings + QSettings settings; + QFont::Weight weight; + + if (!gArgs.IsArgSet("-font-family")) { + fontFamily = fontFamilyFromString(settings.value("fontFamily").toString()); + } + + if (!gArgs.IsArgSet("-font-scale")) { + fontScale = settings.value("fontScale").toInt(); + } + + if (!gArgs.IsArgSet("-font-weight-normal") && weightFromArg(settings.value("fontWeightNormal").toInt(), weight)) { + fontWeightNormal = weight; + } + + if (!gArgs.IsArgSet("-font-weight-bold") && weightFromArg(settings.value("fontWeightBold").toInt(), weight)) { + fontWeightBold = weight; + } + setApplicationFont(); + // Initialize supported font weights for all available fonts + // Generate a vector with supported font weights by comparing the width of a certain test text for all font weights + auto supportedWeights = [](FontFamily family) -> std::vector { + auto getTestWidth = [&](QFont::Weight weight) -> int { + QFont font = getFont(family, weight, false, defaultFontSize); + return QFontMetrics(font).width("Check the width of this text to see if the weight change has an impact!"); + }; + std::vector vecWeights{QFont::Thin, QFont::ExtraLight, QFont::Light, + QFont::Normal, QFont::Medium, QFont::DemiBold, + QFont::Bold, QFont::Black}; + std::vector vecSupported; + QFont::Weight prevWeight = vecWeights.front(); + for (auto weight = vecWeights.begin() + 1; weight != vecWeights.end(); ++weight) { + if (getTestWidth(prevWeight) != getTestWidth(*weight)) { + if (vecSupported.empty()) { + vecSupported.push_back(prevWeight); + } + vecSupported.push_back(*weight); + } + prevWeight = *weight; + } + return vecSupported; + }; + + mapSupportedWeights.insert(std::make_pair(FontFamily::SystemDefault, supportedWeights(FontFamily::SystemDefault))); + mapSupportedWeights.insert(std::make_pair(FontFamily::Montserrat, supportedWeights(FontFamily::Montserrat))); + return true; } void setApplicationFont() { - static QFont osDefaultFont = QApplication::font(); - std::unique_ptr font; if (fontFamily == FontFamily::Montserrat) { @@ -1339,7 +1444,7 @@ void setApplicationFont() font->setWeight(getFontWeightNormal()); #endif } else { - font = std::make_unique(osDefaultFont); + font = std::make_unique(*osDefaultFont); } font->setPointSizeF(defaultFontSize); @@ -1440,13 +1545,11 @@ void updateFonts() } } -QFont getFont(FontWeight weight, bool fItalic, int nPointSize) +QFont getFont(FontFamily family, QFont::Weight qWeight, bool fItalic, int nPointSize) { QFont font; - QFont::Weight qWeight = toQFontWeight(weight); - - if (fontFamily == FontFamily::Montserrat) { + if (family == FontFamily::Montserrat) { static std::map mapMontserratMapping{ {QFont::Thin, "Thin"}, {QFont::ExtraLight, "ExtraLight"}, @@ -1486,7 +1589,7 @@ QFont getFont(FontWeight weight, bool fItalic, int nPointSize) font.setStyle(fItalic ? QFont::StyleItalic : QFont::StyleNormal); #endif } else { - font.setFamily(QApplication::font().family()); + font.setFamily(osDefaultFont->family()); font.setWeight(qWeight); font.setStyle(fItalic ? QFont::StyleItalic : QFont::StyleNormal); } @@ -1496,12 +1599,21 @@ QFont getFont(FontWeight weight, bool fItalic, int nPointSize) } if (gArgs.GetBoolArg("-debug-ui", false)) { - qDebug() << __func__ << ": font size: " << font.pointSizeF() << " family: " << font.family() << ", style: " << font.styleName() << " match: " << font.exactMatch(); + qDebug() << __func__ << ": font size: " << font.pointSizeF() << " family: " << font.family() << ", style: " << font.styleName() << ", weight:" << font.weight() << " match: " << font.exactMatch(); } return font; } +QFont getFont(QFont::Weight qWeight, bool fItalic, int nPointSize) +{ + return getFont(fontFamily, qWeight, fItalic, nPointSize); +} +QFont getFont(FontWeight weight, bool fItalic, int nPointSize) +{ + return getFont(toQFontWeight(weight), fItalic, nPointSize); +} + QFont getFontNormal() { return getFont(FontWeight::Normal); @@ -1512,6 +1624,30 @@ QFont getFontBold() return getFont(FontWeight::Bold); } +std::vector getSupportedWeights() +{ + assert(mapSupportedWeights.count(fontFamily)); + return mapSupportedWeights[fontFamily]; +} + +QFont::Weight supportedWeightFromIndex(int nIndex) +{ + auto vecWeights = getSupportedWeights(); + assert(vecWeights.size() > nIndex); + return vecWeights[nIndex]; +} + +int supportedWeightToIndex(QFont::Weight weight) +{ + auto vecWeights = getSupportedWeights(); + for (int index = 0; index < vecWeights.size(); ++index) { + if (weight == vecWeights[index]) { + return index; + } + } + assert(false); +} + QString getActiveTheme() { QSettings settings; diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 5d44c428251b..a21d317ff76a 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -18,6 +18,7 @@ #include class QValidatedLineEdit; +class OptionsModel; class SendCoinsRecipient; QT_BEGIN_NAMESPACE @@ -95,6 +96,9 @@ namespace GUIUtil void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent, bool fAllowURI = false); void setupAmountWidget(QLineEdit *widget, QWidget *parent); + // Setup appearance settings if not done yet + void setupAppearance(QWidget* parent, OptionsModel* model); + // Parse "dash:" URI into recipient object, return true on successful parsing bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out); bool parseBitcoinURI(QString uri, SendCoinsRecipient *out); @@ -322,8 +326,9 @@ namespace GUIUtil GUIUtil::setFont */ void updateFonts(); - /** Get a properly weighted QFont object with the font Montserrat - Use ExtraLight as default as this lines up with the default in css. */ + /** Get a properly weighted QFont object with the selected font. */ + QFont getFont(FontFamily family, QFont::Weight qWeight, bool fItalic = false, int nPointSize = -1); + QFont getFont(QFont::Weight qWeight, bool fItalic = false, int nPointSize = -1); QFont getFont(FontWeight weight, bool fItalic = false, int nPointSize = -1); /** Get the default normal QFont */ @@ -332,6 +337,13 @@ namespace GUIUtil /** Get the default bold QFont */ QFont getFontBold(); + /** Return supported weights for the current font family */ + std::vector getSupportedWeights(); + /** Convert an index to a weight in the supported weights vector */ + QFont::Weight supportedWeightFromIndex(int nIndex); + /** Convert a weight to an index in the supported weights vector */ + int supportedWeightToIndex(QFont::Weight weight); + /** Return the name of the currently active theme.*/ QString getActiveTheme(); diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index b40d73d0e2a8..70c92bb84250 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -37,8 +38,6 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : { ui->setupUi(this); - previousTheme = GUIUtil::getActiveTheme(); - GUIUtil::setFont({ui->statusLabel}, GUIUtil::FontWeight::Bold, 16); GUIUtil::updateFonts(); @@ -89,6 +88,7 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : pageButtons.addButton(ui->btnWindow, pageButtons.buttons().size()); #endif pageButtons.addButton(ui->btnDisplay, pageButtons.buttons().size()); + pageButtons.addButton(ui->btnAppearance, pageButtons.buttons().size()); connect(&pageButtons, SIGNAL(buttonClicked(int)), this, SLOT(showPage(int))); @@ -103,12 +103,6 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : ui->digits->addItem(digits, digits); } - /* Theme selector */ - for (const QString& entry : GUIUtil::listThemes()) { - ui->theme->addItem(entry, QVariant(entry)); - } - connect(ui->theme, SIGNAL(valueChanged()), this, SLOT(updateTheme())); - /* Language selector */ QDir translations(":translations"); @@ -149,6 +143,12 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : connect(ui->proxyIpTor, SIGNAL(validationDidChange(QValidatedLineEdit *)), this, SLOT(updateProxyValidationState())); connect(ui->proxyPort, SIGNAL(textChanged(const QString&)), this, SLOT(updateProxyValidationState())); connect(ui->proxyPortTor, SIGNAL(textChanged(const QString&)), this, SLOT(updateProxyValidationState())); + + QVBoxLayout* appearanceLayout = new QVBoxLayout(); + appearanceLayout->setContentsMargins(0, 0, 0, 0); + appearance = new AppearanceWidget(ui->widgetAppearance); + appearanceLayout->addWidget(appearance); + ui->widgetAppearance->setLayout(appearanceLayout); } OptionsDialog::~OptionsDialog() @@ -175,6 +175,8 @@ void OptionsDialog::setModel(OptionsModel *_model) setMapper(); mapper->toFirst(); + appearance->setModel(_model); + updateDefaultProxyNets(); } @@ -235,11 +237,13 @@ void OptionsDialog::setMapper() /* Display */ mapper->addMapping(ui->digits, OptionsModel::Digits); - mapper->addMapping(ui->theme, OptionsModel::Theme); mapper->addMapping(ui->lang, OptionsModel::Language); mapper->addMapping(ui->unit, OptionsModel::DisplayUnit); mapper->addMapping(ui->thirdPartyTxUrls, OptionsModel::ThirdPartyTxUrls); + /* Appearance + See AppearanceWidget::setModel + */ } void OptionsDialog::showPage(int index) @@ -285,6 +289,7 @@ void OptionsDialog::on_resetButton_clicked() void OptionsDialog::on_okButton_clicked() { mapper->submit(); + appearance->accept(); #ifdef ENABLE_WALLET privateSendClient.nCachedNumBlocks = std::numeric_limits::max(); if(HasWallets()) @@ -296,9 +301,6 @@ void OptionsDialog::on_okButton_clicked() void OptionsDialog::on_cancelButton_clicked() { - if (previousTheme != GUIUtil::getActiveTheme()) { - updateTheme(previousTheme); - } reject(); } @@ -379,16 +381,6 @@ void OptionsDialog::updateDefaultProxyNets() (strProxy == strDefaultProxyGUI.toStdString()) ? ui->proxyReachTor->setChecked(true) : ui->proxyReachTor->setChecked(false); } -void OptionsDialog::updateTheme(const QString& theme) -{ - QString newValue = theme.isEmpty() ? ui->theme->value().toString() : theme; - if (GUIUtil::getActiveTheme() != newValue) { - QSettings().setValue("theme", newValue); - QSettings().sync(); - Q_EMIT themeChanged(); - } -} - ProxyAddressValidator::ProxyAddressValidator(QObject *parent) : QValidator(parent) { diff --git a/src/qt/optionsdialog.h b/src/qt/optionsdialog.h index 2a9a65005371..cc0bfdb00b93 100644 --- a/src/qt/optionsdialog.h +++ b/src/qt/optionsdialog.h @@ -9,6 +9,7 @@ #include #include +class AppearanceWidget; class OptionsModel; class QValidatedLineEdit; @@ -60,11 +61,9 @@ private Q_SLOTS: void updateProxyValidationState(); /* query the networks, for which the default proxy is used */ void updateDefaultProxyNets(); - void updateTheme(const QString& toTheme = QString()); Q_SIGNALS: void proxyIpChecks(QValidatedLineEdit *pUiProxyIp, int nProxyPort); - void themeChanged(); private: Ui::OptionsDialog *ui; @@ -72,6 +71,7 @@ private Q_SLOTS: QDataWidgetMapper *mapper; QButtonGroup pageButtons; QString previousTheme; + AppearanceWidget* appearance; }; #endif // BITCOIN_QT_OPTIONSDIALOG_H diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index efb95f56e80c..30afa567235b 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -84,9 +84,28 @@ void OptionsModel::Init(bool resetSettings) settings.setValue("strThirdPartyTxUrls", ""); strThirdPartyTxUrls = settings.value("strThirdPartyTxUrls", "").toString(); + // Appearance if (!settings.contains("theme")) settings.setValue("theme", GUIUtil::getDefaultTheme()); + if (!settings.contains("fontFamily")) + settings.setValue("fontFamily", GUIUtil::fontFamilyToString(GUIUtil::getFontFamilyDefault())); + + if (!settings.contains("fontScale")) + settings.setValue("fontScale", GUIUtil::getFontScaleDefault()); + if (!gArgs.SoftSetArg("-font-scale", settings.value("fontScale").toString().toStdString())) + addOverriddenOption("-font-scale"); + + if (!settings.contains("fontWeightNormal")) + settings.setValue("fontWeightNormal", GUIUtil::weightToArg(GUIUtil::getFontWeightNormalDefault())); + if (!gArgs.SoftSetArg("-font-weight-normal", settings.value("fontWeightNormal").toString().toStdString())) + addOverriddenOption("-font-weight-normal"); + + if (!settings.contains("fontWeightBold")) + settings.setValue("fontWeightBold", GUIUtil::weightToArg(GUIUtil::getFontWeightBoldDefault())); + if (!gArgs.SoftSetArg("-font-weight-bold", settings.value("fontWeightBold").toString().toStdString())) + addOverriddenOption("-font-weight-bold"); + #ifdef ENABLE_WALLET if (!settings.contains("fCoinControlFeatures")) settings.setValue("fCoinControlFeatures", false); @@ -337,6 +356,20 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const #endif // ENABLE_WALLET case Theme: return settings.value("theme"); + case FontFamily: + return settings.value("fontFamily"); + case FontScale: + return settings.value("fontScale"); + case FontWeightNormal: { + QFont::Weight weight; + GUIUtil::weightFromArg(settings.value("fontWeightNormal").toInt(), weight); + return GUIUtil::supportedWeightToIndex(weight); + } + case FontWeightBold: { + QFont::Weight weight; + GUIUtil::weightFromArg(settings.value("fontWeightBold").toInt(), weight); + return GUIUtil::supportedWeightToIndex(weight); + } case Language: return settings.value("language"); #ifdef ENABLE_WALLET @@ -510,9 +543,33 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in break; #endif // ENABLE_WALLET case Theme: - // Set in OptionsDialog::updateTheme slot now + // Set in AppearanceWidget::updateTheme slot now // to allow instant theme changes. break; + case FontFamily: + if (settings.value("fontFamily") != value) { + settings.setValue("fontFamily", value); + } + break; + case FontScale: + if (settings.value("fontScale") != value) { + settings.setValue("fontScale", value); + } + break; + case FontWeightNormal: { + int nWeight = GUIUtil::weightToArg(GUIUtil::supportedWeightFromIndex(value.toInt())); + if (settings.value("fontWeightNormal") != nWeight) { + settings.setValue("fontWeightNormal", nWeight); + } + break; + } + case FontWeightBold: { + int nWeight = GUIUtil::weightToArg(GUIUtil::supportedWeightFromIndex(value.toInt())); + if (settings.value("fontWeightBold") != nWeight) { + settings.setValue("fontWeightBold", nWeight); + } + break; + } case Language: if (settings.value("language") != value) { settings.setValue("language", value); diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 01a7181dad01..711318998dff 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -45,6 +45,10 @@ class OptionsModel : public QAbstractListModel ThirdPartyTxUrls, // QString Digits, // QString Theme, // QString + FontFamily, // int + FontScale, // int + FontWeightNormal, // int + FontWeightBold, // int Language, // QString CoinControlFeatures, // bool ThreadsScriptVerif, // int diff --git a/src/qt/res/css/dark.css b/src/qt/res/css/dark.css index a3e1d5f03f5d..09ae5929e430 100644 --- a/src/qt/res/css/dark.css +++ b/src/qt/res/css/dark.css @@ -402,6 +402,7 @@ QPushButton - Special case, tabbar replacement buttons #btnNetwork, #btnWindow, #btnDisplay, +#btnAppearance, /* Sign/Verify dialog buttons */ #btnSignMessage, #btnVerifyMessage { @@ -421,6 +422,7 @@ QPushButton - Special case, tabbar replacement buttons #btnNetwork:hover:checked, #btnWindow:hover:checked, #btnDisplay:hover:checked, +#btnAppearance:hover:checked, /* Sign/Verify dialog buttons */ #btnSignMessage:hover:checked, #btnVerifyMessage:hover:checked { @@ -440,6 +442,7 @@ QPushButton - Special case, tabbar replacement buttons #btnNetwork:hover:!checked, #btnWindow:hover:!checked, #btnDisplay:hover:!checked, +#btnAppearance:hover:!checked, /* Sign/Verify dialog buttons */ #btnSignMessage:hover:!checked, #btnVerifyMessage:hover:!checked { @@ -459,6 +462,7 @@ QPushButton - Special case, tabbar replacement buttons #btnNetwork:checked, #btnWindow:checked, #btnDisplay:checked, +#btnAppearance:checked, /* Sign/Verify dialog buttons */ #btnSignMessage:checked, #btnVerifyMessage:checked { @@ -630,6 +634,20 @@ AddressBookPage QTableView { color: #c7c7c7; } +/****************************************************** +AppearanceSetup +******************************************************/ + +QDialog#AppearanceSetup > QFrame { + border-color: #4a4a4b; +} + +/****************************************************** +AppearanceWidget +******************************************************/ + +/***** No dark.css specific coloring here yet *****/ + /****************************************************** AskPassphraseDialog ******************************************************/ diff --git a/src/qt/res/css/general.css b/src/qt/res/css/general.css index 1982e5555f9e..8d8eeda0b960 100644 --- a/src/qt/res/css/general.css +++ b/src/qt/res/css/general.css @@ -649,6 +649,7 @@ QPushButton - Special case, tabbar replacement buttons #btnNetwork, #btnWindow, #btnDisplay, +#btnAppearance, /* Sign/Verify dialog buttons */ #btnSignMessage, #btnVerifyMessage { @@ -671,6 +672,7 @@ QPushButton - Special case, tabbar replacement buttons #btnNetwork:hover:checked, #btnWindow:hover:checked, #btnDisplay:hover:checked, +#btnAppearance:hover:checked, /* Sign/Verify dialog buttons */ #btnSignMessage:hover:checked, #btnVerifyMessage:hover:checked { @@ -693,6 +695,7 @@ QPushButton - Special case, tabbar replacement buttons #btnNetwork:hover:!checked, #btnWindow:hover:!checked, #btnDisplay:hover:!checked, +#btnAppearance:hover:!checked, /* Sign/Verify dialog buttons */ #btnSignMessage:hover:!checked, #btnVerifyMessage:hover:!checked { @@ -715,6 +718,7 @@ QPushButton - Special case, tabbar replacement buttons #btnNetwork:checked, #btnWindow:checked, #btnDisplay:checked, +#btnAppearance:checked, /* Sign/Verify dialog buttons */ #btnSignMessage:checked, #btnVerifyMessage:checked { @@ -736,6 +740,7 @@ QPushButton - Special case, tabbar replacement buttons #btnNetwork:hover:pressed, #btnWindow:hover:pressed, #btnDisplay:hover:pressed, +#btnAppearance:hover:pressed, /* Sign/Verify dialog buttons */ #btnSignMessage:hover:pressed, #btnVerifyMessage:hover:pressed { @@ -1054,6 +1059,71 @@ QWidget#AddressBookPage QHeaderView::section { /* Min width for Windows fix */ min-width: 260px; } +/****************************************************** +AppearanceSetup +******************************************************/ + +QDialog#AppearanceSetup { + min-width: 600px; + min-height: 400px; +} + +QDialog#AppearanceSetup > QFrame { + border-bottom: 1px solid red; +} + +QDialog#AppearanceSetup > QLabel#lblHeading { + max-height: 50px; +} + +QDialog#AppearanceSetup > QLabel#lblSubHeading { + min-height: 30px; + max-height: 50px; +} + +QDialog#AppearanceSetup > AppearanceWidget { + min-height: 250px; + max-height: 250px; +} + +/****************************************************** +AppearanceWidget +******************************************************/ + +AppearanceWidget #lblSmaller, +AppearanceWidget #lblBigger, +AppearanceWidget #lblBolderNormal, +AppearanceWidget #lblBolderBold, +AppearanceWidget #lblLighterNormal, +AppearanceWidget #lblLighterBold { + /* Exceptional use of font-size here to + avoid changes of the slider postions */ + font-size: 13px; +} + +AppearanceWidget #lblTheme, +AppearanceWidget #lblFontFamily, +AppearanceWidget #lblFontScale, +AppearanceWidget #lblFontWeightNormal, +AppearanceWidget #lblFontWeightBold, +AppearanceWidget #lblSmaller, +AppearanceWidget #lblBigger, +AppearanceWidget #lblBolderNormal, +AppearanceWidget #lblBolderBold, +AppearanceWidget #lblLighterNormal, +AppearanceWidget #lblLighterBold { + min-height: 40px; +} + +AppearanceWidget #lblTheme, +AppearanceWidget #lblFontFamily, +AppearanceWidget #lblFontScale, +AppearanceWidget #lblFontWeightNormal, +AppearanceWidget #lblFontWeightBold { + min-width: 170px; + max-width: 170px; +} + /****************************************************** AskPassphraseDialog ******************************************************/ diff --git a/src/qt/res/css/light.css b/src/qt/res/css/light.css index 297de242a9c5..f94fd8e5816c 100644 --- a/src/qt/res/css/light.css +++ b/src/qt/res/css/light.css @@ -383,6 +383,7 @@ QPushButton - Special case, tabbar replacement buttons #btnNetwork, #btnWindow, #btnDisplay, +#btnAppearance, /* Sign/Verify dialog buttons */ #btnSignMessage, #btnVerifyMessage { @@ -402,6 +403,7 @@ QPushButton - Special case, tabbar replacement buttons #btnNetwork:hover:checked, #btnWindow:hover:checked, #btnDisplay:hover:checked, +#btnAppearance:hover:checked, /* Sign/Verify dialog buttons */ #btnSignMessage:hover:checked, #btnVerifyMessage:hover:checked { @@ -421,6 +423,7 @@ QPushButton - Special case, tabbar replacement buttons #btnNetwork:hover:!checked, #btnWindow:hover:!checked, #btnDisplay:hover:!checked, +#btnAppearance:hover:!checked, /* Sign/Verify dialog buttons */ #btnSignMessage:hover:!checked, #btnVerifyMessage:hover:!checked { @@ -440,6 +443,7 @@ QPushButton - Special case, tabbar replacement buttons #btnNetwork:checked, #btnWindow:checked, #btnDisplay:checked, +#btnAppearance:checked, /* Sign/Verify dialog buttons */ #btnSignMessage:checked, #btnVerifyMessage:checked { @@ -613,6 +617,20 @@ AddressBookPage QTableView { color: #555; } +/****************************************************** +AppearanceSetup +******************************************************/ + +QDialog#AppearanceSetup > QFrame { + border-color: #dcdcdc; +} + +/****************************************************** +AppearanceWidget +******************************************************/ + +/***** No light.css specific coloring here yet *****/ + /****************************************************** AskPassphraseDialog ******************************************************/ diff --git a/src/qt/res/css/traditional.css b/src/qt/res/css/traditional.css index 0fed15c2307b..a41467a0cece 100644 --- a/src/qt/res/css/traditional.css +++ b/src/qt/res/css/traditional.css @@ -56,6 +56,7 @@ QPushButton - Special case, tabbar replacement buttons #btnNetwork, #btnWindow, #btnDisplay, +#btnAppearance, /* Sign/Verify dialog buttons */ #btnSignMessage, #btnVerifyMessage { @@ -79,6 +80,7 @@ QPushButton - Special case, tabbar replacement buttons #btnNetwork:hover:checked, #btnWindow:hover:checked, #btnDisplay:hover:checked, +#btnAppearance:hover:checked, /* Sign/Verify dialog buttons */ #btnSignMessage:hover:checked, #btnVerifyMessage:hover:checked { @@ -101,6 +103,7 @@ QPushButton - Special case, tabbar replacement buttons #btnNetwork:hover:!checked, #btnWindow:hover:!checked, #btnDisplay:hover:!checked, +#btnAppearance:hover:!checked, /* Sign/Verify dialog buttons */ #btnSignMessage:hover:!checked, #btnVerifyMessage:hover:!checked { @@ -123,6 +126,7 @@ QPushButton - Special case, tabbar replacement buttons #btnNetwork:checked, #btnWindow:checked, #btnDisplay:checked, +#btnAppearance:checked, /* Sign/Verify dialog buttons */ #btnSignMessage:checked, #btnVerifyMessage:checked { @@ -152,6 +156,74 @@ QWidget#contentWidget { /* The actual content with the text/buttons/etc... */ border-radius: 6px; } +/****************************************************** +AppearanceSetup +******************************************************/ + +QDialog#AppearanceSetup { + min-width: 600px; + max-width: 600px; + min-height: 400px; + max-height: 400px; +} + +QDialog#AppearanceSetup > QFrame { + border-bottom: 1px solid red; +} + +QDialog#AppearanceSetup > QLabel#lblHeading { + min-height: 50px; + max-height: 50px; +} + +QDialog#AppearanceSetup > QLabel#lblSubHeading { + min-height: 30px; + max-height: 30px; +} + +QDialog#AppearanceSetup > AppearanceWidget { + min-height: 250px; + max-height: 250px; +} + +/****************************************************** +AppearanceWidget +******************************************************/ + +AppearanceWidget #lblSmaller, +AppearanceWidget #lblBigger, +AppearanceWidget #lblBolderNormal, +AppearanceWidget #lblBolderBold, +AppearanceWidget #lblLighterNormal, +AppearanceWidget #lblLighterBold { + /* Exceptional use of font-size here to + avoid changes of the slider postions */ + font-size: 13px; +} + +AppearanceWidget #lblTheme, +AppearanceWidget #lblFontFamily, +AppearanceWidget #lblFontScale, +AppearanceWidget #lblFontWeightNormal, +AppearanceWidget #lblFontWeightBold, +AppearanceWidget #lblSmaller, +AppearanceWidget #lblBigger, +AppearanceWidget #lblBolderNormal, +AppearanceWidget #lblBolderBold, +AppearanceWidget #lblLighterNormal, +AppearanceWidget #lblLighterBold { + min-height: 40px; +} + +AppearanceWidget #lblTheme, +AppearanceWidget #lblFontFamily, +AppearanceWidget #lblFontScale, +AppearanceWidget #lblFontWeightNormal, +AppearanceWidget #lblFontWeightBold { + min-width: 170px; + max-width: 170px; +} + /****************************************************** OverviewPage ******************************************************/ diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 693e1f051f49..bb1a4d1895b4 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -110,6 +110,7 @@ QModelIndex FindTx(const QAbstractItemModel& model, const uint256& txid) // src/qt/test/test_dash-qt -platform cocoa # macOS void TestGUI() { + GUIUtil::loadFonts(); // Set up wallet and chain with 105 blocks (5 mature blocks for spending). TestChain100Setup test; for (int i = 0; i < 5; ++i) {