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

Proposed by Daniel Schürmann
Status: Needs review
Proposed branch: lp:~daschuer/mixxx/features_ipod
Merge into: lp:~mixxxdevelopers/mixxx/trunk
Diff against target: 1887 lines (+1709/-10)
13 files modified
mixxx/build/features.py (+9/-2)
mixxx/res/mixxx.qrc (+1/-0)
mixxx/src/dlgprefplaylist.cpp (+12/-0)
mixxx/src/dlgprefplaylistdlg.ui (+10/-0)
mixxx/src/library/ipod/gpoditdb.cpp (+64/-0)
mixxx/src/library/ipod/gpoditdb.h (+49/-0)
mixxx/src/library/ipod/ipodfeature.cpp (+405/-0)
mixxx/src/library/ipod/ipodfeature.h (+90/-0)
mixxx/src/library/ipod/ipodplaylistmodel.cpp (+901/-0)
mixxx/src/library/ipod/ipodplaylistmodel.h (+146/-0)
mixxx/src/library/library.cpp (+9/-0)
mixxx/src/widget/wlibrarysidebar.cpp (+11/-6)
mixxx/src/widget/wtracktableview.cpp (+2/-2)
To merge this branch: bzr merge lp:~daschuer/mixxx/features_ipod
Reviewer Review Type Date Requested Status
Mixxx Development Team Pending
Review via email: mp+80743@code.launchpad.net

Description of the change

Features:
* New library item "iPod"
* Sorting and Filtering
* new icon for Rhythmbox
* iPod auto detection
* disable iPod feature in preferences

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

Hey Daniel,

Can you just say a word about your ipod auto detection?
What library have you used for that? Do we have dependencies to GTK now?
Have you tested on Windows, too?

Revision history for this message
Daniel Schürmann (daschuer) wrote :

I have tested it only with Ubuntu Lucid, Natty and Oneiric.

---

On Ubuntu the Ipod mounted automatically in /media/IPOD[x]
The ipod feature is looking there for default. "Auto detection" is just pushing it a bit :-).
It is based on code, which I have already found in Mixxx Sources and which also has untested parts for Mac and Windows.

---

The ipod feature drew dependencies to the Glib, not to whole GTK.
The libgpod is based on Glib, which implements the object model for GTK.
On Gnome based systems, Mixxx has GTK dependencies anyway.

lp:~daschuer/mixxx/features_ipod updated
2925. By Daniel Schürmann

merged with lp:mixxx

2926. By Daniel Schürmann

merged with lp:mixxx

2927. By Daniel Schürmann

merged with lp:mixxx

2928. By Daniel Schürmann

merged with lp:mixxx

Unmerged revisions

2928. By Daniel Schürmann

merged with lp:mixxx

2927. By Daniel Schürmann

merged with lp:mixxx

2926. By Daniel Schürmann

merged with lp:mixxx

2925. By Daniel Schürmann

merged with lp:mixxx

2924. By Daniel Schürmann

merged with lp:mixxx

2923. By Daniel Schürmann

brach now working

2922. By Daniel Schürmann

added ipod files

2921. By Daniel Schürmann

merged from daschuers_trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'mixxx/build/features.py'
2--- mixxx/build/features.py 2012-09-26 05:07:07 +0000
3+++ mixxx/build/features.py 2012-11-18 17:09:22 +0000
4@@ -276,7 +276,7 @@
5
6 class IPod(Feature):
7 def description(self):
8- return "NOT-WORKING iPod Support"
9+ return "Guest's iPod Support"
10
11 def enabled(self, build):
12 build.flags['ipod'] = util.get_flags(build.env, 'ipod', 0)
13@@ -291,7 +291,12 @@
14 if not self.enabled(build):
15 return
16
17+ gpod_found = conf.CheckLib(['libgpod','gpod'])
18 build.env.Append(CPPDEFINES = '__IPOD__')
19+
20+ if not gpod_found:
21+ raise Exception('Could not find libgpod or its development headers. Please install it or compile Mixxx without iPod support using the ipod=0 flag.')
22+
23 if build.platform_is_windows:
24 build.env.Append(LIBS = 'gpod');
25 # You must check v-this-v directory out from http://publicsvn.songbirdnest.com/vendor-binaries/trunk/windows-i686-msvc8/libgpod/
26@@ -309,7 +314,9 @@
27 build.env.ParseConfig('pkg-config glib-2.0 --silence-errors --cflags --libs')
28
29 def sources(self, build):
30- return ['wipodtracksmodel.cpp']
31+ return ['library/ipod/ipodfeature.cpp',
32+ 'library/ipod/ipodplaylistmodel.cpp',
33+ 'library/ipod/gpoditdb.cpp']
34
35 class MSVCDebug(Feature):
36 # FIXME: this flag is also detected in mixxx.py at line 100 because it's needed sooner than this is processed
37
38=== added file 'mixxx/res/images/library/ic_library_ipod.png'
39Binary files mixxx/res/images/library/ic_library_ipod.png 1970-01-01 00:00:00 +0000 and mixxx/res/images/library/ic_library_ipod.png 2012-11-18 17:09:22 +0000 differ
40=== removed file 'mixxx/res/images/library/ic_library_rhythmbox.png'
41Binary files mixxx/res/images/library/ic_library_rhythmbox.png 2012-07-06 08:24:39 +0000 and mixxx/res/images/library/ic_library_rhythmbox.png 1970-01-01 00:00:00 +0000 differ
42=== modified file 'mixxx/res/mixxx.qrc'
43--- mixxx/res/mixxx.qrc 2012-09-06 05:11:40 +0000
44+++ mixxx/res/mixxx.qrc 2012-11-18 17:09:22 +0000
45@@ -34,6 +34,7 @@
46 <file>images/library/ic_library_recordings.png</file>
47 <file>images/library/ic_library_rhythmbox.png</file>
48 <file>images/library/ic_library_traktor.png</file>
49+ <file>images/library/ic_library_ipod.png</file>
50 <file>shaders/computemaxsignal.frag</file>
51 <file>shaders/filteredsignal.frag</file>
52 <file>shaders/passthrough.vert</file>
53
54=== modified file 'mixxx/src/dlgprefplaylist.cpp'
55--- mixxx/src/dlgprefplaylist.cpp 2012-07-09 21:38:03 +0000
56+++ mixxx/src/dlgprefplaylist.cpp 2012-11-18 17:09:22 +0000
57@@ -51,6 +51,10 @@
58 //connect(pushButtonM4A, SIGNAL(clicked()), this, SLOT(slotM4ACheck()));
59 connect(pushButtonExtraPlugins, SIGNAL(clicked()), this, SLOT(slotExtraPlugins()));
60
61+#ifndef __IPOD__
62+ checkBox_show_ipod->hide();
63+#endif // __IPOD__
64+
65 if (!PromoTracksFeature::isSupported(config))
66 {
67 groupBoxBundledSongs->hide();
68@@ -146,6 +150,9 @@
69 checkBox_use_relative_path->setChecked((bool)config->getValueString(ConfigKey("[Library]","UseRelativePathOnExport")).toInt());
70 checkBox_show_rhythmbox->setChecked((bool)config->getValueString(ConfigKey("[Library]","ShowRhythmboxLibrary"),"1").toInt());
71 checkBox_show_itunes->setChecked((bool)config->getValueString(ConfigKey("[Library]","ShowITunesLibrary"),"1").toInt());
72+ #ifdef __IPOD__
73+ checkBox_show_ipod->setChecked((bool)config->getValueString(ConfigKey("[Library]","ShowIpod"),"1").toInt());
74+ #endif // __IPOD__
75 checkBox_show_traktor->setChecked((bool)config->getValueString(ConfigKey("[Library]","ShowTraktorLibrary"),"1").toInt());
76 }
77
78@@ -180,6 +187,11 @@
79 config->set(ConfigKey("[Library]","ShowITunesLibrary"),
80 ConfigValue((int)checkBox_show_itunes->isChecked()));
81
82+#ifdef __IPOD__
83+ config->set(ConfigKey("[Library]","ShowIpod"),
84+ ConfigValue((int)checkBox_show_ipod->isChecked()));
85+#endif // __IPOD__
86+
87 config->set(ConfigKey("[Library]","ShowTraktorLibrary"),
88 ConfigValue((int)checkBox_show_traktor->isChecked()));
89
90
91=== modified file 'mixxx/src/dlgprefplaylistdlg.ui'
92--- mixxx/src/dlgprefplaylistdlg.ui 2012-06-09 22:03:56 +0000
93+++ mixxx/src/dlgprefplaylistdlg.ui 2012-11-18 17:09:22 +0000
94@@ -181,6 +181,16 @@
95 </property>
96 </widget>
97 </item>
98+ <item row="4" column="0">
99+ <widget class="QCheckBox" name="checkBox_show_ipod">
100+ <property name="text">
101+ <string>Show iPod</string>
102+ </property>
103+ <property name="checked">
104+ <bool>true</bool>
105+ </property>
106+ </widget>
107+ </item>
108 </layout>
109 </widget>
110 </item>
111
112=== added directory 'mixxx/src/library/ipod'
113=== added file 'mixxx/src/library/ipod/gpoditdb.cpp'
114--- mixxx/src/library/ipod/gpoditdb.cpp 1970-01-01 00:00:00 +0000
115+++ mixxx/src/library/ipod/gpoditdb.cpp 2012-11-18 17:09:22 +0000
116@@ -0,0 +1,64 @@
117+/*
118+ * GPodItdb.cpp
119+ *
120+ * Created on: 01.02.2012
121+ * Author: Daniel Schürmann
122+ */
123+
124+#include <QLibrary>
125+#include <QtDebug>
126+#include <QDir>
127+#include <QMessageBox>
128+
129+#include "gpoditdb.h"
130+
131+
132+GPodItdb::GPodItdb() :
133+ m_itdb(NULL),
134+ m_libGPodLoaded(true) {
135+
136+ // Load shared library
137+ QLibrary libGPod("libgpod");
138+
139+ fp_itdb_free = (itdb_free__)libGPod.resolve("itdb_free");
140+ if(!fp_itdb_free) m_libGPodLoaded = false;
141+
142+ fp_itdb_playlist_mpl = (itdb_playlist_mpl__)libGPod.resolve("itdb_playlist_mpl");
143+ if(!fp_itdb_playlist_mpl) m_libGPodLoaded = false;
144+
145+ fp_itdb_parse = (itdb_parse__)libGPod.resolve("itdb_parse");
146+ if(!fp_itdb_parse) m_libGPodLoaded = false;
147+
148+ qDebug() << "GPodItdb: try to resolve libgpod functions: " << (m_libGPodLoaded?"success":"failed");
149+}
150+
151+GPodItdb::~GPodItdb() {
152+ if (m_itdb) {
153+ fp_itdb_free(m_itdb);
154+ }
155+}
156+
157+void GPodItdb::parse(const QString& mount, GError **error) {
158+
159+ if (m_itdb) {
160+ fp_itdb_free(m_itdb);
161+ m_itdb = NULL;
162+ }
163+ m_itdb = fp_itdb_parse(QDir::toNativeSeparators(mount).toLocal8Bit(), error);
164+}
165+
166+Itdb_Playlist* GPodItdb::getFirstPlaylist() {
167+ m_playlistNode = g_list_first(m_itdb->playlists);
168+ if (m_playlistNode) {
169+ return (Itdb_Playlist*)m_playlistNode->data;
170+ }
171+ return NULL;
172+}
173+
174+Itdb_Playlist* GPodItdb::getNextPlaylist() {
175+ m_playlistNode = g_list_next(m_playlistNode);
176+ if (m_playlistNode) {
177+ return (Itdb_Playlist*)m_playlistNode->data;
178+ }
179+ return NULL;
180+}
181
182=== added file 'mixxx/src/library/ipod/gpoditdb.h'
183--- mixxx/src/library/ipod/gpoditdb.h 1970-01-01 00:00:00 +0000
184+++ mixxx/src/library/ipod/gpoditdb.h 2012-11-18 17:09:22 +0000
185@@ -0,0 +1,49 @@
186+/*
187+ * gpoditdb.h
188+ *
189+ * Created on: 01.02.2012
190+ * Author: daniel
191+ */
192+
193+#include <QObject>
194+
195+extern "C"
196+{
197+#include <gpod/itdb.h>
198+}
199+
200+#ifndef GPODITDB_H_
201+#define GPODITDB_H_
202+
203+class GPodItdb {
204+public:
205+ GPodItdb();
206+ virtual ~GPodItdb();
207+ bool isSupported() { return m_libGPodLoaded; };
208+ void parse(const QString& mount, GError **error);
209+ Itdb_Playlist* getFirstPlaylist();
210+ Itdb_Playlist* getNextPlaylist();
211+ Itdb_Playlist* getMasterPlaylist() { return fp_itdb_playlist_mpl(m_itdb); };
212+ Itdb_Playlist* getCurrentPlaylist() { return m_pCurrentPlaylist; };
213+ void setCurrentPlaylist(Itdb_Playlist* pl) { m_pCurrentPlaylist = pl; };
214+
215+ typedef int (*itdb_free__)(Itdb_iTunesDB *itdb);
216+ itdb_free__ fp_itdb_free;
217+
218+ typedef Itdb_Playlist* (*itdb_playlist_mpl__)(Itdb_iTunesDB *itdb);
219+ itdb_playlist_mpl__ fp_itdb_playlist_mpl;
220+
221+ typedef Itdb_iTunesDB* (*itdb_parse__)(const gchar *mp, GError **error);
222+ itdb_parse__ fp_itdb_parse;
223+
224+ typedef gboolean (*itdb_playlist_is_mpl__)(Itdb_Playlist *pl);
225+ itdb_playlist_is_mpl__ fp_itdb_playlist_is_mpl;
226+
227+private:
228+ Itdb_iTunesDB* m_itdb;
229+ bool m_libGPodLoaded;
230+ GList* m_playlistNode;
231+ Itdb_Playlist *m_pCurrentPlaylist;
232+};
233+
234+#endif /* GPODITDB_H_ */
235
236=== added file 'mixxx/src/library/ipod/ipodfeature.cpp'
237--- mixxx/src/library/ipod/ipodfeature.cpp 1970-01-01 00:00:00 +0000
238+++ mixxx/src/library/ipod/ipodfeature.cpp 2012-11-18 17:09:22 +0000
239@@ -0,0 +1,405 @@
240+#include <QMessageBox>
241+#include <QtDebug>
242+#include <QXmlStreamReader>
243+#include <QDesktopServices>
244+#include <QFileDialog>
245+#include <QMenu>
246+#include <QAction>
247+
248+#include "library/ipod/ipodfeature.h"
249+#include "library/ipod/gpoditdb.h"
250+
251+#include "library/itunes/itunestrackmodel.h"
252+#include "library/ipod/ipodplaylistmodel.h"
253+#include "library/dao/settingsdao.h"
254+
255+extern "C" {
256+#include <glib-object.h> // g_type_init
257+}
258+
259+const QString IPodFeature::IPOD_MOUNT_KEY = "mixxx.IPodFeature.mount";
260+
261+
262+IPodFeature::IPodFeature(QObject* parent, TrackCollection* pTrackCollection)
263+ : LibraryFeature(parent),
264+ m_pTrackCollection(pTrackCollection),
265+ m_isActivated(false),
266+ m_cancelImport(false),
267+ m_itdb(NULL)
268+{
269+ m_title = tr("iPod");
270+ m_pIPodPlaylistModel = new IPodPlaylistModel(this, m_pTrackCollection);
271+
272+ m_gPodItdb = new GPodItdb();
273+
274+ connect(&m_future_watcher, SIGNAL(finished()), this, SLOT(onTrackCollectionLoaded()));
275+
276+ m_pAddToAutoDJAction = new QAction(tr("Add to Auto DJ bottom"),this);
277+ connect(m_pAddToAutoDJAction, SIGNAL(triggered()),
278+ this, SLOT(slotAddToAutoDJ()));
279+
280+ m_pAddToAutoDJTopAction = new QAction(tr("Add to Auto DJ top 2"),this);
281+ connect(m_pAddToAutoDJTopAction, SIGNAL(triggered()),
282+ this, SLOT(slotAddToAutoDJTop()));
283+
284+ m_pImportAsMixxxPlaylistAction = new QAction(tr("Import as Mixxx Playlist"), this);
285+ connect(m_pImportAsMixxxPlaylistAction, SIGNAL(triggered()),
286+ this, SLOT(slotImportAsMixxxPlaylist()));
287+}
288+
289+IPodFeature::~IPodFeature() {
290+ qDebug() << "~IPodFeature()";
291+ // stop import thread, if still running
292+ m_cancelImport = true;
293+ if (m_future.isRunning()) {
294+ qDebug() << "m_future still running";
295+ m_future.waitForFinished();
296+ qDebug() << "m_future finished";
297+ }
298+ if (m_itdb) {
299+ itdb_free(m_itdb);
300+ }
301+ delete m_pIPodPlaylistModel;
302+ delete m_pAddToAutoDJAction;
303+ delete m_pAddToAutoDJTopAction;
304+ delete m_pImportAsMixxxPlaylistAction;
305+ delete m_gPodItdb;
306+}
307+
308+// static
309+bool IPodFeature::isSupported() {
310+ // The iPod might be mount at any time at any location
311+ return true; //m_gPodItdb->isSupported();
312+}
313+
314+
315+QVariant IPodFeature::title() {
316+ return m_title;
317+}
318+
319+QIcon IPodFeature::getIcon() {
320+ return QIcon(":/images/library/ic_library_ipod.png");
321+}
322+
323+void IPodFeature::activate() {
324+ activate(false);
325+}
326+
327+void IPodFeature::activate(bool forceReload) {
328+ //qDebug("IPodFeature::activate()");
329+
330+ if (!m_isActivated || forceReload) {
331+
332+ SettingsDAO settings(m_pTrackCollection->getDatabase());
333+
334+ QString dbSetting(settings.getValue(IPOD_MOUNT_KEY));
335+ // if a path exists in the database, use it
336+ if (!dbSetting.isEmpty() && QFile::exists(dbSetting)) {
337+ m_dbfile = dbSetting;
338+ } else {
339+ // No Path in settings
340+ m_dbfile = "";
341+ }
342+
343+ m_dbfile = detectMountPoint(m_dbfile);
344+
345+ if (!QFile::exists(m_dbfile)) {
346+ m_dbfile = QFileDialog::getExistingDirectory(
347+ NULL,
348+ tr("Select your iPod mount"));
349+ if (m_dbfile.isEmpty()) {
350+ emit(showTrackModel(m_pIPodPlaylistModel));
351+ return;
352+ }
353+ }
354+
355+ m_isActivated = true;
356+
357+ QThreadPool::globalInstance()->setMaxThreadCount(4); //Tobias decided to use 4
358+ // Let a worker thread do the iPod parsing
359+ m_future = QtConcurrent::run(this, &IPodFeature::importLibrary);
360+ m_future_watcher.setFuture(m_future);
361+ m_title = tr("(loading) iPod"); // (loading) at start in respect to small displays
362+ //calls a slot in the sidebar model such that '(loading)iPod' is displayed.
363+ emit (featureIsLoading(this));
364+ }
365+ else{
366+ m_pIPodPlaylistModel->setPlaylist(itdb_playlist_mpl(m_itdb)); // Gets the master playlist
367+ }
368+ emit(showTrackModel(m_pIPodPlaylistModel));
369+}
370+
371+QString IPodFeature::detectMountPoint( QString iPodMountPoint) {
372+ QFileInfoList mountpoints;
373+ #ifdef __WINDOWS__
374+ // Windows iPod Detection
375+ mountpoints = QDir::drives();
376+ #elif __LINUX__
377+ // Linux
378+ mountpoints = QDir("/media").entryInfoList();
379+ mountpoints += QDir("/mnt").entryInfoList();
380+ #elif __APPLE__
381+ // Mac OSX
382+ mountpoints = QDir("/Volumes").entryInfoList();
383+ #endif
384+
385+ QListIterator<QFileInfo> i(mountpoints);
386+ QFileInfo mp;
387+ while (i.hasNext()) {
388+ mp = (QFileInfo) i.next();
389+ qDebug() << "mp:" << mp.filePath();
390+ if( mp.filePath() != iPodMountPoint ) {
391+ if (QDir( QString(mp.filePath() + "/iPod_Control") ).exists() ) {
392+ qDebug() << "iPod found at" << mp.filePath();
393+
394+ // Multiple iPods
395+ if (!iPodMountPoint.isEmpty()) {
396+ int ret = QMessageBox::warning(NULL, tr("Multiple iPods Detected"),
397+ tr("Mixxx has detected another iPod. \n\n")+
398+ tr("Choose Yes to use the newly found iPod @ ")+ mp.filePath()+
399+ tr(" or to continue to search for other iPods. \n")+
400+ tr("Choose No to use the existing iPod @ ")+ iPodMountPoint+
401+ tr( " and end detection. \n"),
402+ QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
403+ if (ret == QMessageBox::No) {
404+ break;
405+ }
406+ }
407+ iPodMountPoint = mp.filePath();
408+ }
409+ }
410+ }
411+ return iPodMountPoint;
412+}
413+
414+void IPodFeature::activateChild(const QModelIndex& index) {
415+ TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
416+ qDebug() << "IPodFeature::activateChild " << item->data() << " " << item->dataPath();
417+ QString playlist = item->dataPath().toString();
418+ Itdb_Playlist* pPlaylist = (Itdb_Playlist*)playlist.toUInt();
419+
420+ if (pPlaylist) {
421+ qDebug() << "Activating " << QString::fromUtf8(pPlaylist->name);
422+ }
423+ m_pIPodPlaylistModel->setPlaylist(pPlaylist);
424+ emit(showTrackModel(m_pIPodPlaylistModel));
425+}
426+
427+TreeItemModel* IPodFeature::getChildModel() {
428+ return &m_childModel;
429+}
430+
431+void IPodFeature::onRightClick(const QPoint& globalPos) {
432+ Q_UNUSED(globalPos);
433+}
434+
435+void IPodFeature::onRightClickChild(const QPoint& globalPos, QModelIndex index) {
436+ //Save the model index so we can get it in the action slots...
437+ m_lastRightClickedIndex = index;
438+
439+ //Create the right-click menu
440+ QMenu menu(NULL);
441+ menu.addAction(m_pAddToAutoDJAction);
442+ menu.addAction(m_pAddToAutoDJTopAction);
443+ menu.addSeparator();
444+ menu.addAction(m_pImportAsMixxxPlaylistAction);
445+ menu.exec(globalPos);
446+}
447+
448+bool IPodFeature::dropAccept(QList<QUrl> urls) {
449+ Q_UNUSED(urls);
450+ return false;
451+}
452+
453+bool IPodFeature::dropAcceptChild(const QModelIndex& index, QList<QUrl> urls) {
454+ Q_UNUSED(index);
455+ Q_UNUSED(urls);
456+ return false;
457+}
458+
459+bool IPodFeature::dragMoveAccept(QUrl url) {
460+ Q_UNUSED(url);
461+ return false;
462+}
463+
464+bool IPodFeature::dragMoveAcceptChild(const QModelIndex& index, QUrl url) {
465+ Q_UNUSED(index);
466+ Q_UNUSED(url);
467+ return false;
468+}
469+
470+/*
471+ * This method is executed in a separate thread
472+ * via QtConcurrent::run
473+ */
474+TreeItem* IPodFeature::importLibrary() {
475+ // Give thread a low priority
476+ QThread* thisThread = QThread::currentThread();
477+ thisThread->setPriority(QThread::LowestPriority);
478+
479+ qDebug() << "IPodFeature::importLibrary() ";
480+
481+ TreeItem* playlist_root = new TreeItem();
482+
483+ GError* err = 0;
484+ qDebug() << "Calling the libgpod db parser for:" << m_dbfile;
485+ if (m_itdb) {
486+ itdb_free(m_itdb);
487+ m_itdb = NULL;
488+ }
489+ m_itdb = itdb_parse(QDir::toNativeSeparators(m_dbfile).toLocal8Bit(), &err);
490+
491+ if (err) {
492+ qDebug() << "There was an error, attempting to free db: "
493+ << err->message;
494+ QMessageBox::warning(NULL, tr("Error Loading iPod database"),
495+ err->message);
496+ g_error_free(err);
497+ if (m_itdb) {
498+ itdb_free(m_itdb);
499+ m_itdb = 0;
500+ }
501+ } else if (m_itdb) {
502+ GList* playlist_node;
503+
504+ for (playlist_node = g_list_first(m_itdb->playlists);
505+ playlist_node != NULL;
506+ playlist_node = g_list_next(playlist_node))
507+ {
508+ Itdb_Playlist* pPlaylist;
509+ pPlaylist = (Itdb_Playlist*)playlist_node->data;
510+ if (!itdb_playlist_is_mpl(pPlaylist)) {
511+ QString playlistname = QString::fromUtf8(pPlaylist->name);
512+ qDebug() << playlistname;
513+ // append the playlist to the child model
514+ TreeItem *item = new TreeItem(playlistname, QString::number((uint)pPlaylist), this, playlist_root);
515+ playlist_root->appendChild(item);
516+ }
517+ }
518+ }
519+ return playlist_root;
520+}
521+
522+void IPodFeature::onTrackCollectionLoaded(){
523+ TreeItem* root = m_future.result();
524+ if(root){
525+ m_childModel.setRootItem(root);
526+ if (m_isActivated) {
527+ activate();
528+ }
529+ qDebug() << "iPod library loaded: success";
530+ SettingsDAO settings(m_pTrackCollection->getDatabase());
531+ settings.setValue(IPOD_MOUNT_KEY, m_dbfile);
532+ }
533+ else{
534+ QMessageBox::warning(
535+ NULL,
536+ tr("Error Loading iPod database"),
537+ tr("There was an error loading your iPod database. Some of "
538+ "your iPod tracks or playlists may not have loaded."));
539+ }
540+ //calls a slot in the sidebarmodel such that 'isLoading' is removed from the feature title.
541+ m_title = tr("iPod");
542+ emit(featureLoadingFinished(this));
543+ activate();
544+}
545+
546+void IPodFeature::onLazyChildExpandation(const QModelIndex &index){
547+ Q_UNUSED(index);
548+ //Nothing to do because the childmodel is not of lazy nature.
549+}
550+
551+void IPodFeature::slotAddToAutoDJ() {
552+ //qDebug() << "slotAddToAutoDJ() row:" << m_lastRightClickedIndex.data();
553+ addToAutoDJ(false); // Top = True
554+}
555+
556+
557+void IPodFeature::slotAddToAutoDJTop() {
558+ //qDebug() << "slotAddToAutoDJTop() row:" << m_lastRightClickedIndex.data();
559+ addToAutoDJ(true); // bTop = True
560+}
561+
562+void IPodFeature::addToAutoDJ(bool bTop) {
563+ // qDebug() << "slotAddToAutoDJ() row:" << m_lastRightClickedIndex.data();
564+
565+ if (m_lastRightClickedIndex.isValid()) {
566+ TreeItem *item = static_cast<TreeItem*>(m_lastRightClickedIndex.internalPointer());
567+ qDebug() << "IPodFeature::addToAutoDJ " << item->data() << " " << item->dataPath();
568+ QString playlist = item->dataPath().toString();
569+ Itdb_Playlist* pPlaylist = (Itdb_Playlist*)playlist.toUInt();
570+ if (pPlaylist) {
571+ IPodPlaylistModel* pPlaylistModelToAdd = new IPodPlaylistModel(this, m_pTrackCollection);
572+ pPlaylistModelToAdd->setPlaylist(pPlaylist);
573+ PlaylistDAO &playlistDao = m_pTrackCollection->getPlaylistDAO();
574+ int autoDJId = playlistDao.getPlaylistIdFromName(AUTODJ_TABLE);
575+
576+ int rows = pPlaylistModelToAdd->rowCount();
577+ for(int i = 0; i < rows; ++i){
578+ QModelIndex index = pPlaylistModelToAdd->index(i,0);
579+ if (index.isValid()) {
580+ TrackPointer track = pPlaylistModelToAdd->getTrack(index);
581+ if (bTop) {
582+ // Start at position 2 because position 1 was already loaded to the deck
583+ playlistDao.insertTrackIntoPlaylist(track->getId(), autoDJId, i+2);
584+ } else {
585+ playlistDao.appendTrackToPlaylist(track->getId(), autoDJId);
586+ }
587+ }
588+ }
589+ delete pPlaylistModelToAdd;
590+ }
591+ }
592+}
593+
594+void IPodFeature::slotImportAsMixxxPlaylist() {
595+ // qDebug() << "slotAddToAutoDJ() row:" << m_lastRightClickedIndex.data();
596+
597+ if (m_lastRightClickedIndex.isValid()) {
598+ TreeItem *item = static_cast<TreeItem*>(m_lastRightClickedIndex.internalPointer());
599+ qDebug() << "IPodFeature::slotImportAsMixxxPlaylist " << item->data() << " " << item->dataPath();
600+ QString playlist = item->dataPath().toString();
601+ Itdb_Playlist* pPlaylist = (Itdb_Playlist*)playlist.toUInt();
602+ playlist = QString::fromUtf8(pPlaylist->name);
603+ if (pPlaylist) {
604+ IPodPlaylistModel* pPlaylistModelToAdd = new IPodPlaylistModel(this, m_pTrackCollection);
605+ pPlaylistModelToAdd->setPlaylist(pPlaylist);
606+ PlaylistDAO &playlistDao = m_pTrackCollection->getPlaylistDAO();
607+
608+ int playlistId = playlistDao.getPlaylistIdFromName(playlist);
609+ int i = 1;
610+
611+ if (playlistId != -1) {
612+ // Calculate a unique name
613+ playlist += "(%1)";
614+ while (playlistId != -1) {
615+ i++;
616+ playlistId = playlistDao.getPlaylistIdFromName(playlist.arg(i));
617+ }
618+ playlist = playlist.arg(i);
619+ }
620+ playlistId = playlistDao.createPlaylist(playlist);
621+
622+ if (playlistId != -1) {
623+ // Copy Tracks
624+ int rows = pPlaylistModelToAdd->rowCount();
625+ for (int i = 0; i < rows; ++i) {
626+ QModelIndex index = pPlaylistModelToAdd->index(i,0);
627+ if (index.isValid()) {
628+ //qDebug() << pPlaylistModelToAdd->getTrackLocation(index);
629+ TrackPointer track = pPlaylistModelToAdd->getTrack(index);
630+ playlistDao.appendTrackToPlaylist(track->getId(), playlistId);
631+ }
632+ }
633+ }
634+ else {
635+ QMessageBox::warning(NULL,
636+ tr("Playlist Creation Failed"),
637+ tr("An unknown error occurred while creating playlist: ")
638+ + playlist);
639+ }
640+
641+ delete pPlaylistModelToAdd;
642+ }
643+ }
644+}
645
646=== added file 'mixxx/src/library/ipod/ipodfeature.h'
647--- mixxx/src/library/ipod/ipodfeature.h 1970-01-01 00:00:00 +0000
648+++ mixxx/src/library/ipod/ipodfeature.h 2012-11-18 17:09:22 +0000
649@@ -0,0 +1,90 @@
650+
651+#ifndef IPODFEATURE_H
652+#define IPODFEATURE_H
653+
654+#include <QStringListModel>
655+#include <QtSql>
656+#include <QFuture>
657+#include <QtConcurrentRun>
658+#include <QFutureWatcher>
659+
660+#include "library/libraryfeature.h"
661+#include "library/trackcollection.h"
662+#include "library/treeitemmodel.h"
663+#include "library/treeitem.h"
664+
665+extern "C"
666+{
667+#include <gpod/itdb.h>
668+}
669+
670+class IPodPlaylistModel;
671+class GPodItdb;
672+
673+class IPodFeature : public LibraryFeature {
674+ Q_OBJECT
675+ public:
676+ IPodFeature(QObject* parent, TrackCollection* pTrackCollection);
677+ virtual ~IPodFeature();
678+ static bool isSupported();
679+
680+ QVariant title();
681+ QIcon getIcon();
682+
683+ bool dropAccept(QList<QUrl> urls);
684+ bool dropAcceptChild(const QModelIndex& index, QList<QUrl> urls);
685+ bool dragMoveAccept(QUrl url);
686+ bool dragMoveAcceptChild(const QModelIndex& index, QUrl url);
687+
688+ TreeItemModel* getChildModel();
689+
690+ public slots:
691+ void activate();
692+ void activate(bool forceReload);
693+ void activateChild(const QModelIndex& index);
694+ void onRightClick(const QPoint& globalPos);
695+ void onRightClickChild(const QPoint& globalPos, QModelIndex index);
696+ void onLazyChildExpandation(const QModelIndex& index);
697+ void onTrackCollectionLoaded();
698+ void slotAddToAutoDJ();
699+ void slotAddToAutoDJTop();
700+ void slotImportAsMixxxPlaylist();
701+
702+ private:
703+ static QString getiTunesMusicPath();
704+ //returns the invisible rootItem for the sidebar model
705+ TreeItem* importLibrary();
706+ void addToAutoDJ(bool bTop);
707+ QString detectMountPoint(QString iPodMountPoint);
708+
709+ QAction* m_pAddToAutoDJAction;
710+ QAction* m_pAddToAutoDJTopAction;
711+ QAction* m_pImportAsMixxxPlaylistAction;
712+
713+ QModelIndex m_lastRightClickedIndex;
714+
715+ IPodPlaylistModel* m_pIPodPlaylistModel;
716+ TreeItemModel m_childModel;
717+ QStringList m_playlists;
718+ TrackCollection* m_pTrackCollection;
719+ //a new DB connection for the worker thread
720+ QSqlDatabase m_database;
721+ bool m_isActivated;
722+ QString m_dbfile;
723+
724+ QFutureWatcher<TreeItem*> m_future_watcher;
725+ QFuture<TreeItem*> m_future;
726+ QString m_title;
727+ bool m_cancelImport;
728+
729+ QString m_dbItunesRoot;
730+ QString m_mixxxItunesRoot;
731+
732+ GPodItdb* m_gPodItdb;
733+
734+ Itdb_iTunesDB* m_itdb;
735+
736+ static const QString IPOD_MOUNT_KEY;
737+};
738+
739+#endif /* IPODFEATURE_H */
740
741=== added file 'mixxx/src/library/ipod/ipodplaylistmodel.cpp'
742--- mixxx/src/library/ipod/ipodplaylistmodel.cpp 1970-01-01 00:00:00 +0000
743+++ mixxx/src/library/ipod/ipodplaylistmodel.cpp 2012-11-18 17:09:22 +0000
744@@ -0,0 +1,901 @@
745+#include <QtAlgorithms>
746+#include <QtDebug>
747+#include <QTime>
748+
749+#include "library/ipod/ipodplaylistmodel.h"
750+#include "mixxxutils.cpp"
751+#include "library/starrating.h"
752+#include "track/beatfactory.h"
753+#include "track/beats.h"
754+
755+extern "C" {
756+#include <glib-object.h> // g_type_init
757+}
758+
759+const bool sDebug = false;
760+
761+IPodPlaylistModel::IPodPlaylistModel(QObject* pParent, TrackCollection* pTrackCollection)
762+ : TrackModel(pTrackCollection->getDatabase(),
763+ "mixxx.db.model.ipod_playlist"),
764+ QAbstractTableModel(pParent),
765+ m_iSortColumn(0),
766+ m_eSortOrder(Qt::AscendingOrder),
767+ m_currentSearch(""),
768+ m_pTrackCollection(pTrackCollection),
769+ m_trackDAO(m_pTrackCollection->getTrackDAO()),
770+ m_pPlaylist(NULL)
771+{
772+ initHeaderData();
773+}
774+
775+IPodPlaylistModel::~IPodPlaylistModel() {
776+
777+}
778+
779+void IPodPlaylistModel::initHeaderData() {
780+ // Set the column heading labels, rename them for translations and have
781+ // proper capitalization
782+
783+ m_headerList.append(qMakePair(QString(tr("#")), (size_t)0xffff));
784+ m_headerList.append(qMakePair(QString(tr("Artist")), offsetof(Itdb_Track, artist)));
785+ m_headerList.append(qMakePair(QString(tr("Title")), offsetof(Itdb_Track, title)));
786+ m_headerList.append(qMakePair(QString(tr("Album")), offsetof(Itdb_Track, album)));
787+ m_headerList.append(qMakePair(QString(tr("Year")), offsetof(Itdb_Track, year)));
788+ m_headerList.append(qMakePair(QString(tr("Duration")), offsetof(Itdb_Track, tracklen)));
789+ m_headerList.append(qMakePair(QString(tr("Rating")), offsetof(Itdb_Track, rating)));
790+ m_headerList.append(qMakePair(QString(tr("Genre")), offsetof(Itdb_Track, genre)));
791+ m_headerList.append(qMakePair(QString(tr("Type")), offsetof(Itdb_Track, filetype)));
792+ m_headerList.append(qMakePair(QString(tr("Track #")), offsetof(Itdb_Track, track_nr)));
793+ m_headerList.append(qMakePair(QString(tr("Date Added")), offsetof(Itdb_Track, time_added)));
794+ m_headerList.append(qMakePair(QString(tr("BPM")), offsetof(Itdb_Track, BPM)));
795+ m_headerList.append(qMakePair(QString(tr("Bitrate")), offsetof(Itdb_Track, bitrate)));
796+ m_headerList.append(qMakePair(QString(tr("Location")), offsetof(Itdb_Track, ipod_path)));
797+ m_headerList.append(qMakePair(QString(tr("Comment")), offsetof(Itdb_Track, comment)));
798+
799+}
800+
801+void IPodPlaylistModel::initDefaultSearchColumns() {
802+ QStringList searchColumns;
803+ searchColumns << "artist"
804+ << "album"
805+ << "location"
806+ << "comment"
807+ << "title";
808+ setSearchColumns(searchColumns);
809+}
810+
811+void IPodPlaylistModel::setSearchColumns(const QStringList& searchColumns) {
812+ m_searchColumns = searchColumns;
813+
814+ // Convert all the search column names to their field indexes because we use
815+ // them a bunch.
816+ m_searchColumnIndices.resize(m_searchColumns.size());
817+ for (int i = 0; i < m_searchColumns.size(); ++i) {
818+ m_searchColumnIndices[i] = fieldIndex(m_searchColumns[i]);
819+ }
820+}
821+
822+QVariant IPodPlaylistModel::headerData(int section, Qt::Orientation orientation, int role) const {
823+ if (role != Qt::DisplayRole)
824+ return QAbstractTableModel::headerData(section, orientation, role);
825+
826+ if ( orientation == Qt::Horizontal
827+ && role == Qt::DisplayRole
828+ && section < m_headerList.size()
829+ ) {
830+ return QVariant(m_headerList.at(section).first);
831+ }
832+ return QAbstractTableModel::headerData(section, orientation, role);
833+}
834+
835+
836+int IPodPlaylistModel::findSortInsertionPoint(int trackId, TrackPointer pTrack,
837+ const QVector<QPair<int, QHash<int, QVariant> > >& rowInfo) {
838+ QVariant trackValue = getTrackValueForColumn(trackId, m_iSortColumn, pTrack);
839+
840+ int min = 0;
841+ int max = rowInfo.size()-1;
842+
843+ if (sDebug) {
844+ qDebug() << this << "Trying to insertion sort:"
845+ << trackValue << "min" << min << "max" << max;
846+ }
847+
848+ while (min <= max) {
849+ int mid = min + (max - min) / 2;
850+ const QPair<int, QHash<int, QVariant> >& otherRowInfo = rowInfo[mid];
851+ int otherTrackId = otherRowInfo.first;
852+ // const QHash<int, QVariant>& otherRowCache = otherRowInfo.second; // not used
853+
854+
855+ // This should not happen, but it's a recoverable error so we should only log it.
856+ if (!m_recordCache.contains(otherTrackId)) {
857+ qDebug() << "WARNING: track" << otherTrackId << "was not in index";
858+// updateTrackInIndex(otherTrackId);
859+ }
860+
861+ QVariant tableValue = getTrackValueForColumn(otherTrackId, m_iSortColumn);
862+ int compare = compareColumnValues(m_iSortColumn, m_eSortOrder, trackValue, tableValue);
863+
864+ if (sDebug) {
865+ qDebug() << this << "Comparing" << trackValue
866+ << "to" << tableValue << ":" << compare;
867+ }
868+
869+ if (compare == 0) {
870+ // Alright, if we're here then we can insert it here and be
871+ // "correct"
872+ min = mid;
873+ break;
874+ } else if (compare > 0) {
875+ min = mid + 1;
876+ } else {
877+ max = mid - 1;
878+ }
879+ }
880+ return min;
881+}
882+
883+
884+const QString IPodPlaylistModel::currentSearch() const {
885+ return m_currentSearch;
886+}
887+
888+void IPodPlaylistModel::setSort(int column, Qt::SortOrder order) {
889+ if (sDebug) {
890+ qDebug() << this << "setSort()";
891+ }
892+
893+ m_iSortColumn = column;
894+ m_eSortOrder = order;
895+}
896+
897+void IPodPlaylistModel::sort(int column, Qt::SortOrder order) {
898+ if (sDebug) {
899+ qDebug() << this << "sort()" << column << order;
900+ }
901+
902+ m_iSortColumn = column;
903+ m_eSortOrder = order;
904+
905+ emit layoutAboutToBeChanged();
906+ qSort(m_sortedPlaylist.begin(), m_sortedPlaylist.end(), columnLessThan);
907+ emit layoutChanged();
908+}
909+
910+int IPodPlaylistModel::rowCount(const QModelIndex& parent) const {
911+ if (m_pPlaylist && !parent.isValid()) {
912+ return m_sortedPlaylist.size();
913+ }
914+ return 0;
915+}
916+
917+int IPodPlaylistModel::columnCount(const QModelIndex& parent) const {
918+ return parent.isValid() ? 0 : m_headerList.size();
919+}
920+
921+int IPodPlaylistModel::fieldIndex(const QString& fieldName) const {
922+ // Usually a small list, so O(n) is small
923+ //return m_queryRecord.indexOf(fieldName);
924+ //return m_columnNames.indexOf(fieldName);
925+ QHash<QString, int>::const_iterator it = m_columnIndex.constFind(fieldName);
926+ if (it != m_columnIndex.end()) {
927+ return it.value();
928+ }
929+ return -1;
930+}
931+
932+QVariant IPodPlaylistModel::data(const QModelIndex& index, int role) const {
933+ //qDebug() << this << "data()";
934+ QVariant value = QVariant();
935+
936+ if ( role != Qt::DisplayRole
937+ && role != Qt::EditRole
938+ && role != Qt::CheckStateRole
939+ && role != Qt::ToolTipRole
940+ ) {
941+ return value;
942+ }
943+
944+
945+ Itdb_Track* pTrack = getPTrackFromModelIndex(index);
946+ if (!pTrack) {
947+ return value;
948+ }
949+
950+ size_t structOffset = m_headerList.at(index.column()).second;
951+
952+
953+ if (structOffset == 0xffff) { // "#"
954+ value = m_sortedPlaylist.at(index.row()).pos;
955+ } else if (structOffset == offsetof(Itdb_Track, year)) {
956+ if (pTrack->year) {
957+ value = QVariant(pTrack->year);
958+ }
959+ } else if (structOffset == offsetof(Itdb_Track, tracklen)) {
960+ if (pTrack->tracklen) {
961+ value = MixxxUtils::millisecondsToMinutes(pTrack->tracklen, true);
962+ }
963+ } else if (structOffset == offsetof(Itdb_Track, rating)) {
964+ value = qVariantFromValue(StarRating(pTrack->rating));
965+ } else if (structOffset == offsetof(Itdb_Track, track_nr)) {
966+ if (pTrack->track_nr) {
967+ value = QVariant(pTrack->track_nr);
968+ }
969+ } else if (structOffset == offsetof(Itdb_Track, BPM)) {
970+ if (pTrack->BPM) {
971+ value = QVariant(pTrack->BPM);
972+ }
973+ } else if (structOffset == offsetof(Itdb_Track, bitrate)) {
974+ if (pTrack->bitrate) {
975+ value = QVariant(pTrack->bitrate);
976+ }
977+ } else if (structOffset == offsetof(Itdb_Track, time_added)) {
978+ if (pTrack->time_added) {
979+ QDateTime timeAdded;
980+ timeAdded.setTime_t(pTrack->time_added);
981+ timeAdded.toString(Qt::ISODate);
982+ value = QVariant(timeAdded.toString(Qt::ISODate));
983+ }
984+ } else {
985+ // for the gchar* elements
986+ //qDebug() << *(gchar**)((char*)(pTrack) + structOffset);
987+ QString ret = QString::fromUtf8(*(gchar**)((char*)(pTrack) + structOffset));
988+
989+ value = QVariant(ret);
990+ }
991+
992+
993+/*
994+ // This value is the value in its most raw form. It was looked up either
995+ // from the SQL table or from the cached track layer.
996+ QVariant value = getBaseValue(index, role);
997+*/
998+
999+
1000+ // Format the value based on whether we are in a tooltip, display, or edit
1001+ // role
1002+ switch (role) {
1003+ case Qt::ToolTipRole:
1004+ case Qt::DisplayRole:
1005+ /*
1006+ if (column == fieldIndex(LIBRARYTABLE_DURATION)) {
1007+ if (qVariantCanConvert<int>(value))
1008+ value = MixxxUtils::secondsToMinutes(qVariantValue<int>(value));
1009+ } else if (column == fieldIndex(LIBRARYTABLE_RATING)) {
1010+ if (qVariantCanConvert<int>(value))
1011+ value = qVariantFromValue(StarRating(value.toInt()));
1012+ } else if (column == fieldIndex(LIBRARYTABLE_TIMESPLAYED)) {
1013+ if (qVariantCanConvert<int>(value))
1014+ value = QString("(%1)").arg(value.toInt());
1015+ } else if (column == fieldIndex(LIBRARYTABLE_PLAYED)) {
1016+ // Convert to a bool. Not really that useful since it gets converted
1017+ // right back to a QVariant
1018+ value = (value == "true") ? true : false;
1019+ }
1020+ */
1021+ break;
1022+ case Qt::EditRole:
1023+ /*
1024+ if (column == fieldIndex(LIBRARYTABLE_BPM)) {
1025+ return value.toDouble();
1026+ } else if (column == fieldIndex(LIBRARYTABLE_TIMESPLAYED)) {
1027+ return index.sibling(row, fieldIndex(LIBRARYTABLE_PLAYED)).data().toBool();
1028+ } else if (column == fieldIndex(LIBRARYTABLE_RATING)) {
1029+ if (qVariantCanConvert<int>(value))
1030+ value = qVariantFromValue(StarRating(value.toInt()));
1031+ }
1032+ */
1033+ break;
1034+ case Qt::CheckStateRole:
1035+ /*
1036+ if (column == fieldIndex(LIBRARYTABLE_TIMESPLAYED)) {
1037+ bool played = index.sibling(row, fieldIndex(LIBRARYTABLE_PLAYED)).data().toBool();
1038+ value = played ? Qt::Checked : Qt::Unchecked;
1039+ } else {
1040+ */
1041+ value = QVariant();
1042+ //}
1043+ break;
1044+ default:
1045+ break;
1046+ }
1047+ return value;
1048+}
1049+
1050+bool IPodPlaylistModel::setData(const QModelIndex& index, const QVariant& value, int role) {
1051+
1052+ if (!index.isValid())
1053+ return false;
1054+
1055+ int row = index.row();
1056+ int column = index.column();
1057+
1058+ if (sDebug) {
1059+ qDebug() << this << "setData() column:" << column << "value:" << value << "role:" << role;
1060+ }
1061+
1062+ // Over-ride sets to TIMESPLAYED and re-direct them to PLAYED
1063+ if (role == Qt::CheckStateRole) {
1064+ if (column == fieldIndex(LIBRARYTABLE_TIMESPLAYED)) {
1065+ QString val = value.toInt() > 0 ? QString("true") : QString("false");
1066+ QModelIndex playedIndex = index.sibling(index.row(), fieldIndex(LIBRARYTABLE_PLAYED));
1067+ return setData(playedIndex, val, Qt::EditRole);
1068+ }
1069+ return false;
1070+ }
1071+
1072+ if (row < 0 || row >= m_rowInfo.size()) {
1073+ return false;
1074+ }
1075+
1076+ const QPair<int, QHash<int, QVariant> >& rowInfo = m_rowInfo[row];
1077+ int trackId = rowInfo.first;
1078+
1079+ // You can't set something in the table columns because we have no way of
1080+ // persisting it.
1081+ const QHash<int, QVariant>& columns = rowInfo.second;
1082+ if (columns.contains(column)) {
1083+ return false;
1084+ }
1085+
1086+ TrackPointer pTrack = m_trackDAO.getTrack(trackId);
1087+ setTrackValueForColumn(pTrack, column, value);
1088+
1089+ // Do not save the track here. Changing the track dirties it and the caching
1090+ // system will automatically save the track once it is unloaded from
1091+ // memory. rryan 10/2010
1092+ //m_trackDAO.saveTrack(pTrack);
1093+
1094+ return true;
1095+}
1096+
1097+TrackModel::CapabilitiesFlags IPodPlaylistModel::getCapabilities() const {
1098+ return TRACKMODELCAPS_NONE
1099+ | TRACKMODELCAPS_ADDTOPLAYLIST
1100+ | TRACKMODELCAPS_ADDTOCRATE
1101+ | TRACKMODELCAPS_ADDTOAUTODJ
1102+ | TRACKMODELCAPS_LOADTODECK
1103+ | TRACKMODELCAPS_LOADTOSAMPLER;
1104+}
1105+
1106+Qt::ItemFlags IPodPlaylistModel::flags(const QModelIndex &index) const {
1107+ return readWriteFlags(index);
1108+}
1109+
1110+Qt::ItemFlags IPodPlaylistModel::readWriteFlags(const QModelIndex &index) const {
1111+ if (!index.isValid())
1112+ return Qt::ItemIsEnabled;
1113+
1114+ Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
1115+
1116+ //Enable dragging songs from this data model to elsewhere (like the waveform
1117+ //widget to load a track into a Player).
1118+ defaultFlags |= Qt::ItemIsDragEnabled;
1119+
1120+ //int row = index.row(); // not used
1121+ int column = index.column();
1122+
1123+ if ( column == fieldIndex(LIBRARYTABLE_FILETYPE)
1124+ || column == fieldIndex(LIBRARYTABLE_LOCATION)
1125+ || column == fieldIndex(LIBRARYTABLE_DURATION)
1126+ || column == fieldIndex(LIBRARYTABLE_BITRATE)
1127+ || column == fieldIndex(LIBRARYTABLE_DATETIMEADDED)) {
1128+ return defaultFlags;
1129+ } else if (column == fieldIndex(LIBRARYTABLE_TIMESPLAYED)) {
1130+ return defaultFlags | Qt::ItemIsUserCheckable;
1131+ } else {
1132+ return defaultFlags | Qt::ItemIsEditable;
1133+ }
1134+}
1135+
1136+Qt::ItemFlags IPodPlaylistModel::readOnlyFlags(const QModelIndex &index) const
1137+{
1138+ Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
1139+ if (!index.isValid())
1140+ return Qt::ItemIsEnabled;
1141+
1142+ //Enable dragging songs from this data model to elsewhere (like the waveform widget to
1143+ //load a track into a Player).
1144+ defaultFlags |= Qt::ItemIsDragEnabled;
1145+
1146+ return defaultFlags;
1147+}
1148+
1149+void IPodPlaylistModel::trackChanged(int trackId) {
1150+ if (sDebug) {
1151+ qDebug() << this << "trackChanged" << trackId;
1152+ }
1153+ // TODO(daschuer): eg. use that for played column
1154+ //m_trackOverrides.insert(trackId);
1155+ //QLinkedList<int> rows = getTrackRows(trackId);
1156+/*
1157+ foreach (int row, rows) {
1158+ //qDebug() << "Row in this result set was updated. Signalling update. track:" << trackId << "row:" << row;
1159+ QModelIndex left = index(row, 0);
1160+ QModelIndex right = index(row, columnCount());
1161+ emit(dataChanged(left, right));
1162+ }
1163+*/
1164+}
1165+
1166+//static
1167+bool IPodPlaylistModel::columnLessThan(const playlist_member &s1, const playlist_member &s2) {
1168+ bool ret;
1169+
1170+ size_t structOffset = s1.pClass->m_headerList.at(s1.pClass->m_iSortColumn).second;
1171+
1172+ if (structOffset == 0xffff) { // "#"
1173+ ret = s1.pos < s2.pos;
1174+ } else if (structOffset == offsetof(Itdb_Track, year)) {
1175+ ret = s1.pTrack->year < s2.pTrack->year;
1176+ } else if (structOffset == offsetof(Itdb_Track, tracklen)) {
1177+ ret = s1.pTrack->tracklen < s2.pTrack->tracklen;
1178+ } else if (structOffset == offsetof(Itdb_Track, rating)) {
1179+ ret = s1.pTrack->rating < s2.pTrack->rating;
1180+ } else if (structOffset == offsetof(Itdb_Track, track_nr)) {
1181+ ret = s1.pTrack->track_nr < s2.pTrack->track_nr;
1182+ } else if (structOffset == offsetof(Itdb_Track, BPM)) {
1183+ ret = s1.pTrack->BPM < s2.pTrack->BPM;
1184+ } else if (structOffset == offsetof(Itdb_Track, bitrate)) {
1185+ ret = s1.pTrack->bitrate < s2.pTrack->bitrate;
1186+ } else if (structOffset == offsetof(Itdb_Track, time_added)) {
1187+ ret = s1.pTrack->time_added < s2.pTrack->time_added;
1188+ } else {
1189+ // for the gchar* elements
1190+ QString string1 = QString::fromUtf8(*(gchar**)((char*)(s1.pTrack) + structOffset));
1191+ QString string2 = QString::fromUtf8(*(gchar**)((char*)(s2.pTrack) + structOffset));
1192+ ret = string1.toLower() < string2.toLower();
1193+ }
1194+
1195+ if (s1.pClass->m_eSortOrder != Qt::AscendingOrder) {
1196+ return !ret;
1197+ }
1198+ return ret;
1199+}
1200+
1201+int IPodPlaylistModel::compareColumnValues(int iColumnNumber, Qt::SortOrder eSortOrder,
1202+ QVariant val1, QVariant val2) {
1203+ int result = 0;
1204+
1205+ if (iColumnNumber == fieldIndex(PLAYLISTTRACKSTABLE_POSITION) ||
1206+ iColumnNumber == fieldIndex(LIBRARYTABLE_BITRATE) ||
1207+ iColumnNumber == fieldIndex(LIBRARYTABLE_BPM) ||
1208+ iColumnNumber == fieldIndex(LIBRARYTABLE_DURATION) ||
1209+ iColumnNumber == fieldIndex(LIBRARYTABLE_TIMESPLAYED) ||
1210+ iColumnNumber == fieldIndex(LIBRARYTABLE_RATING)) {
1211+ // Sort as floats.
1212+ double delta = val1.toDouble() - val2.toDouble();
1213+
1214+ if (fabs(delta) < .00001)
1215+ result = 0;
1216+ else if (delta > 0.0)
1217+ result = 1;
1218+ else
1219+ result = -1;
1220+ } else {
1221+ // Default to case-insensitive string comparison
1222+ result = val1.toString().compare(val2.toString(), Qt::CaseInsensitive);
1223+ }
1224+
1225+ // If we're in descending order, flip the comparison.
1226+ if (eSortOrder == Qt::DescendingOrder) {
1227+ result = -result;
1228+ }
1229+
1230+ return result;
1231+}
1232+
1233+
1234+QVariant IPodPlaylistModel::getTrackValueForColumn(int trackId, int column, TrackPointer pTrack) const {
1235+ QVariant result;
1236+
1237+ // The caller can optionally provide a pTrack if they already looked it
1238+ // up. This is just an optimization to help reduce the # of calls to
1239+ // lookupCachedTrack. If they didn't provide it, look it up.
1240+ if (pTrack) {
1241+ result = getTrackValueForColumn(pTrack, column);
1242+ }
1243+
1244+ // If the track lookup failed (could happen for track properties we dont
1245+ // keep track of in Track, like playlist position) look up the value in
1246+ // their SQL record.
1247+
1248+ // TODO(rryan) this code is flawed for columns that contains row-specific
1249+ // metadata. Currently the upper-levels will not delegate row-specific
1250+ // columns to this method, but there should still be a check here I think.
1251+ if (!result.isValid()) {
1252+ QHash<int, QVector<QVariant> >::const_iterator it =
1253+ m_recordCache.find(trackId);
1254+ if (it != m_recordCache.end()) {
1255+ const QVector<QVariant>& fields = it.value();
1256+ result = fields.value(column, result);
1257+ }
1258+ }
1259+ return result;
1260+}
1261+
1262+QVariant IPodPlaylistModel::getTrackValueForColumn(TrackPointer pTrack, int column) const {
1263+ if (!pTrack)
1264+ return QVariant();
1265+
1266+ // TODO(XXX) Qt properties could really help here.
1267+ if (fieldIndex(LIBRARYTABLE_ARTIST) == column) {
1268+ return QVariant(pTrack->getArtist());
1269+ } else if (fieldIndex(LIBRARYTABLE_TITLE) == column) {
1270+ return QVariant(pTrack->getTitle());
1271+ } else if (fieldIndex(LIBRARYTABLE_ALBUM) == column) {
1272+ return QVariant(pTrack->getAlbum());
1273+ } else if (fieldIndex(LIBRARYTABLE_YEAR) == column) {
1274+ return QVariant(pTrack->getYear());
1275+ } else if (fieldIndex(LIBRARYTABLE_GENRE) == column) {
1276+ return QVariant(pTrack->getGenre());
1277+ } else if (fieldIndex(LIBRARYTABLE_FILETYPE) == column) {
1278+ return QVariant(pTrack->getType());
1279+ } else if (fieldIndex(LIBRARYTABLE_TRACKNUMBER) == column) {
1280+ return QVariant(pTrack->getTrackNumber());
1281+ } else if (fieldIndex(LIBRARYTABLE_LOCATION) == column) {
1282+ return QVariant(pTrack->getLocation());
1283+ } else if (fieldIndex(LIBRARYTABLE_COMMENT) == column) {
1284+ return QVariant(pTrack->getComment());
1285+ } else if (fieldIndex(LIBRARYTABLE_DURATION) == column) {
1286+ return pTrack->getDuration();
1287+ } else if (fieldIndex(LIBRARYTABLE_BITRATE) == column) {
1288+ return QVariant(pTrack->getBitrate());
1289+ } else if (fieldIndex(LIBRARYTABLE_BPM) == column) {
1290+ return QVariant(pTrack->getBpm());
1291+ } else if (fieldIndex(LIBRARYTABLE_PLAYED) == column) {
1292+ return QVariant(pTrack->getPlayed());
1293+ } else if (fieldIndex(LIBRARYTABLE_TIMESPLAYED) == column) {
1294+ return QVariant(pTrack->getTimesPlayed());
1295+ } else if (fieldIndex(LIBRARYTABLE_RATING) == column) {
1296+ return pTrack->getRating();
1297+ } else if (fieldIndex(LIBRARYTABLE_KEY) == column) {
1298+ return pTrack->getKey();
1299+ }
1300+ return QVariant();
1301+}
1302+
1303+void IPodPlaylistModel::setTrackValueForColumn(TrackPointer pTrack, int column, QVariant value) {
1304+ // TODO(XXX) Qt properties could really help here.
1305+ if (fieldIndex(LIBRARYTABLE_ARTIST) == column) {
1306+ pTrack->setArtist(value.toString());
1307+ } else if (fieldIndex(LIBRARYTABLE_TITLE) == column) {
1308+ pTrack->setTitle(value.toString());
1309+ } else if (fieldIndex(LIBRARYTABLE_ALBUM) == column) {
1310+ pTrack->setAlbum(value.toString());
1311+ } else if (fieldIndex(LIBRARYTABLE_YEAR) == column) {
1312+ pTrack->setYear(value.toString());
1313+ } else if (fieldIndex(LIBRARYTABLE_GENRE) == column) {
1314+ pTrack->setGenre(value.toString());
1315+ } else if (fieldIndex(LIBRARYTABLE_FILETYPE) == column) {
1316+ pTrack->setType(value.toString());
1317+ } else if (fieldIndex(LIBRARYTABLE_TRACKNUMBER) == column) {
1318+ pTrack->setTrackNumber(value.toString());
1319+ } else if (fieldIndex(LIBRARYTABLE_LOCATION) == column) {
1320+ pTrack->setLocation(value.toString());
1321+ } else if (fieldIndex(LIBRARYTABLE_COMMENT) == column) {
1322+ pTrack->setComment(value.toString());
1323+ } else if (fieldIndex(LIBRARYTABLE_DURATION) == column) {
1324+ pTrack->setDuration(value.toInt());
1325+ } else if (fieldIndex(LIBRARYTABLE_BITRATE) == column) {
1326+ pTrack->setBitrate(value.toInt());
1327+ } else if (fieldIndex(LIBRARYTABLE_BPM) == column) {
1328+ //QVariant::toFloat needs >= QT 4.6.x
1329+ pTrack->setBpm((float) value.toDouble());
1330+ } else if (fieldIndex(LIBRARYTABLE_PLAYED) == column) {
1331+ pTrack->setPlayed(value.toBool());
1332+ } else if (fieldIndex(LIBRARYTABLE_TIMESPLAYED) == column) {
1333+ pTrack->setTimesPlayed(value.toInt());
1334+ } else if (fieldIndex(LIBRARYTABLE_RATING) == column) {
1335+ StarRating starRating = qVariantValue<StarRating>(value);
1336+ pTrack->setRating(starRating.starCount());
1337+ } else if (fieldIndex(LIBRARYTABLE_KEY) == column) {
1338+ pTrack->setKey(value.toString());
1339+ }
1340+}
1341+
1342+QVariant IPodPlaylistModel::getBaseValue(const QModelIndex& index, int role) const {
1343+ if (role != Qt::DisplayRole &&
1344+ role != Qt::ToolTipRole &&
1345+ role != Qt::EditRole) {
1346+ return QVariant();
1347+ }
1348+
1349+ int row = index.row();
1350+ int column = index.column();
1351+
1352+ if (row < 0 || row >= m_rowInfo.size()) {
1353+ return QVariant();
1354+ }
1355+
1356+ const QPair<int, QHash<int, QVariant> >& rowInfo = m_rowInfo[row];
1357+ int trackId = rowInfo.first;
1358+
1359+ // If the row info has the row-specific column, return that.
1360+ const QHash<int, QVariant>& columns = rowInfo.second;
1361+ if (columns.contains(column)) {
1362+ if (sDebug) {
1363+ qDebug() << "Returning table-column value" << columns[column] << "for column" << column;
1364+ }
1365+ return columns[column];
1366+ }
1367+
1368+ // Otherwise, return the information from the track record cache for the
1369+ // given track ID
1370+ return getTrackValueForColumn(trackId, column);
1371+}
1372+
1373+void IPodPlaylistModel::setPlaylist(Itdb_Playlist* pPlaylist) {
1374+ if (m_pPlaylist) {
1375+ beginRemoveRows(QModelIndex(), 0, m_sortedPlaylist.size()-1);
1376+ m_pPlaylist = NULL;
1377+ m_sortedPlaylist.clear();
1378+ endRemoveRows();
1379+ }
1380+
1381+ if (pPlaylist) {
1382+ beginInsertRows(QModelIndex(), 0, pPlaylist->num-1);
1383+ m_pPlaylist = pPlaylist;
1384+ // walk thought linked list and collect playlist position
1385+
1386+ GList* track_node;
1387+ uint pl_position = 0;
1388+
1389+ QByteArray search = m_currentSearch.toUtf8();
1390+
1391+ for (track_node = g_list_first(pPlaylist->members);
1392+ track_node != NULL;
1393+ track_node = g_list_next(track_node))
1394+ {
1395+ struct playlist_member plMember;
1396+ plMember.pClass = this;
1397+ plMember.pTrack = (Itdb_Track*)track_node->data;
1398+ plMember.pos = ++pl_position;
1399+
1400+ // Filter
1401+ bool add = false;
1402+ if( itdb_playlist_is_mpl(pPlaylist) && search.size()){
1403+ // Apply filter only for the master playlist "IPOD"
1404+ gchar* haystack = g_strconcat(
1405+ plMember.pTrack->artist,
1406+ plMember.pTrack->title,
1407+ plMember.pTrack->album,
1408+ plMember.pTrack->comment,
1409+ plMember.pTrack->genre,
1410+ NULL);
1411+
1412+ if( findInUtf8Case(haystack, search.data())) {
1413+ add = true;
1414+ }
1415+ g_free(haystack);
1416+ } else {
1417+ add = true;
1418+ }
1419+
1420+ if (add) {
1421+ m_sortedPlaylist.append(plMember);
1422+ }
1423+
1424+ }
1425+ qSort(m_sortedPlaylist.begin(), m_sortedPlaylist.end(), columnLessThan);
1426+
1427+ endInsertRows();
1428+ }
1429+}
1430+
1431+//static
1432+bool IPodPlaylistModel::findInUtf8Case(gchar* heystack, gchar* needles) {
1433+ bool ret = true;
1434+ if (heystack == NULL) {
1435+ return false;
1436+ }
1437+ if (needles == NULL) {
1438+ return true;
1439+ }
1440+
1441+ gchar* heystack_casefold = g_utf8_casefold(heystack, -1);
1442+ gchar* needles_casefold = g_utf8_casefold(needles, -1);
1443+ gchar** needle = g_strsplit_set(needles_casefold, " ", 0);
1444+
1445+ qDebug() << "find" << heystack_casefold;
1446+
1447+ // all needles must be found, implicit AND
1448+ for (int i = 0; needle[i]; i++) {
1449+ if (needle[i][0] != 0) {
1450+ if (!g_strrstr(heystack_casefold, needle[i])) {
1451+ ret = false;
1452+ break;
1453+ }
1454+ }
1455+ }
1456+ g_free(heystack_casefold);
1457+ g_free(needles_casefold);
1458+ g_strfreev(needle);
1459+ return ret;
1460+}
1461+
1462+TrackPointer IPodPlaylistModel::getTrack(const QModelIndex& index) const {
1463+
1464+ Itdb_Track* pTrack = getPTrackFromModelIndex(index);
1465+ if (!pTrack) {
1466+ return TrackPointer();
1467+ }
1468+
1469+ QString location = itdb_get_mountpoint(m_pPlaylist->itdb);
1470+ QString ipod_path = pTrack->ipod_path;
1471+ ipod_path.replace(QString(":"), QString("/"));
1472+ location += ipod_path;
1473+
1474+ qDebug() << location;
1475+
1476+ TrackDAO& track_dao = m_pTrackCollection->getTrackDAO();
1477+ int track_id = track_dao.getTrackId(location);
1478+ if (track_id < 0) {
1479+ // Add Track to library
1480+ track_id = track_dao.addTrack(location, true);
1481+ }
1482+
1483+ TrackPointer pTrackP;
1484+
1485+ if (track_id < 0) {
1486+ // Add Track to library failed
1487+ // Create own TrackInfoObject
1488+ pTrackP = TrackPointer(new TrackInfoObject(location), &QObject::deleteLater);
1489+ }
1490+ else {
1491+ pTrackP = track_dao.getTrack(track_id);
1492+ }
1493+
1494+ // Overwrite metadata from Ipod
1495+ // Note: This will be written to the mixxx library as well
1496+ // This is OK here because the location ist still pointing to the iPod device
1497+ pTrackP->setArtist(QString::fromUtf8(pTrack->artist));
1498+ pTrackP->setTitle(QString::fromUtf8(pTrack->title));
1499+ pTrackP->setAlbum(QString::fromUtf8(pTrack->album));
1500+ pTrackP->setYear(QString::number(pTrack->year));
1501+ pTrackP->setGenre(QString::fromUtf8(pTrack->genre));
1502+ double bpm = (double)pTrack->BPM;
1503+ pTrackP->setBpm(bpm);
1504+ pTrackP->setComment(QString::fromUtf8(pTrack->comment));
1505+
1506+ // If the track has a BPM, then give it a static beatgrid.
1507+ if (bpm) {
1508+ BeatsPointer pBeats = BeatFactory::makeBeatGrid(pTrackP.data(), bpm, 0.0f);
1509+ pTrackP->setBeats(pBeats);
1510+ }
1511+
1512+ return pTrackP;
1513+}
1514+// Gets the on-disk location of the track at the given location.
1515+QString IPodPlaylistModel::getTrackLocation(const QModelIndex& index) const {
1516+
1517+ Itdb_Track* pTrack = getPTrackFromModelIndex(index);
1518+ if (!pTrack) {
1519+ return QString();
1520+ }
1521+
1522+ QString location = itdb_get_mountpoint(m_pPlaylist->itdb);
1523+ QString ipod_path = pTrack->ipod_path;
1524+ ipod_path.replace(QString(":"), QString("/"));
1525+ location += ipod_path;
1526+
1527+ return location;
1528+}
1529+
1530+// Gets a significant hint of the track at the given QModelIndex
1531+// This is used to restore the selection after WTrackTableView::doSortByColumn
1532+int IPodPlaylistModel::getTrackId(const QModelIndex& index) const {
1533+ // in our case the position in the playlist is as significant hint
1534+ int row = index.row();
1535+ if (row < m_sortedPlaylist.size()) {
1536+ return m_sortedPlaylist.at(row).pos;
1537+ }
1538+ else {
1539+ return 0;
1540+ }
1541+}
1542+
1543+const QLinkedList<int> IPodPlaylistModel::getTrackRows(int trackId) const {
1544+ // In this case we get the position as trackId, returned from getTrackId above.
1545+ QLinkedList<int> ret;
1546+ for (int i = 0; i < m_sortedPlaylist.size(); ++i) {
1547+ if (m_sortedPlaylist.at(i).pos == trackId ){
1548+ ret.push_back(i);
1549+ break;
1550+ }
1551+ }
1552+ return ret;
1553+}
1554+
1555+void IPodPlaylistModel::search(const QString& searchText) {
1556+ if (sDebug)
1557+ qDebug() << this << "search" << searchText;
1558+
1559+ if (m_currentSearch != searchText) {
1560+ m_currentSearch = searchText;
1561+ if (itdb_playlist_is_mpl(m_pPlaylist)) {
1562+ setPlaylist(m_pPlaylist);
1563+ }
1564+ }
1565+}
1566+
1567+const QString IPodPlaylistModel::currentSearch() {
1568+ return m_currentSearch;
1569+}
1570+
1571+bool IPodPlaylistModel::isColumnInternal(int column) {
1572+ Q_UNUSED(column);
1573+ return false;
1574+}
1575+
1576+QMimeData* IPodPlaylistModel::mimeData(const QModelIndexList &indexes) const {
1577+ QMimeData *mimeData = new QMimeData();
1578+ QList<QUrl> urls;
1579+
1580+ //Ok, so the list of indexes we're given contains separates indexes for
1581+ //each column, so even if only one row is selected, we'll have like 7 indexes.
1582+ //We need to only count each row once:
1583+ QList<int> rows;
1584+
1585+ foreach (QModelIndex index, indexes) {
1586+ if (index.isValid()) {
1587+ if (!rows.contains(index.row())) {
1588+ rows.push_back(index.row());
1589+ QUrl url = QUrl::fromLocalFile(getTrackLocation(index));
1590+ if (!url.isValid())
1591+ qDebug() << "ERROR invalid url\n";
1592+ else
1593+ urls.append(url);
1594+ }
1595+ }
1596+ }
1597+ mimeData->setUrls(urls);
1598+ return mimeData;
1599+}
1600+ /** if no header state exists, we may hide some columns so that the user can reactivate them **/
1601+bool IPodPlaylistModel::isColumnHiddenByDefault(int column) {
1602+ Q_UNUSED(column);
1603+ return false;
1604+}
1605+
1606+void IPodPlaylistModel::removeTrack(const QModelIndex& index) {
1607+ Q_UNUSED(index);
1608+}
1609+
1610+void IPodPlaylistModel::removeTracks(const QModelIndexList& indices) {
1611+ Q_UNUSED(indices);
1612+}
1613+
1614+bool IPodPlaylistModel::addTrack(const QModelIndex& index, QString location) {
1615+ Q_UNUSED(index);
1616+ Q_UNUSED(location);
1617+ return false;
1618+}
1619+
1620+void IPodPlaylistModel::moveTrack(const QModelIndex& sourceIndex, const QModelIndex& destIndex) {
1621+ Q_UNUSED(destIndex);
1622+ Q_UNUSED(sourceIndex);
1623+}
1624+
1625+QItemDelegate* IPodPlaylistModel::delegateForColumn(const int i) {
1626+ Q_UNUSED(i);
1627+ return NULL;
1628+}
1629+
1630+Itdb_Track* IPodPlaylistModel::getPTrackFromModelIndex(const QModelIndex& index) const {
1631+ if ( !index.isValid()
1632+ || m_pPlaylist == NULL
1633+ ) {
1634+ return NULL;
1635+ }
1636+
1637+ int row = index.row();
1638+ int column = index.column();
1639+
1640+ if (row >= m_sortedPlaylist.size() || column >= m_headerList.size()) {
1641+ // index is outside the valid range
1642+ return NULL;
1643+ }
1644+ return m_sortedPlaylist.at(row).pTrack;
1645+}
1646
1647=== added file 'mixxx/src/library/ipod/ipodplaylistmodel.h'
1648--- mixxx/src/library/ipod/ipodplaylistmodel.h 1970-01-01 00:00:00 +0000
1649+++ mixxx/src/library/ipod/ipodplaylistmodel.h 2012-11-18 17:09:22 +0000
1650@@ -0,0 +1,146 @@
1651+#ifndef IPODPLAYLISTMODEL_H
1652+#define IPODPLAYLISTMODEL_H
1653+
1654+#include <QtCore>
1655+#include <QHash>
1656+#include <QtGui>
1657+#include <QtSql>
1658+
1659+#include "library/trackmodel.h"
1660+#include "library/trackcollection.h"
1661+#include "library/dao/trackdao.h"
1662+
1663+extern "C"
1664+{
1665+#include <gpod/itdb.h>
1666+}
1667+
1668+// BaseSqlTableModel is a custom-written SQL-backed table which aggressively
1669+// caches the contents of the table and supports lightweight updates.
1670+class IPodPlaylistModel : public QAbstractTableModel , public virtual TrackModel
1671+{
1672+ Q_OBJECT
1673+ public:
1674+
1675+ struct playlist_member {
1676+ IPodPlaylistModel* pClass;
1677+ int pos;
1678+ Itdb_Track* pTrack;
1679+ };
1680+
1681+ IPodPlaylistModel(QObject* pParent, TrackCollection* pTrackCollection);
1682+ virtual ~IPodPlaylistModel();
1683+
1684+ ////////////////////////////////////////////////////////////////////////////
1685+ // Methods implemented from QAbstractItemModel
1686+ ////////////////////////////////////////////////////////////////////////////
1687+
1688+ virtual TrackPointer getTrack(const QModelIndex& index) const;
1689+ virtual QString getTrackLocation(const QModelIndex& index) const;
1690+ virtual int getTrackId(const QModelIndex& index) const;
1691+ virtual const QLinkedList<int> getTrackRows(int trackId) const;
1692+ virtual void search(const QString& searchText);
1693+ virtual const QString currentSearch();
1694+ virtual bool isColumnInternal(int column);
1695+ virtual bool isColumnHiddenByDefault(int column);
1696+ virtual void removeTrack(const QModelIndex& index);
1697+ virtual void removeTracks(const QModelIndexList& indices);
1698+ virtual bool addTrack(const QModelIndex& index, QString location);
1699+ virtual void moveTrack(const QModelIndex& sourceIndex, const QModelIndex& destIndex);
1700+
1701+ virtual Qt::ItemFlags flags(const QModelIndex &index) const;
1702+ QMimeData* mimeData(const QModelIndexList &indexes) const;
1703+
1704+ QItemDelegate* delegateForColumn(const int i);
1705+ TrackModel::CapabilitiesFlags getCapabilities() const;
1706+
1707+ virtual void sort(int column, Qt::SortOrder order);
1708+ virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
1709+ virtual bool setData(const QModelIndex& index, const QVariant& value, int role=Qt::EditRole);
1710+ virtual int columnCount(const QModelIndex& parent=QModelIndex()) const;
1711+ virtual int rowCount(const QModelIndex& parent=QModelIndex()) const;
1712+ virtual QVariant headerData(int section, Qt::Orientation orientation,
1713+ int role=Qt::DisplayRole) const;
1714+
1715+ void setPlaylist(Itdb_Playlist* pPlaylist);
1716+
1717+ ////////////////////////////////////////////////////////////////////////////
1718+ // Other public methods
1719+ ////////////////////////////////////////////////////////////////////////////
1720+
1721+ virtual const QString currentSearch() const;
1722+ virtual void setSort(int column, Qt::SortOrder order);
1723+ virtual int fieldIndex(const QString& fieldName) const;
1724+
1725+ protected:
1726+ /** Use this if you want a model that is read-only. */
1727+ virtual Qt::ItemFlags readOnlyFlags(const QModelIndex &index) const;
1728+ /** Use this if you want a model that can be changed */
1729+ virtual Qt::ItemFlags readWriteFlags(const QModelIndex &index) const;
1730+
1731+ // Set the columns used for searching. Names must correspond to the column
1732+ // names in the table provided to setTable. Must be called after setTable is
1733+ // called.
1734+ virtual void setSearchColumns(const QStringList& searchColumns);
1735+ // virtual QString orderByClause() const;
1736+ virtual void initHeaderData();
1737+ virtual void initDefaultSearchColumns();
1738+
1739+ private slots:
1740+ void trackChanged(int trackId);
1741+
1742+ private:
1743+ inline TrackPointer lookupCachedTrack(int trackId) const;
1744+ inline QVariant getTrackValueForColumn(TrackPointer pTrack, int column) const;
1745+ inline QVariant getTrackValueForColumn(int trackId, int column,
1746+ TrackPointer pTrack=TrackPointer()) const;
1747+ inline void setTrackValueForColumn(TrackPointer pTrack, int column, QVariant value);
1748+ QVariant getBaseValue(const QModelIndex& index, int role = Qt::DisplayRole) const;
1749+
1750+ static bool columnLessThan(const playlist_member &s1, const playlist_member &s2);
1751+
1752+
1753+ virtual int compareColumnValues(int iColumnNumber, Qt::SortOrder eSortOrder, QVariant val1, QVariant val2);
1754+ virtual int findSortInsertionPoint(int trackId, TrackPointer pTrack,
1755+ const QVector<QPair<int, QHash<int, QVariant> > >& rowInfo);
1756+
1757+ Itdb_Track* getPTrackFromModelIndex(const QModelIndex& index) const;
1758+
1759+ static bool findInUtf8Case(gchar* heystack, gchar* needles);
1760+
1761+ QString m_tableName;
1762+ QStringList m_columnNames;
1763+ QString m_columnNamesJoined;
1764+ QHash<QString, int> m_columnIndex;
1765+ QSet<QString> m_tableColumns;
1766+ QString m_tableColumnsJoined;
1767+ QSet<int> m_tableColumnIndices;
1768+
1769+ QStringList m_searchColumns;
1770+ QVector<int> m_searchColumnIndices;
1771+ QString m_idColumn;
1772+
1773+ int m_iSortColumn;
1774+ Qt::SortOrder m_eSortOrder;
1775+
1776+ bool m_bIndexBuilt;
1777+ QSqlRecord m_queryRecord;
1778+ QHash<int, QVector<QVariant> > m_recordCache;
1779+ QVector<QPair<int, QHash<int, QVariant> > > m_rowInfo;
1780+ QHash<int, QLinkedList<int> > m_trackIdToRows;
1781+ QSet<int> m_trackOverrides;
1782+
1783+ QString m_currentSearch;
1784+ QString m_currentSearchFilter;
1785+
1786+ QList<QPair<QString, size_t> > m_headerList;
1787+
1788+ QList<IPodPlaylistModel::playlist_member> m_sortedPlaylist;
1789+
1790+ TrackCollection* m_pTrackCollection;
1791+ TrackDAO& m_trackDAO;
1792+
1793+ Itdb_Playlist* m_pPlaylist;
1794+};
1795+
1796+#endif /* IPODPLAYLISTMODEL_H */
1797
1798=== modified file 'mixxx/src/library/library.cpp'
1799--- mixxx/src/library/library.cpp 2012-10-04 16:19:07 +0000
1800+++ mixxx/src/library/library.cpp 2012-11-18 17:09:22 +0000
1801@@ -14,6 +14,9 @@
1802 #include "library/rhythmbox/rhythmboxfeature.h"
1803 #include "library/recording/recordingfeature.h"
1804 #include "library/itunes/itunesfeature.h"
1805+#ifdef __IPOD__
1806+#include "library/ipod/ipodfeature.h"
1807+#endif // __IPOD__
1808 #include "library/mixxxlibraryfeature.h"
1809 #include "library/autodjfeature.h"
1810 #include "library/playlistfeature.h"
1811@@ -75,6 +78,12 @@
1812 pConfig->getValueString(ConfigKey("[Library]","ShowITunesLibrary"),"1").toInt()) {
1813 addFeature(new ITunesFeature(this, m_pTrackCollection));
1814 }
1815+#ifdef __IPOD__
1816+ if (IPodFeature::isSupported() &&
1817+ pConfig->getValueString(ConfigKey("[Library]","ShowIpod"),"1").toInt()) {
1818+ addFeature(new IPodFeature(this, m_pTrackCollection));
1819+ }
1820+#endif // __IPOD__
1821 if (TraktorFeature::isSupported() &&
1822 pConfig->getValueString(ConfigKey("[Library]","ShowTraktorLibrary"),"1").toInt()) {
1823 addFeature(new TraktorFeature(this, m_pTrackCollection));
1824
1825=== modified file 'mixxx/src/widget/wlibrarysidebar.cpp'
1826--- mixxx/src/widget/wlibrarysidebar.cpp 2012-07-25 17:20:41 +0000
1827+++ mixxx/src/widget/wlibrarysidebar.cpp 2012-11-18 17:09:22 +0000
1828@@ -59,7 +59,8 @@
1829 if (event->mimeData()->hasUrls()) {
1830 QList<QUrl> urls(event->mimeData()->urls());
1831 //Drag and drop within this widget
1832- if (event->source() == this && event->possibleActions() & Qt::MoveAction) {
1833+ if ( (event->source() == this)
1834+ && (event->possibleActions() & Qt::MoveAction)) {
1835 //Do nothing.
1836 event->ignore();
1837 } else {
1838@@ -77,13 +78,15 @@
1839 }
1840 }
1841 }
1842- if (accepted)
1843+ if (accepted) {
1844 event->acceptProposedAction();
1845- else
1846+ } else {
1847 event->ignore();
1848+ }
1849 }
1850- } else
1851+ } else {
1852 event->ignore();
1853+ }
1854 }
1855
1856 void WLibrarySidebar::timerEvent(QTimerEvent *event) {
1857@@ -106,8 +109,10 @@
1858 if (event->mimeData()->hasUrls()) {
1859 QList<QUrl> urls(event->mimeData()->urls());
1860 //Drag and drop within this widget
1861- if (event->source() == this && event->possibleActions() & Qt::MoveAction) {
1862- event->ignore();
1863+ if ( (event->source() == this)
1864+ && (event->possibleActions() & Qt::MoveAction)) {
1865+ //Do nothing.
1866+ event->ignore();
1867 } else {
1868 //Reset the selected items (if you had anything highlighted, it clears it)
1869 //this->selectionModel()->clear();
1870
1871=== modified file 'mixxx/src/widget/wtracktableview.cpp'
1872--- mixxx/src/widget/wtracktableview.cpp 2012-11-16 04:38:16 +0000
1873+++ mixxx/src/widget/wtracktableview.cpp 2012-11-18 17:09:22 +0000
1874@@ -959,11 +959,11 @@
1875
1876 void WTrackTableView::slotSendToAutoDJ() {
1877 // append to auto DJ
1878- sendToAutoDJ(false); // bTop = false
1879+ sendToAutoDJ(false); // bTop = false
1880 }
1881
1882 void WTrackTableView::slotSendToAutoDJTop() {
1883- sendToAutoDJ(true); // bTop = true
1884+ sendToAutoDJ(true); // bTop = true
1885 }
1886
1887 void WTrackTableView::sendToAutoDJ(bool bTop) {