diff options
Diffstat (limited to 'kdeprint/kdeprintfax/faxctrl.cpp')
-rw-r--r-- | kdeprint/kdeprintfax/faxctrl.cpp | 669 |
1 files changed, 669 insertions, 0 deletions
diff --git a/kdeprint/kdeprintfax/faxctrl.cpp b/kdeprint/kdeprintfax/faxctrl.cpp new file mode 100644 index 000000000..4cb5f3bc7 --- /dev/null +++ b/kdeprint/kdeprintfax/faxctrl.cpp @@ -0,0 +1,669 @@ +/* + * kdeprintfax - a small fax utility + * Copyright (C) 2001 Michael Goffioul + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "faxctrl.h" +#include "kdeprintfax.h" +#include "defcmds.h" + +#include <ktextedit.h> +#include <qfile.h> +#include <qtextstream.h> +#include <kpushbutton.h> +#include <qlayout.h> +#include <qregexp.h> +#include <kprinter.h> +#include <qsimplerichtext.h> +#include <qpainter.h> +#include <qpaintdevicemetrics.h> +#include <qvaluestack.h> +#include <qstylesheet.h> + +#include <kprocess.h> +#include <kglobal.h> +#include <kconfig.h> +#include <klocale.h> +#include <kdialogbase.h> +#include <kmimetype.h> +#include <kstandarddirs.h> +#include <kapplication.h> +#include <kwin.h> +#include <kemailsettings.h> +#include <kdebug.h> +#include <kstdguiitem.h> +#include <kfiledialog.h> +#include <kmessagebox.h> + +#include <stdlib.h> +#include <stdarg.h> + +#define quote(x) KProcess::quote(x) + +/** \brief Return a string for the page size. + * + * @param size the page size, which is actually of type @c QPrinter::PageSize + * @return a pointer to a static string for the name of the page size. + */ +char const* pageSizeText(int size) +{ + switch(size) + { + case QPrinter::A4: return "a4"; + case QPrinter::Legal: return "legal"; + case QPrinter::Letter: return "letter"; + default: return "letter"; + } +} + +/** \brief Return the default page size. + */ +static QString pageSize() +{ + KConfig *conf = KGlobal::config(); + conf->setGroup("Fax"); + return conf->readEntry("Page", pageSizeText(KGlobal::locale()->pageSize())); +} + +static QString stripNumber( const QString& s ) +{ + KConfig *conf = KGlobal::config(); + conf->setGroup( "Personal" ); + + // removes any non-numeric character, except ('+','*','#') (hope it's supported by faxing tools) + QString strip_s = s; + strip_s.replace( QRegExp( "[^\\d+*#]" ), "" ); + if ( strip_s.find( '+' ) != -1 && conf->readBoolEntry( "ReplaceIntChar", false ) ) + strip_s.replace( "+", conf->readEntry( "ReplaceIntCharVal" ) ); + return strip_s; +} + +static QString tagList( int n, ... ) +{ + QString t; + + va_list ap; + va_start( ap, n ); + for ( int i=0; i<n; i++ ) + { + QString tag = va_arg( ap, const char* ); + tag.append( "(_(\\w|\\{[^\\}]*\\}))?" ); + if ( t.isEmpty() ) + t = tag; + else + t.append( "|" ).append( tag ); + } + + return t; +} + +/** Process a @c %variable that has a boolean value. + * The format is <tt>%name_{iftrue??iffalse}</tt>. Either + * @em iftrue or @em iffalse can be empty (or both, but that would + * be silly). For example, if the fax software uses the + * option @c -l for low resolution, you might use <tt>%res_{??-l}</tt>, + * and if it uses high resolution, use <tt>%res_{-h??}</tt>. As an + * abbreviation, omit the trailing <tt>?</tt>, e.g., <tt>%res_{-h}</tt>. + * + * By the way, when using multiple, adjacent question marks, + * always be careful about inadvertently using trigraphs. + * Always escape the second and subsequent question marks, to be safe. + * I suppose question marks are not the best string to use, + * but I think they work well for the end-user. + * + * @param match the string to process + * @param value the Boolean value to control the processing + * @return the new value of the tag + */ +static QString processTag( const QString& match, bool value) +{ + QString v; + int p = match.find( '_' ); + if ( p != -1 && match[ p+1 ] == '{' ) + { + // Find the ?? that separates the iftrue from the iffalse parts. + int q = match.find( "?\?", p+2 ); + if ( q == -1 ) + { + // No iffalse part + if (value) + v = match.mid( p+2 ); + // else value is false, so leave v empty. + } + else if ( value ) + // Extract only the iftrue part + v = match.mid( p+2, q-p-2 ); + else + // Extract only the iffalse part + v = match.mid( q+2, match.length()-q-3 ); + } + // Else the string is malformed: no _ or no { after the _ + // In either case, there isn't much the program can do, + // so just leave the result string empty. + + return v; +} + +static QString processTag( const QString& match, const QString& value ) +{ + QString v; + int p = match.find( '_' ); + if ( p != -1 ) + { + if ( value.isEmpty() ) + v = ""; + else + { + if ( match[ p+1 ] == '{' ) + { + v = match.mid( p+2, match.length()-p-3 ); + v.replace( "@@", quote( value ) ); + } + else + v = ( "-" + match.mid( p+1 ) + " " + quote( value ) ); + } + } + else + v = quote( value ); + return v; +} + +static bool isTag( const QString& m, const QString& t ) +{ + return ( m == t || m.startsWith( t+"_" ) ); +} + +static QString replaceTags( const QString& s, const QString& tags, KdeprintFax *fax = NULL, const KdeprintFax::FaxItem& item = KdeprintFax::FaxItem() ) +{ + // unquote variables (they will be replaced with quoted values later) + + QValueStack<bool> stack; + KConfig *conf = KGlobal::config(); + + QString cmd = s; + + bool issinglequote=false; + bool isdoublequote=false; + QRegExp re_noquote("(\\$\\(|\\)|\\(|\"|'|\\\\|`|"+tags+")"); + QRegExp re_singlequote("('|"+tags+")"); + QRegExp re_doublequote("(\\$\\(|\"|\\\\|`|"+tags+")"); + for ( int i = re_noquote.search(cmd); + i != -1; + i = (issinglequote?re_singlequote.search(cmd,i) + :isdoublequote?re_doublequote.search(cmd,i) + :re_noquote.search(cmd,i)) + ) + { + if (cmd[i]=='(') // (...) + { + // assert(isdoublequote == false) + stack.push(isdoublequote); + i++; + } + else if (cmd[i]=='$') // $(...) + { + stack.push(isdoublequote); + isdoublequote = false; + i+=2; + } + else if (cmd[i]==')') // $(...) or (...) + { + if (!stack.isEmpty()) + isdoublequote = stack.pop(); + else + qWarning("Parse error."); + i++; + } + else if (cmd[i]=='\'') + { + issinglequote=!issinglequote; + i++; + } + else if (cmd[i]=='"') + { + isdoublequote=!isdoublequote; + i++; + } + else if (cmd[i]=='\\') + i+=2; + else if (cmd[i]=='`') + { + // Replace all `...` with safer $(...) + cmd.replace (i, 1, "$("); + QRegExp re_backticks("(`|\\\\`|\\\\\\\\|\\\\\\$)"); + for ( int i2=re_backticks.search(cmd,i+2); + i2!=-1; + i2=re_backticks.search(cmd,i2) + ) + { + if (cmd[i2] == '`') + { + cmd.replace (i2, 1, ")"); + i2=cmd.length(); // leave loop + } + else + { // remove backslash and ignore following character + cmd.remove (i2, 1); + i2++; + } + } + // Leave i unchanged! We need to process "$(" + } + else + { + QString match, v; + + // get match + if (issinglequote) + match=re_singlequote.cap(); + else if (isdoublequote) + match=re_doublequote.cap(); + else + match=re_noquote.cap(); + + // substitute %variables + // settings + if ( isTag( match, "%dev" ) ) + { + conf->setGroup("Fax"); + v = processTag( match, conf->readEntry("Device", "modem") ); + + } + else if (isTag( match, "%server" )) + { + conf->setGroup( "Fax" ); + v = conf->readEntry("Server"); + if (v.isEmpty()) + v = getenv("FAXSERVER"); + if (v.isEmpty()) + v = QString::fromLatin1("localhost"); + v = processTag( match, v ); + } + else if (isTag( match, "%page" )) + { + conf->setGroup( "Fax" ); + v = processTag( match, pageSize() ); + } + else if (isTag( match, "%res" )) + { + conf->setGroup( "Fax" ); + v = processTag(match, conf->readEntry("Resolution", "High") == "High"); + } + else if (isTag( match, "%user" )) + { + conf->setGroup("Personal"); + v = processTag(match, conf->readEntry("Name", getenv("USER"))); + } + else if (isTag( match, "%from" )) + { + conf->setGroup( "Personal" ); + v = processTag(match, conf->readEntry("Number")); + } + else if (isTag( match, "%email" )) + { + KEMailSettings e; + v = processTag(match, e.getSetting(KEMailSettings::EmailAddress)); + } + // arguments + else if (isTag( match, "%number" )) + v = processTag( match, stripNumber( item.number) ); + else if (isTag( match, "%rawnumber" )) + v = processTag( match, item.number ); + else if (isTag( match, "%name" )) + v = processTag(match, item.name); + else if (isTag( match, "%comment" )) + v = processTag(match, fax->comment()); + else if (isTag( match, "%enterprise" )) + v = processTag(match, item.enterprise); + else if ( isTag( match, "%time" ) ) + v = processTag( match, fax->time() ); + else if ( isTag( match, "%subject" ) ) + v = processTag( match, fax->subject() ); + else if (isTag( match, "%cover" )) + v = processTag(match, fax->cover()); + + // %variable inside of a quote? + if (isdoublequote) + v='"'+v+'"'; + else if (issinglequote) + v="'"+v+"'"; + + cmd.replace (i, match.length(), v); + i+=v.length(); + } + } + + return cmd; +} + +FaxCtrl::FaxCtrl(QWidget *parent, const char *name) +: QObject(parent, name) +{ + m_process = new KProcess(); + m_process->setUseShell(true); + connect(m_process, SIGNAL(receivedStdout(KProcess*,char*,int)), SLOT(slotReceivedStdout(KProcess*,char*,int))); + connect(m_process, SIGNAL(receivedStderr(KProcess*,char*,int)), SLOT(slotReceivedStdout(KProcess*,char*,int))); + connect(m_process, SIGNAL(processExited(KProcess*)), SLOT(slotProcessExited(KProcess*))); + connect(this, SIGNAL(faxSent(bool)), SLOT(cleanTempFiles())); + m_logview = 0; +} + +FaxCtrl::~FaxCtrl() +{ + slotCloseLog(); + delete m_process; +} + +bool FaxCtrl::send(KdeprintFax *f) +{ + m_command = faxCommand(); + if (m_command.isEmpty()) + return false; + + // replace tags common to all fax "operations" + m_command = replaceTags( m_command, tagList( 11, "%dev", "%server", "%page", "%res", "%user", "%from", "%email", "%comment", "%time", "%subject", "%cover" ), f ); + + m_log = QString::null; + m_filteredfiles.clear(); + cleanTempFiles(); + m_files = f->files(); + m_faxlist = f->faxList(); + + addLogTitle( i18n( "Converting input files to PostScript" ) ); + filter(); + + return true; +} + +void FaxCtrl::slotReceivedStdout(KProcess*, char *buffer, int len) +{ + QCString str(buffer, len); + kdDebug() << "Received stdout: " << str << endl; + addLog(QString(str)); +} + +void FaxCtrl::slotProcessExited(KProcess*) +{ + // we exited a process: if there's still entries in m_files, this was a filter + // process, else this was the fax process + bool ok = (m_process->normalExit() && ((m_process->exitStatus() & (m_files.count() > 0 ? 0x1 : 0xFFFFFFFF)) == 0)); + if ( ok ) + { + if ( m_files.count() > 0 ) + { + // remove first element + m_files.remove(m_files.begin()); + if (m_files.count() > 0) + filter(); + else + sendFax(); + } + else if ( !m_faxlist.isEmpty() ) + sendFax(); + else + faxSent( true ); + } + else + { + emit faxSent(false); + } +} + +QString FaxCtrl::faxCommand() +{ + KConfig *conf = KGlobal::config(); + conf->setGroup("System"); + QString sys = conf->readPathEntry("System", "efax"); + QString cmd; + if (sys == "hylafax") + cmd = conf->readPathEntry("HylaFax", hylafax_default_cmd); + else if (sys == "mgetty") + cmd = conf->readPathEntry("Mgetty", mgetty_default_cmd); + else if ( sys == "other" ) + cmd = conf->readPathEntry( "Other", QString::null ); + else + cmd = conf->readPathEntry("EFax", efax_default_cmd); + if (cmd.startsWith("%exe_")) + cmd = defaultCommand(cmd); + return cmd; +} + +void FaxCtrl::sendFax() +{ + if ( m_command.find( "%files" ) != -1 ) + { + // replace %files tag + QString filestr; + for (QStringList::ConstIterator it=m_filteredfiles.begin(); it!=m_filteredfiles.end(); ++it) + filestr += (quote(*it)+" "); + m_command.replace("%files", filestr); + } + + if ( !m_faxlist.isEmpty() ) + { + KdeprintFax::FaxItem item = m_faxlist.first(); + m_faxlist.remove(m_faxlist.begin()); + + addLogTitle( i18n( "Sending fax to %1 (%2)" ).arg( item.number ).arg( item.name ) ); + + QString cmd = replaceTags( m_command, tagList( 4, "%number", "%name", "%enterprise", "%rawnumber" ), NULL, item ); + m_process->clearArguments(); + *m_process << cmd; + addLog(i18n("Sending to fax using: %1").arg(cmd)); + if (!m_process->start(KProcess::NotifyOnExit, KProcess::AllOutput)) + emit faxSent(false); + else + emit message(i18n("Sending fax to %1...").arg( item.number )); + } +} + +void FaxCtrl::filter() +{ + if (m_files.count() > 0) + { + QString mimeType = KMimeType::findByURL(KURL(m_files[0]), 0, true)->name(); + if (mimeType == "application/postscript" || mimeType == "image/tiff") + { + emit message(i18n("Skipping %1...").arg(m_files[0])); + m_filteredfiles.prepend(m_files[0]); + m_files.remove(m_files.begin()); + filter(); + } + else + { + QString tmp = locateLocal("tmp","kdeprintfax_") + kapp->randomString(8); + m_filteredfiles.prepend(tmp); + m_tempfiles.append(tmp); + m_process->clearArguments(); + *m_process << locate("data","kdeprintfax/anytops") << "-m" << KProcess::quote(locate("data","kdeprintfax/faxfilters")) + << QString::fromLatin1("--mime=%1").arg(mimeType) + << "-p" << pageSize() + << KProcess::quote(m_files[0]) << KProcess::quote(tmp); + if (!m_process->start(KProcess::NotifyOnExit, KProcess::AllOutput)) + emit faxSent(false); + else + emit message(i18n("Filtering %1...").arg(m_files[0])); + } + } + else + { + sendFax(); + } +} + +bool FaxCtrl::abort() +{ + if (m_process->isRunning()) + return m_process->kill(); + else + return false; +} + +void FaxCtrl::viewLog(QWidget *) +{ + if (!m_logview) + { + QWidget *topView = new QWidget(0, "LogView", WType_TopLevel|WStyle_DialogBorder|WDestructiveClose); + m_logview = new KTextEdit(topView); + m_logview->setTextFormat( Qt::LogText ); + m_logview->setWordWrap( QTextEdit::WidgetWidth ); + m_logview->setPaper( Qt::white ); + //m_logview->setReadOnly(true); + //m_logview->setWordWrap(QTextEdit::NoWrap); + QPushButton *m_clear = new KPushButton(KStdGuiItem::clear(), topView); + QPushButton *m_close = new KPushButton(KStdGuiItem::close(), topView); + QPushButton *m_print = new KPushButton( KStdGuiItem::print(), topView ); + QPushButton *m_save = new KPushButton( KStdGuiItem::saveAs(), topView ); + m_close->setDefault(true); + connect(m_clear, SIGNAL(clicked()), SLOT(slotClearLog())); + connect(m_close, SIGNAL(clicked()), SLOT(slotCloseLog())); + connect(m_logview, SIGNAL(destroyed()), SLOT(slotCloseLog())); + connect( m_print, SIGNAL( clicked() ), SLOT( slotPrintLog() ) ); + connect( m_save, SIGNAL( clicked() ), SLOT( slotSaveLog() ) ); + + QVBoxLayout *l0 = new QVBoxLayout(topView, 10, 10); + l0->addWidget(m_logview); + QHBoxLayout *l1 = new QHBoxLayout(0, 0, 10); + l0->addLayout(l1); + l1->addStretch(1); + l1->addWidget( m_save ); + l1->addWidget( m_print ); + l1->addWidget(m_clear); + l1->addWidget(m_close); + + m_logview->setText(m_log); + + topView->resize(450, 350); + topView->show(); + } + else + { + KWin::activateWindow(m_logview->parentWidget()->winId()); + } +} + +void FaxCtrl::addLogTitle( const QString& s ) +{ + QString t( s ); + t.prepend( '\n' ).append( '\n' ); + addLog( t, true ); +} + +void FaxCtrl::addLog(const QString& s, bool isTitle) +{ + QString t = QStyleSheet::escape(s); + if ( isTitle ) + t.prepend( "<font color=red><b>" ).append( "</b></font>" ); + m_log.append( t + '\n' ); + if (m_logview) + m_logview->append(t); +} + +QString FaxCtrl::faxSystem() +{ + KConfig *conf = KGlobal::config(); + conf->setGroup("System"); + QString s = conf->readEntry("System", "efax"); + s[0] = s[0].upper(); + return s; +} + +void FaxCtrl::cleanTempFiles() +{ + for (QStringList::ConstIterator it=m_tempfiles.begin(); it!=m_tempfiles.end(); ++it) + QFile::remove(*it); + m_tempfiles.clear(); +} + +void FaxCtrl::slotClearLog() +{ + m_log = QString::null; + if (m_logview) + m_logview->clear(); +} + +void FaxCtrl::slotCloseLog() +{ + const QObject *obj = sender(); + if (m_logview) + { + QTextEdit *view = m_logview; + m_logview = 0; + if (obj && obj->inherits("QPushButton")) + delete view->parentWidget(); +kdDebug() << "slotClose()" << endl; + } +} + +void FaxCtrl::slotPrintLog() +{ + if ( m_logview ) + { + KPrinter printer; + printer.setDocName( i18n( "Fax log" ) ); + printer.setDocFileName( "faxlog" ); + if ( printer.setup( m_logview->topLevelWidget(), i18n( "Fax Log" ) ) ) + { + QPainter painter( &printer ); + QPaintDeviceMetrics metric( &printer ); + QRect body( 0, 0, metric.width(), metric.height() ), view( body ); + //QString txt = m_logview->text(); + QString txt = m_log; + + txt.replace( '\n', "<br>" ); + txt.prepend( "<h2>" + i18n( "KDEPrint Fax Tool Log" ) + "</h2>" ); + + kdDebug() << "Log: " << txt << endl; + QSimpleRichText richText( txt, m_logview->font() ); + + richText.setWidth( &painter, body.width() ); + do + { + richText.draw( &painter, body.left(), body.top(), view, m_logview->colorGroup() ); + view.moveBy( 0, body.height() ); + painter.translate( 0, -body.height() ); + if ( view.top() >= richText.height() ) + break; + printer.newPage(); + } while ( true ); + } + } +} + +void FaxCtrl::slotSaveLog() +{ + if ( m_logview ) + { + QString filename = KFileDialog::getSaveFileName( QString::null, QString::null, m_logview ); + if ( !filename.isEmpty() ) + { + QFile f( filename ); + if ( f.open( IO_WriteOnly ) ) + { + QTextStream t( &f ); + t << i18n( "KDEPrint Fax Tool Log" ) << endl; + t << m_logview->text() << endl; + f.close(); + } + else + KMessageBox::error( m_logview, i18n( "Cannot open file for writing." ) ); + } + } +} + +#include "faxctrl.moc" |