goldendict-ng/mainwindow.cc
2010-05-15 16:42:18 +04:00

1798 lines
48 KiB
C++

/* This file is (c) 2008-2010 Konstantin Isakov <ikm@users.berlios.de>
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#include "mainwindow.hh"
#include "editdictionaries.hh"
#include "loaddictionaries.hh"
#include "preferences.hh"
#include "ui_about.h"
#include <limits.h>
#include <QDir>
#include <QMessageBox>
#include <QIcon>
#include <QToolBar>
#include <QCloseEvent>
#include <QDesktopServices>
#include <set>
#include <map>
using std::set;
using std::wstring;
using std::map;
using std::pair;
MainWindow::MainWindow( Config::Class & cfg_ ):
trayIcon( 0 ),
groupLabel( &searchPaneTitleBar ),
groupList( &searchPaneTitleBar ),
escAction( this ),
focusTranslateLineAction( this ),
addTabAction( this ),
closeCurrentTabAction( this ),
switchToNextTabAction( this ),
switchToPrevTabAction( this ),
showDictBarNamesAction( tr( "Show Names in Dictionary Bar" ), this ),
trayIconMenu( this ),
addTab( this ),
cfg( cfg_ ),
history( History::Load() ),
dictionaryBar( this, cfg.mutedDictionaries, configEvents ),
articleMaker( dictionaries, groupInstances, cfg.preferences.displayStyle ),
articleNetMgr( this, dictionaries, articleMaker,
cfg.preferences.disallowContentFromOtherSites ),
dictNetMgr( this ),
wordFinder( this ),
newReleaseCheckTimer( this )
{
applyQtStyleSheet( cfg.preferences.displayStyle );
ui.setupUi( this );
// Make the search pane's titlebar
groupLabel.setText( tr( "Look up in:" ) );
searchPaneTitleBarLayout.setContentsMargins( 8, 5, 8, 4 );
searchPaneTitleBarLayout.addWidget( &groupLabel );
searchPaneTitleBarLayout.addWidget( &groupList );
searchPaneTitleBarLayout.addStretch();
searchPaneTitleBar.setLayout( &searchPaneTitleBarLayout );
ui.searchPane->setTitleBarWidget( &searchPaneTitleBar );
// Make the toolbar
navToolbar = addToolBar( tr( "Navigation" ) );
navToolbar->setObjectName( "navToolbar" );
navBack = navToolbar->addAction( QIcon( ":/icons/previous.png" ), tr( "Back" ) );
navForward = navToolbar->addAction( QIcon( ":/icons/next.png" ), tr( "Forward" ) );
navToolbar->addSeparator();
navToolbar->addAction( ui.print );
navToolbar->addAction( ui.saveArticle );
navToolbar->addSeparator();
enableScanPopup = navToolbar->addAction( QIcon( ":/icons/wizard.png" ), tr( "Scan Popup" ) );
enableScanPopup->setCheckable( true );
enableScanPopup->setVisible( cfg.preferences.enableScanPopup );
if ( cfg.preferences.enableScanPopup && cfg.preferences.startWithScanPopupOn )
enableScanPopup->setChecked( true );
connect( enableScanPopup, SIGNAL( toggled( bool ) ),
this, SLOT( scanEnableToggled( bool ) ) );
navToolbar->addSeparator();
navPronounce = navToolbar->addAction( QIcon( ":/icons/playsound.png" ), tr( "Pronounce Word (Alt+S)" ) );
navPronounce->setShortcut( QKeySequence( "Alt+S" ) );
navPronounce->setEnabled( false );
connect( navPronounce, SIGNAL( triggered() ),
this, SLOT( pronounce() ) );
// zooming
navToolbar->addSeparator();
zoomIn = navToolbar->addAction( QIcon( ":/icons/icon32_zoomin.png" ), tr( "Zoom In" ) );
zoomOut = navToolbar->addAction( QIcon( ":/icons/icon32_zoomout.png" ), tr( "Zoom Out" ) );
zoomBase = navToolbar->addAction( QIcon( ":/icons/icon32_zoombase.png" ), tr( "Normal Size" ) );
connect( zoomIn, SIGNAL( triggered() ),
this, SLOT( zoomin() ) );
connect( zoomOut, SIGNAL( triggered() ),
this, SLOT( zoomout() ) );
connect( zoomBase, SIGNAL( triggered() ),
this, SLOT( unzoom() ) );
// tray icon
connect( trayIconMenu.addAction( tr( "Show &Main Window" ) ), SIGNAL( activated() ),
this, SLOT( showMainWindow() ) );
trayIconMenu.addAction( enableScanPopup );
trayIconMenu.addSeparator();
connect( trayIconMenu.addAction( tr( "&Quit" ) ), SIGNAL( activated() ),
qApp, SLOT( quit() ) );
escAction.setShortcutContext( Qt::WidgetWithChildrenShortcut );
escAction.setShortcut( QKeySequence( "Esc" ) );
connect( &escAction, SIGNAL( triggered() ),
this, SLOT( handleEsc() ) );
focusTranslateLineAction.setShortcutContext( Qt::WidgetWithChildrenShortcut );
focusTranslateLineAction.setShortcuts( QList< QKeySequence >() <<
QKeySequence( "Alt+D" ) <<
QKeySequence( "Ctrl+L" ) );
connect( &focusTranslateLineAction, SIGNAL( triggered() ),
this, SLOT( focusTranslateLine() ) );
ui.centralWidget->addAction( &escAction );
ui.searchPaneWidget->addAction( &escAction );
groupList.addAction( &escAction );
ui.centralWidget->addAction( &focusTranslateLineAction );
ui.searchPaneWidget->addAction( &focusTranslateLineAction );
groupList.addAction( &focusTranslateLineAction );
addTabAction.setShortcutContext( Qt::WidgetWithChildrenShortcut );
addTabAction.setShortcut( QKeySequence( "Ctrl+T" ) );
connect( &addTabAction, SIGNAL( triggered() ),
this, SLOT( addNewTab() ) );
addAction( &addTabAction );
closeCurrentTabAction.setShortcutContext( Qt::WidgetWithChildrenShortcut );
closeCurrentTabAction.setShortcut( QKeySequence( "Ctrl+W" ) );
connect( &closeCurrentTabAction, SIGNAL( triggered() ),
this, SLOT( closeCurrentTab() ) );
addAction( &closeCurrentTabAction );
switchToNextTabAction.setShortcutContext( Qt::WidgetWithChildrenShortcut );
switchToNextTabAction.setShortcut( QKeySequence( "Ctrl+PgDown" ) );
connect( &switchToNextTabAction, SIGNAL( triggered() ),
this, SLOT( switchToNextTab() ) );
addAction( &switchToNextTabAction );
switchToPrevTabAction.setShortcutContext( Qt::WidgetWithChildrenShortcut );
switchToPrevTabAction.setShortcut( QKeySequence( "Ctrl+PgUp" ) );
connect( &switchToPrevTabAction, SIGNAL( triggered() ),
this, SLOT( switchToPrevTab() ) );
addAction( &switchToPrevTabAction );
showDictBarNamesAction.setCheckable( true );
showDictBarNamesAction.setChecked( cfg.showingDictBarNames );
connect( &showDictBarNamesAction, SIGNAL( triggered() ),
this, SLOT( showDictBarNamesTriggered() ) );
// Popuplate 'View' menu
ui.menuView->addAction( ui.searchPane->toggleViewAction() );
ui.menuView->addSeparator();
ui.menuView->addAction( dictionaryBar.toggleViewAction() );
ui.menuView->addAction( navToolbar->toggleViewAction() );
ui.menuView->addSeparator();
ui.menuView->addAction( &showDictBarNamesAction );
// Dictionary bar
showDictBarNamesTriggered(); // Make update its state according to initial
// setting
addToolBar( &dictionaryBar );
connect( dictionaryBar.toggleViewAction(), SIGNAL(triggered(bool)),
this, SLOT(dictionaryBarToggled(bool)) );
// This one will be disconnected once the slot is activated. It exists
// only to handle the initial appearance of the dictionary bar.
connect( dictionaryBar.toggleViewAction(), SIGNAL(toggled(bool)),
this, SLOT(dictionaryBarToggled(bool)) );
connect( &dictionaryBar, SIGNAL(editGroupRequested()),
this, SLOT(editCurrentGroup()) );
// History
connect( &history, SIGNAL( itemsChanged() ),
this, SLOT( historyChanged() ) );
connect( ui.menuHistory, SIGNAL(triggered(QAction*)),
this, SLOT(menuHistoryTriggered(QAction*)), Qt::QueuedConnection );
// Show tray icon early so the user would be happy. It won't be functional
// though until the program inits fully.
if ( cfg.preferences.enableTrayIcon )
{
trayIcon = new QSystemTrayIcon( QIcon( ":/icons/programicon.png" ), this );
trayIcon->setToolTip( tr( "Loading..." ) );
trayIcon->show();
}
connect( navBack, SIGNAL( activated() ),
this, SLOT( backClicked() ) );
connect( navForward, SIGNAL( activated() ),
this, SLOT( forwardClicked() ) );
addTab.setAutoRaise( true );
addTab.setIcon( QIcon( ":/icons/addtab.png" ) );
ui.tabWidget->clear();
ui.tabWidget->setCornerWidget( &addTab, Qt::TopLeftCorner );
//ui.tabWidget->setCornerWidget( &closeTab, Qt::TopRightCorner );
#if QT_VERSION >= 0x040500
ui.tabWidget->setMovable( true );
#endif
#ifndef Q_OS_WIN32
ui.tabWidget->setDocumentMode( true );
#endif
connect( &addTab, SIGNAL( clicked() ),
this, SLOT( addNewTab() ) );
connect( ui.tabWidget, SIGNAL( tabCloseRequested( int ) ),
this, SLOT( tabCloseRequested( int ) ) );
connect( ui.tabWidget, SIGNAL( currentChanged( int ) ),
this, SLOT( tabSwitched( int ) ) );
#if QT_VERSION >= 0x040500
ui.tabWidget->setTabsClosable( true );
#endif
connect( ui.quit, SIGNAL( activated() ),
qApp, SLOT( quit() ) );
connect( ui.dictionaries, SIGNAL( activated() ),
this, SLOT( editDictionaries() ) );
connect( ui.preferences, SIGNAL( activated() ),
this, SLOT( editPreferences() ) );
connect( ui.visitHomepage, SIGNAL( activated() ),
this, SLOT( visitHomepage() ) );
connect( ui.visitForum, SIGNAL( activated() ),
this, SLOT( visitForum() ) );
connect( ui.about, SIGNAL( activated() ),
this, SLOT( showAbout() ) );
connect( &groupList, SIGNAL( currentIndexChanged( QString const & ) ),
this, SLOT( currentGroupChanged( QString const & ) ) );
connect( ui.translateLine, SIGNAL( textChanged( QString const & ) ),
this, SLOT( translateInputChanged( QString const & ) ) );
connect( ui.translateLine, SIGNAL( returnPressed() ),
this, SLOT( translateInputFinished() ) );
connect( ui.wordList, SIGNAL( itemSelectionChanged() ),
this, SLOT( wordListSelectionChanged() ) );
connect( &wordFinder, SIGNAL( updated() ),
this, SLOT( prefixMatchUpdated() ) );
connect( &wordFinder, SIGNAL( finished() ),
this, SLOT( prefixMatchFinished() ) );
connect( &configEvents, SIGNAL( mutedDictionariesChanged() ),
this, SLOT( mutedDictionariesChanged() ) );
ui.translateLine->installEventFilter( this );
ui.wordList->installEventFilter( this );
if ( cfg.mainWindowGeometry.size() )
restoreGeometry( cfg.mainWindowGeometry );
if ( cfg.mainWindowState.size() )
restoreState( cfg.mainWindowState, 1 );
applyProxySettings();
makeDictionaries();
// After we have dictionaries and groups, we can populate history
historyChanged();
addNewTab();
// Show the initial welcome text
{
ArticleView & view =
dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) );
view.showDefinition( tr( "Welcome!" ), Instances::Group::HelpGroupId );
}
ui.translateLine->setFocus();
if ( trayIcon )
{
// Upgrade existing dummy tray icon into a full-functional one
trayIcon->setContextMenu( &trayIconMenu );
trayIcon->show();
connect( trayIcon, SIGNAL( activated( QSystemTrayIcon::ActivationReason ) ),
this, SLOT( trayIconActivated( QSystemTrayIcon::ActivationReason ) ) );
}
updateTrayIcon();
// Update zoomers
applyZoomFactor();
// Update autostart info
setAutostart(cfg.preferences.autoStart);
// Initialize global hotkeys
installHotKeys();
// Only show window initially if it wasn't configured differently
if ( !cfg.preferences.enableTrayIcon || !cfg.preferences.startToTray )
{
show();
focusTranslateLine();
}
connect( &newReleaseCheckTimer, SIGNAL( timeout() ),
this, SLOT( checkForNewRelease() ) );
prepareNewReleaseChecks();
// makeDictionaries() didn't do deferred init - we do it here, at the end.
doDeferredInit( dictionaries );
}
void MainWindow::mousePressEvent( QMouseEvent *event)
{
if (event->button() != Qt::MidButton)
return QMainWindow::mousePressEvent(event);
// middle clicked
QString subtype = "plain";
QString str = QApplication::clipboard()->text(subtype,
QClipboard::Selection);
ui.translateLine->setText(str);
QKeyEvent ev(QEvent::Type(6)/*QEvent::KeyPress*/, Qt::Key_Enter,
Qt::NoModifier);
QApplication::sendEvent(ui.translateLine, &ev);
}
MainWindow::~MainWindow()
{
// Save MainWindow state and geometry
cfg.mainWindowState = saveState( 1 );
cfg.mainWindowGeometry = saveGeometry();
// Close the popup, so it would save its geometry to config
scanPopup.reset();
// Save any changes in last chosen groups etc
Config::save( cfg );
// Close all tabs -- they should be destroyed before network managers
// do.
while( ui.tabWidget->count() )
{
QWidget * w = ui.tabWidget->widget( 0 );
ui.tabWidget->removeTab( 0 );
delete w;
}
}
QPrinter & MainWindow::getPrinter()
{
if ( printer.get() )
return *printer;
printer = new QPrinter( QPrinter::HighResolution );
return *printer;
}
void MainWindow::applyQtStyleSheet( QString const & displayStyle )
{
QFile builtInCssFile( ":/qt-style.css" );
builtInCssFile.open( QFile::ReadOnly );
QByteArray css = builtInCssFile.readAll();
if ( displayStyle.size() )
{
// Load an additional stylesheet
QFile builtInCssFile( QString( ":/qt-style-st-%1.css" ).arg( displayStyle ) );
builtInCssFile.open( QFile::ReadOnly );
css += builtInCssFile.readAll();
}
// Try loading a style sheet if there's one
QFile cssFile( Config::getUserQtCssFileName() );
if ( cssFile.open( QFile::ReadOnly ) )
css += cssFile.readAll();
setStyleSheet( css );
}
void MainWindow::updateTrayIcon()
{
if ( !trayIcon && cfg.preferences.enableTrayIcon )
{
// Need to show it
trayIcon = new QSystemTrayIcon( QIcon( ":/icons/programicon.png" ), this );
trayIcon->setContextMenu( &trayIconMenu );
trayIcon->show();
connect( trayIcon, SIGNAL( activated( QSystemTrayIcon::ActivationReason ) ),
this, SLOT( trayIconActivated( QSystemTrayIcon::ActivationReason ) ) );
}
else
if ( trayIcon && !cfg.preferences.enableTrayIcon )
{
// Need to hide it
delete trayIcon;
trayIcon = 0;
}
if ( trayIcon )
{
// Update the icon to reflect the scanning mode
trayIcon->setIcon( QIcon(
enableScanPopup->isChecked() ?
":/icons/programicon_scan.png" :
":/icons/programicon.png" ) );
trayIcon->setToolTip( "GoldenDict" );
}
// The 'Close to tray' action is associated with the tray icon, so we hide
// or show it here.
ui.actionCloseToTray->setVisible( cfg.preferences.enableTrayIcon );
}
void MainWindow::closeEvent( QCloseEvent * ev )
{
if ( cfg.preferences.enableTrayIcon && cfg.preferences.closeToTray )
{
ev->ignore();
hide();
}
else
{
ev->accept();
qApp->quit();
}
}
void MainWindow::applyProxySettings()
{
QNetworkProxy::ProxyType type = QNetworkProxy::NoProxy;
if ( cfg.preferences.proxyServer.enabled )
{
switch( cfg.preferences.proxyServer.type )
{
case Config::ProxyServer::Socks5:
type = QNetworkProxy::Socks5Proxy;
break;
case Config::ProxyServer::HttpConnect:
type = QNetworkProxy::HttpProxy;
break;
case Config::ProxyServer::HttpGet:
type = QNetworkProxy::HttpCachingProxy;
break;
default:
break;
}
}
QNetworkProxy proxy( type );
if ( cfg.preferences.proxyServer.enabled )
{
proxy.setHostName( cfg.preferences.proxyServer.host );
proxy.setPort( cfg.preferences.proxyServer.port );
if ( cfg.preferences.proxyServer.user.size() )
proxy.setUser( cfg.preferences.proxyServer.user );
if ( cfg.preferences.proxyServer.password.size() )
proxy.setPassword( cfg.preferences.proxyServer.password );
}
QNetworkProxy::setApplicationProxy( proxy );
}
void MainWindow::makeDictionaries()
{
scanPopup.reset();
wordFinder.clear();
dictionariesUnmuted.clear();
loadDictionaries( this, isVisible(), cfg, dictionaries, dictNetMgr, false );
updateStatusLine();
updateGroupList();
makeScanPopup();
}
void MainWindow::updateStatusLine()
{
unsigned articleCount = 0, wordCount = 0;
for( unsigned x = dictionaries.size(); x--; )
{
articleCount += dictionaries[ x ]->getArticleCount();
wordCount += dictionaries[ x ]->getWordCount();
}
statusBar()->showMessage( tr( "%1 dictionaries, %2 articles, %3 words" ).
arg( dictionaries.size() ).arg( articleCount ).
arg( wordCount ) );
}
void MainWindow::updateGroupList()
{
bool haveGroups = cfg.groups.size();
groupList.setVisible( haveGroups );
groupLabel.setText( haveGroups ? tr( "Look up in:" ) : tr( "Look up:" ) );
// currentIndexChanged() signal is very trigger-happy. To avoid triggering
// it, we disconnect it while we're clearing and filling back groups.
disconnect( &groupList, SIGNAL( currentIndexChanged( QString const & ) ),
this, SLOT( currentGroupChanged( QString const & ) ) );
groupInstances.clear();
// Add dictionaryOrder first, as the 'All' group.
{
Instances::Group g( cfg.dictionaryOrder, dictionaries );
// Add any missing entries to dictionary order
Instances::complementDictionaryOrder( g,
Instances::Group( cfg.inactiveDictionaries, dictionaries ),
dictionaries );
g.name = tr( "All" );
g.id = Instances::Group::AllGroupId;
g.icon = "folder.png";
groupInstances.push_back( g );
}
for( unsigned x = 0; x < cfg.groups.size(); ++x )
groupInstances.push_back( Instances::Group( cfg.groups[ x ], dictionaries ) );
// Update names for dictionaries that are present, so that they could be
// found in case they got moved.
Instances::updateNames( cfg, dictionaries );
groupList.fill( groupInstances );
groupList.setCurrentGroup( cfg.lastMainGroupId );
updateDictionaryBar();
connect( &groupList, SIGNAL( currentIndexChanged( QString const & ) ),
this, SLOT( currentGroupChanged( QString const & ) ) );
}
void MainWindow::updateDictionaryBar()
{
if ( !dictionaryBar.toggleViewAction()->isChecked() )
return; // It's not enabled, therefore hidden -- don't waste time
Instances::Group * grp =
groupInstances.findGroup( groupList.getCurrentGroup() );
if ( grp ) // Should always be !0, but check as a safeguard
dictionaryBar.setDictionaries( grp->dictionaries );
}
void MainWindow::makeScanPopup()
{
scanPopup.reset();
if ( !cfg.preferences.enableScanPopup &&
!cfg.preferences.enableClipboardHotkey )
return;
scanPopup = new ScanPopup( 0, cfg, articleNetMgr, dictionaries, groupInstances,
history );
scanPopup->setStyleSheet( styleSheet() );
if ( cfg.preferences.enableScanPopup && enableScanPopup->isChecked() )
scanPopup->enableScanning();
connect( scanPopup.get(), SIGNAL(editGroupRequested( unsigned )),
this,SLOT(editDictionaries( unsigned )), Qt::QueuedConnection );
}
vector< sptr< Dictionary::Class > > const & MainWindow::getActiveDicts()
{
if ( groupInstances.empty() )
return dictionaries;
int current = groupList.currentIndex();
if ( current < 0 || current >= (int) groupInstances.size() )
{
// This shouldn't ever happen
return dictionaries;
}
if ( !dictionaryBar.toggleViewAction()->isChecked() )
return groupInstances[ current ].dictionaries;
else
{
vector< sptr< Dictionary::Class > > const & activeDicts =
groupInstances[ current ].dictionaries;
// Populate the special dictionariesUnmuted array with only unmuted
// dictionaries
dictionariesUnmuted.clear();
dictionariesUnmuted.reserve( activeDicts.size() );
for( unsigned x = 0; x < activeDicts.size(); ++x )
if ( !cfg.mutedDictionaries.contains(
QString::fromStdString( activeDicts[ x ]->getId() ) ) )
dictionariesUnmuted.push_back( activeDicts[ x ] );
return dictionariesUnmuted;
}
}
void MainWindow::addNewTab()
{
createNewTab( true, tr( "(untitled)" ) );
}
ArticleView * MainWindow::createNewTab( bool switchToIt,
QString const & name )
{
ArticleView * view = new ArticleView( this, articleNetMgr, dictionaries,
groupInstances, false, cfg,
dictionaryBar.toggleViewAction(),
&cfg.mutedDictionaries,
&groupList );
connect( view, SIGNAL( titleChanged( ArticleView *, QString const & ) ),
this, SLOT( titleChanged( ArticleView *, QString const & ) ) );
connect( view, SIGNAL( iconChanged( ArticleView *, QIcon const & ) ),
this, SLOT( iconChanged( ArticleView *, QIcon const & ) ) );
connect( view, SIGNAL( pageLoaded( ArticleView * ) ),
this, SLOT( pageLoaded( ArticleView * ) ) );
connect( view, SIGNAL( openLinkInNewTab( QUrl const &, QUrl const &, QString const &, ArticleView::Contexts const & ) ),
this, SLOT( openLinkInNewTab( QUrl const &, QUrl const &, QString const &, ArticleView::Contexts const & ) ) );
connect( view, SIGNAL( showDefinitionInNewTab( QString const &, unsigned, QString const &, ArticleView::Contexts const & ) ),
this, SLOT( showDefinitionInNewTab( QString const &, unsigned, QString const &, ArticleView::Contexts const & ) ) );
connect( view, SIGNAL( typingEvent( QString const & ) ),
this, SLOT( typingEvent( QString const & ) ) );
int index = cfg.preferences.newTabsOpenAfterCurrentOne ?
ui.tabWidget->currentIndex() + 1 : ui.tabWidget->count();
QString escaped = name;
escaped.replace( "&", "&&" );
ui.tabWidget->insertTab( index, view, escaped );
if ( switchToIt )
ui.tabWidget->setCurrentIndex( index );
view->setZoomFactor( cfg.preferences.zoomFactor );
return view;
}
void MainWindow::tabCloseRequested( int x )
{
if ( ui.tabWidget->count() < 2 )
return; // We should always have at least one open tab
QWidget * w = ui.tabWidget->widget( x );
ui.tabWidget->removeTab( x );
delete w;
}
void MainWindow::closeCurrentTab()
{
tabCloseRequested( ui.tabWidget->currentIndex() );
}
void MainWindow::switchToNextTab()
{
if ( ui.tabWidget->count() < 2 )
return;
ui.tabWidget->setCurrentIndex( ( ui.tabWidget->currentIndex() + 1 ) % ui.tabWidget->count() );
}
void MainWindow::switchToPrevTab()
{
if ( ui.tabWidget->count() < 2 )
return;
if ( !ui.tabWidget->currentIndex() )
ui.tabWidget->setCurrentIndex( ui.tabWidget->count() - 1 );
else
ui.tabWidget->setCurrentIndex( ui.tabWidget->currentIndex() - 1 );
}
void MainWindow::backClicked()
{
printf( "Back\n" );
ArticleView & view =
dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) );
view.back();
}
void MainWindow::forwardClicked()
{
printf( "Forward\n" );
ArticleView & view =
dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) );
view.forward();
}
void MainWindow::titleChanged( ArticleView * view, QString const & title )
{
QString escaped = title;
escaped.replace( "&", "&&" );
ui.tabWidget->setTabText( ui.tabWidget->indexOf( view ), escaped );
}
void MainWindow::iconChanged( ArticleView * view, QIcon const & icon )
{
ui.tabWidget->setTabIcon( ui.tabWidget->indexOf( view ), groupInstances.size() > 1 ? icon : QIcon() );
}
void MainWindow::pageLoaded( ArticleView * view )
{
updatePronounceAvailability();
if ( cfg.preferences.pronounceOnLoadMain )
pronounce( view );
}
void MainWindow::tabSwitched( int )
{
updatePronounceAvailability();
}
void MainWindow::dictionaryBarToggled( bool )
{
// From now on, only the triggered() signal is interesting to us
disconnect( dictionaryBar.toggleViewAction(), SIGNAL(toggled(bool)),
this, SLOT(dictionaryBarToggled(bool)) );
updateDictionaryBar(); // Updates dictionary bar contents if it's shown
applyMutedDictionariesState(); // Visibility change affects searches and results
}
void MainWindow::pronounce( ArticleView * view )
{
if ( view )
view->playSound();
else
dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) ).playSound();
}
void MainWindow::updatePronounceAvailability()
{
bool pronounceEnabled = ui.tabWidget->count() > 0 &&
dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) ).hasSound();
navPronounce->setEnabled( pronounceEnabled );
}
void MainWindow::editDictionaries( unsigned editDictionaryGroup )
{
hotkeyWrapper.reset(); // No hotkeys while we're editing dictionaries
scanPopup.reset(); // No scan popup either. No one should use dictionaries.
wordFinder.clear();
dictionariesUnmuted.clear();
EditDictionaries dicts( this, cfg, dictionaries, groupInstances, dictNetMgr );
if ( editDictionaryGroup != Instances::Group::NoGroupId )
dicts.editGroup( editDictionaryGroup );
dicts.exec();
if ( dicts.areDictionariesChanged() || dicts.areGroupsChanged() )
{
updateGroupList();
Config::save( cfg );
}
makeScanPopup();
installHotKeys();
}
void MainWindow::editCurrentGroup()
{
editDictionaries( groupList.getCurrentGroup() );
}
void MainWindow::editPreferences()
{
hotkeyWrapper.reset(); // So we could use the keys it hooks
Preferences preferences( this, cfg.preferences );
preferences.show();
if ( preferences.exec() == QDialog::Accepted )
{
Config::Preferences p = preferences.getPreferences();
// See if we need to reapply stylesheets
if ( cfg.preferences.displayStyle != p.displayStyle )
{
applyQtStyleSheet( p.displayStyle );
articleMaker.setDisplayStyle( p.displayStyle );
for( int x = 0; x < ui.tabWidget->count(); ++x )
{
ArticleView & view =
dynamic_cast< ArticleView & >( *( ui.tabWidget->widget( x ) ) );
view.reload();
}
}
cfg.preferences = p;
enableScanPopup->setVisible( cfg.preferences.enableScanPopup );
if ( !cfg.preferences.enableScanPopup )
enableScanPopup->setChecked( false );
updateTrayIcon();
applyProxySettings();
makeScanPopup();
setAutostart( cfg.preferences.autoStart );
prepareNewReleaseChecks();
Config::save( cfg );
}
installHotKeys();
}
void MainWindow::currentGroupChanged( QString const & )
{
cfg.lastMainGroupId = groupList.getCurrentGroup();
updateDictionaryBar();
// Update word search results
translateInputChanged( ui.translateLine->text() );
}
void MainWindow::translateInputChanged( QString const & newValue )
{
// If there's some status bar message present, clear it since it may be
// about the previous search that has failed.
if ( !statusBar()->currentMessage().isEmpty() )
statusBar()->clearMessage();
// If some word is selected in the word list, unselect it. This prevents
// triggering a set of spurious activation signals when the list changes.
if ( ui.wordList->selectionModel()->hasSelection() )
ui.wordList->setCurrentItem( 0, QItemSelectionModel::Clear );
QString req = newValue.trimmed();
if ( !req.size() )
{
// An empty request always results in an empty result
wordFinder.cancel();
ui.wordList->clear();
ui.wordList->unsetCursor();
// Reset the noResults mark if it's on right now
if ( ui.translateLine->property( "noResults" ).toBool() )
{
ui.translateLine->setProperty( "noResults", false );
setStyleSheet( styleSheet() );
}
return;
}
ui.wordList->setCursor( Qt::WaitCursor );
wordFinder.prefixMatch( req, getActiveDicts() );
}
void MainWindow::translateInputFinished()
{
QString word = ui.translateLine->text();
if ( word.size() )
{
Qt::KeyboardModifiers mods = QApplication::keyboardModifiers();
if ( mods & Qt::ControlModifier )
addNewTab();
showTranslationFor( word );
if ( ui.searchPane->isFloating() )
activateWindow();
dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) ).focus();
}
}
void MainWindow::handleEsc()
{
if ( dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) ).closeSearch() )
return;
focusTranslateLine();
}
void MainWindow::focusTranslateLine()
{
if ( ui.searchPane->isFloating() )
ui.searchPane->activateWindow();
ui.translateLine->setFocus();
ui.translateLine->selectAll();
}
void MainWindow::prefixMatchUpdated()
{
updateMatchResults( false );
}
void MainWindow::prefixMatchFinished()
{
updateMatchResults( true );
}
void MainWindow::updateMatchResults( bool finished )
{
WordFinder::SearchResults const & results = wordFinder.getResults();
ui.wordList->setUpdatesEnabled( false );
for( unsigned x = 0; x < results.size(); ++x )
{
QListWidgetItem * i = ui.wordList->item( x );
if ( !i )
{
i = new QListWidgetItem( results[ x ].first, ui.wordList );
if ( results[ x ].second )
{
QFont f = i->font();
f.setItalic( true );
i->setFont( f );
}
ui.wordList->addItem( i );
}
else
{
if ( i->text() != results[ x ].first )
i->setText( results[ x ].first );
QFont f = i->font();
if ( f.italic() != results[ x ].second )
{
f.setItalic( results[ x ].second );
i->setFont( f );
}
}
if (i->text().at(0).direction() == QChar::DirR)
i->setTextAlignment(Qt::AlignRight);
if (i->text().at(0).direction() == QChar::DirL)
i->setTextAlignment(Qt::AlignLeft);
}
while ( ui.wordList->count() > (int) results.size() )
{
// Chop off any extra items that were there
QListWidgetItem * i = ui.wordList->takeItem( ui.wordList->count() - 1 );
if ( i )
delete i;
else
break;
}
if ( ui.wordList->count() )
{
ui.wordList->scrollToItem( ui.wordList->item( 0 ), QAbstractItemView::PositionAtTop );
ui.wordList->setCurrentItem( 0, QItemSelectionModel::Clear );
}
ui.wordList->setUpdatesEnabled( true );
if ( finished )
{
ui.wordList->unsetCursor();
// Visually mark the input line to mark if there's no results
bool setMark = results.empty();
if ( ui.translateLine->property( "noResults" ).toBool() != setMark )
{
ui.translateLine->setProperty( "noResults", setMark );
setStyleSheet( styleSheet() );
}
if ( !wordFinder.getErrorString().isEmpty() )
statusBar()->showMessage( tr( "WARNING: %1" ).arg( wordFinder.getErrorString() ) );
}
}
void MainWindow::applyMutedDictionariesState()
{
// Redo the current search request
translateInputChanged( ui.translateLine->text() );
// Update active article view
ArticleView & view =
dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) );
view.updateMutedContents();
}
bool MainWindow::eventFilter( QObject * obj, QEvent * ev )
{
if ( obj == ui.translateLine )
{
if ( ev->type() == /*QEvent::KeyPress*/ 6 )
{
QKeyEvent * keyEvent = static_cast< QKeyEvent * >( ev );
if ( keyEvent->matches( QKeySequence::MoveToNextLine ) && ui.wordList->count() )
{
ui.wordList->setFocus( Qt::ShortcutFocusReason );
ui.wordList->setCurrentRow( 0, QItemSelectionModel::ClearAndSelect );
return true;
}
}
}
else
if ( obj == ui.wordList )
{
if ( ev->type() == /*QEvent::KeyPress*/ 6 )
{
QKeyEvent * keyEvent = static_cast< QKeyEvent * >( ev );
if ( keyEvent->matches( QKeySequence::MoveToPreviousLine ) &&
!ui.wordList->currentRow() )
{
ui.wordList->setCurrentRow( 0, QItemSelectionModel::Clear );
ui.translateLine->setFocus( Qt::ShortcutFocusReason );
return true;
}
if ( keyEvent->matches( QKeySequence::InsertParagraphSeparator ) &&
ui.wordList->selectedItems().size() )
{
if ( ui.searchPane->isFloating() )
activateWindow();
dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) ).focus();
return true;
}
// Handle typing events used to initiate new lookups
if ( keyEvent->modifiers() &
( Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier ) )
return false; // A non-typing modifier is pressed
if ( keyEvent->key() == Qt::Key_Space ||
keyEvent->key() == Qt::Key_Backspace ||
keyEvent->key() == Qt::Key_Tab )
return false; // Those key have other uses than to start typing
// or don't make sense
QString text = keyEvent->text();
if ( text.size() )
{
typingEvent( text );
return true;
}
}
}
else
return QMainWindow::eventFilter( obj, ev );
return false;
}
void MainWindow::wordListItemActivated( QListWidgetItem * item )
{
showTranslationFor( item->text() );
}
void MainWindow::wordListSelectionChanged()
{
QList< QListWidgetItem * > selected = ui.wordList->selectedItems();
if ( selected.size() )
wordListItemActivated( selected.front() );
}
void MainWindow::openLinkInNewTab( QUrl const & url,
QUrl const & referrer,
QString const & fromArticle,
ArticleView::Contexts const & contexts )
{
createNewTab( !cfg.preferences.newTabsOpenInBackground, "" )->
openLink( url, referrer, fromArticle, contexts );
}
void MainWindow::showDefinitionInNewTab( QString const & word,
unsigned group,
QString const & fromArticle,
ArticleView::Contexts const & contexts )
{
createNewTab( !cfg.preferences.newTabsOpenInBackground, word )->
showDefinition( word, group, fromArticle, contexts );
}
void MainWindow::typingEvent( QString const & t )
{
if ( t == "\n" || t == "\r" )
focusTranslateLine();
else
{
if ( ui.searchPane->isFloating() )
ui.searchPane->activateWindow();
ui.translateLine->setText( t );
ui.translateLine->setFocus();
ui.translateLine->setCursorPosition( t.size() );
}
}
void MainWindow::mutedDictionariesChanged()
{
if ( dictionaryBar.toggleViewAction()->isChecked() )
applyMutedDictionariesState();
}
void MainWindow::showTranslationFor( QString const & inWord,
unsigned inGroup )
{
ArticleView & view =
dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) );
navPronounce->setEnabled( false );
unsigned group = inGroup ? inGroup :
( groupInstances.empty() ? 0 :
groupInstances[ groupList.currentIndex() ].id );
view.showDefinition( inWord, group );
updatePronounceAvailability();
// Add to history
history.addItem( History::Item( group, inWord.trimmed() ) );
history.save();
#if 0
QUrl req;
req.setScheme( "gdlookup" );
req.setHost( "localhost" );
req.addQueryItem( "word", inWord );
req.addQueryItem( "group",
cfg.groups.empty() ? "" :
groupInstances[ groupList.currentIndex() ].name );
ui.definition->load( req );
return;
#endif
#if 0
wstring word = inWord.trimmed().toStdWString();
// Where to look?
vector< sptr< Dictionary::Class > > const & activeDicts = getActiveDicts();
// Accumulate main forms
vector< wstring > alts;
{
set< wstring > altsSet;
for( unsigned x = 0; x < activeDicts.size(); ++x )
{
vector< wstring > found = activeDicts[ x ]->findHeadwordsForSynonym( word );
altsSet.insert( found.begin(), found.end() );
}
alts.insert( alts.begin(), altsSet.begin(), altsSet.end() );
}
for( unsigned x = 0; x < alts.size(); ++x )
{
printf( "Alt: %ls\n", alts[ x ].c_str() );
}
string result =
"<html><head>"
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">";
QFile cssFile( Config::getUserCssFileName() );
if ( cssFile.open( QFile::ReadOnly ) )
{
result += "<style type=\"text/css\">\n";
result += cssFile.readAll().data();
result += "</style>\n";
}
result += "</head><body>";
for( unsigned x = 0; x < activeDicts.size(); ++x )
{
try
{
string body = activeDicts[ x ]->getArticle( word, alts );
printf( "From %s: %s\n", activeDicts[ x ]->getName().c_str(), body.c_str() );
result += "<div class=\"gddictname\">From " + activeDicts[ x ]->getName() + "</div>" + body;
}
catch( Dictionary::exNoSuchWord & )
{
continue;
}
}
result += "</body></html>";
ArticleMaker am( dictionaries, groupInstances );
string result = am.makeDefinitionFor( inWord, "En" );
ui.definition->setContent( result.c_str(), QString() );
#endif
//ui.tabWidget->setTabText( ui.tabWidget->indexOf(ui.tab), inWord.trimmed() );
}
void MainWindow::toggleMainWindow( bool onlyShow )
{
bool shown = false;
if ( !isVisible() )
{
show();
activateWindow();
raise();
shown = true;
}
else
if ( isMinimized() )
{
showNormal();
activateWindow();
raise();
shown = true;
}
else
if ( !isActiveWindow() )
{
activateWindow();
raise();
shown = true;
}
else
if ( !onlyShow )
hide();
if ( shown )
focusTranslateLine();
}
void MainWindow::installHotKeys()
{
hotkeyWrapper.reset(); // Remove the old one
if ( cfg.preferences.enableMainWindowHotkey ||
cfg.preferences.enableClipboardHotkey )
{
try
{
hotkeyWrapper = new HotkeyWrapper( this );
}
catch( HotkeyWrapper::exInit & )
{
QMessageBox::critical( this, tr( "GoldenDict" ),
tr( "Failed to initialize hotkeys monitoring mechanism.<br>"
"Make sure your XServer has RECORD extension turned on." ) );
return;
}
if ( cfg.preferences.enableMainWindowHotkey )
hotkeyWrapper->setGlobalKey( cfg.preferences.mainWindowHotkey.key1,
cfg.preferences.mainWindowHotkey.key2,
cfg.preferences.mainWindowHotkey.modifiers,
0 );
if ( cfg.preferences.enableClipboardHotkey && scanPopup.get() )
{
hotkeyWrapper->setGlobalKey( cfg.preferences.clipboardHotkey.key1,
cfg.preferences.clipboardHotkey.key2,
cfg.preferences.clipboardHotkey.modifiers,
1 );
}
connect( hotkeyWrapper.get(), SIGNAL( hotkeyActivated( int ) ),
this, SLOT( hotKeyActivated( int ) ) );
}
}
void MainWindow::hotKeyActivated( int hk )
{
if ( !hk )
on_actionCloseToTray_activated();
else
if ( scanPopup.get() )
scanPopup->translateWordFromClipboard();
}
void MainWindow::prepareNewReleaseChecks()
{
if ( cfg.preferences.checkForNewReleases )
{
QDateTime now = QDateTime::currentDateTime();
if ( !cfg.timeForNewReleaseCheck.isValid() ||
now.daysTo( cfg.timeForNewReleaseCheck ) > 2 )
{
// The date is invalid, or the check is set to happen more than 2 days
// in the future -- fix that.
cfg.timeForNewReleaseCheck = now.addDays( 2 );
}
int secsToCheck = now.secsTo( cfg.timeForNewReleaseCheck );
if ( secsToCheck < 1 )
secsToCheck = 1;
newReleaseCheckTimer.setSingleShot( true );
newReleaseCheckTimer.start( secsToCheck * 1000 );
}
else
newReleaseCheckTimer.stop(); // In case it was started before
}
void MainWindow::checkForNewRelease()
{
latestReleaseReply.reset();
QNetworkRequest req(
QUrl( "http://goldendict.berlios.de/latest_release.php?current="
PROGRAM_VERSION "&platform="
#ifdef Q_WS_X11
"x11"
#endif
#ifdef Q_WS_MAC
"mac"
#endif
#ifdef Q_WS_QWS
"qws"
#endif
#ifdef Q_WS_WIN
"win"
#endif
) );
latestReleaseReply = articleNetMgr.get( req );
connect( latestReleaseReply.get(), SIGNAL( finished() ),
this, SLOT( latestReleaseReplyReady() ), Qt::QueuedConnection );
}
void MainWindow::latestReleaseReplyReady()
{
if ( !latestReleaseReply.get() )
return; // Some stray signal
bool success = false;
QString latestVersion, downloadUrl;
// See if we succeeded
if ( latestReleaseReply->error() == QNetworkReply::NoError )
{
QString latestReleaseInfo = QString::fromUtf8( latestReleaseReply->readLine() ).trimmed();
QStringList parts = latestReleaseInfo.split( ' ' );
if ( parts.size() == 2 )
{
latestVersion = parts[ 0 ];
downloadUrl = parts[ 1 ];
success = true;
}
}
latestReleaseReply.reset();
if ( !success )
{
// Failed -- reschedule to check in two hours
newReleaseCheckTimer.start( 2 * 60 * 60 * 1000 );
printf( "Failed to check program version, retry in two hours\n" );
}
else
{
// Success -- reschedule for a normal check and save config
cfg.timeForNewReleaseCheck = QDateTime();
prepareNewReleaseChecks();
Config::save( cfg );
printf( "Program version's check successful, current version is %ls\n",
latestVersion.toStdWString().c_str() );
}
if ( success && latestVersion > PROGRAM_VERSION && latestVersion != cfg.skippedRelease )
{
QMessageBox msg( QMessageBox::Information,
tr( "New Release Available" ),
tr( "Version <b>%1</b> of GoldenDict is now available for download.<br>"
"Click <b>Download</b> to get to the download page." ).arg( latestVersion ),
QMessageBox::NoButton,
this );
QPushButton * dload = msg.addButton( tr( "Download" ), QMessageBox::AcceptRole );
QPushButton * skip = msg.addButton( tr( "Skip This Release" ), QMessageBox::DestructiveRole );
msg.addButton( QMessageBox::Cancel );
msg.exec();
if ( msg.clickedButton() == dload )
QDesktopServices::openUrl( QUrl( downloadUrl ) );
else
if ( msg.clickedButton() == skip )
{
cfg.skippedRelease = latestVersion;
Config::save( cfg );
}
}
}
void MainWindow::trayIconActivated( QSystemTrayIcon::ActivationReason r )
{
switch(r) {
case QSystemTrayIcon::Trigger:
// Left click toggles the visibility of main window
toggleMainWindow();
break;
case QSystemTrayIcon::MiddleClick:
// Middle mouse click on Tray translates selection
// it is functional like as stardict
if ( scanPopup.get() ) {
scanPopup->translateWordFromSelection();
}
break;
default:
break;
}
}
void MainWindow::scanEnableToggled( bool on )
{
if ( !cfg.preferences.enableScanPopup )
return;
if ( scanPopup )
{
if ( on )
scanPopup->enableScanning();
else
scanPopup->disableScanning();
}
updateTrayIcon();
}
void MainWindow::showMainWindow()
{
toggleMainWindow( true );
}
void MainWindow::visitHomepage()
{
QDesktopServices::openUrl( QUrl( "http://goldendict.berlios.de/" ) );
}
void MainWindow::visitForum()
{
QDesktopServices::openUrl( QUrl( "http://goldendict.berlios.de/forum/" ) );
}
void MainWindow::showAbout()
{
QDialog about( this );
Ui::About ui;
ui.setupUi( &about );
ui.version->setText( PROGRAM_VERSION );
about.show();
about.exec();
}
void MainWindow::showDictBarNamesTriggered()
{
bool show = showDictBarNamesAction.isChecked();
dictionaryBar.setToolButtonStyle( show ? Qt::ToolButtonTextBesideIcon :
Qt::ToolButtonIconOnly );
cfg.showingDictBarNames = show;
}
void MainWindow::historyChanged()
{
// Rebuild history menu
ui.menuHistory->clear();
QList< History::Item > const & items = history.getItems();
for( int x = 0; x < items.size(); ++x )
{
History::Item const * i = &items[ x ];
Instances::Group const * group = groupInstances.findGroup( i->groupId );
QIcon icon = group ? group->makeIcon() : QIcon();
QAction * action = ui.menuHistory->addAction( icon, i->word );
action->setData( x );
}
ui.menuHistory->addSeparator();
ui.menuHistory->addAction( ui.clearHistory );
ui.clearHistory->setEnabled( items.size() );
}
void MainWindow::menuHistoryTriggered( QAction * action )
{
if ( action->data().type() != QVariant::Int )
return; // Not the item we added
int index = action->data().toInt();
QList< History::Item > const & items = history.getItems();
if ( index < 0 || index >= items.size() )
return; // Something's not right
History::Item const & item = items[ index ];
showTranslationFor( item.word, item.groupId );
}
void MainWindow::on_clearHistory_activated()
{
history.clear();
}
void MainWindow::setAutostart(bool autostart)
{
#ifdef Q_OS_WIN32
QSettings reg("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run",
QSettings::NativeFormat);
if (autostart) {
QString app_fname = QString("\"%1\"").arg( QCoreApplication::applicationFilePath() );
app_fname.replace("/", "\\");
reg.setValue(QCoreApplication::applicationName(), app_fname);
}
else {
reg.remove(QCoreApplication::applicationName());
}
reg.sync();
#else
// this is for KDE
QString app_fname = QFileInfo(QCoreApplication::applicationFilePath()).baseName();
QString lnk(QDir::homePath()+"/.kde/Autostart/"+app_fname);
if (autostart) {
QFile f(QCoreApplication::applicationFilePath());
f.link(lnk);
} else {
QFile::remove(lnk);
}
#endif
}
void MainWindow::on_actionCloseToTray_activated()
{
toggleMainWindow( !cfg.preferences.enableTrayIcon );
}
void MainWindow::on_pageSetup_activated()
{
if ( getPrinter().isValid() )
{
QPageSetupDialog dialog( &getPrinter(), this );
dialog.exec();
}
else
QMessageBox::critical( this, tr( "Page Setup" ),
tr( "No printer is available. Please install one first." ) );
}
void MainWindow::on_printPreview_activated()
{
QPrintPreviewDialog dialog( &getPrinter(), this );
connect( &dialog, SIGNAL( paintRequested( QPrinter * ) ),
this, SLOT( printPreviewPaintRequested( QPrinter * ) ) );
dialog.exec();
}
void MainWindow::on_print_activated()
{
QPrintDialog dialog( &getPrinter(), this );
dialog.setWindowTitle( tr( "Print Article") );
if ( dialog.exec() != QDialog::Accepted )
return;
ArticleView & view = dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) );
view.print( &getPrinter() );
}
void MainWindow::printPreviewPaintRequested( QPrinter * printer )
{
ArticleView & view = dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) );
view.print( printer );
}
void MainWindow::on_saveArticle_activated()
{
ArticleView & view = dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) );
QFileDialog fileDialog( this, tr( "Save Article As" ), QString(), tr( "Html files (*.html *.htm)" ) );
fileDialog.setAcceptMode( QFileDialog::AcceptSave );
fileDialog.setDefaultSuffix( "html" );
fileDialog.selectFile( view.getTitle() + ".html" );
if ( fileDialog.exec() && fileDialog.selectedFiles().size() == 1 )
{
QString fileName = fileDialog.selectedFiles().front();
QFile file( fileName );
if ( !file.open( QIODevice::WriteOnly ) )
QMessageBox::critical( this, tr( "Error" ),
tr( "Can't save article: %1" ).arg( file.errorString() ) );
else
file.write( view.toHtml().toUtf8() );
}
}
void MainWindow::on_rescanFiles_activated()
{
hotkeyWrapper.reset(); // No hotkeys while we're editing dictionaries
scanPopup.reset(); // No scan popup either. No one should use dictionaries.
groupInstances.clear(); // Release all the dictionaries they hold
loadDictionaries( this, true, cfg, dictionaries, dictNetMgr );
updateGroupList();
makeScanPopup();
installHotKeys();
}
void MainWindow::zoomin()
{
cfg.preferences.zoomFactor += 0.1;
applyZoomFactor();
}
void MainWindow::zoomout()
{
cfg.preferences.zoomFactor -= 0.1;
applyZoomFactor();
}
void MainWindow::unzoom()
{
cfg.preferences.zoomFactor = 1;
applyZoomFactor();
}
void MainWindow::applyZoomFactor()
{
if ( cfg.preferences.zoomFactor >= 3 )
cfg.preferences.zoomFactor = 3;
else if ( cfg.preferences.zoomFactor <= 0.7 )
cfg.preferences.zoomFactor = 0.7;
zoomIn->setEnabled( cfg.preferences.zoomFactor < 3 );
zoomOut->setEnabled( cfg.preferences.zoomFactor > 0.7 );
zoomBase->setEnabled( cfg.preferences.zoomFactor != 1.0 );
for ( int i = 0; i < ui.tabWidget->count(); i++ )
{
ArticleView & view =
dynamic_cast< ArticleView & >( *( ui.tabWidget->widget(i) ) );
view.setZoomFactor( cfg.preferences.zoomFactor );
}
if ( scanPopup.get() )
scanPopup->applyZoomFactor();
}