kdeprint Library API Documentation

kprinterimpl.cpp

00001 /*
00002  *  This file is part of the KDE libraries
00003  *  Copyright (c) 2001 Michael Goffioul <kdeprint@swing.be>
00004  *
00005  *
00006  *  This library is free software; you can redistribute it and/or
00007  *  modify it under the terms of the GNU Library General Public
00008  *  License version 2 as published by the Free Software Foundation.
00009  *
00010  *  This library is distributed in the hope that it will be useful,
00011  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013  *  Library General Public License for more details.
00014  *
00015  *  You should have received a copy of the GNU Library General Public License
00016  *  along with this library; see the file COPYING.LIB.  If not, write to
00017  *  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00018  *  Boston, MA 02111-1307, USA.
00019  **/
00020 
00021 #include "kprinterimpl.h"
00022 #include "kprinter.h"
00023 #include "kmfactory.h"
00024 #include "kmmanager.h"
00025 #include "kmuimanager.h"
00026 #include "kxmlcommand.h"
00027 #include "kmspecialmanager.h"
00028 #include "kmthreadjob.h"
00029 #include "kmprinter.h"
00030 #include "driver.h"
00031 
00032 #include <qfile.h>
00033 #include <qregexp.h>
00034 #include <kinputdialog.h>
00035 #include <klocale.h>
00036 #include <dcopclient.h>
00037 #include <kapplication.h>
00038 #include <kstandarddirs.h>
00039 #include <kdatastream.h>
00040 #include <kdebug.h>
00041 #include <kmimemagic.h>
00042 #include <kmessagebox.h>
00043 #include <kprocess.h>
00044 #include <kconfig.h>
00045 
00046 #include <stdlib.h>
00047 
00048 void dumpOptions(const QMap<QString,QString>&);
00049 void initEditPrinter(KMPrinter *p)
00050 {
00051     if (!p->isEdited())
00052     {
00053         p->setEditedOptions(p->defaultOptions());
00054         p->setEdited(true);
00055     }
00056 }
00057 
00058 //****************************************************************************************
00059 
00060 KPrinterImpl::KPrinterImpl(QObject *parent, const char *name)
00061 : QObject(parent,name)
00062 {
00063     loadAppOptions();
00064 }
00065 
00066 KPrinterImpl::~KPrinterImpl()
00067 {
00068 }
00069 
00070 void KPrinterImpl::preparePrinting(KPrinter *printer)
00071 {
00072     // page size -> try to find page size and margins from driver file
00073     // use "PageSize" as option name to find the wanted page size. It's
00074     // up to the driver loader to use that option name.
00075     KMManager   *mgr = KMFactory::self()->manager();
00076     DrMain  *driver = mgr->loadPrinterDriver(mgr->findPrinter(printer->printerName()), false);
00077     if (driver)
00078     {
00079         // Find the page size:
00080         // 1) print option
00081         // 2) default driver option
00082         QString psname = printer->option("PageSize");
00083         if (psname.isEmpty())
00084         {
00085             DrListOption    *opt = (DrListOption*)driver->findOption("PageSize");
00086             if (opt) psname = opt->get("default");
00087         }
00088         if (!psname.isEmpty())
00089         {
00090             printer->setOption("kde-pagesize",QString::number((int)pageNameToPageSize(psname)));
00091             DrPageSize  *ps = driver->findPageSize(psname);
00092             if (ps)
00093             {
00094                 printer->setRealPageSize( ps );
00095             }
00096         }
00097 
00098         // Find the numerical resolution
00099         // 1) print option (Resolution)
00100         // 2) default driver option (Resolution)
00101         // 3) default printer resolution
00102         // The resolution must have the format: XXXdpi or XXXxYYYdpi. In the second
00103         // case the YYY value is used as resolution.
00104         QString res = printer->option( "Resolution" );
00105         if ( res.isEmpty() )
00106         {
00107             DrBase *opt = driver->findOption( "Resolution" );
00108             if ( opt )
00109                 res = opt->get( "default" );
00110             if ( res.isEmpty() )
00111                 res = driver->get( "resolution" );
00112         }
00113         if ( !res.isEmpty() )
00114         {
00115             QRegExp re( "(\\d+)(?:x(\\d+))?dpi" );
00116             if ( re.search( res ) != -1 )
00117             {
00118                 if ( !re.cap( 2 ).isEmpty() )
00119                     printer->setOption( "kde-resolution", re.cap( 2 ) );
00120                 else
00121                     printer->setOption( "kde-resolution", re.cap( 1 ) );
00122             }
00123         }
00124 
00125         // Find the supported fonts
00126         QString fonts = driver->get( "fonts" );
00127         if ( !fonts.isEmpty() )
00128             printer->setOption( "kde-fonts", fonts );
00129 
00130         delete driver;
00131     }
00132 
00133 }
00134 
00135 bool KPrinterImpl::setupCommand(QString&, KPrinter*)
00136 {
00137     return false;
00138 }
00139 
00140 bool KPrinterImpl::printFiles(KPrinter *p, const QStringList& f, bool flag)
00141 {
00142     QString cmd;
00143     if (p->option("kde-isspecial") == "1")
00144     {
00145         if (p->option("kde-special-command").isEmpty() && p->outputToFile())
00146         {
00147             KURL url( p->outputFileName() );
00148             if ( !url.isLocalFile() )
00149             {
00150                 cmd = ( flag ? "mv" : "cp" ) + ( " %in $out{" + p->outputFileName() + "}" );
00151             }
00152             else
00153             {
00154                 if (f.count() > 1)
00155                 {
00156                     p->setErrorMessage(i18n("Cannot copy multiple files into one file."));
00157                     return false;
00158                 }
00159                 else
00160                 {
00161                     KProcess proc;
00162                     proc << (flag?"mv":"cp") << f[0] << p->outputFileName();
00163                     if (!proc.start(KProcess::Block) || !proc.normalExit() || proc.exitStatus() != 0)
00164                     {
00165                         p->setErrorMessage(i18n("Cannot save print file to %1. Check that you have write access to it.").arg(p->outputFileName()));
00166                         return false;
00167                     }
00168                 }
00169                 return true;
00170             }
00171         }
00172         else if (!setupSpecialCommand(cmd,p,f))
00173             return false;
00174     }
00175     else if (!setupCommand(cmd,p))
00176         return false;
00177     return startPrinting(cmd,p,f,flag);
00178 }
00179 
00180 void KPrinterImpl::broadcastOption(const QString& key, const QString& value)
00181 {
00182     // force printer listing if not done yet (or reload needed)
00183     QPtrList<KMPrinter> *printers = KMFactory::self()->manager()->printerListComplete(false);
00184     if (printers)
00185     {
00186         QPtrListIterator<KMPrinter> it(*printers);
00187         for (;it.current();++it)
00188         {
00189             initEditPrinter(it.current());
00190             it.current()->setEditedOption(key,value);
00191         }
00192     }
00193 }
00194 
00195 int KPrinterImpl::dcopPrint(const QString& cmd, const QStringList& files, bool removeflag)
00196 {
00197     kdDebug(500) << "kdeprint: print command: " << cmd << endl;
00198 
00199     int result = 0;
00200     DCOPClient  *dclient = kapp->dcopClient();
00201     if (!dclient || (!dclient->isAttached() && !dclient->attach()))
00202     {
00203         return result;
00204     }
00205 
00206     QByteArray data, replyData;
00207     QCString replyType;
00208     QDataStream arg( data, IO_WriteOnly );
00209     arg << cmd;
00210     arg << files;
00211     arg << removeflag;
00212     if (dclient->call( "kded", "kdeprintd", "print(QString,QStringList,bool)", data, replyType, replyData ))
00213     {
00214         if (replyType == "int")
00215         {
00216             QDataStream _reply_stream( replyData, IO_ReadOnly );
00217             _reply_stream >> result;
00218         }
00219     }
00220     return result;
00221 }
00222 
00223 void KPrinterImpl::statusMessage(const QString& msg, KPrinter *printer)
00224 {
00225     kdDebug(500) << "kdeprint: status message: " << msg << endl;
00226     KConfig *conf = KMFactory::self()->printConfig();
00227     conf->setGroup("General");
00228     if (!conf->readBoolEntry("ShowStatusMsg", true))
00229         return;
00230 
00231     QString message(msg);
00232     if (printer && !msg.isEmpty())
00233         message.prepend(i18n("Printing document: %1").arg(printer->docName())+"\n");
00234 
00235     DCOPClient  *dclient = kapp->dcopClient();
00236     if (!dclient || (!dclient->isAttached() && !dclient->attach()))
00237     {
00238         return;
00239     }
00240 
00241     QByteArray data;
00242     QDataStream arg( data, IO_WriteOnly );
00243     arg << message;
00244     arg << (int)getpid();
00245     arg << kapp->caption();
00246     dclient->send( "kded", "kdeprintd", "statusMessage(QString,int,QString)", data );
00247 }
00248 
00249 bool KPrinterImpl::startPrinting(const QString& cmd, KPrinter *printer, const QStringList& files, bool flag)
00250 {
00251     statusMessage(i18n("Sending print data to printer: %1").arg(printer->printerName()), printer);
00252 
00253     QString command(cmd), filestr;
00254     QStringList printfiles;
00255     if (command.find("%in") == -1) command.append(" %in");
00256 
00257     for (QStringList::ConstIterator it=files.begin(); it!=files.end(); ++it)
00258         if (QFile::exists(*it))
00259         {
00260             // quote and encode filenames
00261             filestr.append(quote(QFile::encodeName(*it))).append(" ");
00262             printfiles.append(*it);
00263         }
00264         else
00265             kdDebug(500) << "File not found: " << (*it) << endl;
00266 
00267     if (printfiles.count() > 0)
00268     {
00269         command.replace("%in",filestr);
00270         int pid = dcopPrint(command,files,flag);
00271         if (pid > 0)
00272         {
00273             if (printer)
00274                 KMThreadJob::createJob(pid,printer->printerName(),printer->docName(),getenv("USER"),0);
00275             return true;
00276         }
00277         else
00278         {
00279             QString msg = i18n("Unable to start child print process. ");
00280             if (pid == 0)
00281                 msg += i18n("The KDE print server (<b>kdeprintd</b>) could not be contacted. Check that this server is running.");
00282             else
00283                 msg += i18n("1 is the command that <files> is given to", "Check the command syntax:\n%1 <files>").arg(cmd);
00284             printer->setErrorMessage(msg);
00285             return false;
00286         }
00287     }
00288     //else
00289     //{
00290         printer->setErrorMessage(i18n("No valid file was found for printing. Operation aborted."));
00291         return false;
00292     //}
00293 }
00294 
00295 QString KPrinterImpl::tempFile()
00296 {
00297     QString f;
00298     // be sure the file doesn't exist
00299     do f = locateLocal("tmp","kdeprint_") + KApplication::randomString(8); while (QFile::exists(f));
00300     return f;
00301 }
00302 
00303 int KPrinterImpl::filterFiles(KPrinter *printer, QStringList& files, bool flag)
00304 {
00305     QStringList flist = QStringList::split(',',printer->option("_kde-filters"),false);
00306     QMap<QString,QString>   opts = printer->options();
00307 
00308     // generic page selection mechanism (using psselect filter)
00309     // do it only if:
00310     //  - using system-side page selection
00311     //  - special printer or regular printer without page selection support in current plugin
00312     //  - one of the page selection option has been selected to non default value
00313     // Action -> add the psselect filter to the filter chain.
00314     if (printer->pageSelection() == KPrinter::SystemSide &&
00315         (printer->option("kde-isspecial") == "1" || !(KMFactory::self()->uiManager()->pluginPageCap() & KMUiManager::PSSelect)) &&
00316         (printer->pageOrder() == KPrinter::LastPageFirst ||
00317          !printer->option("kde-range").isEmpty() ||
00318          printer->pageSet() != KPrinter::AllPages))
00319     {
00320         if (flist.findIndex("psselect") == -1)
00321         {
00322             int index = KXmlCommandManager::self()->insertCommand(flist, "psselect", false);
00323             if (index == -1 || !KXmlCommandManager::self()->checkCommand("psselect"))
00324             {
00325                 printer->setErrorMessage(i18n("<p>Unable to perform the requested page selection. The filter <b>psselect</b> "
00326                                   "cannot be inserted in the current filter chain. See <b>Filter</b> tab in the "
00327                                   "printer properties dialog for further information.</p>"));
00328                 return -1;
00329             }
00330         }
00331         if (printer->pageOrder() == KPrinter::LastPageFirst)
00332             opts["_kde-psselect-order"] = "r";
00333         if (!printer->option("kde-range").isEmpty())
00334             opts["_kde-psselect-range"] = printer->option("kde-range");
00335         if (printer->pageSet() != KPrinter::AllPages)
00336             opts["_kde-psselect-set"] = (printer->pageSet() == KPrinter::OddPages ? "-o" : "-e");
00337     }
00338 
00339     return doFilterFiles(printer, files, flist, opts, flag);
00340 }
00341 
00342 int KPrinterImpl::doFilterFiles(KPrinter *printer, QStringList& files, const QStringList& flist, const QMap<QString,QString>& opts, bool flag)
00343 {
00344     // nothing to do
00345     if (flist.count() == 0)
00346         return 0;
00347 
00348     QString filtercmd;
00349     QStringList inputMimeTypes;
00350     for (uint i=0;i<flist.count();i++)
00351     {
00352         KXmlCommand *filter = KXmlCommandManager::self()->loadCommand(flist[i]);
00353         if (!filter)
00354         {
00355             // TODO: better error message
00356             printer->setErrorMessage(i18n("<p>Error while reading filter description for <b>%1</b>. Empty command line received.</p>").arg(flist[i]));
00357             return -1; // Error
00358         }
00359         if (i == 0)
00360             inputMimeTypes = filter->inputMimeTypes();
00361 
00362         QString     subcmd = filter->buildCommand(opts,(i>0),(i<(flist.count()-1)));
00363         delete filter;
00364         if (!subcmd.isEmpty())
00365         {
00366             filtercmd.append(subcmd);
00367             if (i < flist.count()-1)
00368                 filtercmd.append("| ");
00369         }
00370         else
00371         {
00372             printer->setErrorMessage(i18n("<p>Error while reading filter description for <b>%1</b>. Empty command line received.</p>").arg(flist[i]));
00373             return -1;
00374         }
00375     }
00376     kdDebug(500) << "kdeprint: filter command: " << filtercmd << endl;
00377 
00378     QString rin("%in"), rout("%out"), rpsl("%psl"), rpsu("%psu");
00379     QString ps = pageSizeToPageName( printer->option( "kde-printsize" ).isEmpty() ? printer->pageSize() : ( KPrinter::PageSize )printer->option( "kde-printsize" ).toInt() );
00380     for (QStringList::Iterator it=files.begin(); it!=files.end(); ++it)
00381     {
00382         QString mime = KMimeMagic::self()->findFileType(*it)->mimeType();
00383         if (inputMimeTypes.find(mime) == inputMimeTypes.end())
00384         {
00385             if (KMessageBox::warningContinueCancel(0,
00386                 "<p>" + i18n("The MIME type %1 is not supported as input of the filter chain "
00387                      "(this may happen with non-CUPS spoolers when performing page selection "
00388                      "on a non-PostScript file). Do you want KDE to convert the file to a supported "
00389                      "format?</p>").arg(mime),
00390                 QString::null, i18n("Convert")) == KMessageBox::Continue)
00391             {
00392                 QStringList ff;
00393                 int done(0);
00394 
00395                 ff << *it;
00396                 while (done == 0)
00397                 {
00398                     bool    ok(false);
00399                     QString targetMime = KInputDialog::getItem(
00400                         i18n("Select MIME Type"),
00401                         i18n("Select the target format for the conversion:"),
00402                         inputMimeTypes, 0, false, &ok);
00403                     if (!ok)
00404                     {
00405                         printer->setErrorMessage(i18n("Operation aborted."));
00406                         return -1;
00407                     }
00408                     QStringList filters = KXmlCommandManager::self()->autoConvert(mime, targetMime);
00409                     if (filters.count() == 0)
00410                     {
00411                         KMessageBox::error(0, i18n("No appropriate filter found. Select another target format."));
00412                     }
00413                     else
00414                     {
00415                         int result = doFilterFiles(printer, ff, filters, QMap<QString,QString>(), flag);
00416                         if (result == 1)
00417                         {
00418                             *it = ff[0];
00419                             done = 1;
00420                         }
00421                         else
00422                         {
00423                             KMessageBox::error(0,
00424                                 i18n("<qt>Operation failed with message:<br>%1<br>Select another target format.</qt>").arg(printer->errorMessage()));
00425                         }
00426                     }
00427                 }
00428             }
00429             else
00430             {
00431                 printer->setErrorMessage(i18n("Operation aborted."));
00432                 return -1;
00433             }
00434         }
00435 
00436         QString tmpfile = tempFile();
00437         QString cmd(filtercmd);
00438         cmd.replace(rout,quote(tmpfile));
00439         cmd.replace(rpsl,ps.lower());
00440         cmd.replace(rpsu,ps);
00441         cmd.replace(rin,quote(*it)); // Replace as last, filename could contain "%psl"
00442         statusMessage(i18n("Filtering print data"), printer);
00443         int status = system(QFile::encodeName(cmd));
00444         if (status < 0 || WEXITSTATUS(status) == 127)
00445         {
00446             printer->setErrorMessage(i18n("Error while filtering. Command was: <b>%1</b>.").arg(filtercmd));
00447             return -1;
00448         }
00449         if (flag) QFile::remove(*it);
00450         *it = tmpfile;
00451     }
00452     return 1;
00453 }
00454 
00455 int KPrinterImpl::autoConvertFiles(KPrinter *printer, QStringList& files, bool flag)
00456 {
00457     QString primaryMimeType = "application/postscript";
00458     QStringList mimeTypes( primaryMimeType );
00459     if ( printer->option( "kde-isspecial" ) == "1" )
00460     {
00461         if ( !printer->option( "kde-special-command" ).isEmpty() )
00462         {
00463             KXmlCommand *cmd = KXmlCommandManager::self()->loadCommand( printer->option( "kde-special-command" ), true );
00464             if ( cmd )
00465             {
00466                 mimeTypes = cmd->inputMimeTypes();
00467                 // FIXME: the XML command description should now contain a primiary
00468                 // mime type as well. This is a temporary-only solution.
00469                 primaryMimeType = mimeTypes[ 0 ];
00470             }
00471         }
00472     }
00473     else
00474     {
00475         KMFactory::PluginInfo   info = KMFactory::self()->pluginInfo(KMFactory::self()->printSystem());
00476         mimeTypes = info.mimeTypes;
00477         primaryMimeType = info.primaryMimeType;
00478     }
00479     KMFactory::PluginInfo   info = KMFactory::self()->pluginInfo(KMFactory::self()->printSystem());
00480     int     status(0), result;
00481     for (QStringList::Iterator it=files.begin(); it!=files.end(); )
00482     {
00483         QString mime = KMimeMagic::self()->findFileType(*it)->mimeType();
00484         if (mimeTypes.findIndex(mime) == -1)
00485         {
00486             if ((result=KMessageBox::warningYesNoCancel(NULL,
00487                            i18n("The file format %1 is not directly supported by the current print system. "
00488                                 "KDE can try to convert this file automatically to a supported format. But you can "
00489                             "still try to send the file to the printer without any conversion. Do you want KDE "
00490                             "to try to convert this file to %2?").arg(mime).arg(primaryMimeType),
00491                            QString::null,
00492                            i18n("Convert"),
00493                            i18n("Keep"),
00494                            QString::fromLatin1("kdeprintAutoConvert"))) == KMessageBox::Yes)
00495             {
00496                 // find the filter chain
00497                 QStringList flist = KXmlCommandManager::self()->autoConvert(mime, primaryMimeType);
00498                 if (flist.count() == 0)
00499                 {
00500                     if (KMessageBox::warningYesNo(NULL,
00501                                       i18n("No appropriate filter was found to convert the file "
00502                                            "format %1 into %2. Do you want to print the "
00503                                        "file using its original format?").arg(mime).arg(primaryMimeType),
00504                                       QString::null,
00505                                       i18n("Print"),
00506                                       i18n("Skip")) == KMessageBox::No)
00507                     {
00508                         if (flag)
00509                             QFile::remove(*it);
00510                         it = files.remove(it);
00511                     }
00512                     else
00513                         ++it;
00514                     continue;
00515                 }
00516                 QStringList l(*it);
00517                 switch (doFilterFiles(printer, l, flist, QMap<QString,QString>(), flag))
00518                 {
00519                     case -1:
00520                         return -1;
00521                     case 0:
00522                         break;
00523                     case 1:
00524                         status = 1;
00525                         *it = l[0];
00526                         break;
00527                 }
00528             }
00529             else if (result == KMessageBox::Cancel)
00530             {
00531                 files.clear();
00532                 return 0;
00533             }
00534         }
00535         ++it;
00536     }
00537     return status;
00538 }
00539 
00540 bool KPrinterImpl::setupSpecialCommand(QString& cmd, KPrinter *p, const QStringList&)
00541 {
00542     QString s(p->option("kde-special-command"));
00543     if (s.isEmpty())
00544     {
00545         p->setErrorMessage("Empty command.");
00546         return false;
00547     }
00548 
00549     s = KMFactory::self()->specialManager()->setupCommand(s, p->options());
00550 
00551     QString ps = pageSizeToPageName( p->option( "kde-printsize" ).isEmpty() ? p->pageSize() : ( KPrinter::PageSize )p->option( "kde-printsize" ).toInt() );
00552     s.replace("%psl", ps.lower());
00553     s.replace("%psu", ps);
00554     s.replace("%out", "$out{" + p->outputFileName() + "}"); // Replace as last
00555     cmd = s;
00556     return true;
00557 }
00558 
00559 QString KPrinterImpl::quote(const QString& s)
00560 { return KProcess::quote(s); }
00561 
00562 void KPrinterImpl::saveOptions(const QMap<QString,QString>& opts)
00563 {
00564     m_options = opts;
00565     saveAppOptions();
00566 }
00567 
00568 void KPrinterImpl::loadAppOptions()
00569 {
00570     KConfig *conf = KGlobal::config();
00571     conf->setGroup("KPrinter Settings");
00572     QStringList opts = conf->readListEntry("ApplicationOptions");
00573     for (uint i=0; i<opts.count(); i+=2)
00574         if (opts[i].startsWith("app-"))
00575             m_options[opts[i]] = opts[i+1];
00576 }
00577 
00578 void KPrinterImpl::saveAppOptions()
00579 {
00580     QStringList optlist;
00581     for (QMap<QString,QString>::ConstIterator it=m_options.begin(); it!=m_options.end(); ++it)
00582         if (it.key().startsWith("app-"))
00583             optlist << it.key() << it.data();
00584 
00585     KConfig *conf = KGlobal::config();
00586     conf->setGroup("KPrinter Settings");
00587     conf->writeEntry("ApplicationOptions", optlist);
00588 }
00589 
00590 #include "kprinterimpl.moc"
KDE Logo
This file is part of the documentation for kdeprint Library Version 3.2.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sun May 16 22:05:16 2004 by doxygen 1.2.15 written by Dimitri van Heesch, © 1997-2003