mpvpart: context menu with advanced subtitles submenu

Signed-off-by: Mavridis Philippe <mavridisf@gmail.com>
Mavridis Philippe 2 years ago
parent da5d94610d
commit 05a16a3e86
No known key found for this signature in database
GPG Key ID: 93F66F98F906147D

@ -22,8 +22,7 @@ link_directories(
tde_add_kpart( libmpvpart AUTOMOC tde_add_kpart( libmpvpart AUTOMOC
SOURCES SOURCES libmpv_part.cpp libmpv_event.cpp libmpv_widget.cpp
libmpv_part.cpp libmpv_event.cpp
LINK LINK
tdecore-shared tdecore-shared
tdeui-shared tdeui-shared

@ -22,6 +22,8 @@
// TQt // TQt
#include <tqapplication.h> #include <tqapplication.h>
#include <tqcursor.h>
#include <tqevent.h>
// TDE // TDE
#include <tdelocale.h> #include <tdelocale.h>
@ -75,10 +77,7 @@ void MpvEventThread::processEvent(mpv_event *event) {
<< msg->text << endl; << msg->text << endl;
m_part->setStatusBarText(TQString("MPV %1 (%2): %3").arg(msg->prefix, msg->level, msg->text)); m_part->setStatusBarText(TQString("MPV %1 (%2): %3").arg(msg->prefix, msg->level, msg->text));
processMessage(msg->prefix, msg->level, msg->text);
if (TQString(msg->level) == "error") {
processErrorMessage(msg->prefix, msg->level, msg->text);
}
break; break;
} }
@ -96,8 +95,9 @@ void MpvEventThread::processEvent(mpv_event *event) {
} }
} }
void MpvEventThread::processErrorMessage(TQString prefix, TQString level, TQString text) { void MpvEventThread::processMessage(TQString prefix, TQString level, TQString text) {
if (prefix == "recorder") { // Stream recorder errors
if (level == "error" && prefix == "recorder") {
kdDebug() << "recorder error:" << text << endl; kdDebug() << "recorder error:" << text << endl;
MpvErrorEvent *ee = new MpvErrorEvent( MpvErrorEvent *ee = new MpvErrorEvent(
i18n("An error occurred in the stream recorder."), i18n("An error occurred in the stream recorder."),
@ -106,4 +106,42 @@ void MpvEventThread::processErrorMessage(TQString prefix, TQString level, TQStri
TQApplication::postEvent(m_part, ee); TQApplication::postEvent(m_part, ee);
m_part->stopRecording(); m_part->stopRecording();
} }
// HACK: click-through for the context menu
// MPV does not yet natively support click-through and thus eats up all mouse
// events on its embedded window (https://github.com/mpv-player/mpv/issues/8938).
// Moreover, there seems to be no way to natively listen for keyboard/mouse events.
// This is a nasty workaround.
else if (level == "warn" && prefix == "input" && text.contains("MBTN_")) {
int button = TQt::NoButton;
if (text.contains("MBTN_LEFT")) {
button = TQt::LeftButton;
}
else if (text.contains("MBTN_MID")) {
button = TQt::MidButton;
}
else if (text.contains("MBTN_RIGHT")) {
button = TQt::RightButton;
}
// Emulate mouse events
TQMouseEvent *me = new TQMouseEvent(TQEvent::MouseButtonPress, TQCursor::pos(), button, button);
TQApplication::postEvent(m_part->parent(), me);
me = new TQMouseEvent(TQEvent::MouseButtonRelease, TQCursor::pos(), button, button);
TQApplication::postEvent(m_part->parent(), me);
// Emulate doubleclicks
if (text.contains("DBL")) {
TQMouseEvent *me = new TQMouseEvent(TQEvent::MouseButtonDblClick, TQCursor::pos(), button, button);
TQApplication::postEvent(m_part->parent(), me);
}
// Context menu emulation
m_part->hideContextMenu();
TQContextMenuEvent *cme;
if (button == TQt::RightButton) {
cme = new TQContextMenuEvent(TQContextMenuEvent::Mouse, TQCursor::pos(), TQt::RightButton);
TQApplication::postEvent(m_part->m_player, cme);
}
}
} }

@ -20,6 +20,9 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/ */
#ifndef __LIBMPVEVENT_H__
#define __LIBMPVEVENT_H__
// TQt // TQt
#include <tqthread.h> #include <tqthread.h>
#include <tqevent.h> #include <tqevent.h>
@ -90,8 +93,10 @@ class MpvEventThread : public TQThread {
private: private:
void initPropertyObservers(); void initPropertyObservers();
void processEvent(mpv_event *event); void processEvent(mpv_event *event);
void processErrorMessage(TQString prefix, TQString level, TQString text); void processMessage(TQString prefix, TQString level, TQString text);
private: private:
MpvPart *m_part; MpvPart *m_part;
}; };
#endif // __LIBMPVEVENT_H__

@ -26,6 +26,9 @@
#include <tqslider.h> #include <tqslider.h>
#include <tqpushbutton.h> #include <tqpushbutton.h>
#include <tqfile.h> #include <tqfile.h>
#include <tqstringlist.h>
#include <tqpopupmenu.h>
#include <tqcursor.h>
// TDE // TDE
#include <tdeparts/genericfactory.h> #include <tdeparts/genericfactory.h>
@ -46,6 +49,7 @@
// Part // Part
#include "libmpv_part.h" #include "libmpv_part.h"
#include "libmpv_event.h" #include "libmpv_event.h"
#include "libmpv_widget.h"
typedef KParts::GenericFactory<MpvPart> MpvPartFactory; typedef KParts::GenericFactory<MpvPart> MpvPartFactory;
K_EXPORT_COMPONENT_FACTORY (libmpvpart, MpvPartFactory); K_EXPORT_COMPONENT_FACTORY (libmpvpart, MpvPartFactory);
@ -54,15 +58,18 @@ MpvPart::MpvPart(TQWidget* parentWidget, const char* widgetName, TQObject* paren
: KaffeinePart(parent, name ? name : "MpvPart"), : KaffeinePart(parent, name ? name : "MpvPart"),
m_current(0), m_current(0),
m_seeking(false), m_seeking(false),
m_recordFilePath() m_recordFilePath(),
m_context(nullptr)
{ {
// Create an instance of this class // Create an instance of this class
setInstance(MpvPartFactory::instance()); setInstance(MpvPartFactory::instance());
// Initialize GUI
setXMLFile("libmpv_part.rc");
initActions();
// Create container widget // Create container widget
m_player = new TQWidget(parentWidget); m_player = new MpvContainerWidget(this, parentWidget);
m_player->setBackgroundColor(TQt::black);
m_player->setFocusPolicy(TQ_ClickFocus);
setWidget(m_player); setWidget(m_player);
// Initialize and embed mpv // Initialize and embed mpv
@ -72,9 +79,6 @@ MpvPart::MpvPart(TQWidget* parentWidget, const char* widgetName, TQObject* paren
return; return;
} }
// Initialize GUI
setXMLFile("libmpv_part.rc");
initActions();
emit setStatusBarText(i18n("Ready")); emit setStatusBarText(i18n("Ready"));
} }
@ -166,6 +170,9 @@ void MpvPart::initActions()
new KWidgetAction(m_recordFile, i18n("Recording file"), 0, 0, 0, actionCollection(), "record_file"); new KWidgetAction(m_recordFile, i18n("Recording file"), 0, 0, 0, actionCollection(), "record_file");
updateRecordFileLabel(); updateRecordFileLabel();
/*** Context menu ***/
new TDEAction(i18n("Add subtitles"), "text-x-generic", 0, this, SLOT(slotAddSubtitles()), actionCollection(), "subtitles_add");
resetTime(); resetTime();
stateChanged("disable_all"); stateChanged("disable_all");
} }
@ -325,13 +332,7 @@ void MpvPart::slotPlay() {
const char *args[] = {"loadfile", curMRL.url().local8Bit(), nullptr}; const char *args[] = {"loadfile", curMRL.url().local8Bit(), nullptr};
mpv_command_async(m_mpv, 0, args); mpv_command_async(m_mpv, 0, args);
// Add subtitles slotReloadSubtitles();
if ((!curMRL.subtitleFiles().isEmpty()) && (curMRL.currentSubtitle() > -1)) {
kdDebug() << "adding subs" << endl;
const char *args[] = {"sub-add", curMRL.subtitleFiles()[curMRL.currentSubtitle()].local8Bit(), nullptr};
mpv_command_async(m_mpv, 0, args);
}
} }
else { else {
emit signalRequestCurrentTrack(); emit signalRequestCurrentTrack();
@ -467,6 +468,23 @@ void MpvPart::slotMute() {
mpv_set_property(m_mpv, "ao-mute", MPV_FORMAT_FLAG, &value); mpv_set_property(m_mpv, "ao-mute", MPV_FORMAT_FLAG, &value);
} }
void MpvPart::slotReloadSubtitles() {
TQStringList subtitles = m_mrl.subtitleFiles();
int subId = m_mrl.currentSubtitle();
// Remove existing subtitles synchronously
const char *rm_args[] = {"sub-remove", nullptr};
mpv_command(m_mpv, rm_args);
if ((!subtitles.isEmpty()) && (subId > -1)) {
kdDebug() << "adding subtitle file " << subtitles[subId] << endl;
// Add requested subtitles
const char *add_args[] = {"sub-add", subtitles[subId].local8Bit(), nullptr};
mpv_command_async(m_mpv, 0, add_args);
}
}
void MpvPart::slotToggleRecording() { void MpvPart::slotToggleRecording() {
if (m_recordAction->isChecked()) { if (m_recordAction->isChecked()) {
startRecording(); startRecording();
@ -527,4 +545,98 @@ void MpvPart::updateRecordFileLabel() {
); );
} }
void MpvPart::slotAddSubtitles() {
TQString openURL = m_mrl.url();
TQString subtitleURL = KFileDialog::getOpenURL(openURL,
i18n("*.smi *.srt *.sub *.txt *.ssa *.asc|Subtitle Files\n*.*|All Files"),
0, i18n("Select Subtitle File")).path();
kdDebug()<<"slotAddSubtitles: "<<subtitleURL<<endl;
kdDebug()<<" exists: "<<TQFile::exists(subtitleURL)<<endl;
if (subtitleURL.isEmpty() || !TQFile::exists(subtitleURL)) {
return;
}
m_mrl.addSubtitleFile(subtitleURL);
emit signalNewMeta(m_mrl);
}
void MpvPart::slotContextSetSubtitles(int choice) {
int subId = m_subs->itemParameter(choice);
if (subId == choice) {
// If itemParameter() returns the menu item id, then we know that
// the item has no item parameter, hence it is not an option that
// we handle through this slot.
return;
}
kdDebug() << "switching to subtitle: " << subId
<< " (" << m_subs->text(choice) << ")" << endl;
m_mrl.setCurrentSubtitle(subId);
emit signalNewMeta(m_mrl);
slotReloadSubtitles();
}
void MpvPart::showContextMenu() {
/*** Context menu ***/
m_context = new TQPopupMenu(0);
actionCollection()->action("player_play")->plug(m_context);
actionCollection()->action("player_pause")->plug(m_context);
actionCollection()->action("player_stop")->plug(m_context);
m_context->insertSeparator();
actionCollection()->action("player_previous")->plug(m_context);
actionCollection()->action("player_next")->plug(m_context);
m_context->insertSeparator();
/*** Subtitles submenu ***/
m_subs = new TQPopupMenu(0);
actionCollection()->action("subtitles_add")->plug(m_subs);
TQStringList subtitles = m_mrl.subtitleFiles();
if (m_mrl.isEmpty()) {
m_subs->setEnabled(false);
}
m_subs->insertSeparator();
int noSubsItem = m_subs->insertItem(i18n("None"));
m_subs->setItemParameter(noSubsItem, -1);
if (!subtitles.empty()) {
int index;
for (TQStringList::Iterator it = subtitles.begin(); it != subtitles.end(); ++it) {
int id = m_subs->insertItem((*it));
m_subs->setItemParameter(id, index);
// Comparing an index is cheaper than doing a string comparison
if (index == m_mrl.currentSubtitle()) {
m_subs->setItemChecked(id, true);
}
index++;
}
connect(m_subs, SIGNAL(activated(int)), this, SLOT(slotContextSetSubtitles(int)));
}
if (subtitles.empty() || m_mrl.currentSubtitle() < 0) {
m_subs->setItemChecked(noSubsItem, true);
}
m_context->insertItem(i18n("Sub&titles"), m_subs);
m_context->exec(TQCursor::pos());
hideContextMenu();
}
void MpvPart::hideContextMenu() {
if (m_context) {
delete m_context;
m_context = nullptr;
}
if (!m_subs) {
delete m_subs;
m_subs = nullptr;
}
}
#include "libmpv_part.moc" #include "libmpv_part.moc"

@ -37,9 +37,11 @@
class TQLabel; class TQLabel;
class TQSlider; class TQSlider;
class TQPopupMenu;
class TQPushButton; class TQPushButton;
class TDEToggleAction; class TDEToggleAction;
class MpvEventThread; class MpvEventThread;
class MpvContainerWidget;
/** /**
* MpvPart -- A KaffeinePart which uses libmpv as backend * MpvPart -- A KaffeinePart which uses libmpv as backend
@ -52,6 +54,7 @@ class MpvPart : public KaffeinePart
TQ_OBJECT TQ_OBJECT
friend class MpvEventThread; friend class MpvEventThread;
friend class MpvContainerWidget;
public: public:
MpvPart(TQWidget*, const char*, TQObject*, const char*, const TQStringList&); MpvPart(TQWidget*, const char*, TQObject*, const char*, const TQStringList&);
@ -81,27 +84,38 @@ class MpvPart : public KaffeinePart
void slotPlay(); void slotPlay();
void slotPause(bool pause); void slotPause(bool pause);
void slotTogglePause(); void slotTogglePause();
void slotSetVolume(uint); /* percent */
void slotSetPosition(uint); /* percent */
void slotStop(); void slotStop();
void slotMute(); void slotMute();
void slotSetVolume(uint); /* percent */
void slotPrevious(); void slotPrevious();
void slotNext(); void slotNext();
void slotSetPosition(uint); /* percent */
void slotToggleRecording(); void slotToggleRecording();
void startRecording(); void startRecording();
void stopRecording(); void stopRecording();
void slotSetRecordingFile(); void slotSetRecordingFile();
void slotAddSubtitles();
void slotReloadSubtitles();
void showContextMenu();
void hideContextMenu();
signals: signals:
void mpvEvents(); void mpvEvents();
protected: protected:
mpv_handle *m_mpv; mpv_handle *m_mpv;
MRL m_mrl;
private slots: private slots:
void slotStartSeeking(); void slotStartSeeking();
void slotStopSeeking(); void slotStopSeeking();
void slotSetSeekingPos(int pos); void slotSetSeekingPos(int pos);
void slotContextSetSubtitles(int choice);
// Overloaded method with "int" argument // Overloaded method with "int" argument
// Used in connection with m_volume::sliderMoved(int) // Used in connection with m_volume::sliderMoved(int)
@ -122,8 +136,9 @@ class MpvPart : public KaffeinePart
TQSlider *m_volume; TQSlider *m_volume;
TQPushButton *m_playtime; TQPushButton *m_playtime;
TQLabel *m_recordFile; TQLabel *m_recordFile;
TQPopupMenu *m_context;
TQPopupMenu *m_subs;
MRL m_mrl;
TQValueList<MRL> m_playlist; TQValueList<MRL> m_playlist;
uint m_current; uint m_current;

@ -0,0 +1,36 @@
/*
* Kaffeine libmpv part
* Copyright (C) 2023 Mavridis Philippe <mavridisf@gmail.com>
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
// Part
#include "libmpv_widget.h"
#include "libmpv_part.h"
MpvContainerWidget::MpvContainerWidget(MpvPart *part, TQWidget* parentWidget)
: TQWidget(parentWidget), m_part(part)
{
setBackgroundColor(TQt::black);
setFocusPolicy(TQ_ClickFocus);
}
MpvContainerWidget::~MpvContainerWidget() {
}
void MpvContainerWidget::contextMenuEvent(TQContextMenuEvent *) {
m_part->showContextMenu();
}

@ -0,0 +1,39 @@
/*
* Kaffeine libmpv part
* Copyright (C) 2023 Mavridis Philippe <mavridisf@gmail.com>
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#ifndef __LIBMPVWIDGET_H__
#define __LIBMPVWIDGET_H__
#include <tqwidget.h>
class MpvPart;
class MpvContainerWidget : public TQWidget {
public:
MpvContainerWidget(MpvPart *part, TQWidget *parentWidget);
~MpvContainerWidget();
private:
void contextMenuEvent(TQContextMenuEvent *);
private:
MpvPart *m_part;
};
#endif // __LIBMPVWIDGET_H__
Loading…
Cancel
Save