1 /*
2 * Cantata
3 *
4 * Copyright (c) 2011-2014 Craig Drummond <craig.p.drummond@gmail.com>
5 *
6 * ----
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; see the file COPYING. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23
24 #include "synccollectionwidget.h"
25 #include "widgets/treeview.h"
26 #include "widgets/toolbutton.h"
27 #include "widgets/icons.h"
28 #include "models/musiclibraryitemartist.h"
29 #include "models/musiclibraryitemalbum.h"
30 #include "models/musiclibraryitemsong.h"
31 #include "support/icon.h"
32 #include "support/actioncollection.h"
33 #include <QTimer>
34 #include <QAction>
35 #include <algorithm>
36
SyncCollectionWidget(QWidget * parent,const QString & title)37 SyncCollectionWidget::SyncCollectionWidget(QWidget *parent, const QString &title)
38 : QWidget(parent)
39 , performedSearch(false)
40 , searchTimer(nullptr)
41 {
42 setupUi(this);
43 titleLabel->setText(title);
44 cfgButton->setIcon(Icons::self()->configureIcon);
45 connect(cfgButton, SIGNAL(clicked(bool)), SIGNAL(configure()));
46
47 proxy.setSourceModel(&model);
48 tree->setModel(&proxy);
49 tree->setPageDefaults();
50 tree->setUseSimpleDelegate();
51 search->setText(QString());
52 search->setPlaceholderText(tr("Search"));
53 connect(&proxy, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex)));
54 connect(search, SIGNAL(returnPressed()), this, SLOT(delaySearchItems()));
55 connect(search, SIGNAL(textChanged(const QString)), this, SLOT(delaySearchItems()));
56
57 checkAction=new Action(tr("Check Items"), this);
58 connect(checkAction, SIGNAL(triggered()), SLOT(checkItems()));
59 unCheckAction=new Action(tr("Uncheck Items"), this);
60 connect(unCheckAction, SIGNAL(triggered()), SLOT(unCheckItems()));
61 tree->addAction(checkAction);
62 tree->addAction(unCheckAction);
63 tree->setContextMenuPolicy(Qt::ActionsContextMenu);
64
65 QAction *expand=ActionCollection::get()->action("expandall");
66 QAction *collapse=ActionCollection::get()->action("collapseall");
67 if (expand && collapse) {
68 tree->addAction(expand);
69 tree->addAction(collapse);
70 addAction(expand);
71 addAction(collapse);
72 connect(expand, SIGNAL(triggered()), this, SLOT(expandAll()));
73 connect(collapse, SIGNAL(triggered()), this, SLOT(collapseAll()));
74 }
75
76 connect(tree, SIGNAL(itemsSelected(bool)), checkAction, SLOT(setEnabled(bool)));
77 connect(tree, SIGNAL(itemsSelected(bool)), unCheckAction, SLOT(setEnabled(bool)));
78 connect(tree, SIGNAL(itemActivated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &)));
79 connect(tree, SIGNAL(clicked(const QModelIndex &)), this, SLOT(itemClicked(const QModelIndex &)));
80 }
81
~SyncCollectionWidget()82 SyncCollectionWidget::~SyncCollectionWidget()
83 {
84 }
85
update(const QSet<Song> & songs)86 void SyncCollectionWidget::update(const QSet<Song> &songs)
87 {
88 model.setSongs(songs);
89 }
90
checkedSongs() const91 QList<Song> SyncCollectionWidget::checkedSongs() const
92 {
93 QList<Song> songs;
94 for (const Song *s: checked) {
95 songs.append(*s);
96 }
97 std::sort(songs.begin(), songs.end());
98 return songs;
99 }
100
dataChanged(const QModelIndex & tl,const QModelIndex & br)101 void SyncCollectionWidget::dataChanged(const QModelIndex &tl, const QModelIndex &br)
102 {
103 bool haveChecked=numCheckedSongs()>0;
104 QModelIndex firstIndex = proxy.mapToSource(tl);
105 QModelIndex lastIndex = proxy.mapToSource(br);
106 const MusicLibraryItem *item=static_cast<const MusicLibraryItem *>(firstIndex.internalPointer());
107 switch (item->itemType()) {
108 case MusicLibraryItem::Type_Artist:
109 for (int i=firstIndex.row(); i<=lastIndex.row(); ++i) {
110 QModelIndex index=model.index(i, 0, firstIndex.parent());
111 const MusicLibraryItemArtist *artist=static_cast<const MusicLibraryItemArtist *>(index.internalPointer());
112 for (const MusicLibraryItem *alItem: artist->childItems()) {
113 for (const MusicLibraryItem *sItem: static_cast<const MusicLibraryItemContainer *>(alItem)->childItems()) {
114 songToggled(static_cast<const MusicLibraryItemSong *>(sItem));
115 }
116 }
117 }
118 break;
119 case MusicLibraryItem::Type_Album:
120 for (int i=firstIndex.row(); i<=lastIndex.row(); ++i) {
121 QModelIndex index=model.index(i, 0, firstIndex.parent());
122 const MusicLibraryItemAlbum *album=static_cast<const MusicLibraryItemAlbum *>(index.internalPointer());
123 for (const MusicLibraryItem *sItem: album->childItems()) {
124 songToggled(static_cast<const MusicLibraryItemSong *>(sItem));
125 }
126 }
127 break;
128 case MusicLibraryItem::Type_Song:
129 for (int i=firstIndex.row(); i<=lastIndex.row(); ++i) {
130 QModelIndex index=model.index(i, 0, firstIndex.parent());
131 songToggled(static_cast<MusicLibraryItemSong *>(index.internalPointer()));
132 }
133 default:
134 break;
135 }
136
137 if (haveChecked!=(numCheckedSongs()>0)) {
138 emit selectionChanged();
139 }
140 }
141
songToggled(const MusicLibraryItemSong * song)142 void SyncCollectionWidget::songToggled(const MusicLibraryItemSong *song)
143 {
144 const Song &s=song->song();
145 if (Qt::Checked==song->checkState()) {
146 checked.insert(&s);
147 spaceRequired+=s.size;
148 } else {
149 checked.remove(&s);
150 spaceRequired-=s.size;
151 }
152 }
153
checkItems()154 void SyncCollectionWidget::checkItems()
155 {
156 checkItems(true);
157 }
158
unCheckItems()159 void SyncCollectionWidget::unCheckItems()
160 {
161 checkItems(false);
162 }
163
checkItems(bool c)164 void SyncCollectionWidget::checkItems(bool c)
165 {
166 const QModelIndexList selected = tree->selectedIndexes();
167
168 if (0==selected.size()) {
169 return;
170 }
171
172 for (const QModelIndex &idx: selected) {
173 model.setData(proxy.mapToSource(idx), c, Qt::CheckStateRole);
174 }
175 }
176
delaySearchItems()177 void SyncCollectionWidget::delaySearchItems()
178 {
179 if (search->text().trimmed().isEmpty()) {
180 if (searchTimer) {
181 searchTimer->stop();
182 }
183 if (performedSearch) {
184 tree->collapseToLevel(0);
185 }
186 searchItems();
187 performedSearch=false;
188 } else {
189 if (!searchTimer) {
190 searchTimer=new QTimer(this);
191 searchTimer->setSingleShot(true);
192 connect(searchTimer, SIGNAL(timeout()), SLOT(searchItems()));
193 }
194 searchTimer->start(500);
195 }
196 }
197
searchItems()198 void SyncCollectionWidget::searchItems()
199 {
200 QString text=search->text().trimmed();
201 proxy.update(text);
202 if (proxy.enabled() && !text.isEmpty()) {
203 tree->expandAll();
204 }
205 performedSearch=true;
206 }
207
expandAll()208 void SyncCollectionWidget::expandAll()
209 {
210 QWidget *f=QApplication::focusWidget();
211 if (f && qobject_cast<QTreeView *>(f)) {
212 static_cast<QTreeView *>(f)->expandAll();
213 }
214 }
215
collapseAll()216 void SyncCollectionWidget::collapseAll()
217 {
218 QWidget *f=QApplication::focusWidget();
219 if (f && qobject_cast<QTreeView *>(f)) {
220 static_cast<QTreeView *>(f)->collapseAll();
221 }
222 }
223
itemClicked(const QModelIndex & index)224 void SyncCollectionWidget::itemClicked(const QModelIndex &index)
225 {
226 if (TreeView::getForceSingleClick() && !tree->checkBoxClicked(index)) {
227 tree->setExpanded(index, !tree->isExpanded(index));
228 }
229 }
230
itemActivated(const QModelIndex & index)231 void SyncCollectionWidget::itemActivated(const QModelIndex &index)
232 {
233 if (!TreeView::getForceSingleClick()) {
234 tree->setExpanded(index, !tree->isExpanded(index));
235 }
236 }
237
238 #include "moc_synccollectionwidget.cpp"
239