tracksequenceiterator.cpp 9.56 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/**
 * Copyright (C) 2002-2004 Michael Pyne <mpyne@kde.org>
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */
16

17 18
#include "tracksequenceiterator.h"

Michael Pyne's avatar
Michael Pyne committed
19
#include <QAction>
20 21
#include <krandom.h>
#include <ktoggleaction.h>
22 23 24

#include "playlist.h"
#include "actioncollection.h"
25
#include "juktag.h"
26
#include "filehandle.h"
Michael Pyne's avatar
Michael Pyne committed
27
#include "juk_debug.h"
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

using namespace ActionCollection;

TrackSequenceIterator::TrackSequenceIterator() :
    m_current(0)
{
}

TrackSequenceIterator::TrackSequenceIterator(const TrackSequenceIterator &other) :
    m_current(other.m_current)
{
}

TrackSequenceIterator::~TrackSequenceIterator()
{
}

void TrackSequenceIterator::setCurrent(PlaylistItem *current)
{
    m_current = current;
}

Michael Pyne's avatar
Michael Pyne committed
50 51 52 53 54 55 56 57
void TrackSequenceIterator::playlistChanged()
{
}

void TrackSequenceIterator::itemAboutToDie(const PlaylistItem *)
{
}

58 59 60 61 62 63 64 65 66 67 68 69 70 71
DefaultSequenceIterator::DefaultSequenceIterator() :
    TrackSequenceIterator()
{
}

DefaultSequenceIterator::DefaultSequenceIterator(const DefaultSequenceIterator &other)
    : TrackSequenceIterator(other)
{
}

DefaultSequenceIterator::~DefaultSequenceIterator()
{
}

72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
// Helper function to return a random number up to (but not including) a given max with
// a truly equal probability of each integer in [0, max) being selected.
// When Qt 5.10 can be required we can use QRandomGenerator for this, but for now need to
// fixup KRandom.
// See https://twitter.com/colmmacc/status/1012723779708088320
static int boundedRandom(const int upperBound)
{
    while (1) {
        const int candidate = KRandom::random();

        // this check excludes integers above the highest multiple of
        // upperBound that is still below RAND_MAX to remove bias
        if (candidate < (RAND_MAX - (RAND_MAX % upperBound))) {
            return candidate % upperBound;
        }
    }
}

90 91 92 93 94 95
void DefaultSequenceIterator::advance()
{
    if(!current())
        return;

    bool isRandom = action("randomPlay") && action<KToggleAction>("randomPlay")->isChecked();
Michael Pyne's avatar
Michael Pyne committed
96
    bool loop = action<QAction>("loopPlaylist") && action<QAction>("loopPlaylist")->isChecked();
97 98 99
    bool albumRandom = action("albumRandomPlay") && action<KToggleAction>("albumRandomPlay")->isChecked();

    if(isRandom || albumRandom) {
100 101 102
        // TODO: This should probably use KRandomSequence's ability to shuffle
        // items instead of making a new random choice each time through.

103 104 105 106 107 108 109 110 111 112 113
        if(m_randomItems.isEmpty() && loop) {

            // Since refillRandomList will remove the currently playing item,
            // we should clear it out first since that's not good for e.g.
            // lists with 1-2 items.  We need to remember the Playlist though.

            Playlist *playlist = current()->playlist();
            setCurrent(0);

            refillRandomList(playlist);
        }
114 115 116 117 118 119 120 121 122

        if(m_randomItems.isEmpty()) {
            setCurrent(0);
            return;
        }

        PlaylistItem *item;

        if(albumRandom) {
123
            if(m_albumSearch.isNull() || m_albumSearch.matchedItems().isEmpty()) {
124
                item = m_randomItems[boundedRandom(m_randomItems.count())];
125 126
                initAlbumSearch(item);
            }
127

128
            // This can be null if initAlbumSearch() left the m_albumSearch
129 130
            // empty because the album text was empty.  Since we initAlbumSearch()
            // with an item, the matchedItems() should never be empty.
131

132
            if(!m_albumSearch.isNull()) {
133 134 135 136
                PlaylistItemList albumMatches;
                const Playlist* const playlist = m_albumSearch.playlists()[0];
                for(QModelIndex index : m_albumSearch.matchedItems())
                    playlist->itemAt(index.row(), index.column());
137
                if(albumMatches.isEmpty()) {
Michael Pyne's avatar
Michael Pyne committed
138 139
                    qCCritical(JUK_LOG) << "Unable to initialize album random play.\n";
                    qCCritical(JUK_LOG) << "List of potential results is empty.\n";
140 141 142

                    return; // item is still set to random song from a few lines earlier.
                }
143

144
                item = albumMatches[0];
145

146
                // Pick first song remaining in list.
147

148
                for(int i = 0; i < albumMatches.count(); ++i)
149 150
                    if(albumMatches[i]->file().tag()->track() < item->file().tag()->track())
                        item = albumMatches[i];
151 152 153 154

                if(m_albumSearch.matchedItems().isEmpty()) {
                    m_albumSearch.clearComponents();
                }
155
            }
156
            else
157
                qCCritical(JUK_LOG) << "Unable to perform album random play on " << *item;
158 159
        }
        else
160
            item = m_randomItems[boundedRandom(m_randomItems.count())];
161 162

        setCurrent(item);
163
        m_randomItems.removeAll(item);
164 165
    }
    else {
166
        PlaylistItem *next = current()->itemBelow();
167
        if(!next && loop) {
168 169 170 171 172
            // Find first visible item and restart playback from there
            QTreeWidgetItemIterator visible(
                    current()->playlist(),
                    QTreeWidgetItemIterator::NotHidden);
            next = static_cast<PlaylistItem *>(*visible);
173 174 175 176 177 178 179 180 181 182 183
        }

        setCurrent(next);
    }
}

void DefaultSequenceIterator::backup()
{
    if(!current())
        return;

184
    PlaylistItem *item = current()->itemAbove();
185 186 187 188 189 190 191 192 193

    if(item)
        setCurrent(item);
}

void DefaultSequenceIterator::prepareToPlay(Playlist *playlist)
{
    bool random = action("randomPlay") && action<KToggleAction>("randomPlay")->isChecked();
    bool albumRandom = action("albumRandomPlay") && action<KToggleAction>("albumRandomPlay")->isChecked();
194

195 196 197 198 199 200 201
    if(random || albumRandom) {
        PlaylistItemList items = playlist->selectedItems();
        if(items.isEmpty())
            items = playlist->visibleItems();

        PlaylistItem *newItem = 0;
        if(!items.isEmpty())
202
            newItem = items[KRandom::random() % items.count()];
203 204 205 206 207

        setCurrent(newItem);
        refillRandomList();
    }
    else {
208 209 210
        QTreeWidgetItemIterator it(playlist, QTreeWidgetItemIterator::NotHidden | QTreeWidgetItemIterator::Selected);
        if(!*it)
            it = QTreeWidgetItemIterator(playlist, QTreeWidgetItemIterator::NotHidden);
211

212
        setCurrent(static_cast<PlaylistItem *>(*it));
213 214 215 216 217 218 219 220 221 222
    }
}

void DefaultSequenceIterator::reset()
{
    m_randomItems.clear();
    m_albumSearch.clearComponents();
    setCurrent(0);
}

Michael Pyne's avatar
Michael Pyne committed
223 224 225 226 227 228 229 230
void DefaultSequenceIterator::playlistChanged()
{
    refillRandomList();
}

void DefaultSequenceIterator::itemAboutToDie(const PlaylistItem *item)
{
    PlaylistItem *stfu_gcc = const_cast<PlaylistItem *>(item);
231
    m_randomItems.removeAll(stfu_gcc);
Michael Pyne's avatar
Michael Pyne committed
232 233
}

234 235
void DefaultSequenceIterator::setCurrent(PlaylistItem *current)
{
236
    PlaylistItem *oldCurrent = DefaultSequenceIterator::current();
237 238 239 240 241

    TrackSequenceIterator::setCurrent(current);

    bool random = action("randomPlay") && action<KToggleAction>("randomPlay")->isChecked();
    bool albumRandom = action("albumRandomPlay") && action<KToggleAction>("albumRandomPlay")->isChecked();
242

243 244 245 246 247 248 249 250
    if((albumRandom || random) && current && m_randomItems.isEmpty()) {

        // We're setting a current item, refill the random list now, and remove
        // the current item.

        refillRandomList();
    }

251
    m_randomItems.removeAll(current);
252 253 254 255 256 257 258 259 260 261 262 263 264 265

    if(albumRandom && current && !oldCurrent) {

        // Same idea as above

        initAlbumSearch(current);
    }
}

DefaultSequenceIterator *DefaultSequenceIterator::clone() const
{
    return new DefaultSequenceIterator(*this);
}

266
void DefaultSequenceIterator::refillRandomList(Playlist *p)
267
{
268 269 270
    if(!p) {
        if (!current())
            return;
271

272
        p = current()->playlist();
273

274
        if(!p) {
Michael Pyne's avatar
Michael Pyne committed
275
            qCCritical(JUK_LOG) << "Item has no playlist!\n";
276 277
            return;
        }
278 279 280
    }

    m_randomItems = p->visibleItems();
281
    m_randomItems.removeAll(current());
282 283 284 285 286 287 288 289 290 291 292 293
    m_albumSearch.clearComponents();
}

void DefaultSequenceIterator::initAlbumSearch(PlaylistItem *searchItem)
{
    if(!searchItem)
        return;

    m_albumSearch.clearPlaylists();
    m_albumSearch.addPlaylist(searchItem->playlist());

    ColumnList columns;
294

295 296 297 298 299 300 301
    m_albumSearch.setSearchMode(PlaylistSearch::MatchAll);
    m_albumSearch.clearComponents();

    // If the album name is empty, it will mess up the search,
    // so ignore empty album names.

    if(searchItem->file().tag()->album().isEmpty())
302
        return;
303 304 305 306

    columns.append(PlaylistItem::AlbumColumn);

    m_albumSearch.addComponent(PlaylistSearch::Component(
307 308 309 310
        searchItem->file().tag()->album(),
        true,
        columns,
        PlaylistSearch::Component::Exact)
311 312
    );

313 314 315 316 317
    // If there is an Artist tag with the track, match against it as well
    // to avoid things like multiple "Greatest Hits" albums matching the
    // search.

    if(!searchItem->file().tag()->artist().isEmpty()) {
Michael Pyne's avatar
Michael Pyne committed
318
        qCDebug(JUK_LOG) << "Searching both artist and album.";
319 320 321 322 323 324 325 326 327
        columns[0] = PlaylistItem::ArtistColumn;

        m_albumSearch.addComponent(PlaylistSearch::Component(
            searchItem->file().tag()->artist(),
            true,
            columns,
            PlaylistSearch::Component::Exact)
        );
    }
328 329
}

330
// vim: set et sw=4 tw=0 sta: