PDA

View Full Version : Problem repainting a subclassed QCalendarWidget



darkadept
28th September 2007, 17:18
I've sublcassed a QCalendarWidget, and reimplemented it's paintCell routine to display text on certain days. So far so good right? Now when the text changes i should call the "update()" method to repaint the widget, right?

Well that doesn't work 100% of the time. I've even tried calling the "repaint()" method as well. Same results. Occasionally it will respond and repaint the correct changes, but more often then not I need to click on the date's cell to have it redraw.

I've noticed that Qt uses a custom model/view in the private implementation of QCalendarWidget, but that's untouchable in my subclass.

What I'm guessing is that QCalendarWidget was never meant for this level of interactivity?? I hope not, because I really don't feel like coding my own version of the calendar widget.... sigh.

[using Qt/X11 4.3.1]

wysota
28th September 2007, 21:01
But where and why are you calling repaint/update? If you reimplement paintCell(), Qt should call your implemention itself - you needn't do anything more. For instance to make a calendar widget that paints sundays on a red background all you'd have to do is:


class RedSundaysCalendarWidget : public QCalendarWidget{
public:
RedSundaysCalendarWidget(QWidget *parent=0) : QCalendarWidget(parent){}
protected:
void paintCell ( QPainter * painter, const QRect & rect, const QDate & date ) const{
if(date.dayOfWeek()==Qt::Sunday){
painter->fillRect(rect, Qt::red);
painter->save();
painter->setPen(Qt::green);
painter->drawText(rect, Qt::AlignCenter, QString::number(date.day()));
painter->restore();
} else QCalendarWidget::paintCell(painter, rect, date);
}
};

darkadept
28th September 2007, 21:26
But i'm doing a bit more then that. My class look something like this:



class EventCalendarWidget : public QCalendarWidget
{
Q_OBJECT
public:
EventCalendarWidget(QWidget *parent = 0) : QCalendarWidget(parent) {}
~EventCalendarWidget() {}

void addEvent(QDate date, QString event) {
_events.insert(date, event);
update(); //this doesn't always work
}

protected:
void paintCell(QPainter *painter, const QRect &rect, const QDate &date) const {
if (_events.contains(date)) {
QList<QString> events = _events.values(date);
QRect r = rect;
QRect br;
painter->save();
foreach(QString event, events) {
r.adjust(0, br.height(), 0, 0);
painter->drawText(r, Qt::TextSingleLine, event, &br);
}
painter->restore();
} else {
QCalendarWidget::paintCell(painter, rect, date);
}
}

private:
QMultiMap<QDate, QString> _events;

};

wysota
28th September 2007, 21:57
Your code seems fine. If you don't get the effect you want, that's probably because you might be drawing outside rect. And your loop seems incorrect. I'd implement it like so:

int y=rect.top();
foreach(QString event, events){
QRect br;
painter->drawText(QRect(rect.left(), y, rect.width(), rect.height()-y), ..., &br);
y+=br.height()+1;
}

darkadept
28th September 2007, 22:37
The drawing code loop actually works, but yes your way works equally as well, if not better. =) But the problem here is with the call to update(), which IS what the qt docs say to use. repaint() has the same problem as update().

I've been studying the Qt source for QCalendarWidget and it seems that it doesn't reimplement it's paintEvent() method from QWidget. Instead it uses a custom view and adds that to QCalendarWidget in a layout. See the QCalendarWidget constructor in the Qt sources.

Looking at QCalendarWidget::setDateTextFormat(....) it seems that it does something else to initiate redrawing.


void QCalendarWidget::setDateTextFormat(const QDate &date, const QTextCharFormat &format)
{
Q_D(QCalendarWidget);
d->m_model->m_dateFormats[date] = format;
d->m_view->viewport()->update();
d->m_view->updateGeometry();
}

But I don't have access to the private 'd' object and therefore don't have access to update the custom view's viewport.

Hmm, looking at this function:


bool QCalendarWidget::event(QEvent *event)
{
Q_D(QCalendarWidget);
switch (event->type()) {
case QEvent::LocaleChange:
d->cachedSizeHint = QSize();
d->m_navigator->setLocale(locale());
d->updateMonthMenuNames();
d->updateNavigationBar();
d->m_view->updateGeometry();
break;
case QEvent::FontChange:
case QEvent::ApplicationFontChange:
d->cachedSizeHint = QSize();
d->m_view->updateGeometry();
break;
case QEvent::StyleChange:
d->cachedSizeHint = QSize();
d->m_view->updateGeometry();
default:
break;
}
return QWidget::event(event);
}

I might be able to fake a font or style change to get it to call it's private updateGeometry() method.

So really (if I understand my Qt properly) QCalendarWidget is just a simple container widget that holds a private custom view and calling QCalendarWidget::update() does nothing to update that custom view.

darkadept
28th September 2007, 22:44
darn... faking a font change event doesn't do it either.

wysota
28th September 2007, 22:50
Yes, you understand correctly :)

I think you should file a bug (or suggestion) report to Trolltech. There should be some invalidate() method available to invalidate the drawing.

But as a temporary solution something like this should work:

void ...::addEvent(const QDate &date, const QString &event){
_events.insert(date, event);
QAbstractItemView *view = qFindChild<QAbstractItemView*>(this);
if(view){
view->viewport()->update();
} else update(); // fallback
}
If this doesn't work, you'll have to hack into accessing protected methods of the view... but try the above code first.


BTW. Nice idea with the event calendar widget.

darkadept
28th September 2007, 23:40
Works so beautifully!!!! Thanks a ton for this.

Of course I can see this is hardly the optimal solution for this problem.

I've sent a message to the qt-interest mailing list documenting this problem as well.

btw, where do I send bug reports to?

derek_r
29th September 2007, 00:18
btw, where do I send bug reports to?


You can file a bug report here: http://trolltech.com/developer/bugreport-form

wysota
29th September 2007, 00:54
Works so beautifully!!!! Thanks a ton for this.
Great.


Of course I can see this is hardly the optimal solution for this problem.
Actually it is kind of optimal (ok... suboptimal), only that there should be a protected method to do that.


btw, where do I send bug reports to?
http://www.trolltech.com/developer/task-tracker