QTableView: align the icon of the item to top left - qt

I need to draw an icon on the items of a QTableView.
What I'm getting now is the following (each big rectangle is an item):
with the following code in the method
QVariant myClass::data(const QModelIndex& index, int role) const
...
case Qt::DecorationRole:
{
QPixmap pix(m_cellSize, m_cellSize);
QPainter painter( &pix );
painter.setFont( QFont("Arial", m_cellSize / 2) );
painter.setPen(Qt::black);
painter.drawRect(0, 0, m_cellSize - 1, m_cellSize - 1);
painter.drawText( QRect(0, 0, m_cellSize, m_cellSize), Qt::AlignTop, QString::number(m_letters[index.row()][index.column()].number) );
QIcon icon(pix);
return icon;
}
...
I do a drawRect only to see how the pixmap is and where the text is inside it. I can align the text in the pixmap but I cannot align the icon inside the item.
In few words, I need to draw this icon on the top left corner of the table view item but I don't know how to. It's always vertically aligned.
Any help would be appreciated.

You can investigate if changing Qt::TextAlignmentRole makes a difference, but my guess is it won't. The usual way to solve this is to implement a custom delegate, i.e subclass QStyledItemDelegate, and override the paint method. I suspect this might also give better performance, since you could do your custom drawing there, and return you model data in Qt::DisplayRole as a QString instead.
I'm making a few assumptions in this, but if you look at the examples of custom item delegates I think you will see something very close to what you need.

Related

How can I wrap text in QGraphicsItem?

1) How can I wrap text in a QGraphicsTextItem to fit a fixed rectangle, with width and height ?
Right now I am experimenting with creating a text, getting its bounding rectangle, and resizing it to fit the box - but I can't get wrapping.
class TTT: public QGraphicsTextItem {
TTT() {
{
setPlainText("abcd");
qreal x = m_itemSize.width()/boundingRect().width();
qreal y = m_itemSize.height()/boundingRect().height();
scale(x, y);
}
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
// experiment with clip regions
// text gets covered by hole in clip
QRegion r0(boundingRect().toRect());
QRegion r1(QRect(5, 5, 10, 10), QRegion::Ellipse);
QRegion r2 = r0.subtracted(r1);
painter->setClipRegion(r2);
painter->setBrush(Qt::yellow);
painter->drawRect(boundingRect());
QGraphicsTextItem::paint(painter, option, widget);
}
}
What makes wrapping happen, how can I trigger it ?
Right now, as I keep typing, the box is automatically expanding.
2) Is it possible to wrap the text in a QGraphicsItem / QGraphicTextItem subclass in a shape that is not a rectangle ?
(Something like in the image above)
I tried to use clipRegion, see code above, but I guess it is not the right way to go, clipping cuts the text but did not wrap.
Maybe it would... If I could figure out how to wrap text in the first place ?
Qt 4.8
You did not specify Qt version but try:
void QGraphicsTextItem::setTextWidth(qreal width)
Sets the preferred width for the item's text. If the actual text is wider than >the specified width then it will be broken into multiple lines.
If width is set to -1 then the text will not be broken into multiple lines >unless it is enforced through an explicit line break or a new paragraph.
The default value is -1.
In answer to 1) I'd opt not to use the QGraphicsTextItem, but draw the text directly in your QGraphicsItem's paint function using the drawText overloaded function, which takes a QTextOption parameter.
Using this, you can set the WrapMode, for example, with a call to
QTextOption::setWrapMode(QTextOption:: WordWrap)
As for 2) with a non-rectangular shape, I don't think Qt will do this for you.
Doing it yourself you can use QFontMetrics, to work out just how much text would fit in each line, depending upon where it lies within its bounding item.
Alternatively, you could adapt the concept of a text-to-path method.

Use Clipping in Qt

Is it possible to use clipping in an widgets painEvent, if the widget is using stylesheets?
The background and reason for my question is that I want to make the widget animating when it appears and disappears. (Something like a resizing circle or square, that gets bigger starting as a small area from the center).
My first (and only) thought on how to solve this, was to use the clipping of a QPainter, so that only the required area is drawn.
If I make the Background of the widget transparent and use the primitive drawing functions from QPainter it works fine. But how can I solve this, if the widget has a stylesheet applied? Is it even possible?
The used Qt version is Qt 4.8.6
My questions are:
Is it possible to achieve what I want with the mentioned strategy?
Is it possible in any way to clip all the children, too?
Is my strategy appropriate or is it a bad Idea to solve it that way?
Are there any other ideas, best practices, Qt Classes, ... that can give me what I want?
Additional Information
I haven't much code to show, because I stuck with this clipping things. But here is something to get an idea of what I have tried:
This works.
/* Shows a small red circle inside the widget as expected */
void MyAnimatingWidget::paintEvent(QPaintEvent *ev) {
QPainter painter(this);
QRect rect = this->geometry()
QStyleOption opt;
painter.setClipRegion(QRegion(rect.width()/2,
rect.height()/2,
150, 150,
QRegion::Ellipse));
painter.setPen(QColor(255, 0, 0));
painter.setBrush(QColor(255, 0, 0));
painter.setOpacity(1);
painter.drawRect(rect);
}
But the following doesn't change anything:
/* This shows the widget as usual */
void MyAnimatingWidget::paintEvent(QPaintEvent *ev) {
QPainter painter(this);
QRect rect = this->geometry();
QStyleOption opt;
painter.setClipRegion(QRegion(rect.width()/2,
rect.height()/2,
150, 150,
QRegion::Ellipse));
painter.setRenderHint(QPainter::Antialiasing);
painter.setOpacity(1);
opt.init(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
}
Moreover I have noticed, that the stylesheet is also drawn, even if I remove the style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); line at all.
The stylesheet you apply to your widget overrides the OS-specific style(s) widgets are equipped with by default. This can even cause problems, if you want to have a, say, Windows look, but still want to use a stylesheet. Anyway, you can check what each style does in the Qt source directory: src/gui/styles. For style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);, the code reads:
case PE_Widget:
if (w && !rule.hasDrawable()) {
QWidget *container = containerWidget(w);
if (styleSheetCaches->autoFillDisabledWidgets.contains(container)
&& (container == w || !renderRule(container, opt).hasBackground())) {
//we do not have a background, but we disabled the autofillbackground anyway. so fill the background now.
// (this may happen if we have rules like :focus)
p->fillRect(opt->rect, opt->palette.brush(w->backgroundRole()));
}
break;
}
As you can see clipping is not meddled with in any way, so your idea of setting a clip region should work. Now for the painting mystery. The painting of the background happens in void QWidgetPrivate::paintBackground(QPainter *painter, const QRegion &rgn, int flags) const, which is called from void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QPoint &offset, int flags, QPainter *sharedPainter, QWidgetBackingStore *backingStore). You can find the code in: /src/gui/kernel/qwidget.cpp. The relevant code reads:
if (q->testAttribute(Qt::WA_StyledBackground)) {
painter->setClipRegion(rgn);
QStyleOption opt;
opt.initFrom(q);
q->style()->drawPrimitive(QStyle::PE_Widget, &opt, painter, q);
}
Maybe turning the attribute off would help? The basic lesson you should draw from my answer is to get accustomed to source diving. The idea behind Qt is nice (instantiating controls, without bothering about implementation details), but it rarely works in practice, i.e. you often need to source dive.
To clip widget's children to arbitrary clip regions, you can capture them into a pixmap, example:
QPixmap pixmap(widget->size());
widget->render(&pixmap);
And then draw the pixmap manually. You might also be able to prevent them repainting automatically (via setUpdatesEnabled() or by hiding them) and then calling their render in you paintEvent handler manually.

Qt: QListWidget separator line after particuler items?

This is related to Qt: QListWidget separator line between items?
But this above answer adds separator line after each items, I would like to know a way to add the separator line after particular items.
Create a QListWidgetItem representing the separator. Such item would need to have defined the setSizeHint(), so its height is small, and also the setFlags() should define Qt::NoItemFlags, so the item is not selectable, etc. Then, after adding the item to the QListWidget, place a QFrame, with its shape set to QFrame::HLine, as the item's widget (using QListWidget::setItemWidget()).
As for your additional question from the comment, which is:
I want to add some gap on each sides of this separator line/frame. How can I achieve this?
The only solution that comes to my mind right now is to embed the QFrame inside of another QWidget and put the QWidget as item's widget (remember that you need to add a layout manager to the QWidget in order to embed anything in it). Then set proper margins on the widget: QWidget::setContentsMargins(int left, int top, int right, int bottom)
I found another possibility and tested it this time :p
You could create a new class inheriting QStyledItemDelegate that look like this :
void MyStyledItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyledItemDelegate::paint(painter, option, index);
// I have decided to use Qt::UserRole + 1 to store my boolean
// but it could be any other role while it's value is bigger than Qt::UserRole
QVariant isSeparator = index.data(Qt::UserRole + 1);
if (isSeparator.isValid() && isSeparator.toBool())
{
QRect rct = option.rect;
rct.setY(rct.bottom() - 1);
painter->fillRect(rct, QColor::fromRgb(qRgb(0, 0, 0)));
}
}
And the for each QListWidgetItem you can do the following :
// Qt::UserRole + 1 => Must match the role set in the delegate
item->setData(Qt::UserRole + 1, true);
Install the custom in your QListWidget like this
listWidget->setItemDelegate(new MyStyledItemDelegate());
It will draw a black line under the text of the item if the Qt::UserRole + 1 is set to true.
You can try using the same trick with dynamic properties.
myListWidget->setStyleSheet( "QListWidget::item[separator="true"] { border-bottom: 1px solid black; }" );
And on the widget you want the line to be drawn :
myWidget->setProperty("separator", true);
However be carefull the documentation says :
Warning: If the value of the Qt property changes after the style sheet has been set, it might be necessary to force a style sheet recomputation. One way to achieve this is to unset the style sheet and set it again.

Need to display the widgets inside my QListWidget with an offset, basically shifted a bit to the right

I have a panel.ui file done using QTDesigner. It's a QFrame class, rectangular shape with few labels on it. And I have a QListWidget class where I insert 3 instances of the panel.ui.
I create a QListWidgetItem and then use List->SetItemWidget(..) to populate my list.
The Result is a list filled with three panels. I was also able to move the panels inside the list using dragDropMode internalMove.
I also tested the ability to shift the panels a bit to the right when I click on them and that worked:
in procedure List::mousePressEvent(QMouseEvent *event)
Panel *child = static_cast<Panel*>(childAt(event->pos()))
...
int y= child->pos().y();
int x = child->pos().x();
child->move (x +10, y); `
Problem: When I run the app and display the list, I want all the panels to be displayed with that 10 offset to the right. So in the List constructor and inside the loop after this->setItemWidget(myPanelItem, myPanel); I try using myPanel->move() like above but it doesn't seem to work.
I run the app, the panels are displayed without my offset ( not sure why?) but when I click on one, it shifts.
move() won't work reliably since the widgets are in a layout. (Well, not a layout as in a QLayout, but the effect is comparable: When any metric in your application changes, e.g. you resize or scroll the list, the widgets are repositioned by the list widget.)
What you can do is wrap your actual widget in a container widget with a layout margin:
QWidget* wrapIntoContainerForOffset(QWidget* widget, int offset /*in pixels*/) {
QWidget* container = new QWidget;
QHBoxLayout* layout = new QLayout(container);
layout->setContentsMargins(/*left=*/ offset, /*others=*/ 0, 0, 0);
layout->addWidget(widget);
return container;
}
Then you add these containers to the listwidget instead.
Have You tried StyleSheets. The QListWidget supports Box model( http://doc.qt.digia.com/qt/stylesheet-customizing.html#box-model ). So You may want to try playing around with margins in the stylesheets.
Style sheet reference: http://doc.qt.digia.com/qt/stylesheet-reference.html

Setting QStyleOptionComboBox.currentText does not have any effect on the drawn widget

I want to draw a QComboBox inside a delegate, which works fine except that I can't figure out how to draw the inital text that's visible inside the combo box.
The documentation says that QStyleOptionComboBox.currentText holds: "the text for the current item of the combo box." but setting the variable does not have any effect.
This is my code:
void MyDelegate::paint(QPainter *painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
QStyleOptionComboBox comboBoxOption;
comboBoxOption.rect = option.rect;
comboBoxOption.state = option.state;
comboBoxOption.state |= QStyle::State_Enabled;
comboBoxOption.editable = false;
comboBoxOption.currentText = "CCC"; // This doesn't show up.
QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &comboBoxOption, painter);
}
Looking at qwindowsxpstyle.cpp I don't see where the text of a "real" combo box is drawn since currentText is not used inside the drawComplexControl method. The only place where it seems to be used for Windows XP style is in qcommonstyle.cpp (Line 2107, Qt 4.7.2), but I can't figure out how those two classes play together.
It seems you also need to force Qt to draw the combo box label, in addition to the complex control. Try this:
QApplication::style()->drawControl(QStyle::CE_ComboBoxLabel, &comboBoxOption, painter)
If I read the documentation, and source, correctly that might force QStyle to draw a combo box label. It seems odd that you'd have to specify both...but I don't know a whole lot about how Qt styles draw themselves, to be honest.

Resources