QStandardItemModel with QListView, external drop action does not work - qt

I'm using Qt 4.2.
I've a QMainWindow with a QListView inside which uses a QStandardItemModel for showing some items that I get from .desktop files.
Now I'm trying to implement a drop action over the app so for ex: I can run firefox when a .html file is droped over the firefox item.
So this is what I've done:
-for the listView:
viewport()->setAcceptDrops(true);
setAcceptDrops(true);
setDragEnabled(true);
setDropIndicatorShown(true);
setDragDropMode(QListView::DragDrop);
-for the standardItemModel:
Qt::DropActions supportedDropActions() const {
return Qt::CopyAction | Qt::MoveAction;
}
Qt::ItemFlags flags(const QModelIndex &index) const {
return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled |
Qt::ItemIsDropEnabled | Qt::ItemIsEnabled;
}
QStringList mimeTypes() const {
QStringList types;
types<<"text/uri-list";
return types;
}
bool dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent) {
qDebug() << "dropMimeData";
}
After that, I never get the dropMimeData debug message when I drop some file over the app.

You also need to reimplement dragEnterEvent() and dropEvent() - they are virtual functions in QListView.
From the documentation:
Dropping
To be able to receive media dropped on a widget, call
setAcceptDrops(true) for the widget, and reimplement the
dragEnterEvent() and dropEvent() event handler functions.
For example,
the following code enables drop events in the constructor of a QWidget
subclass, making it possible to usefully implement drop event
handlers:
void Window::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasFormat("text/plain"))
event->acceptProposedAction();
}
void Window::dropEvent(QDropEvent *event)
{
textBrowser->setPlainText(event->mimeData()->text());
mimeTypeCombo->clear();
mimeTypeCombo->addItems(event->mimeData()->formats());
event->acceptProposedAction();
}

Related

Dragging from an inherited QTableView

I have in my app a table view called "partsview" which is descended from a QTableView, this is on the main form. The relevant header code looks like:
class partsiew : public QTableView
{
Q_OBJECT
public:
explicit partsview(QWidget *parent = 0);
public:
QStringList mimeTypes() const;
QMimeData *mimeData(const QModelIndexList &indexes) const;
};
I have also added the following into the constructor of "partsview":
this->setSelectionMode(QAbstractItemView::ExtendedSelection);
this->setDragEnabled(true);
this->setAcceptDrops(true);
this->setDropIndicatorShown(true);
though the last two are probably not needed.
When dragging, I can pick up a row and drag it to a target - a QTreeView - and I get the appropriate cursor and even a dropMimeData event is fired on the treeview.
However, the row and column values in the dropMimeData() method are always -1 and the two methods in the "partsview" are not called.
I'm guessing that the first error may have something to do with the second. Have I declared the mimeType() and mimeData() methods correctly for them to be called during a drag operation.
I have been following this page in the documentation QT4.6 - Using Model/View Classes
Here is the link for the most recent documentation on it:
http://doc.qt.io/qt-5/model-view-programming.html#using-drag-and-drop-with-item-views
I recently worked on some code for doing an internal move of cells on a single table, but while I was researching it, I found some other useful links.
http://qt-project.org/forums/viewthread/14197
I used the code in the above link and translated it to use QTableView instead of QTableWidget:
void MyTableView::dropEvent(QDropEvent *event)
{
// TODO: switch to using EyeTrackerModel::dropMimeData instead
QPoint old_coordinates = QPoint(-1,-1);
int dropAction = event->dropAction();
if(currentIndex().isValid()) //Check if user is not accessing empty cell
{
old_coordinates = QPoint( currentIndex().row(), currentIndex().column() );
}
QTableView::dropEvent(event);
qDebug() << "Detected drop event...";
event->acceptProposedAction();
if( this->indexAt(event->pos()).isValid() && old_coordinates != QPoint(-1, -1))
{
qDebug() << "Drop Event Accepted.";
qDebug() << "Source: " << old_coordinates.x() << old_coordinates.y()
<< "Destination: " << this->indexAt(event->pos()).row()
<< this->indexAt(event->pos()).column()
<< "Type: " << dropAction;
emit itemDropped( old_coordinates.x(), old_coordinates.y(),
this->indexAt(event->pos()).row(),
this->indexAt(event->pos()).column(),
dropAction);
}
// resize rows and columns right after a move!
this->doAdjustSize();
}
Even though the following answer is for Python, it still helped me when checking for what I could be missing.
QT: internal drag and drop of rows in QTableView, that changes order of rows in QTableModel
Here are some code snippets from my recent project related to drag and drop. I think you have most of them already, but I included them below for completeness.
QAbstractTableModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
Qt::ItemFlags QAbstractTableModel::flags(const QModelIndex &index) const
{
Q_UNUSED(index)
// if(index.isValid())
return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
// else
// return Qt::ItemIsDropEnabled | Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
QAbstractTableView(QWidget *parent) :
QTableView(parent)
{
setAcceptDrops(true);
setDefaultDropAction(Qt::MoveAction);
}
Hope that helps.

Qt: Signal while a QTableView item data is being edited instead of after edit is done?

I have a QTableView which has some QString based items in its model. I implemented setData in my table model, so editing is working (I can change the data in the cell, setData is called to update the model, and the table is properly updated).
Currently setData is only called when the user is done with editing, e.g. after they hit Enter or click out of the text entry box to finalize the text entry. I want to update other other parts of the table while the user is typing/editing into the text edit control instead of after they're done and the edited contents are finalized.
A simple example of what I want to have is for the next table cell to display a count of how many characters have been entered into the cell being edited, but to do this as the user is typing/editing the cell contents, not just after the edit is finalized and setData is called.
Any pointers to what I should be looking for? Thanks!
You can subclass QStyledItemDelegate and commit the data whenever something changes, and then set that delegate for the view with QAbstractItemView::setItemDelegate.
class MyDelegate : public QStyledItemDelegate {
QSignalMapper *mapper;
public:
MyDelegate(QObject*parent = 0)
: QStyledItemDelegate(parent)
, mapper(new QSignalMapper(this))
{
connect(mapper, SIGNAL(mapped(QWidget*)), SIGNAL(commitData(QWidget*)));
}
QWidget * createEditor(QWidget * parent, const QStyleOptionViewItem & option,
const QModelIndex & index ) const
{
QWidget *editor = QStyledItemDelegate::createEditor(parent, option, index);
if(qobject_cast<QLineEdit*>(editor)) {
connect(editor, SIGNAL(textChanged(QString)), mapper, SLOT(map()));
mapper->setMapping(editor, editor);
}
return editor;
}
};
The answer offered by #alexisdm did not work for me when I needed a persistent
editor enabled by QAbstractTableModel::setPersistentEditor(QModelIndex()).
The following solves this problem:
class Delegate : public QStyledItemDelegate
{
Q_OBJECT
public:
// ... omitted for brevity
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const override
{
auto *editor = static_cast<QLineEdit*>(
QStyledItemDelegate::createEditor(parent, option, index));
if (editor) {
connect(editor,
&QLineEdit::textChanged,
[=] (const QString &)
{
const_cast<Delegate*>(this)->commitData(editor);
});
}
return editor;
}
// ... omitted for brevity
};
We simply cast the constness from this and make it commit data for the editor.
Note that in the lambda we capture the editor variable by value [=] because otherwise, capturing with reference would make the value of editor undefined when the function runs out of scope.

QTableView - Not Getting Selection Changed Signal

I am fairly new to QT, and am having trouble understanding how the QTableView selection changed signal is handled. I have setup a window with an openGL widget and a QTableView. I have a data model class that is correctly populating the tableview, so I added a public slot to that class:
class APartsTableModel : public QAbstractTableModel
{
public:
AVehicleModel *vehicle;
explicit APartsTableModel(QObject *parent = 0);
//MVC functions
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &paret) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
public slots:
void selectionChangedSlot(const QItemSelection &newSelection,
const QItemSelection &oldSelection);
};
When I am ready to show the window with the table view, I allocate/initialize it like this:
//create the display view
AStarModelView *displayWindow = new AStarModelView(this,
starModel->vehicle);
//create the datamodel for the table view
APartsTableModel *dataModel = new APartsTableModel(displayWindow);
dataModel->vehicle = starModel->vehicle;
//create selection model for table view
QItemSelectionModel *selModel = new QItemSelectionModel(dataModel);
displayWindow->materialsTable->setSelectionModel(selModel);
//setup model and signal
displayWindow->materialsTable->setModel(dataModel);
connect(selModel,
SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
dataModel,
SLOT(selectionChangedSlot(const QItemSelection &, const QItemSelection &)));
//show the view
displayWindow->show();
When I set a breakpoint in the implementation of the slot function, I never hit it. I've also tried not allocating a new QItemSelectionModel, but that didn't work either. I'm really not sure what I'm doing wrong here.
When you call setModel() on the view, your locally allocated QItemSelectionModel is getting replaced by one created by the view. You shouldn't have to create your own selection model anyway. Just change your connect to
connect(displayWindow->materialsTable->selectionModel(),
SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
dataModel,
SLOT(selectionChangedSlot(const QItemSelection&, const QItemSelection&)));
What's the first thing you should check in QT when signals/slots don't seem to be working correctly? That your class has the Q_OBJECT macro in it. Added this to the APartsTable class definition, and now I'm hitting the breakpoint.
When does Friday get here?
Just to pull the answer out of the discussion:
What's the first thing you should check in QT when signals/slots don't
seem to be working correctly? That your class has the Q_OBJECT macro
in it. Added this to the APartsTable class definition, and now I'm
hitting the breakpoint
.
virtual Qt::ItemFlags QAbstractItemModel::flags(const QModelIndex &index) const
must return Qt::ItemIsSelectable | otherFlags

Qt: TextEdit created by QStyledItemDelegate can't receive Escape key press event?

I have a QTableView to display a bunch of data, which is stored in a QAbstractTableModel. I also want to edit the data sometime, so I use a QStyledItemDelegate to create an editor (of QTextEdit type) when I double click on the cell. However, I want to handle the key Press event myself, but I never catch Key_Escape pressing in the Text Edit editor (Other keys such as Enter and Ascii can be captured). I checked the code, and found that Escape is directly connected to QTableView's closeEditor() function, which is a virtual method and is automatically invoked. As far as I know, key press event is the bottom layer of event handling, but it is not true with such case.
I do need to capture the Escape key press event so that I can handle it myself, can anyone tell me how to do this? Thanks!
I got the answer, and I think would help others:
Override QStyledItemDelegate::eventFilter() method:
MyItemDelegate::eventFilter(QObject* editor, QEvent* event)
{
if(event->type()==QEvent::KeyPress)
return false;
return QStyledItemDelegate::eventFilter(editor, event);
}
According to Qt's documentation, QStyledItemDelegate::eventFilter() returns true if the given editor is a valid QWidget and the given event is handled; otherwise returns false. Tab, Backtab, Enter, Return and Ecs are handled by default. So, if you want to handle the key press event yourself, you must let eventFilter return false when KeyPress event occurs. So that the editor's keyPressEvent() method will be invoked instead.
QStyledItemDelegate::eventFilter is not a possibility beacuse is virtual protected
In order to get the events you should subclass your own QEditLine and override ::keyPressEvent there. Take notice of the code. I pass actual row and colum of my QTableWidget cell in order to know what we are editing in the overrided QLineEditor.
//.h
class MyStyledItemDelegate : public QStyledItemDelegate
{
public:
MyStyledItemDelegate(QObject *parent = 0);
QWidget* createEditor(QWidget* parent,const QStyleOptionViewItem &option,const QModelIndex &index) const;
};
//.cpp
#include "mylineedit.h"
MyStyledItemDelegate::MyStyledItemDelegate(QObject *parent)
:QStyledItemDelegate(parent)
{
}
QWidget* MyStyledItemDelegate::createEditor(QWidget* parent,const QStyleOptionViewItem &option,const QModelIndex &index) const
{
MyLineEdit* editor = new MyLineEdit(parent,index.row(),index.column());
return editor;
}
/////////////////////////////////////////////////////////////
//My own QLineEdit
/////////////////////////////////////////////////////////////
//.h
class MyLineEdit : public QLineEdit
{
Q_OBJECT
public:
int _nRow;
int _nCol;
public:
MyLineEdit(QWidget *parent = 0,int nRow=-1, int nCol=-1);
virtual void keyPressEvent(QKeyEvent* event);
signals:
void mySignal(const QVector<QVariant> &);
public slots:
};
//.cpp
MyLineEdit::MyLineEdit(QWidget *parent, int nRow,int nCol):
QLineEdit(parent)
{
_nRow=nRow;
_nCol=nCol;
}
/////////////////////////////////////////////////////////////////////////
void MyLineEdit::keyPressEvent(QKeyEvent* event)
{
qDebug() << "MyLineEdit::OnKeyPressEvent:"<<event->text()<< " row="<<_nRow<<" col=" <<_nCol;
///SET YOUR CODE HERE
QLineEdit::keyPressEvent(event);
}

Determine if QTableView has an open editor

Is there any way to determine if a QTableView has an open editor in the current cell? I need to handle the following situation:
A user double-clicks a cell and edits the data, but leaves the cell in the "edit" state.
On another part of the UI, an action is taken that changes the selected row of the underlying model.
Back on my view, I want to determine if the newly selected row is the same as the open row. If not, I need to take an action. (Prompt the user? Commit automatically? Revert?)
I see how to get the current item, and can get the delegate on that item, but I don't see any isEditMode() property I was hoping to find.
Can someone point me in the right direction?
Just check whether the return value of
State QAbstractItemView::state () const
is
QTableView::EditingState
Connect to underlying model dataChanged signal
void QAbstractItemModel::dataChanged ( const QModelIndex & topLeft, const QModelIndex & bottomRight )
You can check if the cell where data has changed is the same than the currentIndex
QModelIndex QAbstractItemView::currentIndex () const
You cannot know if the current cell had an open editor straight, but can check if the view is in QAbstractItemView::EditingState
State QAbstractItemView::state () const
It should be enough to do what you want.
You can subclass QTableView in order to be able to access the state() function, which is unfortunately protected. However, I did not try that.
If you already have an QStyledItemDelegate subclass, you can use it to track whether an editor is currently open. However, you can't just use setEditorData/setModelData, because setModelData won't be called, when the user cancels editing. Instead, you can track the creation and destruction of the editor itself.
class MyItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
MyItemDelegate( QObject* parent = nullptr );
~MyItemDelegate();
QWidget* createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
void setEditorData( QWidget* editor, const QModelIndex& index ) const;
void setModelData( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const;
bool isEditorOpen() const { return *m_editorCount > 0; }
protected:
int* m_editorCount;
protected slots:
void onEditorDestroyed( QObject* obj );
};
Implementation:
MyItemDelegate::MyItemDelegate( QObject* parent ) :
QStyledItemDelegate( parent )
{
m_editorCount = new int;
*m_editorCount = 0;
}
MyItemDelegate::~MyItemDelegate()
{
delete m_editorCount;
}
QWidget* MyItemDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
// create an editor, can be changed as needed
QWidget* editor = QStyledItemDelegate::createEditor( parent, option, index );
connect( editor, SIGNAL(destroyed(QObject*)), SLOT(onEditorDestroyed(QObject*)));
printf( "editor %p created\n", (void*) editor );
(*m_editorCount)++;
return editor;
}
void MyItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
...
}
void MyItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
...
}
void MyItemDelegate::onEditorDestroyed( QObject* obj )
{
printf( "editor %p destroyed\n", (void*) obj );
(*m_editorCount)--;
}
On some occasions, e.g. when moving to the next item in the tree using the cursor keys, Qt will create the new editor first and then destroy the old one. Hence, m_editorCount must be an integer instead of a bool.
Unfortunately, createEditor() is a const function. Therefore, you cannot create an int-member. Instead, create a pointer to an int and use that.
Subclass your delegate so that it includes an accessor that tells you when it's editing:
void MyDelegate::setEditorData ( QWidget * editor, const QModelIndex & index ) const {
// _isEditing will have to be mutable because this method is const
_isEditing = true;
QStyledItemDelegate::setEditorData(editor, index);
}
void MyDelegate::setModelData ( QWidget * editor, QAbstractItemModel * model, const QModelIndex & index ) const {
QStyledItemDelegate::setModelData(editor, model, index);
_isEditing = false;
}
bool MyDelegate::isEditing() const { return _isEditing; }
Then you can just check the delegate to see what's going on. Alternatively and/or if you don't like the mutable, you can emit signals so you know what state the delegate is in.
If you know the index of the item being edited, you can call indexWidget() and attempt to cast it. If it's valid, you not only know you're editing, but you also have your editor widget handy.
EditWidget *editWidget = qobject_cast<EditWidget*>(tableView->indexWidget(tableView->currentIndex()));
if(editWidget)
{
//yep, ur editing bro
}
Here is an idea, its even helpful to get the edit/combo widget before the edit begins...
just emit a signal and consume it in the mainwindow... this is what I used one to get combo box in QTableWidget before editing...
first create a signal in ComoBoxItemDelegate...
signals:
void OnComboEdit(QComboBox* pCombo) const;
then emit the signal in the createEditor method...
QWidget* ComboBoxItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
// Create the combobox and populate it
QComboBox* cb = new QComboBox(parent);
emit OnComboEdit(cb);
return cb;
}
and in the MainWindow declare a function to receive the singal...
void MainWindow::OnComboEidt(QComboBox *pCB) const
{
qDebug() << "Combo Eidt Singal Received";
}
Then finally in the constructor of MainWindow connect it...
ComboBoxItemDelegate* cbid = new ComboBoxItemDelegate(ui->tableWidget);
connect(cbid, &ComboBoxItemDelegate::OnComboEdit, this, &MainWindow::OnComboEidt);
ui->tableWidget->setItemDelegateForColumn(0, cbid);

Resources