You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdelibs/khtml/kmultipart/kmultipart.cpp

614 lines
22 KiB

/* This file is part of the KDE project
Copyright (C) 2002 David Faure <david@mandrakesoft.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kmultipart.h"
#include <qvbox.h>
#include <kinstance.h>
#include <kmimetype.h>
#include <klocale.h>
#include <kio/job.h>
#include <qfile.h>
#include <ktempfile.h>
#include <kmessagebox.h>
#include <kparts/componentfactory.h>
#include <kparts/genericfactory.h>
#include <khtml_part.h>
#include <unistd.h>
#include <kxmlguifactory.h>
#include <qtimer.h>
typedef KParts::GenericFactory<KMultiPart> KMultiPartFactory; // factory for the part
K_EXPORT_COMPONENT_FACTORY( libkmultipart /*library name*/, KMultiPartFactory )
//#define DEBUG_PARSING
class KLineParser
{
public:
KLineParser() {
m_lineComplete = false;
}
void addChar( char c, bool storeNewline ) {
if ( !storeNewline && c == '\r' )
return;
Q_ASSERT( !m_lineComplete );
if ( storeNewline || c != '\n' ) {
int sz = m_currentLine.size();
m_currentLine.resize( sz+1, QGArray::SpeedOptim );
m_currentLine[sz] = c;
}
if ( c == '\n' )
m_lineComplete = true;
}
bool isLineComplete() const {
return m_lineComplete;
}
QByteArray currentLine() const {
return m_currentLine;
}
void clearLine() {
Q_ASSERT( m_lineComplete );
reset();
}
void reset() {
m_currentLine.resize( 0, QGArray::SpeedOptim );
m_lineComplete = false;
}
private:
QByteArray m_currentLine;
bool m_lineComplete; // true when ending with '\n'
};
/* testcase:
Content-type: multipart/mixed;boundary=ThisRandomString
--ThisRandomString
Content-type: text/plain
Data for the first object.
--ThisRandomString
Content-type: text/plain
Data for the second and last object.
--ThisRandomString--
*/
KMultiPart::KMultiPart( QWidget *parentWidget, const char *widgetName,
QObject *parent, const char *name, const QStringList& )
: KParts::ReadOnlyPart( parent, name )
{
m_filter = 0L;
setInstance( KMultiPartFactory::instance() );
QVBox *box = new QVBox( parentWidget, widgetName );
setWidget( box );
m_extension = new KParts::BrowserExtension( this );
// We probably need to use m_extension to get the urlArgs in openURL...
m_part = 0L;
m_isHTMLPart = false;
m_job = 0L;
m_lineParser = new KLineParser;
m_tempFile = 0L;
m_timer = new QTimer( this );
connect( m_timer, SIGNAL( timeout() ), this, SLOT( slotProgressInfo() ) );
}
KMultiPart::~KMultiPart()
{
// important: delete the nested part before the part or qobject destructor runs.
// we now delete the nested part which deletes the part's widget which makes
// _OUR_ m_widget 0 which in turn avoids our part destructor to delete the
// widget ;-)
// ### additional note: it _can_ be that the part has been deleted before:
// when we're in a html frameset and the view dies first, then it will also
// kill the htmlpart
if ( m_part )
delete static_cast<KParts::ReadOnlyPart *>( m_part );
delete m_job;
delete m_lineParser;
if ( m_tempFile ) {
m_tempFile->setAutoDelete( true );
delete m_tempFile;
}
delete m_filter;
m_filter = 0L;
}
void KMultiPart::startHeader()
{
m_bParsingHeader = true; // we expect a header to come first
m_bGotAnyHeader = false;
m_gzip = false;
// just to be sure for now
delete m_filter;
m_filter = 0L;
}
bool KMultiPart::openURL( const KURL &url )
{
m_url = url;
m_lineParser->reset();
startHeader();
KParts::URLArgs args = m_extension->urlArgs();
//m_mimeType = args.serviceType;
// Hmm, args.reload is set to true when reloading, but this doesn't seem to be enough...
// I get "HOLD: Reusing held slave for <url>", and the old data
m_job = KIO::get( url, args.reload, false );
emit started( 0 /*m_job*/ ); // don't pass the job, it would interfer with our own infoMessage
connect( m_job, SIGNAL( result( KIO::Job * ) ),
this, SLOT( slotJobFinished( KIO::Job * ) ) );
connect( m_job, SIGNAL( data( KIO::Job *, const QByteArray & ) ),
this, SLOT( slotData( KIO::Job *, const QByteArray & ) ) );
m_numberOfFrames = 0;
m_numberOfFramesSkipped = 0;
m_totalNumberOfFrames = 0;
m_qtime.start();
m_timer->start( 1000 ); //1s
return true;
}
// Yes, libkdenetwork's has such a parser already (MultiPart),
// but it works on the complete string, expecting the whole data to be available....
// The version here is asynchronous.
void KMultiPart::slotData( KIO::Job *job, const QByteArray &data )
{
if (m_boundary.isNull())
{
QString tmp = job->queryMetaData("media-boundary");
kdDebug() << "Got Boundary from kio-http '" << tmp << "'" << endl;
if ( !tmp.isEmpty() ) {
if (tmp.startsWith("--"))
m_boundary = tmp.latin1();
else
m_boundary = QCString("--")+tmp.latin1();
m_boundaryLength = m_boundary.length();
}
}
// Append to m_currentLine until eol
for ( uint i = 0; i < data.size() ; ++i )
{
// Store char. Skip if '\n' and currently parsing a header.
m_lineParser->addChar( data[i], !m_bParsingHeader );
if ( m_lineParser->isLineComplete() )
{
QByteArray lineData = m_lineParser->currentLine();
#ifdef DEBUG_PARSING
kdDebug() << "lineData.size()=" << lineData.size() << endl;
#endif
QCString line( lineData.data(), lineData.size()+1 ); // deep copy
// 0-terminate the data, but only for the line-based tests below
// We want to keep the raw data in case it ends up in sendData()
int sz = line.size();
if ( sz > 0 )
line[sz-1] = '\0';
#ifdef DEBUG_PARSING
kdDebug() << "[" << m_bParsingHeader << "] line='" << line << "'" << endl;
#endif
if ( m_bParsingHeader )
{
if ( !line.isEmpty() )
m_bGotAnyHeader = true;
if ( m_boundary.isNull() )
{
if ( !line.isEmpty() ) {
#ifdef DEBUG_PARSING
kdDebug() << "Boundary is " << line << endl;
#endif
m_boundary = line;
m_boundaryLength = m_boundary.length();
}
}
else if ( !qstrnicmp( line.data(), "Content-Encoding:", 17 ) )
{
QString encoding = QString::fromLatin1(line.data()+17).stripWhiteSpace().lower();
if (encoding == "gzip" || encoding == "x-gzip") {
m_gzip = true;
} else {
kdDebug() << "FIXME: unhandled encoding type in KMultiPart: " << encoding << endl;
}
}
// parse Content-Type
else if ( !qstrnicmp( line.data(), "Content-Type:", 13 ) )
{
Q_ASSERT( m_nextMimeType.isNull() );
m_nextMimeType = QString::fromLatin1( line.data() + 14 ).stripWhiteSpace();
int semicolon = m_nextMimeType.find( ';' );
if ( semicolon != -1 )
m_nextMimeType = m_nextMimeType.left( semicolon );
kdDebug() << "m_nextMimeType=" << m_nextMimeType << endl;
}
// Empty line, end of headers (if we had any header line before)
else if ( line.isEmpty() && m_bGotAnyHeader )
{
m_bParsingHeader = false;
#ifdef DEBUG_PARSING
kdDebug() << "end of headers" << endl;
#endif
startOfData();
}
// First header (when we know it from kio_http)
else if ( line == m_boundary )
; // nothing to do
else if ( !line.isEmpty() ) // this happens with e.g. Set-Cookie:
kdDebug() << "Ignoring header " << line << endl;
} else {
if ( !qstrncmp( line, m_boundary, m_boundaryLength ) )
{
#ifdef DEBUG_PARSING
kdDebug() << "boundary found!" << endl;
kdDebug() << "after it is " << line.data() + m_boundaryLength << endl;
#endif
// Was it the very last boundary ?
if ( !qstrncmp( line.data() + m_boundaryLength, "--", 2 ) )
{
#ifdef DEBUG_PARSING
kdDebug() << "Completed!" << endl;
#endif
endOfData();
emit completed();
} else
{
char nextChar = *(line.data() + m_boundaryLength);
#ifdef DEBUG_PARSING
kdDebug() << "KMultiPart::slotData nextChar='" << nextChar << "'" << endl;
#endif
if ( nextChar == '\n' || nextChar == '\r' ) {
endOfData();
startHeader();
}
else {
// otherwise, false hit, it has trailing stuff
sendData( lineData );
}
}
} else {
// send to part
sendData( lineData );
}
}
m_lineParser->clearLine();
}
}
}
void KMultiPart::setPart( const QString& mimeType )
{
KXMLGUIFactory *guiFactory = factory();
if ( guiFactory ) // seems to be 0 when restoring from SM
guiFactory->removeClient( this );
kdDebug() << "KMultiPart::setPart " << mimeType << endl;
delete m_part;
// Try to find an appropriate viewer component
m_part = KParts::ComponentFactory::createPartInstanceFromQuery<KParts::ReadOnlyPart>
( m_mimeType, QString::null, widget(), 0L, this, 0L );
if ( !m_part ) {
// TODO launch external app
KMessageBox::error( widget(), i18n("No handler found for %1!").arg(m_mimeType) );
return;
}
// By making the part a child XMLGUIClient of ours, we get its GUI merged in.
insertChildClient( m_part );
m_part->widget()->show();
connect( m_part, SIGNAL( completed() ),
this, SLOT( slotPartCompleted() ) );
m_isHTMLPart = ( mimeType == "text/html" );
KParts::BrowserExtension* childExtension = KParts::BrowserExtension::childObject( m_part );
if ( childExtension )
{
// Forward signals from the part's browser extension
// this is very related (but not exactly like) KHTMLPart::processObjectRequest
connect( childExtension, SIGNAL( openURLNotify() ),
m_extension, SIGNAL( openURLNotify() ) );
connect( childExtension, SIGNAL( openURLRequestDelayed( const KURL &, const KParts::URLArgs & ) ),
m_extension, SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ) );
connect( childExtension, SIGNAL( createNewWindow( const KURL &, const KParts::URLArgs & ) ),
m_extension, SIGNAL( createNewWindow( const KURL &, const KParts::URLArgs & ) ) );
connect( childExtension, SIGNAL( createNewWindow( const KURL &, const KParts::URLArgs &, const KParts::WindowArgs &, KParts::ReadOnlyPart *& ) ),
m_extension, SIGNAL( createNewWindow( const KURL &, const KParts::URLArgs & , const KParts::WindowArgs &, KParts::ReadOnlyPart *&) ) );
// Keep in sync with khtml_part.cpp
connect( childExtension, SIGNAL( popupMenu( const QPoint &, const KFileItemList & ) ),
m_extension, SIGNAL( popupMenu( const QPoint &, const KFileItemList & ) ) );
connect( childExtension, SIGNAL( popupMenu( KXMLGUIClient *, const QPoint &, const KFileItemList & ) ),
m_extension, SIGNAL( popupMenu( KXMLGUIClient *, const QPoint &, const KFileItemList & ) ) );
connect( childExtension, SIGNAL( popupMenu( KXMLGUIClient *, const QPoint &, const KFileItemList &, const KParts::URLArgs &, KParts::BrowserExtension::PopupFlags ) ),
m_extension, SIGNAL( popupMenu( KXMLGUIClient *, const QPoint &, const KFileItemList &, const KParts::URLArgs &, KParts::BrowserExtension::PopupFlags ) ) );
connect( childExtension, SIGNAL( popupMenu( const QPoint &, const KURL &, const QString &, mode_t ) ),
m_extension, SIGNAL( popupMenu( const QPoint &, const KURL &, const QString &, mode_t ) ) );
connect( childExtension, SIGNAL( popupMenu( KXMLGUIClient *, const QPoint &, const KURL &, const QString &, mode_t ) ),
m_extension, SIGNAL( popupMenu( KXMLGUIClient *, const QPoint &, const KURL &, const QString &, mode_t ) ) );
connect( childExtension, SIGNAL( popupMenu( KXMLGUIClient *, const QPoint &, const KURL &, const KParts::URLArgs &, KParts::BrowserExtension::PopupFlags, mode_t ) ),
m_extension, SIGNAL( popupMenu( KXMLGUIClient *, const QPoint &, const KURL &, const KParts::URLArgs &, KParts::BrowserExtension::PopupFlags, mode_t ) ) );
if ( m_isHTMLPart )
connect( childExtension, SIGNAL( infoMessage( const QString & ) ),
m_extension, SIGNAL( infoMessage( const QString & ) ) );
// For non-HTML we prefer to show our infoMessage ourselves.
childExtension->setBrowserInterface( m_extension->browserInterface() );
connect( childExtension, SIGNAL( enableAction( const char *, bool ) ),
m_extension, SIGNAL( enableAction( const char *, bool ) ) );
connect( childExtension, SIGNAL( setLocationBarURL( const QString& ) ),
m_extension, SIGNAL( setLocationBarURL( const QString& ) ) );
connect( childExtension, SIGNAL( setIconURL( const KURL& ) ),
m_extension, SIGNAL( setIconURL( const KURL& ) ) );
connect( childExtension, SIGNAL( loadingProgress( int ) ),
m_extension, SIGNAL( loadingProgress( int ) ) );
if ( m_isHTMLPart ) // for non-HTML we have our own
connect( childExtension, SIGNAL( speedProgress( int ) ),
m_extension, SIGNAL( speedProgress( int ) ) );
connect( childExtension, SIGNAL( selectionInfo( const KFileItemList& ) ),
m_extension, SIGNAL( selectionInfo( const KFileItemList& ) ) );
connect( childExtension, SIGNAL( selectionInfo( const QString& ) ),
m_extension, SIGNAL( selectionInfo( const QString& ) ) );
connect( childExtension, SIGNAL( selectionInfo( const KURL::List& ) ),
m_extension, SIGNAL( selectionInfo( const KURL::List& ) ) );
connect( childExtension, SIGNAL( mouseOverInfo( const KFileItem* ) ),
m_extension, SIGNAL( mouseOverInfo( const KFileItem* ) ) );
connect( childExtension, SIGNAL( moveTopLevelWidget( int, int ) ),
m_extension, SIGNAL( moveTopLevelWidget( int, int ) ) );
connect( childExtension, SIGNAL( resizeTopLevelWidget( int, int ) ),
m_extension, SIGNAL( resizeTopLevelWidget( int, int ) ) );
}
m_partIsLoading = false;
// Load the part's plugins too.
// ###### This is a hack. The bug is that KHTMLPart doesn't load its plugins
// if className != "Browser/View".
loadPlugins( this, m_part, m_part->instance() );
// Get the part's GUI to appear
if ( guiFactory )
guiFactory->addClient( this );
}
void KMultiPart::startOfData()
{
kdDebug() << "KMultiPart::startOfData" << endl;
Q_ASSERT( !m_nextMimeType.isNull() );
if( m_nextMimeType.isNull() )
return;
if ( m_gzip )
{
m_filter = new HTTPFilterGZip;
connect( m_filter, SIGNAL( output( const QByteArray& ) ), this, SLOT( reallySendData( const QByteArray& ) ) );
}
if ( m_mimeType != m_nextMimeType )
{
// Need to switch parts (or create the initial one)
m_mimeType = m_nextMimeType;
setPart( m_mimeType );
}
Q_ASSERT( m_part );
// Pass URLArgs (e.g. reload)
KParts::BrowserExtension* childExtension = KParts::BrowserExtension::childObject( m_part );
if ( childExtension )
childExtension->setURLArgs( m_extension->urlArgs() );
m_nextMimeType = QString::null;
if ( m_tempFile ) {
m_tempFile->setAutoDelete( true );
delete m_tempFile;
m_tempFile = 0;
}
if ( m_isHTMLPart )
{
KHTMLPart* htmlPart = static_cast<KHTMLPart *>( static_cast<KParts::ReadOnlyPart *>( m_part ) );
htmlPart->begin( url() );
}
else
{
// ###### TODO use a QByteArray and a data: URL instead
m_tempFile = new KTempFile;
}
}
void KMultiPart::sendData( const QByteArray& line )
{
if ( m_filter )
{
m_filter->slotInput( line );
}
else
{
reallySendData( line );
}
}
void KMultiPart::reallySendData( const QByteArray& line )
{
if ( m_isHTMLPart )
{
KHTMLPart* htmlPart = static_cast<KHTMLPart *>( static_cast<KParts::ReadOnlyPart *>( m_part ) );
htmlPart->write( line.data(), line.size() );
}
else if ( m_tempFile )
{
m_tempFile->file()->writeBlock( line.data(), line.size() );
}
}
void KMultiPart::endOfData()
{
Q_ASSERT( m_part );
if ( m_isHTMLPart )
{
KHTMLPart* htmlPart = static_cast<KHTMLPart *>( static_cast<KParts::ReadOnlyPart *>( m_part ) );
htmlPart->end();
} else if ( m_tempFile )
{
m_tempFile->close();
if ( m_partIsLoading )
{
// The part is still loading the last data! Let it proceed then
// Otherwise we'd keep cancelling it, and nothing would ever show up...
kdDebug() << "KMultiPart::endOfData part isn't ready, skipping frame" << endl;
++m_numberOfFramesSkipped;
m_tempFile->setAutoDelete( true );
}
else
{
kdDebug() << "KMultiPart::endOfData opening " << m_tempFile->name() << endl;
KURL url;
url.setPath( m_tempFile->name() );
m_partIsLoading = true;
(void) m_part->openURL( url );
}
delete m_tempFile;
m_tempFile = 0L;
}
}
void KMultiPart::slotPartCompleted()
{
if ( !m_isHTMLPart )
{
Q_ASSERT( m_part );
// Delete temp file used by the part
Q_ASSERT( m_part->url().isLocalFile() );
kdDebug() << "slotPartCompleted deleting " << m_part->url().path() << endl;
(void) unlink( QFile::encodeName( m_part->url().path() ) );
m_partIsLoading = false;
++m_numberOfFrames;
// Do not emit completed from here.
}
}
bool KMultiPart::closeURL()
{
m_timer->stop();
if ( m_part )
return m_part->closeURL();
return true;
}
void KMultiPart::guiActivateEvent( KParts::GUIActivateEvent * )
{
// Not public!
//if ( m_part )
// m_part->guiActivateEvent( e );
}
void KMultiPart::slotJobFinished( KIO::Job *job )
{
if ( job->error() )
{
// TODO use khtml's error:// scheme
job->showErrorDialog();
emit canceled( job->errorString() );
}
else
{
/*if ( m_khtml->view()->contentsY() == 0 )
{
KParts::URLArgs args = m_ext->urlArgs();
m_khtml->view()->setContentsPos( args.xOffset, args.yOffset );
}*/
emit completed();
//QTimer::singleShot( 0, this, SLOT( updateWindowCaption() ) );
}
m_job = 0L;
}
void KMultiPart::slotProgressInfo()
{
int time = m_qtime.elapsed();
if ( !time ) return;
if ( m_totalNumberOfFrames == m_numberOfFrames + m_numberOfFramesSkipped )
return; // No change, don't overwrite statusbar messages if any
//kdDebug() << m_numberOfFrames << " in " << time << " milliseconds" << endl;
QString str( "%1 frames per second, %2 frames skipped per second" );
str = str.arg( 1000.0 * (double)m_numberOfFrames / (double)time );
str = str.arg( 1000.0 * (double)m_numberOfFramesSkipped / (double)time );
m_totalNumberOfFrames = m_numberOfFrames + m_numberOfFramesSkipped;
//kdDebug() << str << endl;
emit m_extension->infoMessage( str );
}
KAboutData* KMultiPart::createAboutData()
{
KAboutData* aboutData = new KAboutData( "kmultipart", I18N_NOOP("KMultiPart"),
"0.1",
I18N_NOOP( "Embeddable component for multipart/mixed" ),
KAboutData::License_GPL,
"(c) 2001, David Faure <david@mandrakesoft.com>");
return aboutData;
}
#if 0
KMultiPartBrowserExtension::KMultiPartBrowserExtension( KMultiPart *parent, const char *name )
: KParts::BrowserExtension( parent, name )
{
m_imgPart = parent;
}
int KMultiPartBrowserExtension::xOffset()
{
return m_imgPart->doc()->view()->contentsX();
}
int KMultiPartBrowserExtension::yOffset()
{
return m_imgPart->doc()->view()->contentsY();
}
void KMultiPartBrowserExtension::print()
{
static_cast<KHTMLPartBrowserExtension *>( m_imgPart->doc()->browserExtension() )->print();
}
void KMultiPartBrowserExtension::reparseConfiguration()
{
static_cast<KHTMLPartBrowserExtension *>( m_imgPart->doc()->browserExtension() )->reparseConfiguration();
m_imgPart->doc()->setAutoloadImages( true );
}
#endif
#include "kmultipart.moc"