1 /******************************************************************************
2   Copyright (C) 2009-2011 by Holger Danielsson (holger.danielsson@versanet.de)
3             (C) 2019 by Michel Ludwig (michel.ludwig@kdemail.net)
4  ******************************************************************************/
5 
6 /***************************************************************************
7  *                                                                         *
8  *   This program is free software; you can redistribute it and/or modify  *
9  *   it under the terms of the GNU General Public License as published by  *
10  *   the Free Software Foundation; either version 2 of the License, or     *
11  *   (at your option) any later version.                                   *
12  *                                                                         *
13  ***************************************************************************/
14 
15 
16 #include "pdfdialog.h"
17 
18 #include <QCheckBox>
19 #include <QDateTime>
20 #include <QDialogButtonBox>
21 #include <QFile>
22 #include <QGridLayout>
23 #include <QGroupBox>
24 #include <QInputDialog>
25 #include <QLabel>
26 #include <QLayout>
27 #include <QLineEdit>
28 #include <QLocale>
29 #include <QProcess>
30 #include <QPushButton>
31 #include <QRegExp>
32 #include <QStandardPaths>
33 #include <QStringList>
34 #include <QTemporaryFile>
35 #include <QTextStream>
36 #include <QValidator>
37 #include <QVBoxLayout>
38 
39 #include <KComboBox>
40 #include <KConfigGroup>
41 #include <KIconLoader>
42 #include <KLocalizedString>
43 #include <KMessageBox>
44 #include <KProcess>
45 #include <KUrlRequester>
46 
47 #include "errorhandler.h"
48 #include "kileconfig.h"
49 #include "kiledebug.h"
50 
51 
52 namespace KileDialog
53 {
54 
PdfDialog(QWidget * parent,const QString & texfilename,const QString & startdir,const QString & latexextensions,KileTool::Manager * manager,KileErrorHandler * errorHandler,KileWidget::OutputView * output)55 PdfDialog::PdfDialog(QWidget *parent,
56                      const QString &texfilename,const QString &startdir,
57                      const QString &latexextensions,
58                      KileTool::Manager *manager,
59                      KileErrorHandler *errorHandler, KileWidget::OutputView *output)
60     : QDialog(parent)
61     , m_startdir(startdir)
62     , m_manager(manager)
63     , m_errorHandler(errorHandler)
64     , m_output(output)
65     , m_tempdir(Q_NULLPTR)
66     , m_proc(Q_NULLPTR)
67     , m_rearrangeButton(new QPushButton)
68     , m_buttonBox(new QDialogButtonBox(QDialogButtonBox::Help|QDialogButtonBox::Close))
69 {
70     setWindowTitle(i18n("PDF Wizard"));
71     setModal(true);
72     QVBoxLayout *mainLayout = new QVBoxLayout;
73     setLayout(mainLayout);
74     m_rearrangeButton->setDefault(true);
75 
76     // determine if a pdffile already exists
77     QString pdffilename;
78     if(!texfilename.isEmpty()) {
79         // working with a pdf document, so we try to determine the LaTeX source file
80         QStringList extlist = latexextensions.split(' ');
81         for (QStringList::Iterator it = extlist.begin(); it != extlist.end(); ++it) {
82             if (texfilename.indexOf((*it), -(*it).length()) >= 0) {
83                 pdffilename = texfilename.left(texfilename.length() - (*it).length()) + ".pdf";
84                 if (!QFileInfo(pdffilename).exists())
85                     pdffilename.clear();
86                 break;
87             }
88         }
89     }
90 
91     // prepare dialog
92     QWidget *page = new QWidget(this);
93     mainLayout->addWidget(page);
94     m_PdfDialog.setupUi(page);
95     page->setMinimumWidth(500);
96     m_PdfDialog.m_pbPrinting->setIcon(QIcon::fromTheme("printer"));
97     m_PdfDialog.m_pbAll->setIcon(QIcon::fromTheme("list-add"));
98     m_PdfDialog.m_pbBackgroundColor->setColor(QColor(255, 255, 224));
99 
100     // insert KileWidget::CategoryComboBox
101     m_cbTask = new KileWidget::CategoryComboBox(m_PdfDialog.m_gbParameter);
102     QGridLayout *paramLayout = (QGridLayout *)m_PdfDialog.m_gbParameter->layout();
103     paramLayout->addWidget(m_cbTask, 4, 1);
104 
105     // setup filenames
106     m_PdfDialog.m_edInfile->setFilter(i18n("*.pdf|PDF Files"));
107     m_PdfDialog.m_edInfile->lineEdit()->setText(pdffilename);
108     m_PdfDialog.m_edOutfile->setFilter(i18n("*.pdf|PDF Files"));
109     m_PdfDialog.m_edOutfile->setMode(KFile::File | KFile::LocalOnly );
110     m_PdfDialog.m_edOutfile->lineEdit()->setText( getOutfileName(pdffilename) );
111 
112     //max password length for pdf files
113     m_PdfDialog.m_edPassword->setMaxLength(32);
114 
115     // set an user button to execute the task and icon for help button
116     m_rearrangeButton->setText(i18n("Re&arrange"));
117     m_rearrangeButton->setIcon(QIcon::fromTheme("system-run"));
118     m_PdfDialog.m_lbParameterIcon->setPixmap(KIconLoader::global()->loadIcon("help-about", KIconLoader::NoGroup, KIconLoader::SizeSmallMedium));
119 
120     // init important variables
121     m_numpages = 0;
122     m_encrypted = false;
123     m_pdftk = false;
124     m_pdfpages = false;
125     m_scriptrunning = false;
126     m_pagesize = QSize(0,0);
127 
128     // setup tasks
129     m_tasklist << i18n("1 Page + Empty Page --> 2up")           // 0   PDF_PAGE_EMPTY
130                << i18n("1 Page + Duplicate --> 2up")            // 1   PDF_PAGE_DUPLICATE
131                << i18n("2 Pages --> 2up")                       // 2   PDF_2UP
132                << i18n("2 Pages (landscape) --> 2up")           // 3   PDF_2UP_LANDSCAPE
133                << i18n("4 Pages --> 4up")                       // 4   PDF_4UP
134                << i18n("4 Pages (landscape) --> 4up")           // 5   PDF_4UP_LANDSCAPE
135                << i18n("Select Even Pages")                     // 6   PDF_EVEN
136                << i18n("Select Odd Pages")                      // 7   PDF_ODD
137                << i18n("Select Even Pages (reverse order)")     // 8   PDF_EVEN_REV
138                << i18n("Select Odd Pages (reverse order)")      // 9   PDF_ODD_REV
139                << i18n("Reverse All Pages")                     // 10  PDF_REVERSE
140                << i18n("Decrypt")                               // 11  PDF_DECRYPT
141                << i18n("Select Pages")                          // 12  PDF_SELECT
142                << i18n("Delete Pages")                          // 13  PDF_DELETE
143                << i18n("Apply a background watermark")          // 14  PDF_PDFTK_BACKGROUND
144                << i18n("Apply a background color")              // 15  PDF_PDFTK_BGCOLOR
145                << i18n("Apply a foreground stamp")              // 16  PDF_PDFTK_STAMP
146                << i18n("pdftk: Choose Parameter")               // 17  PDF_PDFTK_FREE
147                << i18n("pdfpages: Choose Parameter")            // 18  PDF_PDFPAGES_FREE
148                ;
149 
150     // set data for properties: key/widget
151     m_pdfInfoKeys << "Title" << "Subject" << "Author" << "Creator" << "Producer" << "Keywords";
152 
153     m_pdfInfoWidget["Title"] = m_PdfDialog.m_leTitle;
154     m_pdfInfoWidget["Subject"] = m_PdfDialog.m_leSubject;
155     m_pdfInfoWidget["Keywords"] = m_PdfDialog.m_leKeywords;
156     m_pdfInfoWidget["Author"] = m_PdfDialog.m_leAuthor;
157     m_pdfInfoWidget["Creator"] = m_PdfDialog.m_leCreator;
158     m_pdfInfoWidget["Producer"] = m_PdfDialog.m_leProducer;
159 
160     // set data for  permissions: key/widget
161     m_pdfPermissionKeys    << AllowModify << AllowCopy << AllowPrint
162                            << AllowNotes  << AllowFillForms;
163 
164     m_pdfPermissionWidgets << m_PdfDialog.m_cbModify << m_PdfDialog.m_cbCopy << m_PdfDialog.m_cbPrinting
165                            << m_PdfDialog.m_cbAnnotations << m_PdfDialog.m_cbFormFeeds;
166 
167     m_pdfPermissionPdftk   << "ModifyContents" << "CopyContents" << "Printing"
168                            << "ModifyAnnotations" << "FillIn";
169 
170     // default permissions
171     m_pdfPermissionState << false << false  << false  << false  << false;
172 
173     // check for libpoppler pdf library
174 #if LIBPOPPLER_AVAILABLE
175     m_poppler = true;
176     KILE_DEBUG_MAIN << "working with libpoppler pdf library";
177 #else
178     m_poppler = false;
179     KILE_DEBUG_MAIN << "working without libpoppler pdf library";
180     m_PdfDialog.tabWidget->removeTab(2);
181     m_PdfDialog.tabWidget->removeTab(1);
182 #endif
183 
184     // init Dialog
185     m_PdfDialog.m_lbParameterInfo->setTextFormat(Qt::RichText);
186     m_PdfDialog.m_cbOverwrite->setChecked(false);
187     updateDialog();
188 
189     connect(this, &PdfDialog::output, m_output, &KileWidget::OutputView::receive);
190     connect(m_PdfDialog.m_edInfile->lineEdit(), &QLineEdit::textChanged, this, &PdfDialog::slotInputfileChanged);
191 
192 #if LIBPOPPLER_AVAILABLE
193     connect(m_PdfDialog.tabWidget, SIGNAL(currentChanged(int)), this, SLOT(slotTabwidgetChanged(int)));
194     connect(m_PdfDialog.m_pbPrinting, SIGNAL(clicked()), this, SLOT(slotPrintingClicked()));
195     connect(m_PdfDialog.m_pbAll, SIGNAL(clicked()), this, SLOT(slotAllClicked()));
196 #endif
197 
198     m_buttonBox->addButton(m_rearrangeButton, QDialogButtonBox::ActionRole);
199     connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
200     connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
201     connect(m_buttonBox, &QDialogButtonBox::helpRequested, this, &PdfDialog::slotShowHelp);
202     connect(m_rearrangeButton, &QPushButton::clicked, this, &PdfDialog::slotExecute);
203     mainLayout->addWidget(m_buttonBox);
204 
205     // find available utilities for this dialog
206     executeScript("kpsewhich pdfpages.sty", QString(), PDF_SCRIPTMODE_TOOLS);
207 }
208 
~PdfDialog()209 PdfDialog::~PdfDialog()
210 {
211     if (m_cbTask->currentIndex() != -1) {
212         KileConfig::setPdfWizardLastTask(m_cbTask->currentIndex());
213     }
214     delete m_tempdir;
215     delete m_proc;
216 }
217 
initUtilities()218 void PdfDialog::initUtilities()
219 {
220     // find pdfpages.sty?
221     m_pdfpages = m_outputtext.contains("pdfpages.sty");
222 
223     // additionally look for pdftk
224     m_pdftk = !QStandardPaths::findExecutable("pdftk").isEmpty();
225 
226 //m_pdfpages = false;            // <----------- only for testing  HACK
227 //m_pdftk = false;               // <----------- only for testing  HACK
228 
229     KILE_DEBUG_MAIN << "Looking for pdf tools: pdftk=" << m_pdftk << " pdfpages.sty=" << m_pdfpages;
230 
231 #if !LIBPOPPLER_AVAILABLE
232     m_imagemagick = KileConfig::imagemagick();
233 
234     // we can't use libpoppler pdf library and need to find another method to determine the number of pdf pages
235     // Kile will use three options before giving up
236     if ( m_pdftk )
237         m_numpagesMode = PDF_SCRIPTMODE_NUMPAGES_PDFTK;
238     else if ( m_imagemagick )
239         m_numpagesMode = PDF_SCRIPTMODE_NUMPAGES_IMAGEMAGICK;
240     else
241         m_numpagesMode = PDF_SCRIPTMODE_NUMPAGES_GHOSTSCRIPT;
242 #endif
243 
244     // no pdftk, so properties and permissions are readonly
245     if ( !m_pdftk ) {
246         // set readonly properties
247         for (QStringList::const_iterator it = m_pdfInfoKeys.constBegin(); it != m_pdfInfoKeys.constEnd(); ++it) {
248             m_pdfInfoWidget[*it]->setReadOnly(true);
249         }
250 #if LIBPOPPLER_AVAILABLE
251         // connect permission widgets
252         for (int i=0; i<m_pdfPermissionKeys.size(); ++i) {
253             connect(m_pdfPermissionWidgets.at(i), SIGNAL(clicked(bool)), this, SLOT(slotPermissionClicked(bool)));
254         }
255 #endif
256     }
257 
258     // if we found at least one utility, we can enable some connections
259     if ( m_pdftk || m_pdfpages) {
260         connect(m_PdfDialog.m_edOutfile->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotOutputfileChanged(QString)));
261         connect(m_PdfDialog.m_cbOverwrite, SIGNAL(stateChanged(int)), this, SLOT(slotOverwriteChanged(int)));
262         connect(m_cbTask, SIGNAL(activated(int)), this, SLOT(slotTaskChanged(int)));
263     }
264 
265     // setup dialog
266     slotInputfileChanged(m_PdfDialog.m_edInfile->lineEdit()->text());
267 }
268 
269 // read properties and permissions from the PDF document
pdfParser(const QString & filename)270 void PdfDialog::pdfParser(const QString &filename)
271 {
272 #if LIBPOPPLER_AVAILABLE
273     Poppler::Document *doc = Poppler::Document::load(filename);
274     if ( !doc || doc->isLocked() ) {
275         KILE_DEBUG_MAIN << "Error: could not open pdf document '" << filename << "'";
276         return;
277     }
278     KILE_DEBUG_MAIN << "Parse pdf document: " << filename;
279 
280     // read encryption
281     m_encrypted = doc->isEncrypted();
282     m_PdfDialog.m_lbEncryption->setText( (m_encrypted) ? i18n("yes") : i18n("no") );
283 
284     // read properties
285     for (QStringList::const_iterator it = m_pdfInfoKeys.constBegin(); it != m_pdfInfoKeys.constEnd(); ++it) {
286         QString value = doc->info(*it);
287         m_pdfInfo[*it] = value;
288         m_pdfInfoWidget[*it]->setText(value);
289     }
290 
291     // read creation date and modification date
292     m_PdfDialog.m_lbCreationDate->setText(QLocale().toString(doc->date("CreationDate")));
293     m_PdfDialog.m_lbModDate->setText(QLocale().toString(doc->date("ModDate")));
294 
295     // read PDF version
296     int major,minor;
297     doc->getPdfVersion(&major,&minor);
298     m_PdfDialog.m_lbFormat->setText( QString("PDF version %1.%2").arg(major).arg(minor) );
299 
300     // read permissions
301     for (int i=0; i<m_pdfPermissionKeys.size(); ++i) {
302         bool value = isAllowed( doc, (PDF_Permission)m_pdfPermissionKeys.at(i) );
303         m_pdfPermissionWidgets.at(i)->setChecked(value);
304 
305         if ( !m_pdftk ) {
306             m_pdfPermissionState[i] = value;
307         }
308     }
309 
310     // determine and set number of pages
311     setNumberOfPages( doc->numPages() );
312 
313     // look if all pages have the same size
314     m_pagesize = allPagesSize(doc);
315 
316     delete doc;
317 #else
318     /* libpoppler pdf library is not available:
319      * - we use a brute force method to determine, if this file is encrypted
320      * - then we try to determine the number of pages with
321      *   - pdftk (always first choice, if installed)
322      *   - imagemagick (second choice)
323      *   - gs (third and last choice)
324      * - if the pdf file is encrypted, pdftk will ask for a password
325      */
326 
327     // look if the pdf file is encrypted (brute force)
328     m_encrypted = readEncryption(filename);
329     KILE_DEBUG_MAIN << "PDF encryption: " << m_encrypted;
330 
331     // determine the number of pages of the pdf file
332     determineNumberOfPages(filename,m_encrypted);
333     KILE_DEBUG_MAIN << "PDF number of pages: " << m_numpages;
334 
335     // clear pagesize
336     m_pagesize = QSize(0,0);
337 #endif
338 
339 
340 }
341 
342 #if LIBPOPPLER_AVAILABLE
isAllowed(Poppler::Document * doc,PDF_Permission permission) const343 bool PdfDialog::isAllowed(Poppler::Document *doc, PDF_Permission permission) const
344 {
345     bool b = true;
346     switch ( permission )
347     {
348     case AllowModify:
349         b = doc->okToChange();
350         break;
351     case AllowCopy:
352         b = doc->okToCopy();
353         break;
354     case AllowPrint:
355         b = doc->okToPrint();
356         break;
357     case AllowNotes:
358         b = doc->okToAddNotes();
359         break;
360     case AllowFillForms:
361         b = doc->okToFillForm();
362         break;
363     default:
364         ;
365     }
366     return b;
367 }
368 
allPagesSize(Poppler::Document * doc)369 QSize PdfDialog::allPagesSize(Poppler::Document *doc)
370 {
371     QSize commonsize = QSize(0,0);
372 
373     // Access all pages of the PDF file (m_numpages is known)
374     for ( int i=0; i<m_numpages; ++i ) {
375         Poppler::Page *pdfpage = doc->page(i);
376         if ( pdfpage == 0 ) {
377             KILE_DEBUG_MAIN << "Cannot parse all pages of the PDF file";
378             delete pdfpage;
379             return QSize(0,0);
380         }
381 
382         if ( i == 0 ) {
383             commonsize = pdfpage->pageSize();
384         } else if ( commonsize != pdfpage->pageSize() ) {
385             delete pdfpage;
386             return QSize(0,0);
387         }
388         // documentation says: after the usage, the page must be deleted
389         delete pdfpage;
390     }
391 
392     return commonsize;
393 }
394 #endif
395 
setNumberOfPages(int numpages)396 void PdfDialog::setNumberOfPages(int numpages)
397 {
398     m_numpages = numpages;
399     if (m_numpages > 0) {
400         // show all, if the number of pages is known
401         m_PdfDialog.tabWidget->widget(0)->setEnabled(true);
402 
403         QString pages;
404         if ( m_encrypted )
405             m_PdfDialog.m_lbPages->setText(i18nc("%1 is the number of pages", "%1 (encrypted)", QString::number(m_numpages)));
406         else
407             m_PdfDialog.m_lbPages->setText(pages.setNum(m_numpages));
408     }
409     else {
410         // hide all, if the number of pages can't be determined
411         m_PdfDialog.tabWidget->widget(0)->setEnabled(false);
412         m_PdfDialog.m_lbPages->setText(i18n("Error: unknown number of pages"));
413     }
414 }
415 
416 #if !LIBPOPPLER_AVAILABLE
determineNumberOfPages(const QString & filename,bool askForPassword)417 void PdfDialog::determineNumberOfPages(const QString &filename, bool askForPassword)
418 {
419     // determine the number of pages of the pdf file (delegate this task)
420     QString command;
421     QString passwordparam;
422     int scriptmode = m_numpagesMode;
423 
424     if ( scriptmode==PDF_SCRIPTMODE_NUMPAGES_PDFTK && askForPassword ) {
425         QString password = QInputDialog::getText(this, i18n("PDFTK-Password"),
426                            i18n("This PDF file is encrypted and 'pdftk' cannot open it.\n"
427                                 "Please enter the password for this PDF file\n or leave it blank to try another method: "),
428                            QLineEdit::Normal, QString()).trimmed();
429         if(!password.isEmpty()) {
430             passwordparam = " input_pw " + password;
431         }
432         else {
433             scriptmode = ( m_imagemagick ) ? PDF_SCRIPTMODE_NUMPAGES_IMAGEMAGICK : PDF_SCRIPTMODE_NUMPAGES_GHOSTSCRIPT;
434         }
435     }
436 
437     // now take the original or changed mode
438     if ( scriptmode == PDF_SCRIPTMODE_NUMPAGES_PDFTK ) {
439         command = "pdftk \"" + filename + "\"" + passwordparam + " dump_data | grep NumberOfPages";
440     }
441     else if ( scriptmode == PDF_SCRIPTMODE_NUMPAGES_IMAGEMAGICK ) {
442         command = "identify -format \"%n\" \"" + filename + "\"";
443     }
444     else {
445         command = "gs -q -c \"(" + filename + ") (r) file runpdfbegin pdfpagecount = quit\"";
446     }
447 
448     // run Process
449     KILE_DEBUG_MAIN << "execute for NumberOfPages: " << command;
450     executeScript(command, m_tempdir->path(), scriptmode);
451 }
452 
readNumberOfPages(int scriptmode,const QString & output)453 void PdfDialog::readNumberOfPages(int scriptmode, const QString &output)
454 {
455     int numpages = 0;
456 
457     bool ok;
458     if ( scriptmode == PDF_SCRIPTMODE_NUMPAGES_PDFTK ) {
459         KILE_DEBUG_MAIN << "pdftk output for NumberOfPages: " << output;
460         if ( output.contains("OWNER PASSWORD REQUIRED") ) {
461             QString filename = m_PdfDialog.m_edInfile->lineEdit()->text().trimmed();
462             determineNumberOfPages(m_PdfDialog.m_edInfile->lineEdit()->text().trimmed(),true);
463             return;
464         } else {
465             QRegExp re("\\d+");
466             if ( re.indexIn(output) >= 0) {
467                 numpages = re.cap(0).toInt(&ok);
468             }
469         }
470 
471     }
472     else {
473         QString s = output;
474         numpages = s.remove('\n').toInt(&ok);
475     }
476 
477     setNumberOfPages(numpages);
478 }
479 
readEncryption(const QString & filename)480 bool PdfDialog::readEncryption(const QString &filename)
481 {
482     QFile file(filename);
483     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
484         return false;
485     }
486 
487     KILE_DEBUG_MAIN << "search for encryption ";
488     QRegExp re("/Encrypt(\\W|\\s|$)");
489     QTextStream in(&file);
490     QString line = in.readLine();
491     while ( !line.isNull() ) {
492         if ( re.indexIn(line) >= 0 ) {
493             KILE_DEBUG_MAIN << "pdf file is encrypted !!!";
494             return  true;
495         }
496         line = in.readLine();
497     }
498     return false;
499 }
500 #endif
501 
clearDocumentInfo()502 void PdfDialog::clearDocumentInfo()
503 {
504     m_numpages = 0;
505     m_encrypted = false;
506     m_PdfDialog.m_lbPassword->setEnabled(false);
507     m_PdfDialog.m_edPassword->setEnabled(false);
508     m_PdfDialog.m_edPassword->clear();
509 
510     for (QStringList::const_iterator it = m_pdfInfoKeys.constBegin(); it != m_pdfInfoKeys.constEnd(); ++it) {
511         m_pdfInfoWidget[*it]->clear();
512     }
513 
514     m_PdfDialog.m_lbCreationDate->clear();
515     m_PdfDialog.m_lbModDate->clear();
516 
517     for (int i=0; i<m_pdfPermissionKeys.size(); ++i) {
518         m_pdfPermissionWidgets.at(i)->setChecked(false);
519     }
520 
521     m_PdfDialog.m_lbPages->clear();
522     m_PdfDialog.m_lbFormat->clear();
523     m_PdfDialog.m_lbEncryption->clear();
524 }
525 
updateOwnerPassword(bool infile_exists)526 void PdfDialog::updateOwnerPassword(bool infile_exists)
527 {
528     int tabindex = m_PdfDialog.tabWidget->currentIndex();
529     bool state = ( infile_exists && (m_encrypted || (!m_encrypted && tabindex==2)) ) ? m_pdftk : false;
530     m_PdfDialog.m_lbPassword->setEnabled(state);
531     m_PdfDialog.m_edPassword->setEnabled(state);
532 }
533 
534 // update dialog widgets
updateDialog()535 void PdfDialog::updateDialog()
536 {
537     QString infile = m_PdfDialog.m_edInfile->lineEdit()->text().trimmed();
538     bool infile_exists = QFile(infile).exists();
539 
540     updateOwnerPassword(infile_exists);
541     updateTasks();
542     updateToolsInfo();
543 
544     bool pstate = ( m_encrypted ) ? infile_exists && m_pdftk : infile_exists && (m_pdftk || m_pdfpages);
545     m_PdfDialog.m_gbParameter->setEnabled(pstate);
546 
547     m_PdfDialog.m_gbProperties->setEnabled(infile_exists);
548     m_PdfDialog.m_gbPermissions->setEnabled(infile_exists);
549     m_PdfDialog.m_lbPrinting->setEnabled(infile_exists);
550     m_PdfDialog.m_pbPrinting->setEnabled(infile_exists);
551     m_PdfDialog.m_lbAll->setEnabled(infile_exists);
552     m_PdfDialog.m_pbAll->setEnabled(infile_exists);
553 
554     // and exec button
555     QString outfile = m_PdfDialog.m_edOutfile->lineEdit()->text().trimmed();
556     bool destination = m_PdfDialog.m_cbOverwrite->isChecked() || m_PdfDialog.m_cbView->isChecked();
557 
558     bool state = ( infile_exists && (destination || (!destination && !outfile.isEmpty())) );
559     if ( m_PdfDialog.tabWidget->currentIndex() == 0 ) {
560         state = state && (m_pdfpages || m_pdftk);
561     }
562     else {
563         state = state && m_pdftk;
564     }
565     m_rearrangeButton->setEnabled(state&&!m_scriptrunning);
566 }
567 
568 // update tools information
updateToolsInfo()569 void PdfDialog::updateToolsInfo()
570 {
571     QString info;
572     QString newline = "<br>";
573     QString password = i18n("A password is necessary to set or change the current settings.");
574 
575     int tabindex = m_PdfDialog.tabWidget->currentIndex();
576     if (tabindex == 2 ) {
577         info = ( m_pdftk ) ? i18n("The permissions of this document can be changed with <i>pdftk</i>.") + newline + password
578                : i18n("<i>pdftk</i> is not available, so no permission can be changed.");
579     }
580     else if ( tabindex == 1 ) {
581         if ( ! m_pdftk ) {
582             info = i18n("<i>pdftk</i> is not available, so no property can be changed.");
583         }
584         else {
585             info = i18n("The properties of this document can be changed with <i>pdftk</i>.");
586             if ( m_encrypted ) {
587                 info += newline + password;
588             }
589         }
590     }
591     else { // if ( tabindex == 0 )
592         if ( m_encrypted ) {
593             info = ( m_pdftk ) ? i18n("This input file is encrypted, so only <i>pdftk</i> works.") + newline
594                    + i18n("A password is necessary to rearrange pages.")
595                    : i18n("This input file is encrypted, but <i>pdftk</i> is not installed.");
596         }
597         else {
598             if ( m_pdftk ) { // not encrypted and pdftk
599                 info = ( m_pdfpages ) ? i18n("This wizard will use <i>pdftk</i> and the LaTeX package <i>pdfpages</i>.")
600                        : i18n("This wizard will only use <i>pdftk</i> (<i>pdfpages.sty</i> is not installed).");
601             }
602             else {           // not encrypted and not pdftk
603                 info = ( m_pdfpages ) ? i18n("This wizard will only use the LaTeX package <i>pdfpages</i> (<i>pdftk</i> was not found).")
604                        : i18n("This wizard can't work, because no tool was found (see help section).");
605             }
606         }
607     }
608 
609     QString popplerinfo = (m_poppler ) ? QString() : newline + i18n("<i>(Compiled without libpoppler pdf library. Not all tasks are available.)</i>");
610     info += popplerinfo;
611 
612     // set info text
613     m_PdfDialog.m_lbParameterInfo->setText(info);
614 }
615 
616 // it is important to calculate the task index from the combobox index,
617 // as not all tasks are available, when an utility was not found
updateTasks()618 void PdfDialog::updateTasks()
619 {
620     // according to QT 4.4 docu the index of QComboBox might change if adding or removing items
621     // but because we populate the QComboBox before we start the dialog, we can use the index here
622     int lastindex = m_cbTask->currentIndex();
623     QString lasttext = m_cbTask->currentText();
624 
625     int group = 0;
626     m_cbTask->clear();
627     if (m_pdfpages && !m_encrypted) {                               // task index
628         m_cbTask->addItem( m_tasklist[PDF_PAGE_EMPTY] );             // 0   PDF_PAGE_EMPTY
629         m_cbTask->addItem( m_tasklist[PDF_PAGE_DUPLICATE] );         // 1   PDF_PAGE_DUPLICATE
630         m_cbTask->addItem( m_tasklist[PDF_2UP] );                    // 2   PDF_2UP
631         m_cbTask->addItem( m_tasklist[PDF_2UP_LANDSCAPE] );          // 3   PDF_2UP_LANDSCAPE
632         m_cbTask->addItem( m_tasklist[PDF_4UP] );                    // 4   PDF_4UP
633         m_cbTask->addItem( m_tasklist[PDF_4UP_LANDSCAPE] );          // 5   PDF_4UP_LANDSCAPE
634         group = 1;
635     }
636 
637     if ( (m_pdfpages && !m_encrypted) || m_pdftk ) {
638         if ( group > 0 ) {
639             m_cbTask->addCategoryItem("");
640         }
641         m_cbTask->addItem( m_tasklist[PDF_EVEN] );                   // 6   PDF_EVEN
642         m_cbTask->addItem( m_tasklist[PDF_ODD] );                    // 7   PDF_ODD
643         m_cbTask->addItem( m_tasklist[PDF_EVEN_REV] );               // 8   PDF_EVEN_REV
644         m_cbTask->addItem( m_tasklist[PDF_ODD_REV] );                // 9   PDF_ODD_REV
645         m_cbTask->addItem( m_tasklist[PDF_REVERSE] );                // 10  PDF_REVERSE
646         if (m_encrypted) {
647             m_cbTask->addItem( m_tasklist[PDF_DECRYPT] );             // 11  PDF_DECRYPT
648         }
649         m_cbTask->addCategoryItem("");
650         m_cbTask->addItem( m_tasklist[PDF_SELECT] );                 // 12  PDF_SELECT
651         m_cbTask->addItem( m_tasklist[PDF_DELETE] );                 // 13  PDF_DELETE
652         group = 2;
653     }
654 
655     if (m_pdftk) {
656         m_cbTask->addCategoryItem("");
657         m_cbTask->addItem( m_tasklist[PDF_PDFTK_BACKGROUND] );       // 14  PDF_PDFTK_BACKGROUND
658         if ( ! m_pagesize.isNull() ) {
659             m_cbTask->addItem( m_tasklist[PDF_PDFTK_BGCOLOR] );       // 15  PDF_PDFTK_BGCOLOR
660         }
661         m_cbTask->addItem( m_tasklist[PDF_PDFTK_STAMP] );            // 16  PDF_PDFTK_STAMP
662         m_cbTask->addCategoryItem("");
663         m_cbTask->addItem( m_tasklist[PDF_PDFTK_FREE] );             // 17  PDF_PDFTK_FREE
664         group = 3;
665     }
666 
667     if (m_pdfpages && !m_encrypted) {
668         if ( group < 3 ) {
669             m_cbTask->addCategoryItem("");
670         }
671         m_cbTask->addItem( m_tasklist[PDF_PDFPAGES_FREE] );          // 17  PDF_PDFPAGES_FREE
672     }
673 
674     // choose one common task (need to calculate the combobox index)
675     int index = m_cbTask->findText(lasttext);
676     if ( lastindex==-1 || index==-1 ) {
677         int lastTask = KileConfig::pdfWizardLastTask();
678         int task = ( lastTask < m_cbTask->count() ) ? lastTask : PDF_SELECT;
679         index = m_cbTask->findText(m_tasklist[task]);
680         if ( index == -1 ) {
681             index = 0;
682         }
683     }
684 
685     m_cbTask->setCurrentIndex(index);
686     slotTaskChanged(index);
687 
688     setFocusProxy(m_PdfDialog.m_edInfile);
689     m_PdfDialog.m_edInfile->setFocus();
690 }
691 
getOutfileName(const QString & infile)692 QString PdfDialog::getOutfileName(const QString &infile)
693 {
694     return ( infile.isEmpty() ) ? QString() : infile.left(infile.length()-4) + "-out" + ".pdf";
695 }
696 
697 // calculate task index from comboxbox index
taskIndex()698 int PdfDialog::taskIndex()
699 {
700     return m_tasklist.indexOf( m_cbTask->currentText() );
701 }
702 
setPermissions(bool print,bool other)703 void PdfDialog::setPermissions(bool print, bool other)
704 {
705     for (int i = 0; i<m_pdfPermissionKeys.size(); ++i) {
706         QCheckBox *box = m_pdfPermissionWidgets.at(i);
707         bool state = ( box == m_PdfDialog.m_cbPrinting ) ? print : other;
708         box->setChecked(state);
709     }
710 }
711 
712 // read permissions
readPermissions()713 QString PdfDialog::readPermissions()
714 {
715     QString permissions;
716     for (int i = 0; i < m_pdfPermissionKeys.size(); ++i) {
717         if ( m_pdfPermissionWidgets.at(i)->isChecked() ) {
718             permissions += m_pdfPermissionPdftk.at(i) + ' ';
719         }
720     }
721     return permissions;
722 }
723 
724 //-------------------- slots --------------------
725 
slotTabwidgetChanged(int index)726 void PdfDialog::slotTabwidgetChanged(int index)
727 {
728     m_rearrangeButton->setText(index == 0 ? i18n("Re&arrange") : i18n("&Update"));
729     updateDialog();
730 }
731 
slotPrintingClicked()732 void PdfDialog::slotPrintingClicked()
733 {
734     if ( m_pdftk ) {
735         setPermissions(true, false);
736     }
737 }
738 
slotAllClicked()739 void PdfDialog::slotAllClicked()
740 {
741     if ( m_pdftk ) {
742         setPermissions(true, true);
743     }
744 }
745 
slotPermissionClicked(bool)746 void PdfDialog::slotPermissionClicked(bool)
747 {
748     for (int i = 0; i < m_pdfPermissionKeys.size(); ++i) {
749         QCheckBox *box = m_pdfPermissionWidgets.at(i);
750         if ( box->isChecked() != m_pdfPermissionState[i] ) {
751             box->setChecked( m_pdfPermissionState[i] );
752         }
753     }
754 }
755 
slotInputfileChanged(const QString & text)756 void PdfDialog::slotInputfileChanged(const QString &text)
757 {
758     clearDocumentInfo();
759     if ( QFile(text).exists() ) {
760         m_PdfDialog.m_edOutfile->lineEdit()->setText( getOutfileName(text) );
761         pdfParser(text);
762     }
763 
764     updateDialog();
765 }
766 
slotOverwriteChanged(int state)767 void PdfDialog::slotOverwriteChanged(int state)
768 {
769     bool checked = (state!=Qt::Checked);
770     m_PdfDialog.m_lbOutfile->setEnabled(checked);
771     m_PdfDialog.m_edOutfile->setEnabled(checked);
772 
773     updateDialog();
774 }
775 
slotOutputfileChanged(const QString &)776 void PdfDialog::slotOutputfileChanged(const QString &)
777 {
778     updateDialog();
779 }
780 
slotTaskChanged(int)781 void PdfDialog::slotTaskChanged(int)
782 {
783     if ( m_PdfDialog.tabWidget->currentIndex() > 0 ) {
784         return;
785     }
786 
787     int taskindex = taskIndex();
788     if ( isParameterTask(taskindex) ) {
789         QString s,labeltext;
790         if ( taskindex==PDF_SELECT || taskindex==PDF_DELETE ) {
791             labeltext = i18n("Pages:");
792             s = i18n("Comma separated page list: 1,4-7,9");
793             QRegExp re("((\\d+(-\\d+)?),)*\\d+(-\\d+)?");
794             m_PdfDialog.m_edParameter->setValidator(new QRegExpValidator(re, m_PdfDialog.m_edParameter));
795         }
796         else if (taskindex==PDF_PDFTK_FREE) {
797             labeltext = i18n("Parameter:");
798             s = i18n("All options for 'pdftk'");
799             m_PdfDialog.m_edParameter->setValidator(0);
800         }
801         else { //if (taskindex==PDF_PDFPAGES_FREE) {
802             labeltext = i18n("Parameter:");
803             s = i18n("All options for 'pdfpages'");
804             m_PdfDialog.m_edParameter->setValidator(0);
805         }
806         m_PdfDialog.m_lbParamInfo->setText(" (" + s + ')');
807 
808         m_PdfDialog.m_lbParameter->setText(labeltext);
809         m_PdfDialog.m_lbParameter->show();
810         m_PdfDialog.m_edParameter->clear();
811         m_PdfDialog.m_edParameter->show();
812         m_PdfDialog.m_lbParamInfo->show();
813     }
814     else {
815         m_PdfDialog.m_lbParameter->hide();
816         m_PdfDialog.m_edParameter->hide();
817         m_PdfDialog.m_lbParamInfo->hide();
818     }
819 
820     if ( isOverlayTask(taskindex) ) {
821         m_PdfDialog.m_lbStamp->show();
822         m_PdfDialog.m_edStamp->show();
823 
824         if ( taskindex == PDF_PDFTK_BACKGROUND ) {
825             m_PdfDialog.m_edStamp->setWhatsThis(i18n("Applies a PDF watermark to the background of a single input PDF. "
826                                                 "Pdftk uses only the first page from the background PDF and applies it to every page of the input PDF. "
827                                                 "This page is scaled and rotated as needed to fit the input page.") );
828         }
829         else if ( taskindex == PDF_PDFTK_STAMP ) {
830             m_PdfDialog.m_edStamp->setWhatsThis( i18n("Applies a foreground stamp on top of the input PDF document's pages. "
831                                                  "Pdftk uses only the first page from the stamp PDF and applies it to every page of the input PDF. "
832                                                  "This page is scaled and rotated as needed to fit the input page. "
833                                                  "This works best if the stamp PDF page has a transparent background.") );
834         }
835     }
836     else {
837         m_PdfDialog.m_lbStamp->hide();
838         m_PdfDialog.m_edStamp->hide();
839     }
840 
841     if (isBackgroundColor(taskindex)) {
842         m_PdfDialog.m_lbBackgroundColor->show();
843         m_PdfDialog.m_pbBackgroundColor->show();
844     }
845     else {
846         m_PdfDialog.m_lbBackgroundColor->hide();
847         m_PdfDialog.m_pbBackgroundColor->hide();
848     }
849     if (isOverlayTask(taskindex) || isBackgroundColor(taskindex) || isFreeTask(taskindex)) {
850         m_rearrangeButton->setText(i18n("&Apply"));
851     }
852     else {
853         m_rearrangeButton->setText(i18n("Re&arrange"));
854     }
855 }
856 
857 // execute commands
slotExecute()858 void PdfDialog::slotExecute()
859 {
860     if(!m_tempdir) {
861         // create tempdir
862         m_tempdir = new QTemporaryDir(QDir::tempPath() + QLatin1String("/kile-pdfwizard"));
863         m_tempdir->setAutoRemove(true);
864         KILE_DEBUG_MAIN << "tempdir: " << m_tempdir->path();
865     }
866 
867     if(!m_tempdir->isValid()) {
868         KMessageBox::error(this, i18n("Failed to create a temporary directory.\n\nThis wizard cannot be used."));
869         reject();
870         return;
871     }
872 
873     int tabindex = m_PdfDialog.tabWidget->currentIndex();
874 
875     switch (tabindex) {
876     case 0:
877         if (checkParameter()) {
878             executeAction();
879         }
880         break;
881     case 1:
882         if (checkProperties()) {
883             executeProperties();
884         }
885         break;
886     case 2:
887         if (checkPermissions()) {
888             executePermissions();
889         }
890         break;
891     }
892 }
893 
slotShowHelp()894 void PdfDialog::slotShowHelp()
895 {
896     QString message = i18n("<center>PDF-Wizard</center><br>"
897                            "This wizard uses 'pdftk' and the LaTeX package 'pdfpages' to"
898                            "<ul>"
899                            "<li>rearrange pages of an existing PDF document</li>"
900                            "<li>read and update documentinfo of a PDF document (only pdftk)</li>"
901                            "<li>read, set or change some permissions of a PDF document (only pdftk). "
902                            "A password is necessary to set or change this document settings. "
903                            "Additionally PDF encryption is done to lock the file's content behind this password.</li>"
904                            "</ul>"
905                            "<p>The package 'pdfpages' will only work with non-encrypted documents. "
906                            "'pdftk' can handle both kind of documents, but a password is needed for encrypted files. "
907                            "If one of 'pdftk' or 'pdfpages' is not available, the possible rearrangements are reduced.</p>"
908                            "<p><i>Warning:</i> Encryption and a password does not provide any real PDF security. The content "
909                            "is encrypted, but the key is known. You should see it more as a polite but firm request "
910                            "to respect the author's wishes.</p>");
911 
912 #if !LIBPOPPLER_AVAILABLE
913     message += i18n("<p><i>Information: </i>This version of Kile was compiled without libpoppler library. "
914                     "Setting, changing and removing of properties and permissions is not possible.</p>");
915 #endif
916 
917     KMessageBox::information(this, message, i18n("PDF Tools"));
918 }
919 
executeAction()920 void PdfDialog::executeAction()
921 {
922     QString command = buildActionCommand();
923     if ( command.isEmpty() ) {
924         return;
925     }
926 
927     m_errorHandler->clearMessages();
928     QFileInfo from(m_inputfile);
929     QFileInfo to(m_outputfile);
930 
931     // output for log window
932     QString program = (m_execLatex) ? i18n("LaTeX with 'pdfpages' package") : i18n("pdftk");
933     QString msg = i18n("Rearranging PDF file: %1", from.fileName());
934     if (!to.fileName().isEmpty())
935         msg += " ---> " + to.fileName();
936     m_errorHandler->printMessage(KileTool::Info, msg, program);
937 
938     // some output logs
939     m_output->clear();
940     QString s = QString("*****\n")
941                 + i18n("***** tool:        ") + program + '\n'
942                 + i18n("***** input file:  ") + from.fileName()+ '\n'
943                 + i18n("***** output file: ") + to.fileName()+ '\n'
944                 + i18n("***** param:       ") + m_param + '\n'
945                 + i18n("***** command:     ") + command + '\n'
946                 + i18n("***** viewer:      ") + ((m_PdfDialog.m_cbView->isChecked()) ? i18n("yes") : i18n("no")) + '\n'
947                 + "*****\n";
948     emit( output(s) );
949 
950     // run Process
951     executeScript(command, m_tempdir->path(), PDF_SCRIPTMODE_ACTION);
952 }
953 
executeProperties()954 void PdfDialog::executeProperties()
955 {
956     // create temporary file
957     QTemporaryFile infotemp(m_tempdir->path() + QLatin1String("/kile-pdfdialog-XXXXXX.txt"));
958     infotemp.setAutoRemove(false);
959 
960     if(!infotemp.open()) {
961         KILE_DEBUG_MAIN << "Could not create tempfile for key/value pairs in QString PdfDialog::executeProperties()" ;
962         return;
963     }
964     QString infofile = infotemp.fileName();
965 
966     // create a text file with key/value pairs for pdftk
967     QTextStream infostream(&infotemp);
968     for (QStringList::const_iterator it = m_pdfInfoKeys.constBegin(); it != m_pdfInfoKeys.constEnd(); ++it) {
969         infostream << "InfoKey: " << (*it) << "\n";
970         infostream << "InfoValue: " << m_pdfInfoWidget[*it]->text().trimmed() << "\n";
971     }
972     // add modification Date
973     QString datetime = QDateTime::currentDateTimeUtc().toString("%Y%m%d%H%M%S%:z");
974     datetime = datetime.replace(":","'");
975     infostream << "InfoKey: " << "ModDate" << "\n";
976     infostream << "InfoValue: " << "D:" << datetime << "'\n";
977     infotemp.close();
978 
979     // build command
980     QString inputfile = m_PdfDialog.m_edInfile->lineEdit()->text().trimmed();
981     QString password =  m_PdfDialog.m_edPassword->text().trimmed();
982     QString pdffile = m_tempdir->path() + QFileInfo(m_inputfile).baseName() + "-props.pdf";
983 
984     // read permissions
985     QString permissions = readPermissions();
986 
987     // build param
988     QString param = "\"" + inputfile + "\"";
989     if ( m_encrypted ) {
990         param += " input_pw " + password;
991     }
992 
993     param += " update_info " + infofile + " output \"" + pdffile+ "\"";
994     if ( m_encrypted ) {
995         param += " encrypt_128bit";
996         if ( !permissions.isEmpty() )
997             param += " allow " + permissions;
998         param += " owner_pw " + password;
999     }
1000     QString command = "pdftk " + param;
1001 
1002     // move destination file
1003     m_move_filelist.clear();
1004     m_move_filelist << pdffile << inputfile;
1005 
1006     // execute script
1007     showLogs("Updating properties", inputfile, param);
1008     executeScript(command, QString(), PDF_SCRIPTMODE_PROPERTIES);
1009 
1010 }
1011 
executePermissions()1012 void PdfDialog::executePermissions()
1013 {
1014     // read permissions
1015     QString permissions = readPermissions();
1016 
1017     // build command
1018     QString inputfile = m_PdfDialog.m_edInfile->lineEdit()->text().trimmed();
1019     QString password =  m_PdfDialog.m_edPassword->text().trimmed();
1020     QString pdffile = m_tempdir->path() + QFileInfo(m_inputfile).baseName() + "-perms.pdf";
1021 
1022     QString param = "\"" + inputfile + "\"";
1023     if ( m_encrypted ) {
1024         param += " input_pw " + password;
1025     }
1026     param += " output \"" + pdffile + "\" encrypt_128bit";
1027     if ( !permissions.isEmpty() ) {
1028         param += " allow " + permissions;
1029     }
1030     param += " owner_pw " + password;
1031     QString command = "pdftk " + param;
1032 
1033     // move destination file
1034     m_move_filelist.clear();
1035     m_move_filelist << pdffile << inputfile;
1036 
1037     // execute script
1038     showLogs("Updating permissions", inputfile, param);
1039     executeScript(command, QString(), PDF_SCRIPTMODE_PERMISSIONS);
1040 
1041 }
1042 
showLogs(const QString & title,const QString & inputfile,const QString & param)1043 void PdfDialog::showLogs(const QString &title, const QString &inputfile, const QString &param)
1044 {
1045     // some info for log widget
1046     m_errorHandler->clearMessages();
1047     m_errorHandler->printMessage(KileTool::Info, title, "pdftk" );
1048 
1049     // some info for output widget
1050     QFileInfo input(inputfile);
1051     m_output->clear();
1052     QString s = QString("*****\n")
1053                 + i18n("***** tool:        ") + "pdftk" + '\n'
1054                 + i18n("***** input file:  ") + input.fileName()+ '\n'
1055                 + i18n("***** param:       ") + param + '\n'
1056                 + "*****\n";
1057     emit( output(s) );
1058 }
1059 
executeScript(const QString & command,const QString & dir,int scriptmode)1060 void PdfDialog::executeScript(const QString &command, const QString &dir, int scriptmode)
1061 {
1062     // delete old KProcess
1063     if(m_proc) {
1064         delete m_proc;
1065     }
1066 
1067     m_scriptmode = scriptmode;
1068     m_outputtext = "";
1069 
1070     m_proc = new KProcess();
1071     if (!dir.isEmpty()) {
1072         m_proc->setWorkingDirectory(dir);
1073     }
1074     m_proc->setShellCommand(command);
1075     m_proc->setOutputChannelMode(KProcess::MergedChannels);
1076     m_proc->setReadChannel(QProcess::StandardOutput);
1077 
1078     connect(m_proc, &QProcess::readyReadStandardOutput,
1079             this, &PdfDialog::slotProcessOutput);
1080 
1081     connect(m_proc, &QProcess::readyReadStandardError,
1082             this, &PdfDialog::slotProcessOutput);
1083 
1084     connect(m_proc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
1085             this, &PdfDialog::slotProcessExited);
1086 
1087     connect(m_proc, &QProcess::errorOccurred,
1088             this, [this]() { slotProcessExited(-1, QProcess::CrashExit); });
1089 
1090     KILE_DEBUG_MAIN << "=== PdfDialog::runPdfutils() ====================";
1091     KILE_DEBUG_MAIN << "execute '" << command << "'";
1092     m_scriptrunning = true;
1093     m_rearrangeButton->setEnabled(false);
1094     m_buttonBox->button(QDialogButtonBox::Close)->setEnabled(false);
1095     m_proc->start();
1096 }
1097 
slotProcessOutput()1098 void PdfDialog::slotProcessOutput()
1099 {
1100     m_outputtext += m_proc->readAll();
1101 }
1102 
1103 
slotProcessExited(int exitCode,QProcess::ExitStatus exitStatus)1104 void PdfDialog::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus)
1105 {
1106     if(exitCode != 0 || exitStatus != QProcess::NormalExit) {
1107         if (m_scriptmode != PDF_SCRIPTMODE_TOOLS)
1108             showError(i18n("An error occurred while executing the task."));
1109     }
1110     else {
1111         bool state = ( exitCode == 0 );
1112         if ( m_scriptmode == PDF_SCRIPTMODE_TOOLS ) {
1113             initUtilities();
1114         }
1115 #if !LIBPOPPLER_AVAILABLE
1116         else if ( m_scriptmode==PDF_SCRIPTMODE_NUMPAGES_PDFTK
1117                   || m_scriptmode==PDF_SCRIPTMODE_NUMPAGES_IMAGEMAGICK
1118                   || m_scriptmode==PDF_SCRIPTMODE_NUMPAGES_GHOSTSCRIPT ) {
1119             readNumberOfPages(m_scriptmode,m_outputtext);
1120         }
1121 #endif
1122         else {
1123             finishPdfAction(state);
1124         }
1125     }
1126 
1127     m_scriptrunning = false;
1128     m_buttonBox->button(QDialogButtonBox::Close)->setEnabled(true);
1129     updateDialog();
1130 }
1131 
finishPdfAction(bool state)1132 void PdfDialog::finishPdfAction(bool state)
1133 {
1134     // output window
1135     emit( output(m_outputtext) );
1136 
1137     // log window
1138     QString program = (m_scriptmode==PDF_SCRIPTMODE_ACTION && m_execLatex) ? "LaTeX with 'pdfpages' package" : "pdftk";
1139 
1140     if ( state ) {
1141         m_errorHandler->printMessage(KileTool::Info, "finished", program);
1142 
1143         // should we move the temporary pdf file
1144         if ( ! m_move_filelist.isEmpty() ) {
1145             QFile::remove( m_move_filelist[1] );
1146             QFile::rename( m_move_filelist[0], m_move_filelist[1] );
1147             KILE_DEBUG_MAIN << "move file: " << m_move_filelist[0] << " --->  " << m_move_filelist[1];
1148         }
1149 
1150         // run viewer
1151         if ( m_PdfDialog.m_cbView->isChecked() && m_scriptmode==PDF_SCRIPTMODE_ACTION ) {
1152             runViewer();
1153         }
1154 
1155         // file properties/permissions could be changed
1156         if ( (m_scriptmode==PDF_SCRIPTMODE_ACTION && m_PdfDialog.m_cbOverwrite->isChecked())
1157                 || m_scriptmode==PDF_SCRIPTMODE_PROPERTIES || m_scriptmode==PDF_SCRIPTMODE_PERMISSIONS ) {
1158             slotInputfileChanged( m_PdfDialog.m_edInfile->lineEdit()->text().trimmed() );
1159         }
1160     }
1161     else {
1162         QString msg;
1163         if (m_outputtext.indexOf("OWNER PASSWORD") >= 0 ) {
1164             msg = i18n("Finished with an error (wrong password)");
1165         }
1166         else {
1167             msg = i18n("Finished with an error");
1168         }
1169         m_errorHandler->printMessage(KileTool::Error, msg, program);
1170     }
1171 }
1172 
runViewer()1173 void PdfDialog::runViewer()
1174 {
1175     m_errorHandler->printMessage(KileTool::Info, "Running viewer", "ViewPDF");
1176 
1177     // call ViewPDF
1178     QString cfg = KileTool::configName("ViewPDF", m_manager->config());
1179     KileTool::View *tool = dynamic_cast<KileTool::View*>(m_manager->createTool("ViewPDF", cfg, false));
1180     if(!tool) {
1181         m_errorHandler->printMessage(KileTool::Error, i18n("Could not create the ViewPDF tool"), i18n("ViewPDF"));
1182         return;
1183     }
1184     tool->setFlags(0);
1185     tool->setSource(m_outputfile);
1186     m_manager->run(tool);
1187 }
1188 
buildActionCommand()1189 QString PdfDialog::buildActionCommand()
1190 {
1191     // build action: parameter
1192     m_execLatex = true;           // default
1193     m_inputfile = m_PdfDialog.m_edInfile->lineEdit()->text().trimmed();
1194     m_outputfile = m_PdfDialog.m_edOutfile->lineEdit()->text().trimmed();
1195 
1196     QColor bgcolor;
1197     QString bgfile;
1198     int taskindex = taskIndex();
1199     switch (taskindex) {
1200     case PDF_PAGE_EMPTY:
1201         m_param = "nup=1x2,landscape,pages=" + buildPageRange(PDF_PAGE_EMPTY);
1202         break;
1203 
1204     case PDF_PAGE_DUPLICATE:
1205         m_param = "nup=1x2,landscape,pages=" + buildPageRange(PDF_PAGE_DUPLICATE);
1206         break;
1207 
1208     case PDF_2UP:
1209         m_param = "nup=1x2,landscape,pages=1-";
1210         break;
1211 
1212     case PDF_2UP_LANDSCAPE:
1213         m_param = "nup=1x2,pages=1-";
1214         break;
1215 
1216     case PDF_4UP:
1217         m_param = "nup=2x2,pages=1-";
1218         break;
1219 
1220     case PDF_4UP_LANDSCAPE:
1221         m_param = "nup=2x2,landscape,pages=1-";
1222         break;
1223 
1224     case PDF_EVEN:
1225         if ( m_pdftk ) {
1226             m_param = "cat 1-endeven";
1227             m_execLatex = false;
1228         }
1229         else {
1230             m_param = buildPageList(true);
1231         }
1232         break;
1233 
1234     case PDF_ODD:
1235         if ( m_pdftk ) {
1236             m_param = "cat 1-endodd";
1237             m_execLatex = false;
1238         }
1239         else {
1240             m_param = buildPageList(false);
1241         }
1242         break;
1243 
1244     case PDF_EVEN_REV:
1245         if ( m_pdftk ) {
1246             m_param = "cat end-1even";
1247             m_execLatex = false;
1248         }
1249         else {
1250             m_param = buildReversPageList(true);
1251         }
1252         break;
1253 
1254     case PDF_ODD_REV:
1255         if ( m_pdftk ) {
1256             m_param = "cat end-1odd";
1257             m_execLatex = false;
1258         }
1259         else {
1260             m_param = buildReversPageList(false);
1261         }
1262         break;
1263 
1264     case PDF_REVERSE:
1265         if ( m_pdftk ) {
1266             m_param = "cat end-1";
1267             m_execLatex = false;
1268         }
1269         else {
1270             m_param = "last-1";
1271         }
1272         break;
1273 
1274     case PDF_DECRYPT:
1275         m_param.clear();
1276         m_execLatex = false;
1277         break;
1278 
1279     case PDF_SELECT:
1280     case PDF_DELETE:
1281         m_param = ( taskindex == PDF_SELECT ) ? buildSelectPageList() : buildDeletePageList();
1282         if ( m_pdftk ) {
1283             m_param = "cat " + m_param.replace(","," ");
1284             m_execLatex = false;
1285         }
1286         else {
1287             m_param = "pages={" + m_param + "}";
1288         }
1289         break;
1290 
1291     case PDF_PDFTK_BACKGROUND:
1292         m_param = "background \"" + m_PdfDialog.m_edStamp->text().trimmed() + "\"";
1293         m_execLatex = false;
1294         break;
1295 
1296     case PDF_PDFTK_BGCOLOR:
1297         bgcolor = m_PdfDialog.m_pbBackgroundColor->color();
1298         bgfile = buildPdfBackgroundFile(&bgcolor);
1299         m_param = "background " + bgfile;
1300         m_execLatex = false;
1301         break;
1302 
1303     case PDF_PDFTK_STAMP:
1304         m_param = "stamp \"" + m_PdfDialog.m_edStamp->text().trimmed() + "\"";
1305         m_execLatex = false;
1306         break;
1307 
1308     case PDF_PDFTK_FREE:
1309         m_param = m_PdfDialog.m_edParameter->text().trimmed();
1310         m_execLatex = false;
1311         break;
1312 
1313     case PDF_PDFPAGES_FREE:
1314         m_param = m_PdfDialog.m_edParameter->text().trimmed();
1315         break;
1316     }
1317 
1318     // build action: command
1319     QString command,latexfile,pdffile;
1320     if ( m_execLatex ) {
1321         latexfile = buildLatexFile(m_param);
1322         pdffile = latexfile + ".pdf";
1323         command = "pdflatex -interaction=nonstopmode " + latexfile + ".tex";
1324     }
1325     else {
1326         pdffile = m_tempdir->path() + QFileInfo(m_inputfile).baseName() + "-temp.pdf";
1327         command = "pdftk \"" + m_inputfile + "\"";
1328         if ( m_encrypted ) {
1329             QString password =  m_PdfDialog.m_edPassword->text().trimmed();
1330             command += " input_pw " + password;
1331         }
1332         command += " " + m_param + " output \"" + pdffile+ "\"";
1333     }
1334 
1335     // additional actions
1336     bool viewer = m_PdfDialog.m_cbView->isChecked();
1337 
1338     bool equalfiles = (m_PdfDialog.m_cbOverwrite->isChecked() || m_inputfile==m_outputfile);
1339     if (equalfiles) {
1340         m_outputfile = m_inputfile;
1341     }
1342 
1343     // move destination file
1344     m_move_filelist.clear();
1345     if ( equalfiles ) {
1346         m_move_filelist << pdffile << m_inputfile;
1347     }
1348     else if ( !m_outputfile.isEmpty() ) {
1349         m_move_filelist << pdffile << m_outputfile;
1350     }
1351 
1352     // viewer
1353     if ( viewer && m_outputfile.isEmpty() ) {
1354         m_outputfile = pdffile;
1355     }
1356 
1357     return command;
1358 }
1359 
1360 // create a temporary file to run latex with package pdfpages.sty
buildLatexFile(const QString & param)1361 QString PdfDialog::buildLatexFile(const QString &param)
1362 {
1363     QTemporaryFile temp(m_tempdir->path() + QLatin1String("/kile-pdfdialog-XXXXXX.tex"));
1364     temp.setAutoRemove(false);
1365 
1366     if(!temp.open()) {
1367         KILE_DEBUG_MAIN << "Could not create tempfile in PdfDialog::buildLatexFile()" ;
1368         return QString();
1369     }
1370     QString tempname = temp.fileName();
1371 
1372     QTextStream stream(&temp);
1373     stream << "\\documentclass[a4paper,12pt]{article}\n";
1374     stream << "\\usepackage[final]{pdfpages}\n";
1375     stream << "\\begin{document}\n";
1376     stream << "\\includepdf[" << param << "]{" << m_inputfile << "}\n";
1377     stream << "\\end{document}\n";
1378 
1379     // everything is prepared to do the job
1380     temp.close();
1381     return(tempname.left(tempname.length() - 4));
1382 }
1383 
1384 // create a temporary pdf file to set a background color
buildPdfBackgroundFile(QColor * color)1385 QString PdfDialog::buildPdfBackgroundFile(QColor *color)
1386 {
1387     QTemporaryFile temp(m_tempdir->path() + QLatin1String("/kile-pdfdialog-XXXXXX.pdf"));
1388     temp.setAutoRemove(false);
1389 
1390     if(!temp.open()) {
1391         KILE_DEBUG_MAIN << "Could not create tempfile in PdfDialog::buildPdfBackgroundFile()" ;
1392         return QString();
1393     }
1394     QString tempname = temp.fileName();
1395 
1396     QTextStream stream(&temp);
1397     stream << "%PDF-1.4\n";
1398     stream << '%' << '\0' << '\0' << '\0' << '\0' << '\r';
1399     stream << "5 0 obj \n"
1400            "<<\n"
1401            "/Type /ExtGState\n"
1402            "/OPM 1\n"
1403            ">>\n"
1404            "endobj \n"
1405            "4 0 obj \n"
1406            "<<\n"
1407            "/R7 5 0 R\n"
1408            ">>\n"
1409            "endobj \n"
1410            "6 0 obj \n"
1411            "<<\n"
1412            "/Length 83\n"
1413            ">>\n"
1414            "stream\n"
1415            "q 0.1 0 0 0.1 0 0 cm\n"
1416            "/R7 gs\n";
1417     stream << color->redF() << " " << color->greenF() << " " << color->blueF() << " rg\n";
1418     stream << "0 0 " << 10*m_pagesize.width() << " " << 10*m_pagesize.height() << " re\n";
1419     stream << "f\n"
1420            "0 g\n"
1421            "Q\n"
1422            "\n"
1423            "endstream \n"
1424            "endobj \n"
1425            "3 0 obj \n"
1426            "<<\n"
1427            "/Parent 1 0 R\n";
1428     stream << "/MediaBox [0 0 " << m_pagesize.width() << " " << m_pagesize.height() << "]\n";
1429     stream << "/Resources \n"
1430            "<<\n"
1431            "/ExtGState 4 0 R\n"
1432            "/ProcSet [/PDF]\n"
1433            ">>\n"
1434            "/pdftk_PageNum 1\n"
1435            "/Type /Page\n"
1436            "/Contents 6 0 R\n"
1437            ">>\n"
1438            "endobj \n"
1439            "1 0 obj \n"
1440            "<<\n"
1441            "/Kids [3 0 R]\n"
1442            "/Count 1\n"
1443            "/Type /Pages\n"
1444            ">>\n"
1445            "endobj \n"
1446            "7 0 obj \n"
1447            "<<\n"
1448            "/Pages 1 0 R\n"
1449            "/Type /Catalog\n"
1450            ">>\n"
1451            "endobj \n"
1452            "8 0 obj \n"
1453            "<<\n"
1454            "/Creator ()\n"
1455            "/Producer ())\n"
1456            "/ModDate ()\n"
1457            "/CreationDate ()\n"
1458            ">>\n"
1459            "endobj xref\n"
1460            "0 9\n"
1461            "0000000000 65535 f \n"
1462            "0000000388 00000 n \n"
1463            "0000000000 65536 n \n"
1464            "0000000231 00000 n \n"
1465            "0000000062 00000 n \n"
1466            "0000000015 00000 n \n"
1467            "0000000095 00000 n \n"
1468            "0000000447 00000 n \n"
1469            "0000000498 00000 n \n"
1470            "trailer\n"
1471            "\n"
1472            "<<\n"
1473            "/Info 8 0 R\n"
1474            "/Root 7 0 R\n"
1475            "/Size 9\n"
1476            "/ID [<4a7c31ef3aeb884b18f59c2037a752f5><54079f85d95a11f3400fe5fc3cfc832b>]\n"
1477            ">>\n"
1478            "startxref\n"
1479            "721\n"
1480            "%%EOF\n";
1481 
1482     // everything is prepared to do the job
1483     temp.close();
1484     return tempname;
1485 }
1486 
buildPageRange(int type)1487 QString PdfDialog::buildPageRange(int type)
1488 {
1489     QString s;
1490     for (int i = 1; i <= m_numpages; ++i) {
1491         if (type == PDF_PAGE_EMPTY) {
1492             s += QString("%1,{},").arg(i);
1493         }
1494         else {
1495             s += QString("%1,%2,").arg(i).arg(i);
1496         }
1497     }
1498 
1499     return "{" + s.left(s.length()-1) + "}";
1500 }
1501 
buildPageList(bool even)1502 QString PdfDialog::buildPageList(bool even)
1503 {
1504     QString s, number;
1505 
1506     int start = ( even ) ? 2 : 1;
1507     for (int i=start; i<=m_numpages; i+=2 ) {
1508         s += number.setNum(i) + ',';
1509     }
1510 
1511     if ( !s.isEmpty() ) {
1512         s.truncate(s.length()-1);
1513     }
1514     return "{" + s + "}";
1515 }
1516 
buildReversPageList(bool even)1517 QString PdfDialog::buildReversPageList(bool even)
1518 {
1519     QString s,number;
1520 
1521     int last = m_numpages;
1522     if ( even ) {
1523         if ( (last & 1) == 1 ) {
1524             last--;
1525         }
1526     }
1527     else {
1528         if ( (last & 1) == 0 ) {
1529             last--;
1530         }
1531     }
1532 
1533     for (int i=last; i>=1; i-=2 ) {
1534         s += number.setNum(i) + ",";
1535     }
1536 
1537     if ( !s.isEmpty() ) {
1538         s.truncate(s.length()-1);
1539     }
1540     return "{" + s + "}";
1541 }
1542 
buildSelectPageList()1543 QString PdfDialog::buildSelectPageList()
1544 {
1545     return m_PdfDialog.m_edParameter->text().trimmed();
1546 }
1547 
buildDeletePageList()1548 QString PdfDialog::buildDeletePageList()
1549 {
1550     // m_numpages is known
1551     QString param = m_PdfDialog.m_edParameter->text().trimmed();
1552     QRegExp re("(\\d+)-(\\d+)");
1553 
1554     // analyze delete list
1555     bool ok;
1556     QBitArray arr(m_numpages + 1,false);
1557     QStringList pagelist = param.split(',');
1558     foreach (const QString &s, pagelist) {
1559         if ( s.contains('-') && re.indexIn(s) >= 0 ) {
1560             int from = re.cap(1).toInt(&ok);
1561             int to = re.cap(2).toInt(&ok);
1562             for (int i=from; i<=to; ++i) {
1563                 arr.setBit(i);
1564             }
1565         }
1566         else {
1567             arr.setBit(s.toInt(&ok));
1568         }
1569     }
1570 
1571     // build select list
1572     QString result;
1573     int page = 1;
1574     while ( page <= m_numpages ) {
1575         int from = searchPages(&arr,page,m_numpages,true);
1576         if ( from > m_numpages ) {
1577             break;
1578         }
1579         int to = searchPages(&arr,from+1,m_numpages,false) - 1;
1580         if ( !result.isEmpty() ) {
1581             result += ',';
1582         }
1583         if ( from < to ) {
1584             result += QString::number(from) + '-' + QString::number(to);
1585         }
1586         else {
1587             result += QString::number(from);
1588         }
1589         page = to + 1;
1590     }
1591 
1592     return result;
1593 }
1594 
searchPages(QBitArray * arr,int page,int lastpage,bool value)1595 int PdfDialog::searchPages(QBitArray *arr, int page, int lastpage, bool value)
1596 {
1597     while ( page <= lastpage ) {
1598         if ( arr->at(page) != value ) {
1599             return page;
1600         }
1601         page++;
1602     }
1603     return lastpage + 1;
1604 }
1605 
checkParameter()1606 bool PdfDialog::checkParameter()
1607 {
1608     if ( !checkInputFile() ) {
1609         return false;
1610     }
1611 
1612     if ( m_encrypted ) {
1613         if ( !checkPassword() ) {
1614             return false;
1615         }
1616     }
1617 
1618     // check parameter
1619     int taskindex = taskIndex();
1620     if ( isParameterTask(taskindex) && m_PdfDialog.m_edParameter->text().trimmed().isEmpty() ) {
1621         showError( i18n("The utility needs some parameters in this mode.") );
1622         return false;
1623     }
1624 
1625     // check select/delete page list (m_numpages is known)
1626     if ( taskindex==PDF_SELECT || taskindex==PDF_DELETE ) {
1627         // m_numpages is known
1628         QString param = m_PdfDialog.m_edParameter->text().trimmed();
1629         QRegExp re("(\\d+)-(\\d+)");
1630 
1631         // analyze page list
1632         bool ok;
1633         QStringList pagelist = param.split(',');
1634         foreach (const QString &s, pagelist) {
1635             if ( s.contains('-') && re.indexIn(s)>=0 ) {
1636                 int from = re.cap(1).toInt(&ok);
1637                 int to = re.cap(2).toInt(&ok);
1638                 if ( from > to ) {
1639                     showError(i18n("Illegal page list 'from-to': %1 is bigger than %2.",from,to));
1640                     return false;
1641                 }
1642                 if ( to > m_numpages ) {
1643                     showError(i18n("Illegal pagenumber: %1.",to));
1644                     return false;
1645                 }
1646             }
1647             else {
1648                 int page = s.toInt(&ok);
1649                 if ( page > m_numpages ) {
1650                     showError(i18n("Illegal pagenumber: %1.",page));
1651                     return false;
1652                 }
1653             }
1654         }
1655     }
1656 
1657     // check background/stamp parameter
1658     if ( isOverlayTask(taskindex) ) {
1659         QString filename = m_PdfDialog.m_edStamp->text().trimmed();
1660 
1661         if ( filename.isEmpty() ) {
1662             QString message = ( taskindex == PDF_PDFTK_STAMP )
1663                               ? i18n("You need to define a PDF file as foreground stamp.")
1664                               : i18n("You need to define a PDF file as background watermark.");
1665             showError(message);
1666             return false;
1667         }
1668 
1669         QFileInfo fs(filename);
1670         if (fs.completeSuffix() != "pdf") {
1671             showError(i18n("Unknown file format: only '.pdf' is accepted as image file in this mode."));
1672             return false;
1673         }
1674 
1675         if ( !QFile::exists(filename) ) {
1676             showError(i18n("The given file doesn't exist."));
1677             return false;
1678         }
1679     }
1680 
1681     // overwrite mode: no output file is needed
1682     if ( m_PdfDialog.m_cbOverwrite->isChecked() ) {
1683         return true;
1684     }
1685 
1686     // create a different output file
1687     QString outfile = m_PdfDialog.m_edOutfile->lineEdit()->text().trimmed();
1688     if ( outfile.isEmpty() ) {
1689         showError(i18n("You need to define an output file."));
1690         return false;
1691     }
1692 
1693     // outfile file must have extension pdf
1694     QFileInfo fo(outfile);
1695     if (fo.completeSuffix() != "pdf") {
1696         showError(i18n("Unknown file format: only '.pdf' is accepted as output file."));
1697         return false;
1698     }
1699 
1700     // check, if this output file already exists
1701     if ( fo.exists() ) {
1702         QString s = i18n("A file named \"%1\" already exists. Are you sure you want to overwrite it?", fo.fileName());
1703         if (KMessageBox::questionYesNo(this,
1704                                        "<center>" + s + "</center>",
1705                                        i18n("PDF Tools")) == KMessageBox::No) {
1706             return false;
1707         }
1708     }
1709 
1710     return true;
1711 }
1712 
checkProperties()1713 bool PdfDialog::checkProperties()
1714 {
1715     if ( !checkInputFile() ) {
1716         return false;
1717     }
1718 
1719     return ( m_encrypted ) ? checkPassword() : true;
1720 }
1721 
checkPermissions()1722 bool PdfDialog::checkPermissions()
1723 {
1724     if ( !checkInputFile() ) {
1725         return false;
1726     }
1727 
1728     return checkPassword();
1729 }
1730 
checkInputFile()1731 bool PdfDialog::checkInputFile()
1732 {
1733     QString infile = m_PdfDialog.m_edInfile->lineEdit()->text().trimmed();
1734     if (infile.isEmpty()) {
1735         showError(i18n("No input file is given."));
1736         return false;
1737     }
1738 
1739     QFileInfo fi(infile);
1740     QString suffix = fi.completeSuffix();
1741     if (suffix != "pdf") {
1742         showError(i18n("Unknown file format: only '.pdf' are accepted for input files."));
1743         return false;
1744     }
1745 
1746     if (!fi.exists()) {
1747         showError(i18n("This input file does not exist."));
1748         return false;
1749     }
1750 
1751     return true;
1752 }
1753 
checkPassword()1754 bool PdfDialog::checkPassword()
1755 {
1756     // check password
1757     QString password = m_PdfDialog.m_edPassword->text().trimmed();
1758     if (password.isEmpty()) {
1759         showError(i18n("No password is given."));
1760         return false;
1761     }
1762 
1763     if (password.length() < 6) {
1764         showError(i18n("The password should be at least 6 characters long."));
1765         return false;
1766     }
1767 
1768     return true;
1769 }
1770 
showError(const QString & text)1771 void PdfDialog::showError(const QString &text)
1772 {
1773     KMessageBox::error(this, i18n("<center>") + text + i18n("</center>"), i18n("PDF Tools"));
1774 }
1775 
1776 // check tasks
isParameterTask(int task)1777 bool PdfDialog::isParameterTask(int task)
1778 {
1779     return ( task==PDF_SELECT || task==PDF_DELETE || task==PDF_PDFPAGES_FREE || task==PDF_PDFTK_FREE );
1780 }
1781 
isOverlayTask(int task)1782 bool PdfDialog::isOverlayTask(int task)
1783 {
1784     return ( task==PDF_PDFTK_BACKGROUND || task==PDF_PDFTK_STAMP );
1785 }
1786 
isBackgroundColor(int task)1787 bool PdfDialog::isBackgroundColor(int task)
1788 {
1789     return ( task == PDF_PDFTK_BGCOLOR ) ? true : false;
1790 }
1791 
isFreeTask(int task)1792 bool PdfDialog::isFreeTask(int task)
1793 {
1794     return ( task==PDF_PDFPAGES_FREE || task==PDF_PDFTK_FREE );
1795 }
1796 
1797 }
1798