Merge lp:~daschuer/mixxx/features_setlog into lp:~mixxxdevelopers/mixxx/trunk

Proposed by Daniel Schürmann on 2011-10-10
Status: Merged
Merged at revision: 2990
Proposed branch: lp:~daschuer/mixxx/features_setlog
Merge into: lp:~mixxxdevelopers/mixxx/trunk
Diff against target: 2721 lines (+1490/-224)
32 files modified
mixxx/build/depends.py (+26/-4)
mixxx/lib/soundtouch-1.6.0/STTypes.h (+1/-1)
mixxx/res/html/setlogs.html (+10/-0)
mixxx/res/mixxx.qrc (+3/-0)
mixxx/src/basetrackplayer.cpp (+1/-1)
mixxx/src/dlgautodj.cpp (+1/-1)
mixxx/src/library/cratefeature.cpp (+106/-82)
mixxx/src/library/cratefeature.h (+4/-1)
mixxx/src/library/dao/cratedao.cpp (+5/-4)
mixxx/src/library/dao/cratedao.h (+3/-1)
mixxx/src/library/dao/playlistdao.cpp (+143/-29)
mixxx/src/library/dao/playlistdao.h (+23/-5)
mixxx/src/library/legacylibraryimporter.cpp (+1/-2)
mixxx/src/library/library.cpp (+2/-0)
mixxx/src/library/libraryfeature.h (+2/-1)
mixxx/src/library/parser.cpp (+1/-1)
mixxx/src/library/parsercsv.cpp (+238/-0)
mixxx/src/library/parsercsv.h (+41/-0)
mixxx/src/library/playlistfeature.cpp (+113/-57)
mixxx/src/library/playlistfeature.h (+5/-1)
mixxx/src/library/playlisttablemodel.cpp (+12/-0)
mixxx/src/library/playlisttablemodel.h (+1/-0)
mixxx/src/library/preparefeature.cpp (+13/-2)
mixxx/src/library/setlogfeature.cpp (+571/-0)
mixxx/src/library/setlogfeature.h (+93/-0)
mixxx/src/library/sidebarmodel.cpp (+50/-26)
mixxx/src/library/sidebarmodel.h (+1/-1)
mixxx/src/library/trackcollection.cpp (+1/-1)
mixxx/src/mixxx.cpp (+1/-1)
mixxx/src/trackinfoobject.cpp (+11/-2)
mixxx/src/trackinfoobject.h (+2/-0)
mixxx/src/widget/wlibrarysidebar.cpp (+5/-0)
To merge this branch: bzr merge lp:~daschuer/mixxx/features_setlog
Reviewer Review Type Date Requested Status
Mixxx Development Team 2011-10-10 Pending
Review via email: mp+78882@code.launchpad.net

Description of the change

This is an implementation of set log, a playlist filled with all the played songs from last DJ set.

Features:
* add a set log to Auto DJ
* export a setlog as m3u, m3u8, and pls playlist
* export a setlog as readable text and csv
* merge two set logs in case of mixxx restart

To post a comment you must log in.
lp:~daschuer/mixxx/features_setlog updated on 2011-10-12
2852. By Daniel Schürmann on 2011-10-12

merged with lp:mixxx

jus (jus) wrote :

Tested your branch and would like to few notes:
* In the treeview`s set log submenu sort from newest to oldest entry, currently it`s sorted from oldest to newest. The session that is currently running session should be on 1st position.
* The new icons (setlog & running session ) need some work, I can take care of it if you like.

* We had a small user survey about what format Mixxx users would like to generate session playlists in @ http://mixxx.org/forums/viewtopic.php?f=1&t=2463&p=9265 , txt/csv got the most votes. Is it possible with reasonable effort to add txt/csv export to your branch? That would increase setlog`s usability for professional application (think GEMA/ASCAP/BMI set lists).

Thanks for all your work,
jus

Daniel Schürmann (daschuer) wrote :

Hi Jus, Thank you for testing.

* I have decided to put the most recent setlog to the bottom because the most recent track is also at the bottom. I notice also that its a bit annoying to scroll to bottom. if you have a long list of set logs. If I find time, I will add a sorting option to context menu. Additional sub folder for archived set logs might be also a good idea.

* I will be happy, if you create improved icons.

* Are you able to provide me example GEMA/ASCAP/BMI set lists?

Kind regards,

Daniel

jus (jus) wrote :

There are various application options for Mixxx (playing live, broadcasting, making DJ tapes..) which may follow different rules for the report of royalties. They likely even differ from country to country.

The assistance we can give with the set log feature is to allow the setlist to be exported in a standard compliant format. I really think we are ok with txt/csv export which includes the important song metadata (like Title,Artist,Album,Composer) + the time/date that any song was played.

lp:~daschuer/mixxx/features_setlog updated on 2011-10-22
2853. By Daniel Schürmann on 2011-10-22

merged with lp:mixxx

Don't forget record label information as well, if present. (Required for US radio reporting.)

lp:~daschuer/mixxx/features_setlog updated on 2011-12-10
2854. By Daniel Schürmann on 2011-10-31

merged from lp:mixxx

2855. By Daniel Schürmann on 2011-11-06

merged with lp:mixxx

2856. By Daniel Schürmann on 2011-11-22

csv und m3u8 import and export

2857. By Daniel Schürmann on 2011-11-30

Readable text export

2858. By Daniel Schürmann on 2011-12-01

export position in 01. format

2859. By Daniel Schürmann on 2011-12-10

merged with lp:mixxx

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'mixxx/build/depends.py'
2--- mixxx/build/depends.py 2011-11-08 20:10:32 +0000
3+++ mixxx/build/depends.py 2011-12-10 16:03:24 +0000
4@@ -233,23 +233,43 @@
5 '#lib/%s/FIFOSampleBuffer.cpp' % self.SOUNDTOUCH_PATH,
6 '#lib/%s/FIRFilter.cpp' % self.SOUNDTOUCH_PATH,
7 '#lib/%s/PeakFinder.cpp' % self.SOUNDTOUCH_PATH,
8- '#lib/%s/BPMDetect.cpp' % self.SOUNDTOUCH_PATH,
9- '#lib/%s/mmx_optimized.cpp' % self.SOUNDTOUCH_PATH,
10- '#lib/%s/sse_optimized.cpp' % self.SOUNDTOUCH_PATH,]
11+ '#lib/%s/BPMDetect.cpp' % self.SOUNDTOUCH_PATH]
12
13 # SoundTouch CPU optimizations are only for x86
14 # architectures. SoundTouch automatically ignores these files when it is
15 # not being built for an architecture that supports them.
16- cpu_detection = '#lib/%s/cpu_detect_x86_win.cpp' if build.toolchain_is_msvs else '#lib/%s/cpu_detect_x86_gcc.cpp'
17+ cpu_detection = '#lib/%s/cpu_detect_x86_win.cpp' if build.toolchain_is_msvs else \
18+ '#lib/%s/cpu_detect_x86_gcc.cpp'
19 sources.append(cpu_detection % self.SOUNDTOUCH_PATH)
20+
21+ # Check if the compiler has SSE extention enabled
22+ # Allways the case on x64 (core instructions)
23+ optimize = int(util.get_flags(build.env, 'optimize', 1))
24+ if build.machine_is_64bit or \
25+ (build.toolchain_is_msvs and optimize > 2) or \
26+ (build.toolchain_is_gnu and optimize > 1):
27+ sources.extend(
28+ ['#lib/%s/mmx_optimized.cpp' % self.SOUNDTOUCH_PATH,
29+ '#lib/%s/sse_optimized.cpp' % self.SOUNDTOUCH_PATH,
30+ ])
31+
32 return sources
33
34+
35 def configure(self, build, conf):
36 if build.platform_is_windows:
37 # Regardless of the bitwidth, ST checks for WIN32
38 build.env.Append(CPPDEFINES = 'WIN32')
39 build.env.Append(CPPPATH=['#lib/%s' % self.SOUNDTOUCH_PATH])
40
41+ # Check if the compiler has SSE extention enabled
42+ # Allways the case on x64 (core instructions)
43+ optimize = int(util.get_flags(build.env, 'optimize', 1))
44+ if build.machine_is_64bit or \
45+ (build.toolchain_is_msvs and optimize > 2) or \
46+ (build.toolchain_is_gnu and optimize > 1):
47+ build.env.Append(CPPDEFINES='SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS')
48+
49 class TagLib(Dependence):
50 def configure(self, build, conf):
51 if not conf.CheckLib('tag'):
52@@ -420,6 +440,7 @@
53 "library/autodjfeature.cpp",
54 "library/mixxxlibraryfeature.cpp",
55 "library/playlistfeature.cpp",
56+ "library/setlogfeature.cpp",
57
58 "library/browse/browsetablemodel.cpp",
59 "library/browse/browsethread.cpp",
60@@ -477,6 +498,7 @@
61 "library/parser.cpp",
62 "library/parserpls.cpp",
63 "library/parserm3u.cpp",
64+ "library/parsercsv.cpp",
65
66 "bpm/bpmscheme.cpp",
67
68
69=== modified file 'mixxx/lib/soundtouch-1.6.0/STTypes.h'
70--- mixxx/lib/soundtouch-1.6.0/STTypes.h 2011-07-24 21:30:08 +0000
71+++ mixxx/lib/soundtouch-1.6.0/STTypes.h 2011-12-10 16:03:24 +0000
72@@ -95,7 +95,7 @@
73 /// routines compiled for whatever reason, you may disable these optimizations
74 /// to make the library compile.
75
76- #define SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS 1
77+ // #define SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS 1
78
79 #endif
80
81
82=== added file 'mixxx/res/html/setlogs.html'
83--- mixxx/res/html/setlogs.html 1970-01-01 00:00:00 +0000
84+++ mixxx/res/html/setlogs.html 2011-12-10 16:03:24 +0000
85@@ -0,0 +1,10 @@
86+<h2>Set Logs</h2>
87+<table border="0" cellpadding="5">
88+ <tr>
89+ <td>
90+ <p>Set Logs are automatic generated lists of your last DJ sets.</p>
91+ <p>Every time you start Mixxx, a new Set Log is generated. You can export it as m3u playlist or play it again with Auto DJ.</p>
92+ <p>When you restart mixxx you can join the current Set Log with the previous one. The track played flags are restored.</p>
93+ </td>
94+ </tr>
95+</table>
96
97=== added file 'mixxx/res/images/library/ic_library_setlog.png'
98Binary files mixxx/res/images/library/ic_library_setlog.png 1970-01-01 00:00:00 +0000 and mixxx/res/images/library/ic_library_setlog.png 2011-12-10 16:03:24 +0000 differ
99=== added file 'mixxx/res/images/library/ic_library_setlog_current.png'
100Binary files mixxx/res/images/library/ic_library_setlog_current.png 1970-01-01 00:00:00 +0000 and mixxx/res/images/library/ic_library_setlog_current.png 2011-12-10 16:03:24 +0000 differ
101=== modified file 'mixxx/res/mixxx.qrc'
102--- mixxx/res/mixxx.qrc 2011-11-01 16:14:41 +0000
103+++ mixxx/res/mixxx.qrc 2011-12-10 16:03:24 +0000
104@@ -2,6 +2,7 @@
105 <qresource prefix="/">
106 <file>html/crates.html</file>
107 <file>html/playlists.html</file>
108+ <file>html/setlogs.html</file>
109 <file>images/mixxx-icon.png</file>
110 <file>images/ic_mixxx_window.png</file>
111 <file>images/templates/logo_mixxx.png</file>
112@@ -32,6 +33,8 @@
113 <file>images/library/ic_library_rhythmbox.png</file>
114 <file>images/library/ic_library_traktor.png</file>
115 <file>images/library/ic_library_recordings.png</file>
116+ <file>images/library/ic_library_setlog.png</file>
117+ <file>images/library/ic_library_setlog_current.png</file>
118 <file>translations/mixxx_ar.qm</file>
119 <file>translations/mixxx_bg.qm</file>
120 <file>translations/mixxx_br.qm</file>
121
122=== modified file 'mixxx/src/basetrackplayer.cpp'
123--- mixxx/src/basetrackplayer.cpp 2011-11-26 06:41:02 +0000
124+++ mixxx/src/basetrackplayer.cpp 2011-12-10 16:03:24 +0000
125@@ -197,7 +197,7 @@
126 if(!m_pLoadedTrack->getHeaderParsed())
127 SoundSourceProxy::ParseHeader(m_pLoadedTrack.data());
128
129- m_pLoadedTrack->setPlayed(true);
130+ // m_pLoadedTrack->setPlayed(true); // Actually the song is loaded but not played
131
132 // Generate waveform summary
133 //TODO: Consider reworking this visual resample stuff... need to ask rryan about this -- Albert.
134
135=== modified file 'mixxx/src/dlgautodj.cpp'
136--- mixxx/src/dlgautodj.cpp 2011-11-30 06:19:47 +0000
137+++ mixxx/src/dlgautodj.cpp 2011-12-10 16:03:24 +0000
138@@ -38,7 +38,7 @@
139 "mixxx.db.model.autodj");
140 int playlistId = m_playlistDao.getPlaylistIdFromName(AUTODJ_TABLE);
141 if (playlistId < 0) {
142- m_playlistDao.createPlaylist(AUTODJ_TABLE, true);
143+ m_playlistDao.createPlaylist(AUTODJ_TABLE, PlaylistDAO::PLHT_AUTO_DJ);
144 playlistId = m_playlistDao.getPlaylistIdFromName(AUTODJ_TABLE);
145 }
146 m_pAutoDJTableModel->setPlaylist(playlistId);
147
148=== modified file 'mixxx/src/library/cratefeature.cpp'
149--- mixxx/src/library/cratefeature.cpp 2011-11-27 06:59:02 +0000
150+++ mixxx/src/library/cratefeature.cpp 2011-12-10 16:03:24 +0000
151@@ -9,6 +9,7 @@
152 #include "library/parser.h"
153 #include "library/parserm3u.h"
154 #include "library/parserpls.h"
155+#include "library/parsercsv.h"
156
157 #include "library/cratetablemodel.h"
158 #include "library/trackcollection.h"
159@@ -22,10 +23,12 @@
160 CrateFeature::CrateFeature(QObject* parent,
161 TrackCollection* pTrackCollection, ConfigObject<ConfigValue>* pConfig)
162 : m_pTrackCollection(pTrackCollection),
163+ m_crateDao(pTrackCollection->getCrateDAO()),
164 m_crateListTableModel(this, pTrackCollection->getDatabase()),
165- m_pConfig(pConfig),
166- m_crateTableModel(this, pTrackCollection) {
167- m_pCreateCrateAction = new QAction(tr("New Crate"),this);
168+ m_crateTableModel(this, pTrackCollection),
169+ m_pConfig(pConfig) {
170+ Q_UNUSED(parent);
171+ m_pCreateCrateAction = new QAction(tr("New Crate"),this);
172 connect(m_pCreateCrateAction, SIGNAL(triggered()),
173 this, SLOT(slotCreateCrate()));
174
175@@ -48,6 +51,19 @@
176 connect(m_pExportPlaylistAction, SIGNAL(triggered()),
177 this, SLOT(slotExportPlaylist()));
178
179+ connect(&m_crateDao, SIGNAL(added(int)),
180+ this, SLOT(slotCrateTableChanged(int)));
181+
182+ connect(&m_crateDao, SIGNAL(deleted(int)),
183+ this, SLOT(slotCrateTableChanged(int)));
184+
185+ connect(&m_crateDao, SIGNAL(renamed(int)),
186+ this, SLOT(slotCrateTableChanged(int)));
187+
188+ connect(&m_crateDao, SIGNAL(lockChanged(int)),
189+ this, SLOT(slotCrateTableChanged(int)));
190+
191+
192 m_crateListTableModel.setTable("crates");
193 m_crateListTableModel.setSort(m_crateListTableModel.fieldIndex("name"),
194 Qt::AscendingOrder);
195@@ -57,7 +73,7 @@
196 // construct child model
197 TreeItem *rootItem = new TreeItem();
198 m_childModel.setRootItem(rootItem);
199- constructChildModel();
200+ constructChildModel(-1);
201 }
202
203 CrateFeature::~CrateFeature() {
204@@ -78,12 +94,13 @@
205 }
206
207 bool CrateFeature::dropAccept(QUrl url) {
208+ Q_UNUSED(url)
209 return false;
210 }
211
212 bool CrateFeature::dropAcceptChild(const QModelIndex& index, QUrl url) {
213 QString crateName = index.data().toString();
214- int crateId = m_pTrackCollection->getCrateDAO().getCrateIdByName(crateName);
215+ int crateId = m_crateDao.getCrateIdByName(crateName);
216
217 //XXX: See the comment in PlaylistFeature::dropAcceptChild() about
218 // QUrl::toLocalFile() vs. QUrl::toString() usage.
219@@ -95,10 +112,8 @@
220 qDebug() << "CrateFeature::dropAcceptChild adding track"
221 << trackId << "to crate" << crateId;
222
223- CrateDAO& crateDao = m_pTrackCollection->getCrateDAO();
224-
225 if (trackId >= 0)
226- return crateDao.addTrackToCrate(trackId, crateId);
227+ return m_crateDao.addTrackToCrate(trackId, crateId);
228 return false;
229 }
230
231@@ -110,9 +125,8 @@
232 bool CrateFeature::dragMoveAcceptChild(const QModelIndex& index, QUrl url) {
233 //TODO: Filter by supported formats regex and reject anything that doesn't match.
234 QString crateName = index.data().toString();
235- CrateDAO& crateDao = m_pTrackCollection->getCrateDAO();
236- int crateId = crateDao.getCrateIdByName(crateName);
237- bool locked = crateDao.isCrateLocked(crateId);
238+ int crateId = m_crateDao.getCrateIdByName(crateName);
239+ bool locked = m_crateDao.isCrateLocked(crateId);
240
241 QFileInfo file(url.toLocalFile());
242 bool formatSupported = SoundSourceProxy::isFilenameSupported(file.fileName());
243@@ -143,7 +157,7 @@
244 if (!index.isValid())
245 return;
246 QString crateName = index.data().toString();
247- int crateId = m_pTrackCollection->getCrateDAO().getCrateIdByName(crateName);
248+ int crateId = m_crateDao.getCrateIdByName(crateName);
249 m_crateTableModel.setCrate(crateId);
250 emit(showTrackModel(&m_crateTableModel));
251 }
252@@ -160,10 +174,9 @@
253 m_lastRightClickedIndex = index;
254
255 QString crateName = index.data().toString();
256- CrateDAO& crateDAO = m_pTrackCollection->getCrateDAO();
257- int crateId = crateDAO.getCrateIdByName(crateName);
258+ int crateId = m_crateDao.getCrateIdByName(crateName);
259
260- bool locked = crateDAO.isCrateLocked(crateId);
261+ bool locked = m_crateDao.isCrateLocked(crateId);
262
263 m_pDeleteCrateAction->setEnabled(!locked);
264 m_pRenameCrateAction->setEnabled(!locked);
265@@ -186,7 +199,6 @@
266
267 QString name;
268 bool validNameGiven = false;
269- CrateDAO& crateDao = m_pTrackCollection->getCrateDAO();
270
271 do {
272 bool ok = false;
273@@ -199,7 +211,7 @@
274 if (!ok)
275 return;
276
277- int existingId = crateDao.getCrateIdByName(name);
278+ int existingId = m_crateDao.getCrateIdByName(name);
279
280 if (existingId != -1) {
281 QMessageBox::warning(NULL,
282@@ -217,17 +229,10 @@
283
284 } while (!validNameGiven);
285
286- bool crateCreated = crateDao.createCrate(name);
287+ int crate_id = m_crateDao.createCrate(name);
288
289- if (crateCreated) {
290- clearChildModel();
291- m_crateListTableModel.select();
292- constructChildModel();
293- // Switch to the new crate.
294- int crate_id = crateDao.getCrateIdByName(name);
295- m_crateTableModel.setCrate(crate_id);
296+ if (crate_id != -1) {
297 emit(showTrackModel(&m_crateTableModel));
298- // TODO(XXX) set sidebar selection
299 emit(featureUpdated());
300 } else {
301 qDebug() << "Error creating crate with name " << name;
302@@ -241,22 +246,19 @@
303
304 void CrateFeature::slotDeleteCrate() {
305 QString crateName = m_lastRightClickedIndex.data().toString();
306- CrateDAO &crateDao = m_pTrackCollection->getCrateDAO();
307- int crateId = crateDao.getCrateIdByName(crateName);
308- bool locked = crateDao.isCrateLocked(crateId);
309+ int crateId = m_crateDao.getCrateIdByName(crateName);
310+ bool locked = m_crateDao.isCrateLocked(crateId);
311
312 if (locked) {
313 qDebug() << "Skipping crate deletion because crate" << crateId << "is locked.";
314 return;
315 }
316
317- bool deleted = crateDao.deleteCrate(crateId);
318+ bool deleted = m_crateDao.deleteCrate(crateId);
319
320 if (deleted) {
321- clearChildModel();
322- m_crateListTableModel.select();
323- constructChildModel();
324 emit(featureUpdated());
325+ activate();
326 } else {
327 qDebug() << "Failed to delete crateId" << crateId;
328 }
329@@ -264,9 +266,8 @@
330
331 void CrateFeature::slotRenameCrate() {
332 QString oldName = m_lastRightClickedIndex.data().toString();
333- CrateDAO &crateDao = m_pTrackCollection->getCrateDAO();
334- int crateId = crateDao.getCrateIdByName(oldName);
335- bool locked = crateDao.isCrateLocked(crateId);
336+ int crateId = m_crateDao.getCrateIdByName(oldName);
337+ bool locked = m_crateDao.isCrateLocked(crateId);
338
339 if (locked) {
340 qDebug() << "Skipping crate rename because crate" << crateId << "is locked.";
341@@ -289,7 +290,7 @@
342 return;
343 }
344
345- int existingId = m_pTrackCollection->getCrateDAO().getCrateIdByName(newName);
346+ int existingId = m_crateDao.getCrateIdByName(newName);
347
348 if (existingId != -1) {
349 QMessageBox::warning(NULL,
350@@ -307,12 +308,8 @@
351 } while (!validNameGiven);
352
353
354- if (m_pTrackCollection->getCrateDAO().renameCrate(crateId, newName)) {
355- clearChildModel();
356- m_crateListTableModel.select();
357- constructChildModel();
358+ if (m_crateDao.renameCrate(crateId, newName)) {
359 emit(featureUpdated());
360- m_crateTableModel.setCrate(crateId);
361 } else {
362 qDebug() << "Failed to rename crateId" << crateId;
363 }
364@@ -321,17 +318,12 @@
365 void CrateFeature::slotToggleCrateLock()
366 {
367 QString crateName = m_lastRightClickedIndex.data().toString();
368- CrateDAO& crateDAO = m_pTrackCollection->getCrateDAO();
369- int crateId = crateDAO.getCrateIdByName(crateName);
370- bool locked = !crateDAO.isCrateLocked(crateId);
371+ int crateId = m_crateDao.getCrateIdByName(crateName);
372+ bool locked = !m_crateDao.isCrateLocked(crateId);
373
374- if (!crateDAO.setCrateLocked(crateId, locked)) {
375+ if (!m_crateDao.setCrateLocked(crateId, locked)) {
376 qDebug() << "Failed to toggle lock of crateId " << crateId;
377 }
378-
379- TreeItem* crateItem = m_childModel.getItem(m_lastRightClickedIndex);
380- crateItem->setIcon(
381- locked ? QIcon(":/images/library/ic_library_locked.png") : QIcon());
382 }
383
384
385@@ -340,14 +332,14 @@
386 * we require the sidebar model not to reset.
387 * This method queries the database and does dynamic insertion
388 */
389-void CrateFeature::constructChildModel()
390+QModelIndex CrateFeature::constructChildModel(int selected_id)
391 {
392- QList<TreeItem*> data_list;
393+ QList<TreeItem*> data_list;
394 int nameColumn = m_crateListTableModel.record().indexOf("name");
395 int idColumn = m_crateListTableModel.record().indexOf("id");
396- //Access the invisible root item
397+ int selected_row = -1;
398+ // Access the invisible root item
399 TreeItem* root = m_childModel.getItem(QModelIndex());
400- CrateDAO &crateDao = m_pTrackCollection->getCrateDAO();
401
402 for (int row = 0; row < m_crateListTableModel.rowCount(); ++row) {
403 QModelIndex ind = m_crateListTableModel.index(row, nameColumn);
404@@ -355,14 +347,23 @@
405 ind = m_crateListTableModel.index(row, idColumn);
406 int crate_id = m_crateListTableModel.data(ind).toInt();
407
408- //Create the TreeItem whose parent is the invisible root item
409+ if ( selected_id == crate_id) {
410+ // save index for selection
411+ selected_row = row; m_childModel.index(selected_row, 0);
412+ }
413+
414+ // Create the TreeItem whose parent is the invisible root item
415 TreeItem* item = new TreeItem(crate_name, crate_name, this, root);
416- bool locked = crateDao.isCrateLocked(crate_id);
417+ bool locked = m_crateDao.isCrateLocked(crate_id);
418 item->setIcon(locked ? QIcon(":/images/library/ic_library_locked.png") : QIcon());
419 data_list.append(item);
420 }
421- //Append all the newly created TreeItems in a dynamic way to the childmodel
422+ // Append all the newly created TreeItems in a dynamic way to the childmodel
423 m_childModel.insertRows(data_list, 0, m_crateListTableModel.rowCount());
424+ if (selected_row == -1) {
425+ return QModelIndex();
426+ }
427+ return m_childModel.index(selected_row, 0);
428 }
429
430 /**
431@@ -382,7 +383,7 @@
432 NULL,
433 tr("Import Playlist"),
434 QDesktopServices::storageLocation(QDesktopServices::MusicLocation),
435- tr("Playlist Files (*.m3u *.m3u8 *.pls)"));
436+ tr("Playlist Files (*.m3u *.m3u8 *.pls *.csv)"));
437 // Exit method if user cancelled the open dialog.
438 if (playlist_file.isNull() || playlist_file.isEmpty() ) return;
439
440@@ -394,6 +395,8 @@
441 playlist_parser = new ParserM3u();
442 } else if (playlist_file.endsWith(".pls", Qt::CaseInsensitive)) {
443 playlist_parser = new ParserPls();
444+ } else if (playlist_file.endsWith(".csv", Qt::CaseInsensitive)) {
445+ playlist_parser = new ParserCsv();
446 } else {
447 return;
448 }
449@@ -423,40 +426,61 @@
450 NULL,
451 tr("Export Crate"),
452 QDesktopServices::storageLocation(QDesktopServices::MusicLocation),
453- tr("M3U Playlist (*.m3u);;M3U8 Playlist (*.m3u8);;PLS Playlist (*.pls)"));
454+ tr("M3U Playlist (*.m3u);;M3U8 Playlist (*.m3u8);;PLS Playlist (*.pls);;Text CSV (*.csv);;Readable Text (*.txt)"));
455 // Exit method if user cancelled the open dialog.
456- if (file_location.isNull() || file_location.isEmpty())
457+ if (file_location.isNull() || file_location.isEmpty()) {
458 return;
459+ }
460 // check config if relative paths are desired
461 bool useRelativePath = static_cast<bool>(
462 m_pConfig->getValueString(
463 ConfigKey("[Library]", "UseRelativePathOnExport")).toInt());
464
465- // Create and populate a list of files of the crate
466+ // Create list of files of the crate
467 QList<QString> playlist_items;
468 QScopedPointer<CrateTableModel> pCrateTableModel(
469 new CrateTableModel(this, m_pTrackCollection));
470 pCrateTableModel->setCrate(m_crateTableModel.getCrate());
471 pCrateTableModel->select();
472 int rows = pCrateTableModel->rowCount();
473- for (int i = 0; i < rows; ++i) {
474- QModelIndex index = pCrateTableModel->index(i, 0);
475- playlist_items << pCrateTableModel->getTrackLocation(index);
476- }
477-
478- if (file_location.endsWith(".pls", Qt::CaseInsensitive)) {
479- ParserPls::writePLSFile(file_location, playlist_items, useRelativePath);
480- } else if (file_location.endsWith(".m3u8", Qt::CaseInsensitive)) {
481- ParserM3u::writeM3U8File(file_location, playlist_items,
482- useRelativePath);
483- } else {
484- // Default export to M3U if file extension is missing
485- if (!file_location.endsWith(".m3u", Qt::CaseInsensitive)) {
486- qDebug() << "Crate export: No valid file extension specified. Appending .m3u "
487- << "and exporting to M3U.";
488- file_location.append(".m3u");
489- }
490- ParserM3u::writeM3UFile(file_location, playlist_items, useRelativePath);
491- }
492-}
493-
494+
495+ if (file_location.endsWith(".csv", Qt::CaseInsensitive)) {
496+ ParserCsv::writeCSVFile(file_location, pCrateTableModel.data(), useRelativePath);
497+ } else if (file_location.endsWith(".txt", Qt::CaseInsensitive)) {
498+ ParserCsv::writeReadableTextFile(file_location, pCrateTableModel.data());
499+ } else{
500+ // populate a list of files of the crate
501+ QList<QString> playlist_items;
502+ int rows = pCrateTableModel->rowCount();
503+ for (int i = 0; i < rows; ++i) {
504+ QModelIndex index = m_crateTableModel.index(i, 0);
505+ playlist_items << m_crateTableModel.getTrackLocation(index);
506+ }
507+
508+ if (file_location.endsWith(".pls", Qt::CaseInsensitive)) {
509+ ParserPls::writePLSFile(file_location, playlist_items, useRelativePath);
510+ } else if (file_location.endsWith(".m3u8", Qt::CaseInsensitive)) {
511+ ParserM3u::writeM3U8File(file_location, playlist_items, useRelativePath);
512+ } else {
513+ //default export to M3U if file extension is missing
514+ if(!file_location.endsWith(".m3u", Qt::CaseInsensitive))
515+ {
516+ qDebug() << "Crate export: No valid file extension specified. Appending .m3u "
517+ << "and exporting to M3U.";
518+ file_location.append(".m3u");
519+ }
520+ ParserM3u::writeM3UFile(file_location, playlist_items, useRelativePath);
521+ }
522+ }
523+}
524+
525+void CrateFeature::slotCrateTableChanged(int crateId) {
526+ //qDebug() << "slotPlaylistTableChanged() playlistId:" << playlistId;
527+ clearChildModel();
528+ m_crateListTableModel.select();
529+ m_lastRightClickedIndex = constructChildModel(crateId);
530+ // Switch the view to the crate.
531+ m_crateTableModel.setCrate(crateId);
532+ // Update selection
533+ emit(featureSelect(this, m_lastRightClickedIndex));
534+}
535
536=== modified file 'mixxx/src/library/cratefeature.h'
537--- mixxx/src/library/cratefeature.h 2011-03-12 13:10:25 +0000
538+++ mixxx/src/library/cratefeature.h 2011-12-10 16:03:24 +0000
539@@ -49,11 +49,14 @@
540 void slotImportPlaylist();
541 void slotExportPlaylist();
542
543+ void slotCrateTableChanged(int playlistId);
544+
545 private:
546- void constructChildModel();
547+ QModelIndex constructChildModel(int selected_id);
548 void clearChildModel();
549
550 TrackCollection* m_pTrackCollection;
551+ CrateDAO& m_crateDao;
552 QAction *m_pCreateCrateAction;
553 QAction *m_pDeleteCrateAction;
554 QAction *m_pRenameCrateAction;
555
556=== modified file 'mixxx/src/library/dao/cratedao.cpp'
557--- mixxx/src/library/dao/cratedao.cpp 2011-10-21 00:42:23 +0000
558+++ mixxx/src/library/dao/cratedao.cpp 2011-12-10 16:03:24 +0000
559@@ -31,19 +31,19 @@
560 return query.value(0).toInt();
561 }
562
563-bool CrateDAO::createCrate(const QString& name) {
564+int CrateDAO::createCrate(const QString& name) {
565 QSqlQuery query(m_database);
566 query.prepare("INSERT INTO " CRATE_TABLE " (name) VALUES (:name)");
567 query.bindValue(":name", name);
568
569 if (!query.exec()) {
570 LOG_FAILED_QUERY(query);
571- return false;
572+ return -1;
573 }
574
575 int crateId = query.lastInsertId().toInt();
576 emit(added(crateId));
577- return true;
578+ return crateId;
579 }
580
581 bool CrateDAO::renameCrate(int crateId, const QString& newName) {
582@@ -56,6 +56,7 @@
583 LOG_FAILED_QUERY(query);
584 return false;
585 }
586+ emit(renamed(crateId));
587 return true;
588 }
589
590@@ -71,7 +72,7 @@
591 LOG_FAILED_QUERY(query);
592 return false;
593 }
594-
595+ emit(lockChanged(crateId));
596 return true;
597 }
598
599
600=== modified file 'mixxx/src/library/dao/cratedao.h'
601--- mixxx/src/library/dao/cratedao.h 2011-10-21 00:42:23 +0000
602+++ mixxx/src/library/dao/cratedao.h 2011-12-10 16:03:24 +0000
603@@ -28,7 +28,7 @@
604 void initialize();
605
606 unsigned int crateCount();
607- bool createCrate(const QString& name);
608+ int createCrate(const QString& name);
609 bool deleteCrate(int crateId);
610 bool renameCrate(int crateId, const QString& newName);
611 bool setCrateLocked(int crateId, bool locked);
612@@ -47,6 +47,8 @@
613 void changed(int crateId);
614 void trackAdded(int crateId, int trackId);
615 void trackRemoved(int crateId, int trackId);
616+ void renamed(int crateId);
617+ void lockChanged(int crateId);
618
619 private:
620 QSqlDatabase& m_database;
621
622=== modified file 'mixxx/src/library/dao/playlistdao.cpp'
623--- mixxx/src/library/dao/playlistdao.cpp 2011-10-21 00:42:23 +0000
624+++ mixxx/src/library/dao/playlistdao.cpp 2011-12-10 16:03:24 +0000
625@@ -22,7 +22,7 @@
626 /** Create a playlist with the given name.
627 @param name The name of the playlist to be created.
628 */
629-bool PlaylistDAO::createPlaylist(QString name, bool hidden)
630+int PlaylistDAO::createPlaylist(QString name, enum hidden_type hidden)
631 {
632 // qDebug() << "PlaylistDAO::createPlaylist"
633 // << QThread::currentThread()
634@@ -38,7 +38,7 @@
635 if (!query.exec()) {
636 LOG_FAILED_QUERY(query);
637 m_database.rollback();
638- return false;
639+ return -1;
640 }
641
642 //Get the id of the last playlist.
643@@ -50,34 +50,34 @@
644
645 //qDebug() << "Inserting playlist" << name << "at position" << position;
646
647- query.prepare("INSERT INTO Playlists (name, position, hidden) "
648- "VALUES (:name, :position, :hidden)");
649+ query.prepare("INSERT INTO Playlists (name, position, hidden, date_created, date_modified) "
650+ "VALUES (:name, :position, :hidden, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)");
651 query.bindValue(":name", name);
652 query.bindValue(":position", position);
653- query.bindValue(":hidden", hidden ? 1 : 0);
654+ query.bindValue(":hidden", (int)hidden);
655
656 if (!query.exec()) {
657 LOG_FAILED_QUERY(query);
658 m_database.rollback();
659- return false;
660+ return -1;
661 }
662
663 int playlistId = query.lastInsertId().toInt();
664 //Commit the transaction
665 m_database.commit();
666 emit(added(playlistId));
667- return true;
668+ return playlistId;
669 }
670
671-/** Find out the name of the playlist at the given position */
672-QString PlaylistDAO::getPlaylistName(unsigned int position)
673+/** Find out the name of the playlist at the given Id */
674+QString PlaylistDAO::getPlaylistName(int playlistId)
675 {
676 // qDebug() << "PlaylistDAO::getPlaylistName" << QThread::currentThread() << m_database.connectionName();
677
678 QSqlQuery query(m_database);
679 query.prepare("SELECT name FROM Playlists "
680- "WHERE position = :position");
681- query.bindValue(":position", position);
682+ "WHERE id= :id");
683+ query.bindValue(":id", playlistId);
684
685 if (!query.exec()) {
686 LOG_FAILED_QUERY(query);
687@@ -153,6 +153,9 @@
688 if (!query.exec()) {
689 LOG_FAILED_QUERY(query);
690 }
691+ else {
692+ emit(renamed(playlistId));
693+ }
694 }
695
696
697@@ -169,6 +172,7 @@
698 LOG_FAILED_QUERY(query);
699 return false;
700 }
701+ emit(lockChanged(playlistId));
702 return true;
703 }
704
705@@ -249,7 +253,7 @@
706 return numRecords;
707 }
708
709-int PlaylistDAO::getPlaylistId(int position)
710+int PlaylistDAO::getPlaylistId(int index)
711 {
712 // qDebug() << "PlaylistDAO::getPlaylistId"
713 // << QThread::currentThread() << m_database.connectionName();
714@@ -260,7 +264,7 @@
715 if (query.exec()) {
716 int currentRow = 0;
717 while(query.next()) {
718- if (currentRow++ == position) {
719+ if (currentRow++ == index) {
720 int id = query.value(0).toInt();
721 return id;
722 }
723@@ -272,22 +276,37 @@
724 return -1;
725 }
726
727+enum PlaylistDAO::hidden_type PlaylistDAO::getHiddenType(int playlistId){
728+ // qDebug() << "PlaylistDAO::getHiddenType"
729+ // << QThread::currentThread() << m_database.connectionName();
730+
731+ QSqlQuery query(m_database);
732+ query.prepare("SELECT hidden FROM Playlists WHERE id = :id");
733+ query.bindValue(":id", playlistId);
734+
735+ if (query.exec()) {
736+ if (query.next()) {
737+ return (enum hidden_type)query.value(0).toInt();
738+ }
739+ } else {
740+ LOG_FAILED_QUERY(query);
741+ }
742+ qDebug() << "PlaylistDAO::hidden_type returns PLHT_UNKNOWN for playlistId " << playlistId;
743+ return PLHT_UNKNOWN;
744+}
745+
746 bool PlaylistDAO::isHidden(int playlistId) {
747 // qDebug() << "PlaylistDAO::isHidden"
748 // << QThread::currentThread() << m_database.connectionName();
749
750- QSqlQuery query(m_database);
751- query.prepare("SELECT hidden FROM Playlists WHERE id = :id");
752- query.bindValue(":id", playlistId);
753+ enum hidden_type ht = getHiddenType(playlistId);
754
755- if (query.exec()) {
756- if (query.next()) {
757- return query.value(0).toBool();
758- }
759- } else {
760- LOG_FAILED_QUERY(query);
761- }
762- return false;
763+ if(ht==PLHT_NOT_HIDDEN){
764+ return false;
765+ }
766+ else{
767+ return true;
768+ }
769 }
770
771 void PlaylistDAO::removeTrackFromPlaylists(int trackId) {
772@@ -396,7 +415,7 @@
773 emit(changed(playlistId));
774 }
775
776-void PlaylistDAO::addToAutoDJQueue(int playlistId) {
777+void PlaylistDAO::addToAutoDJQueue(int playlistId, bool bTop) {
778 //qDebug() << "Adding tracks from playlist " << playlistId << " to the Auto-DJ Queue";
779
780 // Query the PlaylistTracks database to locate tracks in the selected playlist
781@@ -411,7 +430,102 @@
782 // Get the ID of the Auto-DJ playlist
783 int autoDJId = getPlaylistIdFromName(AUTODJ_TABLE);
784 // Loop through the tracks, adding them to the Auto-DJ Queue
785- while(query.next()) {
786- appendTrackToPlaylist(query.value(0).toInt(), autoDJId);
787- }
788-}
789+
790+ int i = 2; // Start at position 2 because position 1 was already loaded to the deck
791+
792+ while (query.next()) {
793+ if (bTop) {
794+ insertTrackIntoPlaylist(query.value(0).toInt(), autoDJId, i++);
795+ }
796+ else {
797+ appendTrackToPlaylist(query.value(0).toInt(), autoDJId);
798+ }
799+ }
800+}
801+
802+int PlaylistDAO::getPreviousPlaylist(int currentPlaylistId, enum hidden_type hidden) {
803+ //Start the transaction
804+ m_database.transaction();
805+
806+ //Find out the highest position existing in the playlist so we know what
807+ //position this track should have.
808+ QSqlQuery query(m_database);
809+ query.prepare("SELECT max(id) as id FROM Playlists "
810+ "WHERE id < :id AND hidden = :hidden");
811+ query.bindValue(":id", currentPlaylistId);
812+ query.bindValue(":hidden", hidden);
813+ query.exec();
814+
815+ //Print out any SQL error, if there was one.
816+ if (query.lastError().isValid()) {
817+ qDebug() << "appendTrackToPlaylist" << query.lastError();
818+ // m_database.rollback();
819+ // return;
820+ }
821+
822+ // Get the position of the highest playlist...
823+ int previousPlaylistId = -1;
824+ if (query.next()) {
825+ previousPlaylistId = query.value(query.record().indexOf("id")).toInt();
826+ }
827+ return previousPlaylistId;
828+}
829+
830+
831+void PlaylistDAO::copyPlaylistTracks(int sourcePlaylistID, int targetPlaylistId) {
832+
833+ //Start the transaction
834+ m_database.transaction();
835+
836+ //Find out the highest position existing in the target playlist so we know what
837+ //position this track should have.
838+ QSqlQuery query(m_database);
839+ query.prepare("SELECT max(position) as position FROM PlaylistTracks "
840+ "WHERE playlist_id = :id");
841+ query.bindValue(":id", targetPlaylistId);
842+ query.exec();
843+
844+ //Print out any SQL error, if there was one.
845+ if (query.lastError().isValid()) {
846+ qDebug() << "appendTrackToPlaylist" << query.lastError();
847+ // m_database.rollback();
848+ // return;
849+ }
850+
851+ // Get the position of the highest playlist...
852+ int position = 0;
853+ if (query.next()) {
854+ position = query.value(query.record().indexOf("position")).toInt();
855+ }
856+
857+
858+ // Query Tracks from the source Playlist
859+ query.prepare("SELECT track_id FROM PlaylistTracks "
860+ "WHERE playlist_id = :plid");
861+ query.bindValue(":plid", sourcePlaylistID);
862+ query.exec();
863+
864+ //Print out any SQL error, if there was one.
865+ if (query.lastError().isValid()) {
866+ qDebug() << "addToAutoDJQueue" << query.lastError();
867+ return;
868+ }
869+
870+
871+ QSqlQuery query2(m_database);
872+ //Insert the Tracks into the PlaylistTracks table
873+ query2.prepare("INSERT INTO PlaylistTracks (playlist_id, track_id, position)"
874+ "VALUES (:playlist_id, :track_id, :position)");
875+ query2.bindValue(":playlist_id", targetPlaylistId);
876+
877+ while (query.next()) {
878+ query2.bindValue(":track_id", query.value(0));
879+ query2.bindValue(":position", ++position);
880+ query2.exec();
881+ }
882+
883+ //Start the transaction
884+ m_database.commit();
885+ emit(changed(targetPlaylistId));
886+}
887+
888
889=== modified file 'mixxx/src/library/dao/playlistdao.h'
890--- mixxx/src/library/dao/playlistdao.h 2011-10-21 00:42:23 +0000
891+++ mixxx/src/library/dao/playlistdao.h 2011-12-10 16:03:24 +0000
892@@ -13,17 +13,27 @@
893 const QString PLAYLISTTRACKSTABLE_TRACKID = "track_id";
894 const QString PLAYLISTTRACKSTABLE_POSITION = "position";
895 const QString PLAYLISTTRACKSTABLE_PLAYLISTID = "playlist_id";
896+const QString PLAYLISTTRACKSTABLE_LOCATION = "location";
897+const QString PLAYLISTTRACKSTABLE_ARTIST = "artist";
898+const QString PLAYLISTTRACKSTABLE_TITLE = "title";
899+
900
901 class PlaylistDAO : public QObject, public virtual DAO {
902 Q_OBJECT
903 public:
904+ enum hidden_type {
905+ PLHT_NOT_HIDDEN = 0,
906+ PLHT_AUTO_DJ = 1,
907+ PLHT_SET_LOG = 2,
908+ PLHT_UNKNOWN = -1
909+ };
910 PlaylistDAO(QSqlDatabase& database);
911 virtual ~PlaylistDAO();
912
913 void initialize();
914 void setDatabase(QSqlDatabase& database) { m_database = database; };
915 /** Create a playlist */
916- bool createPlaylist(QString name, bool hidden = false);
917+ int createPlaylist(QString name, enum hidden_type = PLHT_NOT_HIDDEN);
918 /** Delete a playlist */
919 void deletePlaylist(int playlistId);
920 /** Rename a playlist */
921@@ -37,15 +47,17 @@
922 /** Find out how many playlists exist. */
923 unsigned int playlistCount();
924 /** Get the name of the playlist at the given position */
925- QString getPlaylistName(unsigned int position);
926+ QString getPlaylistName(int playlistId);
927 // Get the playlist id by its name
928 int getPlaylistIdFromName(QString name);
929- /** Get the id of the playlist at position. Note that the position is the
930+ /** Get the id of the playlist at index. Note that the index is the
931 * natural position in the database table, not the display order position
932 * column stored in the database. */
933- int getPlaylistId(int position);
934+ int getPlaylistId(int index);
935 // Returns true if the playlist with playlistId is hidden
936 bool isHidden(int playlistId);
937+ // Returns cause of playlistId is hidden
938+ enum hidden_type getHiddenType(int playlistId);
939 /** Remove a track from all playlists */
940 void removeTrackFromPlaylists(int trackId);
941 /** Remove a track from a playlist */
942@@ -53,13 +65,19 @@
943 /** Insert a track into a specific position in a playlist */
944 void insertTrackIntoPlaylist(int trackId, int playlistId, int position);
945 /** Add a playlist to the Auto-DJ Queue */
946- void addToAutoDJQueue(int playlistId);
947+ void addToAutoDJQueue(int playlistId, bool bTop);
948+ // Get a playlist from the same hidden type that was created just before
949+ int getPreviousPlaylist(int currentPlaylistId, enum hidden_type hidden);
950+ // Copy tracks from source to target playlist
951+ void copyPlaylistTracks( int sourcePlaylistID, int targetPlaylistId);
952 signals:
953 void added(int playlistId);
954 void deleted(int playlistId);
955 void changed(int playlistId);
956 void trackAdded(int playlistId, int trackId, int position);
957 void trackRemoved(int playlistId, int trackId, int position);
958+ void renamed(int playlistId);
959+ void lockChanged(int playlistId);
960 private:
961 QSqlDatabase& m_database;
962 DISALLOW_COPY_AND_ASSIGN(PlaylistDAO);
963
964=== modified file 'mixxx/src/library/legacylibraryimporter.cpp'
965--- mixxx/src/library/legacylibraryimporter.cpp 2010-11-19 01:54:28 +0000
966+++ mixxx/src/library/legacylibraryimporter.cpp 2011-12-10 16:03:24 +0000
967@@ -148,8 +148,7 @@
968
969 //Create the playlist with the imported name.
970 //qDebug() << "Importing playlist:" << current.name;
971- m_playlistDao.createPlaylist(current.name, false);
972- int playlistId = m_playlistDao.getPlaylistIdFromName(current.name);
973+ int playlistId = m_playlistDao.createPlaylist(current.name);
974
975 //For each track ID in the XML...
976 QList<int> trackIDs = current.indexes;
977
978=== modified file 'mixxx/src/library/library.cpp'
979--- mixxx/src/library/library.cpp 2011-09-25 06:39:39 +0000
980+++ mixxx/src/library/library.cpp 2011-12-10 16:03:24 +0000
981@@ -21,6 +21,7 @@
982 #include "library/promotracksfeature.h"
983 #include "library/traktor/traktorfeature.h"
984 #include "library/librarycontrol.h"
985+#include "library/setlogfeature.h"
986
987 #include "widget/wtracktableview.h"
988 #include "widget/wlibrary.h"
989@@ -60,6 +61,7 @@
990 addFeature(m_pCrateFeature);
991 addFeature(new BrowseFeature(this, pConfig, m_pTrackCollection, m_pRecordingManager));
992 addFeature(new RecordingFeature(this, pConfig, m_pTrackCollection, m_pRecordingManager));
993+ addFeature(new SetlogFeature(this, pConfig, m_pTrackCollection));
994 addFeature(new PrepareFeature(this, pConfig, m_pTrackCollection));
995 //iTunes and Rhythmbox should be last until we no longer have an obnoxious
996 //messagebox popup when you select them. (This forces you to reach for your
997
998=== modified file 'mixxx/src/library/libraryfeature.h'
999--- mixxx/src/library/libraryfeature.h 2011-03-10 14:06:26 +0000
1000+++ mixxx/src/library/libraryfeature.h 2011-12-10 16:03:24 +0000
1001@@ -66,7 +66,8 @@
1002 void featureIsLoading(LibraryFeature*);
1003 /** emit this signal if the foreign music collection has been imported/parsed. **/
1004 void featureLoadingFinished(LibraryFeature*s);
1005-
1006+ /** emit this signal to move the selection **/
1007+ void featureSelect(LibraryFeature* pFeature, const QModelIndex& index);
1008
1009 };
1010
1011
1012=== modified file 'mixxx/src/library/parser.cpp'
1013--- mixxx/src/library/parser.cpp 2011-11-27 04:30:58 +0000
1014+++ mixxx/src/library/parser.cpp 2011-12-10 16:03:24 +0000
1015@@ -87,7 +87,7 @@
1016 }
1017
1018 const unsigned char* bytes = (const unsigned char *)string;
1019- while(*bytes) {
1020+ while (*bytes) {
1021 if( (// ASCII
1022 bytes[0] == 0x09 ||
1023 bytes[0] == 0x0A ||
1024
1025=== added file 'mixxx/src/library/parsercsv.cpp'
1026--- mixxx/src/library/parsercsv.cpp 1970-01-01 00:00:00 +0000
1027+++ mixxx/src/library/parsercsv.cpp 2011-12-10 16:03:24 +0000
1028@@ -0,0 +1,238 @@
1029+//
1030+// C++ Implementation: parsercsv
1031+//
1032+// Description: module to parse Comma-Separated Values (CSV) formated playlists (rfc4180)
1033+//
1034+//
1035+// Author: Ingo Kossyk <kossyki@cs.tu-berlin.de>, (C) 2004
1036+// Author: Tobias Rafreider trafreider@mixxx.org, (C) 2011
1037+// Author: Daniel Schürmann daschuer@gmx.de, (C) 2011
1038+//
1039+// Copyright: See COPYING file that comes with this distribution
1040+//
1041+//
1042+#include <QTextStream>
1043+#include <QDebug>
1044+#include <QDir>
1045+#include <QMessageBox>
1046+#include "parsercsv.h"
1047+#include <QUrl>
1048+
1049+ParserCsv::ParserCsv() : Parser()
1050+{
1051+}
1052+
1053+ParserCsv::~ParserCsv()
1054+{
1055+
1056+}
1057+
1058+
1059+QList<QString> ParserCsv::parse(QString sFilename)
1060+{
1061+ QFile file(sFilename);
1062+ QString basepath = sFilename.section('/', 0, -2);
1063+
1064+ clearLocations();
1065+ //qDebug() << "ParserCsv: Starting to parse.";
1066+ if (file.open(QIODevice::ReadOnly) && !isBinary(sFilename)) {
1067+ QByteArray ba = file.readAll();
1068+
1069+ QList<QList<QString> > tokens = tokenize( ba, ',');
1070+
1071+ // detect Location column
1072+ int loc_coll = 0x7fffffff;
1073+ if (tokens.size()) {
1074+ for (int i = 0; i < tokens[0].size(); ++i) {
1075+ if (tokens[0][i] == tr("Location")) {
1076+ loc_coll = i;
1077+ break;
1078+ }
1079+ }
1080+ for (int i = 1; i < tokens.size(); ++i) {
1081+ if (loc_coll < tokens[i].size()) {
1082+ // Todo: check if path is relative
1083+ QFileInfo fi = tokens[i][loc_coll];
1084+ if (fi.isRelative()){
1085+ // add base path
1086+ qDebug() << "is relative" << basepath << fi.filePath();
1087+ fi.setFile(basepath,fi.filePath());
1088+ }
1089+ m_sLocations.append(fi.filePath());
1090+
1091+ }
1092+ }
1093+ }
1094+
1095+ file.close();
1096+
1097+ if(m_sLocations.count() != 0)
1098+ return m_sLocations;
1099+ else
1100+ return QList<QString>(); // NULL pointer returned when no locations were found
1101+
1102+ }
1103+
1104+ file.close();
1105+ return QList<QString>(); //if we get here something went wrong
1106+}
1107+
1108+// Code was posted at http://www.qtcentre.org/threads/35511-Parsing-CSV-data
1109+// by "adzajac" and adapted to use QT Classes
1110+QList<QList<QString> > ParserCsv::tokenize(const QByteArray& str, char delimiter) {
1111+ QList<QList<QString> > tokens;
1112+
1113+ unsigned int row = 0;
1114+ bool quotes = false;
1115+ QByteArray field = "";
1116+
1117+ tokens.append(QList<QString>());
1118+
1119+ for (int pos = 0; pos < str.length(); ++pos) {
1120+ char c = str[pos];
1121+ if (!quotes && c == '"' ){
1122+ quotes = true;
1123+ } else if (quotes && c== '"' ){
1124+ if (pos + 1 < str.length() && str[pos+1]== '"') {
1125+ field.append(c);
1126+ pos++;
1127+ } else {
1128+ quotes = false;
1129+ }
1130+ } else if (!quotes && c == delimiter) {
1131+ if (isUtf8(field.data())) {
1132+ tokens[row].append(QString::fromUtf8(field));
1133+ } else {
1134+ tokens[row].append(QString::fromLatin1(field));
1135+ }
1136+ field.clear();
1137+ } else if (!quotes && (c == '\r' || c == '\n')) {
1138+ if (isUtf8(field.data())) {
1139+ tokens[row].append(QString::fromUtf8(field));
1140+ } else {
1141+ tokens[row].append(QString::fromLatin1(field));
1142+ }
1143+ field.clear();
1144+ tokens.append(QList<QString>());
1145+ row++;
1146+ } else {
1147+ field.push_back(c);
1148+ }
1149+ }
1150+ return tokens;
1151+}
1152+
1153+bool ParserCsv::writeCSVFile(const QString &file_str, BaseSqlTableModel* pPlaylistTableModel, bool useRelativePath)
1154+{
1155+ /*
1156+ * Important note:
1157+ * On Windows \n will produce a <CR><CL> (=\r\n)
1158+ * On Linux and OS X \n is <CR> (which remains \n)
1159+ */
1160+
1161+ QFile file(file_str);
1162+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text)){
1163+ QMessageBox::warning(NULL,tr("Playlist Export Failed"),
1164+ tr("Could not create file")+" "+file_str);
1165+ return false;
1166+ }
1167+ //Base folder of file
1168+ QString base = file_str.section('/', 0, -2);
1169+ QDir base_dir(base);
1170+
1171+ qDebug() << "Basepath: " << base;
1172+ QTextStream out(&file);
1173+ out.setCodec("UTF-8"); // rfc4180: Common usage of CSV is US-ASCII ...
1174+ // Using UTF-8 to get around codepage issues
1175+ // and it's the default encoding in Ooo Calc
1176+
1177+ // writing header section
1178+ bool first = true;
1179+ int columns = pPlaylistTableModel->columnCount();
1180+ for (int i = 0; i < columns; ++i) {
1181+ if (pPlaylistTableModel->isColumnInternal(i)) {
1182+ continue;
1183+ }
1184+ if (!first){
1185+ out << ",";
1186+ } else {
1187+ first = false;
1188+ }
1189+ out << "\"";
1190+ QString field = pPlaylistTableModel->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString();
1191+ out << field.replace('\"', "\"\""); // escape "
1192+ out << "\"";
1193+ }
1194+ out << "\r\n"; // CRLF according to rfc4180
1195+
1196+
1197+ int rows = pPlaylistTableModel->rowCount();
1198+ for (int j = 0; j < rows; j++) {
1199+ // writing fields section
1200+ first = true;
1201+ for(int i = 0; i < columns; ++i){
1202+ if (pPlaylistTableModel->isColumnInternal(i)) {
1203+ continue;
1204+ }
1205+ if (!first){
1206+ out << ",";
1207+ } else {
1208+ first = false;
1209+ }
1210+ out << "\"";
1211+ QString field = pPlaylistTableModel->data(pPlaylistTableModel->index(j,i)).toString();
1212+ if (useRelativePath && i == pPlaylistTableModel->fieldIndex(PLAYLISTTRACKSTABLE_LOCATION)) {
1213+ field = base_dir.relativeFilePath(field);
1214+ }
1215+ out << field.replace('\"', "\"\""); // escape "
1216+ out << "\"";
1217+ }
1218+ out << "\r\n"; // CRLF according to rfc4180
1219+ }
1220+ return true;
1221+}
1222+
1223+bool ParserCsv::writeReadableTextFile(const QString &file_str, BaseSqlTableModel* pPlaylistTableModel)
1224+{
1225+ /*
1226+ * Important note:
1227+ * On Windows \n will produce a <CR><CL> (=\r\n)
1228+ * On Linux and OS X \n is <CR> (which remains \n)
1229+ */
1230+
1231+ QFile file(file_str);
1232+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text)){
1233+ QMessageBox::warning(NULL,tr("Readable text Export Failed"),
1234+ tr("Could not create file")+" "+file_str);
1235+ return false;
1236+ }
1237+
1238+ QTextStream out(&file);
1239+
1240+ // export each row as "01. 00:00 Artist - Title"
1241+
1242+ int i; // fieldIndex
1243+ int rows = pPlaylistTableModel->rowCount();
1244+ for (int j = 0; j < rows; j++) {
1245+ // writing fields section
1246+ i = pPlaylistTableModel->fieldIndex(PLAYLISTTRACKSTABLE_POSITION);
1247+ if (i >= 0){
1248+ int nr = pPlaylistTableModel->data(pPlaylistTableModel->index(j,i)).toInt();
1249+ out << QString("%1.").arg(nr,2,10,QLatin1Char('0'));
1250+ }
1251+ // TODO(DSC) // Fill in Cue point
1252+
1253+ i = pPlaylistTableModel->fieldIndex(PLAYLISTTRACKSTABLE_ARTIST);
1254+ if (i >= 0){
1255+ out << " ";
1256+ out << pPlaylistTableModel->data(pPlaylistTableModel->index(j,i)).toString();
1257+ }
1258+ i = pPlaylistTableModel->fieldIndex(PLAYLISTTRACKSTABLE_TITLE);
1259+ if (i >= 0){
1260+ out << " - ";
1261+ out << pPlaylistTableModel->data(pPlaylistTableModel->index(j,i)).toString();;
1262+ }
1263+ out << "\n";
1264+ }
1265+ return true;
1266+}
1267
1268=== added file 'mixxx/src/library/parsercsv.h'
1269--- mixxx/src/library/parsercsv.h 1970-01-01 00:00:00 +0000
1270+++ mixxx/src/library/parsercsv.h 2011-12-10 16:03:24 +0000
1271@@ -0,0 +1,41 @@
1272+//
1273+// C++ Interface: parserm3u
1274+//
1275+// Description: Interface header parse Comma-Separated Values (CSV) formated playlists (rfc4180)
1276+//
1277+//
1278+// Author: Ingo Kossyk <kossyki@cs.tu-berlin.de>, (C) 2004
1279+// Author: Tobias Rafreider trafreider@mixxx.org, (C) 2011
1280+// Author: Daniel Schürmann daschuer@gmx.de, (C) 2011
1281+//
1282+// Copyright: See COPYING file that comes with this distribution
1283+//
1284+//
1285+#include "parser.h"
1286+#include "library/basesqltablemodel.h"
1287+
1288+#ifndef PARSERCSV_H
1289+#define PARSERCSV_H
1290+
1291+class QTextStream;
1292+
1293+class ParserCsv : public Parser
1294+{
1295+ Q_OBJECT
1296+public:
1297+ ParserCsv();
1298+ virtual ~ParserCsv();
1299+ /**Overwriting function parse in class Parser**/
1300+ QList<QString> parse(QString);
1301+ // Playlist Export
1302+ static bool writeCSVFile(const QString &file, BaseSqlTableModel* pPlaylistTableModel, bool useRelativePath);
1303+ // Readable Text export
1304+ static bool writeReadableTextFile(const QString &file, BaseSqlTableModel* pPlaylistTableModel);
1305+private:
1306+ /**Reads a line from the file and returns filepath if a valid file**/
1307+ QList<QList<QString> > tokenize(const QByteArray& str, char delimiter);
1308+
1309+
1310+};
1311+
1312+#endif
1313
1314=== modified file 'mixxx/src/library/playlistfeature.cpp'
1315--- mixxx/src/library/playlistfeature.cpp 2011-11-30 05:27:44 +0000
1316+++ mixxx/src/library/playlistfeature.cpp 2011-12-10 16:03:24 +0000
1317@@ -8,6 +8,7 @@
1318 #include "library/parser.h"
1319 #include "library/parserm3u.h"
1320 #include "library/parserpls.h"
1321+#include "library/parsercsv.h"
1322
1323
1324 #include "widget/wlibrary.h"
1325@@ -26,8 +27,8 @@
1326 m_pTrackCollection(pTrackCollection),
1327 m_playlistDao(pTrackCollection->getPlaylistDAO()),
1328 m_trackDao(pTrackCollection->getTrackDAO()),
1329- m_pConfig(pConfig),
1330- m_playlistTableModel(this, pTrackCollection->getDatabase()) {
1331+ m_playlistTableModel(this, pTrackCollection->getDatabase()),
1332+ m_pConfig(pConfig) {
1333 m_pPlaylistTableModel = new PlaylistTableModel(this, pTrackCollection,
1334 "mixxx.db.model.playlist");
1335
1336@@ -35,10 +36,14 @@
1337 connect(m_pCreatePlaylistAction, SIGNAL(triggered()),
1338 this, SLOT(slotCreatePlaylist()));
1339
1340- m_pAddToAutoDJAction = new QAction(tr("Add to Auto-DJ Queue"),this);
1341+ m_pAddToAutoDJAction = new QAction(tr("Add to Auto DJ bottom"),this);
1342 connect(m_pAddToAutoDJAction, SIGNAL(triggered()),
1343 this, SLOT(slotAddToAutoDJ()));
1344
1345+ m_pAddToAutoDJTopAction = new QAction(tr("Add to Auto DJ top 2"),this);
1346+ connect(m_pAddToAutoDJTopAction, SIGNAL(triggered()),
1347+ this, SLOT(slotAddToAutoDJTop()));
1348+
1349 m_pDeletePlaylistAction = new QAction(tr("Remove"),this);
1350 connect(m_pDeletePlaylistAction, SIGNAL(triggered()),
1351 this, SLOT(slotDeletePlaylist()));
1352@@ -58,6 +63,18 @@
1353 connect(m_pExportPlaylistAction, SIGNAL(triggered()),
1354 this, SLOT(slotExportPlaylist()));
1355
1356+ connect(&m_playlistDao, SIGNAL(added(int)),
1357+ this, SLOT(slotPlaylistTableChanged(int)));
1358+
1359+ connect(&m_playlistDao, SIGNAL(deleted(int)),
1360+ this, SLOT(slotPlaylistTableChanged(int)));
1361+
1362+ connect(&m_playlistDao, SIGNAL(renamed(int)),
1363+ this, SLOT(slotPlaylistTableChanged(int)));
1364+
1365+ connect(&m_playlistDao, SIGNAL(lockChanged(int)),
1366+ this, SLOT(slotPlaylistTableChanged(int)));
1367+
1368 // Setup the sidebar playlist model
1369 m_playlistTableModel.setTable("Playlists");
1370 m_playlistTableModel.setFilter("hidden=0");
1371@@ -68,7 +85,7 @@
1372 //construct child model
1373 TreeItem *rootItem = new TreeItem();
1374 m_childModel.setRootItem(rootItem);
1375- constructChildModel();
1376+ constructChildModel(-1);
1377 }
1378
1379 PlaylistFeature::~PlaylistFeature() {
1380@@ -77,6 +94,7 @@
1381 delete m_pDeletePlaylistAction;
1382 delete m_pImportPlaylistAction;
1383 delete m_pAddToAutoDJAction;
1384+ delete m_pAddToAutoDJTopAction;
1385 delete m_pRenamePlaylistAction;
1386 delete m_pLockPlaylistAction;
1387 }
1388@@ -93,6 +111,8 @@
1389 void PlaylistFeature::bindWidget(WLibrarySidebar* sidebarWidget,
1390 WLibrary* libraryWidget,
1391 MixxxKeyboard* keyboard) {
1392+ Q_UNUSED(sidebarWidget);
1393+ Q_UNUSED(keyboard);
1394 WLibraryTextBrowser* edit = new WLibraryTextBrowser(libraryWidget);
1395 connect(this, SIGNAL(showPage(const QUrl&)),
1396 edit, SLOT(setSource(const QUrl&)));
1397@@ -142,6 +162,7 @@
1398 menu.addAction(m_pCreatePlaylistAction);
1399 menu.addSeparator();
1400 menu.addAction(m_pAddToAutoDJAction);
1401+ menu.addAction(m_pAddToAutoDJTopAction);
1402 menu.addAction(m_pRenamePlaylistAction);
1403 menu.addAction(m_pDeletePlaylistAction);
1404 menu.addAction(m_pLockPlaylistAction);
1405@@ -183,17 +204,10 @@
1406
1407 } while (!validNameGiven);
1408
1409- bool playlistCreated = m_playlistDao.createPlaylist(name);
1410+ int playlistId = m_playlistDao.createPlaylist(name);
1411
1412- if (playlistCreated) {
1413- clearChildModel();
1414- m_playlistTableModel.select();
1415- constructChildModel();
1416+ if (playlistId != -1) {
1417 emit(featureUpdated());
1418- //Switch the view to the new playlist.
1419- int playlistId = m_playlistDao.getPlaylistIdFromName(name);
1420- m_pPlaylistTableModel->setPlaylist(playlistId);
1421- // TODO(XXX) set sidebar selection
1422 emit(showTrackModel(m_pPlaylistTableModel));
1423 }
1424 else {
1425@@ -251,11 +265,7 @@
1426 } while (!validNameGiven);
1427
1428 m_playlistDao.renamePlaylist(playlistId, newName);
1429- clearChildModel();
1430- m_playlistTableModel.select();
1431- constructChildModel();
1432- emit(featureUpdated());
1433- m_pPlaylistTableModel->setPlaylist(playlistId);
1434+ emit(featureUpdated());
1435 }
1436
1437
1438@@ -268,9 +278,6 @@
1439 if (!m_playlistDao.setPlaylistLocked(playlistId, locked)) {
1440 qDebug() << "Failed to toggle lock of playlistId " << playlistId;
1441 }
1442-
1443- TreeItem* playlistItem = m_childModel.getItem(m_lastRightClickedIndex);
1444- playlistItem->setIcon(locked ? QIcon(":/images/library/ic_library_locked.png") : QIcon());
1445 }
1446
1447 void PlaylistFeature::slotDeletePlaylist()
1448@@ -284,20 +291,17 @@
1449 return;
1450 }
1451
1452- if (m_lastRightClickedIndex.isValid() &&
1453- !m_playlistDao.isPlaylistLocked(playlistId)) {
1454+ if (m_lastRightClickedIndex.isValid()) {
1455 Q_ASSERT(playlistId >= 0);
1456
1457- clearChildModel();
1458 m_playlistDao.deletePlaylist(playlistId);
1459- m_playlistTableModel.select();
1460- constructChildModel();
1461 emit(featureUpdated());
1462+ activate();
1463 }
1464-
1465 }
1466
1467 bool PlaylistFeature::dropAccept(QUrl url) {
1468+ Q_UNUSED(url);
1469 return false;
1470 }
1471
1472@@ -334,6 +338,7 @@
1473 }
1474
1475 bool PlaylistFeature::dragMoveAccept(QUrl url) {
1476+ Q_UNUSED(url);
1477 return false;
1478 }
1479
1480@@ -352,20 +357,21 @@
1481 TreeItemModel* PlaylistFeature::getChildModel() {
1482 return &m_childModel;
1483 }
1484+
1485 /**
1486 * Purpose: When inserting or removing playlists,
1487 * we require the sidebar model not to reset.
1488 * This method queries the database and does dynamic insertion
1489 */
1490-void PlaylistFeature::constructChildModel()
1491+QModelIndex PlaylistFeature::constructChildModel(int selected_id)
1492 {
1493 QList<TreeItem*> data_list;
1494 int nameColumn = m_playlistTableModel.record().indexOf("name");
1495 int idColumn = m_playlistTableModel.record().indexOf("id");
1496-
1497- //Access the invisible root item
1498+ int selected_row = -1;
1499+ // Access the invisible root item
1500 TreeItem* root = m_childModel.getItem(QModelIndex());
1501- //Create new TreeItems for the playlists in the database
1502+ // Create new TreeItems for the playlists in the database
1503 for (int row = 0; row < m_playlistTableModel.rowCount(); ++row) {
1504 QModelIndex ind = m_playlistTableModel.index(row, nameColumn);
1505 QString playlist_name = m_playlistTableModel.data(ind).toString();
1506@@ -373,14 +379,23 @@
1507 int playlist_id = m_playlistTableModel.data(ind).toInt();
1508 bool locked = m_playlistDao.isPlaylistLocked(playlist_id);
1509
1510- //Create the TreeItem whose parent is the invisible root item
1511+ if ( selected_id == playlist_id) {
1512+ // save index for selection
1513+ selected_row = row;
1514+ }
1515+
1516+ // Create the TreeItem whose parent is the invisible root item
1517 TreeItem* item = new TreeItem(playlist_name, playlist_name, this, root);
1518 item->setIcon(locked ? QIcon(":/images/library/ic_library_locked.png") : QIcon());
1519 data_list.append(item);
1520 }
1521
1522- //Append all the newly created TreeItems in a dynamic way to the childmodel
1523+ // Append all the newly created TreeItems in a dynamic way to the childmodel
1524 m_childModel.insertRows(data_list, 0, m_playlistTableModel.rowCount());
1525+ if (selected_row == -1) {
1526+ return QModelIndex();
1527+ }
1528+ return m_childModel.index(selected_row, 0);
1529 }
1530
1531 /**
1532@@ -398,8 +413,8 @@
1533 NULL,
1534 tr("Import Playlist"),
1535 QDesktopServices::storageLocation(QDesktopServices::MusicLocation),
1536- tr("Playlist Files (*.m3u *.m3u8 *.pls)"));
1537- // Exit method if user cancelled the open dialog.
1538+ tr("Playlist Files (*.m3u *.m3u8 *.pls *.csv)"));
1539+ //Exit method if user cancelled the open dialog.
1540 if (playlist_file.isNull() || playlist_file.isEmpty()) {
1541 return;
1542 }
1543@@ -411,6 +426,8 @@
1544 playlist_parser = new ParserM3u();
1545 } else if (playlist_file.endsWith(".pls", Qt::CaseInsensitive)) {
1546 playlist_parser = new ParserPls();
1547+ } else if (playlist_file.endsWith(".csv", Qt::CaseInsensitive)) {
1548+ playlist_parser = new ParserCsv();
1549 } else {
1550 return;
1551 }
1552@@ -426,7 +443,9 @@
1553 delete playlist_parser;
1554 }
1555 }
1556+
1557 void PlaylistFeature::onLazyChildExpandation(const QModelIndex &index){
1558+ Q_UNUSED(index);
1559 //Nothing to do because the childmodel is not of lazy nature.
1560 }
1561
1562@@ -436,13 +455,13 @@
1563 NULL,
1564 tr("Export Playlist"),
1565 QDesktopServices::storageLocation(QDesktopServices::MusicLocation),
1566- tr("M3U Playlist (*.m3u);;M3U8 Playlist (*.m3u8);;PLS Playlist (*.pls)"));
1567+ tr("M3U Playlist (*.m3u);;M3U8 Playlist (*.m3u8);;"
1568+ "PLS Playlist (*.pls);;Text CSV (*.csv);;Readable Text (*.txt)"));
1569 // Exit method if user cancelled the open dialog.
1570 if (file_location.isNull() || file_location.isEmpty()) {
1571 return;
1572 }
1573- // Create and populate a list of files of the playlist
1574- QList<QString> playlist_items;
1575+
1576 // Create a new table model since the main one might have an active search.
1577 QScopedPointer<PlaylistTableModel> pPlaylistTableModel(
1578 new PlaylistTableModel(this, m_pTrackCollection,
1579@@ -451,43 +470,80 @@
1580 pPlaylistTableModel->setPlaylist(m_pPlaylistTableModel->getPlaylist());
1581 pPlaylistTableModel->setSort(0, Qt::AscendingOrder);
1582 pPlaylistTableModel->select();
1583- int rows = pPlaylistTableModel->rowCount();
1584- for (int i = 0; i < rows; ++i) {
1585- QModelIndex index = pPlaylistTableModel->index(i, 0);
1586- playlist_items << pPlaylistTableModel->getTrackLocation(index);
1587- }
1588
1589 // check config if relative paths are desired
1590 bool useRelativePath = static_cast<bool>(m_pConfig->getValueString(
1591 ConfigKey("[Library]", "UseRelativePathOnExport")).toInt());
1592
1593- if (file_location.endsWith(".m3u", Qt::CaseInsensitive)) {
1594- ParserM3u::writeM3UFile(file_location, playlist_items, useRelativePath);
1595- } else if (file_location.endsWith(".pls", Qt::CaseInsensitive)) {
1596- ParserPls::writePLSFile(file_location,playlist_items,
1597- useRelativePath);
1598- } else if (file_location.endsWith(".m3u8", Qt::CaseInsensitive)) {
1599- ParserM3u::writeM3U8File(file_location, playlist_items,
1600- useRelativePath);
1601+ if (file_location.endsWith(".csv", Qt::CaseInsensitive)) {
1602+ ParserCsv::writeCSVFile(file_location, pPlaylistTableModel.data(), useRelativePath);
1603+ } else if (file_location.endsWith(".txt", Qt::CaseInsensitive)) {
1604+ ParserCsv::writeReadableTextFile(file_location, pPlaylistTableModel.data());
1605 } else {
1606- //default export to M3U if file extension is missing
1607+ // Create and populate a list of files of the playlist
1608+ QList<QString> playlist_items;
1609+ int rows = pPlaylistTableModel->rowCount();
1610+ for (int i = 0; i < rows; ++i) {
1611+ QModelIndex index = pPlaylistTableModel->index(i, 0);
1612+ playlist_items << pPlaylistTableModel->getTrackLocation(index);
1613+ }
1614
1615- qDebug() << "Playlist export: No file extension specified. Appending .m3u "
1616- << "and exporting to M3U.";
1617- file_location.append(".m3u");
1618- ParserM3u::writeM3UFile(file_location, playlist_items, useRelativePath);
1619+ if (file_location.endsWith(".pls", Qt::CaseInsensitive)) {
1620+ ParserPls::writePLSFile(file_location, playlist_items, useRelativePath);
1621+ } else if (file_location.endsWith(".m3u8", Qt::CaseInsensitive)) {
1622+ ParserM3u::writeM3U8File(file_location, playlist_items, useRelativePath);
1623+ } else {
1624+ //default export to M3U if file extension is missing
1625+ if(!file_location.endsWith(".m3u", Qt::CaseInsensitive))
1626+ {
1627+ qDebug() << "Crate export: No valid file extension specified. Appending .m3u "
1628+ << "and exporting to M3U.";
1629+ file_location.append(".m3u");
1630+ }
1631+ ParserM3u::writeM3UFile(file_location, playlist_items, useRelativePath);
1632+ }
1633 }
1634 }
1635
1636 void PlaylistFeature::slotAddToAutoDJ() {
1637 //qDebug() << "slotAddToAutoDJ() row:" << m_lastRightClickedIndex.data();
1638+ addToAutoDJ(false); // Top = True
1639+}
1640+
1641+void PlaylistFeature::slotAddToAutoDJTop() {
1642+ //qDebug() << "slotAddToAutoDJTop() row:" << m_lastRightClickedIndex.data();
1643+ addToAutoDJ(true); // bTop = True
1644+}
1645+
1646+void PlaylistFeature::addToAutoDJ(bool bTop) {
1647+ //qDebug() << "slotAddToAutoDJ() row:" << m_lastRightClickedIndex.data();
1648
1649 if (m_lastRightClickedIndex.isValid()) {
1650 int playlistId = m_playlistDao.getPlaylistIdFromName(
1651 m_lastRightClickedIndex.data().toString());
1652 if (playlistId >= 0) {
1653- m_playlistDao.addToAutoDJQueue(playlistId);
1654+ // Insert this playlist
1655+ m_playlistDao.addToAutoDJQueue(playlistId, bTop);
1656 }
1657 }
1658 emit(featureUpdated());
1659 }
1660+
1661+void PlaylistFeature::slotPlaylistTableChanged(int playlistId) {
1662+ //qDebug() << "slotPlaylistTableChanged() playlistId:" << playlistId;
1663+ enum PlaylistDAO::hidden_type type = m_playlistDao.getHiddenType(playlistId);
1664+ if ( type == PlaylistDAO::PLHT_NOT_HIDDEN
1665+ || type == PlaylistDAO::PLHT_UNKNOWN // In case of a deleted Playlist
1666+ ){
1667+ clearChildModel();
1668+ m_playlistTableModel.select();
1669+ m_lastRightClickedIndex = constructChildModel(playlistId);
1670+
1671+ if(type != PlaylistDAO::PLHT_UNKNOWN) {
1672+ // Switch the view to the playlist.
1673+ m_pPlaylistTableModel->setPlaylist(playlistId);
1674+ // Update selection
1675+ emit(featureSelect(this, m_lastRightClickedIndex));
1676+ }
1677+ }
1678+}
1679
1680=== modified file 'mixxx/src/library/playlistfeature.h'
1681--- mixxx/src/library/playlistfeature.h 2011-11-27 06:59:02 +0000
1682+++ mixxx/src/library/playlistfeature.h 2011-12-10 16:03:24 +0000
1683@@ -50,15 +50,18 @@
1684 void slotCreatePlaylist();
1685 void slotDeletePlaylist();
1686 void slotAddToAutoDJ();
1687+ void slotAddToAutoDJTop();
1688 void slotRenamePlaylist();
1689 void slotTogglePlaylistLock();
1690 void slotImportPlaylist();
1691 void slotExportPlaylist();
1692
1693+ void slotPlaylistTableChanged(int playlistId);
1694
1695 private:
1696- void constructChildModel();
1697+ QModelIndex constructChildModel(int selected_id);
1698 void clearChildModel();
1699+ void addToAutoDJ(bool bTop);
1700
1701 TrackCollection* m_pTrackCollection;
1702 PlaylistTableModel* m_pPlaylistTableModel;
1703@@ -67,6 +70,7 @@
1704 QAction *m_pCreatePlaylistAction;
1705 QAction *m_pDeletePlaylistAction;
1706 QAction *m_pAddToAutoDJAction;
1707+ QAction *m_pAddToAutoDJTopAction;
1708 QAction *m_pRenamePlaylistAction;
1709 QAction *m_pLockPlaylistAction;
1710 QAction *m_pImportPlaylistAction;
1711
1712=== modified file 'mixxx/src/library/playlisttablemodel.cpp'
1713--- mixxx/src/library/playlisttablemodel.cpp 2011-11-30 06:19:47 +0000
1714+++ mixxx/src/library/playlisttablemodel.cpp 2011-12-10 16:03:24 +0000
1715@@ -94,6 +94,17 @@
1716 return true;
1717 }
1718
1719+bool PlaylistTableModel::appendTrack(int trackId) {
1720+ if (trackId < 0) {
1721+ return false;
1722+ }
1723+
1724+ m_playlistDao.appendTrackToPlaylist(trackId, m_iPlaylistId);
1725+
1726+ select(); //Repopulate the data model.
1727+ return true;
1728+}
1729+
1730 TrackPointer PlaylistTableModel::getTrack(const QModelIndex& index) const {
1731 //FIXME: use position instead of location for playlist tracks?
1732
1733@@ -306,6 +317,7 @@
1734 }
1735
1736 QItemDelegate* PlaylistTableModel::delegateForColumn(const int i) {
1737+ Q_UNUSED(i);
1738 return NULL;
1739 }
1740
1741
1742=== modified file 'mixxx/src/library/playlisttablemodel.h'
1743--- mixxx/src/library/playlisttablemodel.h 2011-11-30 05:27:44 +0000
1744+++ mixxx/src/library/playlisttablemodel.h 2011-12-10 16:03:24 +0000
1745@@ -30,6 +30,7 @@
1746 virtual void removeTrack(const QModelIndex& index);
1747 virtual void removeTracks(const QModelIndexList& indices);
1748 virtual bool addTrack(const QModelIndex& index, QString location);
1749+ virtual bool appendTrack(int trackId);
1750 virtual void moveTrack(const QModelIndex& sourceIndex, const QModelIndex& destIndex);
1751 virtual void shuffleTracks(const QModelIndex& currentIndex);
1752
1753
1754=== modified file 'mixxx/src/library/preparefeature.cpp'
1755--- mixxx/src/library/preparefeature.cpp 2011-03-26 12:29:21 +0000
1756+++ mixxx/src/library/preparefeature.cpp 2011-12-10 16:03:24 +0000
1757@@ -78,34 +78,45 @@
1758 }
1759
1760 void PrepareFeature::activateChild(const QModelIndex& index) {
1761+ Q_UNUSED(index);
1762 }
1763
1764 void PrepareFeature::onRightClick(const QPoint& globalPos) {
1765+ Q_UNUSED(globalPos);
1766 }
1767
1768 void PrepareFeature::onRightClickChild(const QPoint& globalPos,
1769 QModelIndex index) {
1770+ Q_UNUSED(globalPos);
1771+ Q_UNUSED(index);
1772 }
1773
1774 bool PrepareFeature::dropAccept(QUrl url) {
1775+ Q_UNUSED(url);
1776 return false;
1777 }
1778
1779 bool PrepareFeature::dropAcceptChild(const QModelIndex& index, QUrl url) {
1780- return false;
1781+ Q_UNUSED(index);
1782+ Q_UNUSED(url);
1783+ return false;
1784 }
1785
1786 bool PrepareFeature::dragMoveAccept(QUrl url) {
1787- return false;
1788+ Q_UNUSED(url);
1789+ return false;
1790 }
1791
1792 bool PrepareFeature::dragMoveAcceptChild(const QModelIndex& index,
1793 QUrl url) {
1794+ Q_UNUSED(index);
1795+ Q_UNUSED(url);
1796 return false;
1797 }
1798
1799 void PrepareFeature::onLazyChildExpandation(const QModelIndex &index){
1800 //Nothing to do because the childmodel is not of lazy nature.
1801+ Q_UNUSED(index);
1802 }
1803
1804 void PrepareFeature::analyzeTracks(QList<int> trackIds) {
1805
1806=== added file 'mixxx/src/library/setlogfeature.cpp'
1807--- mixxx/src/library/setlogfeature.cpp 1970-01-01 00:00:00 +0000
1808+++ mixxx/src/library/setlogfeature.cpp 2011-12-10 16:03:24 +0000
1809@@ -0,0 +1,571 @@
1810+#include <QtDebug>
1811+#include <QMenu>
1812+#include <QInputDialog>
1813+#include <QFileDialog>
1814+#include <QDesktopServices>
1815+#include <QDateTime>
1816+
1817+#include "library/setlogfeature.h"
1818+#include "library/parser.h"
1819+#include "library/parserm3u.h"
1820+#include "library/parserpls.h"
1821+#include "library/parsercsv.h"
1822+
1823+
1824+#include "widget/wlibrary.h"
1825+#include "widget/wlibrarysidebar.h"
1826+#include "widget/wlibrarytextbrowser.h"
1827+#include "library/trackcollection.h"
1828+#include "library/playlisttablemodel.h"
1829+#include "mixxxkeyboard.h"
1830+#include "treeitem.h"
1831+#include "soundsourceproxy.h"
1832+#include "playerinfo.h"
1833+
1834+const QString SetlogFeature::m_sSetlogViewName = QString("SETLOGHOME");
1835+
1836+SetlogFeature::SetlogFeature(QObject* parent, ConfigObject<ConfigValue>* pConfig, TrackCollection* pTrackCollection)
1837+ : LibraryFeature(parent),
1838+ m_pTrackCollection(pTrackCollection),
1839+ m_playlistDao(pTrackCollection->getPlaylistDAO()),
1840+ m_trackDao(pTrackCollection->getTrackDAO()),
1841+ m_pCOPlayPos1(NULL),
1842+ m_pCOPlayPos2(NULL),
1843+ m_pConfig(pConfig),
1844+ m_playlistTableModel(this, pTrackCollection->getDatabase())
1845+{
1846+ m_pPlaylistTableModel = new PlaylistTableModel(this, pTrackCollection,
1847+ "mixxx.db.model.setlog");
1848+
1849+ m_pAddToAutoDJAction = new QAction(tr("Add to Auto DJ bottom"),this);
1850+ connect(m_pAddToAutoDJAction, SIGNAL(triggered()),
1851+ this, SLOT(slotAddToAutoDJ()));
1852+
1853+ m_pAddToAutoDJTopAction = new QAction(tr("Add to Auto DJ top 2"),this);
1854+ connect(m_pAddToAutoDJTopAction, SIGNAL(triggered()),
1855+ this, SLOT(slotAddToAutoDJTop()));
1856+
1857+ m_pDeletePlaylistAction = new QAction(tr("Remove"),this);
1858+ connect(m_pDeletePlaylistAction, SIGNAL(triggered()),
1859+ this, SLOT(slotDeletePlaylist()));
1860+
1861+ m_pRenamePlaylistAction = new QAction(tr("Rename"),this);
1862+ connect(m_pRenamePlaylistAction, SIGNAL(triggered()),
1863+ this, SLOT(slotRenamePlaylist()));
1864+
1865+ m_pLockPlaylistAction = new QAction(tr("Lock"),this);
1866+ connect(m_pLockPlaylistAction, SIGNAL(triggered()),
1867+ this, SLOT(slotTogglePlaylistLock()));
1868+
1869+ m_pExportPlaylistAction = new QAction(tr("Export Playlist"), this);
1870+ connect(m_pExportPlaylistAction, SIGNAL(triggered()),
1871+ this, SLOT(slotExportPlaylist()));
1872+
1873+ m_pJoinWithPreviousAction = new QAction(tr("Join with previous"), this);
1874+ connect(m_pJoinWithPreviousAction, SIGNAL(triggered()),
1875+ this, SLOT(slotJoinWithPrevious()));
1876+
1877+ connect(&m_playlistDao, SIGNAL(added(int)),
1878+ this, SLOT(slotPlaylistTableChanged(int)));
1879+
1880+ connect(&m_playlistDao, SIGNAL(deleted(int)),
1881+ this, SLOT(slotPlaylistTableChanged(int)));
1882+
1883+ connect(&m_playlistDao, SIGNAL(renamed(int)),
1884+ this, SLOT(slotPlaylistTableChanged(int)));
1885+
1886+ connect(&m_playlistDao, SIGNAL(lockChanged(int)),
1887+ this, SLOT(slotPlaylistTableChanged(int)));
1888+
1889+
1890+ m_oldTrackIdPlayer[0] = 0;
1891+ m_oldTrackIdPlayer[1] = 0;
1892+
1893+ //create a new playlist for today
1894+ QString set_log_name_format;
1895+ QString set_log_name;
1896+
1897+ set_log_name = QDate::currentDate().toString(Qt::ISODate);
1898+ set_log_name_format = set_log_name + " (%1)";
1899+ int i = 1;
1900+
1901+ // calculate name of the todays setlog
1902+ while (m_playlistDao.getPlaylistIdFromName(set_log_name) != -1) {
1903+ set_log_name = set_log_name_format.arg(++i);
1904+ }
1905+
1906+ m_playlistId = m_playlistDao.createPlaylist(set_log_name, PlaylistDAO::PLHT_SET_LOG);
1907+
1908+ if (m_playlistId == -1) {
1909+ qDebug() << tr("Playlist Creation Failed");
1910+ qDebug() << tr("An unknown error occurred while creating playlist: ") << set_log_name;
1911+ }
1912+
1913+ // Setup the sidebar playlist model
1914+ m_playlistTableModel.setTable("Playlists");
1915+ m_playlistTableModel.setFilter("hidden=2"); // PLHT_SET_LOG
1916+ m_playlistTableModel.setSort(m_playlistTableModel.fieldIndex("id"),
1917+ Qt::AscendingOrder);
1918+ m_playlistTableModel.select();
1919+
1920+ //construct child model
1921+ TreeItem *rootItem = new TreeItem();
1922+ m_childModel.setRootItem(rootItem);
1923+ constructChildModel(-1);
1924+}
1925+
1926+SetlogFeature::~SetlogFeature() {
1927+ delete m_pPlaylistTableModel;
1928+ delete m_pDeletePlaylistAction;
1929+ delete m_pAddToAutoDJAction;
1930+ delete m_pAddToAutoDJTopAction;
1931+ delete m_pRenamePlaylistAction;
1932+ delete m_pLockPlaylistAction;
1933+ if (m_pCOPlayPos1) {
1934+ delete m_pCOPlayPos1;
1935+ }
1936+ if (m_pCOPlayPos2) {
1937+ delete m_pCOPlayPos2;
1938+ }
1939+}
1940+
1941+QVariant SetlogFeature::title() {
1942+ return tr("Set Logs");
1943+}
1944+
1945+QIcon SetlogFeature::getIcon() {
1946+ return QIcon(":/images/library/ic_library_setlog.png");
1947+}
1948+
1949+
1950+void SetlogFeature::bindWidget(WLibrarySidebar* sidebarWidget,
1951+ WLibrary* libraryWidget,
1952+ MixxxKeyboard* keyboard) {
1953+ Q_UNUSED(keyboard);
1954+ Q_UNUSED(sidebarWidget);
1955+ WLibraryTextBrowser* edit = new WLibraryTextBrowser(libraryWidget);
1956+ connect(this, SIGNAL(showPage(const QUrl&)),
1957+ edit, SLOT(setSource(const QUrl&)));
1958+
1959+ if (!m_pCOPlayPos1) {
1960+ m_pCOPlayPos1 = new ControlObjectThreadMain(
1961+ ControlObject::getControl(ConfigKey("[Channel1]", "playposition")));
1962+ connect(m_pCOPlayPos1, SIGNAL(valueChanged(double)),
1963+ this, SLOT(slotPositionChanged(double)));
1964+ }
1965+ if (!m_pCOPlayPos2) {
1966+ m_pCOPlayPos2 = new ControlObjectThreadMain(
1967+ ControlObject::getControl(ConfigKey("[Channel2]", "playposition")));
1968+ connect(m_pCOPlayPos2, SIGNAL(valueChanged(double)),
1969+ this, SLOT(slotPositionChanged(double)));
1970+ }
1971+
1972+ libraryWidget->registerView(m_sSetlogViewName, edit);
1973+}
1974+
1975+void SetlogFeature::activate() {
1976+ emit(showPage(QUrl("qrc:/html/setlogs.html")));
1977+ emit(switchToView(m_sSetlogViewName));
1978+}
1979+
1980+void SetlogFeature::activateChild(const QModelIndex& index) {
1981+ //qDebug() << "SetlogFeature::activateChild()" << index;
1982+
1983+ //Switch the playlist table model's playlist.
1984+ QString playlistName = index.data().toString();
1985+ int playlistId = m_playlistDao.getPlaylistIdFromName(playlistName);
1986+ m_pPlaylistTableModel->setPlaylist(playlistId);
1987+ emit(showTrackModel(m_pPlaylistTableModel));
1988+}
1989+
1990+void SetlogFeature::onRightClick(const QPoint& globalPos) {
1991+ Q_UNUSED(globalPos);
1992+ m_lastRightClickedIndex = QModelIndex();
1993+
1994+ //Create the right-click menu
1995+ // QMenu menu(NULL);
1996+ // menu.addAction(m_pCreatePlaylistAction);
1997+ // TODO(DASCHUER) add something like disable logging
1998+ // menu.exec(globalPos);
1999+}
2000+
2001+void SetlogFeature::onRightClickChild(const QPoint& globalPos, QModelIndex index) {
2002+ //Save the model index so we can get it in the action slots...
2003+ m_lastRightClickedIndex = index;
2004+ QString playlistName = index.data().toString();
2005+ int playlistId = m_playlistDao.getPlaylistIdFromName(playlistName);
2006+
2007+
2008+ bool locked = m_playlistDao.isPlaylistLocked(playlistId);
2009+ m_pDeletePlaylistAction->setEnabled(!locked);
2010+ m_pRenamePlaylistAction->setEnabled(!locked);
2011+
2012+ m_pLockPlaylistAction->setText(locked ? tr("Unlock") : tr("Lock"));
2013+
2014+
2015+ //Create the right-click menu
2016+ QMenu menu(NULL);
2017+ //menu.addAction(m_pCreatePlaylistAction);
2018+ //menu.addSeparator();
2019+ menu.addAction(m_pAddToAutoDJAction);
2020+ menu.addAction(m_pAddToAutoDJTopAction);
2021+ menu.addAction(m_pRenamePlaylistAction);
2022+ if (playlistId != m_playlistId) {
2023+ // Todays playlist should not be locked or deleted
2024+ menu.addAction(m_pDeletePlaylistAction);
2025+ menu.addAction(m_pLockPlaylistAction);
2026+ }
2027+ if (index.row() > 0) {
2028+ // The very first setlog cannot be joint
2029+ menu.addAction(m_pJoinWithPreviousAction);
2030+ }
2031+ menu.addSeparator();
2032+ menu.addAction(m_pExportPlaylistAction);
2033+ menu.exec(globalPos);
2034+}
2035+
2036+
2037+void SetlogFeature::slotRenamePlaylist() {
2038+
2039+ qDebug() << "slotRenamePlaylist()";
2040+
2041+ QString oldName = m_lastRightClickedIndex.data().toString();
2042+ int playlistId = m_playlistDao.getPlaylistIdFromName(oldName);
2043+ bool locked = m_playlistDao.isPlaylistLocked(playlistId);
2044+
2045+ if (locked) {
2046+ qDebug() << "Skipping playlist rename because playlist" << playlistId << "is locked.";
2047+ return;
2048+ }
2049+
2050+ QString newName;
2051+ bool validNameGiven = false;
2052+
2053+ do {
2054+ bool ok = false;
2055+ newName = QInputDialog::getText(NULL,
2056+ tr("Rename Playlist"),
2057+ tr("New playlist name:"),
2058+ QLineEdit::Normal,
2059+ oldName,
2060+ &ok).trimmed();
2061+
2062+ if (!ok || oldName == newName) {
2063+ return;
2064+ }
2065+
2066+ int existingId = m_playlistDao.getPlaylistIdFromName(newName);
2067+
2068+ if (existingId != -1) {
2069+ QMessageBox::warning(NULL,
2070+ tr("Renaming Playlist Failed"),
2071+ tr("A playlist by that name already exists."));
2072+ }
2073+ else if (newName.isEmpty()) {
2074+ QMessageBox::warning(NULL,
2075+ tr("Renaming Playlist Failed"),
2076+ tr("A playlist cannot have a blank name."));
2077+ }
2078+ else {
2079+ validNameGiven = true;
2080+ }
2081+ } while (!validNameGiven);
2082+
2083+ m_playlistDao.renamePlaylist(playlistId, newName);
2084+ emit(featureUpdated());
2085+}
2086+
2087+
2088+void SetlogFeature::slotTogglePlaylistLock() {
2089+ QString playlistName = m_lastRightClickedIndex.data().toString();
2090+ int playlistId = m_playlistDao.getPlaylistIdFromName(playlistName);
2091+ bool locked = !m_playlistDao.isPlaylistLocked(playlistId);
2092+
2093+ if (!m_playlistDao.setPlaylistLocked(playlistId, locked)) {
2094+ qDebug() << "Failed to toggle lock of playlistId " << playlistId;
2095+ }
2096+}
2097+
2098+void SetlogFeature::slotDeletePlaylist() {
2099+ //qDebug() << "slotDeletePlaylist() row:" << m_lastRightClickedIndex.data();
2100+ int playlistId = m_playlistDao.getPlaylistIdFromName(m_lastRightClickedIndex.data().toString());
2101+ bool locked = m_playlistDao.isPlaylistLocked(playlistId);
2102+
2103+ if (locked) {
2104+ qDebug() << "Skipping playlist deletion because playlist" << playlistId << "is locked.";
2105+ return;
2106+ }
2107+
2108+ if (m_lastRightClickedIndex.isValid()) {
2109+ Q_ASSERT(playlistId >= 0);
2110+
2111+ m_playlistDao.deletePlaylist(playlistId);
2112+ emit(featureUpdated());
2113+ activate();
2114+ }
2115+}
2116+
2117+bool SetlogFeature::dropAccept(QUrl url) {
2118+ Q_UNUSED(url);
2119+ return false;
2120+}
2121+
2122+bool SetlogFeature::dropAcceptChild(const QModelIndex& index, QUrl url){
2123+ Q_UNUSED(url);
2124+ Q_UNUSED(index);
2125+ return false;
2126+}
2127+
2128+bool SetlogFeature::dragMoveAccept(QUrl url) {
2129+ Q_UNUSED(url);
2130+ return false;
2131+}
2132+
2133+bool SetlogFeature::dragMoveAcceptChild(const QModelIndex& index, QUrl url) {
2134+ Q_UNUSED(url);
2135+ Q_UNUSED(index);
2136+ return false;
2137+}
2138+
2139+
2140+TreeItemModel* SetlogFeature::getChildModel() {
2141+ return &m_childModel;
2142+}
2143+
2144+/**lock
2145+ * Purpose: When inserting or removing playlists,
2146+ * we require the sidebar model not to reset.
2147+ * This method queries the database and does dynamic insertion
2148+*/
2149+QModelIndex SetlogFeature::constructChildModel(int selected_id)
2150+{
2151+ QList<TreeItem*> data_list;
2152+ int nameColumn = m_playlistTableModel.record().indexOf("name");
2153+ int idColumn = m_playlistTableModel.record().indexOf("id");
2154+ int selected_row = -1;
2155+ // Access the invisible root item
2156+ TreeItem* root = m_childModel.getItem(QModelIndex());
2157+
2158+ // Create new TreeItems for the playlists in the database
2159+ for (int row = 0; row < m_playlistTableModel.rowCount(); ++row) {
2160+ QModelIndex ind = m_playlistTableModel.index(row, nameColumn);
2161+ QString playlist_name = m_playlistTableModel.data(ind).toString();
2162+ ind = m_playlistTableModel.index(row, idColumn);
2163+ int playlist_id = m_playlistTableModel.data(ind).toInt();
2164+
2165+ if ( selected_id == playlist_id) {
2166+ // save index for selection
2167+ selected_row = row;
2168+ }
2169+
2170+ // Create the TreeItem whose parent is the invisible root item
2171+ TreeItem* item = new TreeItem(playlist_name, playlist_name, this, root);
2172+ if (playlist_id == m_playlistId) {
2173+ item->setIcon(QIcon(":/images/library/ic_library_setlog_current.png"));
2174+ } else if (m_playlistDao.isPlaylistLocked(playlist_id)) {
2175+ item->setIcon(QIcon(":/images/library/ic_library_locked.png"));
2176+ } else {
2177+ item->setIcon(QIcon());
2178+ }
2179+ data_list.append(item);
2180+ }
2181+
2182+ // Append all the newly created TreeItems in a dynamic way to the childmodel
2183+ m_childModel.insertRows(data_list, 0, m_playlistTableModel.rowCount());
2184+ if (selected_row == -1) {
2185+ return QModelIndex();
2186+ }
2187+ return m_childModel.index(selected_row, 0);
2188+}
2189+
2190+/**
2191+ * Clears the child model dynamically, but the invisible root item remains
2192+ */
2193+void SetlogFeature::clearChildModel()
2194+{
2195+ m_childModel.removeRows(0,m_playlistTableModel.rowCount());
2196+}
2197+
2198+void SetlogFeature::onLazyChildExpandation(const QModelIndex &index) {
2199+ Q_UNUSED(index);
2200+ //Nothing to do because the childmodel is not of lazy nature.
2201+}
2202+
2203+void SetlogFeature::slotExportPlaylist() {
2204+ qDebug() << "Export playlist" << m_lastRightClickedIndex.data();
2205+ QString file_location = QFileDialog::getSaveFileName(
2206+ NULL,
2207+ tr("Export Playlist"),
2208+ QDesktopServices::storageLocation(QDesktopServices::MusicLocation),
2209+ tr("M3U Playlist (*.m3u);;M3U8 Playlist (*.m3u8);;"
2210+ "PLS Playlist (*.pls);;Text CSV (*.csv);;Readable Text (*.txt)"));
2211+ // Exit method if user cancelled the open dialog.
2212+ if (file_location.isNull() || file_location.isEmpty()) {
2213+ return;
2214+ }
2215+
2216+
2217+ // Create a new table model since the main one might have an active search.
2218+ QScopedPointer<PlaylistTableModel> pPlaylistTableModel(
2219+ new PlaylistTableModel(this, m_pTrackCollection,
2220+ "mixxx.db.model.playlist_export"));
2221+
2222+ pPlaylistTableModel->setPlaylist(m_pPlaylistTableModel->getPlaylist());
2223+ pPlaylistTableModel->setSort(0, Qt::AscendingOrder);
2224+ pPlaylistTableModel->select();
2225+
2226+ // check config if relative paths are desired
2227+ bool useRelativePath = static_cast<bool>(m_pConfig->getValueString(
2228+ ConfigKey("[Library]", "UseRelativePathOnExport")).toInt());
2229+
2230+ if (file_location.endsWith(".csv", Qt::CaseInsensitive)) {
2231+ ParserCsv::writeCSVFile(file_location, pPlaylistTableModel.data(), useRelativePath);
2232+ } else if (file_location.endsWith(".txt", Qt::CaseInsensitive)) {
2233+ ParserCsv::writeReadableTextFile(file_location, pPlaylistTableModel.data());
2234+ } else {
2235+ // Create and populate a list of files of the playlist
2236+ QList<QString> playlist_items;
2237+ int rows = pPlaylistTableModel->rowCount();
2238+ for (int i = 0; i < rows; ++i) {
2239+ QModelIndex index = pPlaylistTableModel->index(i, 0);
2240+ playlist_items << pPlaylistTableModel->getTrackLocation(index);
2241+ }
2242+
2243+ if (file_location.endsWith(".pls", Qt::CaseInsensitive)) {
2244+ ParserPls::writePLSFile(file_location, playlist_items, useRelativePath);
2245+ } else if (file_location.endsWith(".m3u8", Qt::CaseInsensitive)) {
2246+ ParserM3u::writeM3U8File(file_location, playlist_items, useRelativePath);
2247+ } else {
2248+ //default export to M3U if file extension is missing
2249+ if(!file_location.endsWith(".m3u", Qt::CaseInsensitive))
2250+ {
2251+ qDebug() << "Crate export: No valid file extension specified. Appending .m3u "
2252+ << "and exporting to M3U.";
2253+ file_location.append(".m3u");
2254+ }
2255+ ParserM3u::writeM3UFile(file_location, playlist_items, useRelativePath);
2256+ }
2257+ }
2258+}
2259+
2260+void SetlogFeature::slotAddToAutoDJ() {
2261+ //qDebug() << "slotAddToAutoDJ() row:" << m_lastRightClickedIndex.data();
2262+ addToAutoDJ(false); // Top = True
2263+}
2264+
2265+void SetlogFeature::slotAddToAutoDJTop() {
2266+ //qDebug() << "slotAddToAutoDJTop() row:" << m_lastRightClickedIndex.data();
2267+ addToAutoDJ(true); // bTop = True
2268+}
2269+
2270+void SetlogFeature::addToAutoDJ(bool bTop) {
2271+ //qDebug() << "slotAddToAutoDJ() row:" << m_lastRightClickedIndex.data();
2272+
2273+ if (m_lastRightClickedIndex.isValid()) {
2274+ int playlistId = m_playlistDao.getPlaylistIdFromName(
2275+ m_lastRightClickedIndex.data().toString());
2276+ if (playlistId >= 0) {
2277+ // Insert this playlist
2278+ m_playlistDao.addToAutoDJQueue(playlistId, bTop);
2279+ }
2280+ }
2281+ emit(featureUpdated());
2282+}
2283+
2284+void SetlogFeature::slotJoinWithPrevious() {
2285+ //qDebug() << "slotJoinWithPrevious() row:" << m_lastRightClickedIndex.data();
2286+
2287+ if (m_lastRightClickedIndex.isValid()) {
2288+ int currentPlaylistId = m_playlistDao.getPlaylistIdFromName(
2289+ m_lastRightClickedIndex.data().toString());
2290+
2291+ if (currentPlaylistId >= 0) {
2292+
2293+ bool locked = m_playlistDao.isPlaylistLocked(currentPlaylistId);
2294+
2295+ if (locked) {
2296+ qDebug() << "Skipping playlist deletion because playlist" << currentPlaylistId << "is locked.";
2297+ return;
2298+ }
2299+
2300+ // Add every track from right klicked playlist to that with the next smaller ID
2301+ int previousPlaylistId = m_playlistDao.getPreviousPlaylist(currentPlaylistId, PlaylistDAO::PLHT_SET_LOG);
2302+ if (previousPlaylistId >= 0) {
2303+
2304+ m_pPlaylistTableModel->setPlaylist(previousPlaylistId);
2305+
2306+ if (currentPlaylistId == m_playlistId) {
2307+ // mark all the Tracks in the previous Playlist as played
2308+
2309+ m_pPlaylistTableModel->select();
2310+ int rows = m_pPlaylistTableModel->rowCount();
2311+ for(int i = 0; i < rows; ++i){
2312+ QModelIndex index = m_pPlaylistTableModel->index(i,0);
2313+ if (index.isValid()) {
2314+ TrackPointer track = m_pPlaylistTableModel->getTrack(index);
2315+ track->restorePlayed(true);
2316+ }
2317+ }
2318+
2319+ // Change current setlog
2320+ m_playlistId = previousPlaylistId;
2321+ }
2322+ qDebug() << "slotJoinWithPrevious() current:" << currentPlaylistId << " previous:" << previousPlaylistId;
2323+ m_playlistDao.copyPlaylistTracks(currentPlaylistId, previousPlaylistId);
2324+ m_playlistDao.deletePlaylist(currentPlaylistId);
2325+ slotPlaylistTableChanged(previousPlaylistId); // For moving selection
2326+ emit(showTrackModel(m_pPlaylistTableModel));
2327+ emit(featureUpdated());
2328+ }
2329+ }
2330+ }
2331+}
2332+
2333+void SetlogFeature::slotPositionChanged(double value) {
2334+ Q_UNUSED(value);
2335+ TrackPointer currendPlayingTrack;
2336+ int currendPlayingTrackId = 0;
2337+
2338+ int deck = PlayerInfo::Instance().getCurrentPlayingDeck();
2339+ if ( deck && deck <= 2) {
2340+ QString chan = QString("[Channel%1]").arg(deck);
2341+ currendPlayingTrack = PlayerInfo::Instance().getTrackInfo(chan);
2342+ if (currendPlayingTrack) {
2343+ currendPlayingTrackId = currendPlayingTrack->getId();
2344+ }
2345+ if (m_oldTrackIdPlayer[deck-1] != currendPlayingTrackId) {
2346+ // The audience listens to a new track
2347+
2348+ qDebug() << "The audience listens to track " << currendPlayingTrackId;
2349+ currendPlayingTrack->setPlayed(true); // Here the song is realy played, not only loaded.
2350+
2351+ if (m_pPlaylistTableModel->getPlaylist() == m_playlistId) {
2352+ // View needs a refresh
2353+ m_pPlaylistTableModel->appendTrack(currendPlayingTrackId);
2354+ }
2355+ else {
2356+ m_playlistDao.appendTrackToPlaylist(currendPlayingTrackId, m_playlistId);
2357+ }
2358+ m_oldTrackIdPlayer[deck-1] = currendPlayingTrackId;
2359+ }
2360+ }
2361+}
2362+
2363+void SetlogFeature::slotPlaylistTableChanged(int playlistId) {
2364+ //qDebug() << "slotPlaylistTableChanged() playlistId:" << playlistId;
2365+ enum PlaylistDAO::hidden_type type = m_playlistDao.getHiddenType(playlistId);
2366+ if ( type == PlaylistDAO::PLHT_SET_LOG
2367+ || type == PlaylistDAO::PLHT_UNKNOWN // In case of a deleted Playlist
2368+ ){
2369+ clearChildModel();
2370+ m_playlistTableModel.select();
2371+ m_lastRightClickedIndex = constructChildModel(playlistId);
2372+
2373+ if(type != PlaylistDAO::PLHT_UNKNOWN) {
2374+ // Switch the view to the playlist.
2375+ m_pPlaylistTableModel->setPlaylist(playlistId);
2376+ // Update selection
2377+ emit(featureSelect(this, m_lastRightClickedIndex));
2378+ }
2379+ }
2380+}
2381
2382=== added file 'mixxx/src/library/setlogfeature.h'
2383--- mixxx/src/library/setlogfeature.h 1970-01-01 00:00:00 +0000
2384+++ mixxx/src/library/setlogfeature.h 2011-12-10 16:03:24 +0000
2385@@ -0,0 +1,93 @@
2386+// setlogfeature.h
2387+// Created 8/17/09 by RJ Ryan (rryan@mit.edu)
2388+
2389+#ifndef SETLOGFEATURE_H
2390+#define SETLOGFEATURE_H
2391+
2392+#include <QSqlTableModel>
2393+#include <QAction>
2394+#include <QList>
2395+
2396+#include "library/libraryfeature.h"
2397+#include "library/dao/playlistdao.h"
2398+#include "library/dao/trackdao.h"
2399+#include "treeitemmodel.h"
2400+#include "configobject.h"
2401+#include "controlobject.h"
2402+#include "controlobjectthreadmain.h"
2403+
2404+
2405+
2406+class PlaylistTableModel;
2407+class TrackCollection;
2408+
2409+class SetlogFeature : public LibraryFeature {
2410+ Q_OBJECT
2411+public:
2412+ SetlogFeature(QObject* parent, ConfigObject<ConfigValue>* pConfig, TrackCollection* pTrackCollection);
2413+ virtual ~SetlogFeature();
2414+
2415+ QVariant title();
2416+ QIcon getIcon();
2417+
2418+ bool dropAccept(QUrl url);
2419+ bool dropAcceptChild(const QModelIndex& index, QUrl url);
2420+ bool dragMoveAccept(QUrl url);
2421+ bool dragMoveAcceptChild(const QModelIndex& index, QUrl url);
2422+
2423+ TreeItemModel* getChildModel();
2424+
2425+ void bindWidget(WLibrarySidebar* sidebarWidget,
2426+ WLibrary* libraryWidget,
2427+ MixxxKeyboard* keyboard);
2428+ signals:
2429+ void showPage(const QUrl& page);
2430+
2431+public slots:
2432+ void activate();
2433+ void activateChild(const QModelIndex& index);
2434+ void onRightClick(const QPoint& globalPos);
2435+ void onRightClickChild(const QPoint& globalPos, QModelIndex index);
2436+ void onLazyChildExpandation(const QModelIndex& index);
2437+
2438+ void slotDeletePlaylist();
2439+ void slotAddToAutoDJ();
2440+ void slotAddToAutoDJTop();
2441+ void slotRenamePlaylist();
2442+ void slotTogglePlaylistLock();
2443+ void slotExportPlaylist();
2444+ void slotJoinWithPrevious();
2445+
2446+ void slotPositionChanged(double /*value*/);
2447+ void slotPlaylistTableChanged(int playlistId);
2448+
2449+ private:
2450+ QModelIndex constructChildModel(int selected_id);
2451+ void clearChildModel();
2452+ void addToAutoDJ(bool bTop);
2453+
2454+ TrackCollection* m_pTrackCollection;
2455+ PlaylistTableModel* m_pPlaylistTableModel;
2456+ PlaylistDAO &m_playlistDao;
2457+ TrackDAO &m_trackDao;
2458+ QAction *m_pDeletePlaylistAction;
2459+ QAction *m_pAddToAutoDJAction;
2460+ QAction *m_pAddToAutoDJTopAction;
2461+ QAction *m_pRenamePlaylistAction;
2462+ QAction *m_pLockPlaylistAction;
2463+ QAction *m_pExportPlaylistAction;
2464+ QAction *m_pJoinWithPreviousAction;
2465+ ControlObjectThreadMain* m_pCOPlayPos1;
2466+ ControlObjectThreadMain* m_pCOPlayPos2;
2467+ QModelIndex m_lastRightClickedIndex;
2468+ TreeItemModel m_childModel;
2469+ ConfigObject<ConfigValue>* m_pConfig;
2470+ QSqlTableModel m_playlistTableModel;
2471+ const static QString m_sSetlogViewName;
2472+ int m_playlistId;
2473+ int m_oldTrackIdPlayer[2];
2474+ //int m_oldTrackPlayer2;
2475+ //int m_oldCurrendPlayingTrack;
2476+};
2477+
2478+#endif /* SETLOGFEATURE_H */
2479
2480=== modified file 'mixxx/src/library/sidebarmodel.cpp'
2481--- mixxx/src/library/sidebarmodel.cpp 2011-10-21 02:26:31 +0000
2482+++ mixxx/src/library/sidebarmodel.cpp 2011-12-10 16:03:24 +0000
2483@@ -17,11 +17,14 @@
2484
2485 void SidebarModel::addLibraryFeature(LibraryFeature* feature) {
2486 m_sFeatures.push_back(feature);
2487- connect(feature, SIGNAL(featureUpdated()), this, SLOT(refreshData()));
2488+ connect(feature, SIGNAL(featureUpdated()),
2489+ this, SLOT(refreshData()));
2490 connect(feature, SIGNAL(featureIsLoading(LibraryFeature*)),
2491 this, SLOT(slotFeatureIsLoading(LibraryFeature*)));
2492 connect(feature, SIGNAL(featureLoadingFinished(LibraryFeature*)),
2493- this,SLOT(slotFeatureLoadingFinished(LibraryFeature*)));
2494+ this, SLOT(slotFeatureLoadingFinished(LibraryFeature*)));
2495+ connect(feature, SIGNAL(featureSelect(LibraryFeature*, const QModelIndex&)),
2496+ this, SLOT(slotFeatureSelect(LibraryFeature*, const QModelIndex&)));
2497
2498 QAbstractItemModel* model = feature->getChildModel();
2499
2500@@ -108,19 +111,23 @@
2501 return QModelIndex();
2502 } else {
2503 TreeItem* tree_item = (TreeItem*)index.internalPointer();
2504+ TreeItem* tree_item_parent = tree_item->parent();
2505 // if we have selected an item at the first level of a childnode
2506- if (tree_item->parent()->data() == "$root"){
2507- LibraryFeature* feature = tree_item->getFeature();
2508- for (int i = 0; i < m_sFeatures.size(); ++i) {
2509- if (feature == m_sFeatures[i]) {
2510- // create a ModelIndex for parent 'this' having a
2511- // library feature at position 'i'
2512- return createIndex(i, 0, (void*)this);
2513- }
2514- }
2515+
2516+ if (tree_item_parent) {
2517+ if (tree_item_parent->data() == "$root"){
2518+ LibraryFeature* feature = tree_item->getFeature();
2519+ for (int i = 0; i < m_sFeatures.size(); ++i) {
2520+ if (feature == m_sFeatures[i]) {
2521+ // create a ModelIndex for parent 'this' having a
2522+ // library feature at position 'i'
2523+ return createIndex(i, 0, (void*)this);
2524+ }
2525+ }
2526+ }
2527+ // if we have selected an item at some deeper level of a childnode
2528+ return createIndex(tree_item_parent->row(), 0 , tree_item_parent);
2529 }
2530- // if we have selected an item at some deeper level of a childnode
2531- return createIndex(tree_item->parent()->row(), 0 , tree_item->parent());
2532 }
2533 }
2534 return QModelIndex();
2535@@ -144,7 +151,8 @@
2536 }
2537
2538 int SidebarModel::columnCount(const QModelIndex& parent) const {
2539- //qDebug() << "SidebarModel::columnCount parent=" << parent;
2540+ Q_UNUSED(parent);
2541+ //qDebug() << "SidebarModel::columnCount parent=" << parent;
2542 // TODO(rryan) will we ever have columns? I don't think so.
2543 return 1;
2544 }
2545@@ -312,6 +320,8 @@
2546 }
2547
2548 void SidebarModel::slotDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) {
2549+ Q_UNUSED(topLeft);
2550+ Q_UNUSED(bottomRight);
2551 //qDebug() << "slotDataChanged topLeft:" << topLeft << "bottomRight:" << bottomRight;
2552 }
2553
2554@@ -330,13 +340,19 @@
2555 }
2556
2557 void SidebarModel::slotRowsInserted(const QModelIndex& parent, int start, int end) {
2558- // qDebug() << "slotRowsInserted" << parent << start << end;
2559+ Q_UNUSED(parent);
2560+ Q_UNUSED(start);
2561+ Q_UNUSED(end);
2562+ // qDebug() << "slotRowsInserted" << parent << start << end;
2563 //QModelIndex newParent = translateSourceIndex(parent);
2564 endInsertRows();
2565 }
2566
2567 void SidebarModel::slotRowsRemoved(const QModelIndex& parent, int start, int end) {
2568- //qDebug() << "slotRowsRemoved" << parent << start << end;
2569+ Q_UNUSED(parent);
2570+ Q_UNUSED(start);
2571+ Q_UNUSED(end);
2572+ //qDebug() << "slotRowsRemoved" << parent << start << end;
2573 //QModelIndex newParent = translateSourceIndex(parent);
2574 endRemoveRows();
2575 }
2576@@ -355,16 +371,16 @@
2577 */
2578 void SidebarModel::slotFeatureIsLoading(LibraryFeature * feature)
2579 {
2580- featureRenamed(feature);
2581- selectFeature(feature);
2582+ featureRenamed(feature);
2583+ slotFeatureSelect(feature);
2584 }
2585
2586 /* Tobias: This slot is somewhat redundant but I decided
2587 * to leave it for code readability reasons
2588 */
2589 void SidebarModel::slotFeatureLoadingFinished(LibraryFeature * feature){
2590- featureRenamed(feature);
2591- selectFeature(feature);
2592+ featureRenamed(feature);
2593+ slotFeatureSelect(feature);
2594 }
2595
2596 void SidebarModel::featureRenamed(LibraryFeature* pFeature){
2597@@ -376,11 +392,19 @@
2598 }
2599 }
2600
2601-void SidebarModel::selectFeature(LibraryFeature* pFeature) {
2602- for (int i=0; i < m_sFeatures.size(); ++i) {
2603- if (m_sFeatures[i] == pFeature) {
2604- QModelIndex ind = index(i, 0);
2605- emit(selectIndex(ind));
2606- }
2607+void SidebarModel::slotFeatureSelect(LibraryFeature* pFeature, const QModelIndex& featureIndex)
2608+{
2609+ QModelIndex ind;
2610+ if (featureIndex.isValid()) {
2611+ TreeItem* item = (TreeItem*)featureIndex.internalPointer();
2612+ ind = createIndex(featureIndex.row(), featureIndex.column(), item);
2613+ } else {
2614+ for (int i=0; i < m_sFeatures.size(); ++i) {
2615+ if (m_sFeatures[i] == pFeature) {
2616+ ind = index(i, 0);
2617+ break;
2618+ }
2619+ }
2620 }
2621+ emit(selectIndex(ind));
2622 }
2623
2624=== modified file 'mixxx/src/library/sidebarmodel.h'
2625--- mixxx/src/library/sidebarmodel.h 2011-03-27 20:12:33 +0000
2626+++ mixxx/src/library/sidebarmodel.h 2011-12-10 16:03:24 +0000
2627@@ -39,7 +39,7 @@
2628 void doubleClicked(const QModelIndex& index);
2629 void rightClicked(const QPoint& globalPos, const QModelIndex& index);
2630 void refreshData();
2631- void selectFeature(LibraryFeature* pFeature);
2632+ void slotFeatureSelect(LibraryFeature* pFeature, const QModelIndex& index = QModelIndex());
2633
2634 // Slots for every single QAbstractItemModel signal
2635 // void slotColumnsAboutToBeInserted(const QModelIndex& parent, int start, int end);
2636
2637=== modified file 'mixxx/src/library/trackcollection.cpp'
2638--- mixxx/src/library/trackcollection.cpp 2011-10-21 02:09:53 +0000
2639+++ mixxx/src/library/trackcollection.cpp 2011-12-10 16:03:24 +0000
2640@@ -15,9 +15,9 @@
2641 TrackCollection::TrackCollection(ConfigObject<ConfigValue>* pConfig)
2642 : m_pConfig(pConfig),
2643 m_db(QSqlDatabase::addDatabase("QSQLITE")), // defaultConnection
2644- m_cueDao(m_db),
2645 m_playlistDao(m_db),
2646 m_crateDao(m_db),
2647+ m_cueDao(m_db),
2648 m_trackDao(m_db, m_cueDao, m_playlistDao, m_crateDao, pConfig),
2649 m_supportedFileExtensionsRegex(
2650 SoundSourceProxy::supportedFileExtensionsRegex(),
2651
2652=== modified file 'mixxx/src/mixxx.cpp'
2653--- mixxx/src/mixxx.cpp 2011-12-06 02:54:33 +0000
2654+++ mixxx/src/mixxx.cpp 2011-12-10 16:03:24 +0000
2655@@ -414,7 +414,7 @@
2656
2657 m_pWidgetParent = NULL;
2658 // Loads the skin as a child of m_pView
2659- // assignment itentional in next line
2660+ // assignment intentional in next line
2661 if (!(m_pWidgetParent = m_pSkinLoader->loadDefaultSkin(m_pView,
2662 m_pKeyboard,
2663 m_pPlayerManager,
2664
2665=== modified file 'mixxx/src/trackinfoobject.cpp'
2666--- mixxx/src/trackinfoobject.cpp 2011-10-03 22:37:35 +0000
2667+++ mixxx/src/trackinfoobject.cpp 2011-12-10 16:03:24 +0000
2668@@ -556,15 +556,24 @@
2669 QMutexLocker lock(&m_qMutex);
2670 if (bPlayed) {
2671 ++m_iTimesPlayed;
2672- setDirty(true);
2673+ setDirty(true);
2674 }
2675 else if (m_bPlayed && !bPlayed) {
2676 --m_iTimesPlayed;
2677- setDirty(true);
2678+ setDirty(true);
2679 }
2680 m_bPlayed = bPlayed;
2681 }
2682
2683+void TrackInfoObject::restorePlayed(bool bPlayed)
2684+{
2685+ QMutexLocker lock(&m_qMutex);
2686+ if (bPlayed != m_bPlayed) {
2687+ m_bPlayed = bPlayed;
2688+ setDirty(true);
2689+ }
2690+}
2691+
2692 QString TrackInfoObject::getComment() const
2693 {
2694 QMutexLocker lock(&m_qMutex);
2695
2696=== modified file 'mixxx/src/trackinfoobject.h'
2697--- mixxx/src/trackinfoobject.h 2011-10-03 22:37:35 +0000
2698+++ mixxx/src/trackinfoobject.h 2011-12-10 16:03:24 +0000
2699@@ -186,6 +186,8 @@
2700 bool getPlayed() const;
2701 /** Set Played status*/
2702 void setPlayed(bool);
2703+ /** To restore the played flag without increment the played counter */
2704+ void restorePlayed(bool bPlayed);
2705
2706 int getId() const;
2707
2708
2709=== modified file 'mixxx/src/widget/wlibrarysidebar.cpp'
2710--- mixxx/src/widget/wlibrarysidebar.cpp 2011-03-27 21:15:15 +0000
2711+++ mixxx/src/widget/wlibrarysidebar.cpp 2011-12-10 16:03:24 +0000
2712@@ -199,4 +199,9 @@
2713 QItemSelectionModel* pModel = new QItemSelectionModel(model());
2714 pModel->select(index, QItemSelectionModel::Select);
2715 setSelectionModel(pModel);
2716+
2717+ if (index.parent().isValid()) {
2718+ expand(index.parent());
2719+ }
2720+ scrollTo(index);
2721 }