diff --git a/article_maker.cc b/article_maker.cc
index b192d368..a2afa205 100644
--- a/article_maker.cc
+++ b/article_maker.cc
@@ -486,7 +486,7 @@ void ArticleRequest::bodyFinished()
head += string( "
" +
+ + "/dicticon.png\">" +
Html::escape( tr( "From " ).toUtf8().data() ) + "" +
Html::escape( activeDict->getName().c_str() )
+ "
";
diff --git a/articleview.cc b/articleview.cc
index e5f16f17..ca09d254 100644
--- a/articleview.cc
+++ b/articleview.cc
@@ -22,6 +22,8 @@
#include
#include
+#include
+
#ifdef Q_OS_WIN32
#include
@@ -952,10 +954,14 @@ void ArticleView::openLink( QUrl const & url, QUrl const & ref,
}
}
-void ArticleView::saveResource( const QUrl & url, QUrl const & ref )
+vector< ResourceToSaveHandler * > ArticleView::saveResource( const QUrl & url, const QString & fileName )
{
- resourceToSaveDownloadRequests.clear();
- resourceToSaveUrl = url;
+ return saveResource( url, ui.definition->url(), fileName );
+}
+
+vector< ResourceToSaveHandler * > ArticleView::saveResource( const QUrl & url, const QUrl & ref, const QString & fileName )
+{
+ vector< ResourceToSaveHandler * > handlers;
sptr< Dictionary::DataRequest > req;
if( url.scheme() == "bres" || url.scheme() == "gico" || url.scheme() == "gdau")
@@ -989,25 +995,8 @@ void ArticleView::saveResource( const QUrl & url, QUrl const & ref )
req = (*activeDicts)[ x ]->getResource(
url.path().mid( 1 ).toUtf8().data() );
- if ( req->isFinished() && req->dataSize() >= 0 )
- {
- // A request was instantly finished with success.
- // If we've managed to spawn some lingering requests already,
- // erase them.
- resourceToSaveDownloadRequests.clear();
-
- // Handle the result
- resourceToSaveDownloadRequests.push_back( req );
- resourceToSaveDownloadFinished();
- return;
- }
- else
- if ( !req->isFinished() )
- {
- resourceToSaveDownloadRequests.push_back( req );
- connect( req.get(), SIGNAL( finished() ),
- this, SLOT( resourceToSaveDownloadFinished() ), Qt::QueuedConnection );
- }
+ ResourceToSaveHandler * handler = new ResourceToSaveHandler( this, req, fileName );
+ handlers.push_back( handler );
}
}
}
@@ -1016,154 +1005,27 @@ void ArticleView::saveResource( const QUrl & url, QUrl const & ref )
// Normal resource download
QString contentType;
req = articleNetMgr.getResource( url, contentType );
+
+ ResourceToSaveHandler * handler = new ResourceToSaveHandler( this, req, fileName );
+ handlers.push_back( handler );
}
}
else
+ {
req = new Dictionary::WebMultimediaDownload( url, articleNetMgr );
- if( url.host().compare( "search" ) != 0 )
- {
- if ( !req.get() )
- {
- // Request failed, fail
- }
- else
- if ( req->isFinished() && req->dataSize() >= 0 )
- {
- // Have data ready, handle it
- resourceToSaveDownloadRequests.push_back( req );
- resourceToSaveDownloadFinished();
- return;
- }
- else
- if ( !req->isFinished() )
- {
- // Queue to be handled when done
-
- resourceToSaveDownloadRequests.push_back( req );
- connect( req.get(), SIGNAL( finished() ),
- this, SLOT( resourceToSaveDownloadFinished() ), Qt::QueuedConnection );
- }
+ ResourceToSaveHandler * handler = new ResourceToSaveHandler( this, req, fileName );
+ handlers.push_back( handler );
}
- if ( resourceToSaveDownloadRequests.empty() ) // No requests were queued
+ if ( handlers.empty() ) // No requests were queued
{
emit statusBarMessage(
tr( "ERROR: %1" ).arg( tr( "The referenced resource doesn't exist." ) ),
10000, QPixmap( ":/icons/error.png" ) );
- return;
- }
- else
- resourceToSaveDownloadFinished(); // Check any requests finished already
-}
-
-void ArticleView::resourceToSaveDownloadFinished()
-{
- if ( resourceToSaveDownloadRequests.empty() )
- return; // Stray signal
-
- QByteArray resourceData;
-
- // Find any finished resources
-
- for( list< sptr< Dictionary::DataRequest > >::iterator i =
- resourceToSaveDownloadRequests.begin(); i != resourceToSaveDownloadRequests.end(); )
- {
- if ( (*i)->isFinished() )
- {
- if ( (*i)->dataSize() >= 0 )
- {
- // Ok, got one finished, all others are irrelevant now
-
- vector< char > const & data = (*i)->getFullData();
- resourceData = QByteArray( data.data(), data.size() );
- break;
- }
- else
- {
- // This one had no data. Erase it.
- resourceToSaveDownloadRequests.erase( i++ );
- }
- }
- else // Unfinished, try the next one.
- i++;
}
- if( !resourceData.isEmpty() )
- {
- // Resource found, clear all requests
- resourceToSaveDownloadRequests.clear();
-
- QString fileName;
- QString savePath;
- if( cfg.resourceSavePath.isEmpty() )
- savePath = QDir::homePath();
- else
- {
- savePath = QDir::fromNativeSeparators( cfg.resourceSavePath );
- if( !QDir( savePath ).exists() )
- savePath = QDir::homePath();
- }
-
- QString name = resourceToSaveUrl.path().section( '/', -1 );
-
- if ( resourceToSaveUrl.scheme() == "gdau" ||
- Dictionary::WebMultimediaDownload::isAudioUrl( resourceToSaveUrl ) )
- {
- // Audio data
- if( name.indexOf( '.' ) < 0 )
- name += ".wav";
-
- fileName = savePath + "/" + name;
- fileName = QFileDialog::getSaveFileName( parentWidget(), tr( "Save sound" ),
- fileName,
- tr( "Sound files (*.wav *.ogg *.mp3 *.mp4 *.aac *.flac *.mid *.wv *.ape);;All files (*.*)" ) );
- }
- else
- {
- // Image data
-
- // Check for babylon image name
- if( name[ 0 ] == '\x1E' )
- name.remove( 0, 1 );
- if( name[ name.length() - 1 ] == '\x1F' )
- name.chop( 1 );
-
- fileName = savePath + "/" + name;
- fileName = QFileDialog::getSaveFileName( parentWidget(), tr( "Save image" ),
- fileName,
- tr( "Image files (*.bmp *.jpg *.png *.tif);;All files (*.*)" ) );
- }
-
- // Write data to file
-
- if( !fileName.isEmpty() )
- {
- QFileInfo fileInfo( fileName );
- emit storeResourceSavePath( QDir::toNativeSeparators( fileInfo.absoluteDir().absolutePath() ) );
- QFile file( fileName );
- if ( file.open( QFile::WriteOnly ) )
- {
- file.write( resourceData.data(), resourceData.size() );
- file.close();
- }
- if( file.error() )
- {
- emit statusBarMessage(
- tr( "ERROR: %1" ).arg( tr( "Resource saving error: " ) + file.errorString() ),
- 10000, QPixmap( ":/icons/error.png" ) );
- }
- }
-
- return;
- }
-
- if ( resourceToSaveDownloadRequests.empty() )
- {
- emit statusBarMessage(
- tr( "ERROR: %1" ).arg( tr( "The referenced resource failed to download." ) ),
- 10000, QPixmap( ":/icons/error.png" ) );
- }
+ return handlers;
}
void ArticleView::updateMutedContents()
@@ -1499,11 +1361,57 @@ void ArticleView::contextMenuRequested( QPoint const & pos )
emit showDefinitionInNewTab( selectedText, groupComboBox->getCurrentGroup(),
QString(), Contexts() );
else
- if( result == saveImageAction )
- saveResource( imageUrl, ui.definition->url() );
- else
- if( result == saveSoundAction )
- saveResource( targetUrl, ui.definition->url() );
+ if( result == saveImageAction || result == saveSoundAction )
+ {
+ QUrl url = ( result == saveImageAction ) ? imageUrl : targetUrl;
+ QString savePath;
+ QString fileName;
+
+ if ( cfg.resourceSavePath.isEmpty() )
+ savePath = QDir::homePath();
+ else
+ {
+ savePath = QDir::fromNativeSeparators( cfg.resourceSavePath );
+ if ( !QDir( savePath ).exists() )
+ savePath = QDir::homePath();
+ }
+
+ QString name = url.path().section( '/', -1 );
+
+ if ( result == saveSoundAction )
+ {
+ // Audio data
+ if ( name.indexOf( '.' ) < 0 )
+ name += ".wav";
+
+ fileName = savePath + "/" + name;
+ fileName = QFileDialog::getSaveFileName( parentWidget(), tr( "Save sound" ),
+ fileName,
+ tr( "Sound files (*.wav *.ogg *.mp3 *.mp4 *.aac *.flac *.mid *.wv *.ape);;All files (*.*)" ) );
+ }
+ else
+ {
+ // Image data
+
+ // Check for babylon image name
+ if ( name[ 0 ] == '\x1E' )
+ name.remove( 0, 1 );
+ if ( name.length() && name[ name.length() - 1 ] == '\x1F' )
+ name.chop( 1 );
+
+ fileName = savePath + "/" + name;
+ fileName = QFileDialog::getSaveFileName( parentWidget(), tr( "Save image" ),
+ fileName,
+ tr( "Image files (*.bmp *.jpg *.png *.tif);;All files (*.*)" ) );
+ }
+
+ if ( !fileName.isEmpty() )
+ {
+ QFileInfo fileInfo( fileName );
+ emit storeResourceSavePath( QDir::toNativeSeparators( fileInfo.absoluteDir().absolutePath() ) );
+ saveResource( url, ui.definition->url(), fileName );
+ }
+ }
else
{
if ( !popupView && result == maxDictionaryRefsAction )
@@ -2147,3 +2055,68 @@ QString ArticleView::wordAtPoint( int x, int y )
}
#endif
+
+ResourceToSaveHandler::ResourceToSaveHandler(
+ ArticleView * view, sptr< Dictionary::DataRequest > req,
+ QString const & fileName ) :
+ QObject( view ),
+ req( req ),
+ fileName( fileName )
+{
+ connect( this, SIGNAL( statusBarMessage( QString, int, QPixmap ) ),
+ view, SIGNAL( statusBarMessage( QString, int, QPixmap ) ) );
+
+ // If DataRequest finsihed immediately, call our handler directly
+ if ( req.get()->isFinished() )
+ {
+ QMetaObject::invokeMethod( this, "downloadFinished", Qt::QueuedConnection );
+ }
+ else
+ {
+ connect( req.get(), SIGNAL( finished() ), this, SLOT( downloadFinished() ) );
+ }
+}
+
+void ResourceToSaveHandler::downloadFinished()
+{
+ assert( req && req.get()->isFinished() );
+
+ QByteArray resourceData;
+
+ if ( req.get()->dataSize() >= 0 )
+ {
+ vector< char > const & data = req.get()->getFullData();
+ resourceData = QByteArray( data.data(), data.size() );
+ }
+
+ // Write data to file
+
+ if ( !resourceData.isEmpty() && !fileName.isEmpty() )
+ {
+ QFileInfo fileInfo( fileName );
+ QDir().mkpath( fileInfo.absoluteDir().absolutePath() );
+
+ QFile file( fileName );
+ if ( file.open( QFile::WriteOnly ) )
+ {
+ file.write( resourceData.data(), resourceData.size() );
+ file.close();
+ }
+
+ if ( file.error() )
+ {
+ emit statusBarMessage(
+ tr( "ERROR: %1" ).arg( tr( "Resource saving error: " ) + file.errorString() ),
+ 10000, QPixmap( ":/icons/error.png" ) );
+ }
+ }
+ else
+ {
+ emit statusBarMessage(
+ tr( "ERROR: %1" ).arg( tr( "The referenced resource failed to download." ) ),
+ 10000, QPixmap( ":/icons/error.png" ) );
+ }
+
+ emit done();
+ deleteLater();
+}
diff --git a/articleview.hh b/articleview.hh
index 7e0ab67a..3fcc3653 100644
--- a/articleview.hh
+++ b/articleview.hh
@@ -13,6 +13,9 @@
#include "groupcombobox.hh"
#include "ui_articleview.h"
+
+class ResourceToSaveHandler;
+
/// A widget with the web view tailored to view and handle articles -- it
/// uses the appropriate netmgr, handles link clicks, rmb clicks etc
class ArticleView: public QFrame
@@ -42,9 +45,6 @@ class ArticleView: public QFrame
/// Url of the resourceDownloadRequests
QUrl resourceDownloadUrl;
- std::list< sptr< Dictionary::DataRequest > > resourceToSaveDownloadRequests;
- QUrl resourceToSaveUrl;
-
/// For resources opened via desktop services
QString desktopOpenedTempFile;
@@ -162,6 +162,9 @@ public:
/// Returns the dictionary id of the currently active article in the view.
QString getActiveArticleId();
+ std::vector< ResourceToSaveHandler * > saveResource( const QUrl & url, const QString & fileName );
+ std::vector< ResourceToSaveHandler * > saveResource( const QUrl & url, const QUrl & ref, const QString & fileName );
+
signals:
void iconChanged( ArticleView *, QIcon const & icon );
@@ -238,7 +241,6 @@ private slots:
void contextMenuRequested( QPoint const & );
void resourceDownloadFinished();
- void resourceToSaveDownloadFinished();
/// We handle pasting by attempting to define the word in clipboard.
void pasteTriggered();
@@ -315,8 +317,6 @@ private:
/// for the given group. If there are none, returns empty string.
QString getMutedForGroup( unsigned group );
- void saveResource( QUrl const &, QUrl const & );
-
protected:
// We need this to hide the search bar when we're showed
@@ -336,4 +336,24 @@ public:
};
+class ResourceToSaveHandler: public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit ResourceToSaveHandler( ArticleView * view, sptr< Dictionary::DataRequest > req,
+ QString const & fileName );
+
+signals:
+ void done();
+ void statusBarMessage( QString const & message, int timeout = 0, QPixmap const & pixmap = QPixmap() );
+
+private slots:
+ void downloadFinished();
+
+private:
+ sptr< Dictionary::DataRequest > req;
+ QString fileName;
+};
+
#endif
diff --git a/mainwindow.cc b/mainwindow.cc
index 347df6e1..78a62fe3 100644
--- a/mainwindow.cc
+++ b/mainwindow.cc
@@ -24,6 +24,7 @@
#include "fsencoding.hh"
#include
#include "historypanewidget.hh"
+#include
#ifdef Q_OS_MAC
#include "lionsupport.h"
@@ -2879,6 +2880,39 @@ void MainWindow::printPreviewPaintRequested( QPrinter * printer )
view->print( printer );
}
+static void filterAndCollectResources( QString & html, QRegExp & rx, const QString & sep,
+ const QString & folder, set< QByteArray > & resourceIncluded,
+ vector< pair< QUrl, QString > > & downloadResources )
+{
+ int pos = 0;
+
+ while ( ( pos = rx.indexIn( html, pos ) ) != -1 )
+ {
+ QUrl url( rx.cap( 1 ) );
+ QString host = url.host();
+ QString resourcePath = QString::fromLatin1( QUrl::toPercentEncoding( url.path(), "/" ) );
+
+ if ( !host.startsWith( '/' ) )
+ host.insert( 0, '/' );
+ if ( !resourcePath.startsWith( '/' ) )
+ resourcePath.insert( 0, '/' );
+
+ QCryptographicHash hash( QCryptographicHash::Md5 );
+ hash.addData( rx.cap().toUtf8() );
+
+ if ( resourceIncluded.insert( hash.result() ).second )
+ {
+ // Gather resouce information (url, filename) to be download later
+ downloadResources.push_back( pair( url, folder + host + resourcePath ) );
+ }
+
+ // Modify original url, set to the native one
+ QString newUrl = sep + QDir( folder ).dirName() + host + resourcePath + sep;
+ html.replace( pos, rx.cap().length(), newUrl );
+ pos += newUrl.length();
+ }
+}
+
void MainWindow::on_saveArticle_triggered()
{
ArticleView *view = getCurrentArticleView();
@@ -2895,10 +2929,19 @@ void MainWindow::on_saveArticle_triggered()
savePath = QDir::homePath();
}
+ QFileDialog::Options options = QFileDialog::HideNameFilterDetails;
+ QString selectedFilter;
+ QStringList filters;
+ filters.push_back( tr( "Article, Complete (*.html)" ) );
+ filters.push_back( tr( "Article, HTML Only (*.html)" ) );
+
fileName = savePath + "/" + fileName;
fileName = QFileDialog::getSaveFileName( this, tr( "Save Article As" ),
- fileName,
- tr( "Html files (*.html *.htm)" ) );
+ fileName,
+ filters.join( ";;" ),
+ &selectedFilter, options );
+
+ bool complete = ( selectedFilter == filters[ 0 ] );
if ( !fileName.isEmpty() )
{
@@ -2912,8 +2955,51 @@ void MainWindow::on_saveArticle_triggered()
}
else
{
- file.write( view->toHtml().toUtf8() );
- cfg.articleSavePath = QDir::toNativeSeparators( QFileInfo( fileName ).absoluteDir().absolutePath() );
+ QString html = view->toHtml();
+ QFileInfo fi( fileName );
+ cfg.articleSavePath = QDir::toNativeSeparators( fi.absoluteDir().absolutePath() );
+
+ if ( complete )
+ {
+ QString folder = fi.absoluteDir().absolutePath() + "/" + fi.baseName() + "_files";
+ QRegExp rx1( "\"((?:bres|gico|gdau|qrcx)://[^\"]+)\"" );
+ QRegExp rx2( "'((?:bres|gico|gdau|qrcx)://[^']+)'" );
+ set< QByteArray > resourceIncluded;
+ vector< pair< QUrl, QString > > downloadResources;
+
+ filterAndCollectResources( html, rx1, "\"", folder, resourceIncluded, downloadResources );
+ filterAndCollectResources( html, rx2, "'", folder, resourceIncluded, downloadResources );
+
+ ArticleSaveProgressDialog * progressDialog = new ArticleSaveProgressDialog( this );
+ // reserve '1' for saving main html file
+ int maxVal = 1;
+
+ // Pull and save resources to files
+ for ( vector< pair< QUrl, QString > >::const_iterator i = downloadResources.begin();
+ i != downloadResources.end(); i++ )
+ {
+ vector< ResourceToSaveHandler * > handlerss = view->saveResource( i->first, i->second );
+ maxVal += handlerss.size();
+
+ for ( vector< ResourceToSaveHandler * >::iterator j = handlerss.begin();
+ j != handlerss.end(); j++ )
+ {
+ connect( *j, SIGNAL( done() ), progressDialog, SLOT( perform() ) );
+ }
+ }
+
+ progressDialog->setLabelText( tr("Saving article...") );
+ progressDialog->setRange( 0, maxVal );
+ progressDialog->setValue( 0 );
+ progressDialog->show();
+
+ file.write( html.toUtf8() );
+ progressDialog->setValue( 1 );
+ }
+ else
+ {
+ file.write( html.toUtf8() );
+ }
}
}
}
diff --git a/mainwindow.hh b/mainwindow.hh
index ec785723..9f743aad 100644
--- a/mainwindow.hh
+++ b/mainwindow.hh
@@ -9,6 +9,7 @@
#include
#include
#include
+#include
#include "ui_mainwindow.h"
#include "folding.hh"
#include "config.hh"
@@ -410,4 +411,29 @@ private slots:
#endif
};
+class ArticleSaveProgressDialog : public QProgressDialog
+{
+Q_OBJECT
+
+public:
+ explicit ArticleSaveProgressDialog( QWidget * parent = 0, Qt::WindowFlags f = 0 ):
+ QProgressDialog( parent, f )
+ {
+ setAutoReset( false );
+ setAutoClose( false );
+ }
+
+public slots:
+ void perform()
+ {
+ int progress = value() + 1;
+ if ( progress == maximum() )
+ {
+ emit close();
+ deleteLater();
+ }
+ setValue( progress );
+ }
+};
+
#endif