/* This file is part of the KDE project Copyright (C) 2001,2002 Carsten Pfeiffer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "algorithmdialog.h" #include "browser.h" #include "collectioncombo.h" #include "mrml_creator.h" #include "mrml_elements.h" #include "mrml_shared.h" #include "mrml_view.h" #include "mrml_part.h" #include "version.h" using namespace KMrml; extern "C" { void * init_libkmrmlpart() { return new KMrml::PartFactory(); } } TDEInstance * PartFactory::s_instance = 0L; PartFactory::PartFactory() : KParts::Factory() { MrmlShared::ref(); } PartFactory::~PartFactory() { MrmlShared::deref(); delete s_instance; s_instance = 0L; } TDEInstance * PartFactory::instance() { if ( !s_instance ) { s_instance = new TDEInstance( "kmrml" ); TDEGlobal::locale()->insertCatalogue( "kmrml" ); } return s_instance; } KParts::Part * PartFactory::createPartObject( TQWidget *parentWidget, const char *widgetName, TQObject *parent, const char *name, const char *, const TQStringList& args ) { return new MrmlPart( parentWidget, widgetName, parent, name, args ); } // can't use this due to MrmlShared ref-counting // typedef KParts::GenericFactory PartFactory; // K_EXPORT_COMPONENT_FACTORY( mrmlpart, PartFactory ) /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// uint MrmlPart::s_sessionId = 0; MrmlPart::MrmlPart( TQWidget *parentWidget, const char * /* widgetName */, TQObject *parent, const char *name, const TQStringList& /* args */ ) : KParts::ReadOnlyPart( parent, name ), m_job( 0L ), m_status( NeedCollection ) { m_sessionId = TQString::number( s_sessionId++ ).prepend("kmrml_"); setName( "MRML Part" ); m_browser = new Browser( this, "mrml browserextension"); setInstance( PartFactory::instance(), true ); // do load plugins :) TDEConfig *config = PartFactory::instance()->config(); config->setGroup("MRML Settings"); TQVBox *box = new TQVBox( parentWidget, "main mrml box" ); m_view = new MrmlView( box, "MrmlView" ); connect( m_view, TQT_SIGNAL( activated( const KURL&, ButtonState )), this, TQT_SLOT( slotActivated( const KURL&, ButtonState ))); connect( m_view, TQT_SIGNAL( onItem( const KURL& )), this, TQT_SLOT( slotSetStatusBar( const KURL& ))); m_panel = new TQHGroupBox( box, "buttons box" ); TQGrid *comboGrid = new TQGrid( 2, m_panel, "combo grid" ); comboGrid->setSpacing( KDialog::spacingHint() ); (void) new TQLabel( i18n("Server to query:"), comboGrid ); m_hostCombo = new KComboBox( false, comboGrid, "host combo" ); initHostCombo(); connect( m_hostCombo, TQT_SIGNAL( activated( const TQString& ) ), TQT_SLOT( slotHostComboActivated( const TQString& ))); (void) new TQLabel( i18n("Search in collection:"), comboGrid ); m_collectionCombo = new CollectionCombo( comboGrid, "collection-combo" ); // will be re-set in initCollections(), but we need to set it here to // prevent crashes when the connection to the server fails m_collectionCombo->setCollections( &m_collections ); m_algoButton = new TQPushButton( TQString(), m_panel ); m_algoButton->setPixmap( SmallIcon("configure") ); m_algoButton->setFixedSize( m_algoButton->sizeHint() ); connect( m_algoButton, TQT_SIGNAL( clicked() ), TQT_SLOT( slotConfigureAlgorithm() )); TQToolTip::add( m_algoButton, i18n("Configure algorithm") ); TQWidget *spacer = new TQWidget( m_panel ); spacer->setSizePolicy( TQSizePolicy( TQSizePolicy::MinimumExpanding, TQSizePolicy::Minimum ) ); int resultSize = config->readNumEntry( "Result-size", 20 ); m_resultSizeInput = new KIntNumInput( resultSize, m_panel ); m_resultSizeInput->setRange( 1, 100 ); m_resultSizeInput->setLabel( i18n("Maximum result images:") ); TQVBox *tmp = new TQVBox( m_panel ); m_random = new TQCheckBox( i18n("Random search"), tmp ); m_startButton = new TQPushButton( TQString(), tmp ); connect( m_startButton, TQT_SIGNAL( clicked() ), TQT_SLOT( slotStartClicked() )); setStatus( NeedCollection ); setWidget( box ); // setXMLFile( "mrml_part.rc" ); slotSetStatusBar( TQString() ); enableServerDependentWidgets( false ); } MrmlPart::~MrmlPart() { closeURL(); } void MrmlPart::enableServerDependentWidgets( bool enable ) { m_collectionCombo->setEnabled( enable ); m_algoButton->setEnabled( enable && false ); // ### re-enable!!! } void MrmlPart::initCollections( const TQDomElement& elem ) { m_collections.initFromDOM( elem ); m_collectionCombo->setCollections( &m_collections ); enableServerDependentWidgets( m_collectionCombo->count() > 0 ); if ( m_collectionCombo->count() == 0 ) { KMessageBox::information( widget(), i18n("There is no image collection available\n" "at %1.\n"), i18n("No Image Collection")); setStatus( NeedCollection ); } else m_collectionCombo->updateGeometry(); // adjust the entire grid } void MrmlPart::initAlgorithms( const TQDomElement& elem ) { m_algorithms.initFromDOM( elem ); } // this is where we start! bool MrmlPart::openURL( const KURL& url ) { closeURL(); if ( url.protocol() != "mrml" || !url.isValid() ) { tqWarning("MrmlPart::openURL: cannot handle url: %s", url.prettyURL().latin1()); return false; // what to do with that? } m_url = url; TQString host = url.host().isEmpty() ? TQString::fromLatin1("localhost") : url.host(); m_hostCombo->setCurrentItem( host ); // urls we need to download before starting the query KURL::List downloadList; m_queryList.clear(); TQString param = url.queryItem( "relevant" ); TQStringList list = TQStringList::split( ';', param ); // we can only search by example on localhost if ( host != "localhost" ) { if ( !list.isEmpty() ) KMessageBox::sorry( m_view, i18n("You can only search by example images " "on a local indexing server."), i18n("Only Local Servers Possible") ); } else // localhost query { for( TQStringList::Iterator it = list.begin(); it != list.end(); ++it ) { KURL u; if ( (*it).at(0) == '/' ) u.setPath( *it ); else u = *it; if ( u.isValid() ) { if ( u.isLocalFile() ) m_queryList.append( u ); else downloadList.append( u ); } } // ### we need a real solution for this! // gift refuses to start when no config file is available. if ( !TQFile::exists( m_config.mrmldDataDir() + "/gift-config.mrml" ) ) { if ( KMessageBox::questionYesNo(0L, i18n("There are no indexable folders " "specified. Do you want to configure them " "now?"), i18n("Configuration Missing"), i18n("Configure"), i18n("Do Not Configure"), "kmrml_ask_configure_gift" ) == KMessageBox::Yes ) { TDEApplication::tdeinitExec( "tdecmshell", TQString::fromLatin1("kcmkmrml")); setStatus( NeedCollection ); return false; } } } if ( !downloadList.isEmpty() ) downloadReferenceFiles( downloadList ); else contactServer( m_url ); return true; } void MrmlPart::contactServer( const KURL& url ) { m_job = transferJob( url ); m_job->addMetaData( MrmlShared::tdeio_task(), MrmlShared::tdeio_initialize() ); TQString host = url.host().isEmpty() ? TQString::fromLatin1("localhost") : url.host(); slotSetStatusBar( i18n("Connecting to indexing server at %1...").arg( host )); } // // schedules a download all urls of downloadList (all remote and wellformed) // No other downloads are running (closeURL() has been called before) // void MrmlPart::downloadReferenceFiles( const KURL::List& downloadList ) { assert( m_downloadJobs.isEmpty() ); KURL::List::ConstIterator it = downloadList.begin(); for ( ; it != downloadList.end(); it++ ) { TQString extension; int index = (*it).fileName().findRev( '.' ); if ( index != -1 ) extension = (*it).fileName().mid( index ); KTempFile tmpFile( TQString(), extension ); if ( tmpFile.status() != 0 ) { kdWarning() << "Can't create temporary file, skipping: " << *it << endl; continue; } m_tempFiles.append( tmpFile.name() ); KURL destURL; destURL.setPath( tmpFile.name() ); TDEIO::FileCopyJob *job = TDEIO::file_copy( *it, destURL, -1, true /* overwrite tmpfile */ ); connect( job, TQT_SIGNAL( result( TDEIO::Job * ) ), TQT_SLOT( slotDownloadResult( TDEIO::Job * ) )); m_downloadJobs.append( job ); // ### should this be only called for one job? emit started( job ); } if ( !m_downloadJobs.isEmpty() ) slotSetStatusBar( i18n("Downloading reference files...") ); else // probably never happens contactServer( m_url ); } bool MrmlPart::closeURL() { m_view->stopDownloads(); m_view->clear(); TQPtrListIterator it( m_downloadJobs ); for ( ; it.current(); ++it ) it.current()->kill(); m_downloadJobs.clear(); TQStringList::Iterator tit = m_tempFiles.begin(); for ( ; tit != m_tempFiles.end(); ++tit ) TQFile::remove( *tit ); m_tempFiles.clear(); if ( m_job ) { m_job->kill(); m_job = 0L; } setStatus( NeedCollection ); return true; } TDEIO::TransferJob * MrmlPart::transferJob( const KURL& url ) { TDEIO::TransferJob *job = TDEIO::get( url, true, false ); // reload, no gui job->setAutoErrorHandlingEnabled( true, m_view ); connect( job, TQT_SIGNAL( result( TDEIO::Job * )), TQT_SLOT( slotResult( TDEIO::Job * ))); connect( job, TQT_SIGNAL( data( TDEIO::Job *, const TQByteArray& )), TQT_SLOT( slotData( TDEIO::Job *, const TQByteArray& ))); // ### // connect( job, TQT_SIGNAL( infoMessage( TDEIO::Job *, const TQString& )), // TQT_SLOT( slotResult( TDEIO::Job *, const TQString& ))); job->setWindow( widget() ); if ( !m_sessionId.isEmpty() ) job->addMetaData( MrmlShared::sessionId(), m_sessionId ); emit started( job ); emit setWindowCaption( url.prettyURL() ); setStatus( InProgress ); return job; } void MrmlPart::slotResult( TDEIO::Job *job ) { if ( job == m_job ) m_job = 0L; slotSetStatusBar( TQString() ); if ( !job->error() ) emit completed(); else { emit canceled( job->errorString() ); // tqDebug("*** canceled: error: %s", job->errorString().latin1()); } bool auto_random = m_view->isEmpty() && m_queryList.isEmpty(); m_random->setChecked( auto_random ); m_random->setEnabled( !auto_random ); setStatus( job->error() ? NeedCollection : CanSearch ); if ( !job->error() && !m_queryList.isEmpty() ) { // we have a connection and we got a list of relevant URLs to query for // (via the URL) createQuery( &m_queryList ); m_queryList.clear(); } } // ### when user cancels download, we crash :( void MrmlPart::slotDownloadResult( TDEIO::Job *job ) { assert( job->inherits( "TDEIO::FileCopyJob" ) ); TDEIO::FileCopyJob *copyJob = static_cast( job ); if ( !copyJob->error() ) m_queryList.append( copyJob->destURL() ); m_downloadJobs.removeRef( copyJob ); if ( m_downloadJobs.isEmpty() ) // finally, we can start the query! { if ( m_queryList.isEmpty() ) // rather unlikely, but could happen ;) { kdWarning() << "Couldn't download the reference files. Will start a random search now" << endl; } contactServer( m_url ); } } // mrml-document in the bytearray void MrmlPart::slotData( TDEIO::Job *, const TQByteArray& data ) { if ( data.isEmpty() ) return; TQDomDocument doc; doc.setContent( data ); if ( !doc.isNull() ) parseMrml( doc ); } void MrmlPart::parseMrml( TQDomDocument& doc ) { TQDomNode mrml = doc.documentElement(); // root element if ( !mrml.isNull() ) { TQDomNode child = mrml.firstChild(); for ( ; !child.isNull(); child = child.nextSibling() ) { // tqDebug("**** HERE %s", child.nodeName().latin1()); if ( child.isElement() ) { TQDomElement elem = child.toElement(); TQString tagName = elem.tagName(); if ( tagName == "acknowledge-session-op" ) m_sessionId = elem.attribute( MrmlShared::sessionId() ); else if ( tagName == MrmlShared::algorithmList() ) { initAlgorithms( elem ); } else if ( tagName == MrmlShared::collectionList() ) { initCollections( elem ); } else if ( tagName == "error" ) { KMessageBox::information( widget(), i18n("Server returned error:\n%1\n") .arg( elem.attribute( "message" )), i18n("Server Error") ); } else if ( tagName == "query-result" ) { m_view->clear(); parseQueryResult( elem ); } } // child.isElement() } } // !mrml.isNull() } void MrmlPart::parseQueryResult( TQDomElement& queryResult ) { TQDomNode child = queryResult.firstChild(); for ( ; !child.isNull(); child = child.nextSibling() ) { if ( child.isElement() ) { TQDomElement elem = child.toElement(); TQString tagName = elem.tagName(); if ( tagName == "query-result-element-list" ) { TQValueList list = KMrml::directChildElements( elem, "query-result-element" ); TQValueListConstIterator it = list.begin(); for ( ; it != list.end(); ++it ) { TQDomNamedNodeMap a = (*it).attributes(); m_view->addItem( KURL( (*it).attribute("image-location" ) ), KURL( (*it).attribute("thumbnail-location" ) ), (*it).attribute("calculated-similarity")); } } else if ( tagName == "query-result" ) parseQueryResult( elem ); } } } // creates/stops the query when the Start/Stop button was pressed void MrmlPart::slotStartClicked() { if ( m_status == InProgress ) { closeURL(); m_startButton->setText( i18n("&Search" ) ); return; } // we need to reconnect, if the initial openURL() didn't work due to // the gift not being available. if ( m_status == NeedCollection ) { openURL( m_url ); return; } // cut off an eventual query and reference from the url, when the user // performs a real query (otherwise restoreState() would restore and // re-do the query from the URL m_url.setRef( TQString() ); m_url.setQuery( TQString() ); createQuery(); m_browser->openURLNotify(); } // // relevantItems is 0L when called from slotStartClicked() and set to a // non-empty list when called initially, from the commandline. // void MrmlPart::createQuery( const KURL::List * relevantItems ) { if ( relevantItems && relevantItems->isEmpty() ) return; TQDomDocument doc( "mrml" ); TQDomElement mrml = MrmlCreator::createMrml( doc, sessionId(), transactionId() ); Collection coll = currentCollection(); // tqDebug("** collection: name: %s, id: %s, valid: %i", coll.name().latin1(), coll.id().latin1(), coll.isValid()); Algorithm algo = firstAlgorithmForCollection( coll ); // tqDebug("** algorithm: name: %s, id: %s, valid: %i, collection-id: %s", algo.name().latin1(), algo.id().latin1(), algo.isValid(), algo.collectionId().latin1()); if ( algo.isValid() ) { MrmlCreator::configureSession( mrml, algo, sessionId() ); } TQDomElement query = MrmlCreator::addQuery( mrml, m_resultSizeInput->value() ); if ( algo.isValid() ) query.setAttribute( MrmlShared::algorithmId(), algo.id() ); // ### result-cutoff, query-type? // start-up with/without urls on the commandline via mrmlsearch if ( relevantItems ) { TQDomElement elem = MrmlCreator::addRelevanceList( query ); KURL::List::ConstIterator it = relevantItems->begin(); for ( ; it != relevantItems->end(); ++it ) MrmlCreator::createRelevanceElement( doc, elem, (*it).url(), MrmlCreator::Relevant ); } // get relevant items from the view? Only do this when relevantItems is 0L else if ( !m_random->isChecked() ) { TQDomElement relevants = MrmlCreator::addRelevanceList( query ); m_view->addRelevanceToQuery( doc, relevants ); } performQuery( doc ); } Collection MrmlPart::currentCollection() const { return m_collectionCombo->current(); } Algorithm MrmlPart::firstAlgorithmForCollection( const Collection& coll ) const { if ( !m_algorithms.isEmpty() ) { AlgorithmList::ConstIterator it = m_algorithms.begin(); for ( ; it != m_algorithms.end(); ++it ) { Algorithm algo = *it; if ( algo.paradigms().matches( coll.paradigms() ) ) { algo.setCollectionId( coll.id() ); return algo; } } } tqDebug("#################### -> ADEFAULT!"); Algorithm algo = Algorithm::defaultAlgorithm(); algo.setCollectionId( coll.id() ); return algo; } // emits the given TQDomDocument for eventual plugins, checks after that // if there are any relevance elements. If there are none, random search is // implied and performed. // finally, the search is actually started void MrmlPart::performQuery( TQDomDocument& doc ) { TQDomElement mrml = doc.documentElement(); emit aboutToStartQuery( doc ); // let plugins play with it :) // no items available? All "neutral"? -> random search TQDomElement queryStep = KMrml::firstChildElement( mrml, "query-step" ); bool randomSearch = false; if ( !queryStep.isNull() ) { TQDomElement relevanceList = KMrml::firstChildElement(queryStep, "user-relevance-element-list"); TQValueList relevanceElements = KMrml::directChildElements( relevanceList, "user-relevance-element" ); randomSearch = relevanceElements.isEmpty(); if ( randomSearch ) { m_random->setChecked( true ); m_random->setEnabled( false ); queryStep.setAttribute("query-type", "at-random"); // remove user-relevance-element-list element for random search relevanceList.parentNode().removeChild( relevanceList ); } } else { KMessageBox::error( m_view, i18n("Error formulating the query. The " "\"query-step\" element is missing."), i18n("Query Error") ); } m_job = transferJob( url() ); slotSetStatusBar( randomSearch ? i18n("Random search...") : i18n("Searching...") ); m_job->addMetaData( MrmlShared::tdeio_task(), MrmlShared::tdeio_startQuery() ); tqDebug("\n\nSending XML:\n%s", doc.toString().latin1()); m_job->addMetaData( MrmlShared::mrml_data(), doc.toString() ); } void MrmlPart::slotSetStatusBar( const TQString& text ) { if ( text.isEmpty() ) emit setStatusBarText( i18n("Ready.") ); else emit setStatusBarText( text ); } void MrmlPart::slotActivated( const KURL& url, ButtonState button ) { if ( button == Qt::LeftButton ) emit m_browser->openURLRequest( url ); else if ( button == Qt::MidButton ) emit m_browser->createNewWindow( url ); else if ( button == Qt::RightButton ) { // enableExtensionActions( url, true ); // for now emit m_browser->popupMenu( TQCursor::pos(), url, TQString() ); // enableExtensionActions( url, false ); } } void MrmlPart::enableExtensionActions( const KURL& url, bool enable ) { bool del = KProtocolInfo::supportsDeleting( url ); emit m_browser->enableAction( "copy", enable ); emit m_browser->enableAction( "trash", del ); emit m_browser->enableAction( "del", del ); emit m_browser->enableAction( "shred", url.isLocalFile() ); emit m_browser->enableAction( "properties", enable ); // emit m_browser->enableAction( "print", enable ); // ### later } // only implemented because it's abstract in the baseclass bool MrmlPart::openFile() { return false; } void MrmlPart::slotConfigureAlgorithm() { m_algoButton->setEnabled( false ); m_algoConfig = new AlgorithmDialog( m_algorithms, m_collections, currentCollection(), m_view, "algorithm configuration" ); connect( m_algoConfig, TQT_SIGNAL( applyClicked() ), TQT_SLOT( slotApplyAlgoConfig() )); connect( m_algoConfig, TQT_SIGNAL( finished() ), TQT_SLOT( slotAlgoConfigFinished() )); m_algoConfig->show(); } void MrmlPart::slotApplyAlgoConfig() { // ### } void MrmlPart::slotAlgoConfigFinished() { if ( m_algoConfig->result() == TQDialog::Accepted ) slotApplyAlgoConfig(); m_algoButton->setEnabled( true ); m_algoConfig->deleteLater(); m_algoConfig = 0L; } void MrmlPart::initHostCombo() { m_hostCombo->clear(); m_hostCombo->insertStringList( m_config.hosts() ); } void MrmlPart::slotHostComboActivated( const TQString& host ) { ServerSettings settings = m_config.settingsForHost( host ); openURL( settings.getUrl() ); } void MrmlPart::setStatus( Status status ) { switch ( status ) { case NeedCollection: m_startButton->setText( i18n("&Connect") ); break; case CanSearch: m_startButton->setText( i18n("&Search") ); break; case InProgress: m_startButton->setText( i18n("Sto&p") ); break; }; m_status = status; } void MrmlPart::saveState( TQDataStream& stream ) { stream << url(); stream << m_sessionId; stream << m_queryList; // stream << m_algorithms; // stream << m_collections; stream << m_resultSizeInput->value(); stream << *m_collectionCombo; m_view->saveState( stream ); } void MrmlPart::restoreState( TQDataStream& stream ) { KURL url; stream >> url; stream >> m_sessionId; stream >> m_queryList; // stream >> m_algorithms; // stream >> m_collections; int resultSize; stream >> resultSize; m_resultSizeInput->setValue( resultSize ); stream >> *m_collectionCombo; m_view->restoreState( stream ); // openURL( url ); m_url = url; } TDEAboutData * MrmlPart::createAboutData() { TDEAboutData *data = new TDEAboutData( "kmrml", I18N_NOOP("MRML Client for TDE"), KMRML_VERSION, I18N_NOOP("A tool to search for images by their content"), TDEAboutData::License_GPL, I18N_NOOP("(c) 2001-2002, Carsten Pfeiffer"), 0, I18N_NOOP("http://devel-home.kde.org/~pfeiffer/kmrml/") ); data->addAuthor( "Carsten Pfeiffer", I18N_NOOP("Developer, Maintainer"), "pfeiffer@kde.org" ); data->addCredit( "Wolfgang Mller", I18N_NOOP("Developer of the GIFT, Helping Hand") ); return data; } /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// #include "mrml_part.moc"