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 ¶m)
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 ¶m)
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