Qt: QTreeView: center icon on header - qt

For some columns from a QTreeView widget, I use an icon. The icon is set on
QVariant headerData (int section, Qt::Orientation orientation, int role) const{
if(role == Qt::DecorationRole)
{
QIcon icon;
if (section == 0) {
icon.addFile(":/icon1");
} else if (section == 1){
icon.addFile(":/icon2");
}
}
if(role == Qt::TextAlignmentRole)
{
return (Qt::AlignLeft + Qt::AlignVCenter);
}
The header looks like this:
I want to align the icons with the text. TextAlignmentRole only works for text, but not for the icons. How can I do that?
I also tried by setting the default alignment:
m_treeview->header()->setDefaultAlignment(Qt::AlignCenter); but no luck.

In order to center icon with text you will have to implement your own proxy style to create this specific style behavior.
#include <QProxyStyle>
#include <QPainter>
class HeaderProxyStyle : public QProxyStyle
{
public:
void drawControl(ControlElement oCtrElement, const QStyleOption * poStylrOptionption, QPainter * poPainter, const QWidget * poWidget = 0) const;
};
Center icon with text implementation
void HeaderProxyStyle::drawControl(ControlElement oCtrElement, const QStyleOption *poStylrOptionption, QPainter *poPainter, const QWidget *poWidget) const
{
// Header label?
if (oCtrElement == CE_HeaderLabel) {
// YES - Allocate style option header
QStyleOptionHeader *poStyleOptionHeader =
(QStyleOptionHeader *) poStylrOptionption;
// Get header icon
QIcon oIcon = qvariant_cast<QIcon>(poStyleOptionHeader->icon);
// Icon is valid?
if(oIcon.isNull()){
// No - Draw text header
QProxyStyle::drawControl(oCtrElement, poStylrOptionption, poPainter, poWidget);
return;
}
// Set icon size 16x16
QSize oIconSize = QSize(16,16);
// Get header section rect
QRect oRect = poStyleOptionHeader->rect;
// Create header icon pixmap
QPixmap oIconPixmap = oIcon.pixmap(oIconSize.width(),oIconSize.height());
// Calculate header text width
int iTextWidth = poStyleOptionHeader->fontMetrics.width(poStyleOptionHeader->text);
QRect oCenterRec = QRect(oRect.left(),
oRect.top() + (oRect.height - iTextSize)/2,
oIconPixmap.width(),oIconPixmap.height());
QRect oTextRect = QRect(oCenterRec.left()+ oIconSize.width(),
oCenterRec.top(), oCenterRec.width() + iTextWidth, oCenterRec.height());
// Draw icon
poPainter->drawPixmap(oCenterRec, oIconPixmap);
// Draw text
poPainter->drawText(oTextRect, poStyleOptionHeader->text);
return;
}
QProxyStyle::drawControl(oCtrElement, poStylrOptionption, poPainter, poWidget);
}
Then apply this header style in your tree view
// Set header style
m_treeview->header()->setStyle(&m_oHeaderStyle);

Related

Qt: QHeaderView place sort-indicator on the right of the header text

If I set: QHeaderView::down-arrow { subcontrol-position: center left}, the down-arrow is on the left of the column, and if I set center right, it is placed on the right of the column, but I want to place the arrow next to the title (on the right side).
You need to set subcontrol-origin: margin | border | padding | content;
Look into below documentation link to understand box model ( which explains the margin rectangle, the border rectangle, the padding rectangle, and the content rectangle).
http://doc.qt.io/qt-5/stylesheet-customizing.html#the-box-model
So try adding subcontrol-origin:padding to your code, which may add next to your content.
Try something like below:
QHeaderView::down-arrow { subcontrol-origin:padding; subcontrol-position: center right;}
The way I sorted was by creating a QProxyStyle for the header, and override drawControl.
I also assigned an empty icon to hide the default one.
treeviewwidget.cpp at initialize():
treeviewHeaderProxy* m_oHeaderStyle = new treeviewHeaderProxy();
treeview->header()->setStyle(m_oHeaderStyle);
treeview->header()->setDefaultAlignment(Qt::AlignCenter);
treeview->header()->setStyleSheet("QHeaderView::down-arrow { image: url(:/shared/empty); }"
"QHeaderView::up-arrow { image: url(:/shared/empty); } ");
treeviewHeaderProxy.h:
class treeviewHeaderProxy : public QProxyStyle
{
public:
explicit treeviewHeaderProxy();
void drawControl(ControlElement oCtrElement, const QStyleOption * poStylrOptionption, QPainter * poPainter, const QWidget * poWidget = 0) const;
};
treeviewHeaderProxy.cpp:
void treeviewHeaderProxy::drawControl(ControlElement oCtrElement, const QStyleOption *poStyleOptionption, QPainter *poPainter, const QWidget *poWidget) const
{
// Header label?
if (oCtrElement == CE_HeaderLabel) {
QStyleOptionHeader *poStyleOptionHeader = (QStyleOptionHeader *) poStyleOptionption;
QStyleOptionHeader::SortIndicator sortOption = poStyleOptionHeader->sortIndicator;
QRect oRect = poStyleOptionHeader->rect;
// Text
int iTextWidth = poStyleOptionHeader->fontMetrics.width(poStyleOptionHeader->text);
int iTextHeight = poStyleOptionHeader->fontMetrics.height();
QRect oTextRect = QRect(oRect.left() + oRect.width(), catRect.top() + (oRect.height() - iTextHeight)/2,
iTextWidth*1.2, iTextHeight);
poPainter->setPen(SUPER_DARK_GREY);
poPainter->drawText(oTextRect, poStyleOptionHeader->text); // Draw text
// Sort Indicator
QPixmap oSortPixmap;
switch(sortOption){
case QStyleOptionHeader::SortDown:
oSortPixmap = QIcon(":/shared/drop_up_grey").pixmap(10,10);
break;
case QStyleOptionHeader::SortUp:
oSortPixmap = QIcon(":/shared/drop_down_grey").pixmap(10,10);
break;
}
if(!oSortPixmap.isNull()){
QRect oSortRect = QRect(oTextRect.left() + oTextRect.width(), oRect.top() + (oRect.height() - oSortPixmap.height())/2,
oSortPixmap.width(), oSortPixmap.height());
poPainter->drawPixmap(oSortRect, oSortPixmap); // Draw sortIcon
}
return;
}
QProxyStyle::drawControl(oCtrElement, poStyleOptionption, poPainter, poWidget);
}

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.

QT Custom QStyle for QTabBar and QIcon

I have customize a Qtabbar's QMdiarea to get horizontal tab instead of vertical tab like this :
http://www.qtcentre.org/wiki/index.php?title=Customizing_QTabWidget%27s_QTabBar
But i want to insert icon in my QTabBar.
If I apply my custom style to my QTabBar my icons doesn't appear.
If I don't apply, my icon appear.
here my custom style :
class CustomTabStyle : public QPlastiqueStyle
{
Q_OBJECT
public:
QSize sizeFromContents(ContentsType type, const QStyleOption *option,const QSize &size, const QWidget *widget) const
{
QSize s = QPlastiqueStyle::sizeFromContents(type, option, size, widget);
if (type == QStyle::CT_TabBarTab)
s.transpose();
return s;
}
void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
{
if (element == CE_TabBarTabLabel)
{
if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option))
{
QStyleOptionTab opt(*tab);
opt.shape = QTabBar::RoundedNorth;
QPlastiqueStyle::drawControl(element, &opt, painter, widget);
return;
}
}
QPlastiqueStyle::drawControl(element, option, painter, widget);
}
};
and i apply my style like this :
mMdiAreaDock=aMdiArea;
m_pMdiAreaTabBar = NULL;
QObjectList listChildren = mMdiAreaDock->children();
for (QObjectList::Iterator i = listChildren.begin(); i != listChildren.end(); ++i)
{
if (QString((*i)->metaObject()->className()) == "QTabBar")
{
m_pMdiAreaTabBar = dynamic_cast<QTabBar*>(*i);
break;
}
}
m_pMdiAreaTabBar->setStyle(new CustomTabStyle());
return 0;
where mMdiAreaDock is an QMdiArea
and m_pMdiAreaTabBar is a QTabBar
Look at this code:
const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option));
QStyleOptionTab opt(*tab);
option is an instance of QStyleOptionTabV2.
When you create a new object QStyleOptionTab opt with copy constructor, you lose some important data which extended QStyleOptionTabV2 contains including information about an icon.
Use this code instead:
if (const QStyleOptionTabV2 *tab = qstyleoption_cast<const QStyleOptionTabV2 *>(option))
{
QStyleOptionTabV2 opt(*tab);
opt.shape = QTabBar::RoundedNorth;
QPlastiqueStyle::drawControl(element, &opt, painter, widget);
return;
}
ps. I used this code to assign an icon for a specific tab:
m_pMdiAreaTabBar->setTabIcon(0, icon);

How to indent the title of a QDockWidget using my own style

I want to indent the title of a QDockWidget w/o adding spaces.
#include <QStyle>
#include <QProxyStyle>
#include <iostream>
class my_style : public QProxyStyle
{
Q_OBJECT
public:
my_style (QStyle* style = 0) :
QProxyStyle (style)
{
}
virtual ~my_style ()
{
}
virtual QRect subElementRect (SubElement element, const QStyleOption * option, const QWidget * widget = 0) const
{
QRect rect = QProxyStyle::subElementRect (element, option, widget);
if (element == QStyle::SE_DockWidgetTitleBarText)
{
rect.adjust (50, 0, 0, 0);
}
//std::cerr << "debug:" << element << std::endl;
return rect;
}
};
I have no clue why but when I apply my style it's never running into that if. If I debug the method I only get an output for two different elements which are the buttons in the title bar.
subElementRect is not called to get the title area for all styles. At least, XP, Vista and MacOSX styles are using directly QStyleOption::rect which is passed as parameter to the drawControl function for CE_DockWidgetTitle.
To handle both cases, you should also reimplement drawControl:
void drawControl(ControlElement element, const QStyleOption *option,
QPainter *painter, const QWidget *widget) const
{
const QStyleOptionDockWidget *dockWidget;
if(element == CE_DockWidgetTitle &&
(dockWidget = qstyleoption_cast<const QStyleOptionDockWidget *>(option)))
{
QStyleOptionDockWidget copy = *dockWidget;
copy.rect.adjust(50,0,0,0);
// or you can add spaces in the title to avoid the border moving left too
// copy.title = QString(50 / copy.fontMetrics.width(" "), QChar(' ')) + copy.title;
QProxyStyle::drawControl(element, &copy, painter, widget);
return;
}
QProxyStyle::drawControl(element, option, painter, widget);
}
Alternatively you could use a style sheet, with a padding or a margin:
dockWidget->setStyleSheet("::title { position: relative; padding-left: 50px;"
" text-align: left center }");
The "position" rule does nothing, but is necessary, because strangely the style is only applied if some other rule categories are also present.
The text needs to be vertically realigned too because the alignment seem to be lost when using a style sheet.

Position of icon in QTreeWidgetItem

My QTreeWidget has a single column. Its items have a check box, an icon, and text. If the user clicks inside an item, I want to know whether the icon was clicked. How can I find the position and size of the icon in a QTreeWidgetItem?
Updated to add: Here is the code for my eventual solution, as requested by webclectic.
First, I sub-classed QItemDelegate so that I could access the coordinates of each part of a QTreeWidgetItem (check box, icon, and text). Here is the header file:
#include <QItemDelegate>
class MyItemDelegate : public QItemDelegate
{
Q_OBJECT
public:
explicit MyItemDelegate (MyTreeWidget *parent)
: QItemDelegate (parent), ParentView (parent) { }
~MyItemDelegate() { }
void GetRects (const QModelIndex &index, QRect& CheckBox, QRect& Icon, QRect& Text) const ;
private:
MyTreeWidget* ParentView ;
} ;
And here is the source file:
void MyItemDelegate::GetRects (const QModelIndex &index, QRect& CheckBox, QRect& Icon, QRect& Text) const
{
QStyleOptionViewItem option = ParentView -> viewOptions() ;
CheckBox = rect (option, index, Qt::CheckStateRole) ;
Icon = rect (option, index, Qt::DecorationRole) ;
Text = rect (option, index, Qt::DisplayRole) ;
doLayout (option, &CheckBox, &Icon, &Text, true) ;
QRect VisualRect = ParentView -> visualRect (index) ;
CheckBox.translate (VisualRect.topLeft()) ;
Icon.translate (VisualRect.topLeft()) ;
Text.translate (VisualRect.topLeft()) ;
}
Then I added a MyItemDelegate* member to MyTreeWidget, and set it as the item view's delegate. In the header:
class MyTreeWidget : public QTreeWidget
{
...
MyItemDelegate* Delegate ;
...
} ;
In the source :
MyTreeWidget::MyTreeWidget (QObject* parent)
{
...
Delegate = new MyItemDelegate (this) ;
setItemDelegate (ItemDelegate) ;
}
Now, to get the coordinates of each part of a QTreeWidgetItem:
QTreeWidgetItem* item ;
...
QModelIndex ModelIndex = indexFromItem (item) ;
QRect CheckBoxRect, IconRect, TextRect ;
ItemDelegate -> GetRects (ModelIndex, &CheckBoxRect, &IconRect, &TextRect) ;
Unfortunately there is no simple way to achieve what you want. The problem is that QTreeWidget is responsible for painting its items so the item itself has no information about the position of its elements in the view.
First of all you have to subclass QTreeWidget and reimplement the mousePressEvent (or mouseReleaseEvent if you prefer). Inside the event you should calculate the position of the icon and handle it correspondingly.
Sample code (but untested) follows:
void mousePressEvent(QMouseEvent *event)
{
QModelIndex clickedIndex = indexAt(event->pos());
// make sure the event was on a valid item
if (clickedIndex.isValid() == false)
return;
// Get the tree widget's x position
int treeX = header()->sectionViewportPosition(0);
// Get the x coordinate of the root item. It is required in order to calculate
// the identation of the item
int rootX = visualRect(rootIndex()).x();
// Get the rectangle of the viewport occupied by the pressed item
QRect vrect = visualRect(clickedIndex);
// Now we can easily calculate the x coordinate of the item
int itemX = treeX + vrect.x() - rootX;
// The item is a checkbox, then an icon and finally the text.
// 1. Get the rect surrounding the checkbox
QRect checkboxRect = QRect(itemX,
vrect.y(),
style()->pixelMetric(QStyle::PM_IndicatorWidth),
vrect.height());
// 2. Get the rect surrounding the icon
QRect iconRect = QRect(itemX + checkboxRect.width(),
vrect.y(),
iconSize().width(),
vrect.height());
// 3. Finally get the rect surrounding the text
QRect textRect = QRect(itemX + checkboxRect.width() + iconRect.width(),
vrect.y(),
vrect.width() - checkboxRect.width() - iconRect.width(),
vrect.height());
// Now check where the press event took place and handle it correspondingly
if(checkboxRect.contains(event->pos()))
{
qDebug() << "Checkbox pressed";
QTreeWidget::mousePressEvent(event);
return;
}
else if (iconRect.contains(event->pos()))
{
qDebug() << "Icon pressed";
QTreeWidget::mousePressEvent(event);
return;
}
else
{
qDebug() << "Text pressed";
QTreeWidget::mousePressEvent(event);
return;
}
}
I repeat that the code is untested but you get the idea about how to achieve what you want.

Resources