mpvpart: context menu with advanced subtitles submenu

Signed-off-by: Mavridis Philippe <mavridisf@gmail.com>
feat/libmpv-backend
Mavridis Philippe 2 years ago
parent 62ee1ff78b
commit 24b7f3423a

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

@ -22,6 +22,8 @@
// TQt
#include <tqapplication.h>
#include <tqcursor.h>
#include <tqevent.h>
// TDE
#include <tdelocale.h>
@ -75,10 +77,7 @@ void MpvEventThread::processEvent(mpv_event *event) {
<< msg->text << endl;
m_part->setStatusBarText(TQString("MPV %1 (%2): %3").arg(msg->prefix, msg->level, msg->text));
if (TQString(msg->level) == "error") {
processErrorMessage(msg->prefix, msg->level, msg->text);
}
processMessage(msg->prefix, msg->level, msg->text);
break;
}
@ -96,8 +95,9 @@ void MpvEventThread::processEvent(mpv_event *event) {
}
}
void MpvEventThread::processErrorMessage(TQString prefix, TQString level, TQString text) {
if (prefix == "recorder") {
void MpvEventThread::processMessage(TQString prefix, TQString level, TQString text) {
// Stream recorder errors
if (level == "error" && prefix == "recorder") {
kdDebug() << "recorder error:" << text << endl;
MpvErrorEvent *ee = new MpvErrorEvent(
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);
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
*/
#ifndef __LIBMPVEVENT_H__
#define __LIBMPVEVENT_H__
// TQt
#include <tqthread.h>
#include <tqevent.h>
@ -90,8 +93,10 @@ class MpvEventThread : public TQThread {
private:
void initPropertyObservers();
void processEvent(mpv_event *event);
void processErrorMessage(TQString prefix, TQString level, TQString text);
void processMessage(TQString prefix, TQString level, TQString text);
private:
MpvPart *m_part;
};
#endif // __LIBMPVEVENT_H__

@ -26,6 +26,9 @@
#include <tqslider.h>
#include <tqpushbutton.h>
#include <tqfile.h>
#include <tqstringlist.h>
#include <tqpopupmenu.h>
#include <tqcursor.h>
// TDE
#include <tdeparts/genericfactory.h>
@ -46,6 +49,7 @@
// Part
#include "libmpv_part.h"
#include "libmpv_event.h"
#include "libmpv_widget.h"
typedef KParts::GenericFactory<MpvPart> MpvPartFactory;
K_EXPORT_COMPONENT_FACTORY (libmpvpart, MpvPartFactory);
@ -54,27 +58,27 @@ MpvPart::MpvPart(TQWidget* parentWidget, const char* widgetName, TQObject* paren
: KaffeinePart(parent, name ? name : "MpvPart"),
m_current(0),
m_seeking(false),
m_recordFilePath()
m_recordFilePath(),
m_context(nullptr)
{
// Create an instance of this class
setInstance(MpvPartFactory::instance());
// Create container widget
m_player = new TQWidget(parentWidget);
m_player->setBackgroundColor(TQt::black);
m_player->setFocusPolicy(TQ_ClickFocus);
setWidget(m_player);
// Initialize and embed mpv
if (!initMpv()) {
kdError() << "libmpvpart: initialization of mpv failed!" << endl;
emit canceled(i18n("MPV initialization failed!"));
return;
}
// Create an instance of this class
setInstance(MpvPartFactory::instance());
// Initialize GUI
setXMLFile("libmpv_part.rc");
initActions();
// Create container widget
m_player = new MpvContainerWidget(this, parentWidget);
setWidget(m_player);
// Initialize and embed mpv
if (!initMpv()) {
kdError() << "libmpvpart: initialization of mpv failed!" << endl;
emit canceled(i18n("MPV initialization failed!"));
return;
}
emit setStatusBarText(i18n("Ready"));
}
@ -166,6 +170,9 @@ void MpvPart::initActions()
new KWidgetAction(m_recordFile, i18n("Recording file"), 0, 0, 0, actionCollection(), "record_file");
updateRecordFileLabel();
/*** Context menu ***/
new TDEAction(i18n("Add subtitles"), "text-x-generic", 0, this, SLOT(slotAddSubtitles()), actionCollection(), "subtitles_add");
resetTime();
stateChanged("disable_all");
}
@ -325,13 +332,7 @@ void MpvPart::slotPlay() {
const char *args[] = {"loadfile", curMRL.url().local8Bit(), nullptr};
mpv_command_async(m_mpv, 0, args);
// Add subtitles
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);
}
slotReloadSubtitles();
}
else {
emit signalRequestCurrentTrack();
@ -467,6 +468,23 @@ void MpvPart::slotMute() {
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() {
if (m_recordAction->isChecked()) {
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"

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