2009-02-05 14:21:47 +00:00
|
|
|
/* This file is (c) 2008-2009 Konstantin Isakov <ikm@users.berlios.de>
|
2009-01-28 20:55:45 +00:00
|
|
|
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
|
|
|
|
|
|
|
#include "mainwindow.hh"
|
|
|
|
#include "sources.hh"
|
|
|
|
#include "groups.hh"
|
2009-02-05 20:55:00 +00:00
|
|
|
#include "preferences.hh"
|
2009-01-28 20:55:45 +00:00
|
|
|
#include "bgl.hh"
|
|
|
|
#include "stardict.hh"
|
|
|
|
#include "lsa.hh"
|
|
|
|
#include "dsl.hh"
|
2009-03-26 19:00:08 +00:00
|
|
|
#include "mediawiki.hh"
|
2009-02-08 14:02:27 +00:00
|
|
|
#include "ui_about.h"
|
2009-01-28 20:55:45 +00:00
|
|
|
#include <QDir>
|
|
|
|
#include <QMessageBox>
|
|
|
|
#include <QIcon>
|
|
|
|
#include <QToolBar>
|
2009-02-05 20:55:00 +00:00
|
|
|
#include <QCloseEvent>
|
2009-02-08 14:02:27 +00:00
|
|
|
#include <QDesktopServices>
|
2009-01-28 20:55:45 +00:00
|
|
|
#include <set>
|
|
|
|
#include <map>
|
|
|
|
|
|
|
|
using std::set;
|
|
|
|
using std::wstring;
|
|
|
|
using std::map;
|
|
|
|
using std::pair;
|
|
|
|
|
|
|
|
MainWindow::MainWindow():
|
2009-02-05 20:55:00 +00:00
|
|
|
trayIcon( 0 ),
|
2009-02-08 20:20:02 +00:00
|
|
|
trayIconMenu( this ),
|
2009-01-28 20:55:45 +00:00
|
|
|
addTab( this ),
|
|
|
|
cfg( Config::load() ),
|
|
|
|
articleMaker( dictionaries, groupInstances ),
|
|
|
|
articleNetMgr( this, dictionaries, articleMaker ),
|
2009-03-26 19:00:08 +00:00
|
|
|
dictNetMgr( this ),
|
2009-01-29 19:16:25 +00:00
|
|
|
wordFinder( this ),
|
2009-01-28 20:55:45 +00:00
|
|
|
initializing( 0 )
|
|
|
|
{
|
|
|
|
ui.setupUi( this );
|
|
|
|
|
|
|
|
// Make the toolbar
|
|
|
|
navToolbar = addToolBar( tr( "Navigation" ) );
|
|
|
|
navBack = navToolbar->addAction( QIcon( ":/icons/previous.png" ), tr( "Back" ) );
|
|
|
|
navForward = navToolbar->addAction( QIcon( ":/icons/next.png" ), tr( "Forward" ) );
|
2009-02-08 20:20:02 +00:00
|
|
|
|
|
|
|
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 ) ) );
|
|
|
|
|
|
|
|
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() ) );
|
|
|
|
|
|
|
|
// Show tray icon early so the user would be happy
|
|
|
|
updateTrayIcon();
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-02-08 20:20:02 +00:00
|
|
|
if ( trayIcon )
|
|
|
|
trayIcon->setToolTip( tr( "Loading..." ) );
|
|
|
|
|
2009-01-28 20:55:45 +00:00
|
|
|
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 );
|
|
|
|
|
|
|
|
ui.tabWidget->setMovable( true );
|
2009-02-09 01:08:52 +00:00
|
|
|
#ifndef Q_OS_WIN32
|
2009-01-28 20:55:45 +00:00
|
|
|
ui.tabWidget->setDocumentMode( true );
|
2009-02-09 01:08:52 +00:00
|
|
|
#endif
|
2009-01-28 20:55:45 +00:00
|
|
|
|
|
|
|
connect( &addTab, SIGNAL( clicked() ),
|
|
|
|
this, SLOT( addNewTab() ) );
|
|
|
|
|
|
|
|
connect( ui.tabWidget, SIGNAL( tabCloseRequested( int ) ),
|
|
|
|
this, SLOT( tabCloseRequested( int ) ) );
|
|
|
|
|
|
|
|
ui.tabWidget->setTabsClosable( true );
|
|
|
|
|
2009-02-05 20:55:00 +00:00
|
|
|
connect( ui.quit, SIGNAL( activated() ),
|
|
|
|
qApp, SLOT( quit() ) );
|
|
|
|
|
2009-01-28 20:55:45 +00:00
|
|
|
connect( ui.sources, SIGNAL( activated() ),
|
|
|
|
this, SLOT( editSources() ) );
|
|
|
|
|
|
|
|
connect( ui.groups, SIGNAL( activated() ),
|
|
|
|
this, SLOT( editGroups() ) );
|
|
|
|
|
2009-02-05 20:55:00 +00:00
|
|
|
connect( ui.preferences, SIGNAL( activated() ),
|
|
|
|
this, SLOT( editPreferences() ) );
|
2009-02-08 14:02:27 +00:00
|
|
|
|
|
|
|
connect( ui.visitHomepage, SIGNAL( activated() ),
|
|
|
|
this, SLOT( visitHomepage() ) );
|
|
|
|
connect( ui.visitForum, SIGNAL( activated() ),
|
|
|
|
this, SLOT( visitForum() ) );
|
|
|
|
connect( ui.about, SIGNAL( activated() ),
|
|
|
|
this, SLOT( showAbout() ) );
|
2009-02-05 20:55:00 +00:00
|
|
|
|
2009-02-06 17:04:11 +00:00
|
|
|
connect( ui.groupList, SIGNAL( currentIndexChanged( QString const & ) ),
|
|
|
|
this, SLOT( currentGroupChanged( QString const & ) ) );
|
|
|
|
|
2009-01-28 20:55:45 +00:00
|
|
|
connect( ui.translateLine, SIGNAL( textChanged( QString const & ) ),
|
|
|
|
this, SLOT( translateInputChanged( QString const & ) ) );
|
2009-02-08 14:02:27 +00:00
|
|
|
|
|
|
|
connect( ui.translateLine, SIGNAL( returnPressed() ),
|
|
|
|
this, SLOT( translateInputFinished() ) );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-02-08 14:02:27 +00:00
|
|
|
connect( ui.wordList, SIGNAL( itemSelectionChanged() ),
|
|
|
|
this, SLOT( wordListSelectionChanged() ) );
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
connect( &wordFinder, SIGNAL( updated() ),
|
|
|
|
this, SLOT( prefixMatchUpdated() ) );
|
|
|
|
connect( &wordFinder, SIGNAL( finished() ),
|
|
|
|
this, SLOT( prefixMatchFinished() ) );
|
2009-01-29 19:16:25 +00:00
|
|
|
|
2009-03-29 17:38:54 +00:00
|
|
|
ui.translateLine->installEventFilter( this );
|
|
|
|
ui.wordList->installEventFilter( this );
|
|
|
|
|
2009-01-28 20:55:45 +00:00
|
|
|
makeDictionaries();
|
|
|
|
|
|
|
|
addNewTab();
|
2009-01-29 19:16:25 +00:00
|
|
|
|
2009-02-08 18:35:29 +00:00
|
|
|
// Show the initial welcome text
|
|
|
|
|
|
|
|
{
|
|
|
|
ArticleView & view =
|
|
|
|
dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) );
|
|
|
|
|
|
|
|
view.showDefinition( "Welcome!", "internal:about" );
|
|
|
|
}
|
|
|
|
|
2009-01-29 19:16:25 +00:00
|
|
|
ui.translateLine->setFocus();
|
2009-02-05 20:55:00 +00:00
|
|
|
|
|
|
|
updateTrayIcon();
|
|
|
|
|
|
|
|
// Only show window initially if it wasn't configured differently
|
|
|
|
if ( !cfg.preferences.enableTrayIcon || !cfg.preferences.startToTray )
|
|
|
|
show();
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
|
|
|
|
2009-02-06 17:04:11 +00:00
|
|
|
MainWindow::~MainWindow()
|
|
|
|
{
|
|
|
|
// Save any changes in last chosen groups etc
|
|
|
|
Config::save( cfg );
|
|
|
|
}
|
|
|
|
|
2009-03-28 22:37:03 +00:00
|
|
|
LoadDictionaries::LoadDictionaries( Config::Paths const & paths_ ):
|
|
|
|
paths( paths_ )
|
2009-01-28 20:55:45 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void LoadDictionaries::run()
|
|
|
|
{
|
2009-03-29 13:34:39 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
for( Config::Paths::const_iterator i = paths.begin(); i != paths.end(); ++i )
|
|
|
|
handlePath( *i );
|
|
|
|
}
|
|
|
|
catch( std::exception & e )
|
|
|
|
{
|
|
|
|
exceptionText = e.what();
|
|
|
|
}
|
2009-03-28 22:37:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void LoadDictionaries::handlePath( Config::Path const & path )
|
|
|
|
{
|
|
|
|
vector< string > allFiles;
|
|
|
|
|
|
|
|
QDir dir( path.path );
|
|
|
|
|
|
|
|
QFileInfoList entries = dir.entryInfoList( QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot );
|
|
|
|
|
|
|
|
for( QFileInfoList::const_iterator i = entries.constBegin();
|
|
|
|
i != entries.constEnd(); ++i )
|
|
|
|
{
|
|
|
|
QString fullName = i->canonicalFilePath();
|
|
|
|
|
|
|
|
if ( path.recursive && i->isDir() )
|
|
|
|
handlePath( Config::Path( fullName, true ) );
|
|
|
|
|
|
|
|
allFiles.push_back( QDir::toNativeSeparators( fullName ).toLocal8Bit().data() );
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
vector< sptr< Dictionary::Class > > bglDictionaries =
|
|
|
|
Bgl::makeDictionaries( allFiles, Config::getIndexDir().toLocal8Bit().data(), *this );
|
|
|
|
|
|
|
|
dictionaries.insert( dictionaries.end(), bglDictionaries.begin(),
|
|
|
|
bglDictionaries.end() );
|
|
|
|
}
|
|
|
|
|
2009-01-28 20:55:45 +00:00
|
|
|
{
|
|
|
|
vector< sptr< Dictionary::Class > > stardictDictionaries =
|
2009-03-26 19:00:08 +00:00
|
|
|
Stardict::makeDictionaries( allFiles, Config::getIndexDir().toLocal8Bit().data(), *this );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
|
|
|
dictionaries.insert( dictionaries.end(), stardictDictionaries.begin(),
|
|
|
|
stardictDictionaries.end() );
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
vector< sptr< Dictionary::Class > > lsaDictionaries =
|
2009-03-26 19:00:08 +00:00
|
|
|
Lsa::makeDictionaries( allFiles, Config::getIndexDir().toLocal8Bit().data(), *this );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
|
|
|
dictionaries.insert( dictionaries.end(), lsaDictionaries.begin(),
|
|
|
|
lsaDictionaries.end() );
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
vector< sptr< Dictionary::Class > > dslDictionaries =
|
2009-03-26 19:00:08 +00:00
|
|
|
Dsl::makeDictionaries( allFiles, Config::getIndexDir().toLocal8Bit().data(), *this );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
|
|
|
dictionaries.insert( dictionaries.end(), dslDictionaries.begin(),
|
|
|
|
dslDictionaries.end() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void LoadDictionaries::indexingDictionary( string const & dictionaryName ) throw()
|
|
|
|
{
|
|
|
|
emit indexingDictionarySignal( QString::fromUtf8( dictionaryName.c_str() ) );
|
|
|
|
}
|
|
|
|
|
2009-02-05 20:55:00 +00:00
|
|
|
void MainWindow::updateTrayIcon()
|
|
|
|
{
|
|
|
|
if ( !trayIcon && cfg.preferences.enableTrayIcon )
|
|
|
|
{
|
|
|
|
// Need to show it
|
|
|
|
trayIcon = new QSystemTrayIcon( QIcon( ":/icons/programicon.png" ), this );
|
2009-02-08 20:20:02 +00:00
|
|
|
trayIcon->setContextMenu( &trayIconMenu );
|
2009-02-05 20:55:00 +00:00
|
|
|
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 )
|
2009-02-08 20:20:02 +00:00
|
|
|
{
|
|
|
|
// Update the icon to reflect the scanning mode
|
|
|
|
trayIcon->setIcon( QIcon(
|
|
|
|
enableScanPopup->isChecked() ?
|
|
|
|
":/icons/programicon_scan.png" :
|
|
|
|
":/icons/programicon.png" ) );
|
|
|
|
|
2009-02-05 20:55:00 +00:00
|
|
|
trayIcon->setToolTip( "GoldenDict" );
|
2009-02-08 20:20:02 +00:00
|
|
|
}
|
2009-02-05 20:55:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::closeEvent( QCloseEvent * ev )
|
|
|
|
{
|
|
|
|
if ( cfg.preferences.enableTrayIcon && cfg.preferences.closeToTray )
|
|
|
|
{
|
|
|
|
ev->ignore();
|
|
|
|
hide();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
ev->accept();
|
|
|
|
}
|
|
|
|
|
2009-01-28 20:55:45 +00:00
|
|
|
void MainWindow::makeDictionaries()
|
|
|
|
{
|
2009-03-26 19:00:08 +00:00
|
|
|
scanPopup.reset();
|
|
|
|
|
|
|
|
wordFinder.clear();
|
|
|
|
|
|
|
|
dictionaries.clear();
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-29 13:34:39 +00:00
|
|
|
::Initializing init( this, isVisible() );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
initializing = &init;
|
|
|
|
|
2009-03-28 22:37:03 +00:00
|
|
|
// Start a thread to load all the dictionaries
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-28 22:37:03 +00:00
|
|
|
LoadDictionaries loadDicts( cfg.paths );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
|
|
|
connect( &loadDicts, SIGNAL( indexingDictionarySignal( QString ) ),
|
|
|
|
this, SLOT( indexingDictionary( QString ) ) );
|
|
|
|
|
|
|
|
QEventLoop localLoop;
|
|
|
|
|
|
|
|
connect( &loadDicts, SIGNAL( finished() ),
|
|
|
|
&localLoop, SLOT( quit() ) );
|
|
|
|
|
|
|
|
loadDicts.start();
|
|
|
|
|
|
|
|
localLoop.exec();
|
|
|
|
|
|
|
|
loadDicts.wait();
|
|
|
|
|
2009-03-29 13:34:39 +00:00
|
|
|
if ( loadDicts.getExceptionText().size() )
|
|
|
|
{
|
|
|
|
initializing = 0;
|
|
|
|
|
|
|
|
QMessageBox::critical( this, tr( "Error loading dictionaries" ),
|
|
|
|
QString::fromUtf8( loadDicts.getExceptionText().c_str() ) );
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
dictionaries = loadDicts.getDictionaries();
|
|
|
|
|
|
|
|
///// We create MediaWiki dicts syncronously, since they use netmgr
|
|
|
|
|
2009-01-29 19:16:25 +00:00
|
|
|
{
|
2009-03-26 19:00:08 +00:00
|
|
|
vector< sptr< Dictionary::Class > > dicts =
|
2009-03-28 22:37:03 +00:00
|
|
|
MediaWiki::makeDictionaries( loadDicts, cfg.mediawikis, dictNetMgr );
|
2009-01-29 19:16:25 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
dictionaries.insert( dictionaries.end(), dicts.begin(), dicts.end() );
|
2009-01-29 19:16:25 +00:00
|
|
|
}
|
2009-01-28 20:55:45 +00:00
|
|
|
|
|
|
|
initializing = 0;
|
|
|
|
|
|
|
|
// Remove any stale index files
|
|
|
|
|
|
|
|
set< string > ids;
|
|
|
|
|
|
|
|
for( unsigned x = dictionaries.size(); x--; )
|
|
|
|
ids.insert( dictionaries[ x ]->getId() );
|
|
|
|
|
|
|
|
QDir indexDir( Config::getIndexDir() );
|
|
|
|
|
|
|
|
QStringList allIdxFiles = indexDir.entryList( QDir::Files );
|
|
|
|
|
|
|
|
for( QStringList::const_iterator i = allIdxFiles.constBegin();
|
|
|
|
i != allIdxFiles.constEnd(); ++i )
|
|
|
|
{
|
|
|
|
if ( ids.find( i->toLocal8Bit().data() ) == ids.end() &&
|
|
|
|
i->size() == 32 )
|
|
|
|
indexDir.remove( *i );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch( ... )
|
|
|
|
{
|
|
|
|
initializing = 0;
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
|
|
|
|
updateStatusLine();
|
|
|
|
updateGroupList();
|
2009-02-01 00:08:08 +00:00
|
|
|
makeScanPopup();
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
ui.groupList->setVisible( haveGroups );
|
|
|
|
|
|
|
|
ui.groupLabel->setText( haveGroups ? tr( "Look up in:" ) : tr( "Look up:" ) );
|
|
|
|
|
2009-02-06 17:04:11 +00:00
|
|
|
// currentIndexChanged() signal is very trigger-happy. To avoid triggering
|
|
|
|
// it, we disconnect it while we're clearing and filling back groups.
|
|
|
|
disconnect( ui.groupList, SIGNAL( currentIndexChanged( QString const & ) ),
|
|
|
|
this, SLOT( currentGroupChanged( QString const & ) ) );
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
groupInstances.clear();
|
|
|
|
|
|
|
|
for( unsigned x = 0; x < cfg.groups.size(); ++x )
|
|
|
|
groupInstances.push_back( Instances::Group( cfg.groups[ x ], dictionaries ) );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-02-01 00:08:08 +00:00
|
|
|
ui.groupList->fill( groupInstances );
|
2009-02-06 17:04:11 +00:00
|
|
|
ui.groupList->setCurrentGroup( cfg.lastMainGroup );
|
|
|
|
|
|
|
|
connect( ui.groupList, SIGNAL( currentIndexChanged( QString const & ) ),
|
|
|
|
this, SLOT( currentGroupChanged( QString const & ) ) );
|
2009-02-01 00:08:08 +00:00
|
|
|
}
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-02-01 00:08:08 +00:00
|
|
|
void MainWindow::makeScanPopup()
|
|
|
|
{
|
|
|
|
scanPopup.reset();
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-02-08 20:20:02 +00:00
|
|
|
if ( !cfg.preferences.enableScanPopup )
|
|
|
|
return;
|
|
|
|
|
2009-02-05 20:55:00 +00:00
|
|
|
scanPopup = new ScanPopup( 0, cfg, articleNetMgr, dictionaries, groupInstances );
|
2009-02-08 20:20:02 +00:00
|
|
|
|
|
|
|
if ( enableScanPopup->isChecked() )
|
|
|
|
scanPopup->enableScanning();
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
vector< sptr< Dictionary::Class > > const & MainWindow::getActiveDicts()
|
|
|
|
{
|
|
|
|
if ( cfg.groups.empty() )
|
|
|
|
return dictionaries;
|
|
|
|
|
|
|
|
int current = ui.groupList->currentIndex();
|
|
|
|
|
|
|
|
if ( current < 0 || current >= (int) groupInstances.size() )
|
|
|
|
{
|
|
|
|
// This shouldn't ever happen
|
|
|
|
return dictionaries;
|
|
|
|
}
|
|
|
|
|
|
|
|
return groupInstances[ current ].dictionaries;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::indexingDictionary( QString dictionaryName )
|
|
|
|
{
|
|
|
|
if ( initializing )
|
|
|
|
initializing->indexing( dictionaryName );
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::addNewTab()
|
|
|
|
{
|
|
|
|
ArticleView * view = new ArticleView( this, articleNetMgr, groupInstances,
|
|
|
|
false );
|
|
|
|
|
|
|
|
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 & ) ) );
|
|
|
|
|
2009-02-08 15:49:17 +00:00
|
|
|
connect( view, SIGNAL( openLinkInNewTab( QUrl const &, QUrl const & ) ),
|
|
|
|
this, SLOT( openLinkInNewTab( QUrl const &, QUrl const & ) ) );
|
|
|
|
|
|
|
|
connect( view, SIGNAL( showDefinitionInNewTab( QString const &, QString const & ) ),
|
|
|
|
this, SLOT( showDefinitionInNewTab( QString const &, QString const & ) ) );
|
|
|
|
|
2009-01-28 20:55:45 +00:00
|
|
|
ui.tabWidget->addTab( view, tr( "(untitled)" ) );
|
|
|
|
|
|
|
|
ui.tabWidget->setCurrentIndex( ui.tabWidget->count() - 1 );
|
|
|
|
}
|
|
|
|
|
|
|
|
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::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 )
|
|
|
|
{
|
|
|
|
ui.tabWidget->setTabText( ui.tabWidget->indexOf( view ), title );
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::iconChanged( ArticleView * view, QIcon const & icon )
|
|
|
|
{
|
|
|
|
ui.tabWidget->setTabIcon( ui.tabWidget->indexOf( view ), icon );
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::editSources()
|
|
|
|
{
|
2009-03-26 19:00:08 +00:00
|
|
|
Sources src( this, cfg.paths, cfg.mediawikis );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
|
|
|
src.show();
|
|
|
|
|
|
|
|
if ( src.exec() == QDialog::Accepted )
|
|
|
|
{
|
|
|
|
cfg.paths = src.getPaths();
|
2009-03-26 19:00:08 +00:00
|
|
|
cfg.mediawikis = src.getMediaWikis();
|
2009-01-28 20:55:45 +00:00
|
|
|
|
|
|
|
makeDictionaries();
|
|
|
|
|
|
|
|
Config::save( cfg );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::editGroups()
|
|
|
|
{
|
2009-03-26 19:00:08 +00:00
|
|
|
Groups groups( this, dictionaries, cfg.groups );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
groups.show();
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
if ( groups.exec() == QDialog::Accepted )
|
|
|
|
{
|
|
|
|
cfg.groups = groups.getGroups();
|
|
|
|
|
|
|
|
Config::save( cfg );
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
2009-03-26 19:00:08 +00:00
|
|
|
else
|
|
|
|
return;
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
scanPopup.reset(); // It was holding group instances
|
2009-01-29 19:16:25 +00:00
|
|
|
updateGroupList();
|
2009-02-01 00:08:08 +00:00
|
|
|
makeScanPopup();
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
|
|
|
|
2009-02-05 20:55:00 +00:00
|
|
|
void MainWindow::editPreferences()
|
|
|
|
{
|
|
|
|
Preferences preferences( this, cfg.preferences );
|
|
|
|
|
|
|
|
preferences.show();
|
|
|
|
|
|
|
|
if ( preferences.exec() == QDialog::Accepted )
|
|
|
|
{
|
|
|
|
cfg.preferences = preferences.getPreferences();
|
2009-02-08 20:20:02 +00:00
|
|
|
|
|
|
|
enableScanPopup->setVisible( cfg.preferences.enableScanPopup );
|
|
|
|
|
|
|
|
if ( !cfg.preferences.enableScanPopup )
|
|
|
|
enableScanPopup->setChecked( false );
|
|
|
|
|
2009-02-05 20:55:00 +00:00
|
|
|
updateTrayIcon();
|
2009-02-08 20:20:02 +00:00
|
|
|
makeScanPopup();
|
2009-02-05 20:55:00 +00:00
|
|
|
Config::save( cfg );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-02-06 17:04:11 +00:00
|
|
|
void MainWindow::currentGroupChanged( QString const & gr )
|
|
|
|
{
|
|
|
|
cfg.lastMainGroup = gr;
|
|
|
|
|
|
|
|
// Update word search results
|
|
|
|
|
|
|
|
translateInputChanged( ui.translateLine->text() );
|
|
|
|
}
|
|
|
|
|
2009-01-29 19:16:25 +00:00
|
|
|
void MainWindow::translateInputChanged( QString const & newValue )
|
2009-01-28 20:55:45 +00:00
|
|
|
{
|
2009-03-26 19:00:08 +00:00
|
|
|
// 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();
|
|
|
|
|
2009-01-29 19:16:25 +00:00
|
|
|
QString req = newValue.trimmed();
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-01-29 19:16:25 +00:00
|
|
|
if ( !req.size() )
|
|
|
|
{
|
|
|
|
// An empty request always results in an empty result
|
2009-03-26 19:00:08 +00:00
|
|
|
wordFinder.cancel();
|
|
|
|
ui.wordList->clear();
|
|
|
|
ui.wordList->unsetCursor();
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
// Reset the noResults mark if it's on right now
|
|
|
|
if ( ui.translateLine->property( "noResults" ).toBool() )
|
|
|
|
{
|
|
|
|
ui.translateLine->setProperty( "noResults", false );
|
|
|
|
qApp->setStyleSheet( qApp->styleSheet() );
|
|
|
|
}
|
2009-01-28 20:55:45 +00:00
|
|
|
return;
|
2009-01-29 19:16:25 +00:00
|
|
|
}
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-01-29 19:16:25 +00:00
|
|
|
ui.wordList->setCursor( Qt::WaitCursor );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
wordFinder.prefixMatch( req, getActiveDicts() );
|
2009-01-29 19:16:25 +00:00
|
|
|
}
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-02-08 14:02:27 +00:00
|
|
|
void MainWindow::translateInputFinished()
|
|
|
|
{
|
2009-03-29 17:38:54 +00:00
|
|
|
QString word = ui.translateLine->text();
|
|
|
|
|
|
|
|
if ( word.size() )
|
|
|
|
showTranslationFor( word );
|
2009-02-08 14:02:27 +00:00
|
|
|
}
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
void MainWindow::prefixMatchUpdated()
|
2009-01-29 19:16:25 +00:00
|
|
|
{
|
2009-03-26 19:00:08 +00:00
|
|
|
updateMatchResults( false );
|
|
|
|
}
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
void MainWindow::prefixMatchFinished()
|
|
|
|
{
|
|
|
|
updateMatchResults( true );
|
|
|
|
}
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
void MainWindow::updateMatchResults( bool finished )
|
|
|
|
{
|
|
|
|
std::vector< QString > const & results = wordFinder.getPrefixMatchResults();
|
|
|
|
|
|
|
|
ui.wordList->setUpdatesEnabled( false );
|
|
|
|
|
|
|
|
for( unsigned x = 0; x < results.size(); ++x )
|
2009-01-28 20:55:45 +00:00
|
|
|
{
|
2009-01-29 19:16:25 +00:00
|
|
|
QListWidgetItem * i = ui.wordList->item( x );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-01-29 19:16:25 +00:00
|
|
|
if ( !i )
|
2009-03-26 19:00:08 +00:00
|
|
|
ui.wordList->addItem( results[ x ] );
|
2009-01-29 19:16:25 +00:00
|
|
|
else
|
2009-03-26 19:00:08 +00:00
|
|
|
if ( i->text() != results[ x ] )
|
|
|
|
i->setText( results[ x ] );
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
while ( ui.wordList->count() > (int) results.size() )
|
2009-01-28 20:55:45 +00:00
|
|
|
{
|
2009-01-29 19:16:25 +00:00
|
|
|
// Chop off any extra items that were there
|
|
|
|
QListWidgetItem * i = ui.wordList->takeItem( ui.wordList->count() - 1 );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-01-29 19:16:25 +00:00
|
|
|
if ( i )
|
|
|
|
delete i;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-01-29 19:16:25 +00:00
|
|
|
if ( ui.wordList->count() )
|
2009-02-06 17:04:11 +00:00
|
|
|
{
|
2009-01-29 19:16:25 +00:00
|
|
|
ui.wordList->scrollToItem( ui.wordList->item( 0 ), QAbstractItemView::PositionAtTop );
|
2009-02-06 17:04:11 +00:00
|
|
|
ui.wordList->setCurrentItem( 0, QItemSelectionModel::Clear );
|
|
|
|
}
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-01-29 19:16:25 +00:00
|
|
|
ui.wordList->setUpdatesEnabled( true );
|
2009-03-26 19:00:08 +00:00
|
|
|
|
|
|
|
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 );
|
|
|
|
qApp->setStyleSheet( qApp->styleSheet() );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !wordFinder.getErrorString().isEmpty() )
|
|
|
|
statusBar()->showMessage( tr( "WARNING: %1" ).arg( wordFinder.getErrorString() ) );
|
|
|
|
}
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
|
|
|
|
2009-03-29 17:38:54 +00:00
|
|
|
bool MainWindow::eventFilter( QObject * obj, QEvent * ev )
|
|
|
|
{
|
|
|
|
if ( obj == ui.translateLine )
|
|
|
|
{
|
|
|
|
if ( ev->type() == QEvent::KeyPress )
|
|
|
|
{
|
|
|
|
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 )
|
|
|
|
{
|
|
|
|
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() )
|
|
|
|
{
|
|
|
|
dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) ).focus();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return QMainWindow::eventFilter( obj, ev );
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2009-01-28 20:55:45 +00:00
|
|
|
void MainWindow::wordListItemActivated( QListWidgetItem * item )
|
|
|
|
{
|
|
|
|
showTranslationFor( item->text() );
|
|
|
|
}
|
|
|
|
|
2009-02-08 14:02:27 +00:00
|
|
|
void MainWindow::wordListSelectionChanged()
|
|
|
|
{
|
|
|
|
QList< QListWidgetItem * > selected = ui.wordList->selectedItems();
|
|
|
|
|
|
|
|
if ( selected.size() )
|
|
|
|
wordListItemActivated( selected.front() );
|
|
|
|
}
|
|
|
|
|
2009-02-08 15:49:17 +00:00
|
|
|
void MainWindow::openLinkInNewTab( QUrl const & url,
|
|
|
|
QUrl const & referrer )
|
|
|
|
{
|
|
|
|
addNewTab();
|
|
|
|
|
|
|
|
ArticleView & view =
|
|
|
|
dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) );
|
|
|
|
|
|
|
|
view.openLink( url, referrer );
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::showDefinitionInNewTab( QString const & word,
|
|
|
|
QString const & group )
|
|
|
|
{
|
|
|
|
addNewTab();
|
|
|
|
|
|
|
|
ArticleView & view =
|
|
|
|
dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) );
|
|
|
|
|
|
|
|
view.showDefinition( word, group );
|
|
|
|
}
|
|
|
|
|
2009-01-28 20:55:45 +00:00
|
|
|
void MainWindow::showTranslationFor( QString const & inWord )
|
|
|
|
{
|
|
|
|
ArticleView & view =
|
|
|
|
dynamic_cast< ArticleView & >( *( ui.tabWidget->currentWidget() ) );
|
|
|
|
|
|
|
|
view.showDefinition( inWord, cfg.groups.empty() ? "" :
|
|
|
|
groupInstances[ ui.groupList->currentIndex() ].name );
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
QUrl req;
|
|
|
|
|
|
|
|
req.setScheme( "gdlookup" );
|
|
|
|
req.setHost( "localhost" );
|
|
|
|
req.addQueryItem( "word", inWord );
|
|
|
|
req.addQueryItem( "group",
|
|
|
|
cfg.groups.empty() ? "" :
|
|
|
|
groupInstances[ ui.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() );
|
|
|
|
}
|
2009-02-05 20:55:00 +00:00
|
|
|
|
2009-02-08 20:20:02 +00:00
|
|
|
void MainWindow::trayIconActivated( QSystemTrayIcon::ActivationReason r )
|
|
|
|
{
|
|
|
|
if ( r == QSystemTrayIcon::DoubleClick )
|
|
|
|
{
|
|
|
|
// Double-click toggles the visibility of main window
|
|
|
|
if ( !isVisible() )
|
|
|
|
show();
|
|
|
|
else
|
|
|
|
if ( isMinimized() )
|
|
|
|
{
|
|
|
|
showNormal();
|
|
|
|
activateWindow();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
hide();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::scanEnableToggled( bool on )
|
|
|
|
{
|
|
|
|
if ( scanPopup )
|
|
|
|
{
|
|
|
|
if ( on )
|
|
|
|
scanPopup->enableScanning();
|
|
|
|
else
|
|
|
|
scanPopup->disableScanning();
|
|
|
|
}
|
|
|
|
|
|
|
|
updateTrayIcon();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::showMainWindow()
|
2009-02-05 20:55:00 +00:00
|
|
|
{
|
|
|
|
if ( !isVisible() )
|
|
|
|
show();
|
2009-02-06 17:04:11 +00:00
|
|
|
else
|
|
|
|
if ( isMinimized() )
|
|
|
|
{
|
|
|
|
showNormal();
|
|
|
|
activateWindow();
|
|
|
|
}
|
2009-02-05 20:55:00 +00:00
|
|
|
}
|
|
|
|
|
2009-02-08 14:02:27 +00:00
|
|
|
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 );
|
|
|
|
|
|
|
|
about.show();
|
|
|
|
about.exec();
|
|
|
|
}
|
|
|
|
|