The Ultimate Qt Community site
Home News Forum Wiki Contest FAQ Links

Go Back   Qt Centre Forum > Qt > Qt Programming

Qt Programming General Qt programming issues.

Reply
 
Thread Tools Search this Thread Display Modes
  #1  
Old 20th August 2007, 16:02
darkadept darkadept is offline
Intermediate user
 
Join Date: May 2006
Qt products used: Qt4
Qt platforms used: Unix/X11 , Windows
Posts: 66
Thanks: 11
Thanked 1 Time in 1 Post
Default Drawing a widget in QItemDelegate's paint method

I know this question was asked before in this thread but I want to add to it.

I want to render a custom widget in a QListView. The above thread shows how to do this using QStyle to paint a progress bar manually. That is a great help! But what if i want a more complex widget (with a layout, buttons, text, and an icon) instead of just a single progress bar?

The ways of doing this that I can think of are:
  1. design the widget in Designer, load my data into it and render it to a pixmap and then paint the pixmap during the QItemDelegate's paint method. The problem here is that the widget would not be interactive (can't click the buttons etc).
  2. create a widget for each item in the list. For long lists this would mean too many widgets. How would you draw a widget during QItemDelegate's paint method anyways?
  3. use QStyle's drawControl method. But how do you make it adhere to a layout? Do I need to have pixel absolute offsets for each control, this would suck because then resizing the list doesn't help. Also how to I communicate the size to the sizeHint method? This seems to be the right way to go except for the problems I mentioned.

Am I on the right track here or has anyone done this before?
Reply With Quote
  #2  
Old 20th August 2007, 16:37
wysota wysota is online now
Guru
 
Join Date: Jan 2006
Location: Warsaw, Poland
Qt products used: Qt3 , Qt4 , Qt/Embedded
Qt platforms used: Unix/X11 , Windows
Posts: 14,336
Thanks: 3
Thanked 2,070 Times in 2,010 Posts
Default Re: Drawing a widget in QItemDelegate's paint method

What do you want to use this complex widget for? Maybe it'll be easier not to use QListView but a QScrollArea and place real widgets there? Do you have a model that you want to handle in the list view?
Reply With Quote
  #3  
Old 20th August 2007, 17:32
darkadept darkadept is offline
Intermediate user
 
Join Date: May 2006
Qt products used: Qt4
Qt platforms used: Unix/X11 , Windows
Posts: 66
Thanks: 11
Thanked 1 Time in 1 Post
Default Re: Drawing a widget in QItemDelegate's paint method

I have a list of plugins that I'm storing and displaying like this:
note: (don't fixate on the fact that they're plugins because I want to use this same technique for all sorts of types of data)
Qt Code:
1
2
3
4
5
6
7
8
9
10
11
QList<Plugin*> myPlugins;
populateWithSampleData(myPlugins);
 
foreach(Plugin *plugin, myPlugins) {
  QListWidgetItem *item = new QListWidgetItem;
  item->setData(0, qVariantFromValue(Plugin(*plugin)));
  listWidget->addItem(item);
}
 
PluginDelegate *pluginDelegate = new PluginDelegate;
listWidget->setItemDelegate(pluginDelegate);
note: (eventually I'll use a custom model and a QListView instead of QListWidget)

I want to provide a widget that looks a bit like the firefox plugin list or like ktorrent's plugin list but add a qpushbutton on it as well. I've included a screenshot of ktorrent's plugin list. Instead of having the Load buttons on the right hand side I would like to put the buttons directly on the list in each list item.
note: (i don't necessarily want the buttons there in my final program but want to know if it's even possible to do in Qt.)

In the code I have written already my delegate is subclassing QItemDelegate and looks like this:
Qt Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void PluginDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
    if (qVariantCanConvert<Plugin>(index.data())) {
        Plugin plugin = qVariantValue<Plugin>(index.data());
 
        //highlight background if selected
        if (option.state & QStyle::State_Selected)
            painter->fillRect(option.rect, option.palette.highlight());
 
        //I was testing the drawing of controls with QStyle
        /*
        QStyleOptionProgressBar opt;
        opt.rect = option.rect;
        opt.minimum = 0;
        opt.maximum = 100;
        opt.progress = 60;
        QApplication::style()->drawControl(QStyle::CE_ProgressBar, &opt, painter, 0);
        */
 
    } else {
        QItemDelegate::paint(painter, option, index);
    }
}
 
QSize PluginDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const
{
    //This just calls the base class method for now...
    return QItemDelegate::sizeHint(option, index);
 
}
To me it makes sense to use the delegate system as I would be able to reuse it in different views.

Maybe I need to hack together a control that takes parts from QScrollArea and QListView but that seems a little too extreme.

Surely I can't be the only one that needs this functionality and I'm a little surprised that Qt can't do this as gracefully as I first thought. Of course, I'm sure all of us Qt programmers could bemoan that thought multiple times during a day!
Attached Images
File Type: jpg ktorrent.jpg (60.0 KB, 180 views)
Reply With Quote
  #4  
Old 20th August 2007, 18:24
wysota wysota is online now
Guru
 
Join Date: Jan 2006
Location: Warsaw, Poland
Qt products used: Qt3 , Qt4 , Qt/Embedded
Qt platforms used: Unix/X11 , Windows
Posts: 14,336
Thanks: 3
Thanked 2,070 Times in 2,010 Posts
Default Re: Drawing a widget in QItemDelegate's paint method

I don't see any widgets in the screenshot you provided. All you need is to draw the icon and the text in proper places using paint() method of the delegate and maybe abusing QTextDocument to render rich text (if you need it).

Qt can do it very gracefully, you just have to implement the painting
Reply With Quote
  #5  
Old 20th August 2007, 18:43
wysota wysota is online now
Guru
 
Join Date: Jan 2006
Location: Warsaw, Poland
Qt products used: Qt3 , Qt4 , Qt/Embedded
Qt platforms used: Unix/X11 , Windows
Posts: 14,336
Thanks: 3
Thanked 2,070 Times in 2,010 Posts
Default Re: Drawing a widget in QItemDelegate's paint method

Here you go - a very simple implementation of what you seek.

BTW. You should not allow casting data from your model to "Plugin". Use QModelIndex::internalPointer() instead. And handle default roles like DisplayRole and DecorationRole.
Attached Images
File Type: png delegate.png (10.0 KB, 183 views)
Attached Files
File Type: cpp main.cpp (1.7 KB, 165 views)
Reply With Quote
  #6  
Old 21st August 2007, 04:53
darkadept darkadept is offline
Intermediate user
 
Join Date: May 2006
Qt products used: Qt4
Qt platforms used: Unix/X11 , Windows
Posts: 66
Thanks: 11
Thanked 1 Time in 1 Post
Default Re: Drawing a widget in QItemDelegate's paint method

I've definitely taken what you've posted and ran with it. I realize now that you can't just have a real QPushButton sitting on a ListView and make it work but you can draw a "fake" one just fine. You can, of course, have an interactive widget as an editor.

I also found out that you can easily draw rich text using a QTextDocument and it's drawContents() method. But I'm not sure how efficient it is to create one more QTextDocument's inside the paint() and sizeHint() methods. Is QTextDocument too "heavy" to use inside these methods? The sizeHint() method seems to fire quite a lot. I took a peek into what Qt does inside these methods of the QItemDelegate class and saw a lot of code that makes me think I should be OK.

I've been working on the sizeHint method and found that you can not determine the current width of the list (or cell, etc). In otherwords you can't dynamically resize your text horizontally dependent on the width of the window. Does anyone happen to know if you actually can get the listView's width?

I've also modified my list to use Qt's role system. I tried reading up on QModelIndex::internalPointer() but I'm not quite sure how that works. Should I be attaching each Plugin object to each QModelIndex? This is what I currently have:
Qt Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Plugin {
  public:
    Plugin(QString name, QString desc, QString author, const QPixmap &icon) {
      //snipped constructor code ...
    };
    ~Plugin() {};
 
    QString pluginName;
    QString pluginDescription;
    QString pluginAuthor;
    QPixmap pluginIcon;
};
 
int main(int argc, char *argv[])
{
  QApplication app(argc, argv);
 
  QListWidget *listWidget = new QListWidget;
 
  QList<Plugin *> pluginList;
  pluginList << new Plugin("plugin1", "plugin1 description", "mr ed", QPixmap("icon.png"));
  pluginList << new Plugin("plugin2", "plugin2 description", "mrs ed", QPixmap("icon2.png"));
 
  foreach(Plugin *plugin, pluginList) {
    QListWidgetItem *item = new QListWidgetItem;
    item->setData(Qt::DisplayRole, plugin->pluginName);
    item->setData(Qt::DecorationRole, plugin->pluginIcon);
    item->setData(Qt::UserRole + 1, plugin->pluginDescription);
    item->setData(Qt::UserRole + 2, plugin->pluginAuthor);
    listWidget->addItem(item);
  }
 
  PluginDelegate *pluginDelegate = new PluginDelegate;
  listWidget->setItemDelegate(pluginDelegate);
 
  listWidget->show();
 
  return app.exec();
}
Then when I'm in my paint() and sizeHint() methods I can access index.data(Qt::UserRole + 1) to get at my custom data, right? Is this what they intended the role system for or should I be passing my data via a different method?

Also, one more quick stupid question, in the example above will I need to manually delete my Plugin *objects or does the listWidget take care of the itself? My Plugin object is not a QObject at the moment.
Reply With Quote
  #7  
Old 21st August 2007, 08:22
wysota wysota is online now
Guru
 
Join Date: Jan 2006
Location: Warsaw, Poland
Qt products used: Qt3 , Qt4 , Qt/Embedded
Qt platforms used: Unix/X11 , Windows
Posts: 14,336
Thanks: 3
Thanked 2,070 Times in 2,010 Posts
Default Re: Drawing a widget in QItemDelegate's paint method

Quote:
Originally Posted by darkadept View Post
I've definitely taken what you've posted and ran with it. I realize now that you can't just have a real QPushButton sitting on a ListView and make it work but you can draw a "fake" one just fine.
You can have a real widget, but it's not worth it - it's too heavy right now (maybe when 4.4 is released this situation will change).

Quote:
I also found out that you can easily draw rich text using a QTextDocument and it's drawContents() method. But I'm not sure how efficient it is to create one more QTextDocument's inside the paint() and sizeHint() methods. Is QTextDocument too "heavy" to use inside these methods?
It's a bit heavy, but Trolltech uses that approach in Qt in some places, so unless you plan to have thousand plugins, you can use that approach. But first think if you need rich text here at all. I don't think you do.

Quote:
I've been working on the sizeHint method and found that you can not determine the current width of the list (or cell, etc). In otherwords you can't dynamically resize your text horizontally dependent on the width of the window.
Does anyone happen to know if you actually can get the listView's width?
Take a look at QStyleOptionViewItem::rect that is passed to the sizeHint() method. It contains the rectangle of the view occupied by the item.

Quote:
I've also modified my list to use Qt's role system. I tried reading up on QModelIndex::internalPointer() but I'm not quite sure how that works. Should I be attaching each Plugin object to each QModelIndex? This is what I currently have
Looks fine.

About the internal pointer - when you implement your own model, you often have an internal data structure there (like a list) where you store pointers to objects representing items of the model. By using internalPointer() you can return such a pointer to the outside world. A similar approach is to use internalId() - then you return an id (like an index of a list).

Quote:
Then when I'm in my paint() and sizeHint() methods I can access index.data(Qt::UserRole + 1) to get at my custom data, right?
Correct.
Quote:
Is this what they intended the role system for or should I be passing my data via a different method?
No, that's exactly how you should access your data.

Quote:
Also, one more quick stupid question, in the example above will I need to manually delete my Plugin *objects or does the listWidget take care of the itself? My Plugin object is not a QObject at the moment.
You have to take care of it in the model destructor (when you decide to use the model approach) or the widget destructor (when you decide to stick with convinience classes).

BTW. I suggest to use Qt::ToolTipRole for the description - you'll get a tooltip that shows the description for free. Also remember that not the whole description might be visible at all times (for example when the item is not high or wide enough to display the whole text) - then you'll have to elide the text.
Reply With Quote
The following user says thank you to wysota for this useful post:
rishid (20th January 2008)
  #8  
Old 21st August 2007, 16:45
darkadept darkadept is offline
Intermediate user
 
Join Date: May 2006
Qt products used: Qt4
Qt platforms used: Unix/X11 , Windows
Posts: 66
Thanks: 11
Thanked 1 Time in 1 Post
Default Re: Drawing a widget in QItemDelegate's paint method

Ok i've rewritten my delegate to use standard painter->drawText() instead of QTextDocument. It's good to know i can use both though.

Quote:
Take a look at QStyleOptionViewItem::rect that is passed to the sizeHint() method. It contains the rectangle of the view occupied by the item.
That was my first thought too but when I qDebug inside the sizeHint() method i get "QRect(0,0 0x0)". I've tried various settings on QListWidget but it never changes. That same parameter works fine in the paint() method.
Hmm, i just noticed i was using Qt 4.2.2 so i'm going to upgrade to Qt 4.3.1 and see if anything changes.

I'm still not entirely sure how to use QModelIndex::internalPointer(). But once I get my paint() and sizeHint() methods working I'll switch to QListView and a custom model and worry about it then.


I've got to say that QT Centre kicks butt and major kudos to you wysota! You've helped me out so incredibly much! thanks!
Reply With Quote
  #9  
Old 21st August 2007, 20:02
darkadept darkadept is offline
Intermediate user
 
Join Date: May 2006
Qt products used: Qt4
Qt platforms used: Unix/X11 , Windows
Posts: 66
Thanks: 11
Thanked 1 Time in 1 Post
Default Re: Drawing a widget in QItemDelegate's paint method

Alright, I guess what I was going for originally would look something like the screencap in this post:

http://www.ereslibre.es/?p=61

I know that's with KDE4.x but I was wondering if there was an easy way to implement it.

Also, I have Qt 4.3.1 installed now and I'm still getting zero's in my sizeHint() option.rect parameter.
Reply With Quote
  #10  
Old 21st August 2007, 22:33
darkadept darkadept is offline
Intermediate user
 
Join Date: May 2006
Qt products used: Qt4
Qt platforms used: Unix/X11 , Windows
Posts: 66
Thanks: 11
Thanked 1 Time in 1 Post
Default Re: Drawing a widget in QItemDelegate's paint method

I'm beginning to think that there is a bug with the QStyleOptionViewItem parameter in QAbstractItemDelegate's sizeHint() method.

How can I calculate the sizeHint if I don't know in what rect I'm working in? This makes drawing wrapped text impossible. I want to dynamically adjust the height depending on how much text can fit on screen.

Ahh well.
Reply With Quote
  #11  
Old 21st August 2007, 22:55
marcel marcel is offline
Expert
 
Join Date: Feb 2006
Location: Romania
Qt products used: Qt4
Qt platforms used: Unix/X11 , Windows
Posts: 2,744
Thanks: 8
Thanked 518 Times in 512 Posts
Default Re: Drawing a widget in QItemDelegate's paint method

It is possible to implement dynamic word-wrap. I have done it for a QListView.

See this post: http://www.qtcentre.org/forum/f-qt-p...view-8371.html

Regards
Reply With Quote
  #12  
Old 22nd August 2007, 18:05
darkadept darkadept is offline
Intermediate user
 
Join Date: May 2006
Qt products used: Qt4
Qt platforms used: Unix/X11 , Windows
Posts: 66
Thanks: 11
Thanked 1 Time in 1 Post
Default Re: Drawing a widget in QItemDelegate's paint method

Your code works wonderfully! Thanks.

But isn't assuming that the parent of the delegate is a QListView a bad thing? So I modified your sizeHint() method to use QAbstractItemView instead of QListView.

Qt Code:
1
2
3
4
5
6
7
8
9
10
QAbstractItemView *p = (QAbstractItemView*)parent();
    QString text = index.data(Qt::DisplayRole).toString();
    QFontMetrics fm(option.fontMetrics);
 
    float rw = float(p->viewport()->size().width());
    float tw = fm.width(text);
    float ratio = tw/rw;
    int lines = int(ratio) + 1;
 
    return QSize(rw,lines*fm.height());
But yes, the gaping flaw is that by using viewport()->size() this delegate will only work when the item consumes the entire width of the view. In a QAbstractTableView we will have problems. I guess I'll tackle one thing at a time.

Thanks a ton for the help!
Reply With Quote
  #13  
Old 22nd August 2007, 18:11
marcel marcel is offline
Expert
 
Join Date: Feb 2006
Location: Romania
Qt products used: Qt4
Qt platforms used: Unix/X11 , Windows
Posts: 2,744
Thanks: 8
Thanked 518 Times in 512 Posts
Default Re: Drawing a widget in QItemDelegate's paint method

Quote:
But isn't assuming that the parent of the delegate is a QListView a bad thing?
I made it for that particular case.
I didn't think someone else might use it.

Regards
Reply With Quote
  #14  
Old 22nd August 2007, 18:16
marcel marcel is offline
Expert
 
Join Date: Feb 2006
Location: Romania
Qt products used: Qt4
Qt platforms used: Unix/X11 , Windows
Posts: 2,744
Thanks: 8
Thanked 518 Times in 512 Posts
Default Re: Drawing a widget in QItemDelegate's paint method

BTW:
Quote:
I'm beginning to think that there is a bug with the QStyleOptionViewItem parameter in QAbstractItemDelegate's sizeHint() method.

How can I calculate the sizeHint if I don't know in what rect I'm working in? This makes drawing wrapped text impossible. I want to dynamically adjust the height depending on how much text can fit on screen.
That QRect is actually based on the sizeHint you return.
The framework first calls sizeHint for the delegate and then paints it.

Regards
Reply With Quote
  #15  
Old 22nd August 2007, 20:14
darkadept darkadept is offline
Intermediate user
 
Join Date: May 2006
Qt products used: Qt4
Qt platforms used: Unix/X11 , Windows
Posts: 66
Thanks: 11
Thanked 1 Time in 1 Post
Default Re: Drawing a widget in QItemDelegate's paint method

Quote:
That QRect is actually based on the sizeHint you return.
The framework first calls sizeHint for the delegate and then paints it.
Following that frame of thought, then making wrapping text work in a QTreeView and QTableView is different then in the QListView.
I haven't tested this yet but I think you would do something like this:

Qt Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
QSize PluginDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const
{
    int width;
 
    if (qobject_cast<QColumnView *>(parent()) != 0) {
        QColumnView *v = qobject_cast<QColumnView *>(parent());
        width = v->columnWidths().at(index.column());
    } else if (qobject_cast<QHeaderView *>(parent()) != 0) {
        //i'm going to leave this out for now because I don't know how to get the width of a QHeaderView cell
        width = 100;  //dumb value
    } else if (qobject_cast<QTableView *>(parent()) != 0) {
        QTableView *v = qobject_cast<QTableView *>(parent());
        width = v->columnWidth(index.column());
    } else if (qobject_cast<QTreeView *>(parent()) !=0) {
        QTreeView *v = qobject_cast<QTreeView *>(parent());
        width = v->columnWidth(index.column());
    } else if (qobject_cast<QListView *>(parent()) != 0) {
        QListView *v = qobject_cast<QListView*>(parent());
        width = p->viewport()->size().width();
    } else {
        //specify default value if we are using a non-standard view
        width = 400;
    }
 
    // ... calculate text wrapping and cell/row height based on width.
 
    // ... future feature: specify max # of text lines and then elide the text if it's too long. (see QFontMetrics::elidedText() method)
 
}
I'm sure there is a way to optimize that whole thing too, of course.

Now if you know for sure that your delegate will only ever be used in a QListView then don't do the above.
Reply With Quote
  #16  
Old 10th January 2009, 11:46
andytork andytork is offline
Beginner
 
Join Date: Jan 2009
Posts: 11
Thanks: 5
Thanked 0 Times in 0 Posts
Default Re: Drawing a widget in QItemDelegate's paint method

Quote:
Originally Posted by darkadept View Post
Alright, I guess what I was going for originally would look something like the screencap in this post:

http://www.ereslibre.es/?p=61

I know that's with KDE4.x but I was wondering if there was an easy way to implement it.

Also, I have Qt 4.3.1 installed now and I'm still getting zero's in my sizeHint() option.rect parameter.

Did anyone ever come up with similar list to the above. I am trying to produce something similar and finding it very hard

Thanks
Andy
Reply With Quote
Reply

Bookmarks

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
Drawing standard widgets using a custom paint engine Waywocket Qt Programming 25 17th June 2007 02:03
How to display widget in calling method, called from main kaydknight Newbie 4 10th March 2007 18:01


All times are GMT +1. The time now is 00:20.


Powered by vBulletin Version 3.7.4 Copyright ©2000 - 2009, Jelsoft Enterprises Ltd., vRewrite 1.5 SEOed URLs completed by Tech Help Forum and Chalo Na.
© 2006–2009 Qt Centre - The Ultimate Qt Community site
Nokia, Qt and their respective logos are trademarks of Nokia Corporation in Finland and/or other countries worldwide.