Copying part of QTableView - qt

So I have a question very closely related to another question I've seen on here but when I tried posing my question there I got no responses, I'm hoping by asking this as a fresh question someone can help me out. Basically I want simply copy a portion of my table that I've created so that I can paste it to an excel file. Here's what I have:
QAbstractItemModel *abmodel = ui.tableview->model();
QItemSelectionModel *model = ui.tableview->selectionModel();
QModelIndexList list = model->selectionIndexes();
qSort(list);
QModelIndex index = list.first();
for(int i = 0; i < list.size(); i++)
{
QModelIndex index = list.at(i);
QString text = abmodel->data(index).toString();
copy_table.append(text);
if(index.row() != previous.row())
{
copy_table.append('\n');
}
else
{
copy_table.append('\t');
}
previous = index;
}
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(copy_table);
This will copy a column fine, but when I attempt to copy a row or say a 2x2 subtable the row index gets messed up, incorrectly assigning the row index for the values. Any thoughts?

Well, already figured it out, sorry anyone that wasted their time and looked.
void TestCopyTable::on_pushButton_copy_clicked()
{
QAbstractItemModel *abmodel = ui.tableView->model();
QItemSelectionModel * model = ui.tableView->selectionModel();
QModelIndexList list = model->selectedIndexes();
qSort(list);
if(list.size() < 1)
return;
QString copy_table;
QModelIndex last = list.last();
QModelIndex previous = list.first();
list.removeFirst();
for(int i = 0; i < list.size(); i++)
{
QVariant data = abmodel->data(previous);
QString text = data.toString();
QModelIndex index = list.at(i);
copy_table.append(text);
if(index.row() != previous.row())
{
copy_table.append('\n');
}
else
{
copy_table.append('\t');
}
previous = index;
}
copy_table.append(abmodel->data(list.last()).toString());
copy_table.append('\n');
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(copy_table);
}

I wrote some code based on Phil's to copy the selection when the user types Control-C.
I subclassed QTableWidget and overrode keyPressEvent():
void MyTableWidget::keyPressEvent(QKeyEvent* event) {
// If Ctrl-C typed
// Or use event->matches(QKeySequence::Copy)
if (event->key() == Qt::Key_C && (event->modifiers() & Qt::ControlModifier))
{
QModelIndexList cells = selectedIndexes();
qSort(cells); // Necessary, otherwise they are in column order
QString text;
int currentRow = 0; // To determine when to insert newlines
foreach (const QModelIndex& cell, cells) {
if (text.length() == 0) {
// First item
} else if (cell.row() != currentRow) {
// New row
text += '\n';
} else {
// Next cell
text += '\t';
}
currentRow = cell.row();
text += cell.data().toString();
}
QApplication::clipboard()->setText(text);
}
}
Output example (tab-separated):
foo bar baz qux
bar baz qux foo
baz qux foo bar
qux foo bar baz

Regarding the cdline:
qSort(cells); // Necessary, otherwise they are in column order
currently(20190118) it brings a warning:
Warnung: 'qSort >' is deprecated: Use std::sort
so my solution to replace the line with:
std::sort(cells.begin(),cells.end());
Compile, Run OK -> so far so good. But Question: Benefit of this cdline?
I found there is no one. Made several Test with copy from gui and parsing it to excel. Everything was fine even with the scenario 2x2 or othe XxY.

Related

Qt: QFormLayout::addRow(QWidget* label, QWidget* field) not adding a new row

I am trying to add a new row to my QFormLayout after I have loaded two QLineEdits from a file, which works, but when I run my code it doesnt add anything, ot atleast anything that I can see. And I am also not able to add any wigets using QLayout::addWidget(QWidget* widget) anymore, which I used to be able to.
Thanks
The code, where it doesnt work:
void Kegelbuch::load(QString path, QString tab) {
//Load json file
Datastream* loadStream = new Datastream;
QJsonObject data = loadStream->loadData(path);
//Declaring all important Variables
QJsonArray keglerFullName;
QJsonArray keglerShortName;
QFormLayout* formLayout = (QFormLayout*)ui.keglerTab->layout();
int defaultRows = 2, width = 155;
//Retriev arrays from file
if (data.contains("KeglerFullName") && data["KeglerFullName"].isArray()) {
keglerFullName = data["KeglerFullName"].toArray();
}
if (data.contains("KeglerShortName") && data["KeglerShortName"].isArray()) {
keglerShortName = data["KeglerShortName"].toArray();
}
//Correctly add QLineEdits to the FormLayout
for (auto names : boost::combine(keglerFullName, keglerShortName)) {
QLineEdit fullNameEdit;
QLineEdit shortNameEdit;
QJsonValue fullNameValue, shortNameValue;
boost::tie(fullNameValue, shortNameValue) = names;
if (fullNameValue.isString()) {
fullNameEdit.setText(fullNameValue.toString());
fullNameEdit.setObjectName("fullName");
fullNameEdit.setMinimumWidth(width);
}
if (shortNameValue.isString()) {
shortNameEdit.setText(shortNameValue.toString());
shortNameEdit.setMaximumWidth(width);
shortNameEdit.setObjectName("shortName");
}
/*
if (keglerFullName.at(1).isString()) {
fullNameEdit->setText(keglerFullName.at(1).toString());
fullNameEdit->setObjectName("fullName");
fullNameEdit->setMinimumWidth(width);
}
if (keglerShortName.at(1).isString()) {
shortNameEdit->setText(keglerShortName.at(1).toString());
shortNameEdit->setMaximumWidth(width);
shortNameEdit->setObjectName("shortName");
}
*/
formLayout->addRow(&fullNameEdit, &shortNameEdit);
}
}

How to pin a tab in Qt

Is it possible to pin a tab with Qt?
I want a tab to always stay in place (index 0) while still able to move other tabs.
So far I tried to listen to QTabBar::tabMoved and revert the move but that's too late. I don't want it even to attempt to move.
Worst case for me would be to be forced to change the mouse handling. Let me know please if there is an other way.
I have never found a nice way to do that. But, I used the fact that you can store raw data in the QTabBar to pin the tabs and undo a move if it was a pinned tab. It's not perfect and I still have some ugly behavior, but I didn't want to use mouse events, neither.
First, create a struct to store the current state of a tab:
struct PinnedTab
{
bool isPinned;
int currentIndex;
};
Q_DECLARE_METATYPE(PinnedTab); // For QVariant
Then, create a custom QTabBar to handle the move and use QTabWidget to replace the current tab bar (you have to do that before inserting the tabs):
class Bar: public QTabBar
{
public:
void pin(int const index)
{
PinnedTab info;
info.isPinned = true;
info.currentIndex = index; // TODO: move the tab to the left and do not use current index
setTabData(index, QVariant::fromValue(info));
}
Bar(QWidget* parent=nullptr): QTabBar(parent)
{}
virtual void tabLayoutChange() override
{
for (int i = 0; i != count(); ++i) // Check if a pinned tab has moved
{
if (tabData(i).isValid())
{
PinnedTab const info = tabData(i).value<PinnedTab>();
if (info.isPinned == true && i != info.currentIndex) {
rollbackLayout();
return;
}
}
}
for (int i = 0; i != count(); ++i)
{
if (tabData(i).isValid())
{
PinnedTab info = tabData(i).value<PinnedTab>();
info.currentIndex = i;
setTabData(i, QVariant::fromValue(info));
}
else
{
PinnedTab info;
info.isPinned = false;
info.currentIndex = i;
setTabData(i, QVariant::fromValue(info));
}
}
}
void rollbackLayout() {
for (int i = 0; i != count(); ++i)
{
if (tabData(i).isValid())
{
PinnedTab const info = tabData(i).value<PinnedTab>();
if (i != info.currentIndex) {
moveTab(i, info.currentIndex);
}
}
}
}
};
tabLayoutChange is called when the layout has changed. So, it will be called when you move a tab.
the rollbackLayout method is used to move each tab to the last position stored in the tab data.
Call pin to pin a tab with the given index.
I simplified my code for more clarity and you may have to redefine some behavior (for now, if you pin a tab, it will keep its current position and it will not handle the insert/remove tabs).

How to implement clipboard actions for custom mime-types?

I am trying to implement copy/cut/paste in a complex application.
I have a QGraphicsScene that can contain QGraphicsItem subtypes of varied subtypes, fairly complex (with Item as a second parent storing custom properties).
I would copy/cut selected items, and paste them back in place.
I already have implemented it using a local version: a list of items.
void copyItemsActionOld()
{
foreach(QGraphicsItem* qItem, selectedItems())
{
Item* newItem = (dynamic_cast<Item*>(qItem))->createItemCopy();
m_itemClipboard.append(newItem);
}
}
On paste, I make a copy of all items in clipboard and add them to the scene. So simple.....
BUT
I need to implement it using the global system clipboard.
I saw that creating a custom mime type is as simple as calling setData on a QMimeData object, after I make up a format name... (I hope that is true)
static const QString _mimeType("application/myItem");
void copyItemsAction()
{
QMimeData* _mimeData = new QMimeData;
2 QByteArray _itemData = ?????;
_mimeData->setData(_mimeType, _itemData);
QClipboard* _clipboard = QApplication::clipboard();
_clipboard->clear();
_clipboard->setMimeData(_mimeData);
}
void pasteItemsAction()
{
QClipboard* _clipboard = QApplication::clipboard();
const QMimeData* _mimeData = _clipboard->mimeData();
QStringList _formats = _mimeData->formats();
foreach (QString _format, _formats)
{
if (_format == _mimeType)
{
QByteArray _itemData = _mimeData->data(_mimeType);
3 // then do what ? How do I parse it ?
}
}
}
My questions
1) Are the above fragments for copyItemsAction and pasteItemsAction anywhere close to how clipboard actions should work ?
2) How can I put item data in the QByteArray ?
3) How do I parse the data in QByteArray ?
4) Do I need to register the custom mime-type anywhere else ? (other than what I just did in my two functions); and will it be multi-platform ?
I have already implemented save and load functionality for all items. Something like...
void Item::saveItem(QDataStream &outFile)
{
outFile << type;
outFile << width;
outFile << color.name();
}
Can I use this to place the items data in the QByteArray ? (How ?)
I was on the right track, and I kept adding code to my question until I found how to make it work:
static const QString _mimeType("application/myItem");
void copyItemsAction()
{
QByteArray _itemData;
QDataStream outData(&_itemData, QIODevice::WriteOnly);
outData << selectedItems().size();
foreach(QGraphicsItem* qItem, selectedItems())
{
Item* item = dynamic_cast<Item*>(qItem);
item->saveItem(outData);
}
QMimeData* _mimeData = new QMimeData;
_mimeData->setData(_mimeType, _itemData);
_mimeData->setText("My Items");
QClipboard* _clipboard = QApplication::clipboard();
_clipboard->clear();
_clipboard->setMimeData(_mimeData);
}
void pasteItemsAction()
{
QClipboard* _clipboard = QApplication::clipboard();
const QMimeData* _mimeData = _clipboard->mimeData();
QStringList _formats = _mimeData->formats();
foreach (QString _format, _formats)
{
if (_format == _mimeType)
{
QByteArray _itemData = _mimeData->data(_mimeType);
QDataStream inData(&_itemData, QIODevice::ReadOnly);
int itemsSize;
inData >> itemsSize;
for (int i = 0; i < itemsSize; ++i)
{
Item* item = ...
item->loadItem(inData);
}
}
}
}
So, for question 1, yes I was on the right track;
For questions 2 and 3 - I was able to use a QDataStream to serialize info to/from the QByteArray.
If there is a better / more effective / faster way, I would love to know...
For question 4 - it seems that I can use just about any string, if all I want is to copy/paste within a single instance of my application.
It is also true if I want to use it between multiple applications, multiple instances of my application, or for drag-and-drop - on most platforms. (It does not seem to work between multiple applications/instances in the embedded platform I target.)
Caveat - it fails frequently when another clipboard using application is open, in windows.

searching for a particular entry in a table by qlineedit?

What i want is that if i enter an ID in the textbox and then press enter,then if the ID is present ,then it gets displayed on the table the valuesof the table are inserted with the help of map in another window from which this window Box1 is opened as map.So as far as i have an idea,we have to run find command of map and then using if loop if that entered value in textbox is presentthen will display it in the same way as dummy data is displayed.
code used
Box1::Box1(QWidget *parent)
:QDialog(parent)
{
searchgroup = new QGroupBox(tr("Data Search"));
QHBoxLayout *layout2 = new QHBoxLayout;
text = new QLineEdit(this);
searchh = new QLabel(tr("&Enter ID:"));
searchh->setBuddy(text);
layout2->addWidget(searchh);
layout2->addWidget(text);
searchgroup->setLayout(layout2);
tableegroup = new QGroupBox(tr("Searched Data"));
QVBoxLayout *layout1 = new QVBoxLayout;
tablee = new QTableView(this);
mode1 = new QStandardItemModel(1,2,this);
mode1->setHorizontalHeaderItem(0, new QStandardItem(QString("ID")));
mode1->setHorizontalHeaderItem(1, new QStandardItem(QString("DATA")));
map<int,QString>::iterator itt;
itt=dataa.begin();
for (int colu = 0; colu < 2; colu++)
{
item1 = new QStandardItem();
if (colu == 0)
{
item1->setData(((*itt).first), Qt::DisplayRole);
mode1->setItem(0,0,item1);
} else
{
item1->setData(((*itt).second), Qt::DisplayRole);
mode1->setItem(0,1,item1);
}
}
tablee->setModel(mode1);
layout1->addWidget(tablee);
tableegroup->setLayout(layout1);
QVBoxLayout *mainlayout1 = new QVBoxLayout;
//mainlayout1->addWidget(menubarr);
mainlayout1->addWidget(searchgroup);
mainlayout1->addWidget(tableegroup);
setLayout(mainlayout1);
}
Thanks for any help in advance
EDIT
what i want
void Box1::textReturn()
{
bool ok;
int id = text->text().toInt(&ok);
// map<int,QString>::iterator itt;
if (ok && dataa.contains(id))
{
// add row (id, data[id] to table
}
else
{
QMessageBox msgbox = new QMessagebox();
msgbox->setWindowTitle("Alert");
msgbox->setText("No such ID present!");
msgbox->show();
}
}
EDIT2
void Box1::textReturn()
{
int id = (text->text()).toInt();
map<int,QString>::iterator itt;
itt = dataa.find(id);
if(itt != dataa.end()) //returns 1 if we found something
{
QList<QStandardItem *> items;
items << new QStandardItem(QString("%1").arg(id));
items << new QStandardItem((*itt).second);
mode1->appendRow(items);
tablee->update();
}
else
{
QMessageBox *msgbox = new QMessageBox();
msgbox->setWindowTitle("Alert");
msgbox->setText("INVALID ID ENTERED");
msgbox->show();
}
}
As #KCiebiera said, you have to do this connection
connect(text, SIGNAL(returnPressed()), this, SLOT(textReturnPressed());
Then you need to find your key in the table using
QList<QStandardItem *> QStandardItemModel::findItems ( const QString & text,
Qt::MatchFlags flags = Qt::MatchExactly, int column = 0 )
As you have map, so elements shouldn't repeat, your QList should be NULL or contains just one element. When u'll get your element (as QStandardItem) you just need to invoke
tablee->showColumn ( int column )
tablee->showRow ( int row )
Where your column will be QStandarItem->column() and row QStandardItem->row();
EDIT
void Box1::textReturnPressed()
{
int id = (test->text()).toInt();
map<int, string>::iterator it;
it = dataa.find(id);
if(it != dataa.end()) //we found something
{
QList<QStandardItem *> items;
items << new QStandardItem(QString("%1").arg(id));
items << new QStandardItem((*it).second);
mode1->appendRow(items);
}
else
QMessageBox::information(this, "Info", "ID not found!", QMessageBox::ok);
}
Something like this;
As far as I understand your question. You need to create a new slot in the Box1 class. Let's call it textReturnPressed(). Then you have to connect it to returnPressed() signal from text
connect(text, SIGNAL(returnPressed()), this, SLOT(textReturnPressed());
and here is textReturnPressed (I hope it compiles)
void textReturnPressed()
{
bool ok;
int id = text->text().toInt(&ok);
if (ok && dataa.count(id) > 0) {
QList<QStandardItem *> items;
items << new QStandardItem(QString("%1").arg(id));
items << new QStandardItem(dataa[id]);
mode1.appendRow(items);
}
}
You don't need an iterator to check if an item is in the map. Just call map.count() function.

QAbstactTableModel insert at top

I have addFile function in my TableModel class which inserts a new record at the end.
void TableModel::addFile(const QString &path)
{
beginInsertRows(QModelIndex(), list.size(),list.size());
TableItem item;
item.filename = path;
QFile file(path);
item.size = file.size();
item.status = StatusNew;
list << item;
endInsertRows();
}
This function works fine but instead of appending record at the end I would like to insert it at the top. Any pointers on how to update my existing function ?
I have already tried some combinations but there is no Luck.
There are two things that you need to do. First is to adjust the call to beginInsertRows. Because it is here that we are telling the model that we are adding rows, where they will go, and how many we are adding. Here is the method description:
void QAbstractItemModel::beginInsertRows ( const QModelIndex & parent,
int first, int last )
So in your case since you want to add a row at the first index, and only one row, we pass 0 as the index of the first item, and 0 which is the index of the last item we are adding (because of course we are only adding one item).
beginInsertRows(modelIndex(), 0, 0);
Next we have to provide the data for the item. I assume that 'list' is a QList (if not it is probably similar). So we want to call the 'insert' method.
list.insert(0, item);
And that should be it.
For display you can try delegates as explained in the link (I haven't tried the example though). It will help the community if you can add your observations.
Thanks to everyone for replying. I have found the solution by my own:
In case if anyone is interested
void TableModel::addFile(const QString &path)
{
beginInsertRows(QModelIndex(), list.size(), list.size());
TableItem item;
item.filename = path;
QFile file(path);
item.size = file.size();
item.status = StatusNew;
list << item; // Why Assign first? Maybe not required
for (int i = list.size() - 1; i > 0; i--)
{
list[i] = list[i-1];
}
list[0] = item; // set newly added item at the top
endInsertRows();
}

Resources