Merge lp:~daschuer/mixxx/features_ipod into lp:~mixxxdevelopers/mixxx/trunk
- features_ipod
- Merge into trunk
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Mixxx Development Team | Pending | ||
Review via email: mp+80743@code.launchpad.net |
Commit message
Description of the change
Features:
* New library item "iPod"
* Sorting and Filtering
* new icon for Rhythmbox
* iPod auto detection
* disable iPod feature in preferences
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.
- 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
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' |
39 | Binary 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' |
41 | Binary 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) { |
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?