7

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) ;
TonyK
  • 16,761
  • 4
  • 37
  • 72

1 Answers1

4

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.

pnezis
  • 12,023
  • 2
  • 38
  • 38
  • 1
    I tried this out, and that thing with `PM_IndicatorWidth` works just fine. Thanks for that! Also, `iconSize()` returns (-1,-1), but if I call `setIconSize()` first it works. That looks circular, but in fact it solved my problem with the icons being smaller than I wanted. – TonyK Nov 25 '11 at 23:42
  • 1
    After some testing, I discovered that this solution doesn't work either -- it ignores the margins around the check box and the icon. To do it properly, I had to sub-class QItemDelegate and provide a version of sizeHint that returns the three parts (check box, icon, text) separately. But you set me on the right track. – TonyK Nov 26 '11 at 18:47
  • Could you post some working code, as edit in your question, for future reference? – pnezis Nov 26 '11 at 18:58
  • Done. Well, not working code, exactly -- I had to prune out too much. – TonyK Nov 26 '11 at 20:05
  • @TonyK Looking at the source code for QItemDelegate, you an get the margin with `style()->pixelMetric( QStyle::PM_FocusFrameHMargin ) + 1`. The magic +1 is not very self-explanatory though, but it works relatively well although +2 seems to work even better for some reason I don't understand but that's hair splitting. – mxmlnkn Mar 11 '20 at 14:37