File indexing completed on 2021-12-21 13:27:59

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