Merge lp:~mhaulo/mixxx/crate-and-playlist-lock into lp:~mixxxdevelopers/mixxx/trunk

Proposed by Mika Haulo
Status: Merged
Merged at revision: 2644
Proposed branch: lp:~mhaulo/mixxx/crate-and-playlist-lock
Merge into: lp:~mixxxdevelopers/mixxx/trunk
Diff against target: 1011 lines (+458/-79) (has conflicts)
20 files modified
mixxx/res/images/templates/ic_template_library_and_preferences.svg (+30/-7)
mixxx/res/mixxx.qrc (+1/-0)
mixxx/res/schema.xml (+9/-0)
mixxx/src/library/cratefeature.cpp (+96/-6)
mixxx/src/library/cratefeature.h (+2/-0)
mixxx/src/library/cratetablemodel.cpp (+41/-20)
mixxx/src/library/dao/cratedao.cpp (+45/-0)
mixxx/src/library/dao/cratedao.h (+2/-0)
mixxx/src/library/dao/playlistdao.cpp (+45/-0)
mixxx/src/library/dao/playlistdao.h (+4/-0)
mixxx/src/library/playlistfeature.cpp (+98/-6)
mixxx/src/library/playlistfeature.h (+4/-2)
mixxx/src/library/playlisttablemodel.cpp (+34/-21)
mixxx/src/library/sidebarmodel.cpp (+6/-2)
mixxx/src/library/trackcollection.cpp (+1/-1)
mixxx/src/library/trackmodel.h (+1/-0)
mixxx/src/library/treeitem.cpp (+12/-0)
mixxx/src/library/treeitem.h (+7/-0)
mixxx/src/library/treeitemmodel.cpp (+2/-2)
mixxx/src/widget/wtracktableview.cpp (+18/-12)
Text conflict in mixxx/src/library/cratefeature.cpp
Text conflict in mixxx/src/library/playlistfeature.cpp
Text conflict in mixxx/src/library/treeitem.h
To merge this branch: bzr merge lp:~mhaulo/mixxx/crate-and-playlist-lock
Reviewer Review Type Date Requested Status
RJ Skerry-Ryan Approve
Phillip Whelan code review Needs Fixing
Review via email: mp+47554@code.launchpad.net

Description of the change

An implementation for wishlist issue #661466 (https://bugs.launchpad.net/mixxx/+bug/661466).

To post a comment you must log in.
Revision history for this message
Phillip Whelan (pwhelan) wrote :

Line 217 could be problematic. Different optimization levels and/or compilers can switch the order of boolean expressions.

review: Needs Fixing (code review)
2633. By Mika Haulo

A fix to prevent compiler switching the order of boolean expressions.

Revision history for this message
Mika Haulo (mhaulo) wrote :

Yep, you're right about that. r2633 should fix it.

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

@Phil The && operator isn't associative, so the compiler should never violate the evaluation order. Tons of code out there relies on short-circuiting for evaluation. Is this documented somewhere? I'd be pretty shocked if there was an optimizing compiler out there that would do that.

@Mika This all looks very good. I think we should keep the 'Remove' option in the context menu for the crate and playlist table models though and just make it not enabled. Ideally we could add a lock icon to the QAction so that there is some feedback that the option is locked. Also, as for drag-and-dropping onto the playlist or crate I think it would be more natural to just deny the drag-and-drop so it shows up as an 'X' as they hover over it instead of showing the "+" and then popping up a dialog box. What do you think?

Thanks!
RJ

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

Here are my thoughts on what needs fixing:

- Since you've worked on this (I'm guessing you based it on Mixxx 1.9) a feature to rename crates and playlists was added to trunk. The locking should affect renaming of the crates and playlists too.

- As I mentioned above, to reject drags to locked Playlists/Crates, just implement Crate/PlaylistFeature::dragMoveAcceptChild to return false if the crate/playlist is locked.

- I think the 'Remove' option for Crates and Playlists should be grayed out instead of omitted so that people don't wonder where it went.

- Similarly, the Remove option in the track-table view should be grayed out instead of omitted.

- Crates and playlists that are locked should be grayed out in the 'Add to Crate' and 'Add to Playlist' context menu's in the TrackTableView.

I'm totally up for helping fix these minor things if you're short on time. When it's ready we can merge it to trunk and it'll be part of the Mixxx 1.10 release. Thanks again for your work, Mika. Finally, sorry it took me so long to get a review done for this -- life changes were afoot :).

Cheers,
RJ

review: Needs Fixing
Revision history for this message
Mika Haulo (mhaulo) wrote :

On Friday 11 February 2011 05:02:54 RJ Ryan wrote:

> @Mika This all looks very good. I think we should keep the 'Remove' option
> in the context menu for the crate and playlist table models though and
> just make it not enabled. Ideally we could add a lock icon to the QAction
> so that there is some feedback that the option is locked. Also, as for
> drag-and-dropping onto the playlist or crate I think it would be more
> natural to just deny the drag-and-drop so it shows up as an 'X' as they
> hover over it instead of showing the "+" and then popping up a dialog box.
> What do you think?

Sure, that makes sense. I'll make some fixes soon.

> - Since you've worked on this (I'm guessing you based it on Mixxx 1.9) a
> feature to rename crates and playlists was added to trunk. The locking
> should affect renaming of the crates and playlists too.
>

There was some discussion if locking should affect renaming (and deleting) or
not. I decided to leave renaming enabled and wait for comments. But I guess it
makes more sense that locking really prevents all modifications, including
renaming.

--
MH

2634. By Mika Haulo

Review fixes

Revision history for this message
Mika Haulo (mhaulo) wrote :

On Feb 11, 2011, at 5:27 AM, RJ Ryan wrote:
>
> I'm totally up for helping fix these minor things if you're short on time.

Thanks RJ, I would actually appreciate some help at least on testing. I started in a new job on monday and the next couple of weeks feel a little bit hectic. I pushed some changes you proposed to my branch. Seems to be working on my machine, but if there's anything else to do or fix, go ahead and take control if you want.

--
MH

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

Hey Mika,

I tested everything out and merged your branch into the Mixxx trunk after fixing a couple minor issues.

Thanks so much for your work on this! I've credited you in the credits as "Mika Haulo". Please let me know if you'd like me to change it to something different. This code will see its first release in Mixxx 1.10.0.

RJ Ryan

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'mixxx/res/images/library/ic_library_locked.png'
2Binary files mixxx/res/images/library/ic_library_locked.png 1970-01-01 00:00:00 +0000 and mixxx/res/images/library/ic_library_locked.png 2011-02-15 19:56:49 +0000 differ
3=== modified file 'mixxx/res/images/templates/ic_template_library_and_preferences.svg'
4--- mixxx/res/images/templates/ic_template_library_and_preferences.svg 2010-11-22 15:24:51 +0000
5+++ mixxx/res/images/templates/ic_template_library_and_preferences.svg 2011-02-15 19:56:49 +0000
6@@ -13,7 +13,7 @@
7 inkscape:export-filename=""
8 style="display:inline"
9 sodipodi:docname="ic_template_library_and_preferences.svg"
10- inkscape:version="0.48.0 r9654"
11+ inkscape:version="0.48+devel r9889"
12 id="svg2"
13 height="32"
14 width="32"
15@@ -32,7 +32,7 @@
16 id="namedview26"
17 showgrid="true"
18 inkscape:zoom="10"
19- inkscape:cx="30.145312"
20+ inkscape:cx="11.595312"
21 inkscape:cy="29.45"
22 inkscape:window-x="1280"
23 inkscape:window-y="0"
24@@ -781,9 +781,9 @@
25 <rdf:RDF>
26 <rdf:Description
27 rdf:about="">
28- <dc:title></dc:title>
29- <dc:creator></dc:creator>
30- <dc:rights></dc:rights>
31+ <dc:title />
32+ <dc:creator />
33+ <dc:rights />
34 <dc:description />
35 <dc:format>image/svg+xml</dc:format>
36 <dc:language>en</dc:language>
37@@ -933,6 +933,29 @@
38 </g>
39 <g
40 inkscape:groupmode="layer"
41+ id="layer5"
42+ inkscape:label="locked.png"
43+ style="display:none"
44+ sodipodi:insensitive="true">
45+ <path
46+ style="fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
47+ d="m 9.5,14 c 0,0 0.012953,-3 0,-4.5 -0.012957,-1.5004487 0.4612148,-5.9608363 6.5,-6 6,-0.038912 6.495685,4.4997009 6.5,7 0.0026,1.5 0,3.5 0,3.5"
48+ id="path8889"
49+ inkscape:connector-curvature="0"
50+ sodipodi:nodetypes="csssc" />
51+ <path
52+ style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
53+ d="m 6,14 c -1,0 -2,1 -2,2 l 0,12 c 0,1 1,2 2,2 l 20,0 c 1,0 2,-1 2,-2 l 0,-12 c 0,-1 -1,-2 -2,-2 z M 9.03125,18 23,18 c 0.522127,0 0.997189,0.476401 0.997189,1 0,0.523599 -0.475062,1 -0.997189,1 L 9.03125,20 C 8.4410263,20 8,19.478827 8,19 8,18.521173 8.4261918,18 9.03125,18 z m 0,4 L 23,22 c 0.522127,0 0.997189,0.476401 0.997189,1 0,0.523599 -0.475062,1 -0.997189,1 L 9.03125,24 C 8.4896665,24 8,23.478827 8,23 8,22.521173 8.4836539,22 9.03125,22 z m 0,4 L 23,26 c 0.522127,0 0.997189,0.476401 0.997189,1 0,0.523599 -0.475062,1 -0.997189,1 L 9.03125,28 C 8.4799385,28 8,27.478827 8,27 8,26.521173 8.4473629,26 9.03125,26 z"
54+ id="rect8885"
55+ inkscape:connector-curvature="0"
56+ sodipodi:nodetypes="ssssssssscssscaccssscaccssscac" />
57+ <g
58+ id="g8895"
59+ style="fill:#0e232e;stroke:#0e232e;stroke-width:0.99776536"
60+ transform="matrix(1.0044843,0,0,1,26.044843,18)" />
61+ </g>
62+ <g
63+ inkscape:groupmode="layer"
64 id="layer39"
65 inkscape:label="library.png"
66 style="display:none"
67@@ -1225,7 +1248,7 @@
68 sodipodi:cy="7"
69 sodipodi:rx="5"
70 sodipodi:ry="5"
71- d="M 12,7 A 5,5 0 1 1 2,7 5,5 0 1 1 12,7 z"
72+ d="M 12,7 C 12,9.7614237 9.7614237,12 7,12 4.2385763,12 2,9.7614237 2,7 2,4.2385763 4.2385763,2 7,2 c 2.7614237,0 5,2.2385763 5,5 z"
73 transform="matrix(1.9222927,0,0,1.9222927,0.5439511,-13.456048)" />
74 <path
75 sodipodi:type="arc"
76@@ -1235,7 +1258,7 @@
77 sodipodi:cy="6"
78 sodipodi:rx="1.4933327"
79 sodipodi:ry="1.4933327"
80- d="M 7.4933327,6 A 1.4933327,1.4933327 0 1 1 4.5066673,6 1.4933327,1.4933327 0 1 1 7.4933327,6 z"
81+ d="M 7.4933327,6 C 7.4933327,6.8247449 6.8247449,7.4933327 6,7.4933327 5.1752551,7.4933327 4.5066673,6.8247449 4.5066673,6 4.5066673,5.1752551 5.1752551,4.5066673 6,4.5066673 6.8247449,4.5066673 7.4933327,5.1752551 7.4933327,6 z"
82 transform="matrix(1.3392862,0,0,1.3392862,5.9642827,-8.0357171)" />
83 </g>
84 </g>
85
86=== modified file 'mixxx/res/mixxx.qrc'
87--- mixxx/res/mixxx.qrc 2010-12-22 21:42:46 +0000
88+++ mixxx/res/mixxx.qrc 2011-02-15 19:56:49 +0000
89@@ -24,6 +24,7 @@
90 <file>images/library/ic_library_crates.png</file>
91 <file>images/library/ic_library_itunes.png</file>
92 <file>images/library/ic_library_library.png</file>
93+ <file>images/library/ic_library_locked.png</file>
94 <file>images/library/ic_library_playlist.png</file>
95 <file>images/library/ic_library_prepare.png</file>
96 <file>images/library/ic_library_promotracks.png</file>
97
98=== modified file 'mixxx/res/schema.xml'
99--- mixxx/res/schema.xml 2011-01-06 13:53:48 +0000
100+++ mixxx/res/schema.xml 2011-02-15 19:56:49 +0000
101@@ -228,4 +228,13 @@
102 );
103 </sql>
104 </revision>
105+ <revision version="10" min_compatible="3">
106+ <description>
107+ Playlist and crate locks
108+ </description>
109+ <sql>
110+ ALTER TABLE crates ADD COLUMN locked integer DEFAULT 0;
111+ ALTER TABLE playlists ADD COLUMN locked integer DEFAULT 0;
112+ </sql>
113+ </revision>
114 </schema>
115
116=== modified file 'mixxx/src/library/cratefeature.cpp'
117--- mixxx/src/library/cratefeature.cpp 2011-01-13 18:40:56 +0000
118+++ mixxx/src/library/cratefeature.cpp 2011-02-15 19:56:49 +0000
119@@ -33,7 +33,11 @@
120
121 m_pRenameCrateAction = new QAction(tr("Rename"),this);
122 connect(m_pRenameCrateAction, SIGNAL(triggered()),
123- this, SLOT(slotRenameCrate()));
124+ this, SLOT(slotRenameCrate()));
125+
126+ m_pLockCrateAction = new QAction(tr("Lock"),this);
127+ connect(m_pLockCrateAction, SIGNAL(triggered()),
128+ this, SLOT(slotToggleCrateLock()));
129
130 m_pImportPlaylistAction = new QAction(tr("Import Playlist"),this);
131 connect(m_pImportPlaylistAction, SIGNAL(triggered()),
132@@ -55,8 +59,18 @@
133 QModelIndex ind = m_crateListTableModel.index(row, idColumn);
134 QString crate_name = m_crateListTableModel.data(ind).toString();
135 TreeItem *playlist_item = new TreeItem(crate_name, crate_name, this, rootItem);
136+ CrateDAO crateDao = m_pTrackCollection->getCrateDAO();
137+ int crateID = crateDao.getCrateIdByName(crate_name);
138+ bool locked = crateDao.isCrateLocked(crateID);
139+
140+ if (locked) {
141+ playlist_item->setIcon(QIcon(":/images/library/ic_library_locked.png"));
142+ }
143+ else {
144+ playlist_item->setIcon(QIcon());
145+ }
146+
147 rootItem->appendChild(playlist_item);
148-
149 }
150 m_childModel.setRootItem(rootItem);
151
152@@ -67,6 +81,7 @@
153 delete m_pCreateCrateAction;
154 delete m_pDeleteCrateAction;
155 delete m_pRenameCrateAction;
156+ delete m_pLockCrateAction;
157 delete m_pImportPlaylistAction;
158
159 }
160@@ -101,8 +116,10 @@
161 qDebug() << "CrateFeature::dropAcceptChild adding track"
162 << trackId << "to crate" << crateId;
163
164+ CrateDAO& crateDao = m_pTrackCollection->getCrateDAO();
165+
166 if (trackId >= 0)
167- return m_pTrackCollection->getCrateDAO().addTrackToCrate(trackId, crateId);
168+ return crateDao.addTrackToCrate(trackId, crateId);
169 return false;
170 }
171
172@@ -112,7 +129,12 @@
173
174 bool CrateFeature::dragMoveAcceptChild(const QModelIndex& index, QUrl url) {
175 //TODO: Filter by supported formats regex and reject anything that doesn't match.
176- return true;
177+ QString crateName = index.data().toString();
178+ CrateDAO& crateDao = m_pTrackCollection->getCrateDAO();
179+ int crateId = crateDao.getCrateIdByName(crateName);
180+ bool locked = crateDao.isCrateLocked(crateId);
181+
182+ return !locked;
183 }
184
185 void CrateFeature::bindWidget(WLibrarySidebar* sidebarWidget,
186@@ -152,12 +174,27 @@
187 void CrateFeature::onRightClickChild(const QPoint& globalPos, QModelIndex index) {
188 //Save the model index so we can get it in the action slots...
189 m_lastRightClickedIndex = index;
190+ QString crateName = index.data().toString();
191+ CrateDAO& crateDAO = m_pTrackCollection->getCrateDAO();
192+ int crateId = crateDAO.getCrateIdByName(crateName);
193+ bool locked = crateDAO.isCrateLocked(crateId);
194+ m_pDeleteCrateAction->setEnabled(!locked);
195+ m_pRenameCrateAction->setEnabled(!locked);
196+
197+ if (locked) {
198+ m_pLockCrateAction->setText(tr("Unlock"));
199+
200+ }
201+ else {
202+ m_pLockCrateAction->setText(tr("Lock"));
203+ }
204
205 QMenu menu(NULL);
206 menu.addAction(m_pCreateCrateAction);
207 menu.addSeparator();
208 menu.addAction(m_pRenameCrateAction);
209 menu.addAction(m_pDeleteCrateAction);
210+ menu.addAction(m_pLockCrateAction);
211 menu.addSeparator();
212 menu.addAction(m_pImportPlaylistAction);
213 menu.exec(globalPos);
214@@ -222,9 +259,12 @@
215
216 void CrateFeature::slotDeleteCrate() {
217 QString crateName = m_lastRightClickedIndex.data().toString();
218- int crateId = m_pTrackCollection->getCrateDAO().getCrateIdByName(crateName);
219+ CrateDAO crateDao = m_pTrackCollection->getCrateDAO();
220+ int crateId = crateDao.getCrateIdByName(crateName);
221+ bool locked = crateDao.isCrateLocked(crateId);
222+ bool deleted = crateDao.deleteCrate(crateId);
223
224- if (m_pTrackCollection->getCrateDAO().deleteCrate(crateId)) {
225+ if (deleted) {
226 clearChildModel();
227 m_crateListTableModel.select();
228 constructChildModel();
229@@ -282,6 +322,29 @@
230 qDebug() << "Failed to rename crateId" << crateId;
231 }
232 }
233+
234+void CrateFeature::slotToggleCrateLock()
235+{
236+ QString crateName = m_lastRightClickedIndex.data().toString();
237+ CrateDAO& crateDAO = m_pTrackCollection->getCrateDAO();
238+ int crateId = crateDAO.getCrateIdByName(crateName);
239+ bool locked = !crateDAO.isCrateLocked(crateId);
240+
241+ if (!crateDAO.setCrateLocked(crateId, locked)) {
242+ qDebug() << "Failed to toggle lock of crateId " << crateId;
243+ }
244+
245+ TreeItem* crateItem = m_childModel.getItem(m_lastRightClickedIndex);
246+
247+ if (locked) {
248+ crateItem->setIcon(QIcon(":/images/library/ic_library_locked.png"));
249+ }
250+ else {
251+ crateItem->setIcon(QIcon());
252+ }
253+}
254+
255+
256 /**
257 * Purpose: When inserting or removing playlists,
258 * we require the sidebar model not to reset.
259@@ -291,18 +354,45 @@
260 {
261 QList<TreeItem*> data_list;
262 int idColumn = m_crateListTableModel.record().indexOf("name");
263+<<<<<<< TREE
264 //Access the invisible root item
265 TreeItem* root = m_childModel.getItem(QModelIndex());
266
267+=======
268+ CrateDAO crateDao = m_pTrackCollection->getCrateDAO();
269+
270+>>>>>>> MERGE-SOURCE
271 for (int row = 0; row < m_crateListTableModel.rowCount(); ++row) {
272 QModelIndex ind = m_crateListTableModel.index(row, idColumn);
273 QString crate_name = m_crateListTableModel.data(ind).toString();
274+<<<<<<< TREE
275 //Create the TreeItem whose parent is the invisible root item
276 TreeItem* item = new TreeItem(crate_name, crate_name, this, root);
277 data_list.append(item);
278 }
279 //Append all the newly created TreeItems in a dynamic way to the childmodel
280 m_childModel.insertRows(data_list, 0, m_crateListTableModel.rowCount());
281+=======
282+ data_list.append(crate_name);
283+ }
284+
285+ m_childModel.insertRows(data_list, 0, m_crateListTableModel.rowCount());
286+
287+ for (int row = 0; row < m_childModel.rowCount(); ++row) {
288+ QModelIndex ind = m_childModel.index(row, 0);
289+ QString crate_name = m_childModel.data(ind, Qt::DisplayRole).toString();
290+ int crateID = crateDao.getCrateIdByName(crate_name);
291+ bool locked = crateDao.isCrateLocked(crateID);
292+ TreeItem* playlist_item = m_childModel.getItem(ind);
293+
294+ if (locked) {
295+ playlist_item->setIcon(QIcon(":/images/library/ic_library_locked.png"));
296+ }
297+ else {
298+ playlist_item->setIcon(QIcon());
299+ }
300+ }
301+>>>>>>> MERGE-SOURCE
302 }
303 /**
304 * Clears the child model dynamically
305
306=== modified file 'mixxx/src/library/cratefeature.h'
307--- mixxx/src/library/cratefeature.h 2011-01-01 14:49:21 +0000
308+++ mixxx/src/library/cratefeature.h 2011-02-15 19:56:49 +0000
309@@ -43,6 +43,7 @@
310 void slotCreateCrate();
311 void slotDeleteCrate();
312 void slotRenameCrate();
313+ void slotToggleCrateLock();
314 void slotImportPlaylist();
315
316 private:
317@@ -53,6 +54,7 @@
318 QAction *m_pCreateCrateAction;
319 QAction *m_pDeleteCrateAction;
320 QAction *m_pRenameCrateAction;
321+ QAction *m_pLockCrateAction;
322 QAction *m_pImportPlaylistAction;
323 QSqlTableModel m_crateListTableModel;
324 CrateTableModel m_crateTableModel;
325
326=== modified file 'mixxx/src/library/cratetablemodel.cpp'
327--- mixxx/src/library/cratetablemodel.cpp 2010-11-16 22:08:45 +0000
328+++ mixxx/src/library/cratetablemodel.cpp 2011-02-15 19:56:49 +0000
329@@ -117,29 +117,38 @@
330 }
331
332 void CrateTableModel::removeTracks(const QModelIndexList& indices) {
333- const int trackIdIndex = fieldIndex(LIBRARYTABLE_ID);
334-
335- QList<int> trackIds;
336- foreach (QModelIndex index, indices) {
337- int trackId = index.sibling(index.row(), fieldIndex(LIBRARYTABLE_ID)).data().toInt();
338- trackIds.append(trackId);
339- }
340-
341 CrateDAO& crateDao = m_pTrackCollection->getCrateDAO();
342- foreach (int trackId, trackIds) {
343- crateDao.removeTrackFromCrate(trackId, m_iCrateId);
344+ bool locked = crateDao.isCrateLocked(m_iCrateId);
345+
346+ if (!locked) {
347+ const int trackIdIndex = fieldIndex(LIBRARYTABLE_ID);
348+
349+ QList<int> trackIds;
350+ foreach (QModelIndex index, indices) {
351+ int trackId = index.sibling(index.row(), fieldIndex(LIBRARYTABLE_ID)).data().toInt();
352+ trackIds.append(trackId);
353+ }
354+
355+ foreach (int trackId, trackIds) {
356+ crateDao.removeTrackFromCrate(trackId, m_iCrateId);
357+ }
358+
359+ select();
360 }
361-
362- select();
363 }
364
365 void CrateTableModel::removeTrack(const QModelIndex& index) {
366- const int trackIdIndex = fieldIndex(LIBRARYTABLE_ID);
367- int trackId = index.sibling(index.row(), trackIdIndex).data().toInt();
368- if (m_pTrackCollection->getCrateDAO().removeTrackFromCrate(trackId, m_iCrateId)) {
369- select();
370- } else {
371- // TODO(XXX) feedback
372+ CrateDAO& crateDao = m_pTrackCollection->getCrateDAO();
373+ bool locked = crateDao.isCrateLocked(m_iCrateId);
374+
375+ if (!locked) {
376+ const int trackIdIndex = fieldIndex(LIBRARYTABLE_ID);
377+ int trackId = index.sibling(index.row(), trackIdIndex).data().toInt();
378+ if (m_pTrackCollection->getCrateDAO().removeTrackFromCrate(trackId, m_iCrateId)) {
379+ select();
380+ } else {
381+ // TODO(XXX) feedback
382+ }
383 }
384 }
385
386@@ -235,6 +244,18 @@
387 }
388
389 TrackModel::CapabilitiesFlags CrateTableModel::getCapabilities() const {
390- return TRACKMODELCAPS_RECEIVEDROPS | TRACKMODELCAPS_ADDTOPLAYLIST |
391- TRACKMODELCAPS_ADDTOCRATE | TRACKMODELCAPS_ADDTOAUTODJ;
392+
393+ CapabilitiesFlags caps = TRACKMODELCAPS_RECEIVEDROPS |
394+ TRACKMODELCAPS_ADDTOPLAYLIST |
395+ TRACKMODELCAPS_ADDTOCRATE |
396+ TRACKMODELCAPS_ADDTOAUTODJ;
397+
398+ CrateDAO& crateDao = m_pTrackCollection->getCrateDAO();
399+ bool locked = crateDao.isCrateLocked(m_iCrateId);
400+
401+ if (locked) {
402+ caps |= TRACKMODELCAPS_LOCKED;
403+ }
404+
405+ return caps;
406 }
407
408=== modified file 'mixxx/src/library/dao/cratedao.cpp'
409--- mixxx/src/library/dao/cratedao.cpp 2010-12-27 10:08:14 +0000
410+++ mixxx/src/library/dao/cratedao.cpp 2011-02-15 19:56:49 +0000
411@@ -70,6 +70,51 @@
412 return true;
413 }
414
415+bool CrateDAO::setCrateLocked(int crateId, bool locked) {
416+ qDebug() << "setCrateLocked()";
417+
418+ // SQLite3 doesn't support boolean value. Using integer instead.
419+ int lock = 0;
420+
421+ if (locked) {
422+ lock = 1;
423+ }
424+
425+ Q_ASSERT(m_database.transaction());
426+ QSqlQuery query;
427+ query.prepare("UPDATE " CRATE_TABLE " SET locked = :lock WHERE id = :id");
428+ query.bindValue(":lock", lock);
429+ query.bindValue(":id", crateId);
430+
431+ if (!query.exec()) {
432+ qDebug() << query.executedQuery() << query.lastError();
433+ Q_ASSERT(m_database.rollback());
434+ return false;
435+ }
436+
437+ Q_ASSERT(m_database.commit());
438+ return true;
439+}
440+
441+bool CrateDAO::isCrateLocked(int crateId) {
442+ qDebug() << "isCrateLocked()";
443+
444+ QSqlQuery query;
445+ query.prepare("SELECT locked FROM " CRATE_TABLE " WHERE id = :id");
446+ query.bindValue(":id", crateId);
447+
448+ if (query.exec()) {
449+ if (query.next()) {
450+ int lockValue = query.value(0).toInt();
451+ return lockValue == 1;
452+ }
453+ } else {
454+ qDebug() << query.lastError();
455+ }
456+
457+ return false;
458+}
459+
460 bool CrateDAO::deleteCrate(int crateId) {
461 Q_ASSERT(m_database.transaction());
462
463
464=== modified file 'mixxx/src/library/dao/cratedao.h'
465--- mixxx/src/library/dao/cratedao.h 2010-12-27 10:08:14 +0000
466+++ mixxx/src/library/dao/cratedao.h 2011-02-15 19:56:49 +0000
467@@ -24,6 +24,8 @@
468 bool createCrate(const QString& name);
469 bool deleteCrate(int crateId);
470 bool renameCrate(int crateId, const QString& newName);
471+ bool setCrateLocked(int crateId, bool locked);
472+ bool isCrateLocked(int crateId);
473 int getCrateIdByName(const QString& name);
474 int getCrateId(int position);
475 QString crateName(int crateId);
476
477=== modified file 'mixxx/src/library/dao/playlistdao.cpp'
478--- mixxx/src/library/dao/playlistdao.cpp 2010-12-27 10:08:14 +0000
479+++ mixxx/src/library/dao/playlistdao.cpp 2011-02-15 19:56:49 +0000
480@@ -166,6 +166,51 @@
481 }
482
483
484+bool PlaylistDAO::setPlaylistLocked(int playlistId, bool locked) {
485+ qDebug() << "setPlaylistLocked()";
486+
487+ // SQLite3 doesn't support boolean value. Using integer instead.
488+ int lock = 0;
489+
490+ if (locked) {
491+ lock = 1;
492+ }
493+
494+ Q_ASSERT(m_database.transaction());
495+ QSqlQuery query;
496+ query.prepare("UPDATE Playlists SET locked = :lock WHERE id = :id");
497+ query.bindValue(":lock", lock);
498+ query.bindValue(":id", playlistId);
499+
500+ if (!query.exec()) {
501+ qDebug() << query.executedQuery() << query.lastError();
502+ Q_ASSERT(m_database.rollback());
503+ return false;
504+ }
505+
506+ Q_ASSERT(m_database.commit());
507+ return true;
508+}
509+
510+bool PlaylistDAO::isPlaylistLocked(int playlistId) {
511+ qDebug() << "isPlaylistLocked()";
512+
513+ QSqlQuery query;
514+ query.prepare("SELECT locked FROM Playlists WHERE id = :id");
515+ query.bindValue(":id", playlistId);
516+
517+ if (query.exec()) {
518+ if (query.next()) {
519+ int lockValue = query.value(0).toInt();
520+ return lockValue == 1;
521+ }
522+ } else {
523+ qDebug() << query.lastError();
524+ }
525+
526+ return false;
527+}
528+
529 /** Append a track to a playlist */
530 void PlaylistDAO::appendTrackToPlaylist(int trackId, int playlistId)
531 {
532
533=== modified file 'mixxx/src/library/dao/playlistdao.h'
534--- mixxx/src/library/dao/playlistdao.h 2010-12-27 10:08:14 +0000
535+++ mixxx/src/library/dao/playlistdao.h 2011-02-15 19:56:49 +0000
536@@ -19,6 +19,10 @@
537 void deletePlaylist(int playlistId);
538 /** Rename a playlist */
539 void renamePlaylist(int playlistId, const QString& newName);
540+ /** Lock or unlock a playlist */
541+ bool setPlaylistLocked(int playlistId, bool locked);
542+ /** Find out the state of a playlist lock */
543+ bool isPlaylistLocked(int playlistId);
544 /** Append a track to a playlist */
545 void appendTrackToPlaylist(int trackId, int playlistId);
546 /** Find out how many playlists exist. */
547
548=== modified file 'mixxx/src/library/playlistfeature.cpp'
549--- mixxx/src/library/playlistfeature.cpp 2011-01-13 18:40:56 +0000
550+++ mixxx/src/library/playlistfeature.cpp 2011-02-15 19:56:49 +0000
551@@ -38,6 +38,10 @@
552 connect(m_pRenamePlaylistAction, SIGNAL(triggered()),
553 this, SLOT(slotRenamePlaylist()));
554
555+ m_pLockPlaylistAction = new QAction(tr("Lock"),this);
556+ connect(m_pLockPlaylistAction, SIGNAL(triggered()),
557+ this, SLOT(slotTogglePlaylistLock()));
558+
559 m_pImportPlaylistAction = new QAction(tr("Import Playlist"),this);
560 connect(m_pImportPlaylistAction, SIGNAL(triggered()),
561 this, SLOT(slotImportPlaylist()));
562@@ -61,6 +65,17 @@
563 QModelIndex ind = m_playlistTableModel.index(row, idColumn);
564 QString playlist_name = m_playlistTableModel.data(ind).toString();
565 TreeItem *playlist_item = new TreeItem(playlist_name, playlist_name, this, rootItem);
566+ int playlistID = m_playlistDao.getPlaylistIdFromName(playlist_name);
567+ bool locked = m_playlistDao.isPlaylistLocked(playlistID);
568+
569+ if (locked) {
570+ playlist_item->setIcon(QIcon(":/images/library/ic_library_locked.png"));
571+ }
572+ else {
573+ playlist_item->setIcon(QIcon());
574+ }
575+
576+
577 rootItem->appendChild(playlist_item);
578
579 }
580@@ -73,6 +88,7 @@
581 delete m_pDeletePlaylistAction;
582 delete m_pImportPlaylistAction;
583 delete m_pRenamePlaylistAction;
584+ delete m_pLockPlaylistAction;
585 }
586
587 QVariant PlaylistFeature::title() {
588@@ -120,13 +136,26 @@
589 void PlaylistFeature::onRightClickChild(const QPoint& globalPos, QModelIndex index) {
590 //Save the model index so we can get it in the action slots...
591 m_lastRightClickedIndex = index;
592-
593+ QString playlistName = index.data().toString();
594+ int playlistId = m_playlistDao.getPlaylistIdFromName(playlistName);
595+ bool locked = m_playlistDao.isPlaylistLocked(playlistId);
596+ m_pDeletePlaylistAction->setEnabled(!locked);
597+ m_pRenamePlaylistAction->setEnabled(!locked);
598+
599+ if (locked) {
600+ m_pLockPlaylistAction->setText(tr("Unlock"));
601+ }
602+ else {
603+ m_pLockPlaylistAction->setText(tr("Lock"));
604+ }
605+
606 //Create the right-click menu
607 QMenu menu(NULL);
608 menu.addAction(m_pCreatePlaylistAction);
609 menu.addSeparator();
610 menu.addAction(m_pRenamePlaylistAction);
611 menu.addAction(m_pDeletePlaylistAction);
612+ menu.addAction(m_pLockPlaylistAction);
613 menu.addSeparator();
614 menu.addAction(m_pImportPlaylistAction);
615 menu.exec(globalPos);
616@@ -235,20 +264,43 @@
617 m_pPlaylistTableModel->setPlaylist(playlistId);
618 }
619
620+
621+void PlaylistFeature::slotTogglePlaylistLock()
622+{
623+ QString playlistName = m_lastRightClickedIndex.data().toString();
624+ int playlistId = m_playlistDao.getPlaylistIdFromName(playlistName);
625+ bool locked = !m_playlistDao.isPlaylistLocked(playlistId);
626+
627+ if (!m_playlistDao.setPlaylistLocked(playlistId, locked)) {
628+ qDebug() << "Failed to toggle lock of playlistId " << playlistId;
629+ }
630+
631+ TreeItem* playlistItem = m_childModel.getItem(m_lastRightClickedIndex);
632+
633+ if (locked) {
634+ playlistItem->setIcon(QIcon(":/images/library/ic_library_locked.png"));
635+ }
636+ else {
637+ playlistItem->setIcon(QIcon());
638+ }
639+}
640+
641 void PlaylistFeature::slotDeletePlaylist()
642 {
643 //qDebug() << "slotDeletePlaylist() row:" << m_lastRightClickedIndex.data();
644- if (m_lastRightClickedIndex.isValid()) {
645- int playlistId = m_playlistDao.getPlaylistIdFromName(m_lastRightClickedIndex.data().toString());
646+ int playlistId = m_playlistDao.getPlaylistIdFromName(m_lastRightClickedIndex.data().toString());
647+
648+ if (m_lastRightClickedIndex.isValid() &&
649+ !m_playlistDao.isPlaylistLocked(playlistId)) {
650 Q_ASSERT(playlistId >= 0);
651
652 clearChildModel();
653 m_playlistDao.deletePlaylist(playlistId);
654 m_playlistTableModel.select();
655 constructChildModel();
656+ emit(featureUpdated());
657 }
658-
659- emit(featureUpdated());
660+
661 }
662
663 bool PlaylistFeature::dropAccept(QUrl url) {
664@@ -295,7 +347,12 @@
665
666 bool PlaylistFeature::dragMoveAcceptChild(const QModelIndex& index, QUrl url) {
667 //TODO: Filter by supported formats regex and reject anything that doesn't match.
668- return true;
669+
670+ QString playlistName = index.data().toString();
671+ int playlistId = m_playlistDao.getPlaylistIdFromName(playlistName);
672+ bool locked = m_playlistDao.isPlaylistLocked(playlistId);
673+
674+ return !locked;
675 }
676
677 TreeItemModel* PlaylistFeature::getChildModel() {
678@@ -316,12 +373,47 @@
679 for (int row = 0; row < m_playlistTableModel.rowCount(); ++row) {
680 QModelIndex ind = m_playlistTableModel.index(row, idColumn);
681 QString playlist_name = m_playlistTableModel.data(ind).toString();
682+<<<<<<< TREE
683 //Create the TreeItem whose parent is the invisible root item
684 TreeItem* item = new TreeItem(playlist_name, playlist_name, this, root );
685 data_list.append(item);
686+=======
687+ data_list.insert(row,playlist_name);
688+
689+ int playlistID = m_playlistDao.getPlaylistIdFromName(playlist_name);
690+ bool locked = m_playlistDao.isPlaylistLocked(playlistID);
691+ TreeItem* playlist_item = m_childModel.getItem(ind);
692+
693+ if (locked) {
694+ playlist_item->setIcon(QIcon(":/images/library/ic_library_locked.png"));
695+ }
696+ else {
697+ playlist_item->setIcon(QIcon());
698+ }
699+>>>>>>> MERGE-SOURCE
700 }
701+<<<<<<< TREE
702 //Append all the newly created TreeItems in a dynamic way to the childmodel
703 m_childModel.insertRows(data_list, 0, m_playlistTableModel.rowCount());
704+=======
705+
706+ m_childModel.insertRows(data_list, 0, m_playlistTableModel.rowCount());
707+
708+ for (int row = 0; row < m_childModel.rowCount(); ++row) {
709+ QModelIndex ind = m_childModel.index(row, 0);
710+ QString playlist_name = m_childModel.data(ind, Qt::DisplayRole).toString();
711+ int playlistID = m_playlistDao.getPlaylistIdFromName(playlist_name);
712+ bool locked = m_playlistDao.isPlaylistLocked(playlistID);
713+ TreeItem* playlist_item = m_childModel.getItem(ind);
714+
715+ if (locked) {
716+ playlist_item->setIcon(QIcon(":/images/library/ic_library_locked.png"));
717+ }
718+ else {
719+ playlist_item->setIcon(QIcon());
720+ }
721+ }
722+>>>>>>> MERGE-SOURCE
723 }
724 /**
725 * Clears the child model dynamically, but the invisible root item remains
726
727=== modified file 'mixxx/src/library/playlistfeature.h'
728--- mixxx/src/library/playlistfeature.h 2010-12-31 15:31:04 +0000
729+++ mixxx/src/library/playlistfeature.h 2011-02-15 19:56:49 +0000
730@@ -47,11 +47,12 @@
731
732 void slotCreatePlaylist();
733 void slotDeletePlaylist();
734- void slotRenamePlaylist();
735+ void slotRenamePlaylist();
736+ void slotTogglePlaylistLock();
737 void slotImportPlaylist();
738
739 private:
740- void constructChildModel();
741+ void constructChildModel();
742 void clearChildModel();
743
744 PlaylistTableModel* m_pPlaylistTableModel;
745@@ -60,6 +61,7 @@
746 QAction *m_pCreatePlaylistAction;
747 QAction *m_pDeletePlaylistAction;
748 QAction *m_pRenamePlaylistAction;
749+ QAction *m_pLockPlaylistAction;
750 QAction *m_pImportPlaylistAction;
751 QSqlTableModel m_playlistTableModel;
752 QModelIndex m_lastRightClickedIndex;
753
754=== modified file 'mixxx/src/library/playlisttablemodel.cpp'
755--- mixxx/src/library/playlisttablemodel.cpp 2010-11-17 21:04:30 +0000
756+++ mixxx/src/library/playlisttablemodel.cpp 2011-02-15 19:56:49 +0000
757@@ -139,30 +139,38 @@
758
759 void PlaylistTableModel::removeTrack(const QModelIndex& index)
760 {
761- const int positionColumnIndex = fieldIndex(PLAYLISTTRACKSTABLE_POSITION);
762- int position = index.sibling(index.row(), positionColumnIndex).data().toInt();
763- m_playlistDao.removeTrackFromPlaylist(m_iPlaylistId, position);
764- select(); //Repopulate the data model.
765+ bool locked = m_playlistDao.isPlaylistLocked(m_iPlaylistId);
766+
767+ if (!locked) {
768+ const int positionColumnIndex = fieldIndex(PLAYLISTTRACKSTABLE_POSITION);
769+ int position = index.sibling(index.row(), positionColumnIndex).data().toInt();
770+ m_playlistDao.removeTrackFromPlaylist(m_iPlaylistId, position);
771+ select(); //Repopulate the data model.
772+ }
773 }
774
775 void PlaylistTableModel::removeTracks(const QModelIndexList& indices) {
776- const int positionColumnIndex = fieldIndex(PLAYLISTTRACKSTABLE_POSITION);
777-
778- QList<int> trackPositions;
779- foreach (QModelIndex index, indices) {
780- int trackPosition = index.sibling(index.row(), positionColumnIndex).data().toInt();
781- trackPositions.append(trackPosition);
782- }
783-
784- qSort(trackPositions);
785- QListIterator<int> iterator(trackPositions);
786- iterator.toBack();
787-
788- while (iterator.hasPrevious()) {
789- m_playlistDao.removeTrackFromPlaylist(m_iPlaylistId, iterator.previous());
790- }
791-
792- select();
793+ bool locked = m_playlistDao.isPlaylistLocked(m_iPlaylistId);
794+
795+ if (!locked) {
796+ const int positionColumnIndex = fieldIndex(PLAYLISTTRACKSTABLE_POSITION);
797+
798+ QList<int> trackPositions;
799+ foreach (QModelIndex index, indices) {
800+ int trackPosition = index.sibling(index.row(), positionColumnIndex).data().toInt();
801+ trackPositions.append(trackPosition);
802+ }
803+
804+ qSort(trackPositions);
805+ QListIterator<int> iterator(trackPositions);
806+ iterator.toBack();
807+
808+ while (iterator.hasPrevious()) {
809+ m_playlistDao.removeTrackFromPlaylist(m_iPlaylistId, iterator.previous());
810+ }
811+
812+ select();
813+ }
814 }
815
816 void PlaylistTableModel::moveTrack(const QModelIndex& sourceIndex, const QModelIndex& destIndex)
817@@ -352,5 +360,10 @@
818 if (m_iPlaylistId != m_playlistDao.getPlaylistIdFromName(AUTODJ_TABLE))
819 caps |= TRACKMODELCAPS_ADDTOAUTODJ;
820
821+ bool locked = m_playlistDao.isPlaylistLocked(m_iPlaylistId);
822+
823+ if (locked)
824+ caps |= TRACKMODELCAPS_LOCKED;
825+
826 return caps;
827 }
828
829=== modified file 'mixxx/src/library/sidebarmodel.cpp'
830--- mixxx/src/library/sidebarmodel.cpp 2011-02-12 19:25:05 +0000
831+++ mixxx/src/library/sidebarmodel.cpp 2011-02-15 19:56:49 +0000
832@@ -188,10 +188,14 @@
833 }
834 }
835 else {
836+ TreeItem* tree_item = (TreeItem*)index.internalPointer();
837+
838 if (role == Qt::DisplayRole) {
839- TreeItem* tree_item = (TreeItem*)index.internalPointer();
840 return tree_item->data();
841- }
842+ }
843+ else if (role == Qt::DecorationRole) {
844+ return tree_item->getIcon();
845+ }
846
847 }
848 }
849
850=== modified file 'mixxx/src/library/trackcollection.cpp'
851--- mixxx/src/library/trackcollection.cpp 2010-12-22 21:23:09 +0000
852+++ mixxx/src/library/trackcollection.cpp 2011-02-15 19:56:49 +0000
853@@ -68,7 +68,7 @@
854 return false;
855 }
856
857- int requiredSchemaVersion = 9;
858+ int requiredSchemaVersion = 10;
859 if (!SchemaManager::upgradeToSchemaVersion(m_pConfig, m_db,
860 requiredSchemaVersion)) {
861 QMessageBox::warning(0, tr("Cannot upgrade database schema"),
862
863=== modified file 'mixxx/src/library/trackmodel.h'
864--- mixxx/src/library/trackmodel.h 2010-11-16 21:01:45 +0000
865+++ mixxx/src/library/trackmodel.h 2011-02-15 19:56:49 +0000
866@@ -29,6 +29,7 @@
867 TRACKMODELCAPS_ADDTOPLAYLIST = 0x0004,
868 TRACKMODELCAPS_ADDTOCRATE = 0x0008,
869 TRACKMODELCAPS_ADDTOAUTODJ = 0x0010,
870+ TRACKMODELCAPS_LOCKED = 0x0020,
871 //0x0004
872 };
873
874
875=== modified file 'mixxx/src/library/treeitem.cpp'
876--- mixxx/src/library/treeitem.cpp 2011-01-13 18:40:56 +0000
877+++ mixxx/src/library/treeitem.cpp 2011-02-15 19:56:49 +0000
878@@ -126,3 +126,15 @@
879 m_dataPath = data_path.toString();
880 return true;
881 }
882+
883+
884+QIcon TreeItem::getIcon()
885+{
886+ return m_icon;
887+}
888+
889+
890+void TreeItem::setIcon(const QIcon& icon)
891+{
892+ m_icon = icon;
893+}
894
895=== modified file 'mixxx/src/library/treeitem.h'
896--- mixxx/src/library/treeitem.h 2011-02-11 18:35:24 +0000
897+++ mixxx/src/library/treeitem.h 2011-02-15 19:56:49 +0000
898@@ -44,10 +44,16 @@
899 bool isPlaylist() const;
900 /** returns true if we have an inner node **/
901 bool isFolder() const;
902+<<<<<<< TREE
903
904+=======
905+>>>>>>> MERGE-SOURCE
906 /* Returns the Library feature object to which an item belongs to */
907 LibraryFeature* getFeature();
908
909+ void setIcon(const QIcon& icon);
910+ QIcon getIcon();
911+
912 private:
913 QList<TreeItem*> m_childItems;
914 QString m_dataPath;
915@@ -55,6 +61,7 @@
916 LibraryFeature* m_feature;
917
918 TreeItem *m_parentItem;
919+ QIcon m_icon;
920 };
921
922 #endif
923
924=== modified file 'mixxx/src/library/treeitemmodel.cpp'
925--- mixxx/src/library/treeitemmodel.cpp 2011-01-19 17:47:08 +0000
926+++ mixxx/src/library/treeitemmodel.cpp 2011-02-15 19:56:49 +0000
927@@ -45,7 +45,7 @@
928 }
929
930 QVariant TreeItemModel::data(const QModelIndex &index, int role) const
931- {
932+ {
933 if (!index.isValid())
934 return QVariant();
935
936@@ -53,7 +53,7 @@
937 return QVariant();
938
939 TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
940-
941+
942 return item->data();
943 }
944
945
946=== modified file 'mixxx/src/widget/wtracktableview.cpp'
947--- mixxx/src/widget/wtracktableview.cpp 2011-02-11 18:35:24 +0000
948+++ mixxx/src/widget/wtracktableview.cpp 2011-02-15 19:56:49 +0000
949@@ -338,20 +338,24 @@
950
951 if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOPLAYLIST)) {
952 m_pPlaylistMenu->clear();
953-
954 PlaylistDAO& playlistDao = m_pTrackCollection->getPlaylistDAO();
955 int numPlaylists = playlistDao.playlistCount();
956+
957 for (int i = 0; i < numPlaylists; ++i) {
958 int iPlaylistId = playlistDao.getPlaylistId(i);
959- if (playlistDao.isHidden(iPlaylistId))
960- continue;
961- QString playlistName = playlistDao.getPlaylistName(iPlaylistId);
962- // No leak because making the menu the parent means they will be
963- // auto-deleted
964- QAction* pAction = new QAction(playlistName, m_pPlaylistMenu);
965- m_pPlaylistMenu->addAction(pAction);
966- m_playlistMapper.setMapping(pAction, iPlaylistId);
967- connect(pAction, SIGNAL(triggered()), &m_playlistMapper, SLOT(map()));
968+
969+ if (!playlistDao.isHidden(iPlaylistId)) {
970+
971+ QString playlistName = playlistDao.getPlaylistName(iPlaylistId);
972+ // No leak because making the menu the parent means they will be
973+ // auto-deleted
974+ QAction* pAction = new QAction(playlistName, m_pPlaylistMenu);
975+ bool locked = playlistDao.isPlaylistLocked(iPlaylistId);
976+ pAction->setEnabled(!locked);
977+ m_pPlaylistMenu->addAction(pAction);
978+ m_playlistMapper.setMapping(pAction, iPlaylistId);
979+ connect(pAction, SIGNAL(triggered()), &m_playlistMapper, SLOT(map()));
980+ }
981 }
982
983 m_pMenu->addMenu(m_pPlaylistMenu);
984@@ -359,7 +363,6 @@
985
986 if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOCRATE)) {
987 m_pCrateMenu->clear();
988-
989 CrateDAO& crateDao = m_pTrackCollection->getCrateDAO();
990 int numCrates = crateDao.crateCount();
991 for (int i = 0; i < numCrates; ++i) {
992@@ -367,6 +370,8 @@
993 // No leak because making the menu the parent means they will be
994 // auto-deleted
995 QAction* pAction = new QAction(crateDao.crateName(iCrateId), m_pCrateMenu);
996+ bool locked = crateDao.isCrateLocked(iCrateId);
997+ pAction->setEnabled(!locked);
998 m_pCrateMenu->addAction(pAction);
999 m_crateMapper.setMapping(pAction, iCrateId);
1000 connect(pAction, SIGNAL(triggered()), &m_crateMapper, SLOT(map()));
1001@@ -375,9 +380,10 @@
1002 m_pMenu->addMenu(m_pCrateMenu);
1003 }
1004
1005+ bool locked = modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOCKED);
1006+ m_pRemoveAct->setEnabled(!locked);
1007 m_pMenu->addSeparator();
1008 m_pMenu->addAction(m_pRemoveAct);
1009-
1010 m_pPropertiesAct->setEnabled(oneSongSelected);
1011 m_pMenu->addAction(m_pPropertiesAct);
1012