Styled custom widgets

Thomas Lübking thomas.luebking at web.de
Mon Sep 8 20:11:23 BST 2008


Am Saturday 06 September 2008 schrieb Sandro Giessl:
> I don't think I'm the person you ask for. :) 
Probably got I think we luckily got past that anyway =D

> Thanks for CC'ing, tough.
Your name is pretty much on top of kstyle.h...

> I especially like the KCustomWidgetStyleOption approach. Seems
> relatively clean to me and this way it won't be necessary to extend Qt
> API for mapping strings to dynamic IDs, yes?
yupp, only the implementing style has to take care (iff it can),
though for name consistency i want to suggest:
- KStyleOptionCustomStyleElement : QStyleOption
- SH_KCustomStyleElement == 0xff000001
(being the first beyond *_KdeBase == 0xff000000)

naming convention:
-KDE can use "PE_" "CE_" "CC_" "SH_" "SC_" "SE_" directly,
all others should use
"appname.PE_" ....

attached is a kind of howto, including a simlpe patch for kcapacitybar

Thomas

-------------- next part --------------
Ok, here're excerpts from a REAL implementation, it's allready in Bespins svn
so if you apply the kcapacitybar patch and have bespin installed you can see the whole thing at
work - ereslibry probably wont like it though as it's less eye catching :(
sosorry man, but this is a status label after all...

Several thing could probably have been done better (e.g. use more or less classmembers)
but I
1. wanted to keep this as private static as possible, preventing any bin compatibility issues
2. needed to fit bespin (the code is spread over several files)
3. Some complexity results out of the lack of "KStyleOptionCustomElement"

in fact it's not necessary to add any function/var to any header
neither on the styles where everything can be done by static global vars / functions
nor on the widget (i added a id caching value to the private class, but this could be done with
static global QMap<QStyle*, int> as well and depending on the use case even more efficient)


A) The StyleHint part:
=================================
Whereever you implement the styleHint, you need the following code fracts or similar:

// this is an internal class as (afaik) nothing like this exists in either Qt or KDE
// i suggest adding it to Qt if any possible as a value add, but KDE would be fine for the moment :-)

// it's just a styleoption with a string "name", though
class KStyleOptionCustomElement : public QStyleOption
{
public:
    KStyleOptionCustomElement(int version = QStyleOption::Version, int type = SO_Default):
    QStyleOption(version, type) {}
    QString name;
};

// a stylehint definition, as the enum doesn't know sth. like this yet.
// according to the Qt 4.4 doc, this is ABOVE SH_CustomBase (though i worry it being uint?!?)
#define SH_KCustomStyleELement 0xff000001

// the stylehint implementation:
int BespinStyle::styleHint( StyleHint hint, const QStyleOption * option, const QWidget * widget,
                            QStyleHintReturn * returnData ) const
{
    switch (hint)
    {
    case ..... // maaaaany stylehints to respond you don't wanna read

    // now this is the interesting part:
    case SH_KCustomStyleELement:
    {
        // this should be a qstyleoption_cast<>(), but this won't work atm as KStyleOptionCustomElement is in no header, nor lib
        // so the classes used in the kcapacitybar widget and here are actually different... allthough the same... %-)
        const KStyleOptionCustomElement *element = static_cast<const KStyleOptionCustomElement*>(option);

        if (!option) // you would query element here once the above issue is solved
            return 0;

        // the working horse function.
        // could be implemented here, but i need to access the used maps from where i implement
        // drawPrimitive etc. (different file)
        // so it's a private const member funtion, (could be static as well)
        int id = elementId(element->name);

        // if there's no support so far anywhere, you could inform you about this new interesting
        // custom element, maybe a popup dialog would be nice as well -
        // comment the below for end user releases ...
        if (!id)
            qDebug() << "Unsupported KCustomStyleElement requested:" << element->name;

        // return the internally assigned id for this element
        // it /can/ be '0' iff unsupported, what will tell the widget it has to paint a fallback
        return id;
    }
    } // switch
} // styleHint()

B) The element mapping and drawing part
==========================================

// NOTICE, really read this!!!
// PE_CustomBase is unlike all others 0xff00000 (only 31 bits... signed int) ...
// AND CAUSES SEGFAULT when used as 32bit 0xff000000 and (unit)pe > X_KdeBase
// as enums are usually int - maybe here's a bug in Qt custom bases?
// i'll keep it 0xff00000 (31 bit) for the moment (0xff000000 32bit is signed negative)
#define PE_KdeBase 0xff00000
#define X_KdeBase 0xff000000

// here the style defines Elements it wants to support for further usage in draw*() etc.
enum CustomPrimitives { _PE_CapacityBar = 0 /*, ...*/, N_CustomPrimitives };
enum CustomComplexs { _CC_AmarokAnalyzer = 0 /*, ...*/, N_CustomComplexs };
enum SubControls { _SC_AmarokAnalyzerSlider = 0 /*, ...*/, N_CustomSubControls };

static QStyle::PrimitiveElement primitives[N_CustomPrimitives];
static QStyle::ComplexControl complexs[N_CustomComplexs];
static QStyle::SubControl subcontrols[N_CustomSubControls];

enum ElementType { PE = 0, CE, CC, SE, SH, SC };

// The Map that assigned the requested element strings to unique id's
// as the widgets are asked to please fit certain naming convetions (see below), one map is enough
// NOTICE: using QHash instead QMap is probably overhead, there won't be too many items per app
static QMap<QString, int> styleElements;

// This map will help to let the baseclass do the job if we can't
static QMap<int, int> parentId[6];

// The counter to assure unique id's for every element type, SH gets '+1' as SH_KCustomElement covers it
static int counter[5] = { PE_KdeBase, X_KdeBase, X_KdeBase, X_KdeBase, X_KdeBase+1 /*sic!*/};
// subcontrols are different as they're 1. flags and 2. attached to a CC, so the counter counts the shift amount and starts at '0'
static int scCounter[N_CustomComplexs] = { 0 };

// NOTICE:
// the data structure above is not the only option.
// one could e.g. drop the Element enums and use static QStyle::PrimitiveElement _PE_CapacityBar instead
// the enum is especially usefull to handle the scCounter array what could be transferred into a map or hash
// asa you start supporting MAAANY custom complex controls
// that's however not that important...



// implementation of the styles drawing routine as usual, just you can check for another element now
void
BespinStyle::drawPrimitive ( PrimitiveElement pe, const QStyleOption * option,
                             QPainter * painter, const QWidget * widget) const
{
    // .......
    else if (pe > X_KdeBase)
    {
        if (pe == primitives[_PE_CapacityBar])
            drawCapacityBar(option, painter, widget);
        //if (pe == primitives[_PE_WhateverElse])
        // ...
        // or maybe dad can do?
        else if (int mappedPe = parentId[PE].value(pe, 0))
            BaseStyle::drawPrimitive( (PrimitiveElement)mappedPe, option, painter, widget );
    }
    else
    //  .......
}
// other drawing routines, geometry queries etc. analoguous

// the maping function:

int
BespinStyle::elementId(const QString &string, QStyleOption *option, QWidget *widget) const
{
    // first check if the element has allready been assigned:
    int id = styleElements.value(string, 0);
    if (id) // yes - just return and outahere
        return id;

    // ok, check whether the element string looks like anything you (NOT the baseclass) want to support
    if (string == "PE_CapacityBar")
        // yes, assign and increase the proper counter
        primitives[_PE_CapacityBar] = (PrimitiveElement)(id = ++counter[PE]);
    else if (string == "amarok.CC_Analyzer")
        complexs[_CC_AmarokAnalyzer] = (ComplexControl)(id = ++counter[CC]);
    // subcontrols (SC_) work much different as they're 1. flags and 2. attached to a CC
    else if (string == "amarok.CC_Analyzer:SC_Slider")
    {
        subcontrols[_SC_AmarokAnalyzerSlider] = (SubControl)(id = (1 << scCounter[_CC_AmarokAnalyzer]));
        ++scCounter[_CC_AmarokAnalyzer];
    }
//     else if blablablaba...

    // last chance: if you don't support element, maybe the parent style does.
    //----
    if (!id && (id = QCommonStyle::styleHint((StyleHint)SH_KCustomStyleELement, option, widget)))
    {
        // ok, we don't support this item, but daddy does, now
        // 1. we'll need to pass calls for this item to dad, with HIS id
        // 2. chances are good, that HIS id is used by US for some other item
        // 3. we cannot simply call the widget(s) that require the id now assigned to the
        // current item by our dad, ask it to change the id as we changed our mind
        // 4. this means we'll have to map calls for this item:
        // a) we assign our next free id an return it to the widget
        // b) when the widget calls "draw*()" with this (our) id, we'll map it to our dad's
        // id just queried and tell dad to handle things for HIS id
        // c) yes, this works for non direct base classes as well -> like map(map())
        int did = id;
        if (string.contains("PE_")) parentId[PE].insert(id = ++counter[PE], did);
        else if (string.contains("CE_")) parentId[CE].insert(id = ++counter[CE], did);
        else if (string.contains("CC_")) parentId[CC].insert(id = ++counter[CC], did);
        else if (string.contains("SE_")) parentId[SE].insert(id = ++counter[SE], did);
        else if (string.contains("SH_")) parentId[SH].insert(id = ++counter[SH], did);
        else if (string.contains(":SC_"))
        {
            parentId[SC].insert(id = (1 << counter[SC]), did);
            ++counter[SC];
        }
    }
    //-----------
    if (id)
        styleElements.insert(string, id);
    return id;
}


Below is the patch for kcapacitybar.cpp
=========================================
NOTICE: this wil "as it is" NOT survive style changes (the event has to be reimplemented and
d->pe_capacityBar requeried) as i resisted to edit the header ;-)

It's not that compliciated:
- the internal class can be replaced, once there's a replacement in Qt or KDE...

- The widget's private memeber (Q_D) has a new atribute: "pe_capacityBar" to cache the id
queried on construction
Notice, that if you expect your widget to be generated many times it could be helpfull to keep
static struct pe_capacityBar { QStyle* style, int id } = {0,0};
or even a
static QMap<QStyle*, int> pe_capacityBar;
in case you expect different styles for each widget (yes, every widget can have a custom style...)

- On construction the style is queried for an unique id for this element
(this should happen on QWidget::changeEvent ( QEvent *event ), event->type() == QEvent::StyleChange
as well...)

- the paint routine now first checks if there's a element id and calls the style in case
(for the moment at least, i abuse QStyleOptionProgressBar - what does not support ALL KCapacityBar's
features. So it could be inherited and extended, you could look out for a better existing StyleOption
or check whether all features are usefull...)

-- That's it. --------------------------------------------

Index: kcapacitybar.cpp
===================================================================
--- kcapacitybar.cpp	(revision 857873)
+++ kcapacitybar.cpp	(working copy)
@@ -37,6 +37,16 @@
 #define ROUND_MARGIN     6
 #define VERTICAL_SPACING 1
 
+#define SH_KCustomStyleELement ((QStyle::StyleHint)0xff000001)
+
+class KStyleOptionCustomElement : public QStyleOption
+{
+public:
+    KStyleOptionCustomElement(int version = QStyleOption::Version, int type = SO_Default):
+    QStyleOption(version, type) {}
+    QString name;
+};
+
 class KCapacityBar::Private
 {
 public:
@@ -56,6 +66,7 @@
     bool continuous;
     int barHeight;
     Qt::Alignment horizontalTextAlignment;
+    QStyle::PrimitiveElement pe_capacityBar;
 
     const KCapacityBar::DrawTextMode drawTextMode;
 };
@@ -64,6 +75,9 @@
     : QWidget(parent)
     , d(new Private(drawTextMode))
 {
+    KStyleOptionCustomElement opt;
+    opt.name = "PE_CapacityBar";
+    d->pe_capacityBar = (QStyle::PrimitiveElement)style()->styleHint(SH_KCustomStyleELement, &opt, this);
 }
 
 KCapacityBar::~KCapacityBar()
@@ -142,6 +156,20 @@
 
 void KCapacityBar::drawCapacityBar(QPainter *p, const QRect &rect) const
 {
+    if (d->pe_capacityBar)
+    {
+        QStyleOptionProgressBar opt;
+        opt.initFrom(this);
+        opt.minimum = 0;
+        opt.maximum = 100;
+        opt.progress = d->value;
+        opt.text = d->text;
+//         opt.textAlignment = Qt::AlignCenter;
+        opt.textVisible = true;
+        style()->drawPrimitive ( d->pe_capacityBar, &opt, p, this);
+        return;
+    }
+    
     p->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
 
     p->save();


More information about the kde-core-devel mailing list