QTreeView draw drop indicator - qt

I need to implement rows moving via drag-n-drop in QTreeView and show the drop indicator between rows. I am wondering if there is a way to override indicator drawing, so it is displayed for all levels of hierarchy between rows only (not the rectangle around the item), the line must be as wide as the entire row (not as the one column).

It is possible by modyfing style used to draw widget. My attempt seemed to work well, but it's a bit of cheating the qt's style system, so i cannot guarante that it will work under all possible styles on all platforms. So here it is:
class myViewStyle: public QProxyStyle{
public:
myViewStyle(QStyle* style = 0);
void drawPrimitive ( PrimitiveElement element, const QStyleOption * option, QPainter * painter, const QWidget * widget = 0 ) const;
};
myViewStyle::myViewStyle(QStyle* style)
:QProxyStyle(style)
{}
void myViewStyle::drawPrimitive ( PrimitiveElement element, const QStyleOption * option, QPainter * painter, const QWidget * widget) const{
if (element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull()){
QStyleOption opt(*option);
opt.rect.setLeft(0);
if (widget) opt.rect.setRight(widget->width());
QProxyStyle::drawPrimitive(element, &opt, painter, widget);
return;
}
QProxyStyle::drawPrimitive(element, option, painter, widget);
}
myView::myView(QWidget *parent) :
QTreeView(parent)
{
setStyle(new myViewStyle(style()));
}

Related

QCombobox not using custom delegate to render current item

I am using a custom delegate to render rich text in a QComboBox. It is used to allow users to select a color to plot a variable on a graph. It works for items in the drop down menu, but not the selected item. Any help would be appreciated.
Here is the code for the delegate I am using:
class CustomDelegate : public QStyledItemDelegate
{
public:
CustomDelegate();
protected:
void paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const;
QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const;
};
void CustomDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const{
QStyleOptionViewItemV4 optionV4 = option;
initStyleOption(&optionV4, index);
QStyle *style = optionV4.widget? optionV4.widget->style() : QApplication::style();
QTextDocument doc;
doc.setHtml(optionV4.text);
/// Painting item without text
optionV4.text = QString();
style->drawControl(QStyle::CE_ItemViewItem, &optionV4, painter);
QAbstractTextDocumentLayout::PaintContext ctx;
// Highlighting text if item is selected
if (optionV4.state & QStyle::State_Selected)
ctx.palette.setColor(QPalette::Text, optionV4.palette.color(QPalette::Active, QPalette::HighlightedText));
QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &optionV4);
painter->save();
painter->translate(textRect.topLeft());
painter->setClipRect(textRect.translated(-textRect.topLeft()));
doc.documentLayout()->draw(painter, ctx);
painter->restore();
}
QSize CustomDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const{
QStyleOptionViewItemV4 optionV4 = option;
initStyleOption(&optionV4, index);
QTextDocument doc;
doc.setHtml(optionV4.text);
doc.setTextWidth(optionV4.rect.width());
return QSize(doc.idealWidth(), doc.size().height());
}
I set the options and delegate for the combo box using code similar, I removed some of the options to reduce the size of the code:
ui->SelectColor->clear();
ui->SelectColor->addItem("Select Color");
ui->SelectColor->addItem("<font color='blue'>Blue</font>");
ui->SelectColor->addItem("<font color='darkBlue'>Dark Blue</font>");
ui->SelectColor->addItem("<font color='red'>Red</font>");
ui->SelectColor->addItem("Dark Yellow");
ui->SelectColor->addItem("<font color='magenta'>Dark Magenta</font>");
ui->SelectColor->addItem("White");
ui->SelectColor->setItemDelegate(new CustomDelegate);
Some of the options are just the names because they don't look good when rendered.
Your custom styled item delegate is applied to items that are part of QComboBox (i.e. ones in QComboBox popup), not for the current item which is represented. The easiest way to achieve what you want would be to introduce your custom class inheriting QComboBox, and then override void QWidget::paintEvent(QPaintEvent *event) method by applying changes you would like to introduce, e.g. set some colored text. Another way (in case you can not introduce another class inheriting QComboBox for some reason) would be to introduce an event filter that does something just after the QPaintEvent. However, usage of event filters might be tricky, and I would advise you to just introduce some another class, and then override paint event.
Now, in case you want to show current item in the same way as an option in the combo box list popup, you could do the following (code is not complete, and you should apply it to your needs yourself):
virtual void paintEvent(QPaintEvent* e) override
{
// QComboBox::paintEvent(e); - this will leave just a rectangle in which you can perform your custom drawings.
// Will make your option colored at least.
QPainter p(this);
QTextDocument doc;
doc.setHtml(this->currentText());
doc.drawContents(&p, rect());
}
Finally, it is worth to mention that QComboBox painting is pretty complex, and you might still need to rewrite pretty everything that Qt already has done for you, so it would be possible to apply other styles you might want not to lose. In order to do that, you should take a look into the source code of this class. You can do this in the Code Browser by Woboq for C & C++ (QComboBox). Hopefully, this clarifies the problem for you more, and now you know what to do in order to achieve your goal.

Change highlight color of text inside a QGraphicsTextItem

I would like to change the highlight color of the selected text inside a QGraphicsTextItem.
I have the paint method subclassed, so I thought it can be as simple as setting a different palette on the QStyleOptionGraphicsItem - but I can't see any examples, and what I try is not working:
void TextItem::paint(QPainter* painter,
const QStyleOptionGraphicsItem* option,
QWidget* widget)
{
QStyleOptionGraphicsItem opt(*option);
opt.palette.setColor(QPalette::HighlightedText, Qt::green);
QGraphicsTextItem::paint(painter, &opt, widget);
}
This has no effect....
How can I change the highlight color of the selected text inside an item ?
The default implementation of QGraphicsTextItem::paint() doesn't care about QStyleOptionGraphicsItem::palette. You have to implement a custom painting if you want different color.
This is a simplified way how to do it:
class CMyTextItem : public QGraphicsTextItem
{
public:
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
{
QAbstractTextDocumentLayout::PaintContext ctx;
if (option->state & QStyle::State_HasFocus)
ctx.cursorPosition = textCursor().position();
if (textCursor().hasSelection())
{
QAbstractTextDocumentLayout::Selection selection;
selection.cursor = textCursor();
// Set the color.
QPalette::ColorGroup cg = option->state & QStyle::State_HasFocus ? QPalette::Active : QPalette::Inactive;
selection.format.setBackground(option->state & QStyle::State_HasFocus ? Qt::cyan : ctx.palette.brush(cg, QPalette::Highlight));
selection.format.setForeground(option->state & QStyle::State_HasFocus ? Qt::blue : ctx.palette.brush(cg, QPalette::HighlightedText));
ctx.selections.append(selection);
}
ctx.clip = option->exposedRect;
document()->documentLayout()->draw(painter, ctx);
if (option->state & (QStyle::State_Selected | QStyle::State_HasFocus))
highlightSelected(this, painter, option);
}
};
However, this solution is not perfect. Not-blinking text cursor is one imperfection. There are probably others. But I believe that improving it a little will be not that big deal for you.

Painting QPixmap in the center of QTableView cell

I have a QTableView that works very well, the first column holds some thumbnails, in each cell of this column the thumbnails are vertically centered, but not horizontally centered.
Do I really need to use a delegate?
If yes, How to center them horizontally using QStyledItemDelegate?
Construct your own delegate and inherit QStyledItemDelegate. Override the paint method.
Then do something like this:
void
MyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
QPixmap pixmap;
pixmap.load("Your pixmap file path");
pixmap = pixmap.scaled(option.rect.width(), option.rect.height(), Qt::KeepAspectRatio);
// Position our pixmap
const int x = option.rect.center().x() - pixmap.rect().width() / 2;
const int y = option.rect.center().y() - pixmap.rect().height() / 2;
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
}
painter->drawPixmap(QRect(x, y, pixmap.rect().width(), pixmap.rect().height()), pixmap);
}
Drawing by yourself is not necessary, but a custom delegate - is. The styled item delegate uses the style's control element drawing code to draw a CE_ItemViewItem - see the source code for Qt 5.5.0. The drawing code takes the style option's decorationAlignment member into account. Unfortunately, there's no data role that would pass that alignment to the styles's implementation. Instead, you have to override the alignment in your delegate:
class DecorationAligningDelegate : public QStyledItemDelegate {
Q_OBJECT
Qt::Alignment const m_alignment;
public:
explicit DecorationAligningDelegate(Qt::Alignment alignment, QObject * parent = 0) :
QStyledItemDelegate(parent), m_alignment(alignment) {}
Qt::Alignment alignment() const { return m_alignment; }
void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const {
auto opt = option;
opt.decorationAlignment = m_alignment;
QStyledItemDelegate::paint(painter, opt, index);
}
};
Then, to center the thumbnails:
view.setItemDelegateForColumn(0,
new DecorationAligningDelegate(Qt::AlignHCenter, &view));
//or
view->setItemDelegateForColumn(0,
new DecorationAligningDelegate(Qt::AlignHCenter, view));
If you really wished to paint it all yourself, even though it's unnecessary, the rectangle of the item to be painted is given in the style option (option.rect). To draw the pixmap centered in the item's rectangle, you could do as follows:
QStyleOption option;
QPixmap pix;
QPainter painter;
...
painter.save();
auto loc = option.rect.center() - pix.rect().center()
painter.drawPixmap(loc, pix);
painter.restore();
I will just leave my version that literary is a combination of the two answers.
class DecorationAligningDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit DecorationAligningDelegate(Qt::Alignment alignment, QObject *parent = nullptr)
: QStyledItemDelegate(parent), m_alignment(alignment) {}
Qt::Alignment alignment() const { return m_alignment; }
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QIcon icon = QIcon(qvariant_cast<QIcon>(index.data(Qt::DecorationRole)));
if (option.state & QStyle::State_Selected)
{
painter->fillRect(option.rect, option.palette.highlight());
}
icon.paint(painter, option.rect, m_alignment);
}
private:
Q_DISABLE_COPY(VDecorationAligningDelegate)
Qt::Alignment const m_alignment;
};
I assume you define your item like this:
auto *item = new QTableWidgetItem();
item->setIcon(QIcon("Your pixmap file path"));
Don't forget about setting a delegate.

Drawing an overlay on top of an application's window

I want to be able to paint on top of my application's window so that I can annotate all the widgets with some extra diagnostic information, similar to the CSS developer tools in Firefox (eg add widget classes, styles, highlight borders etc).
I can walk the widget tree and extract the relevant information, but the question is how can I overlay all the application windows with this information?
One way would be to override my QMainWindow's paint event, but this has to be done for all top level windows. Is there an alternative method where you can paint on the QDesktopWidget for instance? Or any hooks into each QWidget's paint method? Anything that involves subclassing QWidget itself won't work with the standard widgets.
This follows on from my previous question:
Are there any useful tools for diagnosing Qt layout and spacing problems?
cheers
Mandrill
EDIT:
Thanks to Dmitry I've now got a really simple method that is easily extensible:
class DiagnosticStyle : public QWindowsVistaStyle
{
Q_OBJECT
public:
typedef QWindowsVistaStyle BaseStyle;
void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const;
};
void DiagnosticStyle::drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const
{
BaseStyle::drawControl(element, option, painter, widget);
if (widget && painter) {
// draw a border around the widget
painter->setPen(QColor("red"));
painter->drawRect(widget->rect());
// show the classname of the widget
QBrush translucentBrush(QColor(255,246,240, 100));
painter->fillRect(widget->rect(), translucentBrush);
painter->setPen(QColor("darkblue"));
painter->drawText(widget->rect(), Qt::AlignLeft | Qt::AlignVCenter, widget->metaObject()->className());
}
}
qApp->setStyle(new DiagnosticStyle());
You can create own style class based on QMotifStyle or other ... and paint on any widget/control related to him information.
void MyStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option,QPainter *painter, const QWidget *widget) const
{
QStyle::State flags = option->state;
QRect rect = option->rect;
QPalette pal = option->palette;
QBrush brush;
switch (element)
{
case PE_FrameTabWidget:
{
painter->save();
// for example: draw anything on TabWidget
painter->drawPixmap(rect,centerPm,centerPm.rect());
painter->restore();
}
break;
default:
QMotifStyle::drawPrimitive(element, option, painter, widget);
break;
}
}
Somewhere in Qt5 the styles (GTK, Windows, etc) were made internal. Now you need to use QCommonStyle.
If anyone's wondering how to do this with Qt5+. Here's a self-contained version of #the_mandrill's code above.
class DiagnosticStyle : public QCommonStyle
{
Q_OBJECT
public:
typedef QStyle BaseStyle;
void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const
{
QCommonStyle::drawControl(element, option, painter, widget);
if (widget && painter) {
// draw a border around the widget
painter->setPen(QColor("red"));
painter->drawRect(widget->rect());
// show the classname of the widget
QBrush translucentBrush(QColor(255,246,240, 100));
painter->fillRect(widget->rect(), translucentBrush);
painter->setPen(QColor("darkblue"));
painter->drawText(widget->rect(), Qt::AlignLeft | Qt::AlignVCenter, widget->metaObject()->className());
}
};
};
Then, in your main window constructor call
qApp->setStyle(new DiagnosticStyle());

QStyledItemDelegate - How does updateEditorGeometry works?

I'm using Qt 4.7.
I have a model that I display in a QTableView in two columns, and my goal is to provide inline editing of this model in my QTableView.
+-----------------+----------------+
| Axis position | Axis range |
+-----------------+----------------+
| Left | Fixed [0,1] |
| Left | Source: SRC1 |
| Right | Source: SRC2 |
| Left | Fixed [5,10] |
+-----------------+----------------+
The first column is editable by using a simple QComboxBox to switch between Right and Left, and it works quite well. The problem is with my second column, which is editable using a custom widget.
This widget is kind of simple, it describes a range. So there is a QComboBox to select the kind of range ("Fixed": the values are given by the user, "Source": the value are adjusted dynamically from the min/max of a source).
Here is the source code of my custom widget:
class RangeEditor : public QWidget
{
Q_OBJECT
public:
RangeEditor( ... );
~RangeEditor();
public:
CurveView::ConfigAxes::Range range () const;
QVariant minimum() const;
QVariant maximum() const;
DataModel* model () const;
void range ( CurveView::ConfigAxes::Range range );
void minimum( QVariant minimum );
void maximum( QVariant maximum );
void model ( DataModel* model );
public slots:
void rangeTypeChanged( int type );
private: // --- External editors
QComboBox* editRange_;
QSpinBox* editMinimum_;
QSpinBox* editMaximum_;
QComboBox* editModel_;
};
RangeEditor::RangeEditor( ... ) : QWidget(parent)
{
editRange_ = new QComboBox(this);
editMinimum_ = new QSpinBox (this);
editMaximum_ = new QSpinBox (this);
editModel_ = new QComboBox(this);
QHBoxLayout* layout = new QHBoxLayout();
setLayout(layout);
layout->addWidget( editRange_ );
layout->addWidget( editMinimum_ );
layout->addWidget( editMaximum_ );
layout->addWidget( editModel_ );
editRange_->addItem( "Fixed" );
editRange_->addItem( "Source" );
editModel_->setCurrentIndex(0);
editModel_->hide();
QObject::connect( editRange_, SIGNAL(currentIndexChanged(int)),
this, SLOT (rangeTypeChanged(int)) );
}
void RangeEditor::rangeTypeChanged( int type )
{
if ( type==CurveView::ConfigAxes::FIXED )
{
editMinimum_->show();
editMaximum_->show();
editModel_->hide();
}
else if ( type==CurveView::ConfigAxes::SOURCE )
{
editMinimum_->hide();
editMaximum_->hide();
editModel_->show();
}
}
Okay, so now, I created a QStyledItemDelegate to provide the view a custom editor for my columns. Here is how I did it:
class ConfigAxesDelegate : public QStyledItemDelegate
{
public:
ConfigAxesDelegate( ... );
~ConfigAxesDelegate();
public:
virtual QWidget* createEditor ( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
virtual void setEditorData ( QWidget* editor, const QModelIndex& index ) const;
virtual void setModelData ( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const;
virtual void updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
};
QWidget* ConfigAxesDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
if ( index.column()==0 ) // Position
{
PositionEditor* editor = new PositionEditor(parent);
return editor;
}
else if ( index.column()==1 ) // Range
{
RangeEditor* editor = new RangeEditor(parent);
return editor;
}
else
{
return QStyledItemDelegate::createEditor(parent,option,index);
}
}
void ConfigAxesDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
// WHAT TO DO HERE?
editor->setGeometry( option.rect );
}
Basically, what I get is a single pixel height editor.
Here is a screenshot of the result:
I tried to changed updateEditorGeometry to the following:
void ConfigAxesDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
QRect r = option.rect;
r.setSize( editor->sizeHint() );
editor->setGeometry( r );
}
Which seems to fix the size problem, but not the position:
I feel kind of lost since I don't know if the problem comes from my custom widget (not providing enough information for Qt to compute its position properly), or the view (maybe some margins that would crush the editor size), or the updateEditorGeometry() method.
Any help greatly appreciated, thanks for reading!
I would say setting editor's geometry by calling:
editor->setGeometry(rect);
should work correctly; What happens in your case is that your editor is built using QHBoxLayout which has default margins and spacing set. Default height of your tableview rows is less then editor's height and this makes your editor to resize; one pixel row on your screen shot would be: top margin + what's left from controls + bottom margin.
By enabling the vertical header for your tableview you would be able to resize row height to make your editor controls completely visible.
What you could possibly do:
1.Remove\decrease spacing and margins for the layout:
QHBoxLayout* layout = new QHBoxLayout();
layout->setSpacing(1);
layout->setMargin(1);
setLayout(layout);
in this case, updating editor's geometry this way:
QRect rect = option.rect;
QSize sizeHint = editor->sizeHint();
if (rect.width()<sizeHint.width()) rect.setWidth(sizeHint.width());
if (rect.height()<sizeHint.height()) rect.setHeight(sizeHint.height());
editor->setGeometry(rect);
or just
editor->setGeometry(rect);
should work fine for you
2.You can also consider using popup editors for your rows\cells values
3.Resize widget's row heights to fit cell editors.
hope this helps, regards

Resources