#include "configpluginspage.h" #include "config.h" #include "convertpluginloader.h" #include "replaygainpluginloader.h" #include "ripperpluginloader.h" #include #include #include #include #include // #include // #include #include #include // #include #include #include #include #include #include #include #include #include //#include ConfigPluginsPage::ConfigPluginsPage( Config* _config, TQWidget* tqparent, const char* name ) : ConfigPageBase( tqparent, name ) { config = _config; // create an icon loader object for loading icons KIconLoader* iconLoader = new KIconLoader(); TQVBoxLayout* box = new TQVBoxLayout( tqparent, 0, 6 ); TQLabel* lPluginsLabel = new TQLabel( i18n("Installed plugins")+":", tqparent, "lPluginsLabel" ); box->addWidget( lPluginsLabel ); TQHBoxLayout* pluginsBox = new TQHBoxLayout( box ); lPlugins = new KListBox( tqparent, "lPlugins" ); pluginsBox->addWidget(lPlugins); connect( lPlugins, TQT_SIGNAL(highlighted(int)), this, TQT_SLOT(pluginsSelectionChanged(int)) ); refreshPlugins(); TQVBoxLayout* pluginsRightBox = new TQVBoxLayout( pluginsBox ); pAddPlugin = new KPushButton( iconLoader->loadIcon("add",KIcon::Small), i18n("Add ..."), tqparent, "pAddPlugin" ); pluginsRightBox->addWidget( pAddPlugin ); connect( pAddPlugin, TQT_SIGNAL(clicked()), this, TQT_SLOT(getPlugin()) ); pRemovePlugin = new KPushButton( iconLoader->loadIcon("remove",KIcon::Small), i18n("Remove"), tqparent, "pRemovePlugin" ); pRemovePlugin->setEnabled( false ); pluginsRightBox->addWidget( pRemovePlugin ); connect( pRemovePlugin, TQT_SIGNAL(clicked()), this, TQT_SLOT(removePlugin()) ); pluginsRightBox->addStretch(); pAboutPlugin = new KPushButton( iconLoader->loadIcon("messagebox_info",KIcon::Small), i18n("About"), tqparent, "pAboutPlugin" ); pAboutPlugin->setEnabled( false ); pluginsRightBox->addWidget( pAboutPlugin ); connect( pAboutPlugin, TQT_SIGNAL(clicked()), this, TQT_SLOT(aboutPlugin()) ); /* NOTE kaligames.de is down box->addSpacing( 5 ); TQLabel* lOnlinePluginsLabel = new TQLabel( i18n("Available plugins")+":", tqparent, "lOnlinePluginsLabel" ); box->addWidget( lOnlinePluginsLabel ); TQHBoxLayout* onlinePluginsBox = new TQHBoxLayout( box ); lOnlinePlugins = new KListBox( tqparent, "lOnlinePlugins" ); onlinePluginsBox->addWidget( lOnlinePlugins ); connect( lOnlinePlugins, TQT_SIGNAL(highlighted(int)), this, TQT_SLOT(onlinePluginsSelectionChanged(int)) ); TQVBoxLayout* onlinePluginsRightBox = new TQVBoxLayout( onlinePluginsBox ); pRefreshOnlinePlugins = new KPushButton( iconLoader->loadIcon("reload",KIcon::Small), i18n("Refresh"), tqparent, "pRefreshOnlinePlugins" ); TQToolTip::add( pRefreshOnlinePlugins, i18n("Download the latest list of available plugins.") ); onlinePluginsRightBox->addWidget( pRefreshOnlinePlugins ); connect( pRefreshOnlinePlugins, TQT_SIGNAL(clicked()), this, TQT_SLOT(refreshOnlinePlugins()) ); // TODO upgrade button // pUpgradeOnlinePlugins = new KPushButton( iconLoader->loadIcon("filesave",KIcon::Small), i18n("Upgrade"), tqparent, "pUpgradeOnlinePlugins" ); // pUpgradeOnlinePlugins->setEnabled( false ); // TQToolTip::add( pUpgradeOnlinePlugins, i18n("Download all plugins and install them into the soundKonverter directory.") ); // onlinePluginsRightBox->addWidget( pUpgradeOnlinePlugins ); // connect(pInstallAllOnlinePlugins,TQT_SIGNAL(clicked()),this,TQT_SLOT(upgradeOnlinePlugins())); onlinePluginsRightBox->addStretch(); pInstallOnlinePlugin = new KPushButton( iconLoader->loadIcon("filesave",KIcon::Small), i18n("Install"), tqparent, "pInstallOnlinePlugin" ); pInstallOnlinePlugin->setEnabled( false ); TQToolTip::add( pInstallOnlinePlugin, i18n("Download the selected plugin and install it into the soundKonverter directory.") ); onlinePluginsRightBox->addWidget( pInstallOnlinePlugin ); connect( pInstallOnlinePlugin, TQT_SIGNAL(clicked()), this, TQT_SLOT(getOnlinePlugin()) ); pAboutOnlinePlugin = new KPushButton( iconLoader->loadIcon("messagebox_info",KIcon::Small), i18n("About"), tqparent, "pAboutOnlinePlugin" ); pAboutOnlinePlugin->setEnabled( false ); onlinePluginsRightBox->addWidget( pAboutOnlinePlugin ); connect( pAboutOnlinePlugin, TQT_SIGNAL(clicked()), this, TQT_SLOT(aboutOnlinePlugin()) ); cCheckOnlinePlugins = new TQCheckBox( i18n("Check for new plugins on every startup"), tqparent, "cCheckOnlinePlugins" ); cCheckOnlinePlugins->setChecked( config->data.plugins.checkForUpdates ); box->addWidget( cCheckOnlinePlugins ); connect( cCheckOnlinePlugins, TQT_SIGNAL(toggled(bool)), this, TQT_SLOT(cfgChanged()) ); // box->addStretch(); // delete the icon loader object delete iconLoader; if( config->data.plugins.checkForUpdates && config->onlinePluginsChanged ) { // NOTE copied from below TQString line; bool add; TQFile file( locateLocal("data","soundkonverter/pluginlist.txt") ); if( file.open(IO_ReadOnly) ) { TQTextStream stream( &file ); while( !stream.atEnd() ) { line = stream.readLine(); // line of text excluding '\n' line.tqreplace( "&", "&" ); line.tqreplace( "ä", "ä" ); line.tqreplace( "Ä", "Ä" ); line.tqreplace( "ö", "ö" ); line.tqreplace( "Ö", "Ö" ); line.tqreplace( "ü", "ü" ); line.tqreplace( "Ü", "Ü" ); line.tqreplace( "ß", "ß" ); add = true; for( uint i=0; icount(); i++ ) { if( lPlugins->text(i) == line ) { add = false; break; } } if( add ) lOnlinePlugins->insertItem( line ); } file.close(); } } */ } ConfigPluginsPage::~ConfigPluginsPage() {} void ConfigPluginsPage::resetDefaults() { // cCheckOnlinePlugins->setChecked( false ); // cfgChanged(); } void ConfigPluginsPage::saveSettings() { // config->data.plugins.checkForUpdates = cCheckOnlinePlugins->isChecked(); } void ConfigPluginsPage::pluginsSelectionChanged( int index ) { TQString name = lPlugins->text( index ); TQValueList converters = config->allConverters(); for( TQValueList::Iterator it = converters.begin(); it != converters.end(); ++it ) { if( name == (*it)->info.name + " v. " + TQString::number((*it)->info.version) ) { TQFileInfo file( (*it)->filePathName ); if( file.isWritable() ) pRemovePlugin->setEnabled( true ); else pRemovePlugin->setEnabled( false ); break; } } TQValueList replaygains = config->allReplayGains(); for( TQValueList::Iterator it = replaygains.begin(); it != replaygains.end(); ++it ) { if( name == (*it)->info.name + " v. " + TQString::number((*it)->info.version) ) { TQFileInfo file( (*it)->filePathName ); if( file.isWritable() ) pRemovePlugin->setEnabled( true ); else pRemovePlugin->setEnabled( false ); break; } } TQValueList rippers = config->allRippers(); for( TQValueList::Iterator it = rippers.begin(); it != rippers.end(); ++it ) { if( name == (*it)->info.name + " v. " + TQString::number((*it)->info.version) ) { TQFileInfo file( (*it)->filePathName ); if( file.isWritable() ) pRemovePlugin->setEnabled( true ); else pRemovePlugin->setEnabled( false ); break; } } pAboutPlugin->setEnabled( true ); } void ConfigPluginsPage::refreshPlugins() { lPlugins->clear(); TQValueList converters = config->allConverters(); for( TQValueList::Iterator it = converters.begin(); it != converters.end(); ++it ) { lPlugins->insertItem( (*it)->info.name + " v. " + TQString::number((*it)->info.version) ); //lPlugins->insertItem( i18n("%1, Version: %2").tqarg((*it)->info.name).tqarg((*it)->info.version) ); } TQValueList replaygains = config->allReplayGains(); for( TQValueList::Iterator it = replaygains.begin(); it != replaygains.end(); ++it ) { lPlugins->insertItem( (*it)->info.name + " v. " + TQString::number((*it)->info.version) ); //lPlugins->insertItem( i18n("%1, Version: %2").tqarg((*it)->info.name).tqarg((*it)->info.version) ); } TQValueList rippers = config->allRippers(); for( TQValueList::Iterator it = rippers.begin(); it != rippers.end(); ++it ) { lPlugins->insertItem( (*it)->info.name + " v. " + TQString::number((*it)->info.version) ); //lPlugins->insertItem( i18n("%1, Version: %2").tqarg((*it)->info.name).tqarg((*it)->info.version) ); } } void ConfigPluginsPage::getPlugin() { TQString url = KFileDialog::getOpenFileName( TQDir::homeDirPath(), i18n("*.soundkonverter.xml|Plugins (*.soundkonverter.xml)"), this, i18n("Choose a plugin to add!") ); if( !url.isEmpty() ) { TQString filePathName = KURL::decode_string( url ); TQString fileName = filePathName.right( filePathName.length() - filePathName.tqfindRev("/") ); getPluginFilePathName = locateLocal("data","soundkonverter/plugins/") + fileName; getPluginJob = KIO::file_copy( url, getPluginFilePathName, -1, true, false, false ); connect( getPluginJob, TQT_SIGNAL(result(KIO::Job*)), this, TQT_SLOT(getPluginFinished(KIO::Job*)) ); } } void ConfigPluginsPage::getPluginFinished( KIO::Job* job ) { if( job->error() == 0 ) { ConvertPluginLoader* convertPluginLoader = new ConvertPluginLoader(); ReplayGainPluginLoader* replaygainPluginLoader = new ReplayGainPluginLoader(); RipperPluginLoader* ripperPluginLoader = new RipperPluginLoader(); if( convertPluginLoader->verifyFile(getPluginFilePathName) == -1 && replaygainPluginLoader->verifyFile(getPluginFilePathName) == -1 && ripperPluginLoader->verifyFile(getPluginFilePathName) == -1 ) { KIO::del( getPluginFilePathName, false, false ); KMessageBox::error( this, i18n("The plugin could not be installed. Please ensure that you have selected a valid soundKonverter plugin file."), i18n("Error while installing plugin") ); } else { // TODO reload plugins without restart // ConvertPlugin* plugin = convertPluginLoader->loadFile( getPluginFilePathName ); // if( plugin->info.version != -1 ) { // lPlugins->insertItem( plugin->info.name + " v. " + TQString::number(plugin->info.version) + " (" + i18n("restart necessary") + ")" ); // } // else { // delete plugin; // ConvertPlugin* plugin = convertPluginLoader->loadFile( getPluginFilePathName ); // if( plugin->info.version != -1 ) { // lPlugins->insertItem( plugin->info.name + " v. " + TQString::number(plugin->info.version) + " (" + i18n("restart necessary") + ")" ); // } // else { // delete plugin; // ConvertPlugin* plugin = convertPluginLoader->loadFile( getPluginFilePathName ); // if( plugin->info.version != -1 ) { // lPlugins->insertItem( plugin->info.name + " v. " + TQString::number(plugin->info.version) + " (" + i18n("restart necessary") + ")" ); // } // } // } // delete plugin; KMessageBox::information( this, i18n("The plugin was installed successfully. Please restart soundKonverter in order to activate it."), i18n("Plugin successfully installed") ); //config->reloadPlugins(); //refreshPlugins(); //emit rescanForBackends(); //emit reloadEnDecoderPage(); } delete convertPluginLoader; delete replaygainPluginLoader; delete ripperPluginLoader; } else { KMessageBox::error( this, i18n("The plugin could not be installed. Please ensure that you have write permission on your whole user directory."), i18n("Error while installing plugin") ); } } void ConfigPluginsPage::removePlugin() { // TODO reload plugins without restart TQString name = lPlugins->currentText(); TQValueList converters = config->allConverters(); for( TQValueList::Iterator it = converters.begin(); it != converters.end(); ++it ) { if( name == (*it)->info.name + " v. " + TQString::number((*it)->info.version) ) { TQFile file( (*it)->filePathName ); if( file.remove() ) { lPlugins->removeItem( lPlugins->currentItem() ); KMessageBox::information( this, i18n("The plugin was removed successfully. Please restart soundKonverter in order to deactivate it."), i18n("Plugin successfully removed") ); } else { KMessageBox::error( this, i18n("The plugin could not be removed. Please ensure that you have write permission on your whole user directory."), i18n("Error while removing plugin") ); } break; } } TQValueList replaygains = config->allReplayGains(); for( TQValueList::Iterator it = replaygains.begin(); it != replaygains.end(); ++it ) { if( name == (*it)->info.name + " v. " + TQString::number((*it)->info.version) ) { TQFile file( (*it)->filePathName ); if( file.remove() ) { lPlugins->removeItem( lPlugins->currentItem() ); KMessageBox::information( this, i18n("The plugin was removed successfully. Please restart soundKonverter in order to deactivate it."), i18n("Plugin successfully removed") ); } else { KMessageBox::error( this, i18n("The plugin could not be removed. Please ensure that you have write permission on your whole user directory."), i18n("Error while removing plugin") ); } break; } } TQValueList rippers = config->allRippers(); for( TQValueList::Iterator it = rippers.begin(); it != rippers.end(); ++it ) { if( name == (*it)->info.name + " v. " + TQString::number((*it)->info.version) ) { TQFile file( (*it)->filePathName ); if( file.remove() ) { lPlugins->removeItem( lPlugins->currentItem() ); KMessageBox::information( this, i18n("The plugin was removed successfully. Please restart soundKonverter in order to deactivate it."), i18n("Plugin successfully removed") ); } else { KMessageBox::error( this, i18n("The plugin could not be removed. Please ensure that you have write permission on your whole user directory."), i18n("Error while removing plugin") ); } break; } } /* backendPlugins.remove(lPlugins->currentText()); replayGainPlugins.remove(lPlugins->currentText()); backendPlugins.reload(); replayGainPlugins.reload(); lPlugins->clear(); lPlugins->insertStringList(backendPlugins.loadedPlugins()); lPlugins->insertStringList(replayGainPlugins.loadedPlugins()); emit rescanForBackends(); emit reloadEnDecoderPage();*/ } void ConfigPluginsPage::aboutPlugin() { // TODO add link support TQString name = lPlugins->currentText(); TQValueList converters = config->allConverters(); for( TQValueList::Iterator it = converters.begin(); it != converters.end(); ++it ) { if( name == (*it)->info.name + " v. " + TQString::number((*it)->info.version) ) { KMessageBox::information( this, i18n((*it)->info.about) + "\n" + i18n("Version") + ": " + TQString::number((*it)->info.version) + "\n" + i18n("Author") + ": " + (*it)->info.author, i18n("About") + ": " + (*it)->info.name ); break; } } TQValueList replaygains = config->allReplayGains(); for( TQValueList::Iterator it = replaygains.begin(); it != replaygains.end(); ++it ) { if( name == (*it)->info.name + " v. " + TQString::number((*it)->info.version) ) { KMessageBox::information( this, i18n((*it)->info.about) + "\n" + i18n("Version") + ": " + TQString::number((*it)->info.version) + "\n" + i18n("Author") + ": " + (*it)->info.author, i18n("About") + ": " + (*it)->info.name ); break; } } TQValueList rippers = config->allRippers(); for( TQValueList::Iterator it = rippers.begin(); it != rippers.end(); ++it ) { if( name == (*it)->info.name + " v. " + TQString::number((*it)->info.version) ) { KMessageBox::information( this, i18n((*it)->info.about) + "\n" + i18n("Version") + ": " + TQString::number((*it)->info.version) + "\n" + i18n("Author") + ": " + (*it)->info.author, i18n("About") + ": " + (*it)->info.name ); break; } } } void ConfigPluginsPage::onlinePluginsSelectionChanged( int index ) { if( lOnlinePlugins->currentText() != i18n("No new plugins available!") ) { pInstallOnlinePlugin->setEnabled( true ); pAboutOnlinePlugin->setEnabled( true ); } else { pInstallOnlinePlugin->setEnabled( false ); pAboutOnlinePlugin->setEnabled( false ); } } void ConfigPluginsPage::refreshOnlinePlugins() { pRefreshOnlinePlugins->setEnabled( false ); refreshOnlinePluginsJob = KIO::file_copy( "http://kaligames.de/downloads/soundkonverter/plugins/download.php?version=" + TQString::number(config->data.app.configVersion), locateLocal("data","soundkonverter/pluginlist.txt"), -1, true, false, false ); connect( refreshOnlinePluginsJob, TQT_SIGNAL(result(KIO::Job*)), this, TQT_SLOT(refreshOnlinePluginsFinished(KIO::Job*)) ); } void ConfigPluginsPage::refreshOnlinePluginsFinished( KIO::Job* job ) { if( job->error() == 0 ) { lOnlinePlugins->clear(); TQString line; bool add; TQFile file( locateLocal("data","soundkonverter/pluginlist.txt") ); if( file.open(IO_ReadOnly) ) { TQTextStream stream( &file ); while( !stream.atEnd() ) { line = stream.readLine(); // line of text excluding '\n' line.tqreplace( "&", "&" ); line.tqreplace( "ä", "ä" ); line.tqreplace( "Ä", "Ä" ); line.tqreplace( "ö", "ö" ); line.tqreplace( "Ö", "Ö" ); line.tqreplace( "ü", "ü" ); line.tqreplace( "Ü", "Ü" ); line.tqreplace( "ß", "ß" ); add = true; for( uint i=0; icount(); i++ ) { if( lPlugins->text(i) == line ) { add = false; break; } } if( add ) lOnlinePlugins->insertItem( line ); } file.close(); } if( lOnlinePlugins->count() == 0 ) { lOnlinePlugins->insertItem( i18n("No new plugins available!") ); } } else { KMessageBox::error( this, i18n("The plugin list could not be downloaded. Please ensure, that your internet connection works correct.\nMaybe our server is busy at the moment, please try it again later."), i18n("Error while loading plugin list") ); } pRefreshOnlinePlugins->setEnabled( true ); } void ConfigPluginsPage::getOnlinePlugin() { pInstallOnlinePlugin->setEnabled( false ); TQString name; for( uint i=0; icount(); i++ ) { if( lOnlinePlugins->isSelected(i) ) { name = lOnlinePlugins->text( i ); lOnlinePlugins->removeItem( i ); break; } } name.tqreplace( "&", "&" ); name.tqreplace( "ä", "ä" ); name.tqreplace( "Ä", "Ä" ); name.tqreplace( "ö", "ö" ); name.tqreplace( "Ö", "Ö" ); name.tqreplace( "ü", "ü" ); name.tqreplace( "Ü", "Ü" ); name.tqreplace( "ß", "ß" ); KURL::encode_string( name ); getOnlinePluginJob = KIO::file_copy( "http://kaligames.de/downloads/soundkonverter/plugins/getfile.php?version=" + TQString::number(config->data.app.configVersion) + "&file=" + name, locateLocal("data","soundkonverter/plugins/newplugin.xml"), -1, true, false, false ); connect( getOnlinePluginJob, TQT_SIGNAL(result(KIO::Job*)), this, TQT_SLOT(getOnlinePluginFinished(KIO::Job*)) ); } void ConfigPluginsPage::getOnlinePluginFinished( KIO::Job* job ) { if( job->error() == 0 ) { TQString name; TQString line; TQFile file( locateLocal("data","soundkonverter/plugins/newplugin.xml") ); if( file.open(IO_ReadOnly) ) { TQTextStream stream( &file ); name = stream.readLine(); // read the file name from the top of the file getPluginFilePathName = locateLocal("data","soundkonverter/plugins/") + name; TQFile newFile( getPluginFilePathName ); if( newFile.open(IO_WriteOnly) ) { TQTextStream newStream( &newFile ); while( !stream.atEnd() ) { line = stream.readLine(); // line of text excluding '\n' newStream << line << "\n"; } newFile.close(); } file.close(); } file.remove(); ConvertPluginLoader* convertPluginLoader = new ConvertPluginLoader(); ReplayGainPluginLoader* replaygainPluginLoader = new ReplayGainPluginLoader(); RipperPluginLoader* ripperPluginLoader = new RipperPluginLoader(); if( convertPluginLoader->verifyFile(getPluginFilePathName) == -1 && replaygainPluginLoader->verifyFile(getPluginFilePathName) == -1 && ripperPluginLoader->verifyFile(getPluginFilePathName) == -1 ) { KIO::del( getPluginFilePathName, false, false ); KMessageBox::error( this, i18n("The plugin could not be installed. Please ensure that you have selected a valid soundKonverter plugin file."), i18n("Error while installing plugin") ); } else { // TODO reload plugins without restart // ConvertPlugin* plugin = convertPluginLoader->loadFile( getPluginFilePathName ); // if( plugin->info.version != -1 ) { // lPlugins->insertItem( plugin->info.name + " v. " + TQString::number(plugin->info.version) + " (" + i18n("restart necessary") + ")" ); // } // else { // delete plugin; // ConvertPlugin* plugin = convertPluginLoader->loadFile( getPluginFilePathName ); // if( plugin->info.version != -1 ) { // lPlugins->insertItem( plugin->info.name + " v. " + TQString::number(plugin->info.version) + " (" + i18n("restart necessary") + ")" ); // } // else { // delete plugin; // ConvertPlugin* plugin = convertPluginLoader->loadFile( getPluginFilePathName ); // if( plugin->info.version != -1 ) { // lPlugins->insertItem( plugin->info.name + " v. " + TQString::number(plugin->info.version) + " (" + i18n("restart necessary") + ")" ); // } // } // } // delete plugin; KMessageBox::information( this, i18n("The plugin was installed successfully. Please restart soundKonverter in order to activate it."), i18n("Plugin successfully installed") ); //config->reloadPlugins(); //refreshPlugins(); //emit rescanForBackends(); //emit reloadEnDecoderPage(); } delete convertPluginLoader; delete replaygainPluginLoader; delete ripperPluginLoader; } else { KMessageBox::error( this, i18n("The plugin could not be installed. Please ensure that you have write permission on your whole user directory."), i18n("Error while installing plugin") ); } } void ConfigPluginsPage::aboutOnlinePlugin() { pAboutOnlinePlugin->setEnabled( false ); TQString name = lOnlinePlugins->currentText(); name.tqreplace( "&", "&" ); name.tqreplace( "ä", "ä" ); name.tqreplace( "Ä", "Ä" ); name.tqreplace( "ö", "ö" ); name.tqreplace( "Ö", "Ö" ); name.tqreplace( "ü", "ü" ); name.tqreplace( "Ü", "Ü" ); name.tqreplace( "ß", "ß" ); KURL::encode_string( name ); aboutOnlinePluginJob = KIO::file_copy( "http://kaligames.de/downloads/soundkonverter/plugins/info.php?file=" + name + "&lang=" + TQLocale::languageToString(TQLocale::system().language()), locateLocal("data","soundkonverter/plugin_info.txt"), -1, true, false, false ); connect( aboutOnlinePluginJob, TQT_SIGNAL(result(KIO::Job*)), this, TQT_SLOT(aboutOnlinePluginFinished(KIO::Job*)) ); } void ConfigPluginsPage::aboutOnlinePluginFinished( KIO::Job* job ) { if( job->error() == 0 ) { TQString name = lOnlinePlugins->currentText(); TQFile file( locateLocal("data","soundkonverter/plugin_info.txt") ); if( file.open(IO_ReadOnly) ) { TQTextStream stream( &file ); TQString data = stream.readLine(); KMessageBox::information( this, i18n(data), i18n("About") + ": " + name, TQString(), KMessageBox::Notify | KMessageBox::AllowLink ); } else { KMessageBox::error( this, i18n("The plugin info could not be downloaded. Please ensure, that your internet connection works correctly."), i18n("Error while loading plugin info") ); } } else { KMessageBox::error( this, i18n("The plugin info could not be downloaded. Please ensure, that your internet connection works correctly."), i18n("Error while loading plugin info") ); } pAboutOnlinePlugin->setEnabled( true ); }