playlistsplitter.cpp 11.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/**
 * Copyright (C) 2002-2004 Scott Wheeler <wheeler@kde.org>
 * Copyright (C) 2009 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/>.
 */
17

18 19
#include "playlistsplitter.h"

20
#include <kactioncollection.h>
Laurent Montel's avatar
Poirt  
Laurent Montel committed
21
#include <ktoggleaction.h>
22
#include <kacceleratormanager.h>
Michael Pyne's avatar
Michael Pyne committed
23 24 25
#include <KConfigGroup>
#include <KLocalizedString>
#include <KSharedConfig>
26

Michael Pyne's avatar
Michael Pyne committed
27 28
#include <QIcon>
#include <QAction>
29
#include <QEvent>
Laurent Montel's avatar
Laurent Montel committed
30
#include <QVBoxLayout>
31
#include <QLatin1String>
32
#include <QList>
33
#include <QTime>
34
#include <QStackedWidget>
35
#include <QSizePolicy>
Michael Pyne's avatar
Michael Pyne committed
36
#include <QKeySequence>
37

38
#include "searchwidget.h"
39
#include "playlistsearch.h"
40
#include "actioncollection.h"
41 42
#include "tageditor.h"
#include "collectionlist.h"
43
#include "playermanager.h"
44
#include "nowplaying.h"
45
#include "playlistbox.h"
46
#include "lyricswidget.h"
47
#include "mpris2/mpris2.h"
Michael Pyne's avatar
Michael Pyne committed
48
#include "juk_debug.h"
49

50
////////////////////////////////////////////////////////////////////////////////
51
// public methods
52 53
////////////////////////////////////////////////////////////////////////////////

54
PlaylistSplitter::PlaylistSplitter(PlayerManager *player, QWidget *parent) :
55
    QSplitter(Qt::Horizontal, parent),
56
    m_newVisible(0),
57 58 59
    m_playlistBox(0),
    m_searchWidget(0),
    m_playlistStack(0),
60
    m_editor(0),
61
    m_nowPlaying(0),
62
    m_player(player),
Laurent Montel's avatar
Laurent Montel committed
63 64 65
    m_lyricsWidget(0),
    m_editorSplitter(0)

66
{
67
    setObjectName(QLatin1String("playlistSplitter"));
68

69
    setupActions();
70 71
    setupLayout();
    readConfig();
72 73

    m_editor->slotUpdateCollection();
74
    m_editor->setupObservers();
75
}
76

77
PlaylistSplitter::~PlaylistSplitter()
78
{
79
    saveConfig();
80

81 82 83 84 85
    // TagEditor needs to write its configuration out while it's still valid,
    // destroy it now.

    delete m_editor;

86 87
    delete m_lyricsWidget;

88 89 90 91 92 93
    // NowPlaying depends on the PlaylistCollection, so kill it now.
    delete m_nowPlaying;
    m_nowPlaying = 0;

    delete m_searchWidget; // Take no chances here either.

94 95 96 97
    // Since we want to ensure that the shutdown process for the PlaylistCollection
    // (a base class for PlaylistBox) has a chance to write the playlists to disk
    // before they are deleted we're explicitly deleting the PlaylistBox here.

98
    delete m_playlistBox;
99 100
}

101 102 103 104 105
PlaylistInterface *PlaylistSplitter::playlist() const
{
    return m_playlistBox;
}

106 107 108 109 110 111 112 113 114
bool PlaylistSplitter::eventFilter(QObject *, QEvent *event)
{
    if(event->type() == FocusUpEvent::id) {
        m_searchWidget->setFocus();
        return true;
    }
    return false;
}

115 116 117 118 119 120 121 122 123
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

void PlaylistSplitter::setFocus()
{
    m_searchWidget->setFocus();
}

124 125 126 127 128
void PlaylistSplitter::slotFocusCurrentPlaylist()
{
    Playlist *playlist = m_playlistBox->visiblePlaylist();

    if(playlist) {
129
        playlist->setFocus();
130
        playlist->clearSelection();
131

132
        // Select the top visible (and matching) item.
133

134
        PlaylistItem *item = static_cast<PlaylistItem *>(playlist->itemAt(QPoint(0, 0)));
135

136 137
        if(!item)
            return;
138

139 140
        // A little bit of a hack to make QListView repaint things properly.  Switch
        // to single selection mode, set the selection and then switch back.
141

142
        playlist->setSelectionMode(QTreeWidget::SingleSelection);
143

144
        playlist->setCurrentItem(item);
145

146
        playlist->setSelectionMode(QTreeWidget::ExtendedSelection);
147 148 149
    }
}

150
////////////////////////////////////////////////////////////////////////////////
151
// private members
152 153
////////////////////////////////////////////////////////////////////////////////

154 155 156 157 158
Playlist *PlaylistSplitter::visiblePlaylist() const
{
    return m_newVisible ? m_newVisible : m_playlistBox->visiblePlaylist();
}

159
void PlaylistSplitter::setupActions()
160
{
161
    KActionCollection* coll = ActionCollection::actions();
162
    KToggleAction *showSearch =
Michael Pyne's avatar
Michael Pyne committed
163
	 new KToggleAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("Show &Search Bar"), this);
164 165
    coll->addAction("showSearch", showSearch);

Michael Pyne's avatar
Michael Pyne committed
166
    QAction *act = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Edit Track Search"), this);
167
    coll->addAction("editTrackSearch", act);
Michael Pyne's avatar
Michael Pyne committed
168
    coll->setDefaultShortcut(act, Qt::Key_F6);
169
    connect(act, SIGNAL(triggered(bool)), SLOT(setFocus()));
170 171
}

172 173
void PlaylistSplitter::setupLayout()
{
174
    setOpaqueResize(false);
175

Michael Pyne's avatar
Michael Pyne committed
176 177 178 179
    // Disable the GUI until startup is complete (as indicated by PlaylistBox)

    setEnabled(false);

180 181
    // Create a splitter to go between the playlists and the editor.

182
    m_editorSplitter = new QSplitter(Qt::Vertical, this);
183
    m_editorSplitter->setObjectName(QLatin1String("editorSplitter"));
184

185 186 187 188 189 190
    // Make sure none of the optional widgets are collapsible, this causes the
    // widget to be essentially invisible but logically shown.

    this->setChildrenCollapsible(false);
    m_editorSplitter->setChildrenCollapsible(false);

191
    // Create the playlist and the editor.
192

193
    QWidget *top = new QWidget(m_editorSplitter);
194
    QVBoxLayout *topLayout = new QVBoxLayout(top);
Sebastian Sauer's avatar
Sebastian Sauer committed
195 196
    topLayout->setMargin(0);
    topLayout->setSpacing(0);
197

198
    m_playlistStack = new QStackedWidget(top);
199
    m_playlistStack->setObjectName(QLatin1String("playlistStack"));
200
    m_playlistStack->installEventFilter(this);
201
    m_playlistStack->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
202
    m_playlistStack->hide(); // Will be shown after CollectionList filled.
203

204
    m_editor = new TagEditor(m_editorSplitter);
205
    m_editor->setObjectName(QLatin1String("TagEditor"));
206

207 208 209
    // Create the lyrics widget
    m_lyricsWidget = new LyricsWidget(this);
    insertWidget(2, m_lyricsWidget);
210

211
    // Create the PlaylistBox
212
    m_playlistBox = new PlaylistBox(m_player, this, m_playlistStack);
213
    m_playlistBox->setObjectName(QLatin1String("playlistBox"));
214

215 216
    connect(m_playlistBox->object(), SIGNAL(signalSelectedItemsChanged()),
            this, SLOT(slotPlaylistSelectionChanged()));
Laurent Montel's avatar
Laurent Montel committed
217 218
    connect(m_playlistBox, SIGNAL(signalPlaylistDestroyed(Playlist*)),
            m_editor, SLOT(slotPlaylistDestroyed(Playlist*)));
Michael Pyne's avatar
Michael Pyne committed
219
    connect(m_playlistBox, SIGNAL(startupComplete()), SLOT(slotEnable()));
220 221
    connect(m_playlistBox, &QTreeWidget::currentItemChanged,
            this,          &PlaylistSplitter::slotCurrentPlaylistChanged);
222 223 224 225
    connect(m_playlistBox, SIGNAL(startFilePlayback(FileHandle)),
            m_player, SLOT(play(FileHandle)));

    m_player->setPlaylistInterface(m_playlistBox);
226

Michael Pyne's avatar
Michael Pyne committed
227 228 229
    // Let interested parties know we're ready
    connect(m_playlistBox, SIGNAL(startupComplete()), SIGNAL(guiReady()));

230
    insertWidget(0, m_playlistBox);
231

232
    m_nowPlaying = new NowPlaying(top, m_playlistBox);
233 234
    connect(m_player, SIGNAL(signalItemChanged(FileHandle)),
            m_nowPlaying, SLOT(slotUpdate(FileHandle)));
235 236
    connect(m_player, SIGNAL(signalItemChanged(FileHandle)),
            m_lyricsWidget, SLOT(playing(FileHandle)));
237

238
    // Create the search widget -- this must be done after the CollectionList is created.
239

240
    m_searchWidget = new SearchWidget(top);
241 242 243 244

    // auto-shortcuts don't seem to work and aren't needed anyway.
    KAcceleratorManager::setNoAccel(m_searchWidget);

245 246
    connect(m_searchWidget, SIGNAL(signalQueryChanged()),
            this, SLOT(slotShowSearchResults()));
247 248
    connect(m_searchWidget, SIGNAL(signalDownPressed()),
            this, SLOT(slotFocusCurrentPlaylist()));
249 250
    connect(m_searchWidget, SIGNAL(signalShown(bool)),
            m_playlistBox->object(), SLOT(slotSetSearchEnabled(bool)));
Michael Pyne's avatar
Michael Pyne committed
251 252
    connect(m_searchWidget, SIGNAL(returnPressed()),
            m_playlistBox->object(), SLOT(slotPlayFirst()));
253
    connect(ActionCollection::action<KToggleAction>("showSearch"), SIGNAL(toggled(bool)),
254
            m_searchWidget, SLOT(setEnabled(bool)));
255

256
    topLayout->addWidget(m_nowPlaying);
257
    topLayout->addWidget(m_searchWidget);
Michael Pyne's avatar
Michael Pyne committed
258
    topLayout->insertStretch(-1); // Force search bar to top while playlistStack hides
259
    topLayout->addWidget(m_playlistStack, 1);
260

261 262 263 264 265
    // Now that GUI setup is complete, add some auto-update signals.
    connect(CollectionList::instance(), SIGNAL(signalCollectionChanged()),
            m_editor, SLOT(slotUpdateCollection()));
    connect(m_playlistStack, SIGNAL(currentChanged(int)), this, SLOT(slotPlaylistChanged(int)));

266
    // Show the collection on startup.
267
    m_playlistBox->setCurrentItem(m_playlistBox->topLevelItem(0));
268 269 270 271
}

void PlaylistSplitter::readConfig()
{
272
    KConfigGroup config(KSharedConfig::openConfig(), "Splitter");
273

274
    QList<int> splitterSizes = config.readEntry("PlaylistSplitterSizes",QList<int>());
275 276 277
    if(splitterSizes.isEmpty()) {
        splitterSizes.append(100);
        splitterSizes.append(640);
278
    }
279
    setSizes(splitterSizes);
280

281
    bool showSearch = config.readEntry("ShowSearch", true);
282
    ActionCollection::action<KToggleAction>("showSearch")->setChecked(showSearch);
283
    m_searchWidget->setHidden(!showSearch);
284 285 286 287 288 289 290 291 292 293 294

    splitterSizes = config.readEntry("EditorSplitterSizes",QList<int>());
    if(splitterSizes.isEmpty()) {
        // If no sizes were saved, use default sizes for the playlist and the
        // editor, respectively. The values are just hints for the actual size,
        // m_editorSplitter will distribute the space according to their
        // relative weight.
        splitterSizes.append(300);
        splitterSizes.append(200);
    }
    m_editorSplitter->setSizes(splitterSizes);
295 296
}

297
void PlaylistSplitter::saveConfig()
298
{
299
    KConfigGroup config(KSharedConfig::openConfig(), "Splitter");
300
    config.writeEntry("PlaylistSplitterSizes", sizes());
301
    config.writeEntry("ShowSearch", ActionCollection::action<KToggleAction>("showSearch")->isChecked());
302
    config.writeEntry("EditorSplitterSizes", m_editorSplitter->sizes());
303 304
}

305
void PlaylistSplitter::slotShowSearchResults()
306 307 308
{
    PlaylistList playlists;
    playlists.append(visiblePlaylist());
309 310
    PlaylistSearch search = m_searchWidget->search(playlists);
    visiblePlaylist()->setSearch(search);
311 312
}

313
void PlaylistSplitter::slotPlaylistSelectionChanged()
314
{
315
    m_editor->slotSetItems(visiblePlaylist()->selectedItems());
316 317
}

318
void PlaylistSplitter::slotPlaylistChanged(int i)
319
{
320
    Playlist *p = qobject_cast<Playlist *>(m_playlistStack->widget(i));
321

322
    if(!p)
323
        return;
324 325 326 327

    m_newVisible = p;
    m_searchWidget->setSearch(p->search());
    m_newVisible = 0;
328 329
}

330 331 332 333 334 335
void PlaylistSplitter::slotCurrentPlaylistChanged(QTreeWidgetItem *item)
{
    auto pItem = static_cast<PlaylistBox::Item*>(item);
    emit currentPlaylistChanged(*(pItem->playlist()));
}

Michael Pyne's avatar
Michael Pyne committed
336 337 338
void PlaylistSplitter::slotEnable()
{
    setEnabled(true); // Ready to go.
339
    m_playlistStack->show();
340 341

    (void) new Mpris2(this);
Michael Pyne's avatar
Michael Pyne committed
342 343
}

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