goldendict-ng/src/scanpopup.cc
Konstantin Isakov c7126a0281 + Ability to play the first audio reference by clicking on the 'Pronounce word'
button in main window or in scan popup added. It is also possible to
  configure the program to do that automatically.
+ It is now possible to specify which program to use for audio file playback.
2009-04-10 21:07:03 +00:00

415 lines
9.8 KiB
C++

/* This file is (c) 2008-2009 Konstantin Isakov <ikm@users.berlios.de>
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#include "scanpopup.hh"
#include "folding.hh"
#include "mouseover.hh"
#include <QUrl>
#include <QCursor>
#include <QPixmap>
#include <QBitmap>
#include <QMenu>
#include <QMouseEvent>
#include <QDesktopWidget>
using std::wstring;
ScanPopup::ScanPopup( QWidget * parent,
Config::Class & cfg_,
ArticleNetworkAccessManager & articleNetMgr,
std::vector< sptr< Dictionary::Class > > const & allDictionaries_,
Instances::Groups const & groups_ ):
QDialog( parent ),
cfg( cfg_ ),
isScanningEnabled( false ),
allDictionaries( allDictionaries_ ),
groups( groups_ ),
wordFinder( this ),
mouseEnteredOnce( false ),
hideTimer( this )
{
ui.setupUi( this );
ui.queryError->hide();
definition = new ArticleView( ui.outerFrame, articleNetMgr, groups, true,
cfg );
ui.mainLayout->addWidget( definition );
ui.wordListButton->hide();
ui.pronounceButton->hide();
ui.groupList->fill( groups );
ui.groupList->setCurrentGroup( cfg.lastPopupGroupId );
setWindowFlags( Qt::Popup );
if ( cfg.lastPopupSize.isValid() )
resize( cfg.lastPopupSize );
#ifdef Q_OS_WIN32
// On Windows, leaveEvent() doesn't seem to work with popups and we're trying
// to emulate it.
setMouseTracking( true );
#endif
#if 0 // Experimental code to give window a non-rectangular shape (i.e.
// balloon) using a colorkey mask.
QPixmap pixMask( size() );
render( &pixMask );
setMask( pixMask.createMaskFromColor( QColor( 255, 0, 0 ) ) );
// This helps against flickering
setAttribute( Qt::WA_NoSystemBackground );
#endif
connect( ui.groupList, SIGNAL( currentIndexChanged( QString const & ) ),
this, SLOT( currentGroupChanged( QString const & ) ) );
connect( &wordFinder, SIGNAL( finished() ),
this, SLOT( prefixMatchFinished() ) );
connect( ui.pinButton, SIGNAL( clicked( bool ) ),
this, SLOT( pinButtonClicked( bool ) ) );
connect( definition, SIGNAL( pageLoaded() ),
this, SLOT( pageLoaded() ) );
connect( QApplication::clipboard(), SIGNAL( changed( QClipboard::Mode ) ),
this, SLOT( clipboardChanged( QClipboard::Mode ) ) );
connect( &MouseOver::instance(), SIGNAL( hovered( QString const & ) ),
this, SLOT( mouseHovered( QString const & ) ) );
hideTimer.setSingleShot( true );
hideTimer.setInterval( 400 );
connect( &hideTimer, SIGNAL( timeout() ),
this, SLOT( hideTimerExpired() ) );
}
ScanPopup::~ScanPopup()
{
disableScanning();
}
void ScanPopup::enableScanning()
{
if ( !isScanningEnabled )
{
isScanningEnabled = true;
MouseOver::instance().enableMouseOver();
}
}
void ScanPopup::disableScanning()
{
if ( isScanningEnabled )
{
MouseOver::instance().disableMouseOver();
isScanningEnabled = false;
}
}
void ScanPopup::clipboardChanged( QClipboard::Mode m )
{
if ( !isScanningEnabled )
return;
printf( "clipboard changed\n" );
QString subtype = "plain";
handleInputWord( QApplication::clipboard()->text( subtype, m ) );
}
void ScanPopup::mouseHovered( QString const & str )
{
handleInputWord( str );
}
void ScanPopup::handleInputWord( QString const & str )
{
if ( !cfg.preferences.enableScanPopup )
return;
// Check key modifiers
if ( cfg.preferences.enableScanPopupModifiers &&
!checkModifiersPressed( cfg.preferences.scanPopupModifiers ) )
return;
inputWord = QString::fromStdWString( Folding::trimWhitespaceOrPunct( str.toStdWString() ) );
if ( !inputWord.size() )
return;
/// Too large strings make window expand which is probably not what user
/// wants
ui.word->setText( elideInputWord() );
ui.wordListButton->hide();
ui.pronounceButton->hide();
if ( !isVisible() )
{
// Decide where should the window land
QPoint currentPos = QCursor::pos();
QRect desktop = QApplication::desktop()->screenGeometry();
QSize windowSize = geometry().size();
int x, y;
/// Try the to-the-right placement
if ( currentPos.x() + 4 + windowSize.width() <= desktop.topRight().x() )
x = currentPos.x() + 4;
else
/// Try the to-the-left placement
if ( currentPos.x() - 4 - windowSize.width() >= desktop.x() )
x = currentPos.x() - 4 - windowSize.width();
else
// Center it
x = desktop.x() + ( desktop.width() - windowSize.width() ) / 2;
/// Try the to-the-buttom placement
if ( currentPos.y() + 15 + windowSize.height() <= desktop.bottomLeft().y() )
y = currentPos.y() + 15;
else
/// Try the to-the-top placement
if ( currentPos.y() - 15 - windowSize.height() >= desktop.y() )
y = currentPos.y() - 15 - windowSize.height();
else
// Center it
y = desktop.y() + ( desktop.height() - windowSize.height() ) / 2;
move( x, y );
show();
mouseEnteredOnce = false; // Windows-only
// This produced some funky mouse grip-related bugs so we commented it out
//QApplication::processEvents(); // Make window appear immediately no matter what
}
initiateTranslation();
}
QString ScanPopup::elideInputWord()
{
return inputWord.size() > 32 ? inputWord.mid( 0, 32 ) + "..." : inputWord;
}
void ScanPopup::currentGroupChanged( QString const & )
{
if ( isVisible() )
initiateTranslation();
cfg.lastPopupGroupId = ui.groupList->getCurrentGroup();
}
void ScanPopup::initiateTranslation()
{
definition->showDefinition( inputWord, ui.groupList->getCurrentGroup() );
wordFinder.prefixMatch( inputWord, getActiveDicts() );
}
vector< sptr< Dictionary::Class > > const & ScanPopup::getActiveDicts()
{
int currentGroup = ui.groupList->currentIndex();
return
currentGroup < 0 || currentGroup >= (int)groups.size() ? allDictionaries :
groups[ currentGroup ].dictionaries;
}
void ScanPopup::mousePressEvent( QMouseEvent * ev )
{
startPos = ev->globalPos();
setCursor( Qt::ClosedHandCursor );
QDialog::mousePressEvent( ev );
}
void ScanPopup::mouseMoveEvent( QMouseEvent * event )
{
if ( event->buttons() )
{
QPoint newPos = event->globalPos();
QPoint delta = newPos - startPos;
startPos = newPos;
// Find a top-level window
move( pos() + delta );
}
#ifdef Q_OS_WIN32
else
if ( !ui.pinButton->isChecked() )
{
if ( !mouseEnteredOnce )
{
// We're waiting for mouse to enter window
if ( geometry().contains( event->globalPos() ) )
{
mouseEnteredOnce = true;
hideTimer.stop();
}
}
else
{
// We're waiting for mouse to leave window
if ( !geometry().contains( event->globalPos() ) )
{
mouseEnteredOnce = false;
hideTimer.start();
}
}
}
#endif
QDialog::mouseMoveEvent( event );
}
void ScanPopup::mouseReleaseEvent( QMouseEvent * ev )
{
unsetCursor();
QDialog::mouseReleaseEvent( ev );
}
void ScanPopup::leaveEvent( QEvent * event )
{
QDialog::leaveEvent( event );
// We hide the popup when the mouse leaves it. So in order to close it
// without any clicking the cursor has to get inside and then to leave.
// Combo-boxes seem to generate leave events for their parents when
// unfolded, so we check coordinates as well.
// If the dialog is pinned, we don't hide the popup.
// If some mouse buttons are pressed, we don't hide the popup either,
// since it indicates the move operation is underway.
if ( !ui.pinButton->isChecked() && !geometry().contains( QCursor::pos() ) &&
QApplication::mouseButtons() == Qt::NoButton )
{
hideTimer.start();
}
}
void ScanPopup::enterEvent( QEvent * event )
{
QDialog::enterEvent( event );
// If there was a countdown to hide the window, stop it.
hideTimer.stop();
}
void ScanPopup::resizeEvent( QResizeEvent * event )
{
cfg.lastPopupSize = event->size();
QDialog::resizeEvent( event );
}
void ScanPopup::showEvent( QShowEvent * ev )
{
QDialog::showEvent( ev );
if ( groups.empty() )
ui.groupList->hide();
}
void ScanPopup::prefixMatchFinished()
{
// Check that there's a window there at all
if ( isVisible() )
{
if ( wordFinder.getErrorString().size() )
{
ui.queryError->setToolTip( wordFinder.getErrorString() );
ui.queryError->show();
}
else
ui.queryError->hide();
ui.wordListButton->setVisible( wordFinder.getPrefixMatchResults().size() );
}
}
void ScanPopup::on_wordListButton_clicked()
{
if ( !isVisible() )
return;
WordFinder::SearchResults const & results = wordFinder.getPrefixMatchResults();
if ( results.empty() )
return;
QMenu menu( this );
unsigned total = results.size() < 20 ? results.size() : 20;
for( unsigned x = 0; x < total; ++x )
{
// Some items are just too large! For now skip them.
if ( results[ x ].first.size() > 64 )
{
if ( total < results.size() )
++total;
}
else
menu.addAction( results[ x ].first );
}
QAction * result = menu.exec( mapToGlobal( ui.wordListButton->pos() ) +
QPoint( 0, ui.wordListButton->height() ) );
if ( result )
definition->showDefinition( result->text(), ui.groupList->getCurrentGroup() );
}
void ScanPopup::on_pronounceButton_clicked()
{
definition->playSound();
}
void ScanPopup::pinButtonClicked( bool checked )
{
if ( checked )
{
setWindowFlags( Qt::Dialog );
setWindowTitle( elideInputWord() );
hideTimer.stop();
}
else
setWindowFlags( Qt::Popup );
show();
}
void ScanPopup::hideTimerExpired()
{
if ( isVisible() )
{
unsetCursor(); // Just in case
hide();
}
}
void ScanPopup::pageLoaded()
{
ui.pronounceButton->setVisible( definition->hasSound() );
if ( cfg.preferences.pronounceOnLoadPopup )
definition->playSound();
}