1 /***************************************************************************
2  *   Copyright (C) 2008-2021 by Ilya Kotov                                 *
3  *   forkotov02@ya.ru                                                      *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20 
21 #include <QDialog>
22 #include <QMenu>
23 #include <QWidget>
24 #include <QAction>
25 #include <QSettings>
26 #include <QApplication>
27 #include <QMessageBox>
28 #include <QFileInfo>
29 #include <algorithm>
30 #include <qmmp/soundcore.h>
31 #include <qmmp/metadatamanager.h>
32 #include "filedialog.h"
33 #include "playlistparser.h"
34 #include "playlistmanager.h"
35 #include "qmmpuisettings.h"
36 #include "general.h"
37 #include "generalfactory.h"
38 #include "jumptotrackdialog_p.h"
39 #include "aboutdialog_p.h"
40 #include "addurldialog_p.h"
41 #include "mediaplayer.h"
42 #include "uihelper.h"
43 
44 UiHelper *UiHelper::m_instance = nullptr;
45 
UiHelper(QObject * parent)46 UiHelper::UiHelper(QObject *parent)
47         : QObject(parent)
48 {
49     m_instance = this;
50     General::create(parent);
51     QSettings settings(Qmmp::configFile(), QSettings::IniFormat);
52     m_lastDir = settings.value("General/last_dir", QDir::homePath()).toString(); //last directory
53 }
54 
~UiHelper()55 UiHelper::~UiHelper()
56 {
57     QSettings settings(Qmmp::configFile(), QSettings::IniFormat);
58     settings.setValue("General/last_dir",m_lastDir);
59 }
60 
visibilityControl()61 bool UiHelper::visibilityControl()
62 {
63     const QList<GeneralFactory *> factories = General::enabledFactories();
64     return std::any_of(factories.cbegin(), factories.cend(),
65                        [](GeneralFactory *factory){ return factory->properties().visibilityControl; });
66 }
67 
addAction(QAction * action,MenuType type)68 void UiHelper::addAction(QAction *action, MenuType type)
69 {
70     connect(action, SIGNAL(destroyed (QObject *)), SLOT(removeAction(QObject*)));
71 
72     if(!m_menus[type].actions.contains(action))
73     {
74         m_menus[type].actions.append(action);
75 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
76         action->setShortcutVisibleInContextMenu(true);
77 #endif
78     }
79     if(m_menus[type].menu && !m_menus[type].menu->actions().contains(action))
80     {
81         if(m_menus[type].before)
82             m_menus[type].menu->insertAction(m_menus[type].before, action);
83         else
84             m_menus[type].menu->addAction(action);
85         m_menus[type].menu->menuAction()->setVisible(!m_menus[type].autoHide || !m_menus[type].actions.isEmpty());
86     }
87 }
88 
removeAction(QAction * action)89 void UiHelper::removeAction(QAction *action)
90 {
91     for(MenuType type : m_menus.keys())
92     {
93         m_menus[type].actions.removeAll(action);
94         if(m_menus[type].menu)
95         {
96             m_menus[type].menu->removeAction(action);
97             m_menus[type].menu->menuAction()->setVisible(!m_menus[type].autoHide || !m_menus[type].actions.isEmpty());
98         }
99     }
100 }
101 
actions(MenuType type)102 QList<QAction *> UiHelper::actions(MenuType type)
103 {
104     return m_menus[type].actions;
105 }
106 
createMenu(MenuType type,const QString & title,bool autoHide,QWidget * parent)107 QMenu *UiHelper::createMenu(MenuType type, const QString &title, bool autoHide, QWidget *parent)
108 {
109     if(m_menus[type].menu)
110     {
111         m_menus[type].menu->setTitle(title);
112     }
113     else
114     {
115         m_menus[type].menu = new QMenu(title, parent);
116         m_menus[type].menu->addActions(m_menus[type].actions);
117     }
118     m_menus[type].autoHide = autoHide;
119     m_menus[type].menu->menuAction()->setVisible(!autoHide || !m_menus[type].actions.isEmpty());
120     return m_menus[type].menu;
121 }
122 
registerMenu(UiHelper::MenuType type,QMenu * menu,bool autoHide,QAction * before)123 void UiHelper::registerMenu(UiHelper::MenuType type, QMenu *menu, bool autoHide, QAction *before)
124 {
125     m_menus[type].menu = menu;
126     m_menus[type].before = before;
127     m_menus[type].autoHide = autoHide;
128     if(before)
129         m_menus[type].menu->insertActions(before, m_menus[type].actions);
130     else
131         m_menus[type].menu->addActions(m_menus[type].actions);
132     m_menus[type].menu->menuAction()->setVisible(!autoHide || !m_menus[type].actions.isEmpty());
133 }
134 
addFiles(QWidget * parent,PlayListModel * model)135 void UiHelper::addFiles(QWidget *parent, PlayListModel *model)
136 {
137     QStringList filters;
138     filters << tr("All Supported Bitstreams")+" (" +
139             MetaDataManager::instance()->nameFilters().join (" ") +")";
140     filters << MetaDataManager::instance()->filters();
141     m_model = model;
142     FileDialog::popup(parent, FileDialog::PlayDirsFiles, &m_lastDir,
143                       this, SLOT(addSelectedFiles(QStringList,bool)),
144                       tr("Select one or more files to open"), filters.join(";;"));
145 }
146 
playFiles(QWidget * parent,PlayListModel * model)147 void UiHelper::playFiles(QWidget *parent, PlayListModel *model)
148 {
149     QStringList filters;
150     filters << tr("All Supported Bitstreams")+" (" +
151             MetaDataManager::instance()->nameFilters().join (" ") +")";
152     filters << MetaDataManager::instance()->filters();
153     m_model = model;
154     FileDialog::popup(parent, FileDialog::AddDirsFiles, &m_lastDir,
155                       this, SLOT(playSelectedFiles(QStringList)),
156                       tr("Select one or more files to play"), filters.join(";;"));
157 
158 }
159 
addDirectory(QWidget * parent,PlayListModel * model)160 void UiHelper::addDirectory(QWidget *parent, PlayListModel *model)
161 {
162     FileDialog::popup(parent, FileDialog::AddDirs, &m_lastDir,
163                       model, SLOT(add(QStringList)),
164                       tr("Choose a directory"));
165 }
166 
addUrl(QWidget * parent,PlayListModel * model)167 void UiHelper::addUrl(QWidget *parent, PlayListModel *model)
168 {
169     AddUrlDialog::popup(parent, model);
170 }
171 
loadPlayList(QWidget * parent,PlayListModel * model)172 void UiHelper::loadPlayList(QWidget *parent, PlayListModel *model)
173 {
174     if(PlayListParser::nameFilters().isEmpty())
175     {
176         qWarning("UiHelper: There is no registered playlist parsers");
177         return;
178     }
179 
180     QString mask = tr("Playlist Files") + " (" + PlayListParser::nameFilters().join(" ") + ")";
181     //TODO use nonmodal dialog and multiplier playlists
182     QString f_path = FileDialog::getOpenFileName(parent, tr("Open Playlist"), m_lastDir, mask);
183     if (!f_path.isEmpty())
184     {
185         if(QmmpUiSettings::instance()->clearPreviousPlayList())
186         {
187             model->clear();
188             model->setName(QFileInfo(f_path).baseName());
189         }
190         model->loadPlaylist(f_path);
191         m_lastDir = QFileInfo(f_path).absoluteDir().path();
192     }
193 }
194 
savePlayList(QWidget * parent,PlayListModel * model)195 void UiHelper::savePlayList(QWidget *parent, PlayListModel *model)
196 {
197     QStringList nameFilters = PlayListParser::nameFilters();
198 
199     if(nameFilters.isEmpty())
200     {
201         qWarning("UiHelper: There is no registered playlist parsers");
202         return;
203     }
204 
205     QStringList filters;
206     filters << tr("Playlist Files") + " (" + nameFilters.join(" ") + ")";
207     filters << PlayListParser::filters();
208     QString selectedFilter = filters.at(1);
209     QString f_name = FileDialog::getSaveFileName(parent, tr("Save Playlist"), m_lastDir + "/" +
210                                                  model->name(), filters.join(";;"), &selectedFilter);
211 
212     if(f_name.isEmpty())
213         return;
214 
215     if(!PlayListParser::isPlayList(f_name)) //append selected extension
216     {
217         QStringList selectedFilters = selectedFilter.section("(", 1).remove(")").split(" ");
218         if(selectedFilters.isEmpty())
219             return;
220 
221         QString ext = selectedFilters.first().remove("*"); //use first extension
222         f_name.append(ext);
223 
224         QFileInfo info(f_name);
225 
226         if(info.exists())
227         {
228             if (QMessageBox::question(parent, tr("Save Playlist"),  tr("%1 already exists.\nDo you want to replace it?")
229                                       .arg(info.fileName()), QMessageBox::Ok | QMessageBox::Cancel) != QMessageBox::Ok)
230             {
231                 return;
232             }
233         }
234     }
235 
236     if (!f_name.isEmpty())
237     {
238         model->savePlaylist(f_name);
239         m_lastDir = QFileInfo(f_name).absoluteDir().path();
240     }
241 }
242 
jumpToTrack(QWidget * parent,PlayListModel * model)243 void UiHelper::jumpToTrack(QWidget *parent, PlayListModel *model)
244 {
245     if(!m_jumpDialog)
246     {
247         m_jumpDialog = new JumpToTrackDialog(model, parent);
248     }
249     if(m_jumpDialog->isHidden())
250     {
251         m_jumpDialog->show();
252         m_jumpDialog->refresh();
253     }
254     m_jumpDialog->raise();
255 }
256 
about(QWidget * parent)257 void UiHelper::about(QWidget *parent)
258 {
259     AboutDialog *dialog = new AboutDialog(parent);
260     dialog->exec();
261     dialog->deleteLater();
262 }
263 
toggleVisibility()264 void UiHelper::toggleVisibility()
265 {
266     emit toggleVisibilityCalled();
267 }
268 
showMainWindow()269 void UiHelper::showMainWindow()
270 {
271     emit showMainWindowCalled();
272 }
273 
exit()274 void UiHelper::exit()
275 {
276     //send non-spontaneous close event
277     //for all windows
278     for(QWidget *widget : qApp->topLevelWidgets())
279         widget->close();
280 
281     qApp->closeAllWindows();
282     qApp->quit();
283 }
284 
instance()285 UiHelper* UiHelper::instance()
286 {
287     return m_instance;
288 }
289 
removeAction(QObject * action)290 void UiHelper::removeAction(QObject *action)
291 {
292     for(MenuType type : m_menus.keys())
293     {
294         for(QList<QAction *>::iterator it = m_menus[type].actions.begin(); it != m_menus[type].actions.end(); ++it)
295         {
296             if(*it == action)
297             {
298                 m_menus[type].actions.erase(it);
299                 m_menus[type].menu->menuAction()->setVisible(!m_menus[type].autoHide || !m_menus[type].actions.isEmpty());
300                 break;
301             }
302         }
303     }
304 }
305 
addSelectedFiles(const QStringList & files,bool play)306 void UiHelper::addSelectedFiles(const QStringList &files, bool play)
307 {
308     if(files.isEmpty() || !PlayListManager::instance()->playLists().contains(m_model))
309         return;
310     if(play)
311         playSelectedFiles(files);
312     else
313         m_model->add(files);
314 }
315 
playSelectedFiles(const QStringList & files)316 void UiHelper::playSelectedFiles(const QStringList &files)
317 {
318     if(files.isEmpty() || !PlayListManager::instance()->playLists().contains(m_model))
319         return;
320     m_model->clear();
321     PlayListManager::instance()->activatePlayList(m_model);
322     connect(m_model, SIGNAL(trackAdded(PlayListTrack*)), MediaPlayer::instance(), SLOT(play()));
323     connect(m_model, SIGNAL(trackAdded(PlayListTrack*)), SLOT(disconnectPl()));
324     m_model->add(files);
325 }
326 
disconnectPl()327 void UiHelper::disconnectPl()
328 {
329     PlayListModel *model = qobject_cast<PlayListModel*>(sender());
330     if(model)
331     {
332         disconnect(model, SIGNAL(trackAdded(PlayListTrack*)), MediaPlayer::instance(), SLOT(play()));
333         disconnect(model, SIGNAL(trackAdded(PlayListTrack*)), this, SLOT(disconnectPl()));
334     }
335 }
336