File indexing completed on 2024-05-05 17:15:12

0001 /**********************************************************************************
0002     Ccopyright (C) 2005-2007 by Holger Danielsson (holger.danielsson@versanet.de)
0003  **********************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or modify  *
0008  *   it under the terms of the GNU General Public License as published by  *
0009  *   the Free Software Foundation; either version 2 of the License, or     *
0010  *   (at your option) any later version.                                   *
0011  *                                                                         *
0012  ***************************************************************************/
0013 
0014 // 2007-03-12 dani
0015 //  - use KileDocument::Extensions
0016 
0017 #include "dialogs/postscriptdialog.h"
0018 
0019 #include <QLabel>
0020 #include <QCheckBox>
0021 #include <QGridLayout>
0022 #include <QGroupBox>
0023 #include <QLayout>
0024 #include <QLineEdit>
0025 #include <QProcess>
0026 #include <QSpinBox>
0027 #include <QStringList>
0028 #include <QDialogButtonBox>
0029 #include <QPushButton>
0030 #include <QVBoxLayout>
0031 #include <QStandardPaths>
0032 
0033 #include <KComboBox>
0034 #include <KLocalizedString>
0035 #include <KMessageBox>
0036 #include <KProcess>
0037 
0038 #include <QTemporaryFile>
0039 #include <KUrlRequester>
0040 #include <KConfigGroup>
0041 #include <KLineEdit>
0042 
0043 #include "errorhandler.h"
0044 #include "kiledebug.h"
0045 #include "kiletool_enums.h"
0046 
0047 namespace KileDialog
0048 {
0049 
0050 PostscriptDialog::PostscriptDialog(QWidget *parent,
0051                                    const QString &texfilename,const QString &startdir,
0052                                    const QString &latexextensions,
0053                                    KileErrorHandler *errorHandler, KileWidget::OutputView *output) :
0054     QDialog(parent),
0055     m_startdir(startdir),
0056     m_errorHandler(errorHandler),
0057     m_output(output),
0058     m_proc(Q_NULLPTR)
0059 {
0060     setWindowTitle(i18n("Rearrange Postscript File"));
0061     setModal(true);
0062 
0063     QWidget *mainWidget = new QWidget(this);
0064     QVBoxLayout *mainLayout = new QVBoxLayout;
0065     setLayout(mainLayout);
0066     mainLayout->addWidget(mainWidget);
0067 
0068 
0069     // determine if a psfile already exists
0070     QString psfilename,psoutfilename;
0071     if(!texfilename.isEmpty()) {
0072         // working with a postscript document, so we try to determine the LaTeX source file
0073         QStringList extlist = latexextensions.split(' ');
0074         for (QStringList::Iterator it = extlist.begin(); it != extlist.end(); ++it) {
0075             if (texfilename.indexOf((*it), -(*it).length()) >= 0) {
0076                 QString basename = psfilename = texfilename.left(texfilename.length() - (*it).length());
0077                 psfilename = basename + ".ps";
0078                 psoutfilename = basename + "-out.ps";
0079                 if (!QFileInfo::exists(psfilename))
0080                     psfilename.clear();
0081                 break;
0082             }
0083         }
0084     }
0085 
0086     // prepare dialog
0087     QWidget *page = new QWidget(this);
0088     mainLayout->addWidget(page);
0089     m_PostscriptDialog.setupUi(page);
0090 
0091     // line 0: QLabel
0092     bool pstops = !QStandardPaths::findExecutable("pstops").isEmpty();
0093     bool psselect = !QStandardPaths::findExecutable("psselect").isEmpty();
0094 
0095     if (!pstops && !psselect) {
0096         m_PostscriptDialog.m_mwErrors->setMessageType(KMessageWidget::Error);
0097         m_PostscriptDialog.m_mwErrors->setText(i18n("Neither 'pstops' nor 'psselect' found."));
0098     } else if (!pstops) {
0099         m_PostscriptDialog.m_mwErrors->setMessageType(KMessageWidget::Warning);
0100         m_PostscriptDialog.m_mwErrors->setText(i18n("'pstops' not found."));
0101     } else if (!psselect) {
0102         m_PostscriptDialog.m_mwErrors->setMessageType(KMessageWidget::Warning);
0103         m_PostscriptDialog.m_mwErrors->setText(i18n("'psselect' not found."));
0104     } else {
0105         m_PostscriptDialog.m_mwErrors->hide();
0106     }
0107 
0108     m_PostscriptDialog.m_edInfile->lineEdit()->setText(psfilename);
0109     m_PostscriptDialog.m_edOutfile->lineEdit()->setText(psoutfilename);
0110 
0111     // according to QT 4.4 docu the index of QComboBox might change if adding or removing items
0112     // but because we populate the QComboBox before we start the dialog, we can use the index here
0113     if (pstops) {
0114         m_PostscriptDialog.m_cbTask->addItem(i18n("1 DIN A5 Page + Empty Page --> DIN A4")); // 0   PS_A5_EMPTY
0115         m_PostscriptDialog.m_cbTask->addItem(i18n("1 DIN A5 Page + Duplicate --> DIN A4"));  // 1   PS_A5_DUPLICATE
0116         m_PostscriptDialog.m_cbTask->addItem(i18n("2 DIN A5 Pages --> DIN A4"));             // 2   PS_2xA5
0117         m_PostscriptDialog.m_cbTask->addItem(i18n("2 DIN A5L Pages --> DIN A4"));            // 3   PS_2xA5L
0118         m_PostscriptDialog.m_cbTask->addItem(i18n("4 DIN A5 Pages --> DIN A4"));             // 4   PS_4xA5
0119         m_PostscriptDialog.m_cbTask->addItem(i18n("1 DIN A4 Page + Empty Page --> DIN A4")); // 5   m_PostscriptDialog.PS_A4_EMPTY
0120         m_PostscriptDialog.m_cbTask->addItem(i18n("1 DIN A4 Page + Duplicate --> DIN A4"));  // 6   PS_A4_DUPLICATE
0121         m_PostscriptDialog.m_cbTask->addItem(i18n("2 DIN A4 Pages --> DIN A4"));             // 7   PS_2xA4
0122         m_PostscriptDialog.m_cbTask->addItem(i18n("2 DIN A4L Pages --> DIN A4"));            // 8   PS_2xA4L
0123     }
0124     if (psselect) {
0125         m_PostscriptDialog.m_cbTask->addItem(i18n("Select Even Pages"));                 // 9   PS_EVEN
0126         m_PostscriptDialog.m_cbTask->addItem(i18n("Select Odd Pages"));                  // 10  PS_ODD
0127         m_PostscriptDialog.m_cbTask->addItem(i18n("Select Even Pages (reverse order)")); // 11  m_PostscriptDialog.PS_EVEN_REV
0128         m_PostscriptDialog.m_cbTask->addItem(i18n("Select Odd Pages (reverse order)"));  // 12  PS_ODD_REV
0129         m_PostscriptDialog.m_cbTask->addItem(i18n("Reverse All Pages"));                 // 13  PS_REVERSE
0130         m_PostscriptDialog.m_cbTask->addItem(i18n("Copy All Pages (sorted)"));           // 14  PS_COPY_SORTED
0131     }
0132     if (pstops) {
0133         m_PostscriptDialog.m_cbTask->addItem(i18n("Copy All Pages (unsorted)")); // 15  PS_COPY_UNSORTED
0134         m_PostscriptDialog.m_cbTask->addItem(i18n("pstops: Choose Parameter"));  // 16  PS_PSTOPS_FREE
0135     }
0136     if (psselect) {
0137         m_PostscriptDialog.m_cbTask->addItem(i18n("psselect: Choose Parameter")); // 17  PS_PSSELECT_FREE
0138     }
0139     const QStringList postscriptMimeTypes = {
0140         QStringLiteral("application/postscript"),
0141         QStringLiteral("application/x-gzpostscript"),
0142     };
0143     m_PostscriptDialog.m_edInfile->setMimeTypeFilters(postscriptMimeTypes);
0144     m_PostscriptDialog.m_edOutfile->setMimeTypeFilters(postscriptMimeTypes);
0145     m_PostscriptDialog.m_edOutfile->setMode(KFile::File | KFile::LocalOnly);
0146 
0147     // choose one common task
0148     m_PostscriptDialog.m_cbTask->setCurrentIndex(PS_2xA4);
0149     comboboxChanged(PS_2xA4);
0150 
0151     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
0152     QPushButton *executeButton = new QPushButton;
0153     buttonBox->addButton(executeButton, QDialogButtonBox::ActionRole);
0154 
0155     // set an user button to execute the task
0156     buttonBox->button(QDialogButtonBox::Close)->setText(i18n("Done"));
0157     executeButton->setText(i18n("Execute"));
0158     executeButton->setIcon(QIcon::fromTheme("system-run"));
0159     if (!pstops && !psselect)
0160         executeButton->setEnabled(false);
0161 
0162     setFocusProxy(m_PostscriptDialog.m_edInfile);
0163     m_PostscriptDialog.m_edInfile->setFocus();
0164 
0165     mainLayout->addWidget(buttonBox);
0166     executeButton->setDefault(true);
0167     mainLayout->addWidget(buttonBox);
0168     connect(buttonBox, &QDialogButtonBox::accepted,
0169             this, &QDialog::accept);
0170     connect(buttonBox, &QDialogButtonBox::rejected,
0171             this, &QDialog::reject);
0172     connect(executeButton, &QPushButton::clicked,
0173             this, &PostscriptDialog::slotExecuteClicked);
0174     connect(m_PostscriptDialog.m_cbTask, static_cast<void (QComboBox::*)(int)>(&KComboBox::activated),
0175             this, &PostscriptDialog::comboboxChanged);
0176     connect(this, &PostscriptDialog::output, m_output,
0177             &KileWidget::OutputView::receive);
0178 }
0179 
0180 PostscriptDialog::~PostscriptDialog()
0181 {
0182     if (m_proc) {
0183         delete m_proc;
0184     }
0185 }
0186 
0187 void PostscriptDialog::slotExecuteClicked()
0188 {
0189     if (checkParameter()) {
0190         execute();
0191     }
0192 }
0193 
0194 void PostscriptDialog::execute()
0195 {
0196     m_tempfile = buildTempfile();
0197 
0198     if(m_tempfile.isEmpty()) {
0199         m_errorHandler->printMessage(KileTool::Error, i18n("Could not create a temporary file."));
0200         return;
0201     }
0202     else {
0203         m_errorHandler->clearMessages();
0204         QFileInfo from(m_PostscriptDialog.m_edInfile->lineEdit()->text());
0205         QFileInfo to(m_PostscriptDialog.m_edOutfile->lineEdit()->text());
0206 
0207         // output for log window
0208         QString msg = i18n("rearrange ps file: %1", from.fileName());
0209         if (!to.fileName().isEmpty())
0210             msg += " ---> " + to.fileName();
0211         m_errorHandler->printMessage(KileTool::Info, msg, m_program);
0212 
0213         // some output logs
0214         m_output->clear();
0215         QString s = QString("*****\n")
0216                     + i18n("***** tool:        ") + m_program + ' ' + m_param + '\n'
0217                     + i18n("***** input file:  ") + from.fileName()+ '\n'
0218                     + i18n("***** output file: ") + to.fileName()+ '\n'
0219                     + i18n("***** viewer:      ") + ((m_PostscriptDialog.m_cbView->isChecked()) ? i18n("yes") : i18n("no")) + '\n'
0220                     + "*****\n";
0221         emit( output(s) );
0222 
0223         // delete old KProcess
0224         if (m_proc)
0225             delete m_proc;
0226 
0227         m_proc = new KProcess();
0228         m_proc->setShellCommand("sh " + m_tempfile);
0229         m_proc->setOutputChannelMode(KProcess::MergedChannels);
0230         m_proc->setReadChannel(QProcess::StandardOutput);
0231 
0232         connect(m_proc, SIGNAL(readyReadStandardOutput()),
0233                 this, SLOT(slotProcessOutput()));
0234         connect(m_proc, SIGNAL(readyReadStandardError()),
0235                 this, SLOT(slotProcessOutput()));
0236         connect(m_proc, SIGNAL(finished(int,QProcess::ExitStatus)),
0237                 this, SLOT(slotProcessExited(int,QProcess::ExitStatus)));
0238 
0239         KILE_DEBUG_MAIN << "=== PostscriptDialog::runPsutils() ====================";
0240         KILE_DEBUG_MAIN << "   execute '" << m_tempfile << "'";
0241         m_proc->start();
0242     }
0243 }
0244 
0245 void PostscriptDialog::slotProcessOutput()
0246 {
0247     emit(output(m_proc->readAllStandardOutput()));
0248     emit(output(m_proc->readAllStandardError()));
0249 }
0250 
0251 
0252 void PostscriptDialog::slotProcessExited(int /* exitCode */, QProcess::ExitStatus exitStatus)
0253 {
0254     if (exitStatus != QProcess::NormalExit) {
0255         showError(i18n("An error occurred while rearranging the file."));
0256     }
0257 
0258     QFile::remove(m_tempfile);
0259 }
0260 
0261 QString PostscriptDialog::buildTempfile()
0262 {
0263     // build command
0264     m_program = "pstops";          // default
0265     m_param = "";
0266 
0267     switch (m_PostscriptDialog.m_cbTask->currentIndex()) {
0268     case PS_A5_EMPTY:
0269         m_param = "1:0L(29.7cm,0cm)";
0270         break;
0271     case PS_A5_DUPLICATE:
0272         m_param = "1:0L(29.7cm,0cm)+0L(29.7cm,14.85cm)";
0273         break;
0274     case PS_2xA5:
0275         m_param = "2:0L(29.7cm,0cm)+1L(29.7cm,14.85cm)";
0276         break;
0277     case PS_2xA5L:
0278         break;
0279     case PS_4xA5:
0280         m_param = "4:0@0.7(0cm,8.7cm)"
0281                   "+1@0.7(10.5cm,8.7cm)"
0282                   "+2@0.7(0cm,-6.15cm)"
0283                   "+3@0.7(10.5cm,-6.15cm)";
0284         break;
0285     case PS_A4_EMPTY:
0286         m_param = "1:0L@0.7(21cm,0cm)";
0287         break;
0288     case PS_A4_DUPLICATE:
0289         m_param = "1:0L@0.7(21cm,0cm)+0L@0.7(21cm,14.85cm)";
0290         break;
0291     case PS_2xA4:
0292         m_param = "2:0L@0.7(21cm,0cm)+1L@0.7(21cm,14.85cm)";
0293         break;
0294     case PS_2xA4L:
0295         m_param = "2:0R@0.7(0cm,29.7cm)+1R@0.7(0cm,14.85cm)";
0296         break;
0297     case PS_EVEN:
0298         m_program = "psselect";
0299         m_param = "-e";
0300         break;
0301     case PS_ODD:
0302         m_program = "psselect";
0303         m_param = "-o";
0304         break;
0305     case PS_EVEN_REV:
0306         m_program = "psselect";
0307         m_param = "-e -r";
0308         break;
0309     case PS_ODD_REV:
0310         m_program = "psselect";
0311         m_param = "-o -r";
0312         break;
0313     case PS_REVERSE:
0314         m_program = "psselect";
0315         m_param = "-r";
0316         break;
0317     case PS_COPY_SORTED:
0318         m_program = "psselect";
0319         m_param = "-p" + duplicateParameter("1-");
0320         break;
0321     case PS_COPY_UNSORTED:
0322         m_param = "1:" + duplicateParameter("0");
0323         break;
0324     case PS_PSTOPS_FREE:
0325         m_param = m_PostscriptDialog.m_edParameter->text();
0326         break;
0327     case PS_PSSELECT_FREE:
0328         m_program = "psselect";
0329         m_param = m_PostscriptDialog.m_edParameter->text();
0330         break;
0331     }
0332 
0333     // create a temporary file
0334     QTemporaryFile temp;
0335 //code was  temp.setSuffix(".sh");
0336 //Add to constructor and adapt if necessay: QDir::tempPath() + QLatin1String("/myapp_XXXXXX") + QLatin1String(".sh")
0337     temp.setAutoRemove(false);
0338     if(!temp.open()) {
0339         KILE_DEBUG_MAIN << "Could not create tempfile in QString PostscriptDialog::buildTempfile()" ;
0340         return QString();
0341     }
0342     QString tempname = temp.fileName();
0343 
0344     QTextStream stream(&temp);
0345     stream << "#! /bin/sh" << Qt::endl;
0346 
0347     // accept only ".ps" or ".ps.gz" as an input file
0348     QFileInfo fi(m_PostscriptDialog.m_edInfile->lineEdit()->text());
0349     bool zipped_psfile = (fi.completeSuffix() == "ps.gz") ? true : false;
0350 
0351     // there are four possible cases
0352     //         outfile view
0353     //     1)    +      +        pstops/psselect + okular
0354     //     2)    +      -        pstops/psselect
0355     //     3)    -      +        pstops/psselect | okular (nur Shell)
0356     //     4)    -      -        error (already detected by checkParameter())
0357 
0358     // some files, which are used
0359     QString command    = m_program + " \"" + m_param + "\"";
0360     QString inputfile  = "\"" + m_PostscriptDialog.m_edInfile->lineEdit()->text() + "\"";
0361     QString outputfile = "\"" + m_PostscriptDialog.m_edOutfile->lineEdit()->text() + "\"";
0362     bool viewer = m_PostscriptDialog.m_cbView->isChecked();
0363 
0364     bool equalfiles = false;
0365     if (inputfile == outputfile) {
0366         outputfile = tempname + ".tmp";
0367         equalfiles = true;
0368     }
0369 
0370     if (!zipped_psfile) {                                       // unzipped ps files
0371         if (m_PostscriptDialog.m_edOutfile->lineEdit()->text().isEmpty()) { // pstops/psselect | okular
0372             stream << command << " " << inputfile << " | okular -" << Qt::endl;
0373             viewer = false;
0374         } else {                                                    // pstops/psselect
0375             stream << command << " " << inputfile << " " << outputfile << Qt::endl;
0376         }
0377     } else {                                                      // zipped ps files
0378         if (m_PostscriptDialog.m_edOutfile->lineEdit()->text().isEmpty()) { // pstops/psselect | okular
0379             stream << "gunzip -c " << inputfile
0380                    << " | " << command
0381                    << " | okular -"
0382                    << Qt::endl;
0383             viewer = false;
0384         } else {
0385             stream << "gunzip -c " << inputfile                    // pstops/psselect
0386                    << " | " << command
0387                    << " > " << outputfile
0388                    << Qt::endl;
0389         }
0390     }
0391 
0392     // check, if we should stop
0393     if ( equalfiles || viewer ) {
0394         stream << "if [ $? != 0 ]; then" << Qt::endl;
0395         stream << "   exit 1" << Qt::endl;
0396         stream << "fi" << Qt::endl;
0397     }
0398 
0399     // replace the original file
0400     if ( equalfiles ) {
0401         stream << "rm " << inputfile << Qt::endl;
0402         stream << "mv " << outputfile << " " << inputfile << Qt::endl;
0403     }
0404 
0405     // viewer
0406     if ( viewer ) {                                                // viewer: okular
0407         stream << "okular" << " "
0408                << ((equalfiles) ? inputfile : outputfile) << Qt::endl;
0409     }
0410 
0411     // everything is prepared to do the job
0412     temp.close();
0413 
0414     return(tempname);
0415 }
0416 
0417 QString PostscriptDialog::duplicateParameter(const QString &param)
0418 {
0419     QString s;
0420     for (int i = 0; i < m_PostscriptDialog.m_spCopies->value(); ++i) {
0421         if (i == 0)
0422             s += param;
0423         else
0424             s += ',' + param;
0425     }
0426 
0427     return s;
0428 }
0429 
0430 
0431 bool PostscriptDialog::checkParameter()
0432 {
0433     QString infile = m_PostscriptDialog.m_edInfile->lineEdit()->text();
0434     if (infile.isEmpty()) {
0435         showError(i18n("No input file is given."));
0436         return false;
0437     }
0438 
0439     QFileInfo fi(infile);
0440     QString suffix = fi.completeSuffix();
0441     if (suffix != "ps" && suffix != "ps.gz") {
0442         showError(i18n("Unknown file format: only '.ps' and '.ps.gz' are accepted for input files."));
0443         return false;
0444     }
0445 
0446     if (!fi.exists()) {
0447         showError(i18n("This input file does not exist."));
0448         return false;
0449     }
0450 
0451     // check parameter
0452     int index = m_PostscriptDialog.m_cbTask->currentIndex();
0453     if (m_PostscriptDialog.m_edParameter->text().isEmpty()) {
0454         if (index == PS_PSSELECT_FREE) {
0455             showError( i18n("psselect needs some parameters in this mode.") );
0456             return false;
0457         } else if (index == PS_PSTOPS_FREE) {
0458             showError( i18n("pstops needs some parameters in this mode.") );
0459             return false;
0460         }
0461     }
0462 
0463     QString outfile = m_PostscriptDialog.m_edOutfile->lineEdit()->text();
0464     if (outfile.isEmpty() && !m_PostscriptDialog.m_cbView->isChecked()) {
0465         showError(i18n("You need to define an output file or select the viewer."));
0466         return false;
0467     }
0468 
0469     if (! outfile.isEmpty()) {
0470         QFileInfo fo(outfile);
0471         if (fo.completeSuffix() != "ps") {
0472             showError(i18n("Unknown file format: only '.ps' is accepted as output file."));
0473             return false;
0474         }
0475 
0476         if (infile != outfile && fo.exists()) {
0477             QString s = i18n("A file named \"%1\" already exists. Are you sure you want to overwrite it?", fo.fileName());
0478             if (KMessageBox::questionTwoActions(this,
0479                                                 "<center>" + s + "</center>",
0480                                                 "Postscript tools",
0481                                                 KStandardGuiItem::overwrite(), KStandardGuiItem::cancel())
0482                 == KMessageBox::SecondaryAction) {
0483                 return false;
0484             }
0485         }
0486     }
0487 
0488     return true;
0489 }
0490 
0491 void PostscriptDialog::comboboxChanged(int index)
0492 {
0493     KILE_DEBUG_MAIN << index << Qt::endl;
0494     if (index == PS_COPY_SORTED || index == PS_COPY_UNSORTED) {
0495         m_PostscriptDialog.m_lbParameter->setEnabled(true);
0496         m_PostscriptDialog.m_lbParameter->setText(i18n("Copies:"));
0497         m_PostscriptDialog.m_edParameter->hide();
0498         m_PostscriptDialog.m_spCopies->show();
0499         m_PostscriptDialog.m_spCopies->setEnabled(true);
0500     } else {
0501         if (index == PS_PSSELECT_FREE || index == PS_PSTOPS_FREE) {
0502             m_PostscriptDialog.m_lbParameter->setEnabled(true);
0503             m_PostscriptDialog.m_lbParameter->setText(i18n("Parameter:"));
0504             m_PostscriptDialog.m_spCopies->hide();
0505             m_PostscriptDialog.m_edParameter->show();
0506             m_PostscriptDialog.m_edParameter->setEnabled(true);
0507         } else {
0508             m_PostscriptDialog.m_lbParameter->setEnabled(false);
0509             m_PostscriptDialog.m_edParameter->setEnabled(false);
0510             m_PostscriptDialog.m_spCopies->setEnabled(false);
0511         }
0512     }
0513 }
0514 
0515 void PostscriptDialog::showError(const QString &text)
0516 {
0517     KMessageBox::error(this, i18n("<center>") + text + i18n("</center>"), i18n("Postscript Tools"));
0518 }
0519 
0520 }