Mixxx

/home/maxime/Projets/Mixxx/1.10/mixxx/src/library/traktor/traktorfeature.cpp

Go to the documentation of this file.
00001 // traktorfeature.cpp
00002 // Created 9/26/2010 by Tobias Rafreider
00003 
00004 #include <QtDebug>
00005 #include <QMessageBox>
00006 #include <QXmlStreamReader>
00007 #include <QMap>
00008 #include <QSettings>
00009 #include <QDesktopServices>
00010 
00011 #include "library/traktor/traktorfeature.h"
00012 
00013 #include "library/librarytablemodel.h"
00014 #include "library/missingtablemodel.h"
00015 #include "library/trackcollection.h"
00016 #include "library/treeitem.h"
00017 
00018 
00019 TraktorFeature::TraktorFeature(QObject* parent, TrackCollection* pTrackCollection):
00020         LibraryFeature(parent),
00021         m_pTrackCollection(pTrackCollection),
00022         m_cancelImport(false) {
00023     QString tableName = "traktor_library";
00024     QString idColumn = "id";
00025     QStringList columns;
00026     columns << "id"
00027             << "artist"
00028             << "title"
00029             << "album"
00030             << "year"
00031             << "genre"
00032             << "tracknumber"
00033             << "location"
00034             << "comment"
00035             << "rating"
00036             << "duration"
00037             << "bitrate"
00038             << "bpm"
00039             << "key";
00040     pTrackCollection->addTrackSource(QString("traktor"), QSharedPointer<BaseTrackCache>(
00041         new BaseTrackCache(m_pTrackCollection, tableName, idColumn,
00042                            columns, false)));
00043 
00044     m_isActivated = false;
00045     m_pTraktorTableModel = new TraktorTableModel(this, m_pTrackCollection);
00046     m_pTraktorPlaylistModel = new TraktorPlaylistModel(this, m_pTrackCollection);
00047     m_title = tr("Traktor");
00048     if (!m_database.isOpen()) {
00049         m_database = QSqlDatabase::addDatabase("QSQLITE", "TRAKTOR_SCANNER");
00050         m_database.setHostName("localhost");
00051         m_database.setDatabaseName(MIXXX_DB_PATH);
00052         m_database.setUserName("mixxx");
00053         m_database.setPassword("mixxx");
00054 
00055         //Open the database connection in this thread.
00056         if (!m_database.open()) {
00057             qDebug() << "Failed to open database for iTunes scanner." << m_database.lastError();
00058         }
00059     }
00060     connect(&m_future_watcher, SIGNAL(finished()), this, SLOT(onTrackCollectionLoaded()));
00061 }
00062 
00063 TraktorFeature::~TraktorFeature() {
00064     m_cancelImport = true;
00065     m_future.waitForFinished();
00066     if(m_pTraktorTableModel)
00067         delete m_pTraktorTableModel;
00068     if(m_pTraktorPlaylistModel)
00069         delete m_pTraktorPlaylistModel;
00070 }
00071 
00072 QVariant TraktorFeature::title() {
00073     return m_title;
00074 }
00075 
00076 QIcon TraktorFeature::getIcon() {
00077     return QIcon(":/images/library/ic_library_traktor.png");
00078 }
00079 
00080 bool TraktorFeature::isSupported() {
00081     return (QFile::exists(getTraktorMusicDatabase()));
00082 }
00083 
00084 TreeItemModel* TraktorFeature::getChildModel() {
00085     return &m_childModel;
00086 }
00087 
00088 void TraktorFeature::refreshLibraryModels() {
00089 }
00090 
00091 void TraktorFeature::activate() {
00092     qDebug() << "TraktorFeature::activate()";
00093 
00094     if (!m_isActivated) {
00095         m_isActivated =  true;
00096         /* Ususally the maximum number of threads
00097          * is > 2 depending on the CPU cores
00098          * Unfortunately, within VirtualBox
00099          * the maximum number of allowed threads
00100          * is 1 at all times We'll need to increase
00101          * the number to > 1, otherwise importing the music collection
00102          * takes place when the GUI threads terminates, i.e., on
00103          * Mixxx shutdown.
00104          */
00105         QThreadPool::globalInstance()->setMaxThreadCount(4); //Tobias decided to use 4
00106         // Let a worker thread do the XML parsing
00107         m_future = QtConcurrent::run(this, &TraktorFeature::importLibrary, getTraktorMusicDatabase());
00108         m_future_watcher.setFuture(m_future);
00109         m_title = tr("(loading) Traktor");
00110         //calls a slot in the sidebar model such that 'iTunes (isLoading)' is displayed.
00111         emit (featureIsLoading(this));
00112     } else {
00113         emit(showTrackModel(m_pTraktorTableModel));
00114     }
00115 }
00116 
00117 void TraktorFeature::activateChild(const QModelIndex& index) {
00118 
00119     if(!index.isValid()) return;
00120 
00121     //access underlying TreeItem object
00122     TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
00123 
00124     if(item->isPlaylist()){
00125         qDebug() << "Activate Traktor Playlist: " << item->dataPath().toString();
00126         m_pTraktorPlaylistModel->setPlaylist(item->dataPath().toString());
00127         emit(showTrackModel(m_pTraktorPlaylistModel));
00128     }
00129 }
00130 
00131 void TraktorFeature::onRightClick(const QPoint& globalPos) {
00132 }
00133 
00134 void TraktorFeature::onRightClickChild(const QPoint& globalPos,
00135                                             QModelIndex index) {
00136 }
00137 
00138 bool TraktorFeature::dropAccept(QUrl url) {
00139     return false;
00140 }
00141 
00142 bool TraktorFeature::dropAcceptChild(const QModelIndex& index, QUrl url) {
00143     return false;
00144 }
00145 
00146 bool TraktorFeature::dragMoveAccept(QUrl url) {
00147     return false;
00148 }
00149 
00150 bool TraktorFeature::dragMoveAcceptChild(const QModelIndex& index,
00151                                               QUrl url) {
00152     return false;
00153 }
00154 
00155 TreeItem* TraktorFeature::importLibrary(QString file){
00156     //Give thread a low priority
00157     QThread* thisThread = QThread::currentThread();
00158     thisThread->setPriority(QThread::LowestPriority);
00159     //Invisible root item of Traktor's child model
00160     TreeItem* root = NULL;
00161     //Delete all table entries of Traktor feature
00162     m_database.transaction();
00163     clearTable("traktor_playlist_tracks");
00164     clearTable("traktor_library");
00165     clearTable("traktor_playlists");
00166     m_database.commit();
00167 
00168     m_database.transaction();
00169     QSqlQuery query(m_database);
00170     query.prepare("INSERT INTO traktor_library (artist, title, album, year, genre,comment,"
00171                    "tracknumber,"
00172                    "bpm, bitrate,"
00173                    "duration, location,"
00174                    "rating,"
00175                    "key) "
00176                    "VALUES (:artist, :title, :album, :year, :genre,:comment, :tracknumber,"
00177                    ":bpm, :bitrate,"
00178                    ":duration, :location,"
00179                    ":rating,"
00180                    ":key)");
00181 
00182     //Parse Trakor XML file using SAX (for performance)
00183     QFile traktor_file(file);
00184     if (!traktor_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
00185         qDebug() << "Cannot open Traktor music collection";
00186         return false;
00187     }
00188     QXmlStreamReader xml(&traktor_file);
00189     bool inCollectionTag = false;
00190     bool inEntryTag = false;
00191     bool inPlaylistsTag = false;
00192     bool isRootFolderParsed = false;
00193     int nAudioFiles = 0;
00194 
00195     while (!xml.atEnd() && !m_cancelImport)
00196     {
00197         xml.readNext();
00198         if(xml.isStartElement())
00199         {
00200             if(xml.name() == "COLLECTION")
00201             {
00202                 inCollectionTag = true;
00203 
00204             }
00205             /*
00206              * Each "ENTRY" tag in <COLLECTION> represents a track
00207              */
00208             if(inCollectionTag && xml.name() == "ENTRY" )
00209             {
00210                 inEntryTag = true;
00211                 //parse track
00212                 parseTrack(xml, query);
00213 
00214                 ++nAudioFiles; //increment number of files in the music collection
00215             }
00216             if(xml.name() == "PLAYLISTS")
00217             {
00218                 inPlaylistsTag = true;
00219 
00220             }
00221             if(inPlaylistsTag && !isRootFolderParsed && xml.name() == "NODE"){
00222                 QXmlStreamAttributes attr = xml.attributes();
00223                 QString nodetype = attr.value("TYPE").toString();
00224                 QString name = attr.value("NAME").toString();
00225 
00226                 if(nodetype == "FOLDER" && name == "$ROOT"){
00227                     //process all playlists
00228                     root = parsePlaylists(xml);
00229                     isRootFolderParsed = true;
00230                 }
00231             }
00232 
00233         }
00234         if(xml.isEndElement())
00235         {
00236             if(xml.name() == "COLLECTION")
00237             {
00238                 inCollectionTag = false;
00239 
00240             }
00241             if(xml.name() == "ENTRY" && inCollectionTag)
00242             {
00243                 inEntryTag = false;
00244 
00245             }
00246             if(xml.name() == "PLAYLISTS" && inPlaylistsTag)
00247             {
00248                 inPlaylistsTag = false;
00249             }
00250 
00251         }
00252     }
00253     if (xml.hasError()) {
00254          // do error handling
00255          qDebug() << "Cannot process Traktor music collection";
00256          if(root)
00257              delete root;
00258          return false;
00259     }
00260 
00261     qDebug() << "Found: " << nAudioFiles << " audio files in Traktor";
00262     //initialize TraktorTableModel
00263     m_database.commit();
00264 
00265     return root;
00266 }
00267 
00268 void TraktorFeature::parseTrack(QXmlStreamReader &xml, QSqlQuery &query){
00269     QString title;
00270     QString artist;
00271     QString album;
00272     QString year;
00273     QString genre;
00274     //drive letter
00275     QString volume;
00276     QString path;
00277     QString filename;
00278     QString location;
00279     float bpm = 0.0;
00280     int bitrate = 0;
00281     QString key;
00282     //duration of a track
00283     int playtime = 0;
00284     int rating = 0;
00285     QString comment;
00286     QString tracknumber;
00287 
00288     //get XML attributes of starting ENTRY tag
00289     QXmlStreamAttributes attr = xml.attributes ();
00290     title = attr.value("TITLE").toString();
00291     artist = attr.value("ARTIST").toString();
00292 
00293     //read all sub tags of ENTRY until we reach the closing ENTRY tag
00294     while(!xml.atEnd())
00295     {
00296         xml.readNext();
00297         if(xml.isStartElement()){
00298             if(xml.name() == "ALBUM")
00299             {
00300                 QXmlStreamAttributes attr = xml.attributes ();
00301                 album = attr.value("TITLE").toString();
00302                 tracknumber = attr.value("TRACK").toString();
00303 
00304                 continue;
00305             }
00306             if(xml.name() == "LOCATION")
00307             {
00308                 QXmlStreamAttributes attr = xml.attributes ();
00309                 volume = attr.value("VOLUME").toString();
00310                 path = attr.value("DIR").toString();
00311                 filename = attr.value("FILE").toString();
00312                 /*compute the location, i.e, combining all the values
00313                  * On Windows the volume holds the drive letter e.g., d:
00314                  * On OS X, the volume is supposed to be "Macintosh HD" at all times,
00315                  * which is a folder in /Volumes/
00316                  */
00317                 #if defined(__APPLE__)
00318                 location = "/Volumes/"+volume;
00319                 #else
00320                 location = volume;
00321                 #endif
00322                 location += path.replace(QString(":"), QString(""));
00323                 location += filename;
00324                 continue;
00325             }
00326             if(xml.name() == "INFO")
00327             {
00328                 QXmlStreamAttributes attr = xml.attributes();
00329                 key = attr.value("KEY").toString();
00330                 bitrate = attr.value("BITRATE").toString().toInt() / 1000;
00331                 playtime = attr.value("PLAYTIME").toString().toInt();
00332                 genre = attr.value("GENRE").toString();
00333                 year = attr.value("RELEASE_DATE").toString();
00334                 comment = attr.value("COMMENT").toString();
00335                 QString ranking_str = attr.value("RANKING").toString();
00336                 /* A ranking in Traktor has ranges between 0 and 255 internally.
00337                  * This is same as the POPULARIMETER tag in IDv2, see http://help.mp3tag.de/main_tags.html
00338                  *
00339                  * Our rating values range from 1 to 5. The mapping is defined as follow
00340                  * ourRatingValue = TraktorRating / 51
00341                  */
00342                  if(ranking_str != "" && qVariantCanConvert<int>(ranking_str)){
00343                     rating = ranking_str.toInt()/51;
00344                  }
00345                 continue;
00346             }
00347             if(xml.name() == "TEMPO")
00348             {
00349                 QXmlStreamAttributes attr = xml.attributes ();
00350                 bpm = attr.value("BPM").toString().toFloat();
00351                 continue;
00352             }
00353         }
00354         //We leave the infinte loop, if twe have the closing tag "ENTRY"
00355         if(xml.name() == "ENTRY" && xml.isEndElement()){
00356             break;
00357         }
00358     }
00359 
00360     /* If we reach the end of ENTRY within the COLLECTION tag
00361      * Save parsed track to database
00362      */
00363     query.bindValue(":artist", artist);
00364     query.bindValue(":title", title);
00365     query.bindValue(":album", album);
00366     query.bindValue(":genre", genre);
00367     query.bindValue(":year", year);
00368     query.bindValue(":duration", playtime);
00369     query.bindValue(":location", location);
00370     query.bindValue(":rating", rating);
00371     query.bindValue(":comment", comment);
00372     query.bindValue(":tracknumber", tracknumber);
00373     query.bindValue(":key", key);
00374     query.bindValue(":bpm", bpm);
00375     query.bindValue(":bitrate", bitrate);
00376 
00377 
00378     bool success = query.exec();
00379     if(!success){
00380         qDebug() << "SQL Error in TraktorTableModel.cpp: line" << __LINE__ << " " << query.lastError();
00381         return;
00382     }
00383 
00384 }
00385 
00386 /*
00387  * Purpose: Parsing all the folder and playlists of Traktor
00388  * This is a complex operation since Traktor uses the concept of folders and playlist.
00389  * A folder can contain folders and playlists. A playlist contains entries but no folders.
00390  * In other words, Traktor uses a tree structure to organize music. Inner nodes represent folders while
00391  * leaves are playlists.
00392  */
00393 TreeItem* TraktorFeature::parsePlaylists(QXmlStreamReader &xml){
00394 
00395     qDebug() << "Process RootFolder";
00396     //Each playlist is unique and can be identified by a path in the tree structure.
00397     QString current_path = "";
00398     QMap<QString,QString> map;
00399 
00400     QString delimiter = "-->";
00401 
00402     TreeItem *rootItem = new TreeItem();
00403     TreeItem * parent = rootItem;
00404 
00405     bool inPlaylistTag = false;
00406 
00407     QSqlQuery query_insert_to_playlists(m_database);
00408     query_insert_to_playlists.prepare("INSERT INTO traktor_playlists (name) "
00409                   "VALUES (:name)");
00410 
00411     QSqlQuery query_insert_to_playlist_tracks(m_database);
00412     query_insert_to_playlist_tracks.prepare(
00413         "INSERT INTO traktor_playlist_tracks (playlist_id, track_id, position) "
00414         "VALUES (:playlist_id, :track_id, :position)");
00415 
00416     while(!xml.atEnd() && !m_cancelImport) {
00417         //read next XML element
00418         xml.readNext();
00419 
00420         if(xml.isStartElement())
00421         {
00422             if(xml.name() == "NODE"){
00423                 QXmlStreamAttributes attr = xml.attributes();
00424                 QString name = attr.value("NAME").toString();
00425                 QString type = attr.value("TYPE").toString();
00426 
00427                //TODO: What happens if the folder node is a leaf (empty folder)
00428                // Idea: Hide empty folders :-)
00429                if(type == "FOLDER")
00430                {
00431 
00432                     current_path += delimiter;
00433                     current_path += name;
00434                     //qDebug() << "Folder: " +current_path << " has parent " << parent->data().toString();
00435                     map.insert(current_path, "FOLDER");
00436 
00437                     TreeItem * item = new TreeItem(name,current_path, this, parent);
00438                     parent->appendChild(item);
00439                     parent = item;
00440                }
00441                if(type == "PLAYLIST")
00442                {
00443                     current_path += delimiter;
00444                     current_path += name;
00445                     //qDebug() << "Playlist: " +current_path << " has parent " << parent->data().toString();
00446                     map.insert(current_path, "PLAYLIST");
00447 
00448                     TreeItem * item = new TreeItem(name,current_path, this, parent);
00449                     parent->appendChild(item);
00450                     // process all the entries within the playlist 'name' having path 'current_path'
00451                     parsePlaylistEntries(xml,current_path, query_insert_to_playlists, query_insert_to_playlist_tracks);
00452                }
00453             }
00454             if(xml.name() == "ENTRY" && inPlaylistTag){
00455             }
00456         }
00457 
00458         if(xml.isEndElement())
00459         {
00460             if(xml.name() == "NODE")
00461             {
00462                 if(map.value(current_path) == "FOLDER"){
00463                     parent = parent->parent();
00464                 }
00465 
00466                 //Whenever we find a closing NODE, remove the last component of the path
00467                 int lastSlash = current_path.lastIndexOf(delimiter);
00468                 int path_length = current_path.size();
00469 
00470                 current_path.remove(lastSlash, path_length - lastSlash);
00471             }
00472              if(xml.name() == "PLAYLIST")
00473             {
00474                 inPlaylistTag = false;
00475             }
00476             //We leave the infinte loop, if twe have the closing "PLAYLIST" tag
00477             if(xml.name() == "PLAYLISTS")
00478             {
00479                 break;
00480             }
00481         }
00482     }
00483     return rootItem;
00484 }
00485 
00486 void TraktorFeature::parsePlaylistEntries(QXmlStreamReader &xml,QString playlist_path, QSqlQuery query_insert_into_playlist, QSqlQuery query_insert_into_playlisttracks)
00487 {
00488     // In the database, the name of a playlist is specified by the unique path, e.g., /someFolderA/someFolderB/playlistA"
00489     query_insert_into_playlist.bindValue(":name", playlist_path);
00490     bool success = query_insert_into_playlist.exec();
00491     if(!success){
00492         qDebug() << "SQL Error in TraktorTableModel.cpp: line" << __LINE__ << " " << query_insert_into_playlist.lastError();
00493         return;
00494     }
00495     //Get playlist id
00496     QSqlQuery id_query(m_database);
00497     id_query.prepare("select id from traktor_playlists where name=:path");
00498     id_query.bindValue(":path", playlist_path);
00499     success = id_query.exec();
00500 
00501     int playlist_id = -1;
00502     int playlist_position = 1;
00503     if(success){
00504         //playlist_id = id_query.lastInsertId().toInt();
00505         while (id_query.next()) {
00506             playlist_id = id_query.value(id_query.record().indexOf("id")).toInt();
00507         }
00508     }
00509     else
00510         qDebug() << "SQL Error in TraktorTableModel.cpp: line" << __LINE__ << " " << id_query.lastError();
00511 
00512     while(!xml.atEnd() && !m_cancelImport) {
00513         //read next XML element
00514         xml.readNext();
00515         if(xml.isStartElement())
00516         {
00517             if(xml.name() == "PRIMARYKEY"){
00518                 QXmlStreamAttributes attr = xml.attributes();
00519                 QString key = attr.value("KEY").toString();
00520                 QString type = attr.value("TYPE").toString();
00521                 if(type == "TRACK")
00522                 {
00523                     key.replace(QString(":"), QString(""));
00524                     //TODO: IFDEF
00525                     #if defined(__WINDOWS__)
00526                     key.insert(1,":");
00527                     #else
00528                     key.prepend("/Volumes/");
00529                     #endif
00530 
00531                     //insert to database
00532                     int track_id = -1;
00533                     QSqlQuery finder_query(m_database);
00534                     finder_query.prepare("select id from traktor_library where location=:path");
00535                     finder_query.bindValue(":path", key);
00536                     success = finder_query.exec();
00537 
00538                     if(success){
00539                         while (finder_query.next()) {
00540                             track_id = finder_query.value(finder_query.record().indexOf("id")).toInt();
00541                         }
00542                     }
00543                     else
00544                         qDebug() << "SQL Error in TraktorTableModel.cpp: line" << __LINE__ << " " << finder_query.lastError();
00545 
00546                     query_insert_into_playlisttracks.bindValue(":playlist_id", playlist_id);
00547                     query_insert_into_playlisttracks.bindValue(":track_id", track_id);
00548                     query_insert_into_playlisttracks.bindValue(":position", playlist_position++);
00549                     success = query_insert_into_playlisttracks.exec();
00550                     if(!success){
00551                         qDebug() << "SQL Error in TraktorFeature.cpp: line" << __LINE__ << " " << query_insert_into_playlisttracks.lastError();
00552                         qDebug() << "trackid" << track_id << " with path " << key;
00553                         qDebug() << "playlistname; " << playlist_path <<" with ID " << playlist_id;
00554                         qDebug() << "-----------------";
00555                     }
00556                 }
00557             }
00558         }
00559         if(xml.isEndElement()){
00560             //We leave the infinte loop, if twe have the closing "PLAYLIST" tag
00561             if(xml.name() == "PLAYLIST")
00562             {
00563                 break;
00564             }
00565         }
00566     }
00567 }
00568 
00569 void TraktorFeature::clearTable(QString table_name)
00570 {
00571     QSqlQuery query(m_database);
00572     query.prepare("delete from "+table_name);
00573     bool success = query.exec();
00574 
00575     if(!success)
00576         qDebug() << "Could not delete remove old entries from table " << table_name << " : " << query.lastError();
00577     else
00578         qDebug() << "Traktor table entries of '" << table_name <<"' have been cleared.";
00579 }
00580 
00581 QString TraktorFeature::getTraktorMusicDatabase()
00582 {
00583     QString musicFolder = "";
00584 
00585     /*
00586      * As of version 2, Traktor has changed the path of the collection.nml
00587      * In general, the path is <Home>/Documents/Native Instruments/Traktor 2.x.y/collection.nml
00588      *  where x and y denote the bug fix release numbers. For example, Traktor 2.0.3 has the
00589      * following path: <Home>/Documents/Native Instruments/Traktor 2.0.3/collection.nml
00590      */
00591 
00592     //Let's try to detect the latest Traktor version and its collection.nml
00593     QString myDocuments = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
00594     QDir ni_directory(myDocuments +"/Native Instruments/");
00595     ni_directory.setFilter(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks) ;
00596 
00597     //Iterate over the subfolders
00598     QFileInfoList list = ni_directory.entryInfoList();
00599     QMap<int, QString> installed_ts_map;
00600 
00601     for (int i = 0; i < list.size(); ++i) {
00602         QFileInfo fileInfo = list.at(i);
00603         QString folder_name = fileInfo.fileName();
00604 
00605         if(folder_name == "Traktor"){
00606             //We found a Traktor 1 installation
00607             installed_ts_map.insert(1, fileInfo.absoluteFilePath());
00608             continue;
00609         }
00610         if(folder_name.contains("Traktor"))
00611         {
00612             qDebug() << "Found " << folder_name;
00613             QVariant sVersion = folder_name.right(5).remove(".");
00614             if(sVersion.canConvert<int>())
00615             {
00616                 installed_ts_map.insert(sVersion.toInt(), fileInfo.absoluteFilePath());
00617             }
00618         }
00619     }
00620     //If no Traktor installation has been found, return some default string
00621     if(installed_ts_map.isEmpty()){
00622         musicFolder =  QDir::homePath() + "/collection.nml";
00623     }
00624     else  //Select the folder with the highest version as default Traktor folder
00625     {
00626         QList<int> versions = installed_ts_map.keys();
00627         qSort(versions);
00628         musicFolder = installed_ts_map.value(versions.last()) + "/collection.nml";
00629 
00630     }
00631     qDebug() << "Traktor Library Location=[" << musicFolder << "]";
00632     return musicFolder;
00633 }
00634 
00635 void TraktorFeature::onTrackCollectionLoaded() {
00636     TreeItem* root = m_future.result();
00637     if (root) {
00638         m_childModel.setRootItem(root);
00639 
00640         // Tell the rhythmbox track source that it should re-build its index.
00641         m_pTrackCollection->getTrackSource("traktor")->buildIndex();
00642 
00643         //m_pTraktorTableModel->select();
00644         emit(showTrackModel(m_pTraktorTableModel));
00645         qDebug() << "Traktor library loaded successfully";
00646     } else {
00647         QMessageBox::warning(
00648             NULL,
00649             tr("Error Loading Traktor Library"),
00650             tr("There was an error loading your Traktor library. Some of "
00651                "your Traktor tracks or playlists may not have loaded."));
00652     }
00653 
00654     // calls a slot in the sidebarmodel such that 'isLoading' is removed from the feature title.
00655     m_title = tr("Traktor");
00656     emit(featureLoadingFinished(this));
00657     activate();
00658 }
00659 
00660 void TraktorFeature::onLazyChildExpandation(const QModelIndex &index) {
00661     // Nothing to do because the childmodel is not of lazy nature.
00662 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines