MythTV master
lyricsview.cpp
Go to the documentation of this file.
1// C++
2#include <cstdlib>
3#include <iostream>
4#include <unistd.h>
5
6// qt
7#include <QDomDocument>
8#include <QKeyEvent>
9#include <QThread>
10
11// MythTV
18#include <libmythbase/mythversion.h>
30
31// mythmusic
32#include "lyricsview.h"
33#include "musiccommon.h"
34#include "musicplayer.h"
35
37// LyricsView
38
40 : MusicCommon(parent, parentScreen, "lyricsview")
41{
43
45}
46
48{
49 if (m_lyricData)
50 {
51 m_lyricData->disconnect();
52 m_lyricData = nullptr;
53 }
54
56}
57
59{
60 QString windowName = gPlayer->getPlayMode() == MusicPlayer::PLAYMODE_RADIO ? "streamlyricsview" : "trackslyricsview";
61
62 // Load the theme for this screen
63 bool err = LoadWindowFromXML("music-ui.xml", windowName, this);
64
65 if (!err)
66 return false;
67
68 // find common widgets available on any view
69 err = CreateCommon();
70
71 // find widgets specific to this view
72 UIUtilE::Assign(this, m_lyricsList, "lyrics_list", &err);
73 UIUtilW::Assign(this, m_statusText, "status_text");
74 UIUtilW::Assign(this, m_loadingState, "loading_state");
75
76 // for streams
77 UIUtilW::Assign(this, m_bufferStatus, "bufferstatus", &err);
78 UIUtilW::Assign(this, m_bufferProgress, "bufferprogress", &err);
79
80 if (err)
81 {
82 LOG(VB_GENERAL, LOG_ERR, QString("Cannot load screen '%1'").arg(windowName));
83 return false;
84 }
85
87
89
90 findLyrics();
91
92 return true;
93}
94
95void LyricsView::customEvent(QEvent *event)
96{
97 bool handled = false;
98
99 if ((event->type() == MusicPlayerEvent::kTrackChangeEvent) ||
101 {
102 findLyrics();
103 }
104 else if (event->type() == OutputEvent::kInfo)
105 {
106 if (m_autoScroll)
107 {
108 auto *oe = dynamic_cast<OutputEvent *>(event);
110
111 if (!oe || !curMeta)
112 return;
113
114 std::chrono::milliseconds rs = 0ms;
115
117 {
119 }
120 else
121 {
122 rs = oe->elapsedSeconds();
123 }
124
125 int pos = 0;
126 for (int x = 0; x < m_lyricsList->GetCount(); x++)
127 {
129 auto *lyric = item->GetData().value<LyricsLine*>();
130 if (lyric)
131 {
132 if (lyric->m_time > 1s && rs >= lyric->m_time)
133 pos = x;
134 }
135 }
136
138 }
139 }
140 else if (event->type() == DialogCompletionEvent::kEventType)
141 {
142 auto *dce = dynamic_cast<DialogCompletionEvent*>(event);
143
144 // make sure the user didn't ESCAPE out of the menu
145 if ((dce == nullptr) || (dce->GetResult() < 0))
146 return;
147
148 QString resultid = dce->GetId();
149 QString resulttext = dce->GetResultText();
150 if (resultid == "actionmenu")
151 {
152 if (resulttext == tr("Save Lyrics"))
153 {
154 saveLyrics();
155 }
156 else if ((resulttext == tr("Edit Lyrics")) ||
157 (resulttext == tr("Add Lyrics")))
158 {
159 editLyrics();
160 }
161 else if (resulttext == tr("Auto Scroll Lyrics"))
162 {
163 m_autoScroll = true;
164 }
165
166 handled = true;
167 }
168 else if (resultid == "findlyricsmenu")
169 {
170 if (dce->GetResult() >= 0)
171 {
172 QString grabber = dce->GetData().toString();
174 findLyrics(grabber);
175 }
176
177 handled = true;
178 }
179 }
180 else if (event->type() == DecoderHandlerEvent::kOperationStart)
181 {
182 auto *dhe = dynamic_cast<DecoderHandlerEvent*>(event);
183 if (!dhe)
184 return;
185 if (dhe->getMessage() && m_bufferStatus)
186 {
187 m_bufferStatus->SetText(*dhe->getMessage());
188 }
189 }
190 else if (event->type() == DecoderHandlerEvent::kBufferStatus)
191 {
192 auto *dhe = dynamic_cast<DecoderHandlerEvent*>(event);
193 if (!dhe)
194 return;
195
196 int available = 0;
197 int maxSize = 0;
198 dhe->getBufferStatus(&available, &maxSize);
199
200 if (m_bufferStatus)
201 {
202 QString status = QString("%1%").arg((int)(100.0 / ((double)maxSize / (double)available)));
203 m_bufferStatus->SetText(status);
204 }
205
207 {
208 m_bufferProgress->SetTotal(maxSize);
209 m_bufferProgress->SetUsed(available);
210 }
211 }
212 else if (event->type() == DecoderHandlerEvent::kOperationStop)
213 {
214 if (m_bufferStatus)
216 }
217
218 if (!handled)
220}
221
223{
224 QString label = tr("Actions");
225
226 auto *menu = new MythMenu(label, this, "actionmenu");
227
228 if (m_lyricData)
229 {
230 menu->AddItem(tr("Find Lyrics"), nullptr, createFindLyricsMenu());
231
233 {
234 if (!m_lyricData->lyrics()->isEmpty())
235 menu->AddItem(tr("Edit Lyrics"), nullptr, nullptr);
236 else
237 menu->AddItem(tr("Add Lyrics"), nullptr, nullptr);
238
239 if (!m_lyricData->lyrics()->isEmpty() && m_lyricData->changed())
240 menu->AddItem(tr("Save Lyrics"), nullptr, nullptr);
241 }
242
243 if (!m_autoScroll)
244 menu->AddItem(tr("Auto Scroll Lyrics"), nullptr, nullptr);
245 }
246
247 menu->AddItem(tr("Other Options"), nullptr, createMainMenu());
248
249 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
250
251 auto *menuPopup = new MythDialogBox(menu, popupStack, "actionmenu");
252
253 if (menuPopup->Create())
254 popupStack->AddScreen(menuPopup);
255 else
256 delete menuPopup;
257}
258
260{
261 QString label = tr("Find Lyrics");
262
263 auto *menu = new MythMenu(label, this, "findlyricsmenu");
264 menu->AddItemV(tr("Search All Grabbers"), QVariant::fromValue(QString("ALL")));
265
266 QStringList strList("MUSIC_LYRICS_GETGRABBERS");
268 {
269 for (int x = 1; x < strList.count(); x++)
270 menu->AddItemV(tr("Search %1").arg(strList.at(x)), QVariant::fromValue(strList.at(x)));
271 }
272
273 return menu;
274}
275
276bool LyricsView::keyPressEvent(QKeyEvent *event)
277{
278 bool handled = false;
279 QStringList actions;
280
282 {
283 // if the lyrics list widget has focus and the UP/DOWN or PAGEUP/PAGEDOWN keys
284 // are pressed turn off auto scroll
286 {
287 GetMythMainWindow()->TranslateKeyPress("Global", event, actions);
288 if (actions.contains("UP") || actions.contains("DOWN") ||
289 actions.contains("PAGEUP") || actions.contains("PAGEDOWN"))
290 m_autoScroll = false;
291 }
292
293 return true;
294 }
295
296 handled = GetMythMainWindow()->TranslateKeyPress("Music", event, actions);
297
298 for (int i = 0; i < actions.size() && !handled; i++)
299 {
300 const QString& action = actions[i];
301 handled = true;
302
303 if (action == "EDIT")
304 {
306 {
308 editLyrics();
309 }
310 else
311 {
312 // pass the EDIT action on for normal tracks if not on the lyrics list
314 handled = false;
315 }
316 }
317 else if (GetFocusWidget() == m_lyricsList && (action == "MARK" || action == "SELECT"))
318 {
319 setLyricTime();
320 }
321 else
322 {
323 handled = false;
324 }
325 }
326
327 if (!handled && MusicCommon::keyPressEvent(event))
328 handled = true;
329
330 return handled;
331}
332
334{
335 // change the time of the next lyric to the playback time
337 {
339
341 if (item)
342 {
343 auto *lyric = item->GetData().value<LyricsLine*>();
344 if (lyric)
345 {
346 lyric->m_time = gPlayer->getOutput()->GetAudiotime() - 750ms;
347 m_lyricData->setChanged(true);
349 m_autoScroll = false;
350 }
351 }
352 }
353}
354
355void LyricsView::findLyrics(const QString &grabber)
356{
357 if (m_lyricData)
358 {
359 if (m_lyricData->changed())
360 m_lyricData->save();
361
362 m_lyricData->disconnect();
363 m_lyricData = nullptr;
364 }
365
366 MusicMetadata *mdata = nullptr;
367
369 {
370 if (!gPlayer->getPlayedTracksList().isEmpty())
371 mdata = gPlayer->getPlayedTracksList().last();
372 }
373 else
374 {
375 mdata = gPlayer->getCurrentMetadata();
376 }
377
378 if (!mdata)
379 return;
380
382
383 if (m_loadingState)
385
386 m_lyricData = mdata->getLyricsData();
387
390
391 m_lyricData->findLyrics(grabber);
392}
393
394void LyricsView::lyricStatusChanged(LyricsData::Status status, const QString &message)
395{
396 switch (status)
397 {
399 {
400 showMessage(message);
401 break;
402 }
403
405 {
406 if (m_loadingState)
408
409 showMessage(message);
410 break;
411 }
412
414 {
415 showLyrics();
416 break;
417 }
418
419 default:
420 if (m_loadingState)
422
423 showMessage("");
424 break;
425 }
426}
427
428void LyricsView::showMessage(const QString &message)
429{
430 if (m_statusText)
431 {
432 if (message.isEmpty())
433 {
436 }
437 else
438 {
439 m_statusText->SetText(message);
441 }
442 }
443}
444
446{
447 if (m_loadingState)
449
450 showMessage("");
451
453
454 QString syncronized = m_lyricData->syncronized() ? tr("Syncronized") : tr("Unsyncronized");
455 new MythUIButtonListItem(m_lyricsList, tr("** Lyrics from %1 (%2) **")
456 .arg(m_lyricData->grabber(), syncronized));
457
458 for (auto * line : std::as_const(*m_lyricData->lyrics()))
459 {
460 if (line)
461 new MythUIButtonListItem(m_lyricsList, line->m_lyric, QVariant::fromValue(line));
462 }
463
464 m_autoScroll = true;
465}
466
468{
469 if (m_lyricData)
470 m_lyricData->save();
471}
472
474{
476
477 auto *editDialog = new EditLyricsDialog(mainStack, m_lyricData);
478
479 if (!editDialog->Create())
480 {
481 delete editDialog;
482 return;
483 }
484 connect(editDialog, &EditLyricsDialog::haveResult, this, &LyricsView::editFinished);
485 mainStack->AddScreen(editDialog);
486}
487
489{
490 if (result)
491 showLyrics();
492}
493
495// EditLyricsDialog
496
498 MythScreenStack *parent, LyricsData *sourceData) :
499 MythScreenType(parent, "EditLyricsDialog"),
500 m_sourceData(sourceData)
501{
502}
503
505{
506 // Load the theme for this screen
507 bool foundtheme = LoadWindowFromXML("music-ui.xml", "editlyrics", this);
508 if (!foundtheme)
509 return false;
510
511 bool err = false;
512 UIUtilE::Assign(this, m_grabberEdit, "grabberedit", &err);
513 UIUtilE::Assign(this, m_syncronizedCheck, "syncronizedcheck", &err);
514 UIUtilE::Assign(this, m_titleEdit, "titleedit", &err);
515 UIUtilE::Assign(this, m_artistEdit, "artistedit", &err);
516 UIUtilE::Assign(this, m_albumEdit, "albumedit", &err);
517 UIUtilE::Assign(this, m_lyricsEdit, "lyricsedit", &err);
518 UIUtilE::Assign(this, m_okButton, "okbutton", &err);
519 UIUtilE::Assign(this, m_cancelButton, "cancelbutton", &err);
520
521 if (err)
522 {
523 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'editlyrics'");
524 return false;
525 }
526
530
536
537 loadLyrics();
538
540
541 return true;
542}
543
545{
546 bool handled = false;
547 QStringList actions;
548
549 if (GetFocusWidget()->keyPressEvent(event))
550 return true;
551
552 handled = GetMythMainWindow()->TranslateKeyPress("Music", event, actions);
553
554 for (int i = 0; i < actions.size() && !handled; i++)
555 {
556 const QString& action = actions[i];
557
558 if (action == "ESCAPE" && somethingChanged())
559 {
561 return true;
562 }
563 }
564
565 return MythScreenType::keyPressEvent(event);
566}
567
569{
570 QString lyrics;
571 LyricsLineMap::iterator i = m_sourceData->lyrics()->begin();
572 while (i != m_sourceData->lyrics()->end())
573 {
574 LyricsLine *line = (*i);
575 ++i;
576
578
579 if (i != m_sourceData->lyrics()->end())
580 lyrics += '\n';
581 }
582
583 m_lyricsEdit->SetText(lyrics);
584}
585
586void EditLyricsDialog::syncronizedChanged([[maybe_unused]] bool syncronized)
587{
588 loadLyrics();
589}
590
592{
593 bool changed = false;
594
595 changed |= m_sourceData->artist() != m_artistEdit->GetText();
596 changed |= m_sourceData->album() != m_albumEdit->GetText();
597 changed |= m_sourceData->title() != m_titleEdit->GetText();
598 changed |= m_sourceData->grabber() != m_grabberEdit->GetText();
600
601 QStringList lines = m_lyricsEdit->GetText().split('\n');
602
603 if (lines.count() != m_sourceData->lyrics()->count())
604 return true;
605
606 int x = 0;
607 for (auto * line : std::as_const(*m_sourceData->lyrics()))
608 {
609 if (line->toString(m_sourceData->syncronized()) != lines.at(x))
610 changed = true;
611 ++x;
612 }
613
614 return changed;
615}
616
618{
619 if (!somethingChanged())
620 {
621 emit haveResult(false);
622 Close();
623 return;
624 }
625
626 QStringList lyrics = m_lyricsEdit->GetText().split('\n');
627
634 m_sourceData->setLyrics(lyrics);
636 m_sourceData->setChanged(false);
637
638 emit haveResult(true);
639 Close();
640}
641
643{
644 if (!ok)
645 {
646 emit haveResult(false);
647 Close();
648 return;
649 }
650
651 okPressed();
652}
653
655{
656 if (somethingChanged())
657 {
658 ShowOkPopup(tr("Save changes?"), this, &EditLyricsDialog::saveEdits, true);
659 return;
660 }
661
662 emit haveResult(false);
663 Close();
664}
virtual std::chrono::milliseconds GetAudiotime(void)=0
Events sent by the DecoderHandler and it's helper classes.
static const Type kBufferStatus
static const Type kOperationStart
void getBufferStatus(int *available, int *maxSize) const
static const Type kOperationStop
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:41
static const Type kEventType
Definition: mythdialogbox.h:56
MythUIButton * m_cancelButton
Definition: lyricsview.h:92
void cancelPressed(void)
Definition: lyricsview.cpp:654
void saveEdits(bool ok)
Definition: lyricsview.cpp:642
void syncronizedChanged(bool syncronized)
Definition: lyricsview.cpp:586
void loadLyrics(void)
Definition: lyricsview.cpp:568
MythUITextEdit * m_grabberEdit
Definition: lyricsview.h:86
EditLyricsDialog(MythScreenStack *parent, LyricsData *sourceData)
Definition: lyricsview.cpp:497
bool Create(void) override
Definition: lyricsview.cpp:504
MythUITextEdit * m_albumEdit
Definition: lyricsview.h:90
MythUITextEdit * m_lyricsEdit
Definition: lyricsview.h:91
MythUITextEdit * m_artistEdit
Definition: lyricsview.h:89
MythUIButton * m_okButton
Definition: lyricsview.h:93
MythUITextEdit * m_titleEdit
Definition: lyricsview.h:88
bool keyPressEvent(QKeyEvent *e) override
Key event handler.
Definition: lyricsview.cpp:544
MythUICheckBox * m_syncronizedCheck
Definition: lyricsview.h:87
void haveResult(bool ok)
bool somethingChanged(void)
Definition: lyricsview.cpp:591
void okPressed(void)
Definition: lyricsview.cpp:617
LyricsData * m_sourceData
Definition: lyricsview.h:84
QString artist(void)
Definition: lyricsdata.h:67
void setGrabber(const QString &grabber)
Definition: lyricsdata.h:65
void findLyrics(const QString &grabber)
Definition: lyricsdata.cpp:50
LyricsLineMap * lyrics(void)
Definition: lyricsdata.h:76
void save(void)
Definition: lyricsdata.cpp:117
void setSyncronized(bool syncronized)
Definition: lyricsdata.h:80
QString grabber(void)
Definition: lyricsdata.h:64
QString album(void)
Definition: lyricsdata.h:70
void setTitle(const QString &title)
Definition: lyricsdata.h:74
void setLyrics(const QStringList &lyrics)
Definition: lyricsdata.cpp:313
void statusChanged(LyricsData::Status status, const QString &message)
bool syncronized(void) const
Definition: lyricsdata.h:79
QString title(void)
Definition: lyricsdata.h:73
@ STATUS_NOTFOUND
Definition: lyricsdata.h:90
@ STATUS_FOUND
Definition: lyricsdata.h:89
@ STATUS_SEARCHING
Definition: lyricsdata.h:88
void setArtist(const QString &artist)
Definition: lyricsdata.h:68
void clear(void)
Definition: lyricsdata.cpp:27
void setChanged(bool changed)
Definition: lyricsdata.h:83
bool changed(void) const
Definition: lyricsdata.h:82
void setAlbum(const QString &album)
Definition: lyricsdata.h:71
QString toString(bool syncronized)
Definition: lyricsdata.h:29
std::chrono::milliseconds m_time
Definition: lyricsdata.h:26
void lyricStatusChanged(LyricsData::Status status, const QString &message)
Definition: lyricsview.cpp:394
void showLyrics(void)
Definition: lyricsview.cpp:445
MythUIButtonList * m_lyricsList
Definition: lyricsview.h:47
bool m_autoScroll
Definition: lyricsview.h:55
void editLyrics(void)
Definition: lyricsview.cpp:473
MythUIText * m_statusText
Definition: lyricsview.h:48
void showMessage(const QString &message)
Definition: lyricsview.cpp:428
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
Definition: lyricsview.cpp:276
MythUIText * m_bufferStatus
Definition: lyricsview.h:50
bool Create(void) override
Definition: lyricsview.cpp:58
void ShowMenu(void) override
Definition: lyricsview.cpp:222
void saveLyrics(void)
Definition: lyricsview.cpp:467
void setLyricTime(void)
Definition: lyricsview.cpp:333
MythMenu * createFindLyricsMenu(void)
Definition: lyricsview.cpp:259
void editFinished(bool result)
Definition: lyricsview.cpp:488
void customEvent(QEvent *event) override
Definition: lyricsview.cpp:95
void findLyrics(const QString &grabber="ALL")
Definition: lyricsview.cpp:355
MythUIProgressBar * m_bufferProgress
Definition: lyricsview.h:51
LyricsView(MythScreenStack *parent, MythScreenType *parentScreen)
Definition: lyricsview.cpp:39
LyricsData * m_lyricData
Definition: lyricsview.h:53
~LyricsView(void) override
Definition: lyricsview.cpp:47
MythUIStateType * m_loadingState
Definition: lyricsview.h:49
MythMenu * createMainMenu(void)
MusicView m_currentView
Definition: musiccommon.h:141
bool CreateCommon(void)
Definition: musiccommon.cpp:78
bool keyPressEvent(QKeyEvent *e) override
Key event handler.
void customEvent(QEvent *event) override
LyricsData * getLyricsData(void)
static const Type kPlayedTracksChangedEvent
Definition: musicplayer.h:50
static const Type kTrackChangeEvent
Definition: musicplayer.h:39
MusicMetadata * getCurrentMetadata(void)
get the metadata for the current track in the playlist
PlayMode getPlayMode(void)
Definition: musicplayer.h:77
std::chrono::seconds getCurrentTrackTime(void) const
Definition: musicplayer.h:147
QList< MusicMetadata * > & getPlayedTracksList(void)
Definition: musicplayer.h:141
bool isPlaying(void) const
Definition: musicplayer.h:109
AudioOutput * getOutput(void)
Definition: musicplayer.h:128
bool SendReceiveStringList(QStringList &strlist, bool quickTimeout=false, bool block=true)
Send a message to the backend and wait for a response.
Basic menu dialog, message and a list of options.
MythScreenStack * GetMainStack()
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
MythScreenStack * GetStack(const QString &Stackname)
void addListener(QObject *listener)
Add a listener to the observable.
void removeListener(QObject *listener)
Remove a listener to the observable.
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
Screen in which all other widgets are contained and rendered.
void BuildFocusList(void)
MythUIType * GetFocusWidget(void) const
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
virtual void Close()
MythUIButtonListItem * GetItemCurrent() const
void SetItemCurrent(MythUIButtonListItem *item)
void Reset() override
Reset the widget to it's original state, should not reset changes made by the theme.
int GetCurrentPos() const
void itemClicked(MythUIButtonListItem *item)
MythUIButtonListItem * GetItemAt(int pos) const
void Clicked()
void SetCheckState(MythUIStateType::StateType state)
void toggled(bool)
bool GetBooleanCheckState(void) const
void SetUsed(int value)
void SetTotal(int value)
bool DisplayState(const QString &name)
QString GetText(void) const
void SetText(const QString &text, bool moveCursor=true)
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
Definition: mythuitext.cpp:65
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:115
void Hide(void)
void Show(void)
static const Type kInfo
Definition: output.h:65
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
@ MV_LYRICS
Definition: musiccommon.h:34
MusicPlayer * gPlayer
Definition: musicplayer.cpp:38
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
static MythThemedMenu * menu
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
Definition: mythuiutils.h:27