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