File indexing completed on 2024-04-28 16:32:04
0001 /*************************************************************************** 0002 Copyright (C) 2005-2020 Robby Stephenson <robby@periapsis.org> 0003 ***************************************************************************/ 0004 0005 /*************************************************************************** 0006 * * 0007 * This program is free software; you can redistribute it and/or * 0008 * modify it under the terms of the GNU General Public License as * 0009 * published by the Free Software Foundation; either version 2 of * 0010 * the License or (at your option) version 3 or any later version * 0011 * accepted by the membership of KDE e.V. (or its successor approved * 0012 * by the membership of KDE e.V.), which shall act as a proxy * 0013 * defined in Section 14 of version 3 of the license. * 0014 * * 0015 * This program is distributed in the hope that it will be useful, * 0016 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0018 * GNU General Public License for more details. * 0019 * * 0020 * You should have received a copy of the GNU General Public License * 0021 * along with this program. If not, see <http://www.gnu.org/licenses/>. * 0022 * * 0023 ***************************************************************************/ 0024 0025 #include <config.h> 0026 #include "reportdialog.h" 0027 #include "translators/htmlexporter.h" 0028 #include "images/imagefactory.h" 0029 #include "tellico_kernel.h" 0030 #include "collection.h" 0031 #include "document.h" 0032 #include "entry.h" 0033 #include "controller.h" 0034 #include "tellico_debug.h" 0035 #include "gui/combobox.h" 0036 #include "utils/cursorsaver.h" 0037 #include "utils/datafileregistry.h" 0038 #include "utils/tellico_utils.h" 0039 #include "config/tellico_config.h" 0040 #ifdef HAVE_QCHARTS 0041 #include "charts/chartmanager.h" 0042 #include "charts/chartreport.h" 0043 #endif 0044 0045 #include <KLocalizedString> 0046 #include <KStandardGuiItem> 0047 #include <KWindowConfig> 0048 #include <KConfigGroup> 0049 0050 #include <QFile> 0051 #include <QLabel> 0052 #include <QFileInfo> 0053 #include <QTimer> 0054 #include <QTextStream> 0055 #include <QHBoxLayout> 0056 #include <QVBoxLayout> 0057 #include <QPushButton> 0058 #include <QFileDialog> 0059 #include <QDialogButtonBox> 0060 #include <QStackedWidget> 0061 #include <QScrollArea> 0062 #include <QPainter> 0063 #include <QPrinter> 0064 #include <QPrintDialog> 0065 #include <QTemporaryFile> 0066 0067 #ifdef USE_KHTML 0068 #include <KHTMLPart> 0069 #include <KHTMLView> 0070 #else 0071 #include <QWebEngineView> 0072 #include <QWebEnginePage> 0073 #include <QWebEngineSettings> 0074 #endif 0075 0076 namespace { 0077 static const int REPORT_MIN_WIDTH = 600; 0078 static const int REPORT_MIN_HEIGHT = 420; 0079 static const char* dialogOptionsString = "Report Dialog Options"; 0080 static const int INDEX_HTML = 0; 0081 static const int INDEX_CHART = 1; 0082 } 0083 0084 using Tellico::ReportDialog; 0085 0086 // default button is going to be used as a print button, so it's separated 0087 ReportDialog::ReportDialog(QWidget* parent_) 0088 : QDialog(parent_), m_exporter(nullptr), m_tempFile(nullptr) { 0089 setModal(false); 0090 setWindowTitle(i18n("Collection Report")); 0091 0092 QWidget* mainWidget = new QWidget(this); 0093 QVBoxLayout* mainLayout = new QVBoxLayout(); 0094 setLayout(mainLayout); 0095 mainLayout->addWidget(mainWidget); 0096 0097 QVBoxLayout* topLayout = new QVBoxLayout(mainWidget); 0098 0099 QBoxLayout* hlay = new QHBoxLayout(); 0100 topLayout->addLayout(hlay); 0101 QLabel* l = new QLabel(i18n("&Report template:"), mainWidget); 0102 hlay->addWidget(l); 0103 0104 QStringList files = Tellico::locateAllFiles(QStringLiteral("tellico/report-templates/*.xsl")); 0105 QMap<QString, QVariant> templates; // gets sorted by title 0106 foreach(const QString& file, files) { 0107 QFileInfo fi(file); 0108 const QString lfile = fi.fileName(); 0109 // the Group Summary report template doesn't work with QWebView 0110 #ifndef USE_KHTML 0111 if(lfile == QStringLiteral("Group_Summary.xsl")) { 0112 continue; 0113 } 0114 #endif 0115 QString name = lfile.section(QLatin1Char('.'), 0, -2); 0116 name.replace(QLatin1Char('_'), QLatin1Char(' ')); 0117 QString title = i18nc((name + QLatin1String(" XSL Template")).toUtf8().constData(), name.toUtf8().constData()); 0118 templates.insert(title, lfile); 0119 } 0120 #ifdef HAVE_QCHARTS 0121 // add the chart reports 0122 foreach(const auto& report, ChartManager::self()->allReports()) { 0123 templates.insert(report->title(), report->uuid()); 0124 } 0125 #endif 0126 0127 m_templateCombo = new GUI::ComboBox(mainWidget); 0128 for(auto it = templates.constBegin(); it != templates.constEnd(); ++it) { 0129 const bool isChart = static_cast<QMetaType::Type>(it.value().type()) == QMetaType::QUuid; 0130 m_templateCombo->addItem(QIcon::fromTheme(isChart ? QStringLiteral("kchart") : QStringLiteral("text-rdf")), 0131 it.key(), it.value()); 0132 } 0133 hlay->addWidget(m_templateCombo); 0134 l->setBuddy(m_templateCombo); 0135 0136 QPushButton* pb1 = new QPushButton(mainWidget); 0137 KGuiItem::assign(pb1, KGuiItem(i18n("&Generate"), QStringLiteral("application-x-executable"))); 0138 hlay->addWidget(pb1); 0139 connect(pb1, &QAbstractButton::clicked, this, &ReportDialog::slotGenerate); 0140 0141 hlay->addStretch(); 0142 0143 QPushButton* pb2 = new QPushButton(mainWidget); 0144 KGuiItem::assign(pb2, KStandardGuiItem::saveAs()); 0145 hlay->addWidget(pb2); 0146 connect(pb2, &QAbstractButton::clicked, this, &ReportDialog::slotSaveAs); 0147 0148 QPushButton* pb3 = new QPushButton(mainWidget); 0149 KGuiItem::assign(pb3, KStandardGuiItem::print()); 0150 hlay->addWidget(pb3); 0151 connect(pb3, &QAbstractButton::clicked, this, &ReportDialog::slotPrint); 0152 0153 QColor color = palette().color(QPalette::Link); 0154 QString text = QString::fromLatin1("<html><style>p{font-family:sans-serif;font-weight:bold;width:50%;" 0155 "margin:20% auto auto auto;text-align:center;" 0156 "color:%1;}</style><body><p>").arg(color.name()) 0157 + i18n("Select a report template and click <em>Generate</em>.") + QLatin1Char(' ') 0158 + i18n("Some reports may take several seconds to generate for large collections.") 0159 + QLatin1String("</p></body></html>"); 0160 0161 m_reportView = new QStackedWidget(mainWidget); 0162 topLayout->addWidget(m_reportView); 0163 0164 #ifdef USE_KHTML 0165 m_HTMLPart = new KHTMLPart(m_reportView); 0166 m_HTMLPart->setJScriptEnabled(true); 0167 m_HTMLPart->setJavaEnabled(false); 0168 m_HTMLPart->setMetaRefreshEnabled(false); 0169 m_HTMLPart->setPluginsEnabled(false); 0170 0171 m_HTMLPart->begin(); 0172 m_HTMLPart->write(text); 0173 m_HTMLPart->end(); 0174 m_reportView->insertWidget(INDEX_HTML, m_HTMLPart->view()); 0175 #else 0176 m_webView = new QWebEngineView(m_reportView); 0177 connect(m_webView, &QWebEngineView::loadFinished, this, [](bool b) { 0178 if(!b) myDebug() << "ReportDialog - failed to load view"; 0179 }); 0180 QWebEngineSettings* settings = m_webView->page()->settings(); 0181 settings->setAttribute(QWebEngineSettings::JavascriptEnabled, true); 0182 settings->setAttribute(QWebEngineSettings::PluginsEnabled, false); 0183 settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); 0184 settings->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, true); 0185 0186 m_webView->setHtml(text); 0187 m_reportView->insertWidget(INDEX_HTML, m_webView); 0188 #endif 0189 0190 QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); 0191 mainLayout->addWidget(buttonBox); 0192 connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); 0193 connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 0194 buttonBox->button(QDialogButtonBox::Close)->setDefault(true); 0195 0196 setMinimumWidth(qMax(minimumWidth(), REPORT_MIN_WIDTH)); 0197 setMinimumHeight(qMax(minimumHeight(), REPORT_MIN_HEIGHT)); 0198 0199 QTimer::singleShot(0, this, &ReportDialog::slotUpdateSize); 0200 } 0201 0202 ReportDialog::~ReportDialog() { 0203 delete m_exporter; 0204 m_exporter = nullptr; 0205 delete m_tempFile; 0206 m_tempFile = nullptr; 0207 0208 KConfigGroup config(KSharedConfig::openConfig(), QLatin1String(dialogOptionsString)); 0209 KWindowConfig::saveWindowSize(windowHandle(), config); 0210 } 0211 0212 void ReportDialog::slotGenerate() { 0213 GUI::CursorSaver cs(Qt::WaitCursor); 0214 0215 QVariant curData = m_templateCombo->currentData(); 0216 if(static_cast<QMetaType::Type>(curData.type()) == QMetaType::QUuid) { 0217 generateChart(); 0218 m_reportView->setCurrentIndex(INDEX_CHART); 0219 } else { 0220 generateHtml(); 0221 m_reportView->setCurrentIndex(INDEX_HTML); 0222 } 0223 } 0224 0225 void ReportDialog::generateChart() { 0226 #ifdef HAVE_QCHARTS 0227 const QUuid uuid = m_templateCombo->currentData().toUuid(); 0228 auto oldWidget = m_reportView->widget(INDEX_CHART); 0229 auto newWidget = ChartManager::self()->report(uuid)->createWidget(); 0230 if(newWidget) { 0231 m_reportView->insertWidget(INDEX_CHART, newWidget); 0232 } 0233 if(oldWidget) { 0234 m_reportView->removeWidget(oldWidget); 0235 delete oldWidget; 0236 } 0237 #endif 0238 } 0239 0240 void ReportDialog::generateHtml() { 0241 QString fileName = QLatin1String("report-templates/") + m_templateCombo->currentData().toString(); 0242 QString xsltFile = DataFileRegistry::self()->locate(fileName); 0243 if(xsltFile.isEmpty()) { 0244 myWarning() << "can't locate " << m_templateCombo->currentData().toString(); 0245 return; 0246 } 0247 // if it's the same XSL file, no need to reload the XSLTHandler, just refresh 0248 if(xsltFile == m_xsltFile) { 0249 slotRefresh(); 0250 return; 0251 } 0252 0253 m_xsltFile = xsltFile; 0254 0255 delete m_exporter; 0256 m_exporter = new Export::HTMLExporter(Data::Document::self()->collection()); 0257 m_exporter->setXSLTFile(m_xsltFile); 0258 m_exporter->setPrintHeaders(false); // the templates should take care of this themselves 0259 m_exporter->setPrintGrouped(true); // allow templates to take advantage of added DOM 0260 0261 slotRefresh(); 0262 } 0263 0264 void ReportDialog::slotRefresh() { 0265 if(!m_exporter) { 0266 myWarning() << "no exporter"; 0267 return; 0268 } 0269 0270 m_exporter->setGroupBy(Controller::self()->expandedGroupBy()); 0271 m_exporter->setSortTitles(Controller::self()->sortTitles()); 0272 m_exporter->setColumns(Controller::self()->visibleColumns()); 0273 // only print visible entries 0274 m_exporter->setEntries(Controller::self()->visibleEntries()); 0275 0276 long options = Export::ExportUTF8 | Export::ExportComplete | Export::ExportImages; 0277 if(Config::autoFormat()) { 0278 options |= Export::ExportFormatted; 0279 } 0280 m_exporter->setOptions(options); 0281 0282 // by setting the xslt file as the URL, any images referenced in the xslt "theme" can be found 0283 // by simply using a relative path in the xslt file 0284 QUrl u = QUrl::fromLocalFile(m_xsltFile); 0285 #ifdef USE_KHTML 0286 m_HTMLPart->begin(u); 0287 m_HTMLPart->write(m_exporter->text()); 0288 m_HTMLPart->end(); 0289 #else 0290 const auto exporterText = m_exporter->text(); 0291 // limit is 2 MB after percent encoding, etc., so give some padding 0292 if(exporterText.size() > 1200000) { 0293 delete m_tempFile; 0294 m_tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/tellicoreport_XXXXXX") + QLatin1String(".html")); 0295 m_tempFile->open(); 0296 QTextStream stream(m_tempFile); 0297 stream.setCodec("UTF-8"); 0298 stream << exporterText; 0299 m_webView->load(QUrl::fromLocalFile(m_tempFile->fileName())); 0300 } else { 0301 m_webView->setHtml(exporterText, u); 0302 } 0303 #endif 0304 #if 0 0305 myDebug() << "Remove debug from reportdialog.cpp"; 0306 QFile f(QLatin1String("/tmp/test.html")); 0307 if(f.open(QIODevice::WriteOnly)) { 0308 QTextStream t(&f); 0309 t << m_exporter->text(); 0310 } 0311 f.close(); 0312 #endif 0313 } 0314 0315 // actually the print button 0316 void ReportDialog::slotPrint() { 0317 if(m_reportView->currentIndex() == INDEX_CHART) { 0318 QPrinter printer; 0319 printer.setResolution(600); 0320 QPointer<QPrintDialog> dialog = new QPrintDialog(&printer, this); 0321 if(dialog->exec() == QDialog::Accepted) { 0322 QWidget* widget = m_reportView->currentWidget(); 0323 // there might be a widget inside a scroll area 0324 if(QScrollArea* scrollArea = qobject_cast<QScrollArea*>(widget)) { 0325 widget = scrollArea->widget(); 0326 } 0327 QPainter painter; 0328 painter.begin(&printer); 0329 auto const paintRect = printer.pageLayout().paintRectPixels(printer.resolution()); 0330 const double xscale = paintRect.width() / double(widget->width()); 0331 const double yscale = paintRect.height() / double(widget->height()); 0332 const double scale = 0.95*qMin(xscale, yscale); 0333 auto const paperRect = printer.pageLayout().fullRectPixels(printer.resolution()); 0334 painter.translate(paperRect.center()); 0335 painter.scale(scale, scale); 0336 painter.translate(-widget->width()/2, -widget->height()/2); 0337 widget->render(&painter); 0338 } 0339 } else { 0340 #ifdef USE_KHTML 0341 m_HTMLPart->view()->print(); 0342 #else 0343 QPrinter printer; 0344 printer.setResolution(300); 0345 QPointer<QPrintDialog> dialog = new QPrintDialog(&printer, this); 0346 if(dialog->exec() == QDialog::Accepted) { 0347 QEventLoop loop; 0348 GUI::CursorSaver cs(Qt::WaitCursor); 0349 m_webView->page()->print(&printer, [&](bool) { loop.quit(); }); 0350 loop.exec(); 0351 } 0352 #endif 0353 } 0354 } 0355 0356 void ReportDialog::slotSaveAs() { 0357 if(m_reportView->currentIndex() == INDEX_CHART) { 0358 QString filter = i18n("PNG Files") + QLatin1String(" (*.png)") 0359 + QLatin1String(";;") 0360 + i18n("All Files") + QLatin1String(" (*)"); 0361 QUrl u = QFileDialog::getSaveFileUrl(this, QString(), QUrl(), filter); 0362 if(!u.isEmpty() && u.isValid()) { 0363 QWidget* widget = m_reportView->currentWidget(); 0364 // there might be a widget inside a scroll area 0365 if(QScrollArea* scrollArea = qobject_cast<QScrollArea*>(widget)) { 0366 widget = scrollArea->widget(); 0367 } 0368 QPixmap pixmap(widget->size()); 0369 widget->render(&pixmap); 0370 pixmap.save(u.toLocalFile()); 0371 } 0372 } else if(m_exporter) { 0373 QString filter = i18n("HTML Files") + QLatin1String(" (*.html)") 0374 + QLatin1String(";;") 0375 + i18n("All Files") + QLatin1String(" (*)"); 0376 QUrl u = QFileDialog::getSaveFileUrl(this, QString(), QUrl(), filter); 0377 if(!u.isEmpty() && u.isValid()) { 0378 KConfigGroup config(KSharedConfig::openConfig(), "ExportOptions"); 0379 bool encode = config.readEntry("EncodeUTF8", true); 0380 long oldOpt = m_exporter->options(); 0381 0382 // turn utf8 off 0383 long options = oldOpt & ~Export::ExportUTF8; 0384 // now turn it on if true 0385 if(encode) { 0386 options |= Export::ExportUTF8; 0387 } 0388 0389 QUrl oldURL = m_exporter->url(); 0390 m_exporter->setOptions(options); 0391 m_exporter->setURL(u); 0392 0393 m_exporter->exec(); 0394 0395 m_exporter->setURL(oldURL); 0396 m_exporter->setOptions(oldOpt); 0397 } 0398 } 0399 } 0400 0401 void ReportDialog::slotUpdateSize() { 0402 KConfigGroup config(KSharedConfig::openConfig(), QLatin1String(dialogOptionsString)); 0403 KWindowConfig::restoreWindowSize(windowHandle(), config); 0404 }