File indexing completed on 2023-05-30 11:30:51

0001 /**
0002  * Copyright (C) 2002-2004 Scott Wheeler <wheeler@kde.org>
0003  * Copyright (C) 2009 Michael Pyne <mpyne@kde.org>
0004  *
0005  * This program is free software; you can redistribute it and/or modify it under
0006  * the terms of the GNU General Public License as published by the Free Software
0007  * Foundation; either version 2 of the License, or (at your option) any later
0008  * version.
0009  *
0010  * This program is distributed in the hope that it will be useful, but WITHOUT ANY
0011  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
0012  * PARTICULAR PURPOSE. See the GNU General Public License for more details.
0013  *
0014  * You should have received a copy of the GNU General Public License along with
0015  * this program.  If not, see <http://www.gnu.org/licenses/>.
0016  */
0017 
0018 #include "playlistsplitter.h"
0019 
0020 #include <kactioncollection.h>
0021 #include <ktoggleaction.h>
0022 #include <kacceleratormanager.h>
0023 #include <KConfigGroup>
0024 #include <KLocalizedString>
0025 #include <KSharedConfig>
0026 
0027 #include <QAction>
0028 #include <QLatin1String>
0029 #include <QList>
0030 #include <QSizePolicy>
0031 #include <QStackedWidget>
0032 #include <QVBoxLayout>
0033 
0034 #include "actioncollection.h"
0035 #include "collectionlist.h"
0036 #include "iconsupport.h"
0037 #include "juk_debug.h"
0038 #include "lyricswidget.h"
0039 #include "mpris2/mpris2.h"
0040 #include "nowplaying.h"
0041 #include "playermanager.h"
0042 #include "playlistbox.h"
0043 #include "playlistsearch.h"
0044 #include "searchwidget.h"
0045 #include "tageditor.h"
0046 
0047 ////////////////////////////////////////////////////////////////////////////////
0048 // public methods
0049 ////////////////////////////////////////////////////////////////////////////////
0050 
0051 PlaylistSplitter::PlaylistSplitter(PlayerManager *player, QWidget *parent)
0052   : QSplitter(Qt::Horizontal, parent)
0053   , m_player(player)
0054 {
0055     setObjectName(QLatin1String("playlistSplitter"));
0056 
0057     setupActions();
0058     setupLayout();
0059     readConfig();
0060 
0061     m_editor->slotUpdateCollection();
0062     m_editor->setupObservers();
0063 }
0064 
0065 PlaylistSplitter::~PlaylistSplitter()
0066 {
0067     saveConfig();
0068 
0069     m_playlistBox->stop(); // Remove playing item U/I state
0070 
0071     // TagEditor needs to write its configuration out while it's still valid,
0072     // destroy it now.
0073 
0074     delete m_editor;
0075     m_editor = nullptr;
0076 
0077     delete m_lyricsWidget;
0078     m_lyricsWidget = nullptr;
0079 
0080     // NowPlaying depends on the PlaylistCollection, so kill it now.
0081     delete m_nowPlaying;
0082     m_nowPlaying = nullptr;
0083 
0084     delete m_searchWidget; // Take no chances here either.
0085     m_searchWidget = nullptr;
0086 
0087     // Since we want to ensure that the shutdown process for the PlaylistCollection
0088     // (a base class for PlaylistBox) has a chance to write the playlists to disk
0089     // before they are deleted we're explicitly deleting the PlaylistBox here.
0090 
0091     delete m_playlistBox;
0092     m_playlistBox = nullptr;
0093 }
0094 
0095 PlaylistInterface *PlaylistSplitter::playlist() const
0096 {
0097     return m_playlistBox;
0098 }
0099 
0100 ////////////////////////////////////////////////////////////////////////////////
0101 // public slots
0102 ////////////////////////////////////////////////////////////////////////////////
0103 
0104 void PlaylistSplitter::setFocus()
0105 {
0106     if(m_searchWidget->isVisible()) {
0107         m_searchWidget->setFocus();
0108     } else {
0109         slotFocusCurrentPlaylist();
0110     }
0111 }
0112 
0113 void PlaylistSplitter::slotFocusCurrentPlaylist()
0114 {
0115     Playlist *playlist = m_playlistBox->visiblePlaylist();
0116 
0117     if(!playlist) {
0118         return;
0119     }
0120 
0121     playlist->setFocus();
0122     playlist->clearSelection();
0123 
0124     // Select the top visible (and matching) item.
0125 
0126     PlaylistItem *item = static_cast<PlaylistItem *>(playlist->itemAt(QPoint(0, 0)));
0127 
0128     if(!item) {
0129         return;
0130     }
0131 
0132     // A little bit of a hack to make QListView repaint things properly.  Switch
0133     // to single selection mode, set the selection and then switch back.
0134 
0135     playlist->setSelectionMode(QTreeWidget::SingleSelection);
0136 
0137     playlist->setCurrentItem(item);
0138 
0139     playlist->setSelectionMode(QTreeWidget::ExtendedSelection);
0140 }
0141 
0142 ////////////////////////////////////////////////////////////////////////////////
0143 // private members
0144 ////////////////////////////////////////////////////////////////////////////////
0145 
0146 Playlist *PlaylistSplitter::visiblePlaylist() const
0147 {
0148     return m_newVisible ? m_newVisible : m_playlistBox->visiblePlaylist();
0149 }
0150 
0151 void PlaylistSplitter::setupActions()
0152 {
0153     using namespace IconSupport;
0154 
0155     KActionCollection* coll = ActionCollection::actions();
0156     KToggleAction *showSearch =
0157         new KToggleAction("edit-find"_icon, i18n("Show &Search Bar"), this);
0158     coll->addAction("showSearch", showSearch);
0159 
0160     QAction *act = new QAction("edit-clear"_icon, i18n("Edit Track Search"), this);
0161     coll->addAction("editTrackSearch", act);
0162     coll->setDefaultShortcut(act, Qt::Key_F6);
0163     connect(act, &QAction::triggered,
0164             this, &PlaylistSplitter::setFocus);
0165 }
0166 
0167 void PlaylistSplitter::setupLayout()
0168 {
0169     using namespace ActionCollection; // add literal
0170 
0171     setOpaqueResize(false);
0172 
0173     // Disable the GUI until startup is complete (as indicated by PlaylistBox)
0174 
0175     setEnabled(false);
0176 
0177     // Create a splitter to go between the playlists and the editor.
0178 
0179     m_editorSplitter = new QSplitter(Qt::Vertical, this);
0180     m_editorSplitter->setObjectName(QLatin1String("editorSplitter"));
0181 
0182     // Make sure none of the optional widgets are collapsible, this causes the
0183     // widget to be essentially invisible but logically shown.
0184 
0185     this->setChildrenCollapsible(false);
0186     m_editorSplitter->setChildrenCollapsible(false);
0187 
0188     // Create the playlist and the editor.
0189 
0190     QWidget *top = new QWidget(m_editorSplitter);
0191     QVBoxLayout *topLayout = new QVBoxLayout(top);
0192     topLayout->setContentsMargins(0, 0, 0, 0);
0193     topLayout->setSpacing(0);
0194 
0195     m_playlistStack = new QStackedWidget(top);
0196     m_playlistStack->setObjectName(QLatin1String("playlistStack"));
0197     m_playlistStack->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0198     m_playlistStack->hide(); // Will be shown after CollectionList filled.
0199 
0200     m_editor = new TagEditor(m_editorSplitter);
0201     m_editor->setObjectName(QLatin1String("TagEditor"));
0202 
0203     // Create the lyrics widget
0204     m_lyricsWidget = new LyricsWidget(this);
0205     insertWidget(2, m_lyricsWidget);
0206 
0207     // Create the PlaylistBox
0208     m_playlistBox = new PlaylistBox(m_player, this, m_playlistStack);
0209     m_playlistBox->setObjectName(QLatin1String("playlistBox"));
0210 
0211     const auto &plistActions = m_playlistBox->collectionActions();
0212     connect(plistActions,  &PlaylistCollection::ActionHandler::signalSelectedItemsChanged,
0213             this,          &PlaylistSplitter::slotPlaylistSelectionChanged);
0214     connect(m_playlistBox, &PlaylistBox::signalPlaylistDestroyed,
0215             m_editor,      &TagEditor::slotPlaylistDestroyed);
0216     connect(m_playlistBox, &QTreeWidget::currentItemChanged,
0217             this,          &PlaylistSplitter::slotCurrentPlaylistChanged);
0218 
0219     // Let interested parties know we're ready
0220     connect(m_playlistBox, &PlaylistBox::startupComplete, this, [this]() {
0221                 this->slotEnable();
0222                 this->setFocus();
0223 
0224                 // Do this after initial playlist setup otherwise we'll waste
0225                 // a lot of time starting up with the tag editor trying to
0226                 // re-update after every item is loaded.
0227                 connect(CollectionList::instance(),
0228                         &CollectionList::signalCollectionChanged,
0229                         m_editor,
0230                         &TagEditor::slotUpdateCollection);
0231 
0232                 emit guiReady();
0233             });
0234 
0235     m_player->setPlaylistInterface(m_playlistBox);
0236 
0237     insertWidget(0, m_playlistBox);
0238 
0239     m_nowPlaying = new NowPlaying(top, m_playlistBox);
0240     connect(m_player,       &PlayerManager::signalItemChanged,
0241             m_nowPlaying,   &NowPlaying::slotUpdate);
0242     connect(m_player,       &PlayerManager::signalItemChanged,
0243             m_lyricsWidget, &LyricsWidget::playing);
0244 
0245     // Create the search widget -- this must be done after the CollectionList is created.
0246 
0247     m_searchWidget = new SearchWidget(top);
0248 
0249     // auto-shortcuts don't seem to work and aren't needed anyway.
0250     KAcceleratorManager::setNoAccel(m_searchWidget);
0251 
0252     connect(m_searchWidget,   &SearchWidget::signalQueryChanged,
0253             this,             &PlaylistSplitter::slotShowSearchResults);
0254     connect(m_searchWidget,   &SearchWidget::signalDownPressed,
0255             this,             &PlaylistSplitter::slotFocusCurrentPlaylist);
0256     connect(m_searchWidget,   &SearchWidget::signalShown,
0257             m_playlistBox,    [this](bool e) { m_playlistBox->setSearchEnabled(e); });
0258     connect(m_searchWidget,   &SearchWidget::returnPressed,
0259             m_playlistBox,    [this]() { m_playlistBox->playFirst(); });
0260     connect("showSearch"_act, &QAction::toggled,
0261             m_searchWidget,   &SearchWidget::setEnabled);
0262     connect(m_playlistBox,    &PlaylistBox::signalMoveFocusAway,
0263             m_searchWidget,   qOverload<>(&SearchWidget::setFocus));
0264 
0265     topLayout->addWidget(m_nowPlaying);
0266     topLayout->addWidget(m_searchWidget);
0267     topLayout->insertStretch(-1); // Force search bar to top while playlistStack hides
0268     topLayout->addWidget(m_playlistStack, 1);
0269 
0270     connect(m_playlistStack, &QStackedWidget::currentChanged,
0271             this,            &PlaylistSplitter::slotPlaylistChanged);
0272 
0273     // Show the collection on startup.
0274     m_playlistBox->setCurrentItem(m_playlistBox->topLevelItem(0));
0275 }
0276 
0277 void PlaylistSplitter::readConfig()
0278 {
0279     using namespace ActionCollection; // add literal
0280 
0281     KConfigGroup config(KSharedConfig::openConfig(), "Splitter");
0282 
0283     QList<int> splitterSizes = config.readEntry("PlaylistSplitterSizes",QList<int>());
0284     if(splitterSizes.isEmpty()) {
0285         splitterSizes.append(100);
0286         splitterSizes.append(640);
0287     }
0288     setSizes(splitterSizes);
0289 
0290     bool showSearch = config.readEntry("ShowSearch", true);
0291     "showSearch"_act->setChecked(showSearch);
0292     m_searchWidget->setHidden(!showSearch);
0293 
0294     splitterSizes = config.readEntry("EditorSplitterSizes",QList<int>());
0295     if(splitterSizes.isEmpty()) {
0296         // If no sizes were saved, use default sizes for the playlist and the
0297         // editor, respectively. The values are just hints for the actual size,
0298         // m_editorSplitter will distribute the space according to their
0299         // relative weight.
0300         splitterSizes.append(300);
0301         splitterSizes.append(200);
0302     }
0303     m_editorSplitter->setSizes(splitterSizes);
0304 }
0305 
0306 void PlaylistSplitter::saveConfig()
0307 {
0308     using namespace ActionCollection; // add literal
0309 
0310     KConfigGroup config(KSharedConfig::openConfig(), "Splitter");
0311     config.writeEntry("PlaylistSplitterSizes", sizes());
0312     config.writeEntry("ShowSearch", "showSearch"_act->isChecked());
0313     config.writeEntry("EditorSplitterSizes", m_editorSplitter->sizes());
0314 }
0315 
0316 void PlaylistSplitter::slotShowSearchResults()
0317 {
0318     visiblePlaylist()->setSearch(m_searchWidget->search(visiblePlaylist()));
0319 }
0320 
0321 void PlaylistSplitter::slotPlaylistSelectionChanged()
0322 {
0323     m_editor->slotSetItems(visiblePlaylist()->selectedItems());
0324 }
0325 
0326 void PlaylistSplitter::slotPlaylistChanged(int i)
0327 {
0328     Playlist *p = qobject_cast<Playlist *>(m_playlistStack->widget(i));
0329 
0330     if(!p)
0331         return;
0332 
0333     m_newVisible = p;
0334     m_searchWidget->setSearch(p->search());
0335     m_newVisible = nullptr;
0336 }
0337 
0338 void PlaylistSplitter::slotCurrentPlaylistChanged(QTreeWidgetItem *item)
0339 {
0340     auto pItem = static_cast<PlaylistBox::Item*>(item);
0341     emit currentPlaylistChanged(*(pItem->playlist()));
0342 }
0343 
0344 void PlaylistSplitter::slotEnable()
0345 {
0346     setEnabled(true); // Ready to go.
0347     m_playlistStack->show();
0348 
0349     (void) new Mpris2(this);
0350 }
0351 
0352 // vim: set et sw=4 tw=0 sta: