Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the mechanics of the default delegate for item views in Qt?

Tags:

qt

pyqt

pyside

Short version

What is the default delegate used by QTreeView? In particular I am trying to find its paint() method?

Longer version

I am a Python user (Pyside/PyQt), and am using a custom delegate to recreate some of the functionality of QTreeView. Hence, I am trying to find the default delegate, and paint method, that is used in a QTreeView. Even better would be an explanation of how it works.

Cross post

I posted the same question at Qt Centre (http://www.qtcentre.org/threads/64458-Finding-default-delegate-for-QTreeView?).

like image 696
eric Avatar asked Nov 30 '15 01:11

eric


1 Answers

tl;dr

The default delegate for all item views is the QStyledItemDelegate. Its paint() method invokes drawControl(), defined in qcommonstyle.cpp, to draw each item. Hence, peruse qcommonstyle.cpp for the nitty-gritty details about how each item is drawn.


Long-form answer

Those who prefer brevity should read the tl;dr above and the documentation on Styles in Item Views. If you are still stuck (and many Python users probably will be), the rest of this answer should help.

I. The default delegate is QStyledItemDelegate

The QStyledItemDelegate is the default delegate for items in views. This is stated clearly in Qt's Model/View Programming overview:

Since Qt 4.4, the default delegate implementation is provided by QStyledItemDelegate, and this is used as the default delegate by Qt's standard views.

The docs for QStyledItemDelegate provide a little more detail:

When displaying data from models in Qt item views, e.g., a QTableView, the individual items are drawn by a delegate. Also, when an item is edited, it provides an editor widget, which is placed on top of the item view while editing takes place. QStyledItemDelegate is the default delegate for all Qt item views, and is installed upon them when they are created.

In sum, if you want to understand the underlying mechanics of any of the item views (not just a tree view, but tables and lists too), study QStyledItemDelegate.

The paint() method in qstyleditemdleegate.cpp is defined on line 419 of the doxygenated code base. Let's look at the last two lines:

    QStyle *style = widget ? widget->style() : QApplication::style();
    style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget);

Two things are happening here. First, the style is set -- typically to QApplication.style(). Second, that style's drawControl() method is invoked to draw the item being painted. And that's it. That's literally the final line of QStyledItemDelegate.paint()!

Hence, if you really want to figure out how the default delegate is painting things, we've actually got to study the style, which is doing all the real work. That's what we'll do in the rest of this document.

II. QStyle: what gives the delegate its style?

When anything is displayed using Qt, it is drawn according to some style that was chosen, in a system-specific way, when you instantiated QApplication. From the docs for QStyle:

The QStyle class is an abstract base class that encapsulates the look and feel of a GUI. Qt contains a set of QStyle subclasses that emulate the styles of the different platforms supported by Qt (QWindowsStyle, QMacStyle, QMotifStyle, etc.). By default, these styles are built into the QtGui library.

In Qt, you will find the source code for style N in src/gui/styles/N.cpp.

Each style contains the implementation of the elementary operations used for drawing everything in a GUI, from tree views to dropdown menus. The standard styles, such as QWindowsStyle, inherit most of their methods from QCommonStyle. Each particular style typically includes just minor deviations from that common base. Hence, a close study of qcommonstyle.cpp will reveal the base functionality that Qt developers found useful for painting all the parts of a GUI. Its importance is hard to overstate.

In what follows, we'll examine the parts relevant for drawing view items.

III. QStyle.drawControl(): performing endoscopy on the delegate

As mentioned above, understanding the basic mechanics of drawing a view requires examining the implementation of drawControl() in qcommonstyle.cpp, an implementation that starts on line 1197. Note in what follows, when I refer to a line number without mentioning a file name, by convention I am referring to qcommonstyle.cpp in the doxygenated code base.

The documentation for QStyle.drawControl() is instructive:

QStyle.drawControl(element, option, painter)

Parameters:

  • element – QStyle.ControlElement

  • option – QtGui.QStyleOption

  • painter – PySide.QtGui.QPainter

Draws the given element with the provided painter with the style options specified by option.... The option parameter is a pointer to a QStyleOption object and contains all the information required to draw the desired element.

The caller tells drawControl() what type of element it is trying to draw by passing it a QStyle.ControlElement flag. Control elements are higher-level components of a window that display information to the user: things like checkboxes, pushbuttons, and menu items. All of the control elements are enumerated here:

http://pyqt.sourceforge.net/Docs/PyQt4/qstyle.html#ControlElement-enum

Recall the control element sent in the call to QStyledItemDelegate.paint() was CE_ItemViewItem, which is simply an item to be displayed inside an item view. Within QCommonStyle.drawControl(), the CE_ItemViewItem case starts on line 2153. Let's dig in.

A. subElementRect(): size matters

It is key to get the size and layout of each item right. This is the first thing drawControl() calculates. To get this information, it invokes subElementRect() (defined on line 2313, and first called on line 2158). For example, we have:

QRect textRect = subElementRect(SE_ItemViewItemText, vopt, widget);

The first argument is a QStyle.SubElement flag, in this case SE_ItemViewItemText. Style subelements represent constituent parts of control elements. Each item in a view has three possible subelements: the checkbox, the icon, and text; obviously, the SE_ItemViewItemText subelement represents the text. All possible subelements are enumerated here:

http://pyqt.sourceforge.net/Docs/PyQt4/qstyle.html#SubElement-enum

The subElementRect() method contains all the cases in the subelement enumeration: our SE_ItemViewItemText case starts on line 3015.

Note that subElementRect() returns a QRect, which defines a rectangle in the plane using integer precision, and can be constructed with four integers (left, top, width, height). For instance r1 = QRect(100, 200, 11, 16). This specifies, for a subelement, its size as well as the x,y position where it will be painted in the viewport.

subElementRect() actually calls viewItemLayout() (defined on line 999) to do the real work, which is a two-step process. First, viewItemLayout() calculates the height and width of subelements using viewItemSize(). Second, it calculates the x and y position of the subelement. Let's consider each of these operations in turn.

1. viewItemLayout(): calculating the width and height of subelements

Starting on line 1003, viewItemLayout() calls viewItemSize() (defined on line 838), which calculates the height and width needed for a subelement.

Where does viewItemSize() get the default numbers for things like the height of the title bar? This is the province of the pixel metric. A pixel metric is a style-dependent size represented by a single pixel value. For instance, Style.PM_IndicatorWidth returns the width of a check box indicator, and QStyle.PM_TitleBarHeight returns the title bar height for your application's style. All the different QStyle.PixelMetrics are enumerated here:

http://pyqt.sourceforge.net/Docs/PyQt4/qstyle.html#PixelMetric-enum

You can retrieve the value of a given pixel metric using QStyle.pixelMetric(), which is used a lot in viewItemSize(). The first input of pixelMetric() is one of the QStyle.PixelMetrics in the enumeration. In qcommonstyle.cpp the implementation of pixelMetric() starts on line 4367.

For instance, viewItemSize() calculates the width and height of the checkbox (if needed) using the following:

return QSize(proxyStyle->pixelMetric(QStyle::PM_IndicatorWidth, option, widget),
    proxyStyle->pixelMetric(QStyle::PM_IndicatorHeight, option, widget));

Note that pixelMetric() is not just used in viewItemSize(), but is ubiquitous. It is used to calculate metric properties of many GUI elements, from window borders to icons, and is peppered throughout qcommonstyle.cpp. Basically, whenever you need to know how many pixels your style uses for some graphical element whose size doesn't change (like a checkbox), the style will call on pixel metrics.

2. viewItemLayout(): Rubik's Cube the subelements to get x/y positions

The second part of viewItemLayout() is devoted to organizing the layout of the subelements whose width and height were just calculated. That is, it needs to find their x and y positions to finish filling the values into QRects such as textRect. The subelements will be organized differently depending on the general setup of the view (e.g., whether it is right-left or left-right oriented). Hence, viewItemLayout() calculates the final resting position of each subelement depending on such factors.

If you ever need clues about how to reimplement sizeHint() in a custom delegate, subElementRect() could be a useful source of tips and tricks. In particular, viewItemSize() is likely to contain useful tidbits and relevant pixel metrics you might want to query when you want a custom view to closely match the default.

Once the QRects are calculated for the text, icon, and checkbox subelements, drawControl() moves on, and finally starts to draw.

B. Color the background: getting primitive

First the background gets filled in for the item (line 2163):

drawPrimitive(PE_PanelItemViewItem, option, painter);

This is a lot like the call to QStyle.drawControl(), but the first argument is not a control element, but a primitive element (PE). Just as drawControl() is a large sprawling method that controls the drawing of all higher-level control elements, QStyle.drawPrimitive() draws most of the lower-level primitive graphical elements in a GUI (such as the rectangular background in an item view). These lower-level units, the primitive elements, are enumerated here:

http://pyqt.sourceforge.net/Docs/PyQt4/qstyle.html#PrimitiveElement-enum

The docs say, "A primitive element is a common GUI element, such as a checkbox indicator or button bevel." For instance, the PE_PanelItemViewItem primitive element is "[T]he background for an item in an item view."

Each style must have an implementation of drawPrimitive(), and ours starts on line 140. Therein, you can discover in great detail how it carries out its primitive paint operations. This is a useful source of hints for how to use the paint() command in practice in your custom delegates.

The reimplementation of QStyle.drawPrimitive() for the PE_PanelItemViewItem case begins on line 773. It first selects the appropriate background color based on the state of the item. It does this by querying the item's QStyle.StateFlag. The option.state contains the state flags that describe the state of the item at the time (is it enabled, selected, being edited, etc.?). These states are not just used in the back end, but you will likely need to use it when reimplementing QStyledItemDelegate.paint() in your custom delegates. You can find an enumeration of the QStyle.StateFlags here:

http://pyqt.sourceforge.net/Docs/PyQt4/qstyle.html#StateFlag-enum

After picking the right color, drawPrimitive() then uses QPainter.fillRect() to fill the appropriate region with that color (line 786):

p->fillRect(vopt->rect, vopt->backgroundBrush);

QPainter.fillRect() is a very useful method when implementing custom delegates for yourself.

After taking care of the background, drawControl() then proceeds to finish drawing the item, starting with the checkbox (line 2165), then the icon (line 2185), and finally the text (line 2194). We won't go over this in detail, but I will finish by briefly discussing how it draws the text.

C. Drawing the text: going renegade

First, the state of the item is used to specify the text's color. This uses the QStyle.StateFlags just discussed. Then drawControl() kicks text drawing responsibilities out to a custom method viewItemDrawText() (defined at line 921):

viewItemDrawText(painter, vopt, textRect);

This method takes in the painter, option, and the text rectangle described above (part A) as parameters. Note the option parameter is very important: it is a QStyleOption class by which the content is passed around within a style (it includes icon, checkstate, and text properties).

After the text is pulled from option, it is incorporated into a QtGui.QTextLine that is added to a QtGui.QTextLayout. The final, elided (i.e., with ellipses, depending on word wrap settings) text is drawn by QPainter.drawText() (see line 983), which is one of Qt's primitive painting operations.

Frankly a good deal of viewItemDrawText() is spent dealing with word wrapping. This is where we start to get into some of the guts of Qt that no Python user was ever meant to see, much less tinker with. For instance, it uses the QStackTextEngine class. I encourage you to Google 'qstacktextengine pyqt' to see just how infrequently this has come up for Python users. If this interests you, have at it!

IV. Summary

If you want to access the underlying painting mechanics for the default delegate, you will end up studying the implementation of QStyle.drawControl() in qcommonstyle.cpp, a 6000-line beast of a file. This exercise can be very helpful for figuring out the exact procedures used to calculate sizes and draw the primitive graphical elements that items contain. Sometimes, however, this beast can be downright scary and unhelpful, like when it comes to dealing with word wrap. In those cases, you will probably just have to figure out a custom implementation of the desired functionality for your delegate.

Finally, now that we've seen a big-picture view of how things work, we can better appreciate how helpful the documentation for QStyle is, in particular the section Styles in Item Views. There, we find the following revelatory love nugget:

The painting of items in views is performed by a delegate. Qt’s default delegate, QStyledItemDelegate, is also used for calculating bounding rectangles of items (and their sub-elements) …When QStyledItemDelegate paints its items, it draws CE_ItemViewItems…When implementing a style to customize drawing of item views, you need to check the implementation of QCommonStyle (and any other subclasses from which your style inherits). This way, you find out which and how other style elements are already painted, and you can then reimplement the painting of elements that should be drawn differently.

So basically the answer to the original post is, "What they said."

like image 156
eric Avatar answered Oct 17 '22 08:10

eric