views:

2827

answers:

9

Hi. First of all, sorry for bad English.

It's about C++ and Qt. I have a SQLite-Database and I did it into a QSqlTableModel. To show the Database, I put that Model into a QTableView.

Now I want to create a Method where the selected Rows (or the whole Line) will be copied into the QClipboard. After that I want to insert it into my OpenOffice.Calc-Document.

But I have no Idea what to do with the "Selected"-SIGNAL and the QModelIndex and how to put this into the Clipboard.

So can you please help me?

Berschi

+1  A: 

What you'll need to do is access the text data in the model, then pass that text to the QClipboard.

To access the text data in the model, use QModelIndex::data(). The default argument is Qt::DisplayRole, i.e. the displayed text.

Once you've retrieved the text, pass that text to the clipboard using QClipboard::setText().

swongu
thank you, it really helped, for now.I added following code: connect (tableView, SIGNAL(clicked(QModelIndex)), this, SLOT(copy(QModelIndex)));and this: void Widget::copy(QModelIndex sel) { clipboard->setText((sel.data(Qt::DisplayRole)).toString());}this works fine for one single Row.But if I select 2 or more rows, this doesn't work.How can I copy more rows or a whole line to the Clipboard?
Berschi
oh sorry, I'm really new here...and with row (comment above) I meant cell. sorry :(
Berschi
Because you're using clicked(QModelIndex), this will only return the cell the user clicked on. If you want to copy text from all selected cells, you'd use the solution quark proposed. BTW, you should add your code snippets as updates to your original question.
swongu
+5  A: 

To actually capture the selection you use the item view's selection model to get a list of indices. Given that you have a QTableView * called view you get the selection this way:

QAbstractItemModel * model = view->model();
QItemSelectionModel * selection = view->selectionModel()
QModelIndexList indexes = selection->selectedIndexes();

Then loop through the index list calling model->data(index) on each index. Convert the data to a string if it isn't already and concatenate each string together. Then you can use QClipboard.setText to paste the result to the clipboard. Note that, for Excel and Calc, each column is separated from the next by a newline ("\n") and each row is separated by a tab ("\t"). You have to check the indices to determine when you move to the next row.

QString selected_text;
// You need a pair of indexes to find the row changes
QModelIndex previous = indexes.first();
indexes.removeFirst();
foreach(current, indexes)
{
    QVariant data = model->data(current);
    QString text = data.toString();
    // At this point `text` contains the text in one cell
    selected_text.append(text);
    // If you are at the start of the row the row number of the previous index
    // isn't the same.  Text is followed by a row separator, which is a newline.
    if (current.row() != previous.row())
    {
        selected_text.append('\n');
    }
    // Otherwise it's the same row, so append a column separator, which is a tab.
    else
    {
        selected_text.append('\t');
    }
    previous = current;
}
QApplication.clipboard().setText(selected_text);

Warning: I have not had a chance to try this code, but a PyQt equivalent works.

quark
sorry, but I don't get it how to use QItemSelectionModel and QModelIndexList in this case with the Model and TableView
Berschi
Here, you can also use the convenient function QAbstractItemView::selectedIndexes() that is available on your QTableView (in which QAbstractItemView is a parent of). What's returned is a simple list container of QModelIndex objects.
swongu
I've expanded a little on the example to illustrate what swongu describes.
quark
thank you very much, I also got a (not so nice) solution, but it works.
Berschi
thank you for the above, this was very helpful. I did encounter a couple bugs with the above.1. The cells need to be sorted in row ascending orderstd::sort(indexes.begin(), indexes.end());2. The first cell gets left out in the above logic. It should read<code>foreach{QVariant data = model()->data(previous);...}// add last elementselected_text.append(model()->data(current).toString());selected_text.append(QLatin1Char('\n'));</code>
Corwin Joy
A: 

I finally got it, thanks.

void Widget::copy() {

QItemSelectionModel *selectionM = tableView->selectionModel();
QModelIndexList selectionL = selectionM->selectedIndexes();

selectionL.takeFirst(); // ID, not necessary
QString *selectionS = new QString(model->data(selectionL.takeFirst()).toString());
selectionS->append(", ");
selectionS->append(model->data(selectionL.takeFirst()).toString());
selectionS->append(", ");
selectionS->append(model->data(selectionL.takeFirst()).toString());
selectionS->append(", ");
selectionS->append(model->data(selectionL.takeFirst()).toString());

clipboard->setText(*selectionS);
}

and

connect (tableView, SIGNAL(clicked(QModelIndex)), this, SLOT(copy()));
Berschi
If you use a QStringList, you can take advantage of the << operator and the QStringList::join() function.
swongu
A: 

I can't help but notice that you can simplify your code using a foreach() construct and the QStringList class, which has a convenient join() function.

void Widget::copy()
{
   QStringList list ;
   foreach ( const QModelIndex& index, tableView->selectedIndexes() )
   {
      list << index.data() ;
   }

   clipboard->setText( list.join( ", " ) ) ;
}
swongu
thanks for your help, the foreach-method was new to me
Berschi
+1  A: 

I had a similar problem and ended up adapting QTableWidget (which is an extension of QTableView) to add copy/paste functionality. Here is the code which builds on what was provided by quark above:

// QTableWidget with support for copy and paste added
// Here copy and paste can copy/paste the entire grid of cells
class QTableWidgetWithCopyPaste : public QTableWidget
{
public:
  QTableWidgetWithCopyPaste(int rows, int columns, QWidget *parent) :
      QTableWidget(rows, columns, parent)
  {};

  QTableWidgetWithCopyPaste(QWidget *parent) :
  QTableWidget(parent)
  {};

private:
  void copy();
  void paste();

protected:
  virtual void keyPressEvent(QKeyEvent * event);
};

void QTableWidgetWithCopyPaste::copy()
{
  QItemSelectionModel * selection = selectionModel();
  QModelIndexList indexes = selection->selectedIndexes();

  if(indexes.size() < 1)
    return;

  // QModelIndex::operator < sorts first by row, then by column. 
  // this is what we need
  std::sort(indexes.begin(), indexes.end());

  // You need a pair of indexes to find the row changes
  QModelIndex previous = indexes.first();
  indexes.removeFirst();
  QString selected_text;
  QModelIndex current;
  Q_FOREACH(current, indexes)
  {
    QVariant data = model()->data(previous);
    QString text = data.toString();
    // At this point `text` contains the text in one cell
    selected_text.append(text);
    // If you are at the start of the row the row number of the previous index
    // isn't the same.  Text is followed by a row separator, which is a newline.
    if (current.row() != previous.row())
    {
      selected_text.append(QLatin1Char('\n'));
    }
    // Otherwise it's the same row, so append a column separator, which is a tab.
    else
    {
      selected_text.append(QLatin1Char('\t'));
    }
    previous = current;
  }

  // add last element
  selected_text.append(model()->data(current).toString());
  selected_text.append(QLatin1Char('\n'));
  qApp->clipboard()->setText(selected_text);
}

void QTableWidgetWithCopyPaste::paste()
{
  QString selected_text = qApp->clipboard()->text();
  QStringList cells = selected_text.split(QRegExp(QLatin1String("\\n|\\t")));
  while(!cells.empty() && cells.back().size() == 0)
  {
    cells.pop_back(); // strip empty trailing tokens
  }
  int rows = selected_text.count(QLatin1Char('\n'));
  int cols = cells.size() / rows;
  if(cells.size() % rows != 0)
  {
    // error, uneven number of columns, probably bad data
    QMessageBox::critical(this, tr("Error"), 
      tr("Invalid clipboard data, unable to perform paste operation."));
    return;
  }

  if(cols != columnCount())
  {
    // error, clipboard does not match current number of columns
    QMessageBox::critical(this, tr("Error"), 
      tr("Invalid clipboard data, incorrect number of columns."));
    return;
  }

  // don't clear the grid, we want to keep any existing headers
  setRowCount(rows);
  // setColumnCount(cols);
  int cell = 0;
  for(int row=0; row < rows; ++row)
  {
    for(int col=0; col < cols; ++col, ++cell)
    {
      QTableWidgetItem *newItem = new QTableWidgetItem(cells[cell]);
      setItem(row, col, newItem);
    }
  }
}

void QTableWidgetWithCopyPaste::keyPressEvent(QKeyEvent * event)
{
  if(event->matches(QKeySequence::Copy) )
  {
    copy();
  }
  else if(event->matches(QKeySequence::Paste) )
  {
    paste();
  }
  else
  {
    QTableWidget::keyPressEvent(event);
  }

}
Corwin Joy
A: 

For whatever reason I didn't have access to the std::sort function, however I did find that as a neat alternative to Corwin Joy's solution, the sort function can be implemented by replacing

 std::sort(indexes.begin(), indexes.end());

with

  qSort(indexes);

This is the same as writing:

 qSort(indexes.begin(), indexes.end());

Thanks for your helpful code guys!

Cotlone
A: 

Careful with the last element. Note below, indexes may become empty after 'removeFirst()'. Thus, 'current' is never valid and should not be used in model()->data(current).

  indexes.removeFirst();
  QString selected_text;
  QModelIndex current;
  Q_FOREACH(current, indexes)
  {
  .
  .
  .
  }
  // add last element
  selected_text.append(model()->data(current).toString());

Consider

  QModelIndex last = indexes.last();
  indexes.removeFirst();
  QString selected_text;
  Q_FOREACH(QModelIndex current, indexes)
  {
  .
  .
  .
  }
  // add last element
  selected_text.append(model()->data(last).toString());
Russell Collison
A: 

I've been trying to implement something similar to this table problem and have hit a snag. When I copy a single row or a single column everything works fine, however when I attempt to copy an actual nXm table problems occur. Basically everything is printed as a vertical list and then the last value is printed to the right of the second to last item. Any ideas?

Phil
A: 

a pyqt py2.x example:

selection = self.table.selectionModel() #self.table = QAbstractItemView
indexes = selection.selectedIndexes()

columns = indexes[-1].column() - indexes[0].column() + 1
rows = len(indexes) / columns
textTable = [[""] * columns for i in xrange(rows)]

for i, index in enumerate(indexes):
 textTable[i % rows][i / rows] = unicode(self.model.data(index).toString()) #self.model = QAbstractItemModel 

return "\n".join(("\t".join(i) for i in textTable))
hugo24