Drag & Drop of hidden QStandardItemModel columns in QTreeView - c++

Within a QTreeView, I would like to copy rows around by drag and drop. The corresponding Drag & drop settings look like:
this->setDragDropMode( QAbstractItemView::DragDrop );
this->setDropIndicatorShown( true );
This works fine unsing for the columns of the underlying QStandardItemModel which are visualised by the QTreeView. But not all columns of the model are visualised (see Hide future columns of QStandardItemModel in QTreeView):
void MyViewClass::columnCountChanged(int p_nOldCount , int p_nNewCount )
{
QTreeView::columnCountChanged( p_nOldCount, p_nNewCount );
for ( int i = MyViewClass::m_nColumnType; i < p_nNewCount; ++i )
{
setColumnHidden( i, true );
}
}
How can I copy the whole row of a QStandardItemModel by drag and drop in the QTreeView when not all columns are visualised by the QTreeView?

Found the solution:
One has to inherit / implement the QAbstractModel functions:
virtual QMimeData * mimeData(const QModelIndexList &indexes) const;
virtual bool dropMimeData(const QMimeData *p_grData, Qt::DropAction p_grAction, int p_nRow, int p_nColumn, const QModelIndex &p_grParentIdx);
virtual QStringList mimeTypes() const;
while mimeData needs to encode the data and dropMimeData needs to decode the data and needs to insert a new row / column with the draged data.

Related

Change what column of QTreeView displays expand/collapse icon

Qt version 4.8.4
I have a QTreeView that reflects a QAbstractItemModel-derived model. The model provides data in multiple columns. The expand/collapse icon for a row of the view is always displayed in the cell that is in the column that has a logical index of zero, even if that column has been moved so that it has a visual index other than zero.
Is QTreeView compelled to always draw the expand/collapse icon in logical column zero? Can that be customized, either via the QTreeView or the model?
The expand/collapse icon, along with all the other tree branching lines, is indeed compelled to always draw in logical column zero. In Qt/4.8.4/src/gui/itemviews/qtreeview.cpp, in the QTreeView::drawRow() method, it is hardcoded to only call QTreeView::drawBranches() if the headerSection (which contains the logical index of the column) is zero.
void QTreeView::drawRow( QPainter * painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
...
for ( int currentLogicalSection = 0; currentLogicalSection < logicalIndices.count(); ++currentLogicalSection )
{
int headerSection = logicalIndices.at(currentLogicalSection);
...
// If the zeroeth logical column, ...
if ( headerSection == 0 )
{
...
// ... draw tree branches and stuff.
drawBranches(painter, branches, index);
}
else
{
...
}
...
}
...
}

How to animate the color of a QTableview cell in time once it's value gets updated?

I want to animate the color (in time) of a QTableview cell once it's value is updated via the connected data model to attract the end-users attention that something has changed.
The idea is that the color changes in gradients of f.i. blue, starts off blue just after the value change and fades to white in about 1 ~ 2 seconds.
I guess one has to use the QStyledItemDelegate here since I use the model-view concept (http://doc.qt.io/qt-5/model-view-programming.html).
One needs a trigger for the cell's value-change for the animation to start, this can be achieved via the paint() method since it is called on a value change. One can figure out the row and column, from the index parameter that is passed to paint(), to get a hold of which cell to animate.
So far so good, you can set the color of that cell to let's say blue.
Here comes the problem; the color shall fade towards white in time steps. So I was thinking about a QTimer in the QStyledItemDelegate class and maintain a kinda bookkeeping for the cell's that are being animated (could be a simple list of countdown values that are used to calculate the blue-gradient color. The lower the value the more the gradient goes towards white, once 0 the result is white which is the default color of the cell. On each QTimer timeout() event all values not equal to 0 are lowered by 1. The QStyledItemDelegate is only connected to the row of the QTableview that I want to color-animate i.e. where the value items are displayed.
Problem I face is that:
paint() is a const method so you cannot change any class
parameters.
how to re-paint the cell color on a QTimer event
(re-paint the whole QTableview is not god-style)
I managed to get it to work but I consider it a dirty solution. What I did is maintain the bookkeeping of the color animation for each cell in the data-model. I consider it a dirty solution because the color-animation is only a visual aspect so imho it should not reside in the data-model. In this way it is also not a portable solution i.e. in another project you have to rework a lot to get it to work.
I stripped down my application to the core problem. Full code can be found here (a working application): https://github.com/fruitCoder123/animated_tableview_cell
void TableViewDelegateValueWritable::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
// Paint background
uint8_t red_gradient = calculate_color_gradient(RGB_RED_MAX, RGB_RED_MIN, red_gradient_step_size, m_step_value);
uint8_t green_gradient = calculate_color_gradient(RGB_GREEN_MAX, RGB_GREEN_MIN, green_gradient_step_size, m_step_value);
painter->fillRect(option.rect, QColor(red_gradient, green_gradient, 255));
// Paint text
QStyledItemDelegate::paint(painter, option, index);
}
uint8_t TableViewDelegateValueWritable::calculate_color_gradient(const uint8_t MAX_COLOR, const uint8_t MIN_COLOR, const uint8_t step_size, uint8_t step) const
{
uint16_t color = (step_size * (1 + MAX_COLOR_GRADIENT_STEP - step)) + MIN_COLOR;
// Handle overflow and rounding errors
if(color > MAX_COLOR || color > (MAX_COLOR-(step_size/2)))
color = MAX_COLOR;
return static_cast<uint8_t>(color);
}
void TableViewDelegateValueWritable::gradient_timer_elapsed()
{
if(m_step_value)
{
m_step_value--;
m_timer->start(GRADIENT_TIMEOUT_VALUE);
//this->paint(m_painter, m_option, m_model_index);
}
}
I spent a horrific amount of hours to find a nice solution. I started with Qt a month ago so maybe I lack knowledge. Hopefully someone can give a hint how to solve it in a nice way - encapsulated in the view and not entangled with the data-model.
For the stated problems :
paint() is declared as const, you can use a mutable variable member and modify it whenever you want. For example :
class TableViewDelegateValueWritable : public QStyledItemDelegate
{
Q_OBJECT
mutable QTableView * m_view;
public:
explicit TableViewDelegateValueWritable(QTableView * view, QObject *parent){
m_view = view;
//...
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
//...
int nFrame m_view->getFrameOfIndex(index);
drawFrame (painter, nFrame );
m_view->setFrameOfIndex(index, ++nFrame);
//...
}
//class body
}
Repaint the whole QTableView is not a god-style but repaint only the QTableView's viewport is a good option, in the MainWindow constructor :
m_db->insert_record(QString("my_val_1"), "0");
m_db->insert_record(QString("my_val_2"), "0");
m_db->insert_record(QString("my_val_3"), "0");
QTimer * timer = new QTimer( this );
connect( timer, &QTimer::timeout, this, [this](){
ui->tableView->viewport()->repaint();
});
timer->start( TIME_RESOLUTION ); //set to 1000ms
Here is a snippet to animate a cell when it was modified, the color will be changed each 1s, the method used is to subclass QSqlTableModel to track the modified cell (via dataChanged signal) :
enum MyDataRole {
ItemModifiedRole = Qt::UserRole + 1
};
class MySqlTableModel : public QSqlTableModel {
Q_OBJECT
QMap<int, QVariant > mapTimeout;
public:
MySqlTableModel( QObject *parent = Q_NULLPTR, QSqlDatabase db = QSqlDatabase() )
:QSqlTableModel( parent, db ) {
connect( this, &QSqlTableModel::dataChanged,
this, [this]( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles )
{
for(int i = topLeft.row(); i <= bottomRight.row(); i ++ ){
mapTimeout.insert( i , QDateTime::currentDateTime() );
}
} );
}
//this data function will be called in the delegate paint() function.
QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE
{
if( role != ItemModifiedRole )
return QSqlTableModel::data( idx, role );
QMap<int, QVariant>::const_iterator it = mapTimeout.find( idx.row() );
return it == mapTimeout.end() ? QVariant() : it.value();
}
void clearEffects() {
mapTimeout.clear();
}
};
And the delegate paint() function:
// background color manipulation
void TableViewDelegateValueWritable::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QSqlTableModel * db_model = (QSqlTableModel *)index.model();
QVariant v = db_model->data( index, ItemModifiedRole );
if( !v.isNull() ){
QDateTime dt = v.toDateTime();
int nTimePassed = dt.secsTo( QDateTime::currentDateTime() );
int step_value = nTimePassed + 2;
uint8_t red_gradient = calculate_color_gradient( RGB_RED_MAX, RGB_RED_MIN, red_gradient_step_size, step_value );
uint8_t green_gradient = calculate_color_gradient( RGB_GREEN_MAX, RGB_GREEN_MIN, red_gradient_step_size, step_value );
painter->fillRect( option.rect, QColor( red_gradient, green_gradient, 255) );
}
// Paint text
QStyledItemDelegate::paint(painter, option, index);
}
You should use QSqlRecord way if possible instead of executing a sql statement. In fact, after each sql statement was executed, you call the select() function, this will reset the whole table model. Here is an example of insert/update record :
void dbase::insert_record(const QString &signal_name, const QString &value)
{
QSqlRecord r = db_model->record();
r.setValue( "signal_name", signal_name );
r.setValue( "signal_value", value );
db_model->insertRecord(-1, r );
}
void dbase::update_record(const QString &signal_name, const QString &new_value)
{
for(int row = 0; row < db_model->rowCount(); row ++ ){
QSqlRecord r = db_model->record( row );
if( r.value("signal_name").toString() == signal_name ){
r.setValue("signal_value", new_value );
db_model->setRecord( row, r );
break;
}
}
}

How to add QPushButton in QTableView when loading database by C++

I'm working on a chat application. In the chat view, where I show the chat messages using a QTableView, I want to add a QPushButton next to each message. Example:
A: How are you ? --- Button
B: I am fine --- Button
But I want to add only 10 rows. When scrolling, the data will change in the 10 rows, but I don't want to create new rows. And I want to know how to put the QPushButtons into the QTableView. How can I do this?
And I want to know how to put the QPushButtons into the QTableView
You can achieve this using setIndexWidget ( const QModelIndex & index, QWidget * widget ) member function. Something like:
QTableView * table = new QTableView (this);
...
int column = 1;
for (int row = 0; row < 10; ++row) {
QPushButton * button = new QPushButton (tr("Button Name"), table);
table->setIndexWidget (model->index (row, column, QModelIndex () ), button);
}

Autofill to correct size in Qt Tables

I am trying to learn Qt by doing some project, and would like quick pointer on one part of my requirement.
I have database with multi-line passages, that I want to show in Qt using some view.
What I additionally want is that user does not have to re-size the window in order to read, so, if big passage comes in, then size shrinks, and will small passage, the font increase such that it takes the total space to display.
Kindly suggest :
what logic or functionality will suit to shrink and expand size, or is there a widget/view that already do so(by modifying the property) or suggestion on how to achieve it.
Same question again, to show shrink/expanded things, but using only using tree view. Then can I do this in tree view? And how?
Here is a way you could do it with a custom QItemDelegate.
Note that this solution isn't complete, you still need to do some work here.
First, some code to setup a QStandardItemModel and QTreeView.
The View uses a custom Delegate, which is described below.
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QStandardItemModel model(4, 4);
for (int row = 0; row < 4; ++row) {
for (int column = 0; column < 4; ++column) {
QStandardItem *item = new QStandardItem(QString("row %0, column %1").arg(row).arg(column));
model.setItem(row, column, item);
}
}
Delegate delegate;
QTreeView view;
view.setItemDelegate(&delegate);
view.setModel(&model);
view.setWindowTitle("QTreeView with custom delegate");
view.show();
return a.exec();
}
Here comes the code for the Delegate.
It looks how much space is available for the text, and then tries to find a font size that fits.
I'm currently only checking the width and ignoring height.
class Delegate : public QItemDelegate
{
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
void Delegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QRect availableSpace = option.rect;
// padding taking place, don't know how to find out how many pixels are padded.
// for me, subtracting 6 looked ok, but that's not a good solution
availableSpace.setWidth(availableSpace.width() - 6);
QString text = index.data().toString();
QStyleOptionViewItem newOption(option);
// initial font size guess
float size = 20;
int width;
// try to make font smaller until the text fits
do
{
newOption.font.setPointSizeF(size);
size -= .1;
width = QFontMetrics(newOption.font).width(text);
}
while (width > availableSpace.width());
newOption.textElideMode = Qt::ElideNone;
// call the parent paint method with the new font size
QItemDelegate::paint(painter, newOption, index);
}
This is what the result looks like:

Selecting an index in a QListView

This might be a stupid question, but I can't for the life of me figure out how to select the row of a given index in a QListView.
QAbstractItemView , QListView's parent has a setCurrentIndex(const QModelIndex &index). The problem is, I can't construct a QModelIndex with the row number I want since the row and column field of the QModelIndex has no mutators.
QTableView, which also inherits from QAbstractItemView has a selectRow(int row) function, why in the seven hells doesn't the QListView have this?
Good ol' windows forms has the SelectedIndex property on it's listviews.
This should help you get started
QModelIndex index = model->createIndex( row, column );
if ( index.isValid() )
model->selectionModel()->select( index, QItemSelectionModel::Select );
You construct the QModelIndex by using the createIndex(int row, int column) function of the model you gave to the view. QModelIndexes should only be used once, and must be created by the factory in the model.
My working sample at Qt4.8.0 (MSVC2010 Compiller) based on Michael Bishop
QStandardItemModel *Model = (QStandardItemModel *)this->ui->listView_OptionsCategories->model();
QModelIndex index = Model->index(this->ui->stackedWidget->currentIndex(), 0);
if ( index.isValid() )
this->ui->listView_OptionsCategories->selectionModel()->select( index, QItemSelectionModel::Select );
For Qt 6.3.x:
void selectRowInQListView(int row, QListView *listView) {
QModelIndex index = listView->model()->index(row, 0);
if (index.isValid()) {
//listView->selectionModel()->select(index, QItemSelectionModel::Select);
//listView->selectionModel()->select(index, QItemSelectionModel::Current);
listView->setCurrentIndex(index);
}
}