[Gui] New widget supporting per-state styles
The new StatefulLabel widget is designed to be customizable by optional preferences entries, Qt stylesheets, and default styles, set on a per-state basis, where "state" is a Qt property that can be changed dynamically at runtime.
This commit is contained in:
@@ -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);
|
||||
@@ -827,6 +828,11 @@ UrlLabel::~UrlLabel()
|
||||
{
|
||||
}
|
||||
|
||||
void Gui::UrlLabel::setLaunchExternal(bool l)
|
||||
{
|
||||
_launchExternal = l;
|
||||
}
|
||||
|
||||
void UrlLabel::enterEvent ( QEvent * )
|
||||
{
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
@@ -839,26 +845,32 @@ 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
|
||||
@@ -866,6 +878,11 @@ QString UrlLabel::url() const
|
||||
return this->_url;
|
||||
}
|
||||
|
||||
bool Gui::UrlLabel::launchExternal() const
|
||||
{
|
||||
return _launchExternal;
|
||||
}
|
||||
|
||||
void UrlLabel::setUrl(const QString& u)
|
||||
{
|
||||
this->_url = u;
|
||||
@@ -877,15 +894,16 @@ void UrlLabel::setUrl(const QString& u)
|
||||
StatefulLabel::StatefulLabel(QWidget* parent)
|
||||
: QLabel(parent)
|
||||
{
|
||||
// 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()
|
||||
{
|
||||
}
|
||||
|
||||
QString StatefulLabel::state() const
|
||||
{
|
||||
return _state;
|
||||
if (_parameterGroup.isValid())
|
||||
_parameterGroup->Detach(this);
|
||||
_stylesheetGroup->Detach(this);
|
||||
}
|
||||
|
||||
void StatefulLabel::setDefaultStyle(const QString& defaultStyle)
|
||||
@@ -893,52 +911,88 @@ 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& preferenceLocation,
|
||||
const std::string& preferenceName)
|
||||
{
|
||||
_availableStates[state] = { QColor(), QColor(), styleCSS, preferenceLocation, preferenceName };
|
||||
_availableStates[state] = { QColor(), QColor(), styleCSS, preferenceName };
|
||||
}
|
||||
|
||||
void StatefulLabel::registerState(const QString& state, const QColor& color,
|
||||
const std::string& preferenceLocation,
|
||||
const std::string& preferenceName)
|
||||
{
|
||||
_availableStates[state] = {color, QColor(), QString(), preferenceLocation, 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(), preferenceLocation, preferenceName };
|
||||
_availableStates[state] = { foreground, background, QString(), preferenceName };
|
||||
}
|
||||
|
||||
void StatefulLabel::setState(const QString& state)
|
||||
/** 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::setState(QString state)
|
||||
{
|
||||
_state = state;
|
||||
std::string stateIn = state.toStdString();
|
||||
|
||||
// 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.preferenceLocation.empty() && !entry->second.preferenceString.empty()) {
|
||||
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(entry->second.preferenceLocation.c_str());
|
||||
|
||||
if (!entry->second.preferenceString.empty()) {
|
||||
// First, try to see if it's just stored a color (as an unsigned int):
|
||||
auto availableColorPrefs = hGrp->GetUnsignedMap();
|
||||
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 = hGrp->GetASCIIMap();
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -946,9 +1000,10 @@ void StatefulLabel::setState(const QString& state)
|
||||
|
||||
// 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 (styleSheet().isEmpty()) {
|
||||
if (qApp->styleSheet().isEmpty()) {
|
||||
if (!entry->second.defaultCSS.isEmpty()) {
|
||||
this->setStyleSheet(entry->second.defaultCSS);
|
||||
_styleCache[state] = this->styleSheet();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
@@ -959,16 +1014,21 @@ void StatefulLabel::setState(const QString& state)
|
||||
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()));
|
||||
std::string tempForTesting = QString::fromUtf8("Gui--StatefulLabel{ %1 }").arg(colorEntries).toStdString();
|
||||
this->setStyleSheet(QString::fromUtf8("Gui--StatefulLabel{ %1 }").arg(colorEntries));
|
||||
_styleCache[state] = this->styleSheet();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// else the stylesheet has already set our appearance, no need to do anything
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <QToolButton>
|
||||
#include <QModelIndex>
|
||||
#include "ExpressionBinding.h"
|
||||
#include "Base/Parameter.h"
|
||||
|
||||
namespace Gui {
|
||||
class PrefCheckBox;
|
||||
@@ -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 * );
|
||||
@@ -271,6 +278,7 @@ protected:
|
||||
|
||||
private:
|
||||
QString _url;
|
||||
bool _launchExternal;
|
||||
};
|
||||
|
||||
|
||||
@@ -285,50 +293,56 @@ private:
|
||||
*
|
||||
* @author Chris Hennes
|
||||
*/
|
||||
class GuiExport StatefulLabel : public QLabel
|
||||
class GuiExport StatefulLabel : public QLabel, public Base::Observer<const char*>
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY( QString state READ state WRITE setState)
|
||||
Q_PROPERTY( QString state MEMBER _state WRITE setState )
|
||||
|
||||
public:
|
||||
StatefulLabel(QWidget* parent = nullptr);
|
||||
virtual ~StatefulLabel();
|
||||
|
||||
QString state() const;
|
||||
|
||||
/** 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& preferenceLocation = std::string(),
|
||||
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& preferenceLocation = std::string(),
|
||||
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& preferenceLocation = std::string(),
|
||||
const std::string& preferenceName = std::string());
|
||||
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(const QString &state);
|
||||
void setState(QString state);
|
||||
|
||||
private:
|
||||
QString _state;
|
||||
ParameterGrp::handle _parameterGroup;
|
||||
ParameterGrp::handle _stylesheetGroup;
|
||||
|
||||
struct StateData {
|
||||
QColor foregroundColor;
|
||||
QColor backgroundColor;
|
||||
QString defaultCSS;
|
||||
std::string preferenceLocation;
|
||||
std::string preferenceString;
|
||||
};
|
||||
|
||||
|
||||
std::map<QString, StateData> _availableStates;
|
||||
std::map<QString, QString> _styleCache;
|
||||
QString _defaultStyle;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user