QTreeView::dropMimeData - setting values for a new child - qt

I am trying to drop some mime encoded text onto a tree view. It is working - the dropMimeData() method is called, I can decode the mime data into the strings that were dropped, I can insert a child into the model which shows up in the view, but ... I can't find a way to set the text value of the new item/row to the string dragged and dropped (or any string for that matter).
Here is some of the code i've tried inside the dropMimeData() method:
if ( ( row == -1) && (column == -1) && parent.isValid() ) {
int mdlidx = this->data(parent, Qt::DisplayRole).ModelIndex;
qDebug() << "mdlidx: " << mdlidx;
// treet text - the text of the cell that gets dropped onto
QString tt = this->data(parent, Qt::DisplayRole).toString();
qDebug() << "tree text: " << tt;
TreeItem *item = this->getItem(parent);
int ccnt = item->childCount();
qDebug() << "ccnt: " << ccnt ;
if ( item->insertChildren(0, 1, 0) ) {
qDebug() << "Child Inserted";
// how do I access the new child item here ???
} else {
qDebug() << "Failed";
}
How do I access the new child item in order to set the text that would be visible in the view?
I'm using the QStandardItemModel, if that makes any difference.

My solution to this is to create a signal and slot - I emot the signal in the dropMimeData() method and the slot is in a part of the code that has the view and model, so can easily update the model.
I send the mime data and the parent across using the signal.
I'm not sure if this is the correct way of doing this, but it is working.

Related

Problems with Qt iterators in QLinkedList

I know I will be asked for a minimal example, but I am not sure to be able to come up with one at all, or it won't be very minimal. But I may still learn valuable knowledge from this discussion.
So I have a series of data items of a given class organized in a QLinkedList. The user is provided with an interface that allows to navigate through this data and edit it. In particular, some of the possible editing actions require multiple entries in the QLinkedList to be modified - for example all subsequent or all previous entries. Items can also be added or removed to the list, and then edited.
In the code, I simply keep an iterator up to date that points to the current data item in the chain, called m_currentItem. I then use it to display the data at the current item, loop from the current to the end or to the beginning. I require this iterator to be valid throughout the whole execution and lifetime of the application, even if items are added or removed. When the user presses "next" or "previous" the iterator is simply incremented or decremented. When the user deletes an item in the list (except the last), the iterator then points to the next entry and its content is displayed. When a user adds an item in the list, m_currentItem is now made to point to this new item.
This is where things go pretty wrong : On those backwards or forward loops. They often (but not always ...) go over the end condition and continue into unknown data until a major crash ensues. Sometimes it also loops back to the first element in the list (!!!) and then it's an infinite loop for ya.
Here's an example taken from my code, anything obviously wrong ? Anything else I should know about Qt iterators ?
EDIT :
Just learned about reverse iterators ! It seems recommends using them instead of the -- overloaded operator on standard iterators ! But how do I make m_currentItems a reverse iterator ?
Also I read about implicit sharing ... I don't really do anything like that I don't think, but does that go to show that I shouldn't be using long-living iterators like I do ?
include <QLinkedList>
struct Member
{
int memberID;
}
struct Item
{
/* A map of members, each mapped to an ID */
QMap<int, Member> m_members;
QString name;
}
typedef QLinkedList<Item> LinkedItems;
LinkedItems m_items;
LinkedItems::iterator m_currentItem;
...
if(m_currentItem != m_items.end())
{
for(LinkedItems::iterator iter = m_currentItem+1; iter != m_items.end(); iter++)
{
// Do things on *iter
}
}
A more complex implementation : in each linked Items are found Members which contain data, and an ID. It is possible for the user to propagate an ID in all previous items located before an item located (that must itself be located before the current item) that he is asked to choose.
E.g. if we have A-B-C-D-E-F and the user is on E, he can choose to propagate a member ID of E to C and previous items (so A and B, too - but not to D).
Here's the code. Most or all the iterator loops are subject to unexpected behaviors.
void renameAllPreviousMembers(int memberIDToPropagate)
{
/* If first item, then there is no previous altogether */
if(m_currentItem != m_items.begin())
{
if(m_currentItem.m_members.contains(memberIDToPropagate) == false)
{
QMessageBox::critical(nullptr, tr("Rename members in previous items"),
tr("There is no member with the selected ID in the current item."),
QMessageBox::Ok);
return;
}
bool chose_item;
LinkedItems::iterator itemIter;
QStringList previousItems;
QStringList previousItemsBetterOrder;
QMap<QString, LinkedItems::iterator> previousItemsMap;
previousItemsMap.clear();
/* Find all previous items and store them in a QStringList and a map between those strings and actual items */
std::cout << "Search previous items : " << std::flush;
for(itemIter = m_items.begin();
itemIter != m_currentItem;
itemIter++)
{
/* Possibly add conditions on some items that we do not want to apply values to. */
std::cout << itemIter->name << ", " << std::flush;
previousItems << itemIter->name;
previousItemsMap.insert(itemIter->name, itemIter);
}
std::cout << "finished." << std::endl << std::flush;
QStringList::reverse_iterator riter;
for(riter = previousItems.rbegin(); riter != previousItems.rend(); riter++)
{
previousItemsBetterOrder << *riter;
}
QString name_previous_item = QInputDialog::getItem(nullptr,
QString("Previous item"),
QString("Previous item to start from : "),
previousItemsBetterOrder, 0, false, &chose_item);
if(!chose_item)
{
return;
}
/* Decode chosen previous item by retrieving it in the map */
LinkedItems::iterator chosenPrevIter = previousItemsMap.value(name_previous_item);
bool chose_member;
/* Find all existing IDs in previous item */
QStringList previousIDs;
QMap<int, Member>::const_iterator memberIter;
/* Retrieve all members from the chosen previous item */
std::cout << "Search IDs in chosen previous item : " << std::flush;
for(memberIter = chosenPrevIter->m_members.begin(); memberIter != chosenPrevIter->m_members.end(); memberIter++)
{
previousIDs << QString("%1").arg(QString::number(memberIter->ID));
std::cout << memberIter->memberID << ", " << std::flush;
}
std::cout << "finished." << std::endl << std::flush;
/* If no member then no lunch */
if(previousIDs.size() == 0)
{
QMessageBox::critical(nullptr, tr("Rename previous member"),
tr("There are no members in the selected previous item."),
QMessageBox::Ok, QMessageBox::Ok);
return;
std::cout << "Rename previous members finished with no members in chosen previous item." << std::endl << std::flush;
}
QString string_member_id_from_previous = QInputDialog::getItem(nullptr,
QString("Previous member number"),
QString("Member ID from previous item to be used : "),
previousIDs, 0, false, &chose_member);
if(chose_member)
{
int member_id_from_previous = string_member_id_from_previous.toInt();
/* Update member ID at and before chosen previous item */
std::cout << "Update member ID before chosen previous item : " << std::flush;
for(itemIter = chosenPrevIter;
itemIter != m_items.begin();
itemIter--)
{
std::cout << itemIter->name << std::flush;
if(itemIter->m_members.contains(member_id_from_previous))
{
/* Take and reinsert to new ID. */
std::cout << "+" << std::flush;
itemIter->m_members.insert(memberIDToPropagate, itemIter->m_members.take(member_id_from_previous));
}
else
{
/* Do nothing. */
}
std::cout << ", " << std::flush;
}
std::cout << "finished." << std::endl << std::flush;
}
}
else
{
/* Do nothing. */
}
}
else
{
QMessageBox::critical(nullptr, tr("Apply to previous members"),
tr("This is the first item. There are no previous items."),
QMessageBox::Ok);
}
std::cout << "Apply to previous members finished." << std::endl << std::flush;
}

QTextLayout::drawCursor() does not work when subclassing QAbstractTextDocumentLayout

currently QTextEdit and QPlainTextEdit does not meet my requirements, so I need to subclass QAbstractTextDocumentLayout to provide custom layout of the document. I reference QPlainTextDocumentLayout and QTextDocumentLayout a lot, and finally got a simple layout to display the text. However, I couldn't see the cursor in QTextEdit, which should be blinking. I need help to figure this out.
I am using Qt 5.9.1. The simple project is here. The draw() function of VTextDocumentLayout looks like this:
void VTextDocumentLayout::draw(QPainter *p_painter, const PaintContext &p_context)
{
qDebug() << "VTextDocumentLayout draw()" << p_context.clip << p_context.cursorPosition << p_context.selections.size();
// Find out the blocks.
int first, last;
blockRangeFromRect(p_context.clip, first, last);
if (first == -1) {
return;
}
QTextDocument *doc = document();
QPointF offset(m_margin, m_blocks[first].m_offset);
QTextBlock block = doc->findBlockByNumber(first);
QTextBlock lastBlock = doc->findBlockByNumber(last);
qreal maximumWidth = m_width;
while (block.isValid()) {
const BlockInfo &info = m_blocks[block.blockNumber()];
const QRectF &rect = info.m_rect;
QTextLayout *layout = block.layout();
if (!block.isVisible()) {
offset.ry() += rect.height();
if (block == lastBlock) {
break;
}
block = block.next();
continue;
}
QTextBlockFormat blockFormat = block.blockFormat();
QBrush bg = blockFormat.background();
if (bg != Qt::NoBrush) {
fillBackground(p_painter, rect, bg);
}
auto selections = formatRangeFromSelection(block, p_context.selections);
QPen oldPen = p_painter->pen();
p_painter->setPen(p_context.palette.color(QPalette::Text));
layout->draw(p_painter,
offset,
selections,
p_context.clip.isValid() ? p_context.clip : QRectF());
// Draw the cursor.
int blpos = block.position();
int bllen = block.length();
bool drawCursor = p_context.cursorPosition >= blpos
&& p_context.cursorPosition < blpos + bllen;
Q_ASSERT(p_context.cursorPosition >= -1);
if (drawCursor) {
int cpos = p_context.cursorPosition;
cpos -= blpos;
qDebug() << "draw cursor" << block.blockNumber() << blpos << bllen << p_context.cursorPosition << cpos;
layout->drawCursor(p_painter, offset, cpos);
}
p_painter->setPen(oldPen);
offset.ry() += rect.height();
if (block == lastBlock) {
break;
}
block = block.next();
}
}
I called layout->drawCursor() to draw the cursor but this function seems to do nothing.
Any help is appreciated! Thanks!
Update:
Add the log as following:
VTextDocumentLayout draw() QRectF(67,0 9x13) 19 0
block range 0 1
draw cursor 1 5 15 19 14
blockBoundingRect() 1 13 QRectF(0,0 75x17)
VTextDocumentLayout draw() QRectF(67,0 9x13) -1 0
block range 0 1
blockBoundingRect() 1 13 QRectF(0,0 75x17)
VTextDocumentLayout draw() QRectF(67,0 9x13) 19 0
When running this project in Linux, I coundn't see the cursor. However, when running it in Windows, I could see the cursor but not blinking.
Update:
It seems that QTextEdit pass a wrong clip rect to the layout. If I just inserted one line of text (only one block within the document), I could see the blinking cursor. Quite strange!!!
The default value of PaintContext class cursorPosition is -1.
http://doc.qt.io/qt-4.8/qabstracttextdocumentlayout-paintcontext.html#cursorPosition-var
If any default value is not provided to the cursor position ever (it is a public variable), then my guess is value remains -1, and drawCursor is always false (as the block position index minimum value is zero in worst case).
Try setting some default value to PaintContext::cursorPosition, which may display the cursor.
When subclassing QAbstractTextDocumentLayout, blockBoundingRect() should return the geometry of the block, not just the rect as what QPlainTextDocumentLayout does.
In one word, QPlainTextDocumentLayout provides a BAD example for subsclassing QAbstractTextDocumentLayout.

How to set data source for a list view to contain custom data ? (and associate with QTableView)

I am trying to get listview and tableview working together.
The listview must be used for display, the tableview must be used for editing data. The tableview is created on demand in a popup widget (and it may never be needed).
I populate the listview, on start, from a text file - each row a line, with 2 entries separated by a tab. Easy.
The tableview will have to edit 2 columns separately... also, on listview click, I must be able to retrieve the first part of the split...
I have created a model subclass of QStringListModel.
QListView *m_myView = new QListView();
StringList *m_myList = new StringList();
QTextStream in(&myFile);
while (!in.atEnd())
{
QString temp = in.readLine();
if(!temp.isEmpty())
m_myList->append(temp);
}
myFile.close();
m_myView->setModel(m_myList);
where
class StringList : public QStringListModel
{
public:
void append (const QString& string)
{
insertRows(rowCount(), 1);
QModelIndex m = index(rowCount() - 1);
setData(m, string, Qt::EditRole);
QStringList splist = string.split('\t');
setData(m, splist.at(0), Qt::ToolTipRole);
if(splist.size() > 1)
setData(m, splist.at(1), Qt::StatusTipRole);
else
setData(m, "", Qt::StatusTipRole);
qDebug() << data(m, Qt::EditRole).toString()
<< data(m, Qt::ToolTipRole).toString()
<< data(m, Qt::StatusTipRole).toString();
}
};
The EditRole displays correctly, the others display empty strings.
It seems that QStringListModel is incapable of storing anything except EditRole.
So I am left with 2 options - either do the split on each selection, and also when creating the table view, or - what I would like to try but don't know how - create a QStandardItemModel that can act as data sources for both the listview and the tableview, and can easily retrieve the partial data I require on click.
I thought I can simply use it to set the data on listview (like they do here).
QListView *m_myView = new QListView();
QStandardItemModel *m_myList = new QStandardItemModel();
QTextStream in(&myFile);
int r = 0;
while (!in.atEnd())
{
QString temp = in.readLine();
if(!temp.isEmpty())
{
QStringList splist = temp.split('\t'); // assume I know there will be 2 strings always
QStandardItem *item = new QStandardItem(splist.at(0));
m_myList->setItem(r, 0, item);
QStandardItem *item1 = new QStandardItem(splist.at(1));
m_myList->setItem(r, 1, item1);
++r;
}
}
myFile.close();
m_myView->setModel(m_myList);
connect(m_myListView, SIGNAL(clicked(QModelIndex)),
this, SLOT(listChangedSlot(QModelIndex)));
But this will only set the first string in the listview, I really need both, and I still don't know how to retrieve the data
void listChangedSlot(QModelIndex index)
{
qDebug() << m_myList->item(index.row(), 0)->data().toString()
<< m_myList->item(index.row(), 1)->data().toString();
} // shows empty strings
Or...
In the loading section, try:
if(!temp.isEmpty())
{
QStringList splist = temp.split('\t');
splist.append(QString()); // so I don't need to test
QStandardItem *item = new QStandardItem(temp);
m_myList->setItem(r, 0, item);
QModelIndex idx = m_myList->index(r, 0);
QMap<int, QVariant> roles;
roles.insert(Qt::UserRole + 1, QVariant(splist.at(0)));
roles.insert(Qt::UserRole + 2, QVariant(splist.at(1)));
roles.insert(Qt::DisplayRole, QVariant(temp));
m_myList->setItemData(idx, roles);
++r;
}
This works - displays fine and gets the correct content on click - but seems to be unusable for the tableview.
(And seems I am doing twice the work, with setItem and setItemData - so technically storing the content 3 times).
How can I get my listview have a datasource with 2 string items, show both, be able to set it on a tableview, and be able to retrieve the first item on click ?
I figured out a way to share the data source between the listview and tableview:
I create 3 columns - column 0 with the combined components, and columns 1 and 2 with the separated components.
The listview will display only column 0. For tableview, I will hide column 0.
The data I require for processing will be stored in columns 1 and 2. user edit of the data in tableview will require, upon accepting changes, the edit of corresponding column 0.

Doubleclick on QTreeView shall always return first columns value

I have my QTreeView where whole rows were selected:
ui->treeView->setSelectionBehavior (QAbstractItemView::SelectRows);
...and already filled with ID-Number <--> Description. All are structured as a tree. I can click it, and retreive corresponding selection via:
ui->lineEdit->setText( modelIndex.data(Qt::DisplayRole).toString() );
and I already connected:
connect(ui->treeView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(onSelectedTreeView(QModelIndex)));
When I click on a ID-Number, it works perfectly, and my modelIndex.data() returns the ID-Number.
When I click on the description, my modelIndex.data() returns its correct description (second column). BUT I would like to have again the corresponding ID-Number (first column).
I appreciate any help. Thanks in advance.
You can use the sibling(int row, int column) method of QModelIndex
In python:
def onSelectedTreeView(index):
firstColumnIndex=index.sibling(index.row(),0)
print(firstColumnIndex.data())
I guess you could do:
ui->lineEdit->setText(modelIndex.sibling(modelIndex.row(),0).data(Qt::DisplayRole).toString());
You can change treeView selectionBehavior to SelectItems. Than use index.row() and index.column().. And dont forget that it always start from 0
onSelectedTreeView()
{
//Put all your selected indexes into QModelIndexList
QModelIndexList _indexes = ui->treeView->selectionModel()->selectedIndexes();
// For each loop for every selected index..
foreach (QModelIndex index, _indexes)
{
//if your index data == 0 it means that you clicked into ID-number field.
//So you can easly see which index you clicked with qdebug
qDebug() << "Row = " << index.row() << "\t Column = " << index.column();
// So if you want to get always index of first column(ID-Number) use QAbstractItemModel
if(index.column().toInt() != 0 ) //if your index not equal to 0
{
const QAbstractItemModel* absModel = index.model();
// change index of absModel from index.column() to 0;
QModelIndex changedIndex = absModel->data(absModel->index(index.row(), 0), Qt::DisplayRole).toInt();
qDebug() << "Row = " << changedIndex.row() << "\t Column = " << changedIndex.column();
}
}
}

How to work with signals from QTableWidget cell with cellWidget set

I've got a QTableWidget with some columns inside.
Due to my needs I set QComboBox inside some columns and fill them with necessary data.
void settingsDialog::onAddFieldButtonClicked()
{
fieldsTable->setRowCount(++rowCount);
combo = new QComboBox();
combo->addItem(QString("Choose from list..."));
foreach( int height, heightsAvailable)
combo->addItem(QString("%1").arg(height));
fieldsTable->setCellWidget(rowCount-1, 3, combo);
// etc for other columns ...
}
The question is how to catch signals from these combo boxes if they were changed?
I want to know row and col of changed widget (combo box) and the value which was set.
I've tried all available signals which are mentioned in Qt docs for QTableWidget, but they work only if cell doesn't have widget inside it.
Is there a simple and Qt-way to get what I need?
Instead of handling a signal from the table, you can handle currentIndexChanged signal from combo box itself.
QComboBox* combo = new QComboBox();
combo->addItem(QString("Choose from list..."));
combo->addItem(QString("first item"));
combo->setProperty("row", ui->tableWidget->rowCount() - 1);
combo->setProperty("column", 0);
connect(combo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(OnComboIndexChanged(const QString&)));
ui->tableWidget->setCellWidget(ui->tableWidget->rowCount() - 1, 0, combo);
And in the slot, you can use sender() to identify the combo box which emitted the signal.
void MainWindow::OnComboIndexChanged(const QString& text)
{
QComboBox* combo = qobject_cast<QComboBox*>(sender());
if (combo)
{
qDebug() << "row: " << combo->property("row").toInt();
qDebug() << "column: " << combo->property("column").toInt();
}
}

Resources