Drawing a check mark in Qt - qt

I am working with simple QTreeView. Each row of tree is a specific class inherited from one class EditorRow.
EditorRow has these functions:
virtual QVariant data(ColumnIndexEnum index, int role = Qt::DisplayRole) const = 0;
virtual void setData(const QVariant& data, ColumnIndexEnum index, int role = Qt::UserRole);
virtual QWidget* getEditor(QWidget* parent) const;
Each row has its specific widget, which is shown in the right column when selecting that row. When the row is not selected data function returns appropriate value for each row(f.e. the value which was chosen in the QComboBox).
But in case of row, which's widget is QCheckBox, I need to draw a checked(or unchecked) checkbox when the row is not selected.
I have tried to use Decoration role like this:
if(Qt::DecorationRole == role)
{
if(ValueColumn == index)
{
QStyle* style = QApplication::style();
QStyleOptionButton opt;
opt.state |= QStyle::State_Enabled;
if(isChecked())
opt.state = QStyle::State_On;
else
opt.state = QStyle::State_Off;
const int indicatorWidth = style->pixelMetric(QStyle::PM_IndicatorWidth, &opt);
const int indicatorHeight = style->pixelMetric(QStyle::PM_IndicatorHeight, &opt);
const int listViewIconSize = indicatorWidth;
const int pixmapWidth = indicatorWidth;
const int pixmapHeight = qMax(indicatorHeight, listViewIconSize);
opt.rect = QRect(0, 0, indicatorWidth, indicatorHeight);
QPixmap pixmap = QPixmap(pixmapWidth, pixmapHeight);
pixmap.fill(Qt::transparent);
{
QPainter painter(&pixmap);
QCheckBox cb;
cb.setLayoutDirection(Qt::RightToLeft);
style->drawPrimitive(QStyle::PE_IndicatorCheckBox, &opt, &painter, &cb);
}
return QIcon(pixmap);
}
}
It actually works, but the icon is shown always, even when the row is selected.
I think it is because of DecorationRole.
Do You have any ideas how to handle this problem ?
Thank You.

Related

Mime type for custom data in tree view

The items in the tree view hold a instance of class container.
I want to implement drag and drop functionality in the view.
According to the QT tutorial for the data to copy i need specify the mime type and than write the Mimedata and dropMimeData functions.
The QT Example is dealing with a simple string so i am totally clueless of how to implement these function in case of custom objects.
1) What should be the mime type in my case ?
2) How to implement the current mimedata function for Container object data?
3) How to implement the current dropmimedata function for Container object data?
/////////////////////////////////////////
class Container
{
private:
std::string stdstrContainerName;
std::string stdstrPluginType;
int iSegments;
float fRadius;
public:
Container();
Container(std::string , std::string , int , float);
Container(const Container& obj);
~Container();
std::string GetName();
std::string GetType();
void SetName(std::string stdstrName);
};
Q_DECLARE_METATYPE( Container )
////////////////////////////////////////////////////////////
QMimeData *DragDropListModel::mimeData(const QModelIndexList &indexes)
const
{
QMimeData *mimeData = new QMimeData();
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
foreach (const QModelIndex &index, indexes) {
if (index.isValid()) {
QString text = data(index, Qt::DisplayRole).toString();
// I have a GetContainer function which returns the Container
//object and i can use the GetContainer instead of data function.
stream << text;
}
}
mimeData->setData("application/vnd.text.list", encodedData);
return mimeData;
}
//////////////////////////////////////////////////////////////////
bool DragDropListModel::dropMimeData(const QMimeData *data,
Qt::DropAction action, int row, int column, const QModelIndex
&parent)
{
if (action == Qt::IgnoreAction)
return true;
if (!data->hasFormat("application/vnd.text.list"))
return false;
if (column > 0)
return false;
int beginRow;
if (row != -1)
beginRow = row;
else if (parent.isValid())
beginRow = parent.row();
else
beginRow = rowCount(QModelIndex());
QByteArray encodedData = data->data("application/vnd.text.list");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QStringList newItems;
int rows = 0;
while (!stream.atEnd()) {
QString text;
stream >> text;
newItems << text;
++rows;
}
insertRows(beginRow, rows, QModelIndex());
foreach (const QString &text, newItems) {
QModelIndex idx = index(beginRow, 0, QModelIndex());
setData(idx, text);
beginRow++;
}
return true;
}
The header file for TreeItem.
class TreeItem
{
public:
explicit TreeItem( const Container &data , TreeItem *parent = 0 );
~TreeItem();
TreeItem *parent();
void appendChild(TreeItem *child);
TreeItem *child(int iNumber);
int childCount() const;
int childNumber() const;
Container data() const;
bool setData(const Container &data , QVariant value);
void setContainer(const Container &data);
bool insertChildren(int position, int count );
bool removeChildren( int position , int count );
private:
QList<TreeItem*> childItems;
Container itemData;
TreeItem* parentItem;
}
You can add your custom mime types to specify the type of container you want to drag/drop. See this post for details.
The QDrag object constructed by the source contains a list of MIME types that it uses to represent the data (ordered from most appropriate to least appropriate), and the drop target uses one of these to access the data.
First of all, try to find a compatible standard mime type. Those are the most common one assigned by the IANA.
If the one you are looking for is not in the list, then you can label your custom one and serialize your data into a QByteArray to share it.
QByteArray output;
// do whatever
mimeData->setData("my-awesome-mime-type", output);
Now, in your custom widget, don't forget to accept the drops of this mime type:
void Window::dragEnterEvent(QDragEnterEvent *event) {
if (event->mimeData()->hasFormat("my-awesome-mime-type"))
event->acceptProposedAction();
}
You can find a complete example in this project.

Using QT Tree Model with Custom data

This is my Header file for Container object.
class Container
{
private:
std::string stdstrContainerName;
std::string stdstrPluginType;
int iSegments;
float fRadius;
public:
Container();
Container(std::string , std::string , int , float);
Container(const Container& obj);
~Container();
std::string GetName();
std::string GetType();
void SetName(std::string stdstrName);
};
Q_DECLARE_METATYPE( Container )
I use Container as the datatype in TreeItem.
class TreeItem
{
public:
explicit TreeItem( const Container &data , TreeItem *parent = 0 );
~TreeItem();
TreeItem *parent();
TreeItem *child(int iNumber);
int childCount() const;
int childNumber() const;
Container data() const;
bool setData(const Container &data);
bool insertChildren(int position, int count );
bool removeChildren( int position , int count );
private:
QList<TreeItem*> childItems;
Container itemData;
TreeItem* parentItem;
};
The problem is that when i add rows to the Tree Structure the rows gets added but with no text on them.
I want to set the data in stdstrContainerName as the data on the row.
if i try to write something it does not stay.
i think the problem is with my functions which set the data to TreeItem object
Container TreeItem::data() const
{
return itemData;
}
bool TreeItem::setData(const Container &data , QVariant value )
{
itemData = data;
itemData.SetName(value.toString().toStdString() );
return true;
}
/
Data Function for Tree Model
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole && role != Qt::EditRole)
return QVariant();
TreeItem *item = getItem(index);
return QString::fromStdString(item->data().GetName());
}
bool TreeModel::setData(const QModelIndex &index, const QVariant &val, int
role )
{
if (role != Qt::EditRole)
return false;
Container c = val.value<Container>();
TreeItem *item = getItem(index);
bool result = item->setData(c);
if (result)
emit dataChanged(index, index, { role });
return result;
}
I can also post any additional code of the project.
What's missing here is the code of your view.
A QAbstractItemView derived view cannot display Container QVariants by itself.
Either you implement your view in such a way that it
interprets Container-QVariants and displays e.g. its GetName()
provides an editor that can manipulate Container-QVariants
or (simpler method) you shift the concretion layer into your model and
return item->GetName(), ->GetType() depending on role and index.column()
manipulate the Container instance in the TreeItem from the text passed in QVariant val

Qt - Triggering an event when selection of abstract item delegate changes

I am trying to make a tableview with a row which has a separate dropdown for each column. The user can only select a combination of values. That is, if the user selects "A" from the first drop down, the values in the other drop downs should be updated to that which can match "A".
I have made my AbsractItemDelegate class and the values are being assigned fine. But I am stuck up at how I can trigger an event when a value changes in one of the drop downs.
Thanks.
The following is my delegate class implementation:
FillComboBox::FillComboBox(QStringList the_list) : QItemDelegate() {
//list = new QStringList();
list = the_list; }
QWidget* FillComboBox::createEditor(QWidget* parent,
const QStyleOptionViewItem& /* option */,
const QModelIndex& /* index */) const {
QComboBox* editor = new QComboBox(parent);
editor->addItems(list);
editor->setCurrentIndex(2);
return editor; }
void FillComboBox::setEditorData(QWidget* editor,
const QModelIndex &index) const {
QString text = index.model()->data(index, Qt::EditRole).toString();
QComboBox* combo_box = dynamic_cast<QComboBox*>(editor);
combo_box->setCurrentIndex(combo_box->findText(text)); }
void FillComboBox::setModelData(QWidget* editor, QAbstractItemModel* model,
const QModelIndex &index) const {
QComboBox* combo_box = dynamic_cast<QComboBox*>(editor);
QString text = combo_box->currentText();
model->setData(index, text, Qt::EditRole); }
void FillComboBox::updateEditorGeometry(QWidget* editor,
const QStyleOptionViewItem &option, const QModelIndex &/* index */) const {
editor->setGeometry(option.rect); }
You can update the data of the "other" item as soon as the current item's data is updating, i.e. in FillComboBox::setModelData(). Please find the pseudo code that implements desired behavior (see comments):
void FillComboBox::setModelData(QWidget* editor, QAbstractItemModel* model,
const QModelIndex &index) const
{
QComboBox* combo_box = dynamic_cast<QComboBox*>(editor);
QString text = combo_box->currentText();
model->setData(index, text, Qt::EditRole);
// Find the model index of the item that should be changed and its data too
int otherRow = ...; // find the row of the "other" item
int otherColumn = ...; // find the column of the "other" item
QModelIndex otherIndex = model->index(otherRow, otherColumn);
QString newText = text + "_new";
// Update other item too
model->setData(otherIndex, newText, Qt::EditRole);
}

Changing selection behavior of a QTableView

I am using QTableView in Qt and I have one table in which each cell has different text color. I have selection behavior select entire row. But when I select any row, text color changes to white for the selected row. I don't want to change text color while row is selected. I want original color to be displayed when I select any row.
I tried to use stylesheet but it is also changing entire row text color.
I am posting here sample code
QTableView * pQTableView = new QTableView();
QStandardItemModel *model = new QStandardItemModel(5,3);
pQTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
pQTableView->setModel(model);
for(int row = 0;row < 5;row++)
for(int column = 0; column < 3; column++)
{
QModelIndex index1= model->index(row,column);
QVariant value("Swaminarayan");
model->setData(index1, value,Qt::DisplayRole );
}
QModelIndex index1= model->index(0,0);
QVariant Obj(Qt::green);
model->setData(index1,Obj,Qt::TextColorRole );
index1= model->index(0,1);
QVariant Obj1(Qt::red);
model->setData(index1, Obj1,Qt::TextColorRole );
pQTableView->show();
Here you can see color of first cell is green and second sell is red once we select first row color changes to white.
You could also just set the selection mode to NoSelection for the TableView.
You could then use the itemClicked() signal to get the item index and set its color as you wish for each item in the row. Something like this:
connect( myTableView,
SIGNAL( clicked( const QModelIndex &) ),
this,
SLOT( onItemClicked(const QModelIndex &) ) ) ;
//....
void DataModel::onItemClicked(const QModelIndex &index)
{
//get the clicked item
QStandardItem *clickedItem = myDataModel->itemFromIndex(index);
// get the row
int selectedRow = clickedItem->row();
// for each col change the color as you want
for(int c = 0, colCount = myDataModel->columnCount(); c < colCount; ++c)
{
QStandardItem *itemToChange = myDataModel->item( selectedRow, c);
QBrush brush;
brush.setColor(Qt::red);
itemToChange ->setData(brush, Qt::ForegroundRole);
}
}
It seems you should implement your own table view based on QTableView and reload data() function:
QVariant YourTableViewClass::data(const QModelIndex &index, int role) const
{
if(!index.isValid()) {
return QVariant();
}
/* any other checks here */
switch(role) {
case Qt::BackgroundRole:
return QColor(/* background colour here */);
case Qt::ForegroundRole:
return QColor(/* foreground colour here */);
case Qt::DisplayRole:
/* any other actions here */
default:
break;
} /* switch(role) */
return QVariant();
}
For Qt::BackgroundRole and Qt::ForegroundRole you can implement your colours.
Please see http://qt-project.org/doc/qt-5.0/qtcore/qabstracttablemodel.html for QAbstractTableModel for reference. Hope it helps.

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