/* Copyright (c) 2008-2024 Jan W. Krieger () This software is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (LGPL) as published by the Free Software Foundation, either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License (LGPL) for more details. You should have received a copy of the GNU Lesser General Public License (LGPL) along with this program. If not, see . */ #include "jkqtplotter/gui/jkqtpenhancedtableview.h" #include #include #include #include #include #include #include #include #include #ifndef JKQTPLOTTER_COMPILE_WITHOUT_PRINTSUPPORT # include # include #endif #include #include "jkqtplotter/jkqtptools.h" #include "jkqtcommon/jkqtpenhancedpainter.h" JKQTPEnhancedTableView::JKQTPEnhancedTableView(QWidget *parent): QTableView(parent) { setContextMenuPolicy(Qt::ActionsContextMenu); QAction* act; act=new QAction(QIcon(":/JKQTPlotter/jkqtp_copy16.png"), tr("Copy Selection to Clipboard (for Excel ...)"), this); connect(act, SIGNAL(triggered()), this, SLOT(copySelectionToExcel())); addAction(act); act=new QAction(QIcon(":/JKQTPlotter/jkqtp_copy16_nohead.png"), tr("Copy Selection to clipboard (for Excel ...) without header row/column"), this); connect(act, SIGNAL(triggered()), this, SLOT(copySelectionToExcelNoHead())); addAction(act); act=new QAction(QIcon(":/JKQTPlotter/jkqtp_copy16.png"), tr("Copy Selection to Clipboard (as CSV)"), this); connect(act, SIGNAL(triggered()), this, SLOT(copySelectionToCSV())); addAction(act); act=new QAction(QIcon(":/JKQTPlotter/jkqtp_copy16_nohead.png"), tr("Copy Selection to clipboard (as CSV ...) without header row/column"), this); connect(act, SIGNAL(triggered()), this, SLOT(copySelectionToCSVNoHead())); addAction(act); #ifndef JKQTPLOTTER_COMPILE_WITHOUT_PRINTSUPPORT act=new QAction(QIcon(":/JKQTPlotter/jkqtp_printtable.png"), tr("Print Table"), this); connect(act, SIGNAL(triggered()), this, SLOT(print())); addAction(act); printAction=act; #endif } JKQTPEnhancedTableView::~JKQTPEnhancedTableView() = default; QString JKQTPEnhancedTableView::toHtml(int borderWidth, bool /*non_breaking*/, int fontSizePt) const { if (!model()) return ""; QString fsstyle=""; if (fontSizePt>0) { fsstyle=QString(" font-size: %1pt;").arg(fontSizePt); } QString html=QString("").arg(borderWidth); for (int row=-1; rowrowCount(); row++) { html+=""; if (row==-1) { for (int col=-1; colcolumnCount(); col++) { html+=QString(""; } } else { for (int col=-1; colcolumnCount(); col++) { if (col==-1) { html+=QString(""; } else { QModelIndex index=model()->index(row, col); QVariant check=index.data(Qt::CheckStateRole); QBrush back=index.data(Qt::BackgroundRole).value(); QString style=fsstyle+"white-space: nowrap; "; //qDebug()<<"r="<").arg(fsstyle); else html+=QString(""; } } } html+=""; } html+="
").arg(fsstyle); if (col>=0) { html+=model()->headerData(col, Qt::Horizontal).toString(); } html+="").arg(fsstyle); html+=model()->headerData(row, Qt::Vertical).toString(); html+="").arg(style); if (check.isValid()) { if (check.toInt()!=0) { html+= QString("|×|  "); } else { html+= QString("| |  "); } } html+=index.data().toString(); html+="
"; return html; } void JKQTPEnhancedTableView::copySelectionToExcel(int copyrole, bool storeHead) { if (!model()) return; if (!selectionModel()) return; QModelIndexList sel=selectionModel()->selectedIndexes(); QLocale loc=QLocale::system(); loc.setNumberOptions(QLocale::OmitGroupSeparator); if (sel.size()==1) { QVariant vdata=sel[0].data(copyrole); QString txt=""; #if (QT_VERSION>=QT_VERSION_CHECK(6, 0, 0)) switch (vdata.typeId()) { case QMetaType::Int: case QMetaType::LongLong: case QMetaType::UInt: case QMetaType::ULongLong: case QMetaType::Bool: txt=vdata.toString(); break; case QMetaType::Double: txt=loc.toString(vdata.toDouble()); break; case QMetaType::QPointF: txt=loc.toString(vdata.toPointF().x()); break; default: txt=QString("\"%1\"").arg(vdata.toString().replace('"', "''").replace('\n', "\\n ").replace('\r', "\\r ").replace('\t', " ")); break; } #else switch (vdata.type()) { case QVariant::Int: case QVariant::LongLong: case QVariant::UInt: case QVariant::ULongLong: case QVariant::Bool: txt=vdata.toString(); break; case QVariant::Double: txt=loc.toString(vdata.toDouble()); break; case QVariant::PointF: txt=loc.toString(vdata.toPointF().x()); break; default: txt=QString("\"%1\"").arg(vdata.toString().replace('"', "''").replace('\n', "\\n ").replace('\r', "\\r ").replace('\t', " ")); break; } #endif QApplication::clipboard()->setText(txt); } else { QSet rows, cols; int colmin=0; int rowmin=0; for (int i=0; i= QT_VERSION_CHECK(5, 14, 0)) QList rowlist(rows.begin(), rows.end()); #else QList rowlist=QList::fromSet(rows); #endif std::sort(rowlist.begin(), rowlist.end()); #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QList collist(cols.begin(), cols.end()); #else QList collist=QList::fromSet(cols); #endif std::sort(collist.begin(), collist.end()); int rowcnt=rowlist.size(); int colcnt=collist.size(); QList data; // header row: // // | | | ... QStringList hrow; if (storeHead) { hrow.append(""); // empty header for first column (vertical headers!) for (int c=0; cheaderData(collist[c], Qt::Horizontal).toString().replace('"', "''").replace('\n', "\\n ").replace('\r', "\\r ").replace('\t', " "))); } data.append(hrow); } // now add dta rows: // // <~~~~~~~~~ colcnt times ~~~~~~~~~~> // | | | ... | for (int r=0; rheaderData(rowlist[r], Qt::Vertical).toString().replace('"', "''").replace('\n', "\\n ").replace('\r', "\\r ").replace('\t', " "))); // vertical header for (int c=0; c=QT_VERSION_CHECK(6, 0, 0)) switch (vdata.typeId()) { case QMetaType::Int: case QMetaType::LongLong: case QMetaType::UInt: case QMetaType::ULongLong: case QMetaType::Bool: txt=vdata.toString(); break; case QMetaType::Double: txt=loc.toString(vdata.toDouble()); break; case QMetaType::QPointF: txt=loc.toString(vdata.toPointF().x()); break; default: txt=QString("\"%1\"").arg(vdata.toString().replace('"', "''").replace('\n', "\\n ").replace('\r', "\\r ").replace('\t', " ")); break; } #else switch (vdata.type()) { case QVariant::Int: case QVariant::LongLong: case QVariant::UInt: case QVariant::ULongLong: case QVariant::Bool: txt=vdata.toString(); break; case QVariant::Double: txt=loc.toString(vdata.toDouble()); break; case QVariant::PointF: txt=loc.toString(vdata.toPointF().x()); break; default: txt=QString("\"%1\"").arg(vdata.toString().replace('"', "''").replace('\n', "\\n ").replace('\r', "\\r ").replace('\t', " ")); break; } #endif int shift=0; if (storeHead) shift=1; if ((r>=0) && (c>=0) && (r<=data.size()) && (c<=colcnt))data[r+shift][c+shift]=txt; } QString result=""; for (int r=0; rsetText(result); } } void JKQTPEnhancedTableView::copySelectionToExcelNoHead(int copyrole) { copySelectionToExcel(copyrole, false); } void JKQTPEnhancedTableView::copySelectionToCSV(int copyrole, bool storeHead, const QString &separator, const QChar &decimalpoint) { if (!model()) return; if (!selectionModel()) return; QModelIndexList sel=selectionModel()->selectedIndexes(); QLocale loc=QLocale::c(); loc.setNumberOptions(QLocale::OmitGroupSeparator); if (sel.size()==1) { QVariant vdata=sel[0].data(copyrole); QString txt=""; #if (QT_VERSION>=QT_VERSION_CHECK(6, 0, 0)) switch (vdata.typeId()) { case QMetaType::Int: case QMetaType::LongLong: case QMetaType::UInt: case QMetaType::ULongLong: case QMetaType::Bool: txt=vdata.toString(); break; case QMetaType::Double: txt=JKQTPDoubleToQString(vdata.toDouble(), 15, 'g', decimalpoint); break; case QMetaType::QPointF: txt=JKQTPDoubleToQString(vdata.toPointF().x(), 15, 'g', decimalpoint); break; default: txt=QString("\"%1\"").arg(vdata.toString().replace('"', "''").replace('\n', "\\n ").replace('\r', "\\r ").replace('\t', " ")); break; } #else switch (vdata.type()) { case QVariant::Int: case QVariant::LongLong: case QVariant::UInt: case QVariant::ULongLong: case QVariant::Bool: txt=vdata.toString(); break; case QVariant::Double: txt=JKQTPDoubleToQString(vdata.toDouble(), 15, 'g', decimalpoint); break; case QVariant::PointF: txt=JKQTPDoubleToQString(vdata.toPointF().x(), 15, 'g', decimalpoint); break; default: txt=QString("\"%1\"").arg(vdata.toString().replace('"', "''").replace('\n', "\\n ").replace('\r', "\\r ").replace('\t', " ")); break; } #endif QApplication::clipboard()->setText(txt); } else { QSet rows, cols; int colmin=0; int rowmin=0; for (int i=0; i= QT_VERSION_CHECK(5, 14, 0)) QList rowlist(rows.begin(), rows.end()); #else QList rowlist=QList::fromSet(rows); #endif std::sort(rowlist.begin(), rowlist.end()); #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QList collist(cols.begin(), cols.end()); #else QList collist=QList::fromSet(cols); #endif std::sort(collist.begin(), collist.end()); int rowcnt=rowlist.size(); int colcnt=collist.size(); QList data; // header row: // // | | | ... QStringList hrow; if (storeHead) { hrow.append(""); // empty header for first column (vertical headers!) for (int c=0; cheaderData(collist[c], Qt::Horizontal).toString().replace('"', "''").replace('\n', "\\n ").replace('\r', "\\r ").replace('\t', " "))); } data.append(hrow); } // now add dta rows: // // <~~~~~~~~~ colcnt times ~~~~~~~~~~> // | | | ... | for (int r=0; rheaderData(rowlist[r], Qt::Vertical).toString().replace('"', "''").replace('\n', "\\n ").replace('\r', "\\r ").replace('\t', " "))); // vertical header for (int c=0; c=QT_VERSION_CHECK(6, 0, 0)) switch (vdata.typeId()) { case QMetaType::Int: case QMetaType::LongLong: case QMetaType::UInt: case QMetaType::ULongLong: case QMetaType::Bool: txt=vdata.toString(); break; case QMetaType::Double: txt=JKQTPDoubleToQString(vdata.toDouble(), 15, 'g', decimalpoint); break; case QMetaType::QPointF: txt=JKQTPDoubleToQString(vdata.toPointF().x(), 15, 'g', decimalpoint); break; default: txt=QString("\"%1\"").arg(vdata.toString().replace('"', "''").replace('\n', "\\n ").replace('\r', "\\r ").replace('\t', " ")); break; } #else switch (vdata.type()) { case QVariant::Int: case QVariant::LongLong: case QVariant::UInt: case QVariant::ULongLong: case QVariant::Bool: txt=vdata.toString(); break; case QVariant::Double: txt=JKQTPDoubleToQString(vdata.toDouble(), 15, 'g', decimalpoint); break; case QVariant::PointF: txt=JKQTPDoubleToQString(vdata.toPointF().x(), 15, 'g', decimalpoint); break; default: txt=QString("\"%1\"").arg(vdata.toString().replace('"', "''").replace('\n', "\\n ").replace('\r', "\\r ").replace('\t', " ")); break; } #endif int shift=0; if (storeHead) shift=1; if ((r>=0) && (c>=0) && (r<=data.size()) && (c<=colcnt))data[r+shift][c+shift]=txt; } QString result=""; for (int r=0; rsetText(result); } } void JKQTPEnhancedTableView::copySelectionToCSVNoHead(int copyrole, const QString &separator, const QChar &decimalpoint) { copySelectionToCSV(copyrole, false, separator, decimalpoint); } void JKQTPEnhancedTableView::keyPressEvent(QKeyEvent *event) { if (event->matches(QKeySequence::Copy)) { copySelectionToExcel(Qt::EditRole, false); event->accept(); #ifndef JKQTPLOTTER_COMPILE_WITHOUT_PRINTSUPPORT } else if (event->matches(QKeySequence::Print)) { print(); event->accept(); #endif } else QTableView::keyPressEvent(event); emit keyPressed(event->key(), event->modifiers(), event->text()); } #ifndef JKQTPLOTTER_COMPILE_WITHOUT_PRINTSUPPORT void JKQTPEnhancedTableView::print() { QPrinter* tablePrinter=getPrinter(nullptr); if (tablePrinter) { QDialog* dlg=new QDialog(this); dlg->setWindowTitle(tr("Table print options ...")); QGridLayout* lay=new QGridLayout(); dlg->setLayout(lay); lay->addWidget(new QLabel(tr("scaling:")), 0,0); QCheckBox* chk1Wide=new QCheckBox(tr("one page wide"), dlg); QCheckBox* chk1High=new QCheckBox(tr("one page high"), dlg); lay->addWidget(chk1Wide, 0, 1); lay->addWidget(chk1High, 1, 1); QDialogButtonBox* btns=new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel,Qt::Horizontal,dlg); connect(btns, SIGNAL(accepted()), dlg, SLOT(accept())); connect(btns, SIGNAL(rejected()), dlg, SLOT(reject())); lay->addWidget(btns, 2, 0,1,2); if (dlg->exec()) { print(tablePrinter, chk1Wide->isChecked(), chk1High->isChecked()); } delete dlg; delete tablePrinter; } } #endif #ifndef JKQTPLOTTER_COMPILE_WITHOUT_PRINTSUPPORT void JKQTPEnhancedTableView::print(QPrinter *printer, bool onePageWide, bool onePageHigh) { QPrinter* p=printer; //p->setPageMargins(10,10,10,10,QPageLayout::Millimeter); /*if (width()>height()) { p->setPageOrientation(QPageLayout::Landscape); } else { p->setPageOrientation(QPageLayout::Portrait); }*/ clearSelection(); /// PRINT HERE ////////////////////////////////////////////////////////////////////////////////// // calculate the total width/height table would need without scaling const int rows = model()->rowCount(); const int cols = model()->columnCount(); double vhw=verticalHeader()->width()+8; double totalWidth = vhw; double minWidth=1e33; double maxWidth=0; for (int c = -1; c < cols; ++c) { double w=columnWidth(c); totalWidth += w; if (wmaxWidth) maxWidth=w; } double hhh=horizontalHeader()->height()+8; double totalHeight = hhh; double minHeight=1e33; double maxHeight=0; for (int r = 0; r < rows; ++r) { double h=rowHeight(r); totalHeight += h; if (hmaxHeight) maxHeight=h; } double scale=1.0; // adjust scale, so the widest/highest column fits on one page /*if (maxWidth*scale>p->pageLayout().paintRectPixels(p->resolution()).width()) scale=p->pageLayout().paintRectPixels(p->resolution()).width()/maxWidth; if (maxHeight*scale>p->pageLayout().paintRectPixels(p->resolution()).height()) scale=p->pageLayout().paintRectPixels(p->resolution()).height()/maxHeight;*/ if (onePageWide) { if (totalWidth>p->pageLayout().paintRectPixels(p->resolution()).width()) scale=p->pageLayout().paintRectPixels(p->resolution()).width()/totalWidth; } if (onePageHigh) { if (totalHeight>p->pageLayout().paintRectPixels(p->resolution()).height()) scale=qMin(scale, p->pageLayout().paintRectPixels(p->resolution()).height()/totalHeight); } //qDebug()< pageCols, pageRows; pageCols<<0; pageRows<<0; { // find number of pages needed double x=vhw, x0=vhw; if (!onePageWide) { for (int c=0; cp->pageLayout().paintRectPixels(p->resolution()).width()/scale) { pagesWide++; x=0; pageCols<0 && cols>pageCols.last()) pageCols<p->pageLayout().paintRectPixels(p->resolution()).height()/scale) { pagesHigh++; pageRows<0 && rows>pageRows.last()) pageRows<width()+8; double hhh=horizontalHeader()->height()+8; double scaleX=size.width()/double(pageRec.width()); double scaleY=size.height()/double(pageRec.height()); double scale=qMin(scaleX, scaleY); QListpageCols, pageRows; pageCols<<0<columnCount(); pageRows<<0<rowCount(); paint(painter, scale, -1, hhh, vhw, pageCols, pageRows); } QSizeF JKQTPEnhancedTableView::getTotalSize() const { const int rows = model()->rowCount(); const int cols = model()->columnCount(); double vhw=verticalHeader()->width()+8; double totalWidth = vhw; for (int c = -1; c < cols; ++c) { double w=columnWidth(c); totalWidth += w; } double hhh=horizontalHeader()->height()+8; double totalHeight = hhh; for (int r = 0; r < rows; ++r) { double h=rowHeight(r); totalHeight += h; } return QSizeF((totalWidth), (totalHeight)); } #ifndef JKQTPLOTTER_COMPILE_WITHOUT_PRINTSUPPORT void JKQTPEnhancedTableView::paint(QPainter &painter, double scale, int page, double hhh, double vhw, const QList& pageCols, const QList& pageRows, QPrinter* p) #else void JKQTPEnhancedTableView::paint(QPainter &painter, double scale, int page, double hhh, double vhw, const QList& pageCols, const QList& pageRows) #endif { painter.save(); auto __finalpaint=JKQTPFinally([&painter]() {painter.restore();}); QStyleOptionViewItem option; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) initViewItemOption(&option); #else option = viewOptions(); #endif painter.scale(scale, scale); QPen headerPen("black"); headerPen.setWidth(2); QPen cellPen("gray"); cellPen.setWidth(1); QFont headerFont=horizontalHeader()->font(); headerFont.setBold(true); int pagesWide=pageCols.size()-1; int pagesHigh=pageRows.size()-1; //painter.translate(p->pageLayout().paintRectPixels(p->resolution()).topLeft()); int pageCnt=0; for (int ph=0; phpageLayout().paintRectPixels(p->resolution()).topLeft(), pic); double y=0; if (ph==0) { y=hhh; } //qDebug()<<" rows = "<(x), static_cast(y), static_cast(vhw), rh); //verticalHeader()->itemDelegate()->paint(&painter, option, model()->index(r, c, QModelIndex())); x=vhw; } for (int c=pageCols[pw]; c(x), static_cast(y), static_cast(cw), rh); itemDelegate()->paint(&painter, option, model()->index(r, c, QModelIndex())); painter.setPen(cellPen); painter.drawRect(option.rect); x=x+cw; } y=y+rh; } if (ph==0) { y=0; int x=0; if (pw%pagesWide==0) x=static_cast(vhw); painter.setPen(headerPen); for (int c=pageCols[pw]; c(y), columnWidth(c), static_cast(hhh)); painter.fillRect(rec, QColor("lightgrey")); painter.setFont(headerFont); painter.setPen(headerPen); painter.drawText(QRect(rec.x()+4, rec.y()+4, rec.width()-8, rec.height()-8), model()->headerData(c, Qt::Horizontal).toString()); painter.drawRect(rec); //if (x==vhw &&) painter.drawLine(rec.topLeft(), QPoint(rec.left(), p->pageLayout().paintRectPixels(p->resolution()).height())); x=x+columnWidth(c); } } if (pw%pagesWide==0) { y=0; int x=0; if (ph==0) y=hhh; for (int r=pageRows[ph]; r(y), static_cast(vhw), rowHeight(r)); painter.fillRect(rec, QColor("lightgrey")); painter.setPen(headerPen); painter.setFont(headerFont); painter.drawText(QRect(rec.x()+4, rec.y()+4, rec.width()-8, rec.height()-8), model()->headerData(r, Qt::Vertical).toString()); painter.drawRect(rec); //if (x==vhw &&) painter.drawLine(rec.topLeft(), QPoint(rec.left(), p->pageLayout().paintRectPixels(p->resolution()).height())); y=y+rowHeight(r); } } #ifndef JKQTPLOTTER_COMPILE_WITHOUT_PRINTSUPPORT if (p && pwnewPage(); #endif } pageCnt++; } #ifndef JKQTPLOTTER_COMPILE_WITHOUT_PRINTSUPPORT if (p && phnewPage(); #endif } } #ifndef JKQTPLOTTER_COMPILE_WITHOUT_PRINTSUPPORT QPrinter *JKQTPEnhancedTableView::getPrinter(QPrinter *printerIn, bool *localPrinter) { QPrinter* p=printerIn; if (p==nullptr) { p=new QPrinter(); if (localPrinter) *localPrinter=true; } QPrintDialog *dialog = new QPrintDialog(p, nullptr); dialog->setWindowTitle(tr("Print Table")); if (dialog->exec() != QDialog::Accepted) { if (localPrinter && *localPrinter) delete p; delete dialog; return printerIn; } p=dialog->printer(); delete dialog; return p; } #endif