diff --git a/src/Gui/Stylesheets/Behave-dark.qss b/src/Gui/Stylesheets/Behave-dark.qss index da1db23fc5..5c4e3e4da0 100644 --- a/src/Gui/Stylesheets/Behave-dark.qss +++ b/src/Gui/Stylesheets/Behave-dark.qss @@ -2276,3 +2276,38 @@ QPushButton#NavigationIndicator::menu-indicator { image: none; width: 0px; } + +/*================================================================================================== +SKETCHER +==================================================================================================*/ + +Gui--StatefulLabel[state="empty_sketch"] { + color : rgba(255,255,255,127); /* 50% opacity white */ +} +Gui--StatefulLabel[state="under_constrained"] { + color : rgba(255,255,255,255); /* White */ +} +Gui--StatefulLabel[state="conflicting_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="malformed_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="redundant_constraints"] { + color : rgba(255,69,0,255); /* Orange red */ +} +Gui--StatefulLabel[state="partially_redundant_constraints"] { + color : rgba(65,105,225,255); /* Royal blue */ +} +Gui--StatefulLabel[state="solver_failed"] { + color : rgba(255,0,0,255); /* Red */ + font-weight: bold; +} +Gui--StatefulLabel[state="fully_constrained"] { + color : rgba(0,255,0,255); /* Green */ + font-weight: bold; +} +Gui--UrlLabel { + color : rgba(0,91,255,255); /* Deep sky blue */ + text-decoration : underline; +} diff --git a/src/Gui/Stylesheets/Dark-blue.qss b/src/Gui/Stylesheets/Dark-blue.qss index 32c2c0f3d1..22e24d6864 100644 --- a/src/Gui/Stylesheets/Dark-blue.qss +++ b/src/Gui/Stylesheets/Dark-blue.qss @@ -2243,3 +2243,39 @@ QPushButton#NavigationIndicator::menu-indicator { image: none; width: 0px; } + + +/*================================================================================================== +SKETCHER +==================================================================================================*/ + +Gui--StatefulLabel[state="empty_sketch"] { + color : rgba(255,255,255,127); /* 50% opacity white */ +} +Gui--StatefulLabel[state="under_constrained"] { + color : rgba(255,255,255,255); /* White */ +} +Gui--StatefulLabel[state="conflicting_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="malformed_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="redundant_constraints"] { + color : rgba(255,69,0,255); /* Orange red */ +} +Gui--StatefulLabel[state="partially_redundant_constraints"] { + color : rgba(65,105,225,255); /* Royal blue */ +} +Gui--StatefulLabel[state="solver_failed"] { + color : rgba(255,0,0,255); /* Red */ + font-weight: bold; +} +Gui--StatefulLabel[state="fully_constrained"] { + color : rgba(0,255,0,255); /* Green */ + font-weight: bold; +} +Gui--UrlLabel { + color : rgba(0,91,255,255); /* Deep sky blue */ + text-decoration : underline; +} diff --git a/src/Gui/Stylesheets/Dark-contrast.qss b/src/Gui/Stylesheets/Dark-contrast.qss index a60202a669..bbb69eca74 100644 --- a/src/Gui/Stylesheets/Dark-contrast.qss +++ b/src/Gui/Stylesheets/Dark-contrast.qss @@ -2269,3 +2269,38 @@ QToolBar QToolButton[popupMode="1"] { width: 100px; background: red; } + +/*================================================================================================== +SKETCHER +==================================================================================================*/ + +Gui--StatefulLabel[state="empty_sketch"] { + color : rgba(255,255,255,127); /* 50% opacity white */ +} +Gui--StatefulLabel[state="under_constrained"] { + color : rgba(255,255,255,255); /* White */ +} +Gui--StatefulLabel[state="conflicting_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="malformed_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="redundant_constraints"] { + color : rgba(255,69,0,255); /* Orange red */ +} +Gui--StatefulLabel[state="partially_redundant_constraints"] { + color : rgba(65,105,225,255); /* Royal blue */ +} +Gui--StatefulLabel[state="solver_failed"] { + color : rgba(255,0,0,255); /* Red */ + font-weight: bold; +} +Gui--StatefulLabel[state="fully_constrained"] { + color : rgba(0,255,0,255); /* Green */ + font-weight: bold; +} +Gui--UrlLabel { + color : rgba(0,91,255,255); /* Deep sky blue */ + text-decoration : underline; +} diff --git a/src/Gui/Stylesheets/Dark-green.qss b/src/Gui/Stylesheets/Dark-green.qss index 7b49d5bcd0..82e972b618 100644 --- a/src/Gui/Stylesheets/Dark-green.qss +++ b/src/Gui/Stylesheets/Dark-green.qss @@ -2242,3 +2242,38 @@ QPushButton#NavigationIndicator::menu-indicator { image: none; width: 0px; } + +/*================================================================================================== +SKETCHER +==================================================================================================*/ + +Gui--StatefulLabel[state="empty_sketch"] { + color : rgba(255,255,255,127); /* 50% opacity white */ +} +Gui--StatefulLabel[state="under_constrained"] { + color : rgba(255,255,255,255); /* White */ +} +Gui--StatefulLabel[state="conflicting_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="malformed_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="redundant_constraints"] { + color : rgba(255,69,0,255); /* Orange red */ +} +Gui--StatefulLabel[state="partially_redundant_constraints"] { + color : rgba(65,105,225,255); /* Royal blue */ +} +Gui--StatefulLabel[state="solver_failed"] { + color : rgba(255,0,0,255); /* Red */ + font-weight: bold; +} +Gui--StatefulLabel[state="fully_constrained"] { + color : rgba(0,255,0,255); /* Green */ + font-weight: bold; +} +Gui--UrlLabel { + color : rgba(0,91,255,255); /* Deep sky blue */ + text-decoration : underline; +} diff --git a/src/Gui/Stylesheets/Dark-orange.qss b/src/Gui/Stylesheets/Dark-orange.qss index 4305187e0f..99da5bfaa9 100644 --- a/src/Gui/Stylesheets/Dark-orange.qss +++ b/src/Gui/Stylesheets/Dark-orange.qss @@ -2243,3 +2243,38 @@ QPushButton#NavigationIndicator::menu-indicator { image: none; width: 0px; } + +/*================================================================================================== +SKETCHER +==================================================================================================*/ + +Gui--StatefulLabel[state="empty_sketch"] { + color : rgba(255,255,255,127); /* 50% opacity white */ +} +Gui--StatefulLabel[state="under_constrained"] { + color : rgba(255,255,255,255); /* White */ +} +Gui--StatefulLabel[state="conflicting_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="malformed_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="redundant_constraints"] { + color : rgba(255,69,0,255); /* Orange red */ +} +Gui--StatefulLabel[state="partially_redundant_constraints"] { + color : rgba(65,105,225,255); /* Royal blue */ +} +Gui--StatefulLabel[state="solver_failed"] { + color : rgba(255,0,0,255); /* Red */ + font-weight: bold; +} +Gui--StatefulLabel[state="fully_constrained"] { + color : rgba(0,255,0,255); /* Green */ + font-weight: bold; +} +Gui--UrlLabel { + color : rgba(0,91,255,255); /* Deep sky blue */ + text-decoration : underline; +} diff --git a/src/Gui/Stylesheets/Darker-blue.qss b/src/Gui/Stylesheets/Darker-blue.qss index 12cf7a1204..2f08c5139f 100644 --- a/src/Gui/Stylesheets/Darker-blue.qss +++ b/src/Gui/Stylesheets/Darker-blue.qss @@ -2269,3 +2269,38 @@ QToolBar QToolButton[popupMode="1"] { width: 100px; background: red; } + +/*================================================================================================== +SKETCHER +==================================================================================================*/ + +Gui--StatefulLabel[state="empty_sketch"] { + color : rgba(255,255,255,127); /* 50% opacity white */ +} +Gui--StatefulLabel[state="under_constrained"] { + color : rgba(255,255,255,255); /* White */ +} +Gui--StatefulLabel[state="conflicting_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="malformed_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="redundant_constraints"] { + color : rgba(255,69,0,255); /* Orange red */ +} +Gui--StatefulLabel[state="partially_redundant_constraints"] { + color : rgba(65,105,225,255); /* Royal blue */ +} +Gui--StatefulLabel[state="solver_failed"] { + color : rgba(255,0,0,255); /* Red */ + font-weight: bold; +} +Gui--StatefulLabel[state="fully_constrained"] { + color : rgba(0,255,0,255); /* Green */ + font-weight: bold; +} +Gui--UrlLabel { + color : rgba(0,91,255,255); /* Deep sky blue */ + text-decoration : underline; +} diff --git a/src/Gui/Stylesheets/Darker-green.qss b/src/Gui/Stylesheets/Darker-green.qss index 9a54cefc36..529fc276c7 100644 --- a/src/Gui/Stylesheets/Darker-green.qss +++ b/src/Gui/Stylesheets/Darker-green.qss @@ -2269,3 +2269,38 @@ QToolBar QToolButton[popupMode="1"] { width: 100px; background: red; } + +/*================================================================================================== +SKETCHER +==================================================================================================*/ + +Gui--StatefulLabel[state="empty_sketch"] { + color : rgba(255,255,255,127); /* 50% opacity white */ +} +Gui--StatefulLabel[state="under_constrained"] { + color : rgba(255,255,255,255); /* White */ +} +Gui--StatefulLabel[state="conflicting_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="malformed_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="redundant_constraints"] { + color : rgba(255,69,0,255); /* Orange red */ +} +Gui--StatefulLabel[state="partially_redundant_constraints"] { + color : rgba(65,105,225,255); /* Royal blue */ +} +Gui--StatefulLabel[state="solver_failed"] { + color : rgba(255,0,0,255); /* Red */ + font-weight: bold; +} +Gui--StatefulLabel[state="fully_constrained"] { + color : rgba(0,255,0,255); /* Green */ + font-weight: bold; +} +Gui--UrlLabel { + color : rgba(0,91,255,255); /* Deep sky blue */ + text-decoration : underline; +} diff --git a/src/Gui/Stylesheets/Darker-orange.qss b/src/Gui/Stylesheets/Darker-orange.qss index 1f94c59206..68e77082fa 100644 --- a/src/Gui/Stylesheets/Darker-orange.qss +++ b/src/Gui/Stylesheets/Darker-orange.qss @@ -2269,3 +2269,38 @@ QToolBar QToolButton[popupMode="1"] { width: 100px; background: red; } + +/*================================================================================================== +SKETCHER +==================================================================================================*/ + +Gui--StatefulLabel[state="empty_sketch"] { + color : rgba(255,255,255,127); /* 50% opacity white */ +} +Gui--StatefulLabel[state="under_constrained"] { + color : rgba(255,255,255,255); /* White */ +} +Gui--StatefulLabel[state="conflicting_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="malformed_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="redundant_constraints"] { + color : rgba(255,69,0,255); /* Orange red */ +} +Gui--StatefulLabel[state="partially_redundant_constraints"] { + color : rgba(65,105,225,255); /* Royal blue */ +} +Gui--StatefulLabel[state="solver_failed"] { + color : rgba(255,0,0,255); /* Red */ + font-weight: bold; +} +Gui--StatefulLabel[state="fully_constrained"] { + color : rgba(0,255,0,255); /* Green */ + font-weight: bold; +} +Gui--UrlLabel { + color : rgba(0,91,255,255); /* Deep sky blue */ + text-decoration : underline; +} diff --git a/src/Gui/Stylesheets/Light-blue.qss b/src/Gui/Stylesheets/Light-blue.qss index 0742fd3cf2..e009d5826c 100644 --- a/src/Gui/Stylesheets/Light-blue.qss +++ b/src/Gui/Stylesheets/Light-blue.qss @@ -2244,3 +2244,38 @@ QPushButton#NavigationIndicator::menu-indicator { image: none; width: 0px; } + +/*================================================================================================== +SKETCHER +==================================================================================================*/ + +Gui--StatefulLabel[state="empty_sketch"] { + color : rgba(0,0,0,127); /* 50% opacity black */ +} +Gui--StatefulLabel[state="under_constrained"] { + color : rgba(0,0,0,255); /* Black */ +} +Gui--StatefulLabel[state="conflicting_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="malformed_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="redundant_constraints"] { + color : rgba(255,69,0,255); /* Orange red */ +} +Gui--StatefulLabel[state="partially_redundant_constraints"] { + color : rgba(65,105,225,255); /* Royal blue */ +} +Gui--StatefulLabel[state="solver_failed"] { + color : rgba(255,0,0,255); /* Red */ + font-weight: bold; +} +Gui--StatefulLabel[state="fully_constrained"] { + color : rgba(0,255,0,255); /* Green */ + font-weight: bold; +} +Gui--UrlLabel { + color : rgba(0,0,255,255); /* Blue */ + text-decoration : underline; +} diff --git a/src/Gui/Stylesheets/Light-green.qss b/src/Gui/Stylesheets/Light-green.qss index 6bf2e1a460..e2e3c3b1cd 100644 --- a/src/Gui/Stylesheets/Light-green.qss +++ b/src/Gui/Stylesheets/Light-green.qss @@ -2244,3 +2244,39 @@ QPushButton#NavigationIndicator::menu-indicator { image: none; width: 0px; } + + +/*================================================================================================== +SKETCHER +==================================================================================================*/ + +Gui--StatefulLabel[state="empty_sketch"] { + color : rgba(0,0,0,127); /* 50% opacity black */ +} +Gui--StatefulLabel[state="under_constrained"] { + color : rgba(0,0,0,255); /* Black */ +} +Gui--StatefulLabel[state="conflicting_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="malformed_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="redundant_constraints"] { + color : rgba(255,69,0,255); /* Orange red */ +} +Gui--StatefulLabel[state="partially_redundant_constraints"] { + color : rgba(65,105,225,255); /* Royal blue */ +} +Gui--StatefulLabel[state="solver_failed"] { + color : rgba(255,0,0,255); /* Red */ + font-weight: bold; +} +Gui--StatefulLabel[state="fully_constrained"] { + color : rgba(0,255,0,255); /* Green */ + font-weight: bold; +} +Gui--UrlLabel { + color : rgba(0,0,255,255); /* Blue */ + text-decoration : underline; +} diff --git a/src/Gui/Stylesheets/Light-orange.qss b/src/Gui/Stylesheets/Light-orange.qss index 3df634467c..00f410f979 100644 --- a/src/Gui/Stylesheets/Light-orange.qss +++ b/src/Gui/Stylesheets/Light-orange.qss @@ -2244,3 +2244,38 @@ QPushButton#NavigationIndicator::menu-indicator { image: none; width: 0px; } + +/*================================================================================================== +SKETCHER +==================================================================================================*/ + +Gui--StatefulLabel[state="empty_sketch"] { + color : rgba(0,0,0,127); /* 50% opacity black */ +} +Gui--StatefulLabel[state="under_constrained"] { + color : rgba(0,0,0,255); /* Black */ +} +Gui--StatefulLabel[state="conflicting_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="malformed_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="redundant_constraints"] { + color : rgba(255,69,0,255); /* Orange red */ +} +Gui--StatefulLabel[state="partially_redundant_constraints"] { + color : rgba(65,105,225,255); /* Royal blue */ +} +Gui--StatefulLabel[state="solver_failed"] { + color : rgba(255,0,0,255); /* Red */ + font-weight: bold; +} +Gui--StatefulLabel[state="fully_constrained"] { + color : rgba(0,255,0,255); /* Green */ + font-weight: bold; +} +Gui--UrlLabel { + color : rgba(0,0,255,255); /* Blue */ + text-decoration : underline; +} diff --git a/src/Gui/Stylesheets/ProDark.qss b/src/Gui/Stylesheets/ProDark.qss index 1fe4f811fe..0024af9d0c 100644 --- a/src/Gui/Stylesheets/ProDark.qss +++ b/src/Gui/Stylesheets/ProDark.qss @@ -2494,3 +2494,34 @@ QToolBar QToolButton[popupMode="1"] { width: 100px; background: red; } + +/*================================================================================================== +SKETCHER +==================================================================================================*/ + +Gui--StatefulLabel[state="empty_sketch"] { + color : rgba(255,255,255,127); /* 50% opacity white */ +} +Gui--StatefulLabel[state="under_constrained"] { + color : rgba(255,255,255,255); /* White */ +} +Gui--StatefulLabel[state="conflicting_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="malformed_constraints"] { + color : rgba(255,0,0,255); /* Red */ +} +Gui--StatefulLabel[state="redundant_constraints"] { + color : rgba(255,69,0,255); /* Orange red */ +} +Gui--StatefulLabel[state="partially_redundant_constraints"] { + color : rgba(65,105,225,255); /* Royal blue */ +} +Gui--StatefulLabel[state="solver_failed"] { + color : rgba(255,0,0,255); /* Red */ + font-weight: bold; +} +Gui--StatefulLabel[state="fully_constrained"] { + color : rgba(0,255,0,255); /* Green */ + font-weight: bold; +} diff --git a/src/Gui/Widgets.cpp b/src/Gui/Widgets.cpp index d1cfad653d..cf59f11167 100644 --- a/src/Gui/Widgets.cpp +++ b/src/Gui/Widgets.cpp @@ -835,6 +835,7 @@ void ColorButton::onRejected() UrlLabel::UrlLabel(QWidget* parent, Qt::WindowFlags f) : QLabel(parent, f) , _url (QStringLiteral("http://localhost")) + , _launchExternal(true) { setToolTip(this->_url); setCursor(Qt::PointingHandCursor); @@ -846,9 +847,18 @@ UrlLabel::~UrlLabel() { } +void Gui::UrlLabel::setLaunchExternal(bool l) +{ + _launchExternal = l; +} + void UrlLabel::mouseReleaseEvent(QMouseEvent*) { - QDesktopServices::openUrl(this->_url); + if (_launchExternal) + QDesktopServices::openUrl(this->_url); + else + // Someone else will deal with it... + Q_EMIT linkClicked(_url); } QString UrlLabel::url() const @@ -856,6 +866,11 @@ QString UrlLabel::url() const return this->_url; } +bool Gui::UrlLabel::launchExternal() const +{ + return _launchExternal; +} + void UrlLabel::setUrl(const QString& u) { this->_url = u; @@ -864,6 +879,157 @@ void UrlLabel::setUrl(const QString& u) // -------------------------------------------------------------------- +StatefulLabel::StatefulLabel(QWidget* parent) + : QLabel(parent) + , _overridePreference(false) +{ + // Always attach to the parameter group that stores the main FreeCAD stylesheet + _stylesheetGroup = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General"); + _stylesheetGroup->Attach(this); +} + +StatefulLabel::~StatefulLabel() +{ + if (_parameterGroup.isValid()) + _parameterGroup->Detach(this); + _stylesheetGroup->Detach(this); +} + +void StatefulLabel::setDefaultStyle(const QString& defaultStyle) +{ + _defaultStyle = defaultStyle; +} + +void StatefulLabel::setParameterGroup(const std::string& groupName) +{ + if (_parameterGroup.isValid()) + _parameterGroup->Detach(this); + + // Attach to the Parametergroup so we know when it changes + _parameterGroup = App::GetApplication().GetParameterGroupByPath(groupName.c_str()); + if (_parameterGroup.isValid()) + _parameterGroup->Attach(this); +} + +void StatefulLabel::registerState(const QString& state, const QString& styleCSS, + const std::string& preferenceName) +{ + _availableStates[state] = { styleCSS, preferenceName }; +} + +void StatefulLabel::registerState(const QString& state, const QColor& color, + const std::string& preferenceName) +{ + QString css; + if (color.isValid()) + css = QString::fromUtf8("Gui--StatefulLabel{ color : rgba(%1,%2,%3,%4) ;}").arg(color.red()).arg(color.green()).arg(color.blue()).arg(color.alpha()); + _availableStates[state] = { css, preferenceName }; +} + +void StatefulLabel::registerState(const QString& state, const QColor& fg, const QColor& bg, + const std::string& preferenceName) +{ + QString colorEntries; + if (fg.isValid()) + colorEntries.append(QString::fromUtf8("color : rgba(%1,%2,%3,%4);").arg(fg.red()).arg(fg.green()).arg(fg.blue()).arg(fg.alpha())); + if (bg.isValid()) + colorEntries.append(QString::fromUtf8("background-color : rgba(%1,%2,%3,%4);").arg(bg.red()).arg(bg.green()).arg(bg.blue()).arg(bg.alpha())); + QString css = QString::fromUtf8("Gui--StatefulLabel{ %1 }").arg(colorEntries); + _availableStates[state] = { css, preferenceName }; +} + +/** Observes the parameter group and clears the cache if it changes */ +void StatefulLabel::OnChange(Base::Subject& rCaller, const char* rcReason) +{ + Q_UNUSED(rCaller); + auto changedItem = std::string(rcReason); + if (changedItem == "StyleSheet") { + _styleCache.clear(); + } + else { + for (const auto& state : _availableStates) { + if (state.second.preferenceString == changedItem) { + _styleCache.erase(_styleCache.find(state.first)); + } + } + } +} + +void StatefulLabel::setOverridePreference(bool overridePreference) +{ + _overridePreference = overridePreference; +} + +void StatefulLabel::setState(QString state) +{ + _state = state; + this->ensurePolished(); + + // If the stylesheet insists, ignore all other logic and let it do its thing. This + // property is *only* set by the stylesheet. + if (_overridePreference) + return; + + // Check the cache first: + if (auto style = _styleCache.find(_state); style != _styleCache.end()) { + auto test = style->second.toStdString(); + this->setStyleSheet(style->second); + return; + } + + if (auto entry = _availableStates.find(state); entry != _availableStates.end()) { + // Order of precedence: first, check if the user has set this in their preferences: + if (!entry->second.preferenceString.empty()) { + // First, try to see if it's just stored a color (as an unsigned int): + auto availableColorPrefs = _parameterGroup->GetUnsignedMap(); + std::string lookingForGroup = entry->second.preferenceString; + for (const auto &unsignedEntry : availableColorPrefs) { + std::string foundGroup = unsignedEntry.first; + if (unsignedEntry.first == entry->second.preferenceString) { + // Convert the stored Uint into usable color data: + unsigned int col = unsignedEntry.second; + QColor qcolor((col >> 24) & 0xff, (col >> 16) & 0xff, (col >> 8) & 0xff); + this->setStyleSheet(QString::fromUtf8("Gui--StatefulLabel{ color : rgba(%1,%2,%3,%4) ;}").arg(qcolor.red()).arg(qcolor.green()).arg(qcolor.blue()).arg(qcolor.alpha())); + _styleCache[state] = this->styleSheet(); + return; + } + } + + // If not, try to see if there's an entire style string set as ASCII: + auto availableStringPrefs = _parameterGroup->GetASCIIMap(); + for (const auto& stringEntry : availableStringPrefs) { + if (stringEntry.first == entry->second.preferenceString) { + QString css = QString::fromUtf8("Gui--StatefulLabel{ %1 }").arg(QString::fromStdString(stringEntry.second)); + this->setStyleSheet(css); + _styleCache[state] = this->styleSheet(); + return; + } + } + } + + // If there is no preferences entry for this label, allow the stylesheet to set it, and only set to the default + // formatting if there is no stylesheet entry + if (qApp->styleSheet().isEmpty()) { + this->setStyleSheet(entry->second.defaultCSS); + _styleCache[state] = this->styleSheet(); + return; + } + // else the stylesheet sets our appearance: make sure it recalculates the appearance: + this->setStyleSheet(QString()); + this->setStyle(qApp->style()); + this->style()->unpolish(this); + this->style()->polish(this); + } + else { + if (styleSheet().isEmpty()) { + this->setStyleSheet(_defaultStyle); + _styleCache[state] = this->styleSheet(); + } + } +} + +// -------------------------------------------------------------------- + /* TRANSLATOR Gui::LabelButton */ /** diff --git a/src/Gui/Widgets.h b/src/Gui/Widgets.h index 09d9013a99..c4064eb50e 100644 --- a/src/Gui/Widgets.h +++ b/src/Gui/Widgets.h @@ -36,6 +36,7 @@ #include #include #include "ExpressionBinding.h" +#include "Base/Parameter.h" namespace Gui { class PrefCheckBox; @@ -258,21 +259,107 @@ class GuiExport UrlLabel : public QLabel { Q_OBJECT Q_PROPERTY( QString url READ url WRITE setUrl) + Q_PROPERTY( bool launchExternal READ launchExternal WRITE setLaunchExternal) public: UrlLabel ( QWidget * parent = 0, Qt::WindowFlags f = Qt::WindowFlags() ); virtual ~UrlLabel(); QString url() const; + bool launchExternal() const; + +Q_SIGNALS: + void linkClicked(QString url); public Q_SLOTS: void setUrl( const QString &u ); + void setLaunchExternal(bool l); protected: void mouseReleaseEvent ( QMouseEvent * ); private: QString _url; + bool _launchExternal; +}; + + +/** + * A text label whose appearance can change based on a specified state. + * + * The state is an arbitrary string exposed as a Qt Property (and thus available for selection via + * a stylesheet). This is intended for things like messages to the user, where a message that is an + * "error" might be colored differently than one that is a "warning" or a "message". + * + * In order of style precedence for a given state: User preference > Stylesheet > Default + * unless the stylesheet sets the overridePreference, in which case the stylesheet will + * take precedence. If a stylesheet sets styles for this widgets states, it should also + * set the "handledByStyle" property to ensure the style values are used, rather than the + * defaults. + * + * For example, the .qss might contain: + * Gui--StatefulLabel { + * qproperty-overridePreference: true; + * } + * Gui--StatefulLabel[state="special_state"] { + * color: red; + * } + * In this case, StatefulLabels with state "special_state" will be colored red, regardless of any + * entry in preferences. Use the "overridePreference" stylesheet option with care! + * + * @author Chris Hennes + */ +class GuiExport StatefulLabel : public QLabel, public Base::Observer +{ + Q_OBJECT + Q_PROPERTY( bool overridePreference MEMBER _overridePreference WRITE setOverridePreference) + Q_PROPERTY( QString state MEMBER _state WRITE setState ) + +public: + StatefulLabel(QWidget* parent = nullptr); + virtual ~StatefulLabel(); + + /** If an unrecognized state is set, use this style */ + void setDefaultStyle(const QString &defaultStyle); + + /** If any of the states have user preferences associated with them, this sets the parameter + group that stores those preferences. All states must be in the same parameter group, but + the group does not have to have entries for all of them. */ + void setParameterGroup(const std::string& groupName); + + /** Register a state and its corresponding style (optionally attached to a user preference) */ + void registerState(const QString &state, const QString &styleCSS, + const std::string& preferenceName = std::string()); + + /** For convenience, allow simple color-only states via QColor (optionally attached to a user preference) */ + void registerState(const QString& state, const QColor& color, + const std::string& preferenceName = std::string()); + + /** For convenience, allow simple color-only states via QColor (optionally attached to a user preference) */ + void registerState(const QString& state, const QColor& foregroundColor, const QColor& backgroundColor, + const std::string& preferenceName = std::string()); + + /** Observes the parameter group and clears the cache if it changes */ + void OnChange(Base::Subject& rCaller, const char* rcReason); + +public Q_SLOTS: + void setState(QString state); + void setOverridePreference(bool overridePreference); + +private: + QString _state; + bool _overridePreference; + ParameterGrp::handle _parameterGroup; + ParameterGrp::handle _stylesheetGroup; + + struct StateData { + QString defaultCSS; + std::string preferenceString; + }; + + std::map _availableStates; + std::map _styleCache; + QString _defaultStyle; }; // ---------------------------------------------------------------------- diff --git a/src/Gui/resource.cpp b/src/Gui/resource.cpp index 099539a5a1..10391bffbb 100644 --- a/src/Gui/resource.cpp +++ b/src/Gui/resource.cpp @@ -111,6 +111,7 @@ WidgetFactorySupplier::WidgetFactorySupplier() new WidgetProducer; new WidgetProducer; new WidgetProducer; + new WidgetProducer; new WidgetProducer; new WidgetProducer; new WidgetProducer; diff --git a/src/Mod/Sketcher/Gui/TaskSketcherMessages.cpp b/src/Mod/Sketcher/Gui/TaskSketcherMessages.cpp index 14a3b4cb21..85bf3d7e0f 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherMessages.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherMessages.cpp @@ -59,8 +59,7 @@ TaskSketcherMessages::TaskSketcherMessages(ViewProviderSketch *sketchView) : this->groupLayout()->addWidget(proxy); - connectionSetUp = sketchView->signalSetUp.connect(boost::bind(&SketcherGui::TaskSketcherMessages::slotSetUp, this, bp::_1)); - connectionSolved = sketchView->signalSolved.connect(boost::bind(&SketcherGui::TaskSketcherMessages::slotSolved, this, bp::_1)); + connectionSetUp = sketchView->signalSetUp.connect(boost::bind(&SketcherGui::TaskSketcherMessages::slotSetUp, this, bp::_1, bp::_2, bp::_3, bp::_4)); ui->labelConstrainStatus->setOpenExternalLinks(false); @@ -72,6 +71,23 @@ TaskSketcherMessages::TaskSketcherMessages(ViewProviderSketch *sketchView) : else sketchView->getSketchObject()->noRecomputes=true; + // Set up the possible state values for the status label + ui->labelConstrainStatus->setParameterGroup("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); + ui->labelConstrainStatus->registerState(QString::fromUtf8("empty_sketch"), QColor("black"), std::string("EmptySketchMessageColor")); + ui->labelConstrainStatus->registerState(QString::fromUtf8("under_constrained"), QColor("black"), std::string("UnderconstrainedMessageColor")); + ui->labelConstrainStatus->registerState(QString::fromUtf8("malformed_constraints"), QColor("red"), std::string("MalformedConstraintMessageColor")); + ui->labelConstrainStatus->registerState(QString::fromUtf8("conflicting_constraints"), QColor("orangered"), std::string("ConflictingConstraintMessageColor")); + ui->labelConstrainStatus->registerState(QString::fromUtf8("redundant_constraints"), QColor("red"), std::string("RedundantConstraintMessageColor")); + ui->labelConstrainStatus->registerState(QString::fromUtf8("partially_redundant_constraints"), QColor("royalblue"), std::string("PartiallyRedundantConstraintMessageColor")); + ui->labelConstrainStatus->registerState(QString::fromUtf8("solver_failed"), QColor("red"), std::string("SolverFailedMessageColor")); + ui->labelConstrainStatus->registerState(QString::fromUtf8("fully_constrained"), QColor("green"), std::string("FullyConstrainedMessageColor")); + + ui->labelConstrainStatusLink->setLaunchExternal(false); + + // Manually connect the link since it uses "clicked()", which labels don't have natively + connect(ui->labelConstrainStatusLink, &Gui::UrlLabel::linkClicked, + this, &TaskSketcherMessages::on_labelConstrainStatusLink_linkClicked); + /*QObject::connect( ui->labelConstrainStatus, SIGNAL(linkActivated(const QString &)), this , SLOT (on_labelConstrainStatus_linkActivated(const QString &)) @@ -89,20 +105,17 @@ TaskSketcherMessages::TaskSketcherMessages(ViewProviderSketch *sketchView) : TaskSketcherMessages::~TaskSketcherMessages() { connectionSetUp.disconnect(); - connectionSolved.disconnect(); } -void TaskSketcherMessages::slotSetUp(QString msg) +void TaskSketcherMessages::slotSetUp(const QString& state, const QString& msg, const QString& link, const QString& linkText) { + ui->labelConstrainStatus->setState(state); ui->labelConstrainStatus->setText(msg); + ui->labelConstrainStatusLink->setUrl(link); + ui->labelConstrainStatusLink->setText(linkText); } -void TaskSketcherMessages::slotSolved(QString msg) -{ - ui->labelSolverStatus->setText(msg); -} - -void TaskSketcherMessages::on_labelConstrainStatus_linkActivated(const QString &str) +void TaskSketcherMessages::on_labelConstrainStatusLink_linkClicked(const QString &str) { if( str == QString::fromLatin1("#conflicting")) Gui::Application::Instance->commandManager().runCommandByName("Sketcher_SelectConflictingConstraints"); @@ -115,7 +128,7 @@ void TaskSketcherMessages::on_labelConstrainStatus_linkActivated(const QString & else if( str == QString::fromLatin1("#malformed")) Gui::Application::Instance->commandManager().runCommandByName("Sketcher_SelectMalformedConstraints"); - else + else if( str == QString::fromLatin1("#partiallyredundant")) Gui::Application::Instance->commandManager().runCommandByName("Sketcher_SelectPartiallyRedundantConstraints"); diff --git a/src/Mod/Sketcher/Gui/TaskSketcherMessages.h b/src/Mod/Sketcher/Gui/TaskSketcherMessages.h index 30aae3fbb0..300059c905 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherMessages.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherMessages.h @@ -47,11 +47,10 @@ public: TaskSketcherMessages(ViewProviderSketch *sketchView); ~TaskSketcherMessages(); - void slotSetUp(QString msg); - void slotSolved(QString msg); + void slotSetUp(const QString &state, const QString &msg, const QString& link, const QString& linkText); private Q_SLOTS: - void on_labelConstrainStatus_linkActivated(const QString &); + void on_labelConstrainStatusLink_linkClicked(const QString &); void on_autoUpdate_stateChanged(int state); void on_autoRemoveRedundants_stateChanged(int state); void on_manualUpdate_clicked(bool checked); @@ -59,7 +58,6 @@ private Q_SLOTS: protected: ViewProviderSketch *sketchView; Connection connectionSetUp; - Connection connectionSolved; private: QWidget* proxy; diff --git a/src/Mod/Sketcher/Gui/TaskSketcherMessages.ui b/src/Mod/Sketcher/Gui/TaskSketcherMessages.ui index 720b2c0391..8c95744c99 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherMessages.ui +++ b/src/Mod/Sketcher/Gui/TaskSketcherMessages.ui @@ -15,24 +15,35 @@ - - - Undefined degrees of freedom - - - true - - - - - - - Not solved yet - - - true - - + + + + + DOF + + + + + + + Link + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + @@ -94,6 +105,16 @@ QCheckBox
Gui/PrefWidgets.h
+ + Gui::StatefulLabel + QLabel +
Gui/Widgets.h
+
+ + Gui::UrlLabel + QLabel +
Gui/Widgets.h
+
diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index 3bbd7da645..698dadd0a4 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -402,6 +402,9 @@ ViewProviderSketch::ViewProviderSketch() //rubberband selection rubberband = new Gui::Rubberband(); + // Status message states: + + subscribeToParameters(); } @@ -1309,10 +1312,6 @@ bool ViewProviderSketch::mouseMove(const SbVec2s &cursorPos, Gui::View3DInventor if (getSketchObject()->moveTemporaryPoint(GeoId, PosId, vec, false) == 0) { setPositionText(Base::Vector2d(x,y)); draw(true,false); - signalSolved(QString::fromLatin1("Solved in %1 sec").arg(getSolvedSketch().getSolveTime())); - } else { - signalSolved(QString::fromLatin1("Unsolved (%1 sec)").arg(getSolvedSketch().getSolveTime())); - //Base::Console().Log("Error solving:%d\n",ret); } } } @@ -1350,9 +1349,6 @@ bool ViewProviderSketch::mouseMove(const SbVec2s &cursorPos, Gui::View3DInventor if (getSketchObject()->moveTemporaryPoint(edit->DragCurve, Sketcher::none, vec, relative) == 0) { setPositionText(Base::Vector2d(x,y)); draw(true,false); - signalSolved(QString::fromLatin1("Solved in %1 sec").arg(getSolvedSketch().getSolveTime())); - } else { - signalSolved(QString::fromLatin1("Unsolved (%1 sec)").arg(getSolvedSketch().getSolveTime())); } } return true; @@ -6581,6 +6577,29 @@ QString ViewProviderSketch::appendConstraintMsg(const QString & singularmsg, return msg; } +inline QString intListHelper(const std::vector &ints) +{ + QString results; + if (ints.size() < 8) { // The 8 is a bit heuristic... more than that and we shift formats + for (const auto i : ints) { + if (results.isEmpty()) + results.append(QString::fromUtf8("%1").arg(i)); + else + results.append(QString::fromUtf8(", %1").arg(i)); + } + } + else { + const int numToShow = 3; + int more = ints.size() - numToShow; + for (int i = 0; i < numToShow; ++i) { + results.append(QString::fromUtf8("%1, ").arg(ints[i])); + } + results.append(QCoreApplication::translate("ViewProviderSketch","and %1 more").arg(more)); + } + std::string testString = results.toStdString(); + return results; +} + void ViewProviderSketch::UpdateSolverInformation() { // Updates Solver Information with the Last solver execution at SketchObject level @@ -6591,76 +6610,48 @@ void ViewProviderSketch::UpdateSolverInformation() bool hasMalformed = getSketchObject()->getLastHasMalformedConstraints(); if (getSketchObject()->Geometry.getSize() == 0) { - signalSetUp(tr("Empty sketch")); - signalSolved(QString()); + signalSetUp(QString::fromUtf8("empty_sketch"), tr("Empty sketch"), QString(), QString()); } - else if (dofs < 0) { // over-constrained sketch - std::string msg; - SketchObject::appendConflictMsg(getSketchObject()->getLastConflicting(), msg); - signalSetUp(QString::fromLatin1("%1 %2
%3

") - .arg(tr("Over-constrained sketch")) - .arg(tr("(click to select)")) - .arg(QString::fromStdString(msg))); - signalSolved(QString()); + else if (dofs < 0 || hasConflicts) { // over-constrained sketch + signalSetUp(QString::fromUtf8("conflicting_constraints"), + tr("Over-constrained: "), + QString::fromUtf8("#conflicting"), + QString::fromUtf8("(%1)").arg(intListHelper(getSketchObject()->getLastConflicting()))); } else if (hasMalformed) { // malformed constraints - signalSetUp(QString::fromLatin1("%1 %2
%3

") - .arg(tr("Sketch contains malformed constraints")) - .arg(tr("(click to select)")) - .arg(appendMalformedMsg(getSketchObject()->getLastMalformedConstraints()))); - signalSolved(QString()); + signalSetUp(QString::fromUtf8("malformed_constraints"), + tr("Malformed constraints: "), + QString::fromUtf8("#malformed"), + QString::fromUtf8("(%1)").arg(intListHelper(getSketchObject()->getLastMalformedConstraints()))); } - else if (hasConflicts) { // conflicting constraints - signalSetUp(QString::fromLatin1("%1 %2
%3

") - .arg(tr("Sketch contains conflicting constraints")) - .arg(tr("(click to select)")) - .arg(appendConflictMsg(getSketchObject()->getLastConflicting()))); - signalSolved(QString()); + else if (hasRedundancies) { + signalSetUp(QString::fromUtf8("redundant_constraints"), + tr("Redundant constraints:"), + QString::fromUtf8("#redundant"), + QString::fromUtf8("(%1)").arg(intListHelper(getSketchObject()->getLastRedundant()))); + } + else if (hasPartiallyRedundant) { + signalSetUp(QString::fromUtf8("partially_redundant_constraints"), + tr("Partially redundant:"), + QString::fromUtf8("#partiallyredundant"), + QString::fromUtf8("(%1)").arg(intListHelper(getSketchObject()->getLastPartiallyRedundant()))); + } + else if (getSketchObject()->getLastSolverStatus() != 0) { + signalSetUp(QString::fromUtf8("solver_failed"), + tr("Solver failed to converge"), + QString::fromUtf8(""), + QString::fromUtf8("")); + } else if (dofs > 0) { + signalSetUp(QString::fromUtf8("under_constrained"), + tr("Under constrained:"), + QString::fromUtf8("#dofs"), + QString::fromUtf8("%1 %2").arg(dofs).arg(tr("DoF"))); } else { - if (hasRedundancies) { // redundant constraints - signalSetUp(QString::fromLatin1("%1 %2
%3

") - .arg(tr("Sketch contains redundant constraints")) - .arg(tr("(click to select)")) - .arg(appendRedundantMsg(getSketchObject()->getLastRedundant()))); - } - - QString partiallyRedundantString; - - if(hasPartiallyRedundant) { - partiallyRedundantString = QString::fromLatin1("
%1 %2
%3

") - .arg(tr("Sketch contains partially redundant constraints")) - .arg(tr("(click to select)")) - .arg(appendPartiallyRedundantMsg(getSketchObject()->getLastPartiallyRedundant())); - } - - if (getSketchObject()->getLastSolverStatus() == 0) { - if (dofs == 0) { - // color the sketch as fully constrained if it has geometry (other than the axes) - if(getSolvedSketch().getGeometrySize()>2) - edit->FullyConstrained = true; - - if (!hasRedundancies) { - signalSetUp(QString::fromLatin1("%1 %2").arg(tr("Fully constrained sketch")).arg(partiallyRedundantString)); - } - } - else if (!hasRedundancies) { - QString infoString; - - if (dofs == 1) - signalSetUp(tr("Under-constrained sketch with 1 degree of freedom. %1") - .arg(partiallyRedundantString)); - else - signalSetUp(tr("Under-constrained sketch with %1 degrees of freedom. %2") - .arg(dofs) - .arg(partiallyRedundantString)); - } - - signalSolved(QString::fromLatin1("%1").arg(tr("Solved in %1 sec").arg(getSketchObject()->getLastSolveTime()))); - } - else { - signalSolved(QString::fromLatin1("%1").arg(tr("Unsolved (%1 sec)").arg(getSketchObject()->getLastSolveTime()))); - } + signalSetUp(QString::fromUtf8("fully_constrained"), tr("Fully constrained"), QString(), QString()); + // color the sketch as fully constrained if it has geometry (other than the axes) + if(getSolvedSketch().getGeometrySize()>2) + edit->FullyConstrained = true; } } diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.h b/src/Mod/Sketcher/Gui/ViewProviderSketch.h index 598ff154c9..fabb68c1ea 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.h +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.h @@ -273,9 +273,7 @@ public: /// signals if the constraints list has changed boost::signals2::signal signalConstraintsChanged; /// signals if the sketch has been set up - boost::signals2::signal signalSetUp; - /// signals if the sketch has been solved - boost::signals2::signal signalSolved; + boost::signals2::signal signalSetUp; /// signals if the elements list has changed boost::signals2::signal signalElementsChanged;