Save images and sounds via context menu (issue #223)

This commit is contained in:
Abs62 2013-02-22 16:44:23 +04:00
parent 4e799b9ed2
commit 0ac060576d
6 changed files with 273 additions and 1 deletions

View file

@ -11,6 +11,7 @@
#include <QWebHistory>
#include <QClipboard>
#include <QKeyEvent>
#include <QFileDialog>
#include "folding.hh"
#include "wstring_qt.hh"
#include "webmultimediadownload.hh"
@ -954,6 +955,221 @@ void ArticleView::openLink( QUrl const & url, QUrl const & ref,
}
}
void ArticleView::saveResource( const QUrl & url, QUrl const & ref )
{
resourceToSaveDownloadRequests.clear();
resourceToSaveUrl = url;
sptr< Dictionary::DataRequest > req;
if( url.scheme() == "bres" || url.scheme() == "gico" || url.scheme() == "gdau")
{
if ( url.host() == "search" )
{
// Since searches should be limited to current group, we just do them
// here ourselves since otherwise we'd need to pass group id to netmgr
// and it should've been having knowledge of the current groups, too.
unsigned currentGroup = getGroup( ref );
std::vector< sptr< Dictionary::Class > > const * activeDicts = 0;
if ( groups.size() )
{
for( unsigned x = 0; x < groups.size(); ++x )
if ( groups[ x ].id == currentGroup )
{
activeDicts = &( groups[ x ].dictionaries );
break;
}
}
else
activeDicts = &allDictionaries;
if ( activeDicts )
{
for( unsigned x = 0; x < activeDicts->size(); ++x )
{
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() ) );
}
}
}
}
else
{
// Normal resource download
QString contentType;
req = articleNetMgr.getResource( url, contentType );
}
}
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() ) );
}
}
if ( resourceToSaveDownloadRequests.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::fromRawData( 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() )
{
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" ) );
}
}
// Ok, whatever it was, it's finished. Remove this and any other
// requests and finish.
resourceToSaveDownloadRequests.clear();
return;
}
if ( resourceToSaveDownloadRequests.empty() )
{
emit statusBarMessage(
tr( "ERROR: %1" ).arg( tr( "The referenced resource failed to download." ) ),
10000, QPixmap( ":/icons/error.png" ) );
}
}
void ArticleView::updateMutedContents()
{
QUrl currentUrl = ui.definition->url();
@ -1073,6 +1289,8 @@ void ArticleView::contextMenuRequested( QPoint const & pos )
QAction * addWordToHistoryAction = 0;
QAction * addHeaderToHistoryAction = 0;
QAction * sendWordToInputLineAction = 0;
QAction * saveImageAction = 0;
QAction * saveSoundAction = 0;
QUrl targetUrl( r.linkUrl() );
Contexts contexts;
@ -1102,6 +1320,25 @@ void ArticleView::contextMenuRequested( QPoint const & pos )
}
}
QWebElement el = r.element();
QUrl imageUrl;
if( !popupView && el.tagName().compare( "img", Qt::CaseInsensitive ) == 0 )
{
imageUrl = QUrl::fromPercentEncoding( el.attribute( "src" ).toLatin1() );
if( !imageUrl.isEmpty() )
{
saveImageAction = new QAction( tr( "Save &image" ), &menu );
menu.addAction( saveImageAction );
}
}
if( !popupView && ( targetUrl.scheme() == "gdau"
|| Dictionary::WebMultimediaDownload::isAudioUrl( targetUrl ) ) )
{
saveSoundAction = new QAction( tr( "Save s&ound" ), &menu );
menu.addAction( saveSoundAction );
}
QString selectedText = ui.definition->selectedText();
if ( selectedText.size() && selectedText.size() < 60 )
@ -1265,6 +1502,12 @@ 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() );
else
{
if ( !popupView && result == maxDictionaryRefsAction )
emit showDictsPane();

View file

@ -46,6 +46,9 @@ 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;
@ -209,6 +212,8 @@ signals:
void sendWordToInputLine( QString const & word );
void storeResourceSavePath(QString const & );
public slots:
void on_searchPrevious_clicked();
@ -237,6 +242,7 @@ private slots:
void contextMenuRequested( QPoint const & );
void resourceDownloadFinished();
void resourceToSaveDownloadFinished();
/// We handle pasting by attempting to define the word in clipboard.
void pasteTriggered();
@ -307,6 +313,8 @@ 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

View file

@ -777,6 +777,9 @@ Class load() throw( exError )
if ( !root.namedItem( "historyExportPath" ).isNull() )
c.historyExportPath = root.namedItem( "historyExportPath" ).toElement().text();
if ( !root.namedItem( "resourceSavePath" ).isNull() )
c.resourceSavePath = root.namedItem( "resourceSavePath" ).toElement().text();
if ( !root.namedItem( "editDictionaryCommandLine" ).isNull() )
c.editDictionaryCommandLine = root.namedItem( "editDictionaryCommandLine" ).toElement().text();
@ -1470,6 +1473,13 @@ void save( Class const & c ) throw( exError )
root.appendChild( opt );
}
if( !c.resourceSavePath.isEmpty() )
{
opt = dd.createElement( "resourceSavePath" );
opt.appendChild( dd.createTextNode( c.resourceSavePath ) );
root.appendChild( opt );
}
opt = dd.createElement( "editDictionaryCommandLine" );
opt.appendChild( dd.createTextNode( c.editDictionaryCommandLine ) );
root.appendChild( opt );

View file

@ -404,6 +404,7 @@ struct Class
QByteArray dictInfoGeometry; // Geometry of "Dictionary info" window
QString historyExportPath; // Path for export/import history
QString resourceSavePath; // Path to save images/audio
bool pinPopupWindow; // Last pin status

View file

@ -1335,7 +1335,10 @@ ArticleView * MainWindow::createNewTab( bool switchToIt,
this, SLOT( addWordToHistory( QString ) ) );
connect( view, SIGNAL( sendWordToInputLine( QString const & ) ),
this, SLOT(sendWordToInputLine( QString const & ) ) );
this, SLOT( sendWordToInputLine( QString const & ) ) );
connect( view, SIGNAL( storeResourceSavePath( QString const & ) ),
this, SLOT( storeResourceSavePath( QString const & ) ) );
view->setSelectionBySingleClick( cfg.preferences.selectWordBySingleClick );
@ -3340,6 +3343,11 @@ void MainWindow::sendWordToInputLine( const QString & word )
translateLine->setText( word );
}
void MainWindow::storeResourceSavePath( const QString & newPath )
{
cfg.resourceSavePath = newPath;
}
#ifdef Q_OS_WIN32
bool MainWindow::winEvent( MSG * message, long * result )

View file

@ -385,6 +385,8 @@ private slots:
void sendWordToInputLine( QString const & word );
void storeResourceSavePath( QString const & );
signals:
/// Set optional parts expand mode for all tabs
void setExpandOptionalParts( bool expand );