![]() |
Mixxx
|
00001 // cratefeature.cpp 00002 // Created 10/22/2009 by RJ Ryan (rryan@mit.edu) 00003 00004 #include <QInputDialog> 00005 #include <QMenu> 00006 #include <QLineEdit> 00007 00008 #include "library/cratefeature.h" 00009 #include "library/parser.h" 00010 #include "library/parserm3u.h" 00011 #include "library/parserpls.h" 00012 00013 #include "library/cratetablemodel.h" 00014 #include "library/trackcollection.h" 00015 #include "widget/wlibrarytextbrowser.h" 00016 #include "widget/wlibrary.h" 00017 #include "widget/wlibrarysidebar.h" 00018 #include "mixxxkeyboard.h" 00019 #include "treeitem.h" 00020 #include "soundsourceproxy.h" 00021 00022 CrateFeature::CrateFeature(QObject* parent, 00023 TrackCollection* pTrackCollection, ConfigObject<ConfigValue>* pConfig) 00024 : m_pTrackCollection(pTrackCollection), 00025 m_crateListTableModel(this, pTrackCollection->getDatabase()), 00026 m_pConfig(pConfig), 00027 m_crateTableModel(this, pTrackCollection) { 00028 m_pCreateCrateAction = new QAction(tr("New Crate"),this); 00029 connect(m_pCreateCrateAction, SIGNAL(triggered()), 00030 this, SLOT(slotCreateCrate())); 00031 00032 m_pDeleteCrateAction = new QAction(tr("Remove"),this); 00033 connect(m_pDeleteCrateAction, SIGNAL(triggered()), 00034 this, SLOT(slotDeleteCrate())); 00035 00036 m_pRenameCrateAction = new QAction(tr("Rename"),this); 00037 connect(m_pRenameCrateAction, SIGNAL(triggered()), 00038 this, SLOT(slotRenameCrate())); 00039 00040 m_pLockCrateAction = new QAction(tr("Lock"),this); 00041 connect(m_pLockCrateAction, SIGNAL(triggered()), 00042 this, SLOT(slotToggleCrateLock())); 00043 00044 m_pImportPlaylistAction = new QAction(tr("Import Crate"),this); 00045 connect(m_pImportPlaylistAction, SIGNAL(triggered()), 00046 this, SLOT(slotImportPlaylist())); 00047 m_pExportPlaylistAction = new QAction(tr("Export Crate"), this); 00048 connect(m_pExportPlaylistAction, SIGNAL(triggered()), 00049 this, SLOT(slotExportPlaylist())); 00050 00051 m_crateListTableModel.setTable("crates"); 00052 m_crateListTableModel.setSort(m_crateListTableModel.fieldIndex("name"), 00053 Qt::AscendingOrder); 00054 m_crateListTableModel.setFilter("show = 1"); 00055 m_crateListTableModel.select(); 00056 00057 // construct child model 00058 TreeItem *rootItem = new TreeItem(); 00059 m_childModel.setRootItem(rootItem); 00060 constructChildModel(); 00061 } 00062 00063 CrateFeature::~CrateFeature() { 00064 //delete QActions 00065 delete m_pCreateCrateAction; 00066 delete m_pDeleteCrateAction; 00067 delete m_pRenameCrateAction; 00068 delete m_pLockCrateAction; 00069 delete m_pImportPlaylistAction; 00070 } 00071 00072 QVariant CrateFeature::title() { 00073 return tr("Crates"); 00074 } 00075 00076 QIcon CrateFeature::getIcon() { 00077 return QIcon(":/images/library/ic_library_crates.png"); 00078 } 00079 00080 bool CrateFeature::dropAccept(QUrl url) { 00081 return false; 00082 } 00083 00084 bool CrateFeature::dropAcceptChild(const QModelIndex& index, QUrl url) { 00085 QString crateName = index.data().toString(); 00086 int crateId = m_pTrackCollection->getCrateDAO().getCrateIdByName(crateName); 00087 00088 //XXX: See the comment in PlaylistFeature::dropAcceptChild() about 00089 // QUrl::toLocalFile() vs. QUrl::toString() usage. 00090 QFileInfo file(url.toLocalFile()); 00091 00092 // Adds track, does not insert duplicates, handles unremoving logic. 00093 int trackId = m_pTrackCollection->getTrackDAO().addTrack(file, true); 00094 00095 qDebug() << "CrateFeature::dropAcceptChild adding track" 00096 << trackId << "to crate" << crateId; 00097 00098 CrateDAO& crateDao = m_pTrackCollection->getCrateDAO(); 00099 00100 if (trackId >= 0) 00101 return crateDao.addTrackToCrate(trackId, crateId); 00102 return false; 00103 } 00104 00105 bool CrateFeature::dragMoveAccept(QUrl url) { 00106 Q_UNUSED(url) 00107 return false; 00108 } 00109 00110 bool CrateFeature::dragMoveAcceptChild(const QModelIndex& index, QUrl url) { 00111 //TODO: Filter by supported formats regex and reject anything that doesn't match. 00112 QString crateName = index.data().toString(); 00113 CrateDAO& crateDao = m_pTrackCollection->getCrateDAO(); 00114 int crateId = crateDao.getCrateIdByName(crateName); 00115 bool locked = crateDao.isCrateLocked(crateId); 00116 00117 QFileInfo file(url.toLocalFile()); 00118 bool formatSupported = SoundSourceProxy::isFilenameSupported(file.fileName()); 00119 return !locked && formatSupported; 00120 } 00121 00122 void CrateFeature::bindWidget(WLibrarySidebar* sidebarWidget, 00123 WLibrary* libraryWidget, 00124 MixxxKeyboard* keyboard) { 00125 Q_UNUSED(sidebarWidget); 00126 Q_UNUSED(keyboard); 00127 WLibraryTextBrowser* edit = new WLibraryTextBrowser(libraryWidget); 00128 connect(this, SIGNAL(showPage(const QUrl&)), 00129 edit, SLOT(setSource(const QUrl&))); 00130 libraryWidget->registerView("CRATEHOME", edit); 00131 } 00132 00133 TreeItemModel* CrateFeature::getChildModel() { 00134 return &m_childModel; 00135 } 00136 00137 void CrateFeature::activate() { 00138 emit(showPage(QUrl("qrc:/html/crates.html"))); 00139 emit(switchToView("CRATEHOME")); 00140 } 00141 00142 void CrateFeature::activateChild(const QModelIndex& index) { 00143 if (!index.isValid()) 00144 return; 00145 QString crateName = index.data().toString(); 00146 int crateId = m_pTrackCollection->getCrateDAO().getCrateIdByName(crateName); 00147 m_crateTableModel.setCrate(crateId); 00148 emit(showTrackModel(&m_crateTableModel)); 00149 } 00150 00151 void CrateFeature::onRightClick(const QPoint& globalPos) { 00152 m_lastRightClickedIndex = QModelIndex(); 00153 QMenu menu(NULL); 00154 menu.addAction(m_pCreateCrateAction); 00155 menu.exec(globalPos); 00156 } 00157 00158 void CrateFeature::onRightClickChild(const QPoint& globalPos, QModelIndex index) { 00159 //Save the model index so we can get it in the action slots... 00160 m_lastRightClickedIndex = index; 00161 00162 QString crateName = index.data().toString(); 00163 CrateDAO& crateDAO = m_pTrackCollection->getCrateDAO(); 00164 int crateId = crateDAO.getCrateIdByName(crateName); 00165 00166 bool locked = crateDAO.isCrateLocked(crateId); 00167 00168 m_pDeleteCrateAction->setEnabled(!locked); 00169 m_pRenameCrateAction->setEnabled(!locked); 00170 00171 m_pLockCrateAction->setText(locked ? tr("Unlock") : tr("Lock")); 00172 00173 QMenu menu(NULL); 00174 menu.addAction(m_pCreateCrateAction); 00175 menu.addSeparator(); 00176 menu.addAction(m_pRenameCrateAction); 00177 menu.addAction(m_pDeleteCrateAction); 00178 menu.addAction(m_pLockCrateAction); 00179 menu.addSeparator(); 00180 menu.addAction(m_pImportPlaylistAction); 00181 menu.addAction(m_pExportPlaylistAction); 00182 menu.exec(globalPos); 00183 } 00184 00185 void CrateFeature::slotCreateCrate() { 00186 00187 QString name; 00188 bool validNameGiven = false; 00189 CrateDAO& crateDao = m_pTrackCollection->getCrateDAO(); 00190 00191 do { 00192 bool ok = false; 00193 name = QInputDialog::getText(NULL, 00194 tr("New Crate"), 00195 tr("Crate name:"), 00196 QLineEdit::Normal, tr("New Crate"), 00197 &ok).trimmed(); 00198 00199 if (!ok) 00200 return; 00201 00202 int existingId = crateDao.getCrateIdByName(name); 00203 00204 if (existingId != -1) { 00205 QMessageBox::warning(NULL, 00206 tr("Creating Crate Failed"), 00207 tr("A crate by that name already exists.")); 00208 } 00209 else if (name.isEmpty()) { 00210 QMessageBox::warning(NULL, 00211 tr("Creating Crate Failed"), 00212 tr("A crate cannot have a blank name.")); 00213 } 00214 else { 00215 validNameGiven = true; 00216 } 00217 00218 } while (!validNameGiven); 00219 00220 bool crateCreated = crateDao.createCrate(name); 00221 00222 if (crateCreated) { 00223 clearChildModel(); 00224 m_crateListTableModel.select(); 00225 constructChildModel(); 00226 // Switch to the new crate. 00227 int crate_id = crateDao.getCrateIdByName(name); 00228 m_crateTableModel.setCrate(crate_id); 00229 emit(showTrackModel(&m_crateTableModel)); 00230 // TODO(XXX) set sidebar selection 00231 emit(featureUpdated()); 00232 } else { 00233 qDebug() << "Error creating crate with name " << name; 00234 QMessageBox::warning(NULL, 00235 tr("Creating Crate Failed"), 00236 tr("An unknown error occurred while creating crate: ") 00237 + name); 00238 00239 } 00240 } 00241 00242 void CrateFeature::slotDeleteCrate() { 00243 QString crateName = m_lastRightClickedIndex.data().toString(); 00244 CrateDAO &crateDao = m_pTrackCollection->getCrateDAO(); 00245 int crateId = crateDao.getCrateIdByName(crateName); 00246 bool locked = crateDao.isCrateLocked(crateId); 00247 00248 if (locked) { 00249 qDebug() << "Skipping crate deletion because crate" << crateId << "is locked."; 00250 return; 00251 } 00252 00253 bool deleted = crateDao.deleteCrate(crateId); 00254 00255 if (deleted) { 00256 clearChildModel(); 00257 m_crateListTableModel.select(); 00258 constructChildModel(); 00259 emit(featureUpdated()); 00260 } else { 00261 qDebug() << "Failed to delete crateId" << crateId; 00262 } 00263 } 00264 00265 void CrateFeature::slotRenameCrate() { 00266 QString oldName = m_lastRightClickedIndex.data().toString(); 00267 CrateDAO &crateDao = m_pTrackCollection->getCrateDAO(); 00268 int crateId = crateDao.getCrateIdByName(oldName); 00269 bool locked = crateDao.isCrateLocked(crateId); 00270 00271 if (locked) { 00272 qDebug() << "Skipping crate rename because crate" << crateId << "is locked."; 00273 return; 00274 } 00275 00276 QString newName; 00277 bool validNameGiven = false; 00278 00279 do { 00280 bool ok = false; 00281 newName = QInputDialog::getText(NULL, 00282 tr("Rename Crate"), 00283 tr("New crate name:"), 00284 QLineEdit::Normal, 00285 oldName, 00286 &ok).trimmed(); 00287 00288 if (!ok || newName == oldName) { 00289 return; 00290 } 00291 00292 int existingId = m_pTrackCollection->getCrateDAO().getCrateIdByName(newName); 00293 00294 if (existingId != -1) { 00295 QMessageBox::warning(NULL, 00296 tr("Renaming Crate Failed"), 00297 tr("A crate by that name already exists.")); 00298 } 00299 else if (newName.isEmpty()) { 00300 QMessageBox::warning(NULL, 00301 tr("Renaming Crate Failed"), 00302 tr("A crate cannot have a blank name.")); 00303 } 00304 else { 00305 validNameGiven = true; 00306 } 00307 } while (!validNameGiven); 00308 00309 00310 if (m_pTrackCollection->getCrateDAO().renameCrate(crateId, newName)) { 00311 clearChildModel(); 00312 m_crateListTableModel.select(); 00313 constructChildModel(); 00314 emit(featureUpdated()); 00315 m_crateTableModel.setCrate(crateId); 00316 } else { 00317 qDebug() << "Failed to rename crateId" << crateId; 00318 } 00319 } 00320 00321 void CrateFeature::slotToggleCrateLock() 00322 { 00323 QString crateName = m_lastRightClickedIndex.data().toString(); 00324 CrateDAO& crateDAO = m_pTrackCollection->getCrateDAO(); 00325 int crateId = crateDAO.getCrateIdByName(crateName); 00326 bool locked = !crateDAO.isCrateLocked(crateId); 00327 00328 if (!crateDAO.setCrateLocked(crateId, locked)) { 00329 qDebug() << "Failed to toggle lock of crateId " << crateId; 00330 } 00331 00332 TreeItem* crateItem = m_childModel.getItem(m_lastRightClickedIndex); 00333 crateItem->setIcon( 00334 locked ? QIcon(":/images/library/ic_library_locked.png") : QIcon()); 00335 } 00336 00337 00343 void CrateFeature::constructChildModel() 00344 { 00345 QList<TreeItem*> data_list; 00346 int nameColumn = m_crateListTableModel.record().indexOf("name"); 00347 int idColumn = m_crateListTableModel.record().indexOf("id"); 00348 //Access the invisible root item 00349 TreeItem* root = m_childModel.getItem(QModelIndex()); 00350 CrateDAO &crateDao = m_pTrackCollection->getCrateDAO(); 00351 00352 for (int row = 0; row < m_crateListTableModel.rowCount(); ++row) { 00353 QModelIndex ind = m_crateListTableModel.index(row, nameColumn); 00354 QString crate_name = m_crateListTableModel.data(ind).toString(); 00355 ind = m_crateListTableModel.index(row, idColumn); 00356 int crate_id = m_crateListTableModel.data(ind).toInt(); 00357 00358 //Create the TreeItem whose parent is the invisible root item 00359 TreeItem* item = new TreeItem(crate_name, crate_name, this, root); 00360 bool locked = crateDao.isCrateLocked(crate_id); 00361 item->setIcon(locked ? QIcon(":/images/library/ic_library_locked.png") : QIcon()); 00362 data_list.append(item); 00363 } 00364 //Append all the newly created TreeItems in a dynamic way to the childmodel 00365 m_childModel.insertRows(data_list, 0, m_crateListTableModel.rowCount()); 00366 } 00367 00371 void CrateFeature::clearChildModel() 00372 { 00373 m_childModel.removeRows(0,m_crateListTableModel.rowCount()); 00374 } 00375 00376 void CrateFeature::slotImportPlaylist() 00377 { 00378 qDebug() << "slotImportPlaylist() row:" ; //<< m_lastRightClickedIndex.data(); 00379 00380 00381 QString playlist_file = QFileDialog::getOpenFileName( 00382 NULL, 00383 tr("Import Playlist"), 00384 QDesktopServices::storageLocation(QDesktopServices::MusicLocation), 00385 tr("Playlist Files (*.m3u *.m3u8 *.pls)")); 00386 // Exit method if user cancelled the open dialog. 00387 if (playlist_file.isNull() || playlist_file.isEmpty() ) return; 00388 00389 Parser* playlist_parser = NULL; 00390 00391 if (playlist_file.endsWith(".m3u", Qt::CaseInsensitive) || 00392 playlist_file.endsWith(".m3u8", Qt::CaseInsensitive)) { 00393 // .m3u8 is Utf8 representation of an m3u playlist 00394 playlist_parser = new ParserM3u(); 00395 } else if (playlist_file.endsWith(".pls", Qt::CaseInsensitive)) { 00396 playlist_parser = new ParserPls(); 00397 } else { 00398 return; 00399 } 00400 00401 QList<QString> entries = playlist_parser->parse(playlist_file); 00402 //qDebug() << "Size of Imported Playlist: " << entries.size(); 00403 00404 //Iterate over the List that holds URLs of playlist entires 00405 for (int i = 0; i < entries.size(); ++i) { 00406 m_crateTableModel.addTrack(QModelIndex(), entries[i]); 00407 //qDebug() << "Playlist entry: " << entries[i]; 00408 } 00409 00410 //delete the parser object 00411 if(playlist_parser) 00412 delete playlist_parser; 00413 } 00414 00415 void CrateFeature::onLazyChildExpandation(const QModelIndex &index){ 00416 Q_UNUSED(index); 00417 //Nothing to do because the childmodel is not of lazy nature. 00418 } 00419 00420 void CrateFeature::slotExportPlaylist(){ 00421 qDebug() << "Export crate" << m_lastRightClickedIndex.data(); 00422 QString file_location = QFileDialog::getSaveFileName( 00423 NULL, 00424 tr("Export Crate"), 00425 QDesktopServices::storageLocation(QDesktopServices::MusicLocation), 00426 tr("M3U Playlist (*.m3u);;M3U8 Playlist (*.m3u8);;PLS Playlist (*.pls)")); 00427 // Exit method if user cancelled the open dialog. 00428 if (file_location.isNull() || file_location.isEmpty()) 00429 return; 00430 // check config if relative paths are desired 00431 bool useRelativePath = static_cast<bool>( 00432 m_pConfig->getValueString( 00433 ConfigKey("[Library]", "UseRelativePathOnExport")).toInt()); 00434 00435 // Create and populate a list of files of the crate 00436 QList<QString> playlist_items; 00437 // Create a new table model since the main one might have an active search. 00438 QScopedPointer<CrateTableModel> pCrateTableModel( 00439 new CrateTableModel(this, m_pTrackCollection)); 00440 pCrateTableModel->setCrate(m_crateTableModel.getCrate()); 00441 pCrateTableModel->select(); 00442 int rows = pCrateTableModel->rowCount(); 00443 for (int i = 0; i < rows; ++i) { 00444 QModelIndex index = pCrateTableModel->index(i, 0); 00445 playlist_items << pCrateTableModel->getTrackLocation(index); 00446 } 00447 00448 if (file_location.endsWith(".pls", Qt::CaseInsensitive)) { 00449 ParserPls::writePLSFile(file_location, playlist_items, useRelativePath); 00450 } else if (file_location.endsWith(".m3u8", Qt::CaseInsensitive)) { 00451 ParserM3u::writeM3U8File(file_location, playlist_items, 00452 useRelativePath); 00453 } else { 00454 // Default export to M3U if file extension is missing 00455 if (!file_location.endsWith(".m3u", Qt::CaseInsensitive)) { 00456 qDebug() << "Crate export: No valid file extension specified. Appending .m3u " 00457 << "and exporting to M3U."; 00458 file_location.append(".m3u"); 00459 } 00460 ParserM3u::writeM3UFile(file_location, playlist_items, useRelativePath); 00461 } 00462 } 00463