Merge lp:~mixxxdevelopers/mixxx/tree_item_browser into lp:~mixxxdevelopers/mixxx/trunk

Proposed by RAFFI TEA
Status: Merged
Merged at revision: 2714
Proposed branch: lp:~mixxxdevelopers/mixxx/tree_item_browser
Merge into: lp:~mixxxdevelopers/mixxx/trunk
Diff against target: 1262 lines (+452/-121)
36 files modified
mixxx/src/dlgprefplaylist.cpp (+4/-0)
mixxx/src/dlgprefplaylistdlg.ui (+7/-0)
mixxx/src/library/autodjfeature.cpp (+3/-0)
mixxx/src/library/autodjfeature.h (+1/-0)
mixxx/src/library/browse/browsefeature.cpp (+21/-10)
mixxx/src/library/browse/browsefeature.h (+3/-1)
mixxx/src/library/browse/browsetablemodel.cpp (+33/-25)
mixxx/src/library/browse/browsetablemodel.h (+40/-34)
mixxx/src/library/browse/browsethread.cpp (+76/-30)
mixxx/src/library/browse/browsethread.h (+28/-6)
mixxx/src/library/cratefeature.cpp (+46/-3)
mixxx/src/library/cratefeature.h (+6/-1)
mixxx/src/library/itunes/itunesfeature.cpp (+3/-1)
mixxx/src/library/itunes/itunesfeature.h (+1/-0)
mixxx/src/library/library.cpp (+3/-3)
mixxx/src/library/libraryfeature.h (+11/-0)
mixxx/src/library/mixxxlibraryfeature.cpp (+2/-0)
mixxx/src/library/mixxxlibraryfeature.h (+1/-0)
mixxx/src/library/parser.cpp (+3/-0)
mixxx/src/library/parser.h (+1/-0)
mixxx/src/library/parserm3u.cpp (+34/-0)
mixxx/src/library/parserm3u.h (+3/-0)
mixxx/src/library/parserpls.cpp (+31/-0)
mixxx/src/library/parserpls.h (+2/-0)
mixxx/src/library/playlistfeature.cpp (+46/-2)
mixxx/src/library/playlistfeature.h (+6/-1)
mixxx/src/library/preparefeature.cpp (+5/-0)
mixxx/src/library/preparefeature.h (+1/-0)
mixxx/src/library/promotracksfeature.cpp (+3/-0)
mixxx/src/library/promotracksfeature.h (+1/-0)
mixxx/src/library/rhythmbox/rhythmboxfeature.cpp (+3/-1)
mixxx/src/library/rhythmbox/rhythmboxfeature.h (+1/-0)
mixxx/src/library/sidebarmodel.cpp (+18/-3)
mixxx/src/library/sidebarmodel.h (+1/-0)
mixxx/src/library/traktor/traktorfeature.cpp (+3/-0)
mixxx/src/library/traktor/traktorfeature.h (+1/-0)
To merge this branch: bzr merge lp:~mixxxdevelopers/mixxx/tree_item_browser
Reviewer Review Type Date Requested Status
RJ Skerry-Ryan Approve
Review via email: mp+52850@code.launchpad.net

Description of the change

This a rewrite of the BrowseFeature and has been tested on OS X, Windows and Linux.

The BrowseFeature is now expandable showing drive letters and folders. All supported audio files are listed in the right track table view along with their metadata.

Thanks,

Tobias

To post a comment you must log in.
Revision history for this message
RAFFI TEA (raffitea) wrote :

I have tweaked the new Browser a bit. The sidebar now distinguishes between single and double clicks, see comment of commit r2658.

2659. By Raffitea

Fixed some typos.

2660. By Raffitea

Implemented Playlist Export to M3U and PLS.

2661. By Raffitea

merging from trunk

2662. By Raffitea

Implemented relative M3U and PLS playlist export. Works fine on OS X with VLC but iTunes does not support relative paths.

2663. By Raffitea

Added an option to preferences to make relative playlist export optional.

2664. By Raffitea

Fixed a compiler warning on Windows. Also tested PLS and M3U export on Windows. Works as expected with Media Player and iTunes but also VLC, RealPlayer and Winamp.

2665. By Raffitea

Added a comment about QDir::relativePath().

Revision history for this message
RAFFI TEA (raffitea) wrote :

I've added playlist export to M3U and PLS since it is requested more than once in the forums. Relative paths for PLS and M3U is realized. By default relative paths are turned off. You can enable that feature in the preferences under "Library".

Please not that playlist import is already in trunk which supports relative paths, too.

Revision history for this message
jus (jus) wrote :

Hi Tobias,
thanks for the good work, really like the improvements.
Currently testing http://bazaar.launchpad.net/~mixxxdevelopers/mixxx/tree_item_browser/revision/2659 on MacOS 10.6.6

1st
Performance problem in "Browse" mode
The track listing is super slow, its like every track is displayed line after line. It takes about 2 seconds to display a folder with 50 mp3 tracks in there.

Every track got its own "Debug: []: ~TrackInfoObject()" line in the terminal output.

2nd
Could you please elaborate how to save a playlist in pls format? The "Export Playlist" file dialog saves as m3u per default.

Revision history for this message
jus (jus) wrote :

> Currently testing http://bazaar.launchpad.net/~mixxxdevelopers/mixxx/tree_item
> _browser/revision/2659 on MacOS 10.6.6
Ok, reply to myself since there is no edit function :-)
It was actually http://bazaar.launchpad.net/~mixxxdevelopers/mixxx/tree_item_browser/revision/2665

2666. By Raffitea

Merging from trunk

2667. By Raffitea

Remove qDebug for TIO

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

I believe the slow populating is because it pauses for 50ms between reading
every track. Tobias said this was to ease the load that browse-mode puts on
the computer to prevent skips while playing and browsing at the same time.

On Mon, Mar 14, 2011 at 11:46 AM, jus <email address hidden> wrote:

> > Currently testing
> http://bazaar.launchpad.net/~mixxxdevelopers/mixxx/tree_item
> > _browser/revision/2659 on MacOS 10.6.6
> Ok, reply to myself since there is no edit function :-)
> It was actually
> http://bazaar.launchpad.net/~mixxxdevelopers/mixxx/tree_item_browser/revision/2665
>
> --
>
> https://code.launchpad.net/~mixxxdevelopers/mixxx/tree_item_browser/+merge/52850
> You are requested to review the proposed merge of
> lp:~mixxxdevelopers/mixxx/tree_item_browser into lp:mixxx.
>

2668. By Raffitea

Improved Playlist export dialog.

Revision history for this message
RAFFI TEA (raffitea) wrote :

Addressed most of your problems in r2668.

You're right. The performance is quite slow. I did it on purpose because a faster browse feature will cause the waveform to stutter. Feel free to tweak the browser performance. You'll need to decrease the value of line 130 in browsethread.cpp.

I'll try to improve the performance soon. I've some ideas on how to realize that :-)

2669. By Raffitea

Drastically improved the population of the tracktable view in BrowseFeature. Hopefully does not freez the GUI while clicking through the folder structure.

Revision history for this message
RAFFI TEA (raffitea) wrote :

Ok. Performance should be much better now while not hitting the waveform to much :-)

2670. By Tobias <Tobias@Silverstar>

Merging manually from trunk

2671. By Tobias <Tobias@Silverstar>

Merging manually from trunk the third time.

2672. By Tobias <Tobias@Silverstar>

Merging from trunk to get library caching fixes

2673. By Raffitea

made BrowseTableModel singleton because the recording feature will use the BrowseTableModel to display contents of a special recording directory. This approach avoids two BrowseThreads. If I had made BrowseThread singleton there would be two BrowseTableModel objects and both would populate when the Thread is active. This is not performant

2674. By Raffitea

Merging from trunk to get PrepareFeature fixes

2675. By Raffitea

Merging from trunk

2676. By Raffitea

Merging from trunk

2677. By Raffitea

Removed the singleton character of BrowseTableModel. This was a really stupid idea....

2678. By Raffitea

Now made BrowseThread singleton. BrowseTableModel objects will only process received signals if they have asked the thread to do so.

2679. By Raffitea

Merging from trunk to get auto-expand feature

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

More solid work, Tobias!

* The strings in the CrateFeature context menu should be "Export Crate" and "Import Crate" probably.

* If the user types an invalid filename, I would re-popup the box with whatever they typed so they can pick a new name. I typed "crate" and hit save and then had to right-click and hit export again and type "crate.pls".

* BrowseTableModel now has BlockingQueuedConnections to slotClear and slotInsert. BlockingQueuedConnections are dangerous because they make it easy to cause deadlocks. Could you add warnings to those two methods that they are usually called from a BlockingQueuedConnection from the BrowseThread? The way it's written shouldn't cause any problems, but in the future if someone goes and adds something there it could cause issues, so it's good to have the warning.

* Could you add a static member variable s_mutex alongside the m_instance (also, rename that to s_instance to call out that it's static?) variable in BrowseTableModel and use that instead of the local-variable static mutex in the getInstance/destroyInstance methods? There's technically a tiny race condition in the example code it was taken from.

review: Needs Fixing
2680. By Raffitea

Merging from trunk

2681. By Raffitea

Renamed context menu entry for crate export/import; If file extension is missing, append .m3u to the export file and continue to export in m3u; Replaced Qt::BlockingQueuedConnection with QueuedConnection, which seems to work for rapid sidebar folder iterations with keyboard/MIDI; Added a global static member for BrowseThread::getInstance().

Revision history for this message
RAFFI TEA (raffitea) wrote :

Addressed all your concerns.

If QueuedConnection cause segfaults on rapid sidebar folder iterations, we can replace it with the BlockingQueuedConnection. Works fine now on OS X, but needs some testing on Windows and Linux, too.

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

Cool, looks good. Merge away!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'mixxx/src/dlgprefplaylist.cpp'
2--- mixxx/src/dlgprefplaylist.cpp 2011-03-05 05:18:52 +0000
3+++ mixxx/src/dlgprefplaylist.cpp 2011-03-28 22:22:23 +0000
4@@ -152,6 +152,7 @@
5 checkBoxPromoStats->setChecked((bool)config->getValueString(ConfigKey("[Promo]","StatTracking")).toInt());
6 checkBox_library_scan->setChecked((bool)config->getValueString(ConfigKey("[Library]","RescanOnStartup")).toInt());
7 checkbox_ID3_sync->setChecked((bool)config->getValueString(ConfigKey("[Library]","WriteAudioTags")).toInt());
8+ checkBox_use_relative_path->setChecked((bool)config->getValueString(ConfigKey("[Library]","UseRelativePathOnExport")).toInt());
9
10
11 }
12@@ -237,6 +238,9 @@
13 config->set(ConfigKey("[Library]","WriteAudioTags"),
14 ConfigValue((int)checkbox_ID3_sync->isChecked()));
15
16+ config->set(ConfigKey("[Library]","UseRelativePathOnExport"),
17+ ConfigValue((int)checkBox_use_relative_path->isChecked()));
18+
19
20 config->Save();
21
22
23=== modified file 'mixxx/src/dlgprefplaylistdlg.ui'
24--- mixxx/src/dlgprefplaylistdlg.ui 2010-12-30 09:59:53 +0000
25+++ mixxx/src/dlgprefplaylistdlg.ui 2011-03-28 22:22:23 +0000
26@@ -168,6 +168,13 @@
27 </property>
28 </widget>
29 </item>
30+ <item row="2" column="0">
31+ <widget class="QCheckBox" name="checkBox_use_relative_path">
32+ <property name="text">
33+ <string>Use relative paths for playlist export if possible</string>
34+ </property>
35+ </widget>
36+ </item>
37 </layout>
38 </widget>
39 </item>
40
41=== modified file 'mixxx/src/library/autodjfeature.cpp'
42--- mixxx/src/library/autodjfeature.cpp 2011-03-27 01:36:30 +0000
43+++ mixxx/src/library/autodjfeature.cpp 2011-03-28 22:22:23 +0000
44@@ -120,3 +120,6 @@
45 QUrl url) {
46 return false;
47 }
48+void AutoDJFeature::onLazyChildExpandation(const QModelIndex &index){
49+ //Nothing to do because the childmodel is not of lazy nature.
50+}
51
52=== modified file 'mixxx/src/library/autodjfeature.h'
53--- mixxx/src/library/autodjfeature.h 2010-12-23 18:55:54 +0000
54+++ mixxx/src/library/autodjfeature.h 2011-03-28 22:22:23 +0000
55@@ -42,6 +42,7 @@
56 void activateChild(const QModelIndex& index);
57 void onRightClick(const QPoint& globalPos);
58 void onRightClickChild(const QPoint& globalPos, QModelIndex index);
59+ void onLazyChildExpandation(const QModelIndex& index);
60
61 private:
62 ConfigObject<ConfigValue>* m_pConfig;
63
64=== modified file 'mixxx/src/library/browse/browsefeature.cpp'
65--- mixxx/src/library/browse/browsefeature.cpp 2011-03-20 03:47:06 +0000
66+++ mixxx/src/library/browse/browsefeature.cpp 2011-03-28 22:22:23 +0000
67@@ -135,10 +135,30 @@
68 void BrowseFeature::activate() {
69 emit(restoreSearch(m_currentSearch));
70 }
71-
72+/*
73+ * Note: This is executed whenever you single click on an child item
74+ * Single clicks will not populate sub folders
75+ */
76 void BrowseFeature::activateChild(const QModelIndex& index) {
77 TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
78 qDebug() << "BrowseFeature::activateChild " << item->data() << " " << item->dataPath();
79+ m_browseModel.setPath(item->dataPath().toString());
80+ emit(showTrackModel(&m_proxyModel));
81+
82+}
83+
84+void BrowseFeature::onRightClick(const QPoint& globalPos) {
85+}
86+
87+void BrowseFeature::onRightClickChild(const QPoint& globalPos, QModelIndex index) {
88+}
89+/*
90+ * This is called whenever you double click or use the triangle symbol to expand
91+ * the subtree. The method will read the subfolders.
92+ */
93+void BrowseFeature::onLazyChildExpandation(const QModelIndex &index){
94+ TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
95+ qDebug() << "BrowseFeature::onLazyChildExpandation " << item->data() << " " << item->dataPath();
96
97 // If the item is a build-in node, e.g., 'QuickLink' return
98 if(item->dataPath().toString() == QUICK_LINK_NODE)
99@@ -191,13 +211,4 @@
100 //On Ubuntu 10.04, otherwise, this will draw an icon although the folder has no subfolders
101 if(!folders.isEmpty())
102 m_childModel.insertRows(folders, 0, folders.size() , index);
103- m_browseModel.setPath(item->dataPath().toString());
104- emit(showTrackModel(&m_proxyModel));
105-
106-}
107-
108-void BrowseFeature::onRightClick(const QPoint& globalPos) {
109-}
110-
111-void BrowseFeature::onRightClickChild(const QPoint& globalPos, QModelIndex index) {
112 }
113
114=== modified file 'mixxx/src/library/browse/browsefeature.h'
115--- mixxx/src/library/browse/browsefeature.h 2011-03-20 03:47:06 +0000
116+++ mixxx/src/library/browse/browsefeature.h 2011-03-28 22:22:23 +0000
117@@ -42,7 +42,9 @@
118 void onRightClick(const QPoint& globalPos);
119 void onRightClickChild(const QPoint& globalPos, QModelIndex index);
120
121- signals:
122+ void onLazyChildExpandation(const QModelIndex& index);
123+
124+ signals:
125 void setRootIndex(const QModelIndex&);
126
127 private:
128
129=== modified file 'mixxx/src/library/browse/browsetablemodel.cpp'
130--- mixxx/src/library/browse/browsetablemodel.cpp 2011-03-20 03:47:06 +0000
131+++ mixxx/src/library/browse/browsetablemodel.cpp 2011-03-28 22:22:23 +0000
132@@ -38,16 +38,18 @@
133 addSearchColumn(COLUMN_KEY);
134 addSearchColumn(COLUMN_COMMENT);
135
136- m_backgroundThread.start(QThread::LowestPriority);
137+
138
139 setHorizontalHeaderLabels(header_data);
140 //register the QList<T> as a metatype since we use QueuedConnection below
141- qRegisterMetaType< QList<QStandardItem*> >("QList<QStandardItem*>");
142-
143- QObject::connect(&m_backgroundThread, SIGNAL(clearModel()),
144- this, SLOT(slotClear()), Qt::BlockingQueuedConnection);
145- QObject::connect(&m_backgroundThread, SIGNAL(rowDataAppended(const QList<QStandardItem*>&)),
146- this, SLOT(slotInsert(const QList<QStandardItem*>&)), Qt::BlockingQueuedConnection);
147+ qRegisterMetaType< QList< QList<QStandardItem*> > >("QList< QList<QStandardItem*> >");
148+ qRegisterMetaType<BrowseTableModel*>("BrowseTableModel*");
149+
150+ QObject::connect(BrowseThread::getInstance(), SIGNAL(clearModel(BrowseTableModel*)),
151+ this, SLOT(slotClear(BrowseTableModel*)), Qt::QueuedConnection);
152+
153+ QObject::connect(BrowseThread::getInstance(), SIGNAL(rowsAppended(const QList< QList<QStandardItem*> >&, BrowseTableModel*)),
154+ this, SLOT(slotInsert(const QList< QList<QStandardItem*> >&, BrowseTableModel*)), Qt::QueuedConnection);
155
156 }
157
158@@ -64,7 +66,8 @@
159 }
160 void BrowseTableModel::setPath(QString absPath)
161 {
162- m_backgroundThread.setPath(absPath);
163+ BrowseThread::getInstance()->executePopulation(absPath, this);
164+
165 }
166
167 TrackPointer BrowseTableModel::getTrack(const QModelIndex& index) const
168@@ -73,16 +76,6 @@
169 return TrackPointer(tio, &QObject::deleteLater);
170 }
171
172-int BrowseTableModel::getTrackId(const QModelIndex& index) const {
173- // We can't implement this as it stands.
174- return -1;
175-}
176-
177-int BrowseTableModel::getTrackRow(int trackId) const {
178- // We can't implement this as it stands.
179- return -1;
180-}
181-
182 QString BrowseTableModel::getTrackLocation(const QModelIndex& index) const
183 {
184 int row = index.row();
185@@ -91,7 +84,15 @@
186 return data(index2).toString();
187
188 }
189+int BrowseTableModel::getTrackId(const QModelIndex& index) const {
190+ // We can't implement this as it stands.
191+ return -1;
192+}
193
194+int BrowseTableModel::getTrackRow(int trackId) const {
195+ // We can't implement this as it stands.
196+ return -1;
197+}
198 void BrowseTableModel::search(const QString& searchText)
199 {
200
201@@ -159,14 +160,21 @@
202 return mimeData;
203 }
204
205-void BrowseTableModel::slotClear()
206+void BrowseTableModel::slotClear(BrowseTableModel* caller_object)
207 {
208- removeRows(0, rowCount());
209+ if(caller_object == this)
210+ removeRows(0, rowCount());
211 }
212
213-void BrowseTableModel::slotInsert(const QList<QStandardItem*> &column_data)
214-{
215- appendRow(column_data);
216- //Does not work for some reason
217- //setItem(row, column, item);
218+
219+void BrowseTableModel::slotInsert(const QList< QList<QStandardItem*> >& rows, BrowseTableModel* caller_object){
220+ //There exists more than one BrowseTableModel in Mixxx
221+ //We only want to receive items here, this object has 'ordered' by the BrowserThread (singleton)
222+ if(caller_object == this){
223+ //qDebug() << "BrowseTableModel::slotInsert";
224+ for(int i=0; i < rows.size(); ++i){
225+ appendRow(rows.at(i));
226+ }
227+ }
228+
229 }
230
231=== modified file 'mixxx/src/library/browse/browsetablemodel.h'
232--- mixxx/src/library/browse/browsetablemodel.h 2011-03-20 03:47:06 +0000
233+++ mixxx/src/library/browse/browsetablemodel.h 2011-03-28 22:22:23 +0000
234@@ -23,41 +23,47 @@
235 const int COLUMN_TYPE = 11;
236 const int COLUMN_BITRATE = 12;
237 const int COLUMN_LOCATION = 13;
238-
239-class BrowseTableModel : public QStandardItemModel, public TrackModel {
240+/*
241+ * The BrowseTable models displays tracks
242+ * of given directory on the HDD.
243+ * The class is realized as sigleton because
244+ * the 'recording' feature uses the BrowseTableModel, too.
245+ * This simply does not create two BrowseThreads
246+ */
247+class BrowseTableModel : public QStandardItemModel, public TrackModel
248+{
249+
250 Q_OBJECT
251- public:
252- BrowseTableModel(QObject* parent);
253- ~BrowseTableModel();
254-
255- void setPath(QString absPath);
256- //reimplemented from TrackModel class
257- virtual TrackPointer getTrack(const QModelIndex& index) const;
258- virtual QString getTrackLocation(const QModelIndex& index) const;
259- virtual int getTrackId(const QModelIndex& index) const;
260- virtual int getTrackRow(int trackId) const;
261-
262- virtual void search(const QString& searchText);
263- virtual void removeTrack(const QModelIndex& index);
264- virtual void removeTracks(const QModelIndexList& indices);
265- virtual bool addTrack(const QModelIndex& index, QString location);
266- virtual QMimeData* mimeData(const QModelIndexList &indexes) const;
267- virtual const QString currentSearch();
268- virtual bool isColumnInternal(int);
269- virtual void moveTrack(const QModelIndex&, const QModelIndex&);
270- virtual QItemDelegate* delegateForColumn(const int);
271- virtual bool isColumnHiddenByDefault(int column);
272- virtual const QList<int>& searchColumns() const;
273-
274- public slots:
275- void slotClear();
276- void slotInsert(const QList<QStandardItem*> &item);
277-
278- private:
279- void addSearchColumn(int index);
280-
281- BrowseThread m_backgroundThread;
282- QList<int> m_searchColumns;
283+ public:
284+ BrowseTableModel(QObject* parent);
285+ virtual ~BrowseTableModel();
286+ void setPath(QString absPath);
287+ //reimplemented from TrackModel class
288+ virtual TrackPointer getTrack(const QModelIndex& index) const;
289+ virtual QString getTrackLocation(const QModelIndex& index) const;
290+ virtual int getTrackId(const QModelIndex& index) const;
291+ virtual int getTrackRow(int trackId) const;
292+
293+ virtual void search(const QString& searchText);
294+ virtual void removeTrack(const QModelIndex& index);
295+ virtual void removeTracks(const QModelIndexList& indices);
296+ virtual bool addTrack(const QModelIndex& index, QString location);
297+ virtual QMimeData* mimeData(const QModelIndexList &indexes) const;
298+ virtual const QString currentSearch();
299+ virtual bool isColumnInternal(int);
300+ virtual void moveTrack(const QModelIndex&, const QModelIndex&);
301+ virtual QItemDelegate* delegateForColumn(const int);
302+ virtual bool isColumnHiddenByDefault(int column);
303+ virtual const QList<int>& searchColumns() const;
304+ private:
305+
306+
307+ void addSearchColumn(int index);
308+ QList<int> m_searchColumns;
309+
310+ public slots:
311+ void slotClear(BrowseTableModel*);
312+ void slotInsert(const QList< QList<QStandardItem*> >&, BrowseTableModel*);
313 };
314
315 #endif
316
317=== modified file 'mixxx/src/library/browse/browsethread.cpp'
318--- mixxx/src/library/browse/browsethread.cpp 2011-03-20 03:47:06 +0000
319+++ mixxx/src/library/browse/browsethread.cpp 2011-03-28 22:22:23 +0000
320@@ -1,3 +1,7 @@
321+/*
322+ * browsethread.cpp (C) 2011 Tobias Rafreider
323+ */
324+
325 #include <QStringList>
326 #include <QDirIterator>
327 #include <QtCore>
328@@ -7,10 +11,25 @@
329 #include "soundsourceproxy.h"
330 #include "mixxxutils.cpp"
331
332-BrowseThread::BrowseThread(QObject *parent)
333- : QThread(parent),
334- m_bStopThread(false) {
335- //QObject::moveToThread(this);
336+BrowseThread* BrowseThread::m_instance = 0;
337+static QMutex s_Mutex;
338+
339+/*
340+ * This class is a singleton and represents a thread
341+ * that is used to read ID3 metadata
342+ * from a particular folder.
343+ *
344+ * The BroseTableModel uses this class.
345+ * Note: Don't call getInstance() from places
346+ * other than the GUI thread. BrowseThreads emit
347+ * signals to BrowseModel objects. It does not
348+ * make sense to use this class in non-GUI threads
349+ */
350+BrowseThread::BrowseThread(QObject *parent): QThread(parent)
351+{
352+ m_bStopThread = false;
353+ //start Thread
354+ start(QThread::LowestPriority);
355 }
356
357 BrowseThread::~BrowseThread() {
358@@ -23,9 +42,31 @@
359 wait();
360 qDebug() << "Browser background thread terminated!";
361 }
362-
363-void BrowseThread::setPath(QString& path) {
364+BrowseThread* BrowseThread::getInstance(){
365+ if (!m_instance)
366+ {
367+ s_Mutex.lock();;
368+
369+ if (!m_instance)
370+ m_instance = new BrowseThread();
371+
372+ s_Mutex.unlock();
373+ }
374+ return m_instance;
375+}
376+void BrowseThread::destroyInstance()
377+{
378+ s_Mutex.lock();
379+ if(m_instance){
380+ delete m_instance;
381+ m_instance = 0;
382+ }
383+ s_Mutex.unlock();
384+}
385+
386+void BrowseThread::executePopulation(QString& path, BrowseTableModel* client) {
387 m_path = path;
388+ m_model_observer = client;
389 m_locationUpdated.wakeAll();
390 }
391
392@@ -56,8 +97,9 @@
393 * This is a blocking operation
394 * see signal/slot connection in BrowseTableModel
395 */
396- emit(clearModel());
397+ emit(clearModel(m_model_observer));
398
399+ QList< QList<QStandardItem*> > rows;
400
401 int row = 0;
402 //Iterate over the files
403@@ -74,59 +116,63 @@
404
405 QString filepath = fileIt.next();
406 TrackInfoObject tio(filepath);
407- QList<QStandardItem*> column_data;
408+ QList<QStandardItem*> row_data;
409
410 QStandardItem* item = new QStandardItem(tio.getFilename());
411- column_data.insert(COLUMN_FILENAME, item);
412+ row_data.insert(COLUMN_FILENAME, item);
413
414 item = new QStandardItem(tio.getArtist());
415- column_data.insert(COLUMN_ARTIST, item);
416+ row_data.insert(COLUMN_ARTIST, item);
417
418 item = new QStandardItem(tio.getTitle());
419- column_data.insert(COLUMN_TITLE, item);
420+ row_data.insert(COLUMN_TITLE, item);
421
422 item = new QStandardItem(tio.getAlbum());
423- column_data.insert(COLUMN_ALBUM, item);
424+ row_data.insert(COLUMN_ALBUM, item);
425
426 item = new QStandardItem(tio.getTrackNumber());
427- column_data.insert(COLUMN_TRACK_NUMBER, item);
428+ row_data.insert(COLUMN_TRACK_NUMBER, item);
429
430 item = new QStandardItem(tio.getYear());
431- column_data.insert(COLUMN_YEAR, item);
432+ row_data.insert(COLUMN_YEAR, item);
433
434 item = new QStandardItem(tio.getGenre());
435- column_data.insert(COLUMN_GENRE, item);
436+ row_data.insert(COLUMN_GENRE, item);
437
438 item = new QStandardItem(tio.getComment());
439- column_data.insert(COLUMN_COMMENT, item);
440+ row_data.insert(COLUMN_COMMENT, item);
441
442 QString duration = MixxxUtils::secondsToMinutes(qVariantValue<int>(tio.getDuration()));
443 item = new QStandardItem(duration);
444- column_data.insert(COLUMN_DURATION, item);
445+ row_data.insert(COLUMN_DURATION, item);
446
447 item = new QStandardItem(tio.getBpmStr());
448- column_data.insert(COLUMN_BPM, item);
449+ row_data.insert(COLUMN_BPM, item);
450
451 item = new QStandardItem(tio.getKey());
452- column_data.insert(COLUMN_KEY, item);
453+ row_data.insert(COLUMN_KEY, item);
454
455 item = new QStandardItem(tio.getType());
456- column_data.insert(COLUMN_TYPE, item);
457+ row_data.insert(COLUMN_TYPE, item);
458
459 item = new QStandardItem(tio.getBitrateStr());
460- column_data.insert(COLUMN_BITRATE, item);
461+ row_data.insert(COLUMN_BITRATE, item);
462
463 item = new QStandardItem(filepath);
464- column_data.insert(COLUMN_LOCATION, item);
465-
466- // this is a blocking operation
467- // see signal/slot connection in BrowseTableModel
468- emit(rowDataAppended(column_data));
469-
470- //QCoreApplication::processEvents();
471+ row_data.insert(COLUMN_LOCATION, item);
472+
473+ rows.append(row_data);
474 ++row;
475+ //If 10 tracks have been analyzed, send it to GUI
476+ //Will limit GUI freezing
477+ if(row % 10 == 0){
478+ //this is a blocking operation
479+ emit(rowsAppended(rows, m_model_observer));
480
481- //Sleep for 50ms which prevents us from GUI freezes
482- msleep(50);
483+ rows.clear();
484+ }
485+ //Sleep additionally for 10ms which prevents us from GUI freezes
486+ msleep(20);
487 }
488+ emit(rowsAppended(rows, m_model_observer));
489 }
490
491=== modified file 'mixxx/src/library/browse/browsethread.h'
492--- mixxx/src/library/browse/browsethread.h 2011-03-20 03:47:06 +0000
493+++ mixxx/src/library/browse/browsethread.h 2011-03-28 22:22:23 +0000
494@@ -1,3 +1,7 @@
495+/*
496+ * browsethread.h (C) 2011 Tobias Rafreider
497+ */
498+
499 #ifndef BROWSETHREAD_H
500 #define BROWSETHREAD_H
501
502@@ -7,20 +11,35 @@
503 #include <QStandardItem>
504 #include <QList>
505
506+#include "library/browse/browsetablemodel.h"
507+
508+/*
509+ * This class is a singleton and represents a thread
510+ * that is used to read ID3 metadata
511+ * from a particular folder.
512+ *
513+ * The BroseTableModel uses this class.
514+ * Note: Don't call getInstance() from places
515+ * other than the GUI thread.
516+ */
517+class BrowseTableModel;
518+
519 class BrowseThread : public QThread {
520 Q_OBJECT
521 public:
522- BrowseThread(QObject *parent = 0);
523- ~BrowseThread();
524-
525- void setPath(QString& path);
526+ void executePopulation(QString& path, BrowseTableModel* client);
527 void run();
528+ static BrowseThread* getInstance();
529+ static void destroyInstance();
530
531 signals:
532- void rowDataAppended(const QList<QStandardItem*>&);
533- void clearModel();
534+ void rowsAppended(const QList< QList<QStandardItem*> >&, BrowseTableModel*);
535+ void clearModel(BrowseTableModel*);
536
537 private:
538+ BrowseThread(QObject *parent = 0);
539+ virtual ~BrowseThread();
540+
541 void populateModel();
542
543 QMutex m_mutex;
544@@ -28,6 +47,9 @@
545 QList<int> m_searchColumns;
546 QString m_path;
547 bool m_bStopThread;
548+
549+ static BrowseThread* m_instance;
550+ BrowseTableModel* m_model_observer;
551 };
552
553 #endif // BROWSETHREAD_H
554
555=== modified file 'mixxx/src/library/cratefeature.cpp'
556--- mixxx/src/library/cratefeature.cpp 2011-03-27 01:36:30 +0000
557+++ mixxx/src/library/cratefeature.cpp 2011-03-28 22:22:23 +0000
558@@ -20,9 +20,10 @@
559 #include "soundsourceproxy.h"
560
561 CrateFeature::CrateFeature(QObject* parent,
562- TrackCollection* pTrackCollection)
563+ TrackCollection* pTrackCollection, ConfigObject<ConfigValue>* pConfig)
564 : m_pTrackCollection(pTrackCollection),
565 m_crateListTableModel(this, pTrackCollection->getDatabase()),
566+ m_pConfig(pConfig),
567 m_crateTableModel(this, pTrackCollection) {
568 m_pCreateCrateAction = new QAction(tr("New Crate"),this);
569 connect(m_pCreateCrateAction, SIGNAL(triggered()),
570@@ -40,9 +41,12 @@
571 connect(m_pLockCrateAction, SIGNAL(triggered()),
572 this, SLOT(slotToggleCrateLock()));
573
574- m_pImportPlaylistAction = new QAction(tr("Import Playlist"),this);
575+ m_pImportPlaylistAction = new QAction(tr("Import Crate"),this);
576 connect(m_pImportPlaylistAction, SIGNAL(triggered()),
577 this, SLOT(slotImportPlaylist()));
578+ m_pExportPlaylistAction = new QAction(tr("Export Crate"), this);
579+ connect(m_pExportPlaylistAction, SIGNAL(triggered()),
580+ this, SLOT(slotExportPlaylist()));
581
582 m_crateListTableModel.setTable("crates");
583 m_crateListTableModel.setSort(m_crateListTableModel.fieldIndex("name"),
584@@ -175,6 +179,7 @@
585 menu.addAction(m_pLockCrateAction);
586 menu.addSeparator();
587 menu.addAction(m_pImportPlaylistAction);
588+ menu.addAction(m_pExportPlaylistAction);
589 menu.exec(globalPos);
590 }
591
592@@ -409,4 +414,42 @@
593 if(playlist_parser)
594 delete playlist_parser;
595 }
596-
597+void CrateFeature::onLazyChildExpandation(const QModelIndex &index){
598+ //Nothing to do because the childmodel is not of lazy nature.
599+}
600+void CrateFeature::slotExportPlaylist(){
601+ qDebug() << "Export playlist" << m_lastRightClickedIndex.data();
602+ QString file_location = QFileDialog::getSaveFileName(NULL,
603+ tr("Export Playlist"),
604+ QDesktopServices::storageLocation(QDesktopServices::MusicLocation),
605+ tr("M3U Playlist (*.m3u);;PLS Playlist (*.pls)"));
606+ //Exit method if user cancelled the open dialog.
607+ if(file_location.isNull() || file_location.isEmpty()) return;
608+ //create and populate a list of files of the playlist
609+ QList<QString> playlist_items;
610+ int rows = m_crateTableModel.rowCount();
611+ for(int i = 0; i < rows; ++i){
612+ QModelIndex index = m_crateTableModel.index(i,0);
613+ playlist_items << m_crateTableModel.getTrackLocation(index);
614+ }
615+ //check config if relative paths are desired
616+ bool useRelativePath = (bool)m_pConfig->getValueString(ConfigKey("[Library]","UseRelativePathOnExport")).toInt();
617+
618+ if(file_location.endsWith(".m3u", Qt::CaseInsensitive))
619+ {
620+ ParserM3u::writeM3UFile(file_location, playlist_items, useRelativePath);
621+ }
622+ else if(file_location.endsWith(".pls", Qt::CaseInsensitive))
623+ {
624+ ParserPls::writePLSFile(file_location,playlist_items, useRelativePath);
625+ }
626+ else
627+ {
628+ //default export to M3U if file extension is missing
629+
630+ qDebug() << "Crate export: No file extension specified. Appending .m3u "
631+ << "and exporting to M3U.";
632+ file_location.append(".m3u");
633+ ParserM3u::writeM3UFile(file_location, playlist_items, useRelativePath);
634+ }
635+}
636
637=== modified file 'mixxx/src/library/cratefeature.h'
638--- mixxx/src/library/cratefeature.h 2011-02-21 07:01:11 +0000
639+++ mixxx/src/library/cratefeature.h 2011-03-28 22:22:23 +0000
640@@ -9,13 +9,14 @@
641 #include "library/cratetablemodel.h"
642
643 #include "treeitemmodel.h"
644+#include "configobject.h"
645
646 class TrackCollection;
647
648 class CrateFeature : public LibraryFeature {
649 Q_OBJECT
650 public:
651- CrateFeature(QObject* parent, TrackCollection* pTrackCollection);
652+ CrateFeature(QObject* parent, TrackCollection* pTrackCollection, ConfigObject<ConfigValue>* pConfig);
653 virtual ~CrateFeature();
654
655 QVariant title();
656@@ -39,12 +40,14 @@
657 void activateChild(const QModelIndex& index);
658 void onRightClick(const QPoint& globalPos);
659 void onRightClickChild(const QPoint& globalPos, QModelIndex index);
660+ void onLazyChildExpandation(const QModelIndex& index);
661
662 void slotCreateCrate();
663 void slotDeleteCrate();
664 void slotRenameCrate();
665 void slotToggleCrateLock();
666 void slotImportPlaylist();
667+ void slotExportPlaylist();
668
669 private:
670 void constructChildModel();
671@@ -56,10 +59,12 @@
672 QAction *m_pRenameCrateAction;
673 QAction *m_pLockCrateAction;
674 QAction *m_pImportPlaylistAction;
675+ QAction *m_pExportPlaylistAction;
676 QSqlTableModel m_crateListTableModel;
677 CrateTableModel m_crateTableModel;
678 QModelIndex m_lastRightClickedIndex;
679 TreeItemModel m_childModel;
680+ ConfigObject<ConfigValue>* m_pConfig;
681 };
682
683 #endif /* CRATEFEATURE_H */
684
685=== modified file 'mixxx/src/library/itunes/itunesfeature.cpp'
686--- mixxx/src/library/itunes/itunesfeature.cpp 2011-03-24 06:34:54 +0000
687+++ mixxx/src/library/itunes/itunesfeature.cpp 2011-03-28 22:22:23 +0000
688@@ -587,4 +587,6 @@
689 emit(featureLoadingFinished(this));
690 activate();
691 }
692-
693+void ITunesFeature::onLazyChildExpandation(const QModelIndex &index){
694+ //Nothing to do because the childmodel is not of lazy nature.
695+}
696
697=== modified file 'mixxx/src/library/itunes/itunesfeature.h'
698--- mixxx/src/library/itunes/itunesfeature.h 2011-02-21 23:53:01 +0000
699+++ mixxx/src/library/itunes/itunesfeature.h 2011-03-28 22:22:23 +0000
700@@ -43,6 +43,7 @@
701 void activateChild(const QModelIndex& index);
702 void onRightClick(const QPoint& globalPos);
703 void onRightClickChild(const QPoint& globalPos, QModelIndex index);
704+ void onLazyChildExpandation(const QModelIndex& index);
705 void onTrackCollectionLoaded();
706
707 private:
708
709=== modified file 'mixxx/src/library/library.cpp'
710--- mixxx/src/library/library.cpp 2011-03-27 20:05:54 +0000
711+++ mixxx/src/library/library.cpp 2011-03-28 22:22:23 +0000
712@@ -52,9 +52,9 @@
713 }
714
715 addFeature(new AutoDJFeature(this, pConfig, m_pTrackCollection));
716- m_pPlaylistFeature = new PlaylistFeature(this, m_pTrackCollection);
717+ m_pPlaylistFeature = new PlaylistFeature(this, m_pTrackCollection, pConfig);
718 addFeature(m_pPlaylistFeature);
719- m_pCrateFeature = new CrateFeature(this, m_pTrackCollection);
720+ m_pCrateFeature = new CrateFeature(this, m_pTrackCollection, pConfig);
721 addFeature(m_pCrateFeature);
722 addFeature(new BrowseFeature(this, pConfig, m_pTrackCollection));
723 addFeature(new PrepareFeature(this, pConfig, m_pTrackCollection));
724@@ -122,7 +122,7 @@
725 m_pSidebarModel, SLOT(clicked(const QModelIndex&)));
726 // Lazy model: Let triange symbol increment the model
727 connect(pSidebarWidget, SIGNAL(expanded(const QModelIndex&)),
728- m_pSidebarModel, SLOT(clicked(const QModelIndex&)));
729+ m_pSidebarModel, SLOT(doubleClicked(const QModelIndex&)));
730
731 connect(pSidebarWidget, SIGNAL(rightClicked(const QPoint&, const QModelIndex&)),
732 m_pSidebarModel, SLOT(rightClicked(const QPoint&, const QModelIndex&)));
733
734=== modified file 'mixxx/src/library/libraryfeature.h'
735--- mixxx/src/library/libraryfeature.h 2011-02-20 11:34:47 +0000
736+++ mixxx/src/library/libraryfeature.h 2011-03-28 22:22:23 +0000
737@@ -42,10 +42,19 @@
738 virtual TreeItemModel* getChildModel() = 0;
739
740 public slots:
741+ /** called when you single click on the root item **/
742 virtual void activate() = 0;
743+ /** called when you single click on a child item, e.g., a concrete playlist or crate **/
744 virtual void activateChild(const QModelIndex& index) = 0;
745+ /** called when you right click on the root item **/
746 virtual void onRightClick(const QPoint& globalPos) = 0;
747+ /** called when you right click on a child item, e.g., a concrete playlist or crate **/
748 virtual void onRightClickChild(const QPoint& globalPos, QModelIndex index) = 0;
749+ /*
750+ * Only implement this, if using incremental or lazy childmodels, see BrowseFeature.
751+ * This method is executed whenever you **double** click child items
752+ */
753+ virtual void onLazyChildExpandation(const QModelIndex& index) = 0;
754 signals:
755 void featureUpdated();
756 void showTrackModel(QAbstractItemModel* model);
757@@ -53,7 +62,9 @@
758 void loadTrack(TrackPointer pTrack);
759 void loadTrackToPlayer(TrackPointer pTrack, QString group);
760 void restoreSearch(const QString&);
761+ /** emit this signal before you parse a large music collection, e.g., iTunes, Traktor. **/
762 void featureIsLoading(LibraryFeature*);
763+ /** emit this signal if the foreign music collection has been imported/parsed. **/
764 void featureLoadingFinished(LibraryFeature*s);
765
766
767
768=== modified file 'mixxx/src/library/mixxxlibraryfeature.cpp'
769--- mixxx/src/library/mixxxlibraryfeature.cpp 2011-02-19 13:29:31 +0000
770+++ mixxx/src/library/mixxxlibraryfeature.cpp 2011-03-28 22:22:23 +0000
771@@ -85,4 +85,6 @@
772 bool MixxxLibraryFeature::dragMoveAcceptChild(const QModelIndex& index,
773 QUrl url) {
774 return false;
775+}void MixxxLibraryFeature::onLazyChildExpandation(const QModelIndex &index){
776+//Nothing to do because the childmodel is not of lazy nature.
777 }
778
779=== modified file 'mixxx/src/library/mixxxlibraryfeature.h'
780--- mixxx/src/library/mixxxlibraryfeature.h 2010-12-23 18:55:54 +0000
781+++ mixxx/src/library/mixxxlibraryfeature.h 2011-03-28 22:22:23 +0000
782@@ -35,6 +35,7 @@
783 void activateChild(const QModelIndex& index);
784 void onRightClick(const QPoint& globalPos);
785 void onRightClickChild(const QPoint& globalPos, QModelIndex index);
786+ void onLazyChildExpandation(const QModelIndex& index);
787 void refreshLibraryModels();
788 private:
789 LibraryTableModel* m_pLibraryTableModel;
790
791=== modified file 'mixxx/src/library/parser.cpp'
792--- mixxx/src/library/parser.cpp 2010-12-30 20:44:34 +0000
793+++ mixxx/src/library/parser.cpp 2011-03-28 22:22:23 +0000
794@@ -5,12 +5,14 @@
795 //
796 //
797 // Author: Ingo Kossyk <kossyki@cs.tu-berlin.de>, (C) 2004
798+// Author: Tobias Rafreider trafreider@mixxx.org, (C) 2011
799 //
800 // Copyright: See COPYING file that comes with this distribution
801 //
802 //
803
804 #include <QtDebug>
805+#include <QStringList>
806 #include "parser.h"
807
808 /**
809@@ -74,3 +76,4 @@
810 file.close();
811 return false;
812 }
813+
814
815=== modified file 'mixxx/src/library/parser.h'
816--- mixxx/src/library/parser.h 2010-12-30 20:44:34 +0000
817+++ mixxx/src/library/parser.h 2011-03-28 22:22:23 +0000
818@@ -5,6 +5,7 @@
819 //
820 //
821 // Author: Ingo Kossyk <kossyki@cs.tu-berlin.de>, (C) 2004
822+// Author: Tobias Rafreider trafreider@mixxx.org, (C) 2011
823 //
824 // Copyright: See COPYING file that comes with this distribution
825 //
826
827=== modified file 'mixxx/src/library/parserm3u.cpp'
828--- mixxx/src/library/parserm3u.cpp 2011-01-01 14:33:45 +0000
829+++ mixxx/src/library/parserm3u.cpp 2011-03-28 22:22:23 +0000
830@@ -5,12 +5,15 @@
831 //
832 //
833 // Author: Ingo Kossyk <kossyki@cs.tu-berlin.de>, (C) 2004
834+// Author: Tobias Rafreider trafreider@mixxx.org, (C) 2011
835 //
836 // Copyright: See COPYING file that comes with this distribution
837 //
838 //
839 #include <QTextStream>
840 #include <QDebug>
841+#include <QDir>
842+#include <QMessageBox>
843 #include "parserm3u.h"
844 #include <QUrl>
845
846@@ -120,3 +123,34 @@
847 return 0;
848
849 }
850+bool ParserM3u::writeM3UFile(const QString &file_str, QList<QString> &items, bool useRelativePath)
851+{
852+ QFile file(file_str);
853+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text)){
854+ QMessageBox::warning(NULL,tr("Playlist Export Failed"),
855+ tr("Could not create file")+" "+file_str);
856+ return false;
857+ }
858+ //Base folder of file
859+ QString base = file_str.section('/', 0, -2);
860+ QDir base_dir(base);
861+
862+ qDebug() << "Basepath: " << base;
863+ QTextStream out(&file);
864+ out << "#EXTM3U\n";
865+ for(int i =0; i < items.size(); ++i){
866+ out << "#EXTINF\n";
867+
868+ //Write relative path if possible
869+ if(useRelativePath){
870+ //QDir::relativePath() will return the absolutePath if it cannot compute the
871+ //relative Path
872+ out << base_dir.relativeFilePath(items.at(i)) << "\n";
873+ }
874+ else
875+ out << items.at(i) << "\n";
876+ }
877+
878+ return true;
879+
880+}
881
882=== modified file 'mixxx/src/library/parserm3u.h'
883--- mixxx/src/library/parserm3u.h 2010-12-30 20:44:34 +0000
884+++ mixxx/src/library/parserm3u.h 2011-03-28 22:22:23 +0000
885@@ -5,6 +5,7 @@
886 //
887 //
888 // Author: Ingo Kossyk <kossyki@cs.tu-berlin.de>, (C) 2004
889+// Author: Tobias Rafreider trafreider@mixxx.org, (C) 2011
890 //
891 // Copyright: See COPYING file that comes with this distribution
892 //
893@@ -24,6 +25,8 @@
894 ~ParserM3u();
895 /**Overwriting function parse in class Parser**/
896 QList<QString> parse(QString);
897+ //Playlist Export
898+ static bool writeM3UFile(const QString &file, QList<QString> &items, bool useRelativePath);
899
900 private:
901 /**Reads a line from the file and returns filepath if a valid file**/
902
903=== modified file 'mixxx/src/library/parserpls.cpp'
904--- mixxx/src/library/parserpls.cpp 2011-01-01 14:33:45 +0000
905+++ mixxx/src/library/parserpls.cpp 2011-03-28 22:22:23 +0000
906@@ -5,6 +5,7 @@
907 //
908 //
909 // Author: Ingo Kossyk <kossyki@cs.tu-berlin.de>, (C) 2004
910+// Author: Tobias Rafreider trafreider@mixxx.org, (C) 2011
911 //
912 // Copyright: See COPYING file that comes with this distribution
913 //
914@@ -13,6 +14,8 @@
915 #include "parserpls.h"
916 #include <QDebug>
917 #include <QTextStream>
918+#include <QMessageBox>
919+#include <QDir>
920 #include <QFile>
921 #include <QUrl>
922
923@@ -141,3 +144,31 @@
924 return 0;
925
926 }
927+bool ParserPls::writePLSFile(QString &file_str, QList<QString> &items, bool useRelativePath)
928+{
929+ QFile file(file_str);
930+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text)){
931+ QMessageBox::warning(NULL,tr("Playlist Export Failed"),
932+ tr("Could not create file")+" "+file_str);
933+ return false;
934+ }
935+ //Base folder of file
936+ QString base = file_str.section('/', 0, -2);
937+ QDir base_dir(base);
938+
939+ QTextStream out(&file);
940+ out << "[playlist]\n";
941+ out << "NumberOfEntries=" << items.size() << "\n";
942+ for(int i =0; i < items.size(); ++i){
943+ //Write relative path if possible
944+ if(useRelativePath){
945+ //QDir::relativePath() will return the absolutePath if it cannot compute the
946+ //relative Path
947+ out << "File" << i << "=" << base_dir.relativeFilePath(items.at(i)) << "\n";
948+ }
949+ else
950+ out << "File" << i << "=" << items.at(i) << "\n";
951+ }
952+
953+ return true;
954+}
955
956=== modified file 'mixxx/src/library/parserpls.h'
957--- mixxx/src/library/parserpls.h 2010-12-30 20:44:34 +0000
958+++ mixxx/src/library/parserpls.h 2011-03-28 22:22:23 +0000
959@@ -26,6 +26,8 @@
960 ~ParserPls();
961 /**Can be called to parse a pls file**/
962 QList<QString> parse(QString);
963+ //Playlist Export
964+ static bool writePLSFile(QString &file, QList<QString> &items, bool useRelativePath);
965
966 private:
967 /**Returns the Number of entries in the pls file**/
968
969=== modified file 'mixxx/src/library/playlistfeature.cpp'
970--- mixxx/src/library/playlistfeature.cpp 2011-03-27 01:36:30 +0000
971+++ mixxx/src/library/playlistfeature.cpp 2011-03-28 22:22:23 +0000
972@@ -19,11 +19,12 @@
973 #include "treeitem.h"
974 #include "soundsourceproxy.h"
975
976-PlaylistFeature::PlaylistFeature(QObject* parent, TrackCollection* pTrackCollection)
977+PlaylistFeature::PlaylistFeature(QObject* parent, TrackCollection* pTrackCollection, ConfigObject<ConfigValue>* pConfig)
978 : LibraryFeature(parent),
979 // m_pTrackCollection(pTrackCollection),
980 m_playlistDao(pTrackCollection->getPlaylistDAO()),
981 m_trackDao(pTrackCollection->getTrackDAO()),
982+ m_pConfig(pConfig),
983 m_playlistTableModel(this, pTrackCollection->getDatabase()) {
984 m_pPlaylistTableModel = new PlaylistTableModel(this, pTrackCollection);
985
986@@ -50,6 +51,9 @@
987 m_pImportPlaylistAction = new QAction(tr("Import Playlist"),this);
988 connect(m_pImportPlaylistAction, SIGNAL(triggered()),
989 this, SLOT(slotImportPlaylist()));
990+ m_pExportPlaylistAction = new QAction(tr("Export Playlist"), this);
991+ connect(m_pExportPlaylistAction, SIGNAL(triggered()),
992+ this, SLOT(slotExportPlaylist()));
993
994 // Setup the sidebar playlist model
995 m_playlistTableModel.setTable("Playlists");
996@@ -140,6 +144,7 @@
997 menu.addAction(m_pLockPlaylistAction);
998 menu.addSeparator();
999 menu.addAction(m_pImportPlaylistAction);
1000+ menu.addAction(m_pExportPlaylistAction);
1001 menu.exec(globalPos);
1002 }
1003
1004@@ -426,7 +431,46 @@
1005 //delete the parser object
1006 if(playlist_parser) delete playlist_parser;
1007 }
1008-
1009+void PlaylistFeature::onLazyChildExpandation(const QModelIndex &index){
1010+ //Nothing to do because the childmodel is not of lazy nature.
1011+}
1012+void PlaylistFeature::slotExportPlaylist(){
1013+ qDebug() << "Export playlist" << m_lastRightClickedIndex.data();
1014+ QString file_location = QFileDialog::getSaveFileName(NULL,
1015+ tr("Export Playlist"),
1016+ QDesktopServices::storageLocation(QDesktopServices::MusicLocation),
1017+ tr("M3U Playlist (*.m3u);;PLS Playlist (*.pls)"));
1018+ //Exit method if user cancelled the open dialog.
1019+ if(file_location.isNull() || file_location.isEmpty()) return;
1020+ //create and populate a list of files of the playlist
1021+ QList<QString> playlist_items;
1022+ int rows = m_pPlaylistTableModel->rowCount();
1023+ for(int i = 0; i < rows; ++i){
1024+ QModelIndex index = m_pPlaylistTableModel->index(i,0);
1025+ playlist_items << m_pPlaylistTableModel->getTrackLocation(index);
1026+ }
1027+ //check config if relative paths are desired
1028+ bool useRelativePath = (bool)m_pConfig->getValueString(ConfigKey("[Library]","UseRelativePathOnExport")).toInt();
1029+
1030+ if(file_location.endsWith(".m3u", Qt::CaseInsensitive))
1031+ {
1032+ ParserM3u::writeM3UFile(file_location, playlist_items, useRelativePath);
1033+ }
1034+ else if(file_location.endsWith(".pls", Qt::CaseInsensitive))
1035+ {
1036+ ParserPls::writePLSFile(file_location,playlist_items, useRelativePath);
1037+ }
1038+ else
1039+ {
1040+ //default export to M3U if file extension is missing
1041+
1042+ qDebug() << "Playlist export: No file extension specified. Appending .m3u "
1043+ << "and exporting to M3U.";
1044+ file_location.append(".m3u");
1045+ ParserM3u::writeM3UFile(file_location, playlist_items, useRelativePath);
1046+ }
1047+
1048+}
1049 void PlaylistFeature::slotAddToAutoDJ() {
1050 //qDebug() << "slotAddToAutoDJ() row:" << m_lastRightClickedIndex.data();
1051
1052
1053=== modified file 'mixxx/src/library/playlistfeature.h'
1054--- mixxx/src/library/playlistfeature.h 2011-03-26 07:47:01 +0000
1055+++ mixxx/src/library/playlistfeature.h 2011-03-28 22:22:23 +0000
1056@@ -12,6 +12,7 @@
1057 #include "library/dao/playlistdao.h"
1058 #include "library/dao/trackdao.h"
1059 #include "treeitemmodel.h"
1060+#include "configobject.h"
1061
1062
1063 class PlaylistTableModel;
1064@@ -20,7 +21,7 @@
1065 class PlaylistFeature : public LibraryFeature {
1066 Q_OBJECT
1067 public:
1068- PlaylistFeature(QObject* parent, TrackCollection* pTrackCollection);
1069+ PlaylistFeature(QObject* parent, TrackCollection* pTrackCollection, ConfigObject<ConfigValue>* pConfig);
1070 virtual ~PlaylistFeature();
1071
1072 QVariant title();
1073@@ -44,6 +45,7 @@
1074 void activateChild(const QModelIndex& index);
1075 void onRightClick(const QPoint& globalPos);
1076 void onRightClickChild(const QPoint& globalPos, QModelIndex index);
1077+ void onLazyChildExpandation(const QModelIndex& index);
1078
1079 void slotCreatePlaylist();
1080 void slotDeletePlaylist();
1081@@ -51,6 +53,7 @@
1082 void slotRenamePlaylist();
1083 void slotTogglePlaylistLock();
1084 void slotImportPlaylist();
1085+ void slotExportPlaylist();
1086
1087
1088 private:
1089@@ -66,9 +69,11 @@
1090 QAction *m_pRenamePlaylistAction;
1091 QAction *m_pLockPlaylistAction;
1092 QAction *m_pImportPlaylistAction;
1093+ QAction *m_pExportPlaylistAction;
1094 QSqlTableModel m_playlistTableModel;
1095 QModelIndex m_lastRightClickedIndex;
1096 TreeItemModel m_childModel;
1097+ ConfigObject<ConfigValue>* m_pConfig;
1098 };
1099
1100 #endif /* PLAYLISTFEATURE_H */
1101
1102=== modified file 'mixxx/src/library/preparefeature.cpp'
1103--- mixxx/src/library/preparefeature.cpp 2011-03-26 07:45:47 +0000
1104+++ mixxx/src/library/preparefeature.cpp 2011-03-28 22:22:23 +0000
1105@@ -104,6 +104,10 @@
1106 return false;
1107 }
1108
1109+void PrepareFeature::onLazyChildExpandation(const QModelIndex &index){
1110+ //Nothing to do because the childmodel is not of lazy nature.
1111+}
1112+
1113 void PrepareFeature::analyzeTracks(QList<int> trackIds) {
1114 if (m_pAnalyserQueue == NULL) {
1115 //Save the old BPM detection prefs setting (on or off)
1116@@ -155,4 +159,5 @@
1117 //Restore old BPM detection setting for preferences...
1118 m_pConfig->set(ConfigKey("[BPM]","BPMDetectionEnabled"), ConfigValue(m_iOldBpmEnabled));
1119 }
1120+
1121 }
1122
1123=== modified file 'mixxx/src/library/preparefeature.h'
1124--- mixxx/src/library/preparefeature.h 2011-03-26 07:45:47 +0000
1125+++ mixxx/src/library/preparefeature.h 2011-03-28 22:22:23 +0000
1126@@ -46,6 +46,7 @@
1127 void activateChild(const QModelIndex& index);
1128 void onRightClick(const QPoint& globalPos);
1129 void onRightClickChild(const QPoint& globalPos, QModelIndex index);
1130+ void onLazyChildExpandation(const QModelIndex& index);
1131
1132 private slots:
1133 void analyzeTracks(QList<int> trackIds);
1134
1135=== modified file 'mixxx/src/library/promotracksfeature.cpp'
1136--- mixxx/src/library/promotracksfeature.cpp 2011-01-09 17:53:56 +0000
1137+++ mixxx/src/library/promotracksfeature.cpp 2011-03-28 22:22:23 +0000
1138@@ -205,3 +205,6 @@
1139 QUrl url) {
1140 return false;
1141 }
1142+void PromoTracksFeature::onLazyChildExpandation(const QModelIndex &index){
1143+ //Nothing to do because the childmodel is not of lazy nature.
1144+}
1145
1146=== modified file 'mixxx/src/library/promotracksfeature.h'
1147--- mixxx/src/library/promotracksfeature.h 2010-12-23 18:55:54 +0000
1148+++ mixxx/src/library/promotracksfeature.h 2011-03-28 22:22:23 +0000
1149@@ -66,6 +66,7 @@
1150 void activateChild(const QModelIndex& index);
1151 void onRightClick(const QPoint& globalPos);
1152 void onRightClickChild(const QPoint& globalPos, QModelIndex index);
1153+ void onLazyChildExpandation(const QModelIndex& index);
1154
1155 private:
1156 ConfigObject<ConfigValue>* m_pConfig;
1157
1158=== modified file 'mixxx/src/library/rhythmbox/rhythmboxfeature.cpp'
1159--- mixxx/src/library/rhythmbox/rhythmboxfeature.cpp 2011-02-21 23:53:01 +0000
1160+++ mixxx/src/library/rhythmbox/rhythmboxfeature.cpp 2011-03-28 22:22:23 +0000
1161@@ -419,5 +419,7 @@
1162 emit(featureLoadingFinished(this));
1163 activate();
1164 }
1165-
1166+void RhythmboxFeature::onLazyChildExpandation(const QModelIndex &index){
1167+ //Nothing to do because the childmodel is not of lazy nature.
1168+}
1169
1170
1171=== modified file 'mixxx/src/library/rhythmbox/rhythmboxfeature.h'
1172--- mixxx/src/library/rhythmbox/rhythmboxfeature.h 2011-02-21 23:53:01 +0000
1173+++ mixxx/src/library/rhythmbox/rhythmboxfeature.h 2011-03-28 22:22:23 +0000
1174@@ -66,6 +66,7 @@
1175 void activateChild(const QModelIndex& index);
1176 void onRightClick(const QPoint& globalPos);
1177 void onRightClickChild(const QPoint& globalPos, QModelIndex index);
1178+ void onLazyChildExpandation(const QModelIndex& index);
1179 void onTrackCollectionLoaded();
1180
1181 };
1182
1183=== modified file 'mixxx/src/library/sidebarmodel.cpp'
1184--- mixxx/src/library/sidebarmodel.cpp 2011-03-27 20:05:54 +0000
1185+++ mixxx/src/library/sidebarmodel.cpp 2011-03-28 22:22:23 +0000
1186@@ -153,16 +153,18 @@
1187 if (parent.isValid()) {
1188 if (parent.internalPointer() == this) {
1189 return QAbstractItemModel::hasChildren(parent);
1190- } else {
1191+ }
1192+ else
1193+ {
1194 TreeItem* tree_item = (TreeItem*)parent.internalPointer();
1195 if (tree_item) {
1196 LibraryFeature* feature = tree_item->getFeature();
1197 return feature->getChildModel()->hasChildren(parent);
1198 }
1199 }
1200- } else {
1201- return QAbstractItemModel::hasChildren(parent);
1202 }
1203+
1204+ return QAbstractItemModel::hasChildren(parent);
1205 }
1206
1207 QVariant SidebarModel::data(const QModelIndex& index, int role) const {
1208@@ -211,6 +213,19 @@
1209 }
1210 }
1211 }
1212+void SidebarModel::doubleClicked(const QModelIndex& index) {
1213+ if (index.isValid()) {
1214+ if (index.internalPointer() == this) {
1215+ return;
1216+ } else {
1217+ TreeItem* tree_item = (TreeItem*)index.internalPointer();
1218+ if (tree_item) {
1219+ LibraryFeature* feature = tree_item->getFeature();
1220+ feature->onLazyChildExpandation(index);
1221+ }
1222+ }
1223+ }
1224+}
1225
1226 void SidebarModel::rightClicked(const QPoint& globalPos, const QModelIndex& index) {
1227 //qDebug() << "SidebarModel::rightClicked() index=" << index;
1228
1229=== modified file 'mixxx/src/library/sidebarmodel.h'
1230--- mixxx/src/library/sidebarmodel.h 2011-03-27 20:05:54 +0000
1231+++ mixxx/src/library/sidebarmodel.h 2011-03-28 22:22:23 +0000
1232@@ -36,6 +36,7 @@
1233
1234 public slots:
1235 void clicked(const QModelIndex& index);
1236+ void doubleClicked(const QModelIndex& index);
1237 void rightClicked(const QPoint& globalPos, const QModelIndex& index);
1238 void refreshData();
1239 void selectFeature(LibraryFeature* pFeature);
1240
1241=== modified file 'mixxx/src/library/traktor/traktorfeature.cpp'
1242--- mixxx/src/library/traktor/traktorfeature.cpp 2011-03-24 06:34:54 +0000
1243+++ mixxx/src/library/traktor/traktorfeature.cpp 2011-03-28 22:22:23 +0000
1244@@ -615,3 +615,6 @@
1245 emit(featureLoadingFinished(this));
1246 activate();
1247 }
1248+void TraktorFeature::onLazyChildExpandation(const QModelIndex &index){
1249+ //Nothing to do because the childmodel is not of lazy nature.
1250+}
1251
1252=== modified file 'mixxx/src/library/traktor/traktorfeature.h'
1253--- mixxx/src/library/traktor/traktorfeature.h 2011-02-21 23:53:01 +0000
1254+++ mixxx/src/library/traktor/traktorfeature.h 2011-03-28 22:22:23 +0000
1255@@ -42,6 +42,7 @@
1256 void activateChild(const QModelIndex& index);
1257 void onRightClick(const QPoint& globalPos);
1258 void onRightClickChild(const QPoint& globalPos, QModelIndex index);
1259+ void onLazyChildExpandation(const QModelIndex& index);
1260 void refreshLibraryModels();
1261 void onTrackCollectionLoaded();
1262 private: