Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Sketcher] Make constraint label smaller and styleable #5091

Merged
merged 10 commits into from Oct 13, 2021
31 changes: 31 additions & 0 deletions src/Gui/Stylesheets/Behave-dark.qss
Expand Up @@ -2276,3 +2276,34 @@ QPushButton#NavigationIndicator::menu-indicator {
image: none;
width: 0px;
}

/*==================================================================================================
SKETCHER
==================================================================================================*/

Gui--StatefulLabel[state="empty_sketch"] {
color : rgba(255,255,255,127);
}
Gui--StatefulLabel[state="under_constrained"] {
color : rgba(255,255,255,255);
}
Gui--StatefulLabel[state="conflicting_constraints"] {
color : rgba(255,50,50,255);
}
Gui--StatefulLabel[state="malformed_constraints"] {
color : rgba(255,50,50,255);
}
Gui--StatefulLabel[state="redundant_constraints"] {
color : rgba(255,0,100,255);
}
Gui--StatefulLabel[state="partially_redundant_constraints"] {
color : rgba(255,25,100,255);
}
Gui--StatefulLabel[state="solver_failed"] {
color : rgba(255,0,0,255);
font-weight: bold;
}
Gui--StatefulLabel[state="fully_constrained"] {
color : rgba(0,255,0,255);
font-weight: bold;
}
202 changes: 187 additions & 15 deletions src/Gui/Widgets.cpp
Expand Up @@ -814,6 +814,7 @@ void ColorButton::onRejected()

UrlLabel::UrlLabel(QWidget * parent, Qt::WindowFlags f)
: QLabel(parent, f)
, _launchExternal(true)
{
_url = QString::fromLatin1("http://localhost");
setToolTip(this->_url);
Expand All @@ -827,6 +828,11 @@ UrlLabel::~UrlLabel()
{
}

void Gui::UrlLabel::setLaunchExternal(bool l)
{
_launchExternal = l;
}

void UrlLabel::enterEvent ( QEvent * )
{
setCursor(Qt::PointingHandCursor);
Expand All @@ -839,33 +845,44 @@ void UrlLabel::leaveEvent ( QEvent * )

void UrlLabel::mouseReleaseEvent (QMouseEvent *)
{
// The webbrowser Python module allows to start the system browser in an OS-independent way
Base::PyGILStateLocker lock;
PyObject* module = PyImport_ImportModule("webbrowser");
if (module) {
// get the methods dictionary and search for the 'open' method
PyObject* dict = PyModule_GetDict(module);
PyObject* func = PyDict_GetItemString(dict, "open");
if (func) {
PyObject* args = Py_BuildValue("(s)", (const char*)this->_url.toLatin1());
if (_launchExternal) {
// The webbrowser Python module allows to start the system browser in an OS-independent way
Base::PyGILStateLocker lock;
PyObject* module = PyImport_ImportModule("webbrowser");
if (module) {
// get the methods dictionary and search for the 'open' method
PyObject* dict = PyModule_GetDict(module);
PyObject* func = PyDict_GetItemString(dict, "open");
if (func) {
PyObject* args = Py_BuildValue("(s)", (const char*)this->_url.toLatin1());
#if PY_VERSION_HEX < 0x03090000
PyObject* result = PyEval_CallObject(func,args);
PyObject* result = PyEval_CallObject(func, args);
#else
PyObject* result = PyObject_CallObject(func,args);
PyObject* result = PyObject_CallObject(func, args);
#endif
// decrement the args and module reference
Py_XDECREF(result);
Py_DECREF(args);
Py_DECREF(module);
// decrement the args and module reference
Py_XDECREF(result);
Py_DECREF(args);
Py_DECREF(module);
}
}
}
else {
// Someone else will deal with it...
Q_EMIT linkClicked(_url);
}
}

QString UrlLabel::url() const
{
return this->_url;
}

bool Gui::UrlLabel::launchExternal() const
{
return _launchExternal;
}

void UrlLabel::setUrl(const QString& u)
{
this->_url = u;
Expand All @@ -874,6 +891,161 @@ 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] = { QColor(), QColor(), styleCSS, preferenceName };
}

void StatefulLabel::registerState(const QString& state, const QColor& color,
const std::string& preferenceName)
{
_availableStates[state] = {color, QColor(), QString(), preferenceName};
}

void StatefulLabel::registerState(const QString& state, const QColor& foreground, const QColor& background,
const std::string& preferenceLocation,
const std::string& preferenceName)
{
_availableStates[state] = { foreground, background, QString(), preferenceName };
}

/** Observes the parameter group and clears the cache if it changes */
void StatefulLabel::OnChange(Base::Subject<const char*>& rCaller, const char* rcReason)
{
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) {
this->setStyleSheet(QString::fromStdString(stringEntry.second));
_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()) {
if (!entry->second.defaultCSS.isEmpty()) {
this->setStyleSheet(entry->second.defaultCSS);
_styleCache[state] = this->styleSheet();
return;
}
else {
auto fg = entry->second.foregroundColor;
auto bg = entry->second.backgroundColor;
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()));
this->setStyleSheet(QString::fromUtf8("Gui--StatefulLabel{ %1 }").arg(colorEntries));
_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 */

/**
Expand Down
90 changes: 90 additions & 0 deletions src/Gui/Widgets.h
Expand Up @@ -36,6 +36,7 @@
#include <QToolButton>
#include <QModelIndex>
#include "ExpressionBinding.h"
#include "Base/Parameter.h"

namespace Gui {
class PrefCheckBox;
Expand Down Expand Up @@ -254,15 +255,21 @@ 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 enterEvent ( QEvent * );
Expand All @@ -271,6 +278,89 @@ public Q_SLOTS:

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<const char*>
{
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& foreground, const QColor &background,
const std::string& foregroundPreference = std::string(),
const std::string& backgroundPreference = std::string());

/** Observes the parameter group and clears the cache if it changes */
void OnChange(Base::Subject<const char *>& 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 {
QColor foregroundColor;
QColor backgroundColor;
QString defaultCSS;
std::string preferenceString;
};

std::map<QString, StateData> _availableStates;
std::map<QString, QString> _styleCache;
QString _defaultStyle;
};

// ----------------------------------------------------------------------
Expand Down