diff --git a/CMakeLists.txt b/CMakeLists.txt index cd7189aa..7166ec73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,9 +19,13 @@ else() find_package(DummyPkgConfig REQUIRED) endif() -find_package(Qt5Widgets 5.6 REQUIRED) -if(Qt5Widgets_VERSION VERSION_LESS 5.6.3) - message(AUTHOR_WARNING "Qt5 >= 5.6.3, >= 5.9.1 is recommended") +option(USE_QT4 "Build with Qt4" ON) + +if(USE_QT4) + find_package(Qt4 REQUIRED QtCore QtGui) + include("${QT_USE_FILE}") +else() + find_package(Qt5Widgets 5.10 REQUIRED) endif() set(CMAKE_LINK_DEPENDS_NO_SHARED ON) @@ -49,6 +53,10 @@ endif() add_definitions(-D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS -DQT_USE_FAST_OPERATOR_PLUS) +if(APPLE AND USE_QT4) + add_definitions(-DQ_OS_MACOS) +endif() + if(WIN32) option(USE_CMD "Show CMD when running QMPlay2" OFF) mark_as_advanced(USE_CMD) @@ -283,7 +291,7 @@ endif() if(APPLE) find_library(APPKIT_LIBRARY AppKit) - find_library(IOKIT_LIBRARY IoKit) + find_library(IOKIT_LIBRARY IOKit) set(SOLID_ACTIONS_DEFAULT "None") set(DEFAULT_INSTALL_RPATH ON) endif() @@ -306,7 +314,11 @@ if(USE_MEDIABROWSER) endif() if(NOT ANDROID) + if(USE_QT4) + find_package(Qt4 QUIET OPTIONAL_COMPONENTS QtSvg) + else() find_package(Qt5Svg QUIET) + endif() else() find_package(Qt5Svg REQUIRED) find_package(Qt5AndroidExtras REQUIRED) @@ -337,7 +349,7 @@ if(NOT APPLE AND NOT WIN32) endif() # Show warning if QtSvg doesn't exist -if(NOT Qt5Svg_FOUND) +if(NOT Qt5Svg_FOUND AND NOT Qt4_FOUND) message(WARNING "Missing QtSvg module - SVG icons will not be visible!") endif() diff --git a/lang/CMakeLists.txt b/lang/CMakeLists.txt index db28debd..bb5043e7 100644 --- a/lang/CMakeLists.txt +++ b/lang/CMakeLists.txt @@ -12,8 +12,12 @@ else() endforeach() endif() -find_package(Qt5LinguistTools REQUIRED) -qt5_add_translation(QM_FILES ${QMPLAY2_TSS}) +if(USE_QT4) + qt4_add_translation(QM_FILES ${QMPLAY2_TSS}) +else() + find_package(Qt5LinguistTools REQUIRED) + qt5_add_translation(QM_FILES ${QMPLAY2_TSS}) +endif() add_custom_target(translations ALL DEPENDS ${QM_FILES}) diff --git a/src/gui/AboutWidget.cpp b/src/gui/AboutWidget.cpp index dfc52ccd..0f867e67 100644 --- a/src/gui/AboutWidget.cpp +++ b/src/gui/AboutWidget.cpp @@ -52,7 +52,8 @@ AboutWidget::AboutWidget() QTabWidget *tabW = new QTabWidget; - const QFont font(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + QFont font("Monospace"); + font.setStyleHint(QFont::TypeWriter); logE = new QPlainTextEdit; logE->setFont(font); diff --git a/src/gui/AboutWidget.hpp b/src/gui/AboutWidget.hpp index 98994f7a..59af0de5 100644 --- a/src/gui/AboutWidget.hpp +++ b/src/gui/AboutWidget.hpp @@ -24,14 +24,14 @@ class QPlainTextEdit; class QPushButton; -class AboutWidget final : public QWidget +class AboutWidget : public QWidget { Q_OBJECT public: AboutWidget(); private: - void showEvent(QShowEvent *) override; - void closeEvent(QCloseEvent *) override; + void showEvent(QShowEvent *) override final; + void closeEvent(QCloseEvent *) override final; QPlainTextEdit *logE, *clE, *auE; QPushButton *clrLogB; diff --git a/src/gui/AddressDialog.hpp b/src/gui/AddressDialog.hpp index 4a6f779a..3835b97d 100644 --- a/src/gui/AddressDialog.hpp +++ b/src/gui/AddressDialog.hpp @@ -24,12 +24,12 @@ #include #include -class AddressDialog final : public QDialog +class AddressDialog : public QDialog { Q_DECLARE_TR_FUNCTIONS(AddressDialog) public: AddressDialog(QWidget *); - ~AddressDialog(); + ~AddressDialog() final; inline bool addAndPlay() const { diff --git a/src/gui/AudioThr.hpp b/src/gui/AudioThr.hpp index f2382376..c0842611 100644 --- a/src/gui/AudioThr.hpp +++ b/src/gui/AudioThr.hpp @@ -28,14 +28,14 @@ class QMPlay2Extensions; class PlayClass; class AudioFilter; -class AudioThr final : public AVThread +class AudioThr : public AVThread { Q_OBJECT public: AudioThr(PlayClass &, const QStringList &pluginsName = {}); - ~AudioThr(); + ~AudioThr() final; - void stop(bool terminate = false) override; + void stop(bool terminate = false) override final; void clearVisualizations(); bool setParams(uchar realChn, uint realSRate, uchar chn, uint sRate, bool resamplerFirst); @@ -47,7 +47,7 @@ public: allowAudioDrain = true; } private: - void run() override; + void run() override final; bool resampler_create(); @@ -55,7 +55,7 @@ private: inline uint currentSampleRate() const; #ifdef Q_OS_WIN - void timerEvent(QTimerEvent *) override; + void timerEvent(QTimerEvent *) override final; #endif SndResampler sndResampler; diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 7f8c22b5..8bd630af 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -34,10 +34,15 @@ set(GUI_HDR KeyBindingsDialog.hpp Updater.hpp RepeatMode.hpp - PanGestureEventFilter.hpp - EventFilterWorkarounds.hpp ) +if(NOT USE_QT4) + list(APPEND GUI_HDR + PanGestureEventFilter.hpp + EventFilterWorkarounds.hpp + ) +endif() + set(GUI_SRC Main.cpp MenuBar.cpp @@ -65,7 +70,6 @@ set(GUI_SRC ShortcutHandler.cpp KeyBindingsDialog.cpp Updater.cpp - EventFilterWorkarounds.cpp ) set(GUI_FORMS # *.ui files @@ -79,24 +83,35 @@ set(GUI_RESOURCES resources.qrc ) -if(NOT WIN32) +if(NOT USE_QT4) list(APPEND GUI_SRC - PanGestureEventFilter.cpp + EventFilterWorkarounds.cpp ) + if(NOT WIN32) + list(APPEND GUI_SRC + PanGestureEventFilter.cpp + ) + endif() endif() if(APPLE) list(APPEND GUI_HDR macOS/QMPlay2MacExtensions.hpp ) - list(APPEND GUI_SRC - macOS/QMPlay2MacExtensions.mm - ) + if(NOT USE_QT4) + list(APPEND GUI_SRC + macOS/QMPlay2MacExtensions.mm + ) + endif() include_directories("macOS") endif() -qt5_wrap_ui(GUI_FORM_HDR ${GUI_FORMS}) +if(USE_QT4) + qt4_wrap_ui(GUI_FORM_HDR ${GUI_FORMS}) +else() + qt5_wrap_ui(GUI_FORM_HDR ${GUI_FORMS}) +endif() set_property(SOURCE ${GUI_FORM_HDR} PROPERTY SKIP_AUTOMOC ON) include_directories(../qmplay2/headers) @@ -121,7 +136,7 @@ if(USE_TAGLIB) list(APPEND GUI_SRC TagEditor.cpp) endif() -if(NOT APPLE) +if(USE_QT4 OR NOT APPLE) add_definitions(-DQMPLAY2_ALLOW_ONLY_ONE_INSTANCE) endif() @@ -216,11 +231,13 @@ if(WIN32) elseif(APPLE) install(TARGETS ${PROJECT_NAME} BUNDLE DESTINATION ${CMAKE_INSTALL_PREFIX}) - set(QT_LIBS_DIR "${Qt5Widgets_DIR}/../..") - set(QT_PLUGINS_DIR "${QT_LIBS_DIR}/../plugins") + set(QT_LIBS_DIR "@qt_libs_dir@") + set(QT_PLUGINS_DIR "@qt_plugins_dir@") +if(NOT USE_QT4) install(FILES "${QT_PLUGINS_DIR}/platforms/libqcocoa.dylib" DESTINATION "${MAC_BUNDLE_PATH}/Contents/plugins/platforms") +endif() install(FILES "${QT_PLUGINS_DIR}/iconengines/libqsvgicon.dylib" DESTINATION "${MAC_BUNDLE_PATH}/Contents/plugins/iconengines") @@ -238,23 +255,7 @@ elseif(APPLE) DESTINATION "${MAC_BUNDLE_PATH}/Contents" FILES_MATCHING PATTERN "qtbase_*.qm") - if(EXISTS "/usr/local/bin/ffmpeg") - install(PROGRAMS - "/usr/local/bin/ffmpeg" - DESTINATION "${MAC_BUNDLE_PATH}/Contents/MacOS") - else() - message(WARNING "FFmpeg executable not copied!") - endif() - install(CODE " - include(BundleUtilities) - set(BU_CHMOD_BUNDLE_ITEMS ON) - list(APPEND DIRS /usr/local/lib ${QT_LIBS_DIR}) - file(GLOB_RECURSE QMPLAY2_MODULES_AND_QT_PLUGINS - \"${MAC_BUNDLE_PATH}/Contents/MacOS/modules/*\" - \"${MAC_BUNDLE_PATH}/Contents/plugins/*.dylib\") - file(WRITE \"${MAC_BUNDLE_PATH}/Contents/Resources/qt.conf\") - fixup_bundle(${MAC_BUNDLE_PATH} \"\${QMPLAY2_MODULES_AND_QT_PLUGINS}\" \"\${DIRS}\") - ") + file(WRITE \"@destroot@${MAC_BUNDLE_PATH}/Contents/Resources/qt.conf\") else() # executable install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/gui/DeintSettingsW.hpp b/src/gui/DeintSettingsW.hpp index ea74fac2..92787425 100644 --- a/src/gui/DeintSettingsW.hpp +++ b/src/gui/DeintSettingsW.hpp @@ -24,14 +24,14 @@ class QCheckBox; class QComboBox; class Module; -class DeintSettingsW final : public QGroupBox +class DeintSettingsW : public QGroupBox { Q_OBJECT public: static void init(); DeintSettingsW(); - ~DeintSettingsW(); + ~DeintSettingsW() final; void writeSettings(); private slots: diff --git a/src/gui/DemuxerThr.cpp b/src/gui/DemuxerThr.cpp index 2fdedd42..60dbf30b 100644 --- a/src/gui/DemuxerThr.cpp +++ b/src/gui/DemuxerThr.cpp @@ -451,6 +451,12 @@ void DemuxerThr::run() if (stillImage && playC.paused) playC.paused = false; + const bool isHls = (demuxer->name() == "hls"); + QTime waitForDataTimer; + bool waitForDataTimerValid = false; + bool firstWaitForData = true; + bool canWaitForData = true; + while (!demuxer.isAborted()) { { @@ -534,9 +540,33 @@ void DemuxerThr::run() ) ) { + if (isHls && !firstWaitForData && canWaitForData && !playC.endOfStream) + { + // Wait a bit longer for HLS streams to prevent stuttering on every HLS chunk. Do a short + // sleep and continue, because reading from demuxer might block for HLS chunk length. + if (!waitForDataTimerValid) + { + waitForDataTimer.start(); + waitForDataTimerValid = true; + } + if (waitForDataTimer.elapsed() / 1000.0 <= playIfBuffered) + { + msleep(50); + continue; + } + waitForDataTimerValid = false; + canWaitForData = false; + } + playC.waitForData = false; if (!paused) playC.emptyBufferCond.wakeAll(); + + firstWaitForData = false; + } + else + { + canWaitForData = true; } if (playC.endOfStream || bufferedAllPackets(vS, aS, forwardPackets)) diff --git a/src/gui/DemuxerThr.hpp b/src/gui/DemuxerThr.hpp index 35db9e36..9fdfd88d 100644 --- a/src/gui/DemuxerThr.hpp +++ b/src/gui/DemuxerThr.hpp @@ -32,14 +32,14 @@ class AVThread; class Demuxer; class BasicIO; -class DemuxerThr final : public QThread +class DemuxerThr : public QThread { friend class DemuxerTimer; friend class PlayClass; Q_OBJECT private: DemuxerThr(PlayClass &); - ~DemuxerThr(); + ~DemuxerThr() final; QByteArray getCoverFromStream() const; @@ -65,7 +65,7 @@ private: void checkReadyWrite(AVThread *avThr); - void run() override; + void run() override final; inline void ensureTrueUpdateBuffered(); inline bool canUpdateBuffered() const; diff --git a/src/gui/EntryProperties.cpp b/src/gui/EntryProperties.cpp index d537bb9e..21c8da7d 100644 --- a/src/gui/EntryProperties.cpp +++ b/src/gui/EntryProperties.cpp @@ -107,12 +107,6 @@ EntryProperties::EntryProperties(QWidget *p, QTreeWidgetItem *_tWI, bool &sync, addrB = new AddressBox(Qt::Horizontal, url); layout.addWidget(addrB, row, 0, 1, 2); - openUrlB = new QToolButton; - openUrlB->setToolTip(tr("Open URL or directory containing chosen file")); - openUrlB->setIcon(QMPlay2Core.getIconFromTheme("folder-open")); - connect(openUrlB, &QToolButton::clicked, this, &EntryProperties::openUrl); - layout.addWidget(openUrlB, row, 2, 1, 1); - fileSizeL = new QLabel; #ifdef QMPlay2_TagEditor diff --git a/src/gui/EntryProperties.hpp b/src/gui/EntryProperties.hpp index d89c2ccb..ba203300 100644 --- a/src/gui/EntryProperties.hpp +++ b/src/gui/EntryProperties.hpp @@ -30,7 +30,7 @@ class QCheckBox; #endif class QLabel; -class EntryProperties final : public QDialog +class EntryProperties : public QDialog { Q_OBJECT public: @@ -54,6 +54,6 @@ private slots: #endif void browse(); void openUrl(); - void accept() override; - void reject() override; + void accept() override final; + void reject() override final; }; diff --git a/src/gui/EventFilterWorkarounds.hpp b/src/gui/EventFilterWorkarounds.hpp index e8c28cff..ce32dda1 100644 --- a/src/gui/EventFilterWorkarounds.hpp +++ b/src/gui/EventFilterWorkarounds.hpp @@ -20,7 +20,7 @@ #include -class EventFilterWorkarounds final : public QObject +class EventFilterWorkarounds : public QObject { Q_OBJECT @@ -29,5 +29,5 @@ public: ~EventFilterWorkarounds(); private: - bool eventFilter(QObject *watched, QEvent *event) override; + bool eventFilter(QObject *watched, QEvent *event) override final; }; diff --git a/src/gui/InfoDock.hpp b/src/gui/InfoDock.hpp index cd3354a6..c46c4c7b 100644 --- a/src/gui/InfoDock.hpp +++ b/src/gui/InfoDock.hpp @@ -22,11 +22,11 @@ #include -class TextEdit final : public QTextEdit +class TextEdit : public QTextEdit { private: - void mouseMoveEvent(QMouseEvent *) override; - void mousePressEvent(QMouseEvent *) override; + void mouseMoveEvent(QMouseEvent *) override final; + void mousePressEvent(QMouseEvent *) override final; }; /**/ diff --git a/src/gui/KeyBindingsDialog.cpp b/src/gui/KeyBindingsDialog.cpp index 3dca5428..4c71bdd0 100644 --- a/src/gui/KeyBindingsDialog.cpp +++ b/src/gui/KeyBindingsDialog.cpp @@ -35,8 +35,8 @@ KeyBindingsDialog::KeyBindingsDialog(QWidget *p) : shortcuts->setModel(QMPlay2GUI.shortcutHandler); shortcuts->setFrameShape(QFrame::NoFrame); shortcuts->setAlternatingRowColors(true); - shortcuts->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); - shortcuts->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + shortcuts->horizontalHeader()->setResizeMode(0, QHeaderView::ResizeToContents); + shortcuts->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch); shortcuts->setSelectionMode(QAbstractItemView::SingleSelection); shortcuts->verticalHeader()->setVisible(false); diff --git a/src/gui/Main.cpp b/src/gui/Main.cpp index f6f61584..01ce78c6 100644 --- a/src/gui/Main.cpp +++ b/src/gui/Main.cpp @@ -18,8 +18,6 @@ #include -#include -#include #include #include #include @@ -31,7 +29,6 @@ #include #include -#include #include #include #include @@ -42,6 +39,7 @@ #include #include #include +#include #ifdef Q_OS_MACOS #include #endif @@ -52,6 +50,8 @@ #include #include + static QPair g_arguments; + static ScreenSaver *g_screenSaver = nullptr; static bool g_useGui = true; #ifdef Q_OS_MACOS @@ -111,12 +111,14 @@ void QMPlay2GUIClass::setTreeWidgetItemIcon(QTreeWidgetItem *tWI, const QIcon &i if (!treeWidget) treeWidget = tWI->treeWidget(); if (treeWidget) - tWI->setData(column, Qt::DecorationRole, icon.pixmap(treeWidget->window()->windowHandle(), treeWidget->iconSize())); + tWI->setData(column, Qt::DecorationRole, icon.pixmap(treeWidget->iconSize())); else setDefaultIcon = true; } if (setDefaultIcon) + { tWI->setIcon(column, icon); + } } #ifdef UPDATER @@ -168,7 +170,7 @@ QString QMPlay2GUIClass::getCurrentPth(QString pth, bool leaveFilename) pth.remove(0, 7); if (!leaveFilename) pth = Functions::filePath(pth); - if (!QFileInfo::exists(pth)) + if (!QFileInfo(pth).exists()) pth = settings->getString("currPth"); return pth; } @@ -216,58 +218,6 @@ QMPlay2GUIClass::~QMPlay2GUIClass() /**/ -static QCommandLineParser *createCmdParser(bool descriptions) -{ - static constexpr const char *translations[] = { - QT_TRANSLATE_NOOP("Help", "Opens and plays specified ."), - QT_TRANSLATE_NOOP("Help", "Opens new QMPlay2 instance and plays specified ."), - QT_TRANSLATE_NOOP("Help", "Adds specified to playlist."), - QT_TRANSLATE_NOOP("Help", "Starts the application with given ."), - QT_TRANSLATE_NOOP("Help", "Doesn't play after run (bypass \"Remember playback position\" option)."), - QT_TRANSLATE_NOOP("Help", "Toggles playback."), - QT_TRANSLATE_NOOP("Help", "Starts playback."), - QT_TRANSLATE_NOOP("Help", "Stops playback."), - QT_TRANSLATE_NOOP("Help", "Ensures that the window will be visible if the application is running."), - QT_TRANSLATE_NOOP("Help", "Toggles fullscreen."), - QT_TRANSLATE_NOOP("Help", "Sets specified volume."), - QT_TRANSLATE_NOOP("Help", "Sets specified playback speed."), - QT_TRANSLATE_NOOP("Help", "Seeks to the specified value."), - QT_TRANSLATE_NOOP("Help", "Plays next entry on playlist."), - QT_TRANSLATE_NOOP("Help", "Plays previous entry on playlist."), - QT_TRANSLATE_NOOP("Help", "Terminates the application."), - QT_TRANSLATE_NOOP("Help", "Displays this help."), - }; - - const auto maybeGetTranslatedText = [&](const char *text) { - if (descriptions) - return QCoreApplication::translate("Help", text); - return QString(); - }; - - QCommandLineParser *parser = new QCommandLineParser; - parser->addPositionalArgument("", maybeGetTranslatedText(translations[0]), "[url]"); - parser->addOptions({ - {"open", maybeGetTranslatedText(translations[0]), "url"}, - {"opennew", maybeGetTranslatedText(translations[1]), "url"}, - {"enqueue", maybeGetTranslatedText(translations[2]), "url"}, - {"profile", maybeGetTranslatedText(translations[3]), "profile name"}, - {"noplay", maybeGetTranslatedText(translations[4])}, - {"toggle", maybeGetTranslatedText(translations[5])}, - {"play", maybeGetTranslatedText(translations[6])}, - {"stop", maybeGetTranslatedText(translations[7])}, - {"show", maybeGetTranslatedText(translations[8])}, - {"fullscreen", maybeGetTranslatedText(translations[9])}, - {"volume", maybeGetTranslatedText(translations[10]), "0..100"}, - {"speed", maybeGetTranslatedText(translations[11]), "0.05..100.0"}, - {"seek", maybeGetTranslatedText(translations[12]), "s"}, - {"next", maybeGetTranslatedText(translations[13])}, - {"prev", maybeGetTranslatedText(translations[14])}, - {"quit", maybeGetTranslatedText(translations[15])}, - {{"h", "help"}, maybeGetTranslatedText(translations[16])}, - }); - - return parser; -} static QString fileArg(const QString &arg) { if (!arg.contains("://")) @@ -278,62 +228,90 @@ static QString fileArg(const QString &arg) } return arg; } -static QList> parseArguments(const QCommandLineParser &parser) -{ - QList> arguments; - for (const QString &option : parser.optionNames()) - { - QString value = parser.value(option); - if (option == "open" || option == "enqueue") - value = fileArg(value); - arguments += {option, value}; - } - QString urlLines; - for (const QString &url : parser.positionalArguments()) - urlLines += fileArg(url) + "\n"; - urlLines.chop(1); - if (!urlLines.isEmpty()) +static void parseArguments(QStringList &arguments) +{ + QString param; + while (arguments.count()) { - bool found = false; - for (int i = arguments.count() - 1; i >= 0; --i) + const QString arg = arguments.takeFirst(); + if (arg.startsWith('-')) { - if (arguments.at(i).first == "open" || arguments.at(i).first == "enqueue") + param = arg; + while (param.startsWith('-')) + param.remove(0, 1); + if (!param.isEmpty() && !g_arguments.first.contains(param)) { - arguments[i].second += "\n" + urlLines; - found = true; - break; + g_arguments.first += param; + g_arguments.second += QString(); + } + else + { + param.clear(); } } - if (!found) - arguments += {"open", urlLines}; + else if (!param.isEmpty()) + { + QString &data = g_arguments.second.last(); + if (!data.isEmpty()) + data += '\n'; + if (param == "open" || param == "enqueue") + data += fileArg(arg); + else + data += arg; + } + else if (!g_arguments.first.contains("open")) + { + param = "open"; + g_arguments.first += param; + g_arguments.second += fileArg(arg); + } } +} - return arguments; +static void showHelp() +{ + QFile f; + f.open(stdout, QFile::WriteOnly); + f.write("QMPlay2 - Qt Media Player 2 (" + Version::get() + ")\n"); + f.write(QObject::tr( +" Parameters list:\n" +" -open \"address\"\n" +" -enqueue \"address\"\n" +" -profile \"name\" - starts application with given profile name\n" +" -noplay - doesn't play after run (bypass \"Remember playback position\" option)\n" +" -toggle - toggles play/pause\n" +" -show - ensures that the window will be visible if the application is running\n" +" -fullscreen - toggles fullscreen\n" +" -volume - sets volume [0..100]\n" +" -speed - sets playback speed [0.05..100.0]\n" +" -seek - seeks to the specified value [s]\n" +" -stop - stops playback\n" +" -next - plays next on the list\n" +" -prev - plays previous on the list\n" +" -quit - terminates the application" + ).toLocal8Bit() + "\n"); } -static bool writeToSocket(IPCSocket &socket, QList> &arguments) +static bool writeToSocket(IPCSocket &socket) { bool ret = false; - - for (auto &&argument : arguments) + for (int i = g_arguments.first.count() - 1; i >= 0; i--) { - if (argument.first == "noplay" || argument.first == "profile") + if (g_arguments.first[i] == "noplay" || g_arguments.first[i] == "profile") continue; - - if (argument.first == "open" || argument.first == "enqueue") + else if (g_arguments.first[i] == "open" || g_arguments.first[i] == "enqueue") { - if (!argument.second.isEmpty()) - argument.second = Functions::Url(argument.second); + if (!g_arguments.second[i].isEmpty()) + g_arguments.second[i] = Functions::Url(g_arguments.second[i]); #ifdef Q_OS_WIN - if (argument.second.startsWith("file://")) - argument.second.remove(0, 7); + if (g_arguments.second[i].startsWith("file://")) + g_arguments.second[i].remove(0, 7); #endif } - socket.write(QString(argument.first + '\t' + argument.second).toUtf8() + '\0'); + socket.write(QString(g_arguments.first[i] + '\t' + g_arguments.second[i]).toUtf8() + '\0'); ret = true; } - return ret; } @@ -352,13 +330,6 @@ static inline void exitProcedure() g_screenSaver = nullptr; } -#ifndef Q_OS_WIN - #include - static jmp_buf env; - static bool qAppOK; - static bool canDeleteApp = true; -#endif - static inline void forceKill() { #ifdef Q_OS_WIN @@ -400,13 +371,6 @@ static void signal_handler(int s) } break; case SIGABRT: -#ifndef Q_OS_WIN - if (!qAppOK && g_useGui) - { - canDeleteApp = g_useGui = false; - longjmp(env, 1); - } -#endif QMPlay2Core.log("QMPlay2 has been aborted (SIGABRT)", ErrorLog | AddTimeToLog | (qApp ? SaveLog : DontShowInGUI)); callDefaultSignalHandler(s); break; @@ -432,6 +396,12 @@ static void signal_handler(int s) } } +static inline void noAutoPlay() +{ + g_arguments.first += "noplay"; + g_arguments.second += QString(); +} + #ifdef Q_OS_WIN static LRESULT CALLBACK MMKeysHookProc(int code, WPARAM wparam, LPARAM lparam) { @@ -461,50 +431,24 @@ static LRESULT CALLBACK MMKeysHookProc(int code, WPARAM wparam, LPARAM lparam) } #endif -static QtMessageHandler g_defaultMsgHandler = nullptr; -static QMutex g_messageHandlerMutex; -static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message) + static QtMsgHandler g_defaultMsgHandler = nullptr; + +void messageHandler(QtMsgType type, const char *message) { - bool qmplay2Log = false; - if (QCoreApplication::instance()) - { - // Use QMPlay2 logger only when we have a "QApplication" instance (we're still executing "main()"), - // so any static data including "QSystemLocaleSingleton" and "QMPlay2CoreClass" are still valid. - switch (type) - { - case QtWarningMsg: - case QtCriticalMsg: - case QtFatalMsg: - g_messageHandlerMutex.lock(); - QMPlay2Core.logError(qFormatLogMessage(type, context, message), false); - g_messageHandlerMutex.unlock(); - qmplay2Log = true; - break; - case QtDebugMsg: - case QtInfoMsg: - if (type != QtDebugMsg || qstrcmp(context.category, "js") == 0) - { - g_messageHandlerMutex.lock(); - QMPlay2Core.logInfo(qFormatLogMessage(type, context, message), false); - g_messageHandlerMutex.unlock(); - qmplay2Log = true; - } - break; - default: - break; - } - } - if (!qmplay2Log) + switch (type) { -#ifdef Q_OS_ANDROID - if (g_defaultMsgHandler) - g_defaultMsgHandler(type, context, message); -#else - g_messageHandlerMutex.lock(); - fprintf(stderr, "%s\n", qFormatLogMessage(type, context, message).toLocal8Bit().constData()); - fflush(stderr); - g_messageHandlerMutex.unlock(); -#endif + case QtDebugMsg: + fprintf(stderr, "Debug: %s\n", message); + break; + case QtWarningMsg: + fprintf(stderr, "Warning: %s\n", message); + break; + case QtCriticalMsg: + fprintf(stderr, "Critical: %s\n", message); + break; + case QtFatalMsg: + fprintf(stderr, "Fatal: %s\n", message); + abort(); // Abort on fatal messages } } @@ -608,51 +552,27 @@ int main(int argc, char *argv[]) checkForEGL(); #endif - QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); -#ifndef Q_OS_WIN - QGuiApplication::setAttribute(Qt::AA_ShareOpenGLContexts); -#endif + new QApplication(argc, argv, g_useGui); -#ifndef Q_OS_WIN - if (!setjmp(env)) -#endif - new QApplication(argc, argv); -#ifndef Q_OS_WIN - qAppOK = true; -#endif + QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8")); + QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); QCoreApplication::setApplicationName("QMPlay2"); QMPlay2GUIClass &qmplay2Gui = QMPlay2GUI; //Create "QMPlay2GUI" instance - g_defaultMsgHandler = qInstallMessageHandler(messageHandler); + g_defaultMsgHandler = qInstallMsgHandler(messageHandler); - QCommandLineParser *parser = createCmdParser(false); - parser->setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); - parser->process(*qApp); - QList> arguments = parseArguments(*parser); - const bool help = parser->isSet("help"); - QString cmdLineProfile = parser->value("profile"); - delete parser; + QStringList arguments = QCoreApplication::arguments(); + arguments.removeFirst(); + const bool help = arguments.contains("-help") || arguments.contains("-h"); if (!help) { - bool useSocket = true; - - for (auto &&argument : arguments) - { - if (argument.first == "opennew") - { - argument.first = "open"; - useSocket = false; - break; - } - } - - if (useSocket) - { +// FIXME: try sorting this mess out. IPCSocket socket(qmplay2Gui.getPipe()); + parseArguments(arguments); if (socket.open(IPCSocket::WriteOnly)) { - if (writeToSocket(socket, arguments)) + if (writeToSocket(socket)) g_useGui = false; socket.close(); } @@ -660,22 +580,27 @@ int main(int argc, char *argv[]) else if (QFile::exists(qmplay2Gui.getPipe())) { QFile::remove(qmplay2Gui.getPipe()); - arguments.append({"noplay", QString()}); + noAutoPlay(); } #endif - } if (!g_useGui) { -#ifndef Q_OS_WIN - if (canDeleteApp) -#endif delete qApp; return 0; } } - qmplay2Gui.cmdLineProfile = std::move(cmdLineProfile); + for (int i = 0; i < g_arguments.first.count(); ++i) + { + if (g_arguments.first.at(i) == "profile") + { + qmplay2Gui.cmdLineProfile = g_arguments.second.at(i); + g_arguments.first.removeAt(i); + g_arguments.second.removeAt(i); + break; + } + } QString libPath, sharePath = QCoreApplication::applicationDirPath(); bool cmakeBuildFound = false; @@ -717,7 +642,6 @@ int main(int argc, char *argv[]) qRegisterMetaType("VideoFrame"); - QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); QDir::setCurrent(QCoreApplication::applicationDirPath()); //Is it really needed? @@ -726,8 +650,6 @@ int main(int argc, char *argv[]) { qmplay2Gui.screenSaver = g_screenSaver = new ScreenSaver; QApplication::setQuitOnLastWindowClosed(false); - qApp->installEventFilter(new EventFilterWorkarounds(qApp)); - PanGestureEventFilter::install(); } #ifdef Q_OS_WIN @@ -751,16 +673,6 @@ int main(int argc, char *argv[]) /* QMPlay2GUI musi być stworzone już wcześniej */ QMPlay2Core.init(!help, cmakeBuildFound, libPath, sharePath, qmplay2Gui.cmdLineProfile); - if (help) - { - parser = createCmdParser(true); - parser->setApplicationDescription(QString("QMPlay2 - Qt Media Player 2 (%1)").arg((QString)Version::get())); - printf("%s", parser->helpText().toLocal8Bit().constData()); - fflush(stdout); - delete parser; - break; - } - if (!qmplay2Gui.cmdLineProfile.isEmpty() && QMPlay2Core.getSettingsProfile() == "/") qmplay2Gui.cmdLineProfile = QMPlay2Core.getSettingsProfile(); // Default profile @@ -783,6 +695,12 @@ int main(int argc, char *argv[]) settings.remove("Volume"); } + if (help) + { + showHelp(); + break; + } + qmplay2Gui.loadIcons(); { const QIcon scaledIcon = QMPlay2Core.getQMPlay2Icon(); @@ -861,7 +779,7 @@ int main(int argc, char *argv[]) qmplay2Gui.restartApp = qmplay2Gui.removeSettings = qmplay2Gui.noAutoPlay = false; qmplay2Gui.newProfileName.clear(); - new MainWidget(arguments); + new MainWidget(g_arguments); do { QCoreApplication::exec(); @@ -894,22 +812,15 @@ int main(int argc, char *argv[]) } if (qmplay2Gui.noAutoPlay) - arguments.append({"noplay", QString()}); + noAutoPlay(); delete qmplay2Gui.pipe; } while (qmplay2Gui.restartApp); qmplay2Gui.deleteIcons(); -#ifdef Q_OS_WIN - UnhookWindowsHookEx(keyboardHook); -#endif - exitProcedure(); -#ifndef Q_OS_WIN - if (canDeleteApp) -#endif delete qApp; return 0; } diff --git a/src/gui/Main.hpp b/src/gui/Main.hpp index e4916950..26f17d54 100644 --- a/src/gui/Main.hpp +++ b/src/gui/Main.hpp @@ -35,7 +35,7 @@ class VideoDock; class MenuBar; class QWidget; -class QMPlay2GUIClass final : private QMPlay2CoreClass +class QMPlay2GUIClass : private QMPlay2CoreClass { Q_DECLARE_TR_FUNCTIONS(QMPlay2GUIClass) @@ -68,7 +68,7 @@ public: void updateInDockW(); - const QWidget *getVideoDock() const override; + const QWidget *getVideoDock() const override final; QColor grad1, grad2, qmpTxt; QIcon *groupIcon, *mediaIcon, *folderIcon; @@ -84,7 +84,7 @@ public: QString newProfileName, cmdLineProfile; private: QMPlay2GUIClass(); - ~QMPlay2GUIClass(); + ~QMPlay2GUIClass() final; }; #define QMPlay2GUI \ diff --git a/src/gui/MainWidget.cpp b/src/gui/MainWidget.cpp index 139dd2a4..4bbc7a61 100644 --- a/src/gui/MainWidget.cpp +++ b/src/gui/MainWidget.cpp @@ -32,8 +32,6 @@ #include #ifdef Q_OS_MACOS #include - #include - #include #endif #ifdef Q_OS_WIN #include @@ -62,7 +60,8 @@ #include #include #ifdef Q_OS_MACOS - #include + #include + extern void qt_mac_set_dock_menu(QMenu *); #endif using Functions::timeToStr; @@ -76,21 +75,6 @@ using Functions::timeToStr; #include -/* MainWidgetTmpStyle - dock widget separator extent must be larger for touch screens */ -class MainWidgetTmpStyle final : public QCommonStyle -{ -public: - ~MainWidgetTmpStyle() = default; - - int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const override - { - const int pM = QCommonStyle::pixelMetric(metric, option, widget); - if (metric == QStyle::PM_DockWidgetSeparatorExtent) - return pM * 5 / 2; - return pM; - } -}; - #ifndef Q_OS_MACOS static void copyMenu(QMenu *dest, QMenu *src, QMenu *dontCopy = nullptr) { @@ -110,25 +94,13 @@ static void copyMenu(QMenu *dest, QMenu *src, QMenu *dontCopy = nullptr) #endif /* MainWidget */ -MainWidget::MainWidget(QList> &arguments) : +MainWidget::MainWidget(QPair &arguments) : updater(this) { QMPlay2GUI.videoAdjustment = new VideoAdjustmentW; QMPlay2GUI.shortcutHandler = new ShortcutHandler(this); QMPlay2GUI.mainW = this; - /* Looking for touch screen */ - for (const QTouchDevice *touchDev : QTouchDevice::devices()) - { - /* Touchscreen found */ - if (touchDev->type() == QTouchDevice::TouchScreen) - { - MainWidgetTmpStyle mainWidgetTmpStyle; - setStyle(&mainWidgetTmpStyle); - break; - } - } - setObjectName("MainWidget"); settingsW = nullptr; @@ -284,9 +256,7 @@ MainWidget::MainWidget(QList> &arguments) : connect(playlistDock, SIGNAL(play(const QString &)), &playC, SLOT(play(const QString &))); connect(playlistDock, SIGNAL(repeatEntry(bool)), &playC, SLOT(repeatEntry(bool))); connect(playlistDock, SIGNAL(stop()), &playC, SLOT(stop())); - connect(playlistDock, &PlaylistDock::addAndPlayRestoreWindow, this, [this] { - m_restoreWindowOnVideo = true; - }); + connect(playlistDock, SIGNAL(addAndPlayRestoreWindow()), this, SLOT(handleAddAndPlayRestoreWindow())); connect(seekS, SIGNAL(valueChanged(int)), this, SLOT(seek(int))); connect(seekS, SIGNAL(mousePosition(int)), this, SLOT(mousePositionOnSlider(int))); @@ -320,17 +290,10 @@ MainWidget::MainWidget(QList> &arguments) : connect(&playC, SIGNAL(updateBufferedRange(int, int)), seekS, SLOT(drawRange(int, int))); connect(&playC, SIGNAL(updateWindowTitle(const QString &)), this, SLOT(updateWindowTitle(const QString &))); connect(&playC, SIGNAL(updateImage(const QImage &)), videoDock, SLOT(updateImage(const QImage &))); - connect(&playC, &PlayClass::videoStarted, this, &MainWidget::videoStarted); - connect(&playC, &PlayClass::videoNotStarted, this, [this] { - m_restoreWindowOnVideo = false; - }); + connect(&playC, SIGNAL(videoNotStarted()), this, SLOT(handleVideoNotStarted())); + connect(playlistDock, SIGNAL(addAndPlayRestoreWindow()), this, SLOT(onAddAndPlayRestoreWindow())); connect(&playC, SIGNAL(uncheckSuspend()), this, SLOT(uncheckSuspend())); - connect(&playC, &PlayClass::setVideoCheckState, this, [this](bool rotate90, bool hFlip, bool vFlip, bool spherical) { - menuBar->playback->videoFilters->rotate90->setChecked(rotate90); - menuBar->playback->videoFilters->hFlip->setChecked(hFlip); - menuBar->playback->videoFilters->vFlip->setChecked(vFlip); - menuBar->playback->videoFilters->spherical->setChecked(spherical); - }); + connect(&playC, SIGNAL(setVideoCheckState(bool, bool, bool, bool)), this, SLOT(handleSetVideoCheckState(bool, bool, bool, bool))); /**/ if (settings.getBool("MainWidget/TabPositionNorth")) @@ -390,21 +353,21 @@ MainWidget::MainWidget(QList> &arguments) : if (tabBar && tabBar->property("changeCurrentOnDrag").isValid()) { tabBar->setAcceptDrops(true); - tabBar->setChangeCurrentOnDrag(true); } } playlistDock->load(QMPlay2Core.getSettingsDir() + "Playlist.pls"); bool noplay = false; - for (const auto &argument : asConst(arguments)) + for (int i = 0; i < arguments.first.size(); ++i) // Iterate over the size of the first QStringList { - const QString ¶m = argument.first; - const QString &data = argument.second; + const QString ¶m = arguments.first.at(i); // Access the elements of the first QStringList + const QString &data = arguments.second.at(i); // Access the corresponding elements of the second QStringList noplay |= (param == "open" || param == "noplay"); processParam(param, data); } - arguments.clear(); + arguments.first.clear(); // Clear the first QStringList + arguments.second.clear(); // Clear the second QStringList if (!noplay) { @@ -421,22 +384,11 @@ MainWidget::MainWidget(QList> &arguments) : playStateChanged(false); } -#ifdef Q_OS_MACOS - qApp->installEventFilter(this); - fileOpenTimer.setSingleShot(true); - connect(&fileOpenTimer, &QTimer::timeout, this, &MainWidget::fileOpenTimerTimeout); - if (QMPlay2GUI.pipe) // Register media keys only for first QMPlay2 instance - QMPlay2MacExtensions::registerMacOSMediaKeys(std::bind(&MainWidget::processParam, this, std::placeholders::_1, QString())); -#endif - if (settings.getBool("AutoUpdates")) updater.downloadUpdate(); } MainWidget::~MainWidget() { -#ifdef Q_OS_MACOS - QMPlay2MacExtensions::unregisterMacOSMediaKeys(); -#endif QMPlay2Extensions::closeExtensions(); emit QMPlay2Core.restoreCursor(); Notifies::finalize(); @@ -557,6 +509,15 @@ void MainWidget::updateWindowTitle(const QString &t) title.replace('\n', ' '); setWindowTitle(title); } + +void MainWidget::handleAddAndPlayRestoreWindow() { + m_restoreWindowOnVideo = true; +} + +void MainWidget::handleVideoNotStarted() { + m_restoreWindowOnVideo = false; +} + void MainWidget::videoStarted(bool noVideo) { const bool autoRestoreMainWindowOnVideo = m_restoreWindowOnVideo ? QMPlay2Core.getSettings().getBool("AutoRestoreMainWindowOnVideo") : false; @@ -693,20 +654,22 @@ void MainWidget::resetSpherical() void MainWidget::visualizationFullScreen() { QWidget *senderW = (QWidget *)sender(); - const auto maybeGoFullScreen = [this, senderW] { - if (!fullScreen) - { - videoDock->setWidget(senderW); - toggleFullScreen(); - } - }; #ifdef Q_OS_MACOS - // On macOS if full screen is toggled to fast after double click, mouse remains in clicked state... - QTimer::singleShot(200, maybeGoFullScreen); + QTimer::singleShot(200, this, SLOT(maybeGoFullScreen())); #else - maybeGoFullScreen(); + maybeGoFullScreen(senderW); #endif } + +void MainWidget::maybeGoFullScreen(QWidget *senderW) +{ + if (!fullScreen) + { + videoDock->setWidget(senderW); + toggleFullScreen(); + } +} + void MainWidget::hideAllExtensions() { for (QMPlay2Extensions *QMPlay2Ext : QMPlay2Extensions::QMPlay2ExtensionsList()) @@ -724,11 +687,7 @@ void MainWidget::toggleVisibility() toggleFullScreen(); if (!isTray) { -#ifndef Q_OS_MACOS showMinimized(); -#else - QMPlay2MacExtensions::setApplicationVisible(false); -#endif } else { @@ -764,6 +723,7 @@ void MainWidget::toggleVisibility() } #endif } + void MainWidget::createMenuBar() { menuBar = QMPlay2GUI.menuBar; @@ -779,7 +739,7 @@ void MainWidget::createMenuBar() connect(menuBar->window->toggleVisibility, SIGNAL(triggered()), this, SLOT(toggleVisibility())); connect(menuBar->window->toggleFullScreen, SIGNAL(triggered()), this, SLOT(toggleFullScreen())); connect(menuBar->window->toggleCompactView, SIGNAL(triggered()), this, SLOT(toggleCompactView())); - connect(menuBar->window->alwaysOnTop, &QAction::triggered, this, &MainWidget::toggleAlwaysOnTop); + connect(menuBar->window->alwaysOnTop, SIGNAL(triggered(bool)), this, SLOT(toggleAlwaysOnTop(bool))); connect(menuBar->window->close, SIGNAL(triggered()), this, SLOT(close())); connect(menuBar->playlist->add->address, SIGNAL(triggered()), this, SLOT(openUrl())); @@ -794,7 +754,7 @@ void MainWidget::createMenuBar() connect(menuBar->playlist->newGroup, SIGNAL(triggered()), playlistDock, SLOT(newGroup())); connect(menuBar->playlist->renameGroup, SIGNAL(triggered()), playlistDock, SLOT(renameGroup())); connect(menuBar->playlist->lock, SIGNAL(triggered()), playlistDock, SLOT(toggleLock())); - connect(menuBar->playlist->alwaysSync, &QAction::triggered, playlistDock, &PlaylistDock::alwaysSyncTriggered); + connect(menuBar->playlist->alwaysSync, SIGNAL(triggered(bool)), playlistDock, SLOT(alwaysSyncTriggered(bool))); connect(menuBar->playlist->delEntries, SIGNAL(triggered()), playlistDock, SLOT(delEntries())); connect(menuBar->playlist->delNonGroupEntries, SIGNAL(triggered()), playlistDock, SLOT(delNonGroupEntries())); connect(menuBar->playlist->clear, SIGNAL(triggered()), playlistDock, SLOT(clear())); @@ -931,19 +891,18 @@ void MainWidget::createMenuBar() secondMenu->addSeparator(); // Copy action, because PreferencesRole doesn't show in dock menu. QAction *settings = new QAction(menuBar->options->settings->icon(), menuBar->options->settings->text(), menuBar->options->settings->parent()); - connect(settings, &QAction::triggered, menuBar->options->settings, &QAction::trigger); + connect(settings, SIGNAL(triggered()), menuBar->options->settings, SLOT(trigger())); secondMenu->addAction(settings); QAction *newInstanceAct = new QAction(tr("New window"), secondMenu); - connect(newInstanceAct, &QAction::triggered, [] { - QProcess::startDetached(QCoreApplication::applicationFilePath(), {"-noplay"}, QCoreApplication::applicationDirPath()); - }); + connect(newInstanceAct, SIGNAL(triggered()), this, SLOT(createNewWindow())); secondMenu->addSeparator(); secondMenu->addAction(newInstanceAct); qt_mac_set_dock_menu(secondMenu); #endif } + void MainWidget::trayIconClicked(QSystemTrayIcon::ActivationReason reason) { #if !defined Q_OS_MACOS && !defined Q_OS_ANDROID @@ -966,6 +925,7 @@ void MainWidget::trayIconClicked(QSystemTrayIcon::ActivationReason reason) Q_UNUSED(reason) #endif } + void MainWidget::toggleCompactView() { if (!isCompactView) @@ -1004,6 +964,7 @@ void MainWidget::toggleCompactView() statusBar->show(); } } + void MainWidget::toggleAlwaysOnTop(bool checked) { auto flags = windowFlags(); @@ -1016,6 +977,7 @@ void MainWidget::toggleAlwaysOnTop(bool checked) if (visible) show(); } + void MainWidget::toggleFullScreen() { static bool visible, compact_view, tb_movable; @@ -1082,30 +1044,17 @@ void MainWidget::toggleFullScreen() videoDock->fullScreen(true); videoDock->show(); -#ifdef Q_OS_MACOS - menuBar->window->toggleVisibility->setEnabled(false); -#endif menuBar->window->toggleCompactView->setEnabled(false); menuBar->window->toggleFullScreen->setShortcuts(QList() << menuBar->window->toggleFullScreen->shortcut() << QKeySequence("ESC")); fullScreen = true; -#ifndef Q_OS_MACOS showFullScreen(); -#else - setWindowFlags(Qt::Window | Qt::FramelessWindowHint); - setGeometry(window()->windowHandle()->screen()->geometry()); - QMPlay2MacExtensions::showSystemUi(windowHandle(), false); - show(); -#endif if (playC.isPlaying()) QMPlay2GUI.screenSaver->inhibit(1); } else { -#ifdef Q_OS_MACOS - menuBar->window->toggleVisibility->setEnabled(true); -#endif menuBar->window->toggleCompactView->setEnabled(true); menuBar->window->toggleFullScreen->setShortcuts(QList() << menuBar->window->toggleFullScreen->shortcut()); @@ -1113,19 +1062,11 @@ void MainWidget::toggleFullScreen() fullScreen = false; #ifndef Q_OS_ANDROID -#ifdef Q_OS_MACOS - QMPlay2MacExtensions::showSystemUi(windowHandle(), true); - setWindowFlags(Qt::Window); -#else showNormal(); -#endif // Q_OS_MACOS if (maximized) showMaximized(); else { -#ifdef Q_OS_MACOS - showNormal(); -#endif setGeometry(savedGeo); } #else // Q_OS_ANDROID @@ -1168,6 +1109,7 @@ void MainWidget::toggleFullScreen() } emit QMPlay2Core.fullScreenChanged(fullScreen); } + void MainWidget::showMessage(const QString &msg, const QString &title, int messageIcon, int ms) { if (ms < 1 || !Notifies::notify(title, msg, ms, messageIcon)) @@ -1182,6 +1124,7 @@ void MainWidget::showMessage(const QString &msg, const QString &title, int messa messageBox->show(); } } + void MainWidget::statusBarMessage(const QString &msg, int ms) { statusBar->showMessage(msg, ms); @@ -1199,14 +1142,17 @@ void MainWidget::openUrl() } } + void MainWidget::openFiles() { playlistDock->add(QFileDialog::getOpenFileNames(this, tr("Choose files"), QMPlay2GUI.getCurrentPth(playlistDock->getUrl(), true))); } + void MainWidget::openDir() { playlistDock->add(QFileDialog::getExistingDirectory(this, tr("Choose directory"), QMPlay2GUI.getCurrentPth(playlistDock->getUrl()))); } + void MainWidget::loadPlist() { QString filter = tr("Playlists") + " ("; @@ -1218,14 +1164,17 @@ void MainWidget::loadPlist() return; playlistDock->load(QFileDialog::getOpenFileName(this, tr("Choose playlist"), QMPlay2GUI.getCurrentPth(playlistDock->getUrl()), filter)); } + void MainWidget::savePlist() { savePlistHelper(tr("Save playlist"), QMPlay2GUI.getCurrentPth(playlistDock->getUrl()), false); } + void MainWidget::saveGroup() { savePlistHelper(tr("Save group"), QMPlay2GUI.getCurrentPth(playlistDock->getUrl()) + playlistDock->getCurrentItemName(), true); } + void MainWidget::showSettings(const QString &moduleName) { if (!settingsW) @@ -1247,6 +1196,7 @@ void MainWidget::showSettings(const QString &moduleName) showSettings(moduleName); } } + void MainWidget::showSettings() { showSettings(QString()); @@ -1259,6 +1209,7 @@ void MainWidget::itemDropped(const QString &pth, bool subs) else playlistDock->addAndPlay(pth); } + void MainWidget::browseSubsFile() { QString dir = Functions::filePath(playC.getUrl()); @@ -1301,6 +1252,7 @@ void MainWidget::setSeekSMaximum(int max) seekS->setEnabled(false); } } + void MainWidget::updatePos(double pos) { static int currPos; @@ -1322,6 +1274,7 @@ void MainWidget::updatePos(double pos) #endif } } + void MainWidget::mousePositionOnSlider(int pos) { statusBar->showMessage(tr("Pointed position") + ": " + timeToStr(pos / 10.0, true), 750); @@ -1332,6 +1285,7 @@ void MainWidget::newConnection(IPCSocket *socket) connect(socket, SIGNAL(readyRead()), this, SLOT(readSocket())); connect(socket, SIGNAL(aboutToClose()), socket, SLOT(deleteLater())); } + void MainWidget::readSocket() { IPCSocket *socket = (IPCSocket *)sender(); @@ -1348,6 +1302,10 @@ void MainWidget::readSocket() socket->deleteLater(); } +void MainWidget::createNewWindow() { + QProcess::startDetached(QCoreApplication::applicationFilePath(), QStringList() << "-noplay", QCoreApplication::applicationDirPath()); +} + void MainWidget::about() { if (!aboutW) @@ -1374,6 +1332,7 @@ void MainWidget::hideMenu(bool h) } } #endif + void MainWidget::lockWidgets(bool l) { if (fullScreen || isCompactView) @@ -1405,18 +1364,14 @@ void MainWidget::hideDocksSlot() hideDocks(); } } + void MainWidget::doRestoreState(const QByteArray &data) { if (isMaximized()) { setUpdatesEnabled(false); - QTimer::singleShot(0, this, [=] { - QTimer::singleShot(0, this, [=] { - restoreState(data); - setUpdatesEnabled(true); - repaint(); - }); - }); + QTimer::singleShot(0, this, SLOT(doRestoreStateStep1())); + m_restoreStateData = data; // Store the data for use in later steps } else { @@ -1424,6 +1379,18 @@ void MainWidget::doRestoreState(const QByteArray &data) } } +void MainWidget::doRestoreStateStep1() +{ + QTimer::singleShot(0, this, SLOT(doRestoreStateStep2())); +} + +void MainWidget::doRestoreStateStep2() +{ + restoreState(m_restoreStateData); + setUpdatesEnabled(true); + repaint(); +} + void MainWidget::uncheckSuspend() { if (menuBar->player->suspend) @@ -1486,6 +1453,7 @@ void MainWidget::showToolBar(bool showTB) statusBar->hide(); } } + void MainWidget::hideDocks() { fullScreenDockWidgetState = saveState(); @@ -1595,6 +1563,7 @@ void MainWidget::keyPressEvent(QKeyEvent *e) } QMainWindow::keyPressEvent(e); } + void MainWidget::mouseMoveEvent(QMouseEvent *e) { if ((fullScreen || isCompactView) && (e->buttons() == Qt::NoButton || videoDock->isTouch)) @@ -1663,12 +1632,14 @@ void MainWidget::mouseMoveEvent(QMouseEvent *e) } QMainWindow::mouseMoveEvent(e); } + void MainWidget::leaveEvent(QEvent *e) { if (fullScreen || isCompactView) QMetaObject::invokeMethod(this, "hideDocksSlot", Qt::QueuedConnection); //Qt5 can't hide docks properly here QMainWindow::leaveEvent(e); } + void MainWidget::closeEvent(QCloseEvent *e) { const QString quitMsg = tr("Are you sure you want to quit?"); @@ -1735,12 +1706,14 @@ void MainWidget::closeEvent(QCloseEvent *e) playC.stop(true); } + void MainWidget::moveEvent(QMoveEvent *e) { if (videoDock->isVisible() && !videoDock->isFloating()) emit QMPlay2Core.videoDockMoved(); QMainWindow::moveEvent(e); } + void MainWidget::showEvent(QShowEvent *) { if (!wasShow) @@ -1759,6 +1732,7 @@ void MainWidget::showEvent(QShowEvent *) } menuBar->window->toggleVisibility->setText(tr("&Hide")); } + void MainWidget::hideEvent(QHideEvent *) { #ifndef Q_OS_ANDROID @@ -1776,7 +1750,7 @@ bool MainWidget::eventFilter(QObject *obj, QEvent *event) if (tray && obj == tray && event->type() == QEvent::Wheel) { QWheelEvent *we = static_cast(event); - volW->changeVolume(we->angleDelta().y() / 30); + volW->changeVolume(we->delta() / 30); } #ifdef Q_OS_MACOS else if (event->type() == QEvent::FileOpen) @@ -1798,3 +1772,10 @@ void MainWidget::fileOpenTimerTimeout() filesToAdd.clear(); } #endif + +void MainWidget::handleSetVideoCheckState(bool rotate90, bool hFlip, bool vFlip, bool spherical) { + menuBar->playback->videoFilters->rotate90->setChecked(rotate90); + menuBar->playback->videoFilters->hFlip->setChecked(hFlip); + menuBar->playback->videoFilters->vFlip->setChecked(vFlip); + menuBar->playback->videoFilters->spherical->setChecked(spherical); +} diff --git a/src/gui/MainWidget.hpp b/src/gui/MainWidget.hpp index 2fe04bc5..daaf545c 100644 --- a/src/gui/MainWidget.hpp +++ b/src/gui/MainWidget.hpp @@ -42,14 +42,18 @@ class QMPlay2Extensions; class QWinTaskbarProgress; #endif -class MainWidget final : public QMainWindow +class MainWidget : public QMainWindow { friend class QMPlay2GUIClass; Q_PROPERTY(bool fullScreen READ getFullScreen) Q_OBJECT public: - MainWidget(QList> &argument); - ~MainWidget(); + MainWidget(QPair &argument); + ~MainWidget() final; +public slots: + void handleAddAndPlayRestoreWindow(); + void handleVideoNotStarted(); + void handleSetVideoCheckState(bool rotate90, bool hFlip, bool vFlip, bool spherical); private slots: void detachFromPipe(); @@ -77,6 +81,7 @@ private slots: void resetSpherical(); void visualizationFullScreen(); + void maybeGoFullScreen(QWidget *senderW); void hideAllExtensions(); void toggleVisibility(); void createMenuBar(); @@ -86,6 +91,7 @@ private slots: void toggleFullScreen(); void showMessage(const QString &, const QString &, int, int); void statusBarMessage(const QString &, int ms); + void createNewWindow(); void openUrl(); void openFiles(); @@ -117,12 +123,14 @@ private slots: void hideDocksSlot(); void doRestoreState(const QByteArray &data); + void doRestoreStateStep1(); + void doRestoreStateStep2(); void uncheckSuspend(); private: void savePlistHelper(const QString &, const QString &, bool); - QMenu *createPopupMenu() override; + QMenu *createPopupMenu() override final; void showToolBar(bool); void hideDocks(); @@ -135,15 +143,15 @@ private: void setWindowsTaskBarFeatures(); #endif - void keyPressEvent(QKeyEvent *) override; - void mouseMoveEvent(QMouseEvent *) override; - void leaveEvent(QEvent *) override; - void closeEvent(QCloseEvent *) override; - void moveEvent(QMoveEvent *) override; - void showEvent(QShowEvent *) override; - void hideEvent(QHideEvent *) override; + void keyPressEvent(QKeyEvent *) override final; + void mouseMoveEvent(QMouseEvent *) override final; + void leaveEvent(QEvent *) override final; + void closeEvent(QCloseEvent *) override final; + void moveEvent(QMoveEvent *) override final; + void showEvent(QShowEvent *) override final; + void hideEvent(QHideEvent *) override final; - bool eventFilter(QObject *obj, QEvent *event) override; + bool eventFilter(QObject *obj, QEvent *event) override final; #ifdef Q_OS_MACOS void fileOpenTimerTimeout(); @@ -185,6 +193,8 @@ private: #endif QAction *lockWidgetsAct; + QByteArray m_restoreStateData; // Temporary storage for the state data + Updater updater; #ifdef Q_OS_WIN diff --git a/src/gui/MenuBar.cpp b/src/gui/MenuBar.cpp index b3ea2162..7210239d 100644 --- a/src/gui/MenuBar.cpp +++ b/src/gui/MenuBar.cpp @@ -366,27 +366,6 @@ MenuBar::Playback::VideoFilters::VideoFilters(QMenu *parent) : widgetAction->setDefaultWidget(QMPlay2GUI.videoAdjustment); QMPlay2GUI.videoAdjustment->setObjectName(videoAdjustmentMenu->title().remove('&')); videoAdjustmentMenu->addAction(widgetAction); -#ifdef Q_OS_MACOS - // Update visibility and update geometry of video adjustment widget - connect(videoAdjustmentMenu, &VideoFilters::aboutToShow, [] { - QWidget *parent = QMPlay2GUI.videoAdjustment->parentWidget(); - if (parent) - { - const QString parentObject = parent->metaObject()->className(); - if (parentObject == "QMacNativeWidget") - { - QMPlay2GUI.videoAdjustment->update(); - QMPlay2GUI.videoAdjustment->show(); - } - if (parentObject == "QMacNativeWidget" || parentObject == "QMenu") - { - QTimer::singleShot(1, [parent] { - QMPlay2GUI.videoAdjustment->setGeometry(parent->rect()); - }); - } - } - }); -#endif /**/ addSeparator(); newAction(VideoFilters::tr("&Spherical view"), this, spherical, true, QIcon(), true); diff --git a/src/gui/OSDSettingsW.cpp b/src/gui/OSDSettingsW.cpp index 7b988cbd..b6a67ca1 100644 --- a/src/gui/OSDSettingsW.cpp +++ b/src/gui/OSDSettingsW.cpp @@ -38,7 +38,7 @@ static void appendColon(const QObjectList &objects) void OSDSettingsW::init(const QString &prefix, int a, int b, int c, int d, int e, int f, double g, double h, const QColor &i, const QColor &j, const QColor &k) { Settings &QMPSettings = QMPlay2Core.getSettings(); - QMPSettings.init(prefix + "/FontName", QGuiApplication::font().family()); + QMPSettings.init(prefix + "/FontName", QFontComboBox().currentText()); QMPSettings.init(prefix + "/FontSize", a); QMPSettings.init(prefix + "/Spacing", b); QMPSettings.init(prefix + "/LeftMargin", c); diff --git a/src/gui/OtherVFiltersW.cpp b/src/gui/OtherVFiltersW.cpp index 6d449b42..b4136468 100644 --- a/src/gui/OtherVFiltersW.cpp +++ b/src/gui/OtherVFiltersW.cpp @@ -37,7 +37,7 @@ OtherVFiltersW::OtherVFiltersW(bool hw) : for (const QString &filter : QMPlay2Core.getSettings().getStringList("VideoFilters")) { videoFilters.first += filter.mid(1); - videoFilters.second += filter.leftRef(1).toInt(); + videoFilters.second += filter.left(1).toInt(); } for (int i = 0; i < videoFilters.first.count(); ++i) diff --git a/src/gui/PlayClass.cpp b/src/gui/PlayClass.cpp index 1138db76..5b103071 100644 --- a/src/gui/PlayClass.cpp +++ b/src/gui/PlayClass.cpp @@ -311,14 +311,14 @@ void PlayClass::seek(double pos, bool allowAccurate) void PlayClass::chStream(const QString &s) { if (s.startsWith("audio")) - chosenAudioStream = s.rightRef(s.length() - 5).toInt(); + chosenAudioStream = s.rightRef(s.length() - 5).toString().toInt(); else if (s.startsWith("video")) - chosenVideoStream = s.rightRef(s.length() - 5).toInt(); + chosenVideoStream = s.rightRef(s.length() - 5).toString().toInt(); else if (s.startsWith("subtitles")) - chosenSubtitlesStream = s.rightRef(s.length() - 9).toInt(); + chosenSubtitlesStream = s.rightRef(s.length() - 9).toString().toInt(); else if (s.startsWith("fileSubs")) { - int idx = s.rightRef(s.length() - 8).toInt(); + int idx = s.rightRef(s.length() - 5).toString().toInt(); if (fileSubsList.count() > idx) loadSubsFile(fileSubsList[idx]); } diff --git a/src/gui/PlayClass.hpp b/src/gui/PlayClass.hpp index 41e4e629..bc4152ed 100644 --- a/src/gui/PlayClass.hpp +++ b/src/gui/PlayClass.hpp @@ -44,7 +44,7 @@ enum SEEK_REPEAT = -3 }; -class PlayClass final : public QObject +class PlayClass : public QObject { Q_OBJECT friend class DemuxerThr; @@ -53,7 +53,7 @@ class PlayClass final : public QObject friend class AudioThr; public: PlayClass(); - ~PlayClass(); + ~PlayClass() final; Q_SLOT void play(const QString &); Q_SLOT void stop(bool quitApp = false); diff --git a/src/gui/PlaylistDock.cpp b/src/gui/PlaylistDock.cpp index f2872ab0..29e5de72 100644 --- a/src/gui/PlaylistDock.cpp +++ b/src/gui/PlaylistDock.cpp @@ -56,7 +56,7 @@ PlaylistDock::PlaylistDock() : connect(list, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(itemDoubleClicked(QTreeWidgetItem *))); connect(list, SIGNAL(returnItem(QTreeWidgetItem *)), this, SLOT(addAndPlay(QTreeWidgetItem *))); - connect(list, &PlaylistWidget::itemExpanded, this, &PlaylistDock::maybeDoQuickSync, Qt::QueuedConnection); // Must be queued to not crash at startup in some cases + connect(list, SIGNAL(itemExpanded(QTreeWidgetItem *)), this, SLOT(maybeDoQuickSync(QTreeWidgetItem *)), Qt::QueuedConnection); connect(list, SIGNAL(visibleItemsCount(int)), this, SLOT(visibleItemsCount(int))); connect(list, SIGNAL(addStatus(bool)), findE, SLOT(setDisabled(bool))); connect(findE, SIGNAL(textChanged(const QString &)), this, SLOT(findItems(const QString &))); diff --git a/src/gui/PlaylistWidget.cpp b/src/gui/PlaylistWidget.cpp index 87164c7c..3c3a38bc 100644 --- a/src/gui/PlaylistWidget.cpp +++ b/src/gui/PlaylistWidget.cpp @@ -57,20 +57,6 @@ static inline MenuBar::Playlist *playlistMenu() return QMPlay2GUI.menuBar->playlist; } -/* PlaylistItem class */ -class PlaylistItem : public QTreeWidgetItem -{ -public: - bool operator <(const QTreeWidgetItem &other) const override - { - if (treeWidget() && treeWidget()->sortColumn() == 2) - { - return (data(2, Qt::UserRole) < other.data(2, Qt::UserRole)); - } - return QTreeWidgetItem::operator <(other); - } -}; - /* UpdateEntryThr class */ UpdateEntryThr::UpdateEntryThr(PlaylistWidget &pLW) : pendingUpdates(0), @@ -595,7 +581,7 @@ PlaylistWidget::PlaylistWidget() : setAnimated(true); header()->setStretchLastSection(false); setHeaderHidden(true); - header()->setSectionResizeMode(0, QHeaderView::Stretch); + header()->setResizeMode(0, QHeaderView::Stretch); header()->hideSection(1); setItemsResizeToContents(true); setIconSize({22, 22}); @@ -608,7 +594,7 @@ PlaylistWidget::PlaylistWidget() : connect(&animationTimer, SIGNAL(timeout()), this, SLOT(animationUpdate())); connect(&addTimer, SIGNAL(timeout()), this, SLOT(addTimerElapsed())); connect(&addThr, SIGNAL(status(bool)), this, SIGNAL(addStatus(bool))); - connect(playlistMenu(), &MenuBar::Playlist::aboutToShow, this, &PlaylistWidget::createExtensionsMenu); + connect(playlistMenu(), SIGNAL(aboutToShow()), this, SLOT(createExtensionsMenu())); } QString PlaylistWidget::getUrl(QTreeWidgetItem *tWI) const @@ -622,7 +608,7 @@ void PlaylistWidget::setItemsResizeToContents(bool b) { const QHeaderView::ResizeMode rm = b ? QHeaderView::ResizeToContents : QHeaderView::Fixed; for (int i = 1; i <= 2; ++i) - header()->setSectionResizeMode(i, rm); + header()->setResizeMode(i, rm); } void PlaylistWidget::sortCurrentGroup(int column, Qt::SortOrder sortOrder) @@ -915,7 +901,7 @@ void PlaylistWidget::setEntryFont(QTreeWidgetItem *tWI, const int flags) QTreeWidgetItem *PlaylistWidget::newGroup(const QString &name, const QString &url, QTreeWidgetItem *parent, int insertChildAt, QStringList *existingEntries) { - QTreeWidgetItem *tWI = new PlaylistItem; + QTreeWidgetItem *tWI = new QTreeWidgetItem; tWI->setFlags(tWI->flags() | Qt::ItemIsEditable); QMPlay2GUI.setTreeWidgetItemIcon(tWI, url.isEmpty() ? *QMPlay2GUI.groupIcon : *QMPlay2GUI.folderIcon, 0, this); @@ -928,9 +914,10 @@ QTreeWidgetItem *PlaylistWidget::newGroup(const QString &name, const QString &ur QMetaObject::invokeMethod(this, "insertItem", Q_ARG(QTreeWidgetItem *, tWI), Q_ARG(QTreeWidgetItem *, parent), Q_ARG(int, insertChildAt)); return tWI; } + QTreeWidgetItem *PlaylistWidget::newEntry(const Playlist::Entry &entry, QTreeWidgetItem *parent, const Functions::DemuxersInfo &demuxersInfo, int insertChildAt, QStringList *existingEntries) { - QTreeWidgetItem *tWI = new PlaylistItem; + QTreeWidgetItem *tWI = new QTreeWidgetItem; QIcon icon; Functions::getDataIfHasPluginPrefix(entry.url, nullptr, nullptr, &icon, nullptr, demuxersInfo); diff --git a/src/gui/PlaylistWidget.hpp b/src/gui/PlaylistWidget.hpp index 1f20e523..dfbd11c1 100644 --- a/src/gui/PlaylistWidget.hpp +++ b/src/gui/PlaylistWidget.hpp @@ -34,7 +34,7 @@ class QTreeWidgetItem; class PlaylistWidget; class Demuxer; -class UpdateEntryThr final : public QThread +class UpdateEntryThr : public QThread { Q_OBJECT @@ -51,7 +51,7 @@ public: } private: - void run() override; + void run() override final; QAtomicInt pendingUpdates; IOController<> ioCtrl; @@ -84,7 +84,7 @@ private slots: void finished(); }; -class AddThr final : public QThread +class AddThr : public QThread { Q_OBJECT public: @@ -101,7 +101,7 @@ private slots: void modifyItemFlags(QTreeWidgetItem *tWI, int flags); void deleteTreeWidgetItem(QTreeWidgetItem *tWI); private: - void run() override; + void run() override final; bool add(const QStringList &urls, QTreeWidgetItem *parent, const Functions::DemuxersInfo &demuxersInfo, QStringList *existingEntries = nullptr, bool loadList = false); QTreeWidgetItem *insertPlaylistEntries(const Playlist::Entries &entries, QTreeWidgetItem *parent, const Functions::DemuxersInfo &demuxersInfo, int insertChildAt, QStringList *existingEntries); @@ -120,7 +120,7 @@ signals: void status(bool s); }; -class PlaylistWidget final : public QTreeWidget +class PlaylistWidget : public QTreeWidget { friend class AddThr; friend class UpdateEntryThr; @@ -194,14 +194,12 @@ private: void quickSyncScanDirs(const QString &pth, QTreeWidgetItem *par, bool &mustRefresh, bool recursive, QTreeWidgetItem *&itemToNull); - void createExtensionsMenu(); - - void mouseMoveEvent(QMouseEvent *) override; - void dragEnterEvent(QDragEnterEvent *) override; - void dragMoveEvent(QDragMoveEvent *) override; - void dropEvent(QDropEvent *) override; - void paintEvent(QPaintEvent *) override; - void scrollContentsBy(int dx, int dy) override; + void mouseMoveEvent(QMouseEvent *) override final; + void dragEnterEvent(QDragEnterEvent *) override final; + void dragMoveEvent(QDragMoveEvent *) override final; + void dropEvent(QDropEvent *) override final; + void paintEvent(QPaintEvent *) override final; + void scrollContentsBy(int dx, int dy) override final; QRect getArcRect(int size); @@ -226,8 +224,10 @@ private slots: void addTimerElapsed(); public slots: void modifyMenu(); + void createExtensionsMenu(); signals: void returnItem(QTreeWidgetItem *); void visibleItemsCount(int); void addStatus(bool s); + void aboutToShow(); }; diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index d5b5a702..32809a17 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -26,7 +26,7 @@ #include #include -#include +#include #include #include #include @@ -55,6 +55,8 @@ #include #include +#include + #include "ui_SettingsGeneral.h" #include "ui_SettingsPlayback.h" #include "ui_SettingsPlaybackModulesList.h" @@ -124,7 +126,7 @@ void SettingsWidget::InitSettings() QMPSettings.init("AudioLanguage", QString()); QMPSettings.init("SubtitlesLanguage", QString()); - QMPSettings.init("screenshotPth", QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).value(0, QDir::homePath())); + QMPSettings.init("screenshotPth", QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); #ifdef Q_OS_WIN QMPSettings.init("screenshotFormat", ".bmp"); #else @@ -230,6 +232,31 @@ void SettingsWidget::SetAudioChannels(int chn) QMPlay2Core.getSettings().set("ForceChannels", forceChannels); } +void SettingsWidget::onStoreARatioAndZoomToggled(bool checked) +{ + if (checked) + { + page2->keepZoom->setChecked(true); + page2->keepARatio->setChecked(true); + } +} + +void SettingsWidget::onKeepZoomToggled(bool checked) +{ + if (!checked && !page2->keepARatio->isChecked()) + { + page2->storeARatioAndZoomB->setChecked(false); + } +} + +void SettingsWidget::onKeepARatioToggled(bool checked) +{ + if (!checked && !page2->keepZoom->isChecked()) + { + page2->storeARatioAndZoomB->setChecked(false); + } +} + SettingsWidget::SettingsWidget(int page, const QString &moduleName, QWidget *videoEq) : videoEq(videoEq), videoEqOriginalParent(videoEq->parentWidget()), wasShow(false), @@ -451,29 +478,13 @@ SettingsWidget::SettingsWidget(int page, const QString &moduleName, QWidget *vid page2->wheelVolumeB->setChecked(QMPSettings.getBool("WheelVolume")); page2->storeARatioAndZoomB->setChecked(QMPSettings.getBool("StoreARatioAndZoom")); - connect(page2->storeARatioAndZoomB, &QCheckBox::toggled, this, [this](bool checked) { - if (checked) - { - page2->keepZoom->setChecked(true); - page2->keepARatio->setChecked(true); - } - }); + connect(page2->storeARatioAndZoomB, SIGNAL(toggled(bool)), this, SLOT(onStoreARatioAndZoomToggled(bool))); page2->keepZoom->setChecked(QMPSettings.getBool("KeepZoom")); - connect(page2->keepZoom, &QCheckBox::toggled, this, [this](bool checked) { - if (!checked && !page2->keepARatio->isChecked()) - { - page2->storeARatioAndZoomB->setChecked(false); - } - }); + connect(page2->keepZoom, SIGNAL(toggled(bool)), this, SLOT(onKeepZoomToggled(bool))); page2->keepARatio->setChecked(QMPSettings.getBool("KeepARatio")); - connect(page2->keepARatio, &QCheckBox::toggled, this, [this](bool checked) { - if (!checked && !page2->keepZoom->isChecked()) - { - page2->storeARatioAndZoomB->setChecked(false); - } - }); + connect(page2->keepARatio, SIGNAL(toggled(bool)), this, SLOT(onKeepARatioToggled(bool))); page2->showBufferedTimeOnSlider->setChecked(QMPSettings.getBool("ShowBufferedTimeOnSlider")); page2->savePos->setChecked(QMPSettings.getBool("SavePos")); @@ -677,7 +688,7 @@ void SettingsWidget::applyProxy() Settings &QMPSettings = QMPlay2Core.getSettings(); if (!QMPSettings.getBool("Proxy/Use")) { - qunsetenv("http_proxy"); + unsetenv("http_proxy"); } else { diff --git a/src/gui/SettingsWidget.hpp b/src/gui/SettingsWidget.hpp index b36ac00c..0047b120 100644 --- a/src/gui/SettingsWidget.hpp +++ b/src/gui/SettingsWidget.hpp @@ -35,7 +35,7 @@ namespace Ui { class ModulesList; } -class SettingsWidget final : public QWidget +class SettingsWidget : public QWidget { Q_OBJECT public: @@ -44,7 +44,7 @@ public: static void SetAudioChannels(int chn); SettingsWidget(int page, const QString &module, QWidget *videoEq); - ~SettingsWidget(); + ~SettingsWidget() final; void setAudioChannels(); private: @@ -56,8 +56,8 @@ private: inline QString getSelectedProfile(); - void showEvent(QShowEvent *) override; - void closeEvent(QCloseEvent *) override; + void showEvent(QShowEvent *) override final; + void closeEvent(QCloseEvent *) override final; Ui::GeneralSettings *page1; Ui::PlaybackSettings *page2; @@ -89,6 +89,9 @@ private slots: void resetSettings(); void profileListIndexChanged(int index); void removeProfile(); + void onStoreARatioAndZoomToggled(bool checked); + void onKeepZoomToggled(bool checked); + void onKeepARatioToggled(bool checked); signals: void settingsChanged(int, bool); void setWheelStep(int); diff --git a/src/gui/ShortcutHandler.hpp b/src/gui/ShortcutHandler.hpp index 1723eb78..ad140325 100644 --- a/src/gui/ShortcutHandler.hpp +++ b/src/gui/ShortcutHandler.hpp @@ -23,23 +23,23 @@ class QAction; -class ShortcutHandler final : public QAbstractTableModel +class ShortcutHandler : public QAbstractTableModel { Q_DECLARE_TR_FUNCTIONS(ShortcutHandler) public: ShortcutHandler(QObject *parent); - ~ShortcutHandler(); + ~ShortcutHandler() final; - int columnCount(const QModelIndex &parent) const override; - int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override final; + int rowCount(const QModelIndex &parent) const override final; - Qt::ItemFlags flags(const QModelIndex &index) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override final; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override final; - QVariant data(const QModelIndex &index, int role) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role) const override final; + bool setData(const QModelIndex &index, const QVariant &value, int role) override final; void appendAction(QAction *action, const QString &settingsName, const QString &defaultShortcut); diff --git a/src/gui/TagEditor.cpp b/src/gui/TagEditor.cpp index e780f555..00d82972 100644 --- a/src/gui/TagEditor.cpp +++ b/src/gui/TagEditor.cpp @@ -26,6 +26,11 @@ #define TAGLIB19 (TAGLIB_VERSION >= 0x109) #define TAGLIB1B (TAGLIB_VERSION >= 0x10B) +/* Find taglib */ +#ifndef TAGLIB_FULL_INCLUDE_PATH +#define TAGLIB_FULL_INCLUDE_PATH +#endif + #ifdef TAGLIB_FULL_INCLUDE_PATH #include #else diff --git a/src/gui/TagEditor.hpp b/src/gui/TagEditor.hpp index 24edcf0e..283c1497 100644 --- a/src/gui/TagEditor.hpp +++ b/src/gui/TagEditor.hpp @@ -29,12 +29,12 @@ class QLineEdit; class QSpinBox; class QLabel; -class PictureW final : public QWidget +class PictureW : public QWidget { public: PictureW(TagLib::ByteVector &picture); private: - void paintEvent(QPaintEvent *) override; + void paintEvent(QPaintEvent *) override final; TagLib::ByteVector &picture; }; @@ -44,7 +44,7 @@ class TagEditor : public QGroupBox Q_OBJECT public: TagEditor(); - ~TagEditor(); + ~TagEditor() final; bool open(const QString &fileName); void clear(); diff --git a/src/gui/Updater.hpp b/src/gui/Updater.hpp index 027ce819..a18d1158 100644 --- a/src/gui/Updater.hpp +++ b/src/gui/Updater.hpp @@ -30,7 +30,7 @@ class QLabel; #endif -class Updater final : public +class Updater : public #ifdef UPDATER QDialog #else @@ -45,7 +45,7 @@ public: #else Updater(QObject *parent); #endif - ~Updater(); + ~Updater() final; #ifdef UPDATER bool downloading() const; diff --git a/src/gui/VideoAdjustmentW.cpp b/src/gui/VideoAdjustmentW.cpp index d3f71cca..ad9d1af0 100644 --- a/src/gui/VideoAdjustmentW.cpp +++ b/src/gui/VideoAdjustmentW.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include enum CONTROLS { @@ -69,37 +69,31 @@ VideoAdjustmentW::VideoAdjustmentW() slider->setRange(-100, 100); slider->setWheelStep(1); slider->setValue(0); - connect(slider, &Slider::valueChanged, this, [=](int v) { - valueL->setText(QString::number(v)); - emit videoAdjustmentChanged(titleL->text() + QString::number(v)); - }); + + connect(slider, SIGNAL(valueChanged(int)), this, SLOT(onSliderValueChanged(int))); + m_sliders.push_back(slider); QAction *actionDown = new QAction(this); - connect(actionDown, &QAction::triggered, this, [=] { - slider->setValue(slider->value() - g_step); - }); - QAction *actionUp = new QAction(this); - connect(actionUp, &QAction::triggered, this, [=] { - slider->setValue(slider->value() + g_step); - }); + + connect(actionDown, SIGNAL(triggered()), this, SLOT(onActionDownTriggered())); + connect(actionUp, SIGNAL(triggered()), this, SLOT(onActionUpTriggered())); m_actions.push_back({actionDown, actionUp}); layout->addWidget(titleL, i, 0); layout->addWidget(slider, i, 1); layout->addWidget(valueL, i, 2); + + m_labelMap[slider] = std::make_pair(valueL, titleL); } QPushButton *resetB = new QPushButton(tr("Reset")); - connect(resetB, &QPushButton::clicked, this, [this] { - for (int i = 0; i < CONTROLS_COUNT; ++i) - m_sliders[i]->setValue(0); - }); + connect(resetB, SIGNAL(clicked()), this, SLOT(onResetClicked())); m_resetAction = new QAction(tr("Reset video adjustments"), this); - connect(m_resetAction, &QAction::triggered, resetB, &QPushButton::click); + connect(m_resetAction, SIGNAL(triggered()), resetB, SLOT(click())); layout->addWidget(resetB, layout->rowCount(), 0, 1, 3); layout->addItem(new QSpacerItem(40, 0, QSizePolicy::Maximum, QSizePolicy::Minimum), layout->rowCount(), 2); @@ -114,6 +108,7 @@ void VideoAdjustmentW::restoreValues() for (int i = 0; i < CONTROLS_COUNT; ++i) m_sliders[i]->setValue(QMPlay2Core.getSettings().getInt(QString("VideoAdjustment/") + g_controlsNames[i])); } + void VideoAdjustmentW::saveValues() { for (int i = 0; i < CONTROLS_COUNT; ++i) @@ -162,6 +157,7 @@ void VideoAdjustmentW::setKeyShortcuts() appendAction(m_resetAction, QString(), "reset", "0"); } + void VideoAdjustmentW::addActionsToWidget(QWidget *w) { for (int i = 0; i < CONTROLS_COUNT; ++i) @@ -171,3 +167,45 @@ void VideoAdjustmentW::addActionsToWidget(QWidget *w) } w->addAction(m_resetAction); } + +void VideoAdjustmentW::onSliderValueChanged(int value) +{ + Slider *slider = qobject_cast(sender()); + if (slider && m_labelMap.count(slider)) { + QLabel *valueL = m_labelMap[slider].first; + QLabel *titleL = m_labelMap[slider].second; + valueL->setText(QString::number(value)); + emit videoAdjustmentChanged(titleL->text() + QString::number(value)); + } +} + +void VideoAdjustmentW::onActionDownTriggered() +{ + QAction *action = qobject_cast(sender()); + for (size_t i = 0; i < m_actions.size(); ++i) { + if (m_actions[i][0] == action) { // Use array index 0 for "first" + Slider *slider = m_sliders[i]; + slider->setValue(slider->value() - g_step); + break; + } + } +} + +void VideoAdjustmentW::onActionUpTriggered() +{ + QAction *action = qobject_cast(sender()); + for (size_t i = 0; i < m_actions.size(); ++i) { + if (m_actions[i][1] == action) { // Use array index 1 for "second" + Slider *slider = m_sliders[i]; + slider->setValue(slider->value() + g_step); + break; + } + } +} + +void VideoAdjustmentW::onResetClicked() +{ + for (int i = 0; i < CONTROLS_COUNT; ++i) { + m_sliders[i]->setValue(0); + } +} diff --git a/src/gui/VideoAdjustmentW.hpp b/src/gui/VideoAdjustmentW.hpp index 419ebb01..9e9c1ece 100644 --- a/src/gui/VideoAdjustmentW.hpp +++ b/src/gui/VideoAdjustmentW.hpp @@ -18,22 +18,25 @@ #pragma once +#include #include -#include #include +#include +#include +#include class ModuleParams; class QAction; class Slider; -class VideoAdjustmentW final : public QWidget +class VideoAdjustmentW : public QWidget { Q_OBJECT public: VideoAdjustmentW(); - ~VideoAdjustmentW(); + ~VideoAdjustmentW() final; void restoreValues(); void saveValues(); @@ -51,4 +54,11 @@ private: std::vector m_sliders; std::vector> m_actions; QAction *m_resetAction = nullptr; + std::map> m_labelMap; + +private slots: + void onSliderValueChanged(int value); + void onActionDownTriggered(); + void onActionUpTriggered(); + void onResetClicked(); }; diff --git a/src/gui/VideoDock.cpp b/src/gui/VideoDock.cpp index 74485f76..e90c5773 100644 --- a/src/gui/VideoDock.cpp +++ b/src/gui/VideoDock.cpp @@ -38,6 +38,12 @@ constexpr int g_hideCursorTimeout = 750; +void VideoDock::onDockVideo(QWidget *w) +{ + iDW.setWidget(w); + mouseMoveEvent(nullptr); +} + VideoDock::VideoDock() : isTouch(false), touchEnded(false), iDW(QMPlay2GUI.grad1, QMPlay2GUI.grad2, QMPlay2GUI.qmpTxt), @@ -88,10 +94,7 @@ VideoDock::VideoDock() : connect(&iDW, SIGNAL(resized(int, int)), this, SLOT(resizedIDW(int, int))); connect(&iDW, SIGNAL(hasCoverImage(bool)), this, SLOT(hasCoverImage(bool))); connect(this, SIGNAL(visibilityChanged(bool)), this, SLOT(visibilityChanged(bool))); - connect(&QMPlay2Core, &QMPlay2CoreClass::dockVideo, this, [this](QWidget *w) { - iDW.setWidget(w); - mouseMoveEvent(nullptr); - }); + connect(&QMPlay2Core, SIGNAL(dockVideo(QWidget *)), this, SLOT(onDockVideo(QWidget *))); if ((isBreeze = QApplication::style()->objectName() == "breeze")) setStyle(&commonStyle); diff --git a/src/gui/VideoDock.hpp b/src/gui/VideoDock.hpp index bd26b3f9..c19f4627 100644 --- a/src/gui/VideoDock.hpp +++ b/src/gui/VideoDock.hpp @@ -26,7 +26,7 @@ class QMenu; -class VideoDock final : public DockWidget +class VideoDock : public DockWidget { Q_OBJECT public: @@ -49,17 +49,17 @@ private: void unsetCursor(QWidget *w); - void dragEnterEvent(QDragEnterEvent *) override; - void dropEvent(QDropEvent *) override; - void mouseMoveEvent(QMouseEvent *) override; - void mouseDoubleClickEvent(QMouseEvent *) override; - void mousePressEvent(QMouseEvent *) override; - void mouseReleaseEvent(QMouseEvent *) override; - void moveEvent(QMoveEvent *) override; - void wheelEvent(QWheelEvent *) override; - void leaveEvent(QEvent *) override; - void enterEvent(QEvent *) override; - bool event(QEvent *) override; + void dragEnterEvent(QDragEnterEvent *) override final; + void dropEvent(QDropEvent *) override final; + void mouseMoveEvent(QMouseEvent *) override final; + void mouseDoubleClickEvent(QMouseEvent *) override final; + void mousePressEvent(QMouseEvent *) override final; + void mouseReleaseEvent(QMouseEvent *) override final; + void moveEvent(QMoveEvent *) override final; + void wheelEvent(QWheelEvent *) override final; + void leaveEvent(QEvent *) override final; + void enterEvent(QEvent *) override final; + bool event(QEvent *) override final; QTimer hideCursorTim, leftButtonPlayTim; InDockW iDW; @@ -76,6 +76,7 @@ private slots: void updateImage(const QImage &); void visibilityChanged(bool); void hasCoverImage(bool); + void onDockVideo(QWidget *w); signals: void resized(int, int); void itemDropped(const QString &, bool); diff --git a/src/gui/VideoThr.cpp b/src/gui/VideoThr.cpp index 83c03089..85a503a8 100644 --- a/src/gui/VideoThr.cpp +++ b/src/gui/VideoThr.cpp @@ -181,7 +181,7 @@ void VideoThr::initFilters(bool processParams) { for (QString filterName : QMPSettings.getStringList("VideoFilters")) { - if (filterName.leftRef(1).toInt()) //if filter is enabled + if (filterName.left(1).toInt()) // if filter is enabled { VideoFilter *filter = filters.on((filterName = filterName.mid(1))); bool ok = false; @@ -231,7 +231,7 @@ inline VideoWriter *VideoThr::videoWriter() const void VideoThr::run() { - bool skip = false, paused = false, oneFrame = false, useLastDelay = false, lastOSDListEmpty = true, maybeFlush = false, lastAVDesync = false, interlaced = false, err = false; + bool skip = false, paused = false, oneFrame = false, useLastDelay = false, lastOSDListEmpty = true, maybeFlush = false, lastAVDesync = false, interlaced = false, err = false, skipNonKey = false; double tmp_time = 0.0, sync_last_pts = 0.0, frame_timer = -1.0, sync_timer = 0.0, framesDisplayedTime = 0.0; QMutex emptyBufferMutex; VideoFrame videoFrame; @@ -398,17 +398,18 @@ void VideoThr::run() /**/ filtersMutex.lock(); - if (playC.flushVideo) + if (playC.flushVideo || skipNonKey) { filters.clearBuffers(); - frame_timer = -1.0; + if (playC.flushVideo) + frame_timer = -1.0; } - if (!packet.isEmpty() || maybeFlush) + if ((!packet.isEmpty() || maybeFlush) && (!skipNonKey || packet.hasKeyFrame)) { VideoFrame decoded; QByteArray newPixelFormat; - const int bytes_consumed = dec->decodeVideo(packet, decoded, newPixelFormat, playC.flushVideo, skip ? ~0 : (fast >> 1)); + const int bytes_consumed = dec->decodeVideo(packet, decoded, newPixelFormat, playC.flushVideo || skipNonKey, (skip && !skipNonKey) ? ~0 : (fast >> 1)); if (!newPixelFormat.isEmpty()) emit playC.pixelFormatUpdate(newPixelFormat); if (playC.flushVideo) @@ -437,7 +438,9 @@ void VideoThr::run() gotFrameOrError = true; } else if (skip) + { filters.removeLastFromInputBuffer(); + } if (bytes_consumed < 0) { gotFrameOrError = true; @@ -447,6 +450,7 @@ void VideoThr::run() { tmp_br += bytes_consumed; } + skipNonKey = false; } // This thread will wait for "DemuxerThr" which'll detect this error and restart with new decoder. @@ -485,7 +489,7 @@ void VideoThr::run() playC.chPos(packet.ts); double delay = packet.ts - playC.frame_last_pts; - if (useLastDelay || delay <= 0.0 || (playC.frame_last_pts <= 0.0 && delay > playC.frame_last_delay)) + if (useLastDelay || delay <= 0.0 || (playC.frame_last_pts <= 0.0 && delay > playC.frame_last_delay) || (delay > 1.0 && delay / playC.frame_last_delay > 10.0)) { delay = playC.frame_last_delay; useLastDelay = false; @@ -494,11 +498,11 @@ void VideoThr::run() tmp_time += delay; ++frames; - delay /= playC.speed; - playC.frame_last_delay = delay; playC.frame_last_pts = packet.ts; + delay /= playC.speed; + if (playC.skipAudioFrame < 0.0) playC.skipAudioFrame = 0.0; @@ -559,6 +563,8 @@ void VideoThr::run() delay = 0.0; if (fast >= 7) skip = true; + if (fast >= 56 || (fast >= 28 && fDiff >= max_threshold * 4.0)) + skipNonKey = true; } else if (diff > 0.0) //obraz idzie za szybko { @@ -616,7 +622,7 @@ void VideoThr::run() { oneFrame = canWrite = false; QMetaObject::invokeMethod(this, "write", Q_ARG(VideoFrame, videoFrame), Q_ARG(quint32, seq)); - if (canSkipFrames) + if (canSkipFrames && !skipNonKey) ++framesDisplayed; } if (canSkipFrames) @@ -660,7 +666,7 @@ void VideoThr::screenshot(VideoFrame videoFrame) quint16 num = 0; for (const QString &f : QDir(dir).entryList({"QMPlay2_snap_?????" + ext}, QDir::Files, QDir::Name)) { - const quint16 n = f.midRef(13, 5).toUShort(); + const quint16 n = f.mid(13, 5).toUShort(); if (n > num) num = n; } diff --git a/src/gui/VideoThr.hpp b/src/gui/VideoThr.hpp index 728edce8..6713829f 100644 --- a/src/gui/VideoThr.hpp +++ b/src/gui/VideoThr.hpp @@ -25,16 +25,16 @@ class QMPlay2OSD; class VideoWriter; -class VideoThr final : public AVThread +class VideoThr : public AVThread { Q_OBJECT public: VideoThr(PlayClass &, VideoWriter *, const QStringList &pluginsName = {}); - ~VideoThr(); + ~VideoThr() final; - void stop(bool terminate = false) override; + void stop(bool terminate = false) override final; - bool hasDecoderError() const override; + bool hasDecoderError() const override final; inline VideoWriter *getHWAccelWriter() const { @@ -80,7 +80,7 @@ public: private: inline VideoWriter *videoWriter() const; - void run() override; + void run() override final; bool deleteSubs, syncVtoA, doScreenshot, canWrite, deleteOSD, deleteFrame, gotFrameOrError, decoderError; double lastSampleAspectRatio; diff --git a/src/gui/macOS/ScreenSaver.cpp b/src/gui/macOS/ScreenSaver.cpp index 0165d5cc..d71b25b7 100644 --- a/src/gui/macOS/ScreenSaver.cpp +++ b/src/gui/macOS/ScreenSaver.cpp @@ -20,6 +20,10 @@ #include +#ifdef __APPLE__ + #include +#endif + #define QMPLAY2_MEDIA_PLAYBACK CFSTR("QMPlay2 media playback") class ScreenSaverPriv @@ -32,8 +36,13 @@ public: inline void inhibit() { - m_okDisp = (IOPMAssertionCreateWithName(kIOPMAssertPreventUserIdleDisplaySleep, kIOPMAssertionLevelOn, QMPLAY2_MEDIA_PLAYBACK, &m_idDisp) == kIOReturnSuccess); - m_okSys = (IOPMAssertionCreateWithName(kIOPMAssertPreventUserIdleSystemSleep, kIOPMAssertionLevelOn, QMPLAY2_MEDIA_PLAYBACK, &m_idSys) == kIOReturnSuccess); +#if defined(__APPLE__) && (MAC_OS_X_VERSION_MAX_ALLOWED < 1060) + m_okDisp = (IOPMAssertionCreate(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, &m_idDisp) == kIOReturnSuccess); + m_okSys = (IOPMAssertionCreate(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, &m_idSys) == kIOReturnSuccess); +#else + m_okDisp = (IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, QMPLAY2_MEDIA_PLAYBACK, &m_idDisp) == kIOReturnSuccess); + m_okSys = (IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, QMPLAY2_MEDIA_PLAYBACK, &m_idSys) == kIOReturnSuccess); +#endif } inline void unInhibit() { diff --git a/src/modules/AudioCD/AudioCD.hpp b/src/modules/AudioCD/AudioCD.hpp index 95065ae0..bd19f789 100644 --- a/src/modules/AudioCD/AudioCD.hpp +++ b/src/modules/AudioCD/AudioCD.hpp @@ -24,19 +24,19 @@ class CDIODestroyTimer; -class AudioCD final : public Module +class AudioCD : public Module { Q_OBJECT public: AudioCD(); - ~AudioCD(); + ~AudioCD() final; private: - QList getModulesInfo(const bool) const override; - void *createInstance(const QString &) override; + QList getModulesInfo(const bool) const override final; + void *createInstance(const QString &) override final; - QList getAddActions() override; + QList getAddActions() override final; - SettingsWidget *getSettingsWidget() override; + SettingsWidget *getSettingsWidget() override final; QIcon CD; @@ -52,13 +52,13 @@ class QGridLayout; class QGroupBox; class QCheckBox; -class ModuleSettingsWidget final : public Module::SettingsWidget +class ModuleSettingsWidget : public Module::SettingsWidget { Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget) public: ModuleSettingsWidget(Module &); private: - void saveSettings() override; + void saveSettings() override final; QGroupBox *audioCDB; QCheckBox *useCDDB, *useCDTEXT; diff --git a/src/modules/AudioCD/AudioCDDemux.hpp b/src/modules/AudioCD/AudioCDDemux.hpp index 3a0c6f3d..d98bc518 100644 --- a/src/modules/AudioCD/AudioCDDemux.hpp +++ b/src/modules/AudioCD/AudioCDDemux.hpp @@ -26,19 +26,19 @@ #include #include -class CDIODestroyTimer final : public QObject +class CDIODestroyTimer : public QObject { Q_OBJECT public: CDIODestroyTimer(); - ~CDIODestroyTimer(); + ~CDIODestroyTimer() final; Q_SIGNAL void setInstance(CdIo_t *_cdio, const QString &_device, unsigned _discID); CdIo_t *getInstance(const QString &_device, unsigned &_discID); private slots: void setInstanceSlot(CdIo_t *_cdio, const QString &_device, unsigned _discID); private: - void timerEvent(QTimerEvent *e) override; + void timerEvent(QTimerEvent *e) override final; QAtomicInt timerId; CdIo_t *cdio; @@ -48,7 +48,7 @@ private: /**/ -class AudioCDDemux final : public Demuxer +class AudioCDDemux : public Demuxer { Q_DECLARE_TR_FUNCTIONS(AudioCDDemux) public: @@ -56,23 +56,23 @@ public: AudioCDDemux(Module &, CDIODestroyTimer &destroyTimer); private: - ~AudioCDDemux(); + ~AudioCDDemux() final; - bool set() override; + bool set() override final; - QString name() const override; - QString title() const override; - QList tags() const override; - double length() const override; - int bitrate() const override; + QString name() const override final; + QString title() const override final; + QList tags() const override final; + double length() const override final; + int bitrate() const override final; - bool seek(double, bool) override; - bool read(Packet &, int & ) override; - void abort() override; + bool seek(double, bool) override final; + bool read(Packet &, int & ) override final; + void abort() override final; - bool open(const QString &) override; + bool open(const QString &) override final; - Playlist::Entries fetchTracks(const QString &url, bool &ok) override; + Playlist::Entries fetchTracks(const QString &url, bool &ok) override final; /**/ diff --git a/src/modules/AudioFilters/AudioFilters.hpp b/src/modules/AudioFilters/AudioFilters.hpp index 6823f625..02d3d1c1 100644 --- a/src/modules/AudioFilters/AudioFilters.hpp +++ b/src/modules/AudioFilters/AudioFilters.hpp @@ -20,15 +20,15 @@ #include -class AudioFilters final : public Module +class AudioFilters : public Module { public: AudioFilters(); private: - QList getModulesInfo(const bool) const override; - void *createInstance(const QString &) override; + QList getModulesInfo(const bool) const override final; + void *createInstance(const QString &) override final; - SettingsWidget *getSettingsWidget() override; + SettingsWidget *getSettingsWidget() override final; }; /**/ @@ -41,7 +41,7 @@ class QComboBox; class QSpinBox; class Slider; -class ModuleSettingsWidget final : public Module::SettingsWidget +class ModuleSettingsWidget : public Module::SettingsWidget { Q_OBJECT public: @@ -55,7 +55,7 @@ private slots: void compressor(); void defaultSettings(); private: - void saveSettings() override; + void saveSettings() override final; bool restoringDefault; diff --git a/src/modules/AudioFilters/BS2B.hpp b/src/modules/AudioFilters/BS2B.hpp index f62e4235..00822620 100644 --- a/src/modules/AudioFilters/BS2B.hpp +++ b/src/modules/AudioFilters/BS2B.hpp @@ -22,17 +22,17 @@ #include -class BS2B final : public AudioFilter +class BS2B : public AudioFilter { public: BS2B(Module &module); - ~BS2B(); + ~BS2B() final; - bool set() override; + bool set() override final; private: - bool setAudioParameters(uchar, uint srate) override; - void clearBuffers() override; - double filter(Buffer &data, bool flush) override; + bool setAudioParameters(uchar, uint srate) override final; + void clearBuffers() override final; + double filter(Buffer &data, bool flush) override final; void alloc(); diff --git a/src/modules/AudioFilters/DysonCompressor.hpp b/src/modules/AudioFilters/DysonCompressor.hpp index 2f2b086f..69409429 100644 --- a/src/modules/AudioFilters/DysonCompressor.hpp +++ b/src/modules/AudioFilters/DysonCompressor.hpp @@ -25,19 +25,19 @@ #define NFILT 12 #define NEFILT 17 -class DysonCompressor final : public AudioFilter +class DysonCompressor : public AudioFilter { public: DysonCompressor(Module &module); - ~DysonCompressor(); + ~DysonCompressor() final; - bool set() override; + bool set() override final; private: - bool setAudioParameters(uchar chn, uint srate) override; - int bufferedSamples() const override; - void clearBuffers() override; - double filter(Buffer &data, bool flush) override; + bool setAudioParameters(uchar chn, uint srate) override final; + int bufferedSamples() const override final; + void clearBuffers() override final; + double filter(Buffer &data, bool flush) override final; using FloatVector = QVector; diff --git a/src/modules/AudioFilters/Echo.hpp b/src/modules/AudioFilters/Echo.hpp index c418ec3c..5ad34e3e 100644 --- a/src/modules/AudioFilters/Echo.hpp +++ b/src/modules/AudioFilters/Echo.hpp @@ -20,15 +20,15 @@ #include -class Echo final : public AudioFilter +class Echo : public AudioFilter { public: Echo(Module &); - bool set() override; + bool set() override final; private: - bool setAudioParameters(uchar, uint) override; - double filter(Buffer &, bool) override; + bool setAudioParameters(uchar, uint) override final; + double filter(Buffer &, bool) override final; void alloc(bool); diff --git a/src/modules/AudioFilters/Equalizer.hpp b/src/modules/AudioFilters/Equalizer.hpp index 7c8d9665..f6b79283 100644 --- a/src/modules/AudioFilters/Equalizer.hpp +++ b/src/modules/AudioFilters/Equalizer.hpp @@ -23,7 +23,7 @@ struct FFTContext; struct FFTComplex; -class Equalizer final : public AudioFilter +class Equalizer : public AudioFilter { public: static QVector interpolate(const QVector &, const int); @@ -31,14 +31,14 @@ public: static float getAmpl(int val); Equalizer(Module &); - ~Equalizer(); + ~Equalizer() final; - bool set() override; + bool set() override final; private: - bool setAudioParameters(uchar, uint) override; - int bufferedSamples() const override; - void clearBuffers() override; - double filter(Buffer &data, bool flush) override; + bool setAudioParameters(uchar, uint) override final; + int bufferedSamples() const override final; + void clearBuffers() override final; + double filter(Buffer &data, bool flush) override final; /**/ diff --git a/src/modules/AudioFilters/EqualizerGUI.hpp b/src/modules/AudioFilters/EqualizerGUI.hpp index eb48d902..52aa4293 100644 --- a/src/modules/AudioFilters/EqualizerGUI.hpp +++ b/src/modules/AudioFilters/EqualizerGUI.hpp @@ -20,7 +20,7 @@ #include -class GraphW final : public QWidget +class GraphW : public QWidget { public: GraphW(); @@ -31,7 +31,7 @@ public: values.resize(vals); } private: - void paintEvent(QPaintEvent *) override; + void paintEvent(QPaintEvent *) override final; QVector values; float preamp; @@ -44,15 +44,15 @@ class QCheckBox; class QSlider; class QMenu; -class EqualizerGUI final : public QWidget, public QMPlay2Extensions +class EqualizerGUI : public QWidget, public QMPlay2Extensions { Q_OBJECT public: EqualizerGUI(Module &); - bool set() override; + bool set() override final; - DockWidget *getDockWidget() override; + DockWidget *getDockWidget() override final; private slots: void wallpaperChanged(bool hasWallpaper, double alpha); void enabled(bool); @@ -80,7 +80,7 @@ private: void loadPresets(); - void showEvent(QShowEvent *event) override; + void showEvent(QShowEvent *event) override final; QMap getPresetValues(const QString &name); diff --git a/src/modules/AudioFilters/PhaseReverse.hpp b/src/modules/AudioFilters/PhaseReverse.hpp index 470aad80..59fe4456 100644 --- a/src/modules/AudioFilters/PhaseReverse.hpp +++ b/src/modules/AudioFilters/PhaseReverse.hpp @@ -20,15 +20,15 @@ #include -class PhaseReverse final : public AudioFilter +class PhaseReverse : public AudioFilter { public: PhaseReverse(Module &); - bool set() override; + bool set() override final; private: - bool setAudioParameters(uchar, uint) override; - double filter(Buffer &, bool) override; + bool setAudioParameters(uchar, uint) override final; + double filter(Buffer &, bool) override final; bool enabled, hasParameters, canFilter, reverseRight; uchar chn; diff --git a/src/modules/AudioFilters/VoiceRemoval.hpp b/src/modules/AudioFilters/VoiceRemoval.hpp index b67e6844..024c98af 100644 --- a/src/modules/AudioFilters/VoiceRemoval.hpp +++ b/src/modules/AudioFilters/VoiceRemoval.hpp @@ -20,15 +20,15 @@ #include -class VoiceRemoval final : public AudioFilter +class VoiceRemoval : public AudioFilter { public: VoiceRemoval(Module &); - bool set() override; + bool set() override final; private: - bool setAudioParameters(uchar, uint) override; - double filter(Buffer &, bool) override; + bool setAudioParameters(uchar, uint) override final; + double filter(Buffer &, bool) override final; bool enabled, hasParameters, canFilter; uchar chn; diff --git a/src/modules/Extensions/CMakeLists.txt b/src/modules/Extensions/CMakeLists.txt index 9be7afeb..9263b6f1 100644 --- a/src/modules/Extensions/CMakeLists.txt +++ b/src/modules/Extensions/CMakeLists.txt @@ -55,7 +55,11 @@ if(USE_MEDIABROWSER) set(QML Qt5::Qml) endif() -qt5_wrap_ui(Extensions_FORM_HDR ${Extensions_FORMS}) +if(USE_QT4) + qt4_wrap_ui(Extensions_FORM_HDR ${Extensions_FORMS}) +else() + qt5_wrap_ui(Extensions_FORM_HDR ${Extensions_FORMS}) +endif() set_property(SOURCE ${Extensions_FORM_HDR} PROPERTY SKIP_AUTOMOC ON) include_directories(../../qmplay2/headers @@ -69,9 +73,15 @@ add_library(${PROJECT_NAME} ${QMPLAY2_MODULE} ${Extensions_RESOURCES} ) +if(USE_QT4) + add_definitions(-I@prefix@/include/QJson4) + target_link_libraries(${PROJECT_NAME} Qt4::QtCore Qt4::QtGui QJson4) +else() + target_link_libraries(${PROJECT_NAME} ${QML}) +endif() + target_link_libraries(${PROJECT_NAME} ${DBUS} - ${QML} libqmplay2 ) diff --git a/src/modules/Extensions/Downloader.cpp b/src/modules/Extensions/Downloader.cpp index 78f30efe..0160b026 100644 --- a/src/modules/Extensions/Downloader.cpp +++ b/src/modules/Extensions/Downloader.cpp @@ -29,8 +29,6 @@ #include #include #include -#include -#include #include #include #include @@ -46,14 +44,10 @@ #include #include #include -#include -#include #include #include -Q_LOGGING_CATEGORY(downloader, "Downloader") - /**/ constexpr const char *g_defaultMp3ConvertCommand = "ffmpeg -i -vn -sn -c:a libmp3lame -ab 224k -f mp3 -y %f.mp3"; @@ -358,7 +352,7 @@ void DownloadItemW::downloadStop(bool ok) finished = true; if (!dontDeleteDownloadThr) { - if (visibleRegion().isNull()) + if (visibleRegion().isEmpty()) emit QMPlay2Core.sendMessage(titleL->text(), sizeL->text()); } } @@ -368,30 +362,12 @@ void DownloadItemW::startConversion() deleteConvertProcess(); m_convertProcess = new QProcess(this); - m_convertProcessConn[0] = connect(m_convertProcess, Overload::of(&QProcess::finished), this, [this](int exitCode) { - if (exitCode == 0) - { - sizeL->setText(tr(g_downloadComplete)); - QFile::remove(filePath); - m_needsConversion = false; - filePath = m_convertedFilePath; - downloadStop(true); - } - else - { - sizeL->setText(tr(g_conversionError)); - qCWarning(downloader) << "Failed to convert:" << m_convertProcess->program() << m_convertProcess->arguments() << m_convertProcess->readAllStandardError().constData(); - downloadStop(false); - } - }); - m_convertProcessConn[1] = connect(m_convertProcess, &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) { - if (error == QProcess::FailedToStart) - { - sizeL->setText(tr(g_conversionError)); - downloadStop(false); - qCWarning(downloader) << "Failed to start process:" << m_convertProcess->program(); - } - }); + + // Use old-style connect for Qt4 compatibility + connect(m_convertProcess, SIGNAL(finished(int, QProcess::ExitStatus)), + this, SLOT(handleConversionFinished(int, QProcess::ExitStatus))); + connect(m_convertProcess, SIGNAL(error(QProcess::ProcessError)), + this, SLOT(handleConversionError(QProcess::ProcessError))); m_needsConversion = true; finished = false; @@ -404,7 +380,7 @@ void DownloadItemW::startConversion() const auto conversionError = [&](const QString &errStr) { sizeL->setText(tr(g_conversionError)); downloadStop(false); - qCWarning(downloader).noquote() << errStr; + qWarning() << errStr; }; QString convertCommand; @@ -448,21 +424,63 @@ void DownloadItemW::startConversion() convertCommand.replace(idx1, idx2 - idx1 + 9, "\"" + m_convertedFilePath + "\""); maybeAddAbsolutePath(convertCommand); - qDebug() << "Starting conversion:" << convertCommand.toUtf8().constData(); - m_convertProcess->start(convertCommand); + // Store program name and arguments for Qt4 compatibility + m_processProgram = convertCommand.section(' ', 0, 0); // Extract the program name + m_processArguments = convertCommand.split(' ').mid(1); // Extract the arguments (everything after the program name) + + qDebug() << "Starting conversion:" << m_processProgram << m_processArguments; + + // Start the process using the program name and arguments + m_convertProcess->start(m_processProgram, m_processArguments); } + void DownloadItemW::deleteConvertProcess() { if (m_convertProcess) { - disconnect(m_convertProcessConn[0]); - disconnect(m_convertProcessConn[1]); + disconnect(m_convertProcess, SIGNAL(finished(int)), this, SLOT(yourSlotForFinished(int))); + disconnect(m_convertProcess, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(yourSlotForError(QProcess::ProcessError))); m_convertProcess->close(); delete m_convertProcess; m_convertProcess = nullptr; } } +void DownloadItemW::handleConversionFinished(int exitCode, QProcess::ExitStatus) +{ + if (exitCode == 0) // Conversion succeeded + { + sizeL->setText(tr(g_downloadComplete)); + QFile::remove(filePath); + m_needsConversion = false; + filePath = m_convertedFilePath; + downloadStop(true); + } + else // Conversion failed + { + sizeL->setText(tr(g_conversionError)); + + // Manually log the program and arguments stored during the process start + qWarning() << "Failed to convert: Program - " << m_processProgram + << " Arguments - " << m_processArguments.join(" ") + << " Error - " << m_convertProcess->readAllStandardError().constData(); + + downloadStop(false); + } +} + +void DownloadItemW::handleConversionError(QProcess::ProcessError error) +{ + if (error == QProcess::FailedToStart) + { + sizeL->setText(tr(g_conversionError)); + downloadStop(false); + + // Manually log the program name stored earlier + qWarning() << "Failed to start process: Program - " << m_processProgram; + } +} + /**/ DownloaderThread::DownloaderThread(QDataStream *stream, const QString &url, DownloadListW *downloadLW, const QMenu *convertsMenu, const QString &name, const QString &prefix, const QString ¶m, const QString &preset) : @@ -843,7 +861,7 @@ void Downloader::init() connect(downloadLW, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(itemDoubleClicked(QTreeWidgetItem *))); m_convertsMenu = new QMenu(this); - connect(m_convertsMenu->addAction(tr("&Add")), &QAction::triggered, this, &Downloader::addConvertPreset); + connect(m_convertsMenu->addAction(tr("&Add")), SIGNAL(triggered()), this, SLOT(addConvertPreset())); m_convertsMenu->addSeparator(); setDownloadsDirB = new QToolButton; @@ -879,7 +897,7 @@ void Downloader::init() layout->addItem(new QSpacerItem(10, 0, QSizePolicy::Fixed, QSizePolicy::Minimum), 1, 4, 1, 1); layout->addWidget(m_convertsPresetsB, 1, 5, 1, 1); - QString defDownloadPath = QStandardPaths::standardLocations(QStandardPaths::DownloadLocation).value(0, QDir::homePath()) + "/"; + QString defDownloadPath = QDir::homePath() + "/"; #ifdef Q_OS_WIN defDownloadPath.replace('\\', '/'); #endif @@ -930,7 +948,7 @@ void Downloader::init() const auto createPreset = [this](const QString &name, const QString &data) { QAction *act = m_convertsMenu->addAction(name); act->setData(data); - connect(act, &QAction::triggered, this, &Downloader::editConvertAction); + connect(act, SIGNAL(triggered()), this, SLOT(editConvertAction())); return act; }; @@ -976,7 +994,7 @@ QVector Downloader::getActions(const QString &name, double, const QSt const auto createAction = [&](const QString &actionName, const QString &preset) { QAction *act = new QAction(actionName, nullptr); act->setIcon(QIcon(":/downloader.svgz")); - act->connect(act, &QAction::triggered, this, &Downloader::download); + act->connect(act, SIGNAL(triggered()), this, SLOT(download())); act->setProperty("name", name); if (!prefix.isEmpty()) { @@ -1009,15 +1027,17 @@ void Downloader::addConvertPreset() QAction *action = m_convertsMenu->addAction("MP3 224k"); action->setData(g_defaultMp3ConvertCommand); if (modifyConvertAction(action, false)) - connect(action, &QAction::triggered, this, &Downloader::editConvertAction); + connect(action, SIGNAL(triggered()), this, SLOT(editConvertAction())); else action->deleteLater(); } + void Downloader::editConvertAction() { if (QAction *action = qobject_cast(sender())) modifyConvertAction(action); } + bool Downloader::modifyConvertAction(QAction *action, bool addRemoveButton) { QDialog dialog(this); @@ -1026,83 +1046,60 @@ bool Downloader::modifyConvertAction(QAction *action, bool addRemoveButton) QLineEdit *nameE = new QLineEdit(action->text()); QLineEdit *commandE = new QLineEdit(action->data().toString()); - commandE->setToolTip(tr("Command line to execute after download.\n\n - specifies downloaded file.\n%f.mp3 - converted file will be input file with \"mp3\" extension.")); - QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); - connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + connect(buttons, SIGNAL(accepted()), &dialog, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), &dialog, SLOT(reject())); + + QPushButton *removeB = nullptr; if (addRemoveButton) { - QPushButton *removeB = buttons->addButton(tr("Remove"), QDialogButtonBox::DestructiveRole); + removeB = buttons->addButton(tr("Remove"), QDialogButtonBox::DestructiveRole); removeB->setIcon(QMPlay2Core.getIconFromTheme("list-remove")); - connect(buttons, &QDialogButtonBox::clicked, &dialog, [&](QAbstractButton *button) { - if (button == removeB) - { - action->deleteLater(); - dialog.reject(); - } - }); + + // Store the button, action, and dialog in member variables for later access + currentAction = action; + currentDialog = &dialog; + + // Connect the remove button to the slot + connect(removeB, SIGNAL(clicked()), this, SLOT(handleRemoveButtonClicked())); } QFormLayout *layout = new QFormLayout(&dialog); - layout->setMargin(4); - layout->setSpacing(4); layout->addRow(tr("Preset name"), nameE); layout->addRow(tr("Command line"), commandE); layout->addRow(buttons); - if (QWindow *win = window()->windowHandle()) + if (dialog.exec() == QDialog::Accepted) { - if (QScreen *screen = win->screen()) - dialog.resize(screen->availableGeometry().width() / 2, 1); + action->setText(nameE->text().simplified()); + action->setData(commandE->text().trimmed()); + return true; } - - while (dialog.exec() == QDialog::Accepted) + else if (removeB && dialog.result() == QDialogButtonBox::DestructiveRole) { - const QString name = nameE->text().simplified(); - const QString command = commandE->text(); - - if (name.isEmpty() || !command.contains(' ')) - { - QMessageBox::warning(this, dialog.windowTitle(), tr("Incorrect/empty name or command line!")); - continue; - } - - if (!command.contains("")) - { - QMessageBox::warning(this, dialog.windowTitle(), tr("Command must contain tag!")); - continue; - } - if (getCommandOutput(command).isEmpty()) - { - QMessageBox::warning(this, dialog.windowTitle(), tr("Command must contain correct file tag!")); - continue; - } - - const QList actions = m_convertsMenu->actions(); - bool ok = true; - for (int i = 1; i < actions.count(); ++i) // Skip first "Add" action - { - if (actions[i] != action && actions[i]->text().compare(name, Qt::CaseInsensitive) == 0) - { - ok = false; - break; - } - } - - if (!ok) - { - QMessageBox::warning(this, dialog.windowTitle(), tr("Preset name already exists!")); - continue; - } + action->deleteLater(); + return false; + } - action->setText(name); - action->setData(command.trimmed()); + return false; +} - return true; +void Downloader::handleButtonClicked(QAbstractButton *button) +{ + if (button == removeB && currentAction && currentDialog) // Ensure valid pointers + { + currentAction->deleteLater(); + currentDialog->reject(); } +} - return false; +void Downloader::handleRemoveButtonClicked() +{ + if (removeB && currentAction && currentDialog) // Ensure valid pointers + { + currentAction->deleteLater(); + currentDialog->reject(); + } } void Downloader::setDownloadsDir() @@ -1120,6 +1117,7 @@ void Downloader::setDownloadsDir() else if (dir.filePath() != QString()) QMessageBox::warning(this, DownloaderName, tr("Cannot change downloading files directory")); } + void Downloader::clearFinished() { const QList items = downloadLW->findItems(QString(), Qt::MatchContains); @@ -1127,6 +1125,7 @@ void Downloader::clearFinished() if (((DownloadItemW *)downloadLW->itemWidget(items[i], 0))->isFinished()) delete items[i]; } + void Downloader::addUrl() { QString clipboardUrl; @@ -1141,6 +1140,7 @@ void Downloader::addUrl() if (!url.isEmpty()) new DownloaderThread(nullptr, url, downloadLW, m_convertsMenu); } + void Downloader::download() { QAction *action = qobject_cast(sender()); @@ -1158,6 +1158,7 @@ void Downloader::download() ); downloadLW->setCurrentItem(downloadLW->invisibleRootItem()->child(0)); } + void Downloader::itemDoubleClicked(QTreeWidgetItem *item) { DownloadItemW *downloadItemW = (DownloadItemW *)downloadLW->itemWidget(item, 0); diff --git a/src/modules/Extensions/Downloader.hpp b/src/modules/Extensions/Downloader.hpp index a19787ac..c14ce2ac 100644 --- a/src/modules/Extensions/Downloader.hpp +++ b/src/modules/Extensions/Downloader.hpp @@ -21,9 +21,12 @@ #include #include +#include #include #include #include +#include +#include class QLabel; class QProcess; @@ -32,11 +35,11 @@ class QProgressBar; class QTreeWidgetItem; class DownloaderThread; -class DownloadItemW final : public QWidget +class DownloadItemW : public QWidget { Q_OBJECT public: - DownloadItemW(DownloaderThread *downloaderThr, QString name, const QIcon &icon, QDataStream *stream, QString preset); + explicit DownloadItemW(DownloaderThread *downloaderThr, QString name, const QIcon &icon, QDataStream *stream, QString preset); ~DownloadItemW(); void setName(const QString &); @@ -69,6 +72,8 @@ signals: void stop(); private slots: void toggleStartStop(); + void handleConversionFinished(int exitCode, QProcess::ExitStatus); + void handleConversionError(QProcess::ProcessError); private: void downloadStop(bool); @@ -89,8 +94,10 @@ private: QProgressBar *progressB; } *speedProgressW = nullptr; - QProcess *m_convertProcess = nullptr; - QMetaObject::Connection m_convertProcessConn[2]; + QString m_processProgram; // To store the program name + QStringList m_processArguments; // To store the process arguments + QProcess *m_convertProcess; // QProcess is now fully included + int m_convertProcessConn[2]; // Replace QMetaObject::Connection with int bool finished, readyToPlay, m_needsConversion = false; QString m_convertPreset; QString filePath; @@ -99,7 +106,7 @@ private: /**/ -class DownloadListW final : public QTreeWidget +class DownloadListW : public QTreeWidget { friend class Downloader; public: @@ -113,13 +120,13 @@ private: /**/ -class DownloaderThread final : public QThread +class DownloaderThread : public QThread { Q_OBJECT enum {ADD_ENTRY, NAME, SET, SET_POS, SET_SPEED, DOWNLOAD_ERROR, FINISH}; public: DownloaderThread(QDataStream *stream, const QString &url, DownloadListW *downloadLW, const QMenu *convertsMenu, const QString &name = QString(), const QString &prefix = QString(), const QString ¶m = QString(), const QString &preset = QString()); - ~DownloaderThread(); + ~DownloaderThread() final; void serialize(QDataStream &stream); @@ -131,7 +138,7 @@ private slots: void stop(); void finished(); private: - void run() override; + void run() override final; QIcon getIcon(); @@ -145,19 +152,19 @@ private: /**/ -class Downloader final : public QWidget, public QMPlay2Extensions +class Downloader : public QWidget, public QMPlay2Extensions { Q_OBJECT public: Downloader(Module &module); - ~Downloader(); + ~Downloader() final; - void init() override; + void init() override final; - DockWidget *getDockWidget() override; + DockWidget *getDockWidget() override final; - QVector getActions(const QString &, double, const QString &, const QString &, const QString &) override; + QVector getActions(const QString &, double, const QString &, const QString &, const QString &) override final; private: void addConvertPreset(); @@ -170,6 +177,8 @@ private slots: void addUrl(); void download(); void itemDoubleClicked(QTreeWidgetItem *); + void handleButtonClicked(QAbstractButton *button); + void handleRemoveButtonClicked(); private: Settings m_sets; @@ -182,6 +191,10 @@ private: QToolButton *m_convertsPresetsB; QMenu *m_convertsMenu; + + QAction *currentAction; // Pointer to the current action being modified + QDialog *currentDialog; // Pointer to the current dialog being used + QPushButton *removeB; }; #define DownloaderName "QMPlay2 Downloader" diff --git a/src/modules/Extensions/Extensions.cpp b/src/modules/Extensions/Extensions.cpp index e4f8f9be..e2b4a8d1 100644 --- a/src/modules/Extensions/Extensions.cpp +++ b/src/modules/Extensions/Extensions.cpp @@ -150,17 +150,31 @@ ModuleSettingsWidget::ModuleSettingsWidget(Module &module) : subtitlesB->setToolTip(tr("Displays subtitles from YouTube. Follows default subtitles language and QMPlay2 language.")); subtitlesB->setChecked(sets().getBool("YouTube/Subtitles")); + int idx; + + m_preferredCodec = new QComboBox; + m_preferredCodec->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred)); + m_preferredCodec->addItems({"VP9", "H.264", "AV1"}); // Must match "PreferredCodec" enum + idx = m_preferredCodec->findText(sets().getString("YouTube/PreferredCodec")); + m_preferredCodec->setCurrentIndex(idx > -1 ? idx : 0); + qualityPreset = new QComboBox; qualityPreset->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred)); qualityPreset->addItems(YouTube::getQualityPresets()); - int idx = qualityPreset->findText(sets().getString("YouTube/QualityPreset")); + idx = qualityPreset->findText(sets().getString("YouTube/QualityPreset")); qualityPreset->setCurrentIndex(idx > -1 ? idx : 3); + m_allowVp9Hdr = new QCheckBox(tr("Allow HDR content for VP9 codec")); + m_allowVp9Hdr->setChecked(sets().getBool("YouTube/AllowVp9HDR")); + layout = new QGridLayout(youTubeB); layout->addWidget(userNameB, 0, 0, 1, 2); layout->addWidget(subtitlesB, 1, 0, 1, 2); - layout->addWidget(new QLabel(tr("Preferred quality") + ": "), 2, 0, 1, 1); - layout->addWidget(qualityPreset, 2, 1, 1, 1); + layout->addWidget(new QLabel(tr("Preferred video codec") + ": "), 2, 0, 1, 1); + layout->addWidget(m_preferredCodec, 2, 1, 1, 1); + layout->addWidget(new QLabel(tr("Preferred quality") + ": "), 3, 0, 1, 1); + layout->addWidget(qualityPreset, 3, 1, 1, 1); + layout->addWidget(m_allowVp9Hdr, 4, 0, 1, 2); layout->setMargin(2); #ifdef USE_LASTFM @@ -233,6 +247,8 @@ void ModuleSettingsWidget::saveSettings() sets().set("YouTube/ShowUserName", userNameB->isChecked()); sets().set("YouTube/Subtitles", subtitlesB->isChecked()); sets().set("YouTube/QualityPreset", qualityPreset->currentText()); + sets().set("YouTube/PreferredCodec", m_preferredCodec->currentText()); + sets().set("YouTube/AllowVp9HDR", m_allowVp9Hdr->isChecked()); #ifdef USE_LASTFM sets().set("LastFM/DownloadCovers", downloadCoversGB->isChecked()); diff --git a/src/modules/Extensions/Extensions.hpp b/src/modules/Extensions/Extensions.hpp index c2a424e1..07d6c163 100644 --- a/src/modules/Extensions/Extensions.hpp +++ b/src/modules/Extensions/Extensions.hpp @@ -20,15 +20,15 @@ #include -class Extensions final : public Module +class Extensions : public Module { public: Extensions(); private: - QList getModulesInfo(const bool) const override; - void *createInstance(const QString &) override; + QList getModulesInfo(const bool) const override final; + void *createInstance(const QString &) override final; - SettingsWidget *getSettingsWidget() override; + SettingsWidget *getSettingsWidget() override final; QIcon downloader, youtube, radio; #ifdef USE_LASTFM @@ -45,7 +45,7 @@ class QGroupBox; class QCheckBox; class LineEdit; -class ModuleSettingsWidget final : public Module::SettingsWidget +class ModuleSettingsWidget : public Module::SettingsWidget { Q_OBJECT public: @@ -56,14 +56,15 @@ private slots: void passwordEdited(); #endif private: - void saveSettings() override; + void saveSettings() override final; #ifdef USE_MPRIS2 QCheckBox *MPRIS2B; #endif QCheckBox *userNameB, *subtitlesB; - QComboBox *qualityPreset; + QComboBox *m_preferredCodec, *qualityPreset; + QCheckBox *m_allowVp9Hdr; #ifdef USE_LASTFM QGroupBox *downloadCoversGB; diff --git a/src/modules/Extensions/LastFM.cpp b/src/modules/Extensions/LastFM.cpp index 99af0d25..e765dd9a 100644 --- a/src/modules/Extensions/LastFM.cpp +++ b/src/modules/Extensions/LastFM.cpp @@ -29,9 +29,20 @@ Q_DECLARE_METATYPE(LastFM::Scrobble) #include #include -#include +#include #include +static QString buildQueryString(const QList > ¶ms) +{ + QStringList result; + for (int i = 0; i < params.size(); ++i) + { + const QPair &pair = params.at(i); + result << QUrl::toPercentEncoding(pair.first) + "=" + QUrl::toPercentEncoding(pair.second); + } + return result.join("&"); +} + LastFM::LastFM(Module &module) : coverReply(nullptr), loginReply(nullptr), @@ -53,7 +64,10 @@ bool LastFM::set() imageSizes.clear(); if (sets().getBool("LastFM/AllowBigCovers")) imageSizes += "mega"; - imageSizes += {"extralarge", "large", "medium", "small"}; + imageSizes += "extralarge"; + imageSizes += "large"; + imageSizes += "medium"; + imageSizes += "small"; const QString _user = sets().getString("LastFM/Login"); const QString _md5pass = sets().getString("LastFM/Password"); @@ -161,19 +175,20 @@ void LastFM::updateNowPlayingAndScrobble(const Scrobble &scrobble) QCryptographicHash::Md5 ).toHex(); - QUrlQuery updateNowPlayingQuery; - updateNowPlayingQuery.addQueryItem("method", "track.updatenowplaying"); - updateNowPlayingQuery.addQueryItem("artist", scrobble.artist); - updateNowPlayingQuery.addQueryItem("track", scrobble.title); - updateNowPlayingQuery.addQueryItem("album", scrobble.album.isEmpty() ? "" : scrobble.album); - updateNowPlayingQuery.addQueryItem("duration", duration); - updateNowPlayingQuery.addQueryItem("api_key", api_key); - updateNowPlayingQuery.addQueryItem("api_sig", apiSig); - updateNowPlayingQuery.addQueryItem("sk", session_key); + QList > updateNowPlayingParams; + updateNowPlayingParams << qMakePair(QString("method"), QString("track.updatenowplaying")); + updateNowPlayingParams << qMakePair(QString("artist"), scrobble.artist); + updateNowPlayingParams << qMakePair(QString("track"), scrobble.title); + updateNowPlayingParams << qMakePair(QString("album"), scrobble.album.isEmpty() ? "" : scrobble.album); + updateNowPlayingParams << qMakePair(QString("duration"), duration); + updateNowPlayingParams << qMakePair(QString("api_key"), QString(api_key)); + updateNowPlayingParams << qMakePair(QString("api_sig"), QString(apiSig)); + updateNowPlayingParams << qMakePair(QString("sk"), session_key); + + QString updateNowPlayingQueryString = buildQueryString(updateNowPlayingParams); - reply = net.start(audioScrobbler2URL, updateNowPlayingQuery.toString(QUrl::EncodeDelimiters).toUtf8(), NetworkAccess::UrlEncoded); - connect(reply, &NetworkReply::finished, - reply, &NetworkReply::deleteLater); + reply = net.start(audioScrobbler2URL, updateNowPlayingQueryString.toUtf8(), NetworkAccess::UrlEncoded); + connect(reply, SIGNAL(finished()), reply, SLOT(deleteLater())); // scrobble const auto ts = QString::number(scrobble.startTime); @@ -185,25 +200,30 @@ void LastFM::updateNowPlayingAndScrobble(const Scrobble &scrobble) QCryptographicHash::Md5 ).toHex(); - QUrlQuery scrobbleQuery; - scrobbleQuery.addQueryItem("method", "track.scrobble"); - scrobbleQuery.addQueryItem("artist", scrobble.artist); - scrobbleQuery.addQueryItem("track", scrobble.title); - scrobbleQuery.addQueryItem("timestamp", ts); - scrobbleQuery.addQueryItem("album", scrobble.album.isEmpty() ? "" : scrobble.album); - scrobbleQuery.addQueryItem("api_key", api_key); - scrobbleQuery.addQueryItem("api_sig", apiSig); - scrobbleQuery.addQueryItem("sk", session_key); - - reply = net.start(audioScrobbler2URL, scrobbleQuery.toString(QUrl::EncodeDelimiters).toUtf8(), NetworkAccess::UrlEncoded); + QList > scrobbleParams; + scrobbleParams << qMakePair(QString("method"), QString("track.scrobble")); + scrobbleParams << qMakePair(QString("artist"), scrobble.artist); + scrobbleParams << qMakePair(QString("track"), scrobble.title); + scrobbleParams << qMakePair(QString("timestamp"), ts); + scrobbleParams << qMakePair(QString("album"), scrobble.album.isEmpty() ? "" : scrobble.album); + scrobbleParams << qMakePair(QString("api_key"), QString(api_key)); + scrobbleParams << qMakePair(QString("api_sig"), QString(apiSig)); + scrobbleParams << qMakePair(QString("sk"), session_key); + + QString scrobbleQueryString = buildQueryString(scrobbleParams); + + reply = net.start(audioScrobbler2URL, scrobbleQueryString.toUtf8(), NetworkAccess::UrlEncoded); reply->setProperty("scrobble", QVariant::fromValue(scrobble)); m_scrobbleReplies.push_back(reply); - connect(reply, &NetworkReply::destroyed, - this, [=] { + connect(reply, SIGNAL(destroyed(QObject*)), this, SLOT(onReplyDestroyed(QObject*))); + connect(reply, SIGNAL(finished()), this, SLOT(scrobbleFinished())); +} + +void LastFM::onReplyDestroyed(QObject* obj) +{ + NetworkReply* reply = qobject_cast(obj); + if (reply) m_scrobbleReplies.removeOne(reply); - }); - connect(reply, &NetworkReply::finished, - this, &LastFM::scrobbleFinished); } void LastFM::clear() @@ -274,7 +294,7 @@ void LastFM::albumFinished() emit QMPlay2Core.updateCover(taa[0], taa[1], taa[2], reply); else { - for (const QString &size : asConst(imageSizes)) + foreach (const QString &size, imageSizes) { int idx = reply.indexOf(size); if (idx > -1) @@ -316,6 +336,7 @@ void LastFM::albumFinished() coverReply->deleteLater(); coverReply = nullptr; } + void LastFM::loginFinished() { if (loginReply->hasError()) @@ -351,6 +372,7 @@ void LastFM::loginFinished() loginReply->deleteLater(); loginReply = nullptr; } + void LastFM::scrobbleFinished() { const auto reply = qobject_cast(sender()); diff --git a/src/modules/Extensions/LastFM.hpp b/src/modules/Extensions/LastFM.hpp index 3d2b3e73..40b87d48 100644 --- a/src/modules/Extensions/LastFM.hpp +++ b/src/modules/Extensions/LastFM.hpp @@ -29,9 +29,10 @@ class QImage; -class LastFM final : public QObject, public QMPlay2Extensions +class LastFM : public QObject, public QMPlay2Extensions { Q_OBJECT + public: class Scrobble { @@ -48,8 +49,9 @@ public: }; LastFM(Module &module); + private: - bool set() override; + bool set() override final; void getAlbumCover(const QString &title, const QString &artist, const QString &album, bool titleAsAlbum = false); @@ -59,6 +61,7 @@ private: void updateNowPlayingAndScrobble(const Scrobble &scrobble); void clear(); + private slots: void updatePlaying(bool play, const QString &title, const QString &artist, const QString &album, int length, bool needCover, const QString &fileName); @@ -67,6 +70,9 @@ private slots: void scrobbleFinished(); void processScrobbleQueue(); + + void onReplyDestroyed(QObject* obj); + private: NetworkReply *coverReply, *loginReply; QList m_scrobbleReplies; diff --git a/src/modules/Extensions/Lyrics.cpp b/src/modules/Extensions/Lyrics.cpp index 87c1fc18..560d4ea1 100644 --- a/src/modules/Extensions/Lyrics.cpp +++ b/src/modules/Extensions/Lyrics.cpp @@ -56,8 +56,10 @@ Lyrics::Lyrics(Module &module) : { SetModule(module); - connect(&QMPlay2Core, &QMPlay2CoreClass::updatePlaying, - this, &Lyrics::updatePlaying); + connect(&QMPlay2Core, + SIGNAL(updatePlaying(bool, const QString &, const QString &, const QString &, int, bool, const QString &, const QString &)), + this, + SLOT(updatePlaying(bool, const QString &, const QString &, const QString &, int, bool, const QString &, const QString &))); connect(&m_net, SIGNAL(finished(NetworkReply *)), this, SLOT(finished(NetworkReply *))); m_dW = new DockWidget; diff --git a/src/modules/Extensions/Lyrics.hpp b/src/modules/Extensions/Lyrics.hpp index d4f954a3..572571cc 100644 --- a/src/modules/Extensions/Lyrics.hpp +++ b/src/modules/Extensions/Lyrics.hpp @@ -25,15 +25,15 @@ #include #include -class Lyrics final : public QTextEdit, public QMPlay2Extensions +class Lyrics : public QTextEdit, public QMPlay2Extensions { Q_OBJECT public: Lyrics(Module &module); - ~Lyrics(); + ~Lyrics() final; - DockWidget *getDockWidget() override; + DockWidget *getDockWidget() override final; private slots: void visibilityChanged(bool v); diff --git a/src/modules/Extensions/Radio.cpp b/src/modules/Extensions/Radio.cpp index c0310b5c..92348b29 100644 --- a/src/modules/Extensions/Radio.cpp +++ b/src/modules/Extensions/Radio.cpp @@ -24,19 +24,20 @@ #include #include -#include #include #include #include -#include #include -#include #include #include #include #include #include +#include +#include +#include + constexpr const char *g_fileDialogFilter = "QMPlay2 radio station list (*.qmplay2radio)"; Radio::Radio(Module &module) : @@ -84,8 +85,8 @@ Radio::Radio(Module &module) : ui->radioView->setIconSize({m_radioBrowserModel->elementHeight(), m_radioBrowserModel->elementHeight()}); QHeaderView *header = ui->radioView->header(); - header->setSectionResizeMode(0, QHeaderView::Stretch); - header->setSectionResizeMode(4, QHeaderView::ResizeToContents); + header->setResizeMode(0, QHeaderView::Stretch); + header->setResizeMode(4, QHeaderView::ResizeToContents); connect(m_radioBrowserMenu->addAction(tr("Play")), SIGNAL(triggered(bool)), this, SLOT(radioBrowserPlay())); connect(m_radioBrowserMenu->addAction(tr("Enqueue")), SIGNAL(triggered(bool)), this, SLOT(radioBrowserEnqueue())); @@ -109,9 +110,7 @@ Radio::Radio(Module &module) : connect(m_net, SIGNAL(finished(NetworkReply *)), this, SLOT(replyFinished(NetworkReply *))); m_tabChangedOnVisibilityTimer->setSingleShot(true); - connect(m_tabChangedOnVisibilityTimer, &QTimer::timeout, this, [=] { - tabChanged(currentIndex()); - }); + connect(m_tabChangedOnVisibilityTimer, SIGNAL(timeout()), this, SLOT(triggerTabChanged())); } Radio::~Radio() { @@ -221,6 +220,7 @@ void Radio::searchData() ui->radioView->setEnabled(false); ui->filterEdit->clear(); } + void Radio::searchFinished() { ui->radioView->setEnabled(true); @@ -285,6 +285,7 @@ void Radio::on_addMyRadioStationButton_clicked() addMyRadioStation(name, address); } } + void Radio::on_editMyRadioStationButton_clicked() { if (QListWidgetItem *item = ui->myRadioListWidget->currentItem()) @@ -300,10 +301,12 @@ void Radio::on_editMyRadioStationButton_clicked() } } } + void Radio::on_removeMyRadioStationButton_clicked() { delete ui->myRadioListWidget->currentItem(); } + void Radio::on_loadMyRadioStationButton_clicked() { const QString filePath = QFileDialog::getOpenFileName(this, tr("Load radio station list"), QString(), g_fileDialogFilter); @@ -312,6 +315,7 @@ void Radio::on_loadMyRadioStationButton_clicked() loadMyRadios(QSettings(filePath, QSettings::IniFormat).value("Radia").toStringList()); } } + void Radio::on_saveMyRadioStationButton_clicked() { QString filePath = QFileDialog::getSaveFileName(this, tr("Save radio station list"), QString(), g_fileDialogFilter); @@ -327,6 +331,7 @@ void Radio::on_myRadioListWidget_itemDoubleClicked(QListWidgetItem *item) { firstTabItemDoubleClicked(item); } + void Radio::on_qmplay2RadioListWidget_itemDoubleClicked(QListWidgetItem *item) { firstTabItemDoubleClicked(item); @@ -370,14 +375,17 @@ void Radio::on_searchByComboBox_activated(int idx) ui->searchComboBox->setInsertPolicy(QComboBox::InsertAtBottom); } } + void Radio::on_addRadioBrowserStationButton_clicked() { QDesktopServices::openUrl(QUrl("http://www.radio-browser.info/gui/#/add")); } + void Radio::on_radioView_doubleClicked(const QModelIndex &index) { radioBrowserPlayOrEnqueue(index, "open"); } + void Radio::on_radioView_customContextMenuRequested(const QPoint &pos) { if (ui->radioView->currentIndex().isValid()) @@ -390,6 +398,7 @@ void Radio::radioBrowserPlay() if (index.isValid()) radioBrowserPlayOrEnqueue(index, "open"); } + void Radio::radioBrowserAdd() { const QModelIndex index = ui->radioView->currentIndex(); @@ -400,18 +409,21 @@ void Radio::radioBrowserAdd() addMyRadioStation(title, url); } } + void Radio::radioBrowserEnqueue() { const QModelIndex index = ui->radioView->currentIndex(); if (index.isValid()) radioBrowserPlayOrEnqueue(index, "enqueue"); } + void Radio::radioBrowserOpenHomePage() { const QModelIndex index = ui->radioView->currentIndex(); if (index.isValid()) QDesktopServices::openUrl(m_radioBrowserModel->getHomePageUrl(index)); } + void Radio::radioBrowserEdit() { const QModelIndex index = ui->radioView->currentIndex(); @@ -495,6 +507,7 @@ QStringList Radio::getMyRadios() const myRadios += item->text() + '\n' + item->data(Qt::UserRole).toString(); return myRadios; } + void Radio::loadMyRadios(const QStringList &radios) { ui->myRadioListWidget->clear(); @@ -539,3 +552,8 @@ bool Radio::eventFilter(QObject *watched, QEvent *event) } return QTabWidget::eventFilter(watched, event); } + +void Radio::triggerTabChanged() +{ + tabChanged(currentIndex()); +} diff --git a/src/modules/Extensions/Radio.hpp b/src/modules/Extensions/Radio.hpp index 1bf167a5..a5e95949 100644 --- a/src/modules/Extensions/Radio.hpp +++ b/src/modules/Extensions/Radio.hpp @@ -24,6 +24,7 @@ #include #include #include +#include namespace Ui { class Radio; @@ -36,21 +37,23 @@ class NetworkReply; class QTimer; class QMenu; -class Radio final : public QTabWidget, public QMPlay2Extensions +class Radio : public QTabWidget, public QMPlay2Extensions { Q_OBJECT public: Radio(Module &); - ~Radio(); + ~Radio() final; - DockWidget *getDockWidget() override; + DockWidget *getDockWidget() override final; private slots: void visibilityChanged(const bool v); void tabChanged(int index); + void triggerTabChanged(); + void qmplay2RadioStationsFinished(); void searchData(); @@ -95,7 +98,7 @@ private: void loadMyRadios(const QStringList &radios); private: - bool eventFilter(QObject *watched, QEvent *event) override; + bool eventFilter(QObject *watched, QEvent *event) override final; private: const QString m_newStationTxt; diff --git a/src/modules/Extensions/Radio/RadioBrowserModel.cpp b/src/modules/Extensions/Radio/RadioBrowserModel.cpp index 395ed835..22641a2e 100644 --- a/src/modules/Extensions/Radio/RadioBrowserModel.cpp +++ b/src/modules/Extensions/Radio/RadioBrowserModel.cpp @@ -22,13 +22,14 @@ #include #include -#include -#include -#include #include #include #include +#include +#include +#include + #include struct Column @@ -394,10 +395,9 @@ void RadioBrowserModel::replyFinished(NetworkReply *reply) if (!image.isNull()) { const int s = elementHeight(); - const qreal dpr = m_widget->devicePixelRatioF(); + const qreal dpr = 1.0; column->icon = QPixmap(s * dpr, s * dpr); - column->icon.setDevicePixelRatio(dpr); column->icon.fill(Qt::transparent); QPainter painter(&column->icon); diff --git a/src/modules/Extensions/Radio/RadioBrowserModel.hpp b/src/modules/Extensions/Radio/RadioBrowserModel.hpp index 9ecd5270..42586e28 100644 --- a/src/modules/Extensions/Radio/RadioBrowserModel.hpp +++ b/src/modules/Extensions/Radio/RadioBrowserModel.hpp @@ -30,7 +30,7 @@ class NetworkAccess; class NetworkReply; struct Column; -class RadioBrowserModel final : public QAbstractItemModel +class RadioBrowserModel : public QAbstractItemModel { Q_OBJECT @@ -49,14 +49,14 @@ public: QUrl getEditUrl(const QModelIndex &index) const; QUrl getHomePageUrl(const QModelIndex &index) const; - QModelIndex index(int row, int column, const QModelIndex &parent) const override; - QModelIndex parent(const QModelIndex &child) const override; - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QVariant data(const QModelIndex &index, int role) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - void sort(int columnIdx, Qt::SortOrder order = Qt::AscendingOrder) override; + QModelIndex index(int row, int column, const QModelIndex &parent) const override final; + QModelIndex parent(const QModelIndex &child) const override final; + int rowCount(const QModelIndex &parent) const override final; + int columnCount(const QModelIndex &parent) const override final; + QVariant data(const QModelIndex &index, int role) const override final; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override final; + Qt::ItemFlags flags(const QModelIndex &index) const override final; + void sort(int columnIdx, Qt::SortOrder order = Qt::AscendingOrder) override final; public slots: void setFiltrText(const QString &text = QString()); diff --git a/src/modules/Extensions/YouTube.cpp b/src/modules/Extensions/YouTube.cpp index 4d7b794f..d168f9bb 100644 --- a/src/modules/Extensions/YouTube.cpp +++ b/src/modules/Extensions/YouTube.cpp @@ -21,19 +21,14 @@ #include #include -#include #include #include -#include #include -#include #include #include -#include #include #include #include -#include #include #include #include @@ -41,8 +36,12 @@ #include #include #include +#include +#include -Q_LOGGING_CATEGORY(youtube, "Extensions/YouTube") +#include +#include +#include #define YOUTUBE_URL "https://www.youtube.com" @@ -53,7 +52,7 @@ static inline QString toPercentEncoding(const QString &txt) static inline QString getYtUrl(const QString &title, const int page, const int sortByIdx) { - static constexpr const char *sortBy[4] { + static const char *sortBy[4] { "", // Relevance ("&sp=CAA%253D") "&sp=CAI%253D", // Upload date "&sp=CAM%253D", // View count @@ -89,8 +88,8 @@ ResultsYoutube::ResultsYoutube() headerItem()->setText(2, tr("User")); header()->setStretchLastSection(false); - header()->setSectionResizeMode(0, QHeaderView::Stretch); - header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + header()->setResizeMode(0, QHeaderView::Stretch); + header()->setResizeMode(1, QHeaderView::ResizeToContents); connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(playEntry(QTreeWidgetItem *))); connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(contextMenu(const QPoint &))); @@ -126,6 +125,24 @@ void ResultsYoutube::playEntry(QTreeWidgetItem *tWI) playOrEnqueue("open", tWI); } +void ResultsYoutube::playCurrentItem() +{ + QTreeWidgetItem *tWI = currentItem(); + if (!tWI) + return; + + playOrEnqueue("open", tWI, currentParam); +} + +void ResultsYoutube::enqueueCurrentItem() +{ + QTreeWidgetItem *tWI = currentItem(); + if (!tWI) + return; + + playOrEnqueue("enqueue", tWI, currentParam); +} + void ResultsYoutube::openPage() { QTreeWidgetItem *tWI = currentItem(); @@ -155,17 +172,19 @@ void ResultsYoutube::contextMenu(const QPoint &point) for (int i = 0; i < 2; ++i) { - menu->addSection(i == 0 ? tr("Audio and video") : tr("Audio only")); + QAction *section = new QAction(i == 0 ? tr("Audio and video") : tr("Audio only"), menu); + section->setEnabled(false); // Make it non-interactive + menu->addAction(section); if (!tWI->isDisabled()) { - const auto param = i == 0 ? QString() : QString("audio"); - menu->addAction(tr("Play"), this, [=] { - playOrEnqueue("open", currentItem(), param); - }); - menu->addAction(tr("Enqueue"), this, [=] { - playOrEnqueue("enqueue", currentItem(), param); - }); + currentParam = (i == 0 ? QString() : QString("audio")); + QAction *playAction = menu->addAction(tr("Play")); + QAction *enqueueAction = menu->addAction(tr("Enqueue")); + + connect(playAction, SIGNAL(triggered()), this, SLOT(playCurrentItem())); + connect(enqueueAction, SIGNAL(triggered()), this, SLOT(enqueueCurrentItem())); + menu->addSeparator(); } @@ -239,6 +258,18 @@ const QStringList YouTube::getQualityPresets() }; } +void YouTube::onQualityPresetChanged(const QString &preset) { + sets().set("YouTube/QualityPreset", preset); +} + +void YouTube::onQualityToggled() { + QAction *act = qobject_cast(sender()); + if (act && act->isChecked()) { + int qualityIdx = m_qualityGroup->actions().indexOf(act); + setItags(qualityIdx); + } +} + YouTube::YouTube(Module &module) : completer(new QCompleter(new QStringListModel(this), this)), currPage(1), @@ -270,9 +301,8 @@ YouTube::YouTube(Module &module) : searchB->setAutoRaise(true); QToolButton *showSettingsB = new QToolButton; - connect(showSettingsB, &QToolButton::clicked, this, [] { - emit QMPlay2Core.showSettings("Extensions"); - }); + connect(showSettingsB, SIGNAL(clicked()), this, SLOT(onShowSettingsClicked())); + showSettingsB->setIcon(QMPlay2Core.getIconFromTheme("configure")); showSettingsB->setToolTip(tr("Settings")); showSettingsB->setAutoRaise(true); @@ -283,20 +313,23 @@ YouTube::YouTube(Module &module) : QMenu *qualityMenu = new QMenu(this); int qualityIdx = 0; - for (QAction *act : m_qualityGroup->actions()) - { - connect(act, &QAction::triggered, this, [=] { - sets().set("YouTube/QualityPreset", act->text()); - }); - connect(act, &QAction::toggled, this, [=](bool checked) { - if (checked) - setItags(qualityIdx); - }); + QSignalMapper *qualitySignalMapper = new QSignalMapper(this); + + for (int i = 0; i < m_qualityGroup->actions().size(); ++i) { + QAction *act = m_qualityGroup->actions().at(i); + + qualitySignalMapper->setMapping(act, act->text()); + connect(act, SIGNAL(triggered()), qualitySignalMapper, SLOT(map())); + connect(act, SIGNAL(toggled(bool)), this, SLOT(onQualityToggled())); + act->setCheckable(true); qualityMenu->addAction(act); ++qualityIdx; } - qualityMenu->insertSeparator(qualityMenu->actions().at(5)); + connect(qualitySignalMapper, SIGNAL(mapped(QString)), this, SLOT(onQualityPresetChanged(QString))); + if (qualityMenu->actions().size() > 5) { + qualityMenu->insertSeparator(qualityMenu->actions().at(5)); + } QToolButton *qualityB = new QToolButton; qualityB->setPopupMode(QToolButton::InstantPopup); @@ -312,21 +345,18 @@ YouTube::YouTube(Module &module) : m_sortByGroup->addAction(tr("Rating")); QMenu *sortByMenu = new QMenu(this); - int sortByIdx = 0; - for (QAction *act : m_sortByGroup->actions()) - { - connect(act, &QAction::triggered, this, [=] { - if (m_sortByIdx != sortByIdx) - { - m_sortByIdx = sortByIdx; - sets().set("YouTube/SortBy", m_sortByIdx); - search(); - } - }); + QSignalMapper *sortBySignalMapper = new QSignalMapper(this); + + for (int i = 0; i < m_sortByGroup->actions().size(); ++i) { + QAction *act = m_sortByGroup->actions().at(i); + + sortBySignalMapper->setMapping(act, i); + connect(act, SIGNAL(triggered()), sortBySignalMapper, SLOT(map())); + act->setCheckable(true); sortByMenu->addAction(act); - ++sortByIdx; } + connect(sortBySignalMapper, SIGNAL(mapped(int)), this, SLOT(onSortByChanged(int))); QToolButton *sortByB = new QToolButton; sortByB->setPopupMode(QToolButton::InstantPopup); @@ -371,6 +401,23 @@ YouTube::~YouTube() bool YouTube::set() { + const auto preferredCodec = sets().getString("YouTube/PreferredCodec"); + const auto oldPpreferredCodec = m_preferredCodec; + const auto oldAllowVp9Hdr = m_allowVp9Hdr; + + if (preferredCodec == "H.264") + m_preferredCodec = PreferredCodec::H264; + else if (preferredCodec == "AV1") + m_preferredCodec = PreferredCodec::AV1; + else + m_preferredCodec = PreferredCodec::VP9; + + m_allowVp9Hdr = sets().getBool("YouTube/AllowVp9HDR"); + + auto forceToggledSignal = [&] { + return (oldPpreferredCodec != m_preferredCodec || oldAllowVp9Hdr != m_allowVp9Hdr); + }; + const auto qualityActions = m_qualityGroup->actions(); const auto qualityText = sets().getString("YouTube/QualityPreset"); bool qualityActionChecked = false; @@ -380,6 +427,8 @@ bool YouTube::set() { if (qualityAction->text() == qualityText) { + if (forceToggledSignal() && qualityAction->isChecked()) + qualityAction->setChecked(false); // Force "toggled" signal qualityAction->setChecked(true); qualityActionChecked = true; break; @@ -387,7 +436,11 @@ bool YouTube::set() } } if (!qualityActionChecked) + { + if (forceToggledSignal() && qualityActions[3]->isChecked()) + qualityActions[3]->setChecked(false); // Force "toggled" signal qualityActions[3]->setChecked(true); + } resultsW->setColumnCount(sets().getBool("YouTube/ShowUserName") ? 3 : 2); m_allowSubtitles = sets().getBool("YouTube/Subtitles"); @@ -597,118 +650,191 @@ void YouTube::searchMenu() void YouTube::setItags(int qualityIdx) { -#if 0 // Itag info (incomplete) - Video + Audio: - 43 = 360p WebM (VP8 + Vorbis 128kbps) - 36 = 180p MP4 (MPEG4 + AAC 32kbps) - 22 = 720p MP4 (H.264 + AAC 192kbps) - 18 = 360p MP4 (H.264 + AAC 96kbps) - 5 = 240p FLV (FLV + MP3 64kbps) - - Video only: - 247 = Video 720p (VP9) - 248 = Video 1080p (VP9) - 271 = Video 1440p (VP9) - 313 = Video 2160p (VP9) - 272 = Video 4320p/2160p (VP9) - - 302 = Video 720p 60fps (VP9) - 303 = Video 1080p 60fps (VP9) - 308 = Video 1440p 60fps (VP9) - 315 = Video 2160p 60fps (VP9) - - 298 = Video 720p 60fps (H.264) - 299 = Video 1080p 60fps (H.264) - - 135 = Video 480p (H.264) - 136 = Video 720p (H.264) - 137 = Video 1080p (H.264) - 264 = Video 1440p (H.264) - 266 = Video 2160p (H.264) - - 170 = Video 480p (VP8) - 168 = Video 720p (VP8) - 170 = Video 1080p (VP8) - - Audio only: - 139 = Audio (AAC 48kbps) - 140 = Audio (AAC 128kbps) - 141 = Audio (AAC 256kbps) //? - - 171 = Audio (Vorbis 128kbps) - 172 = Audio (Vorbis 256kbps) //? - - 249 = Audio (Opus 50kbps) - 250 = Audio (Opus 70kbps) - 251 = Audio (Opus 160kbps) -#endif + // Itag info: https://gist.github.com/AgentOak/34d47c65b1d28829bb17c24c04a0096f + enum + { + // Video + H264_144p = 160, + H264_240p = 133, + H264_360p = 134, + H264_480p = 135, + H264_720p = 136, + H264_1080p = 137, + H264_1440p = 264, + H264_2160p = 266, + + H264_720p60 = 298, + H264_1080p60 = 299, + H264_1440p60 = 304, + H264_2160p60 = 305, + + VP9_144p = 278, + VP9_240p = 242, + VP9_360p = 243, + VP9_480p = 244, + VP9_720p = 247, + VP9_1080p = 248, + VP9_1440p = 271, + VP9_2160p = 313, + + VP9_720p60 = 302, + VP9_1080p60 = 303, + VP9_1440p60 = 308, + VP9_2160p60 = 315, + VP9_4320p60 = 272, + + VP9_720p60_HDR = 334, + VP9_1080p60_HDR = 335, + VP9_1440p60_HDR = 336, + VP9_2160p60_HDR = 337, + + AV1_480p = 397, + AV1_360p = 396, + AV1_240p = 395, + AV1_144p = 394, + + AV1_HFR_4320p_1 = 571, + AV1_HFR_4320p_2 = 402, + AV1_HFR_2160p = 401, + AV1_HFR_1440p = 400, + AV1_HFR_1080p = 399, + AV1_HFR_720p = 398, + + AV1_HFR_HIGH_2160p = 701, + AV1_HFR_HIGH_1440p = 700, + AV1_HFR_HIGH_1080p = 699, + AV1_HFR_HIGH_720p = 698, + + // Live video + H264_144p_AAC_48 = 91, + H264_240p_AAC_48 = 92, + H264_360p_AAC_128 = 93, + H264_480p_AAC_128 = 94, + H264_720p_AAC_256 = 95, + H264_1080p_AAC_256 = 96, + H264_720p60_AAC_128 = 300, + H264_1080p60_AAC_128 = 301, + + // Audio + Opus_160 = 251, + AAC_128 = 140, + + // Video + Audio + H264_360P_AAC_128 = 18, + H264_720P_AAC_128 = 22, + }; enum { - _4320p60, - _2160p60, - _1440p60, - _1080p60, - _720p60, - _2160p, - _1440p, - _1080p, - _720p, - _480p, - QualityPresetsCount, + Preset_4320p60, + Preset_2160p60, + Preset_1440p60, + Preset_1080p60, + Preset_720p60, + Preset_2160p, + Preset_1440p, + Preset_1080p, + Preset_720p, + Preset_480p, + + PresetCount, }; - QList qualityPresets[QualityPresetsCount]; + QVector qualityPresets[PresetCount]; { - qualityPresets[_720p60] << 298 << 302; - qualityPresets[_1080p60] << 299 << 303 << qualityPresets[_720p60]; - qualityPresets[_1440p60] << 308 << qualityPresets[_1080p60]; - qualityPresets[_2160p60] << 315 << qualityPresets[_1440p60]; - qualityPresets[_4320p60] << 272 << qualityPresets[_2160p60]; - - qualityPresets[_480p] << 135 << 134 << 133; - qualityPresets[_720p] << 136 << 247 << qualityPresets[_480p]; - qualityPresets[_1080p] << 137 << 248 << qualityPresets[_720p]; - qualityPresets[_1440p] << 264 << 271 << qualityPresets[_1080p]; - qualityPresets[_2160p] << 266 << 313 << qualityPresets[_1440p]; + auto maybeAppendVp9Hdr = [this, &qualityPresets] { + if (m_allowVp9Hdr) + { + qualityPresets[Preset_720p60] << VP9_720p60_HDR; + qualityPresets[Preset_1080p60] << VP9_1080p60_HDR; + qualityPresets[Preset_1440p60] << VP9_1440p60_HDR; + qualityPresets[Preset_2160p60] << VP9_2160p60_HDR; + } + }; + if (m_preferredCodec == PreferredCodec::VP9) + { + qualityPresets[Preset_480p] << VP9_480p << H264_480p << VP9_360p << H264_360p << H264_360P_AAC_128 << VP9_240p << H264_240p << VP9_144p << H264_144p; + qualityPresets[Preset_720p] << VP9_720p << H264_720p << H264_720P_AAC_128 << qualityPresets[Preset_480p]; + qualityPresets[Preset_1080p] << VP9_1080p << H264_1080p << qualityPresets[Preset_720p]; + qualityPresets[Preset_1440p] << VP9_1440p << H264_1440p << qualityPresets[Preset_1080p]; + qualityPresets[Preset_2160p] << VP9_2160p << H264_2160p << qualityPresets[Preset_1440p]; + + maybeAppendVp9Hdr(); + qualityPresets[Preset_720p60] << VP9_720p60 << H264_720p60; + qualityPresets[Preset_1080p60] << VP9_1080p60 << H264_1080p60 << qualityPresets[Preset_720p60]; + qualityPresets[Preset_1440p60] << VP9_1440p60 << H264_1440p60 << qualityPresets[Preset_1080p60]; + qualityPresets[Preset_2160p60] << VP9_2160p60 << H264_2160p60 << qualityPresets[Preset_1440p60]; + qualityPresets[Preset_4320p60] << VP9_4320p60 << qualityPresets[Preset_2160p60]; + } + else if (m_preferredCodec == PreferredCodec::H264) + { + qualityPresets[Preset_480p] << H264_480p << VP9_480p << H264_360p << VP9_360p << H264_360P_AAC_128 << H264_240p << VP9_240p << H264_144p << VP9_144p; + qualityPresets[Preset_720p] << H264_720p << VP9_720p << H264_720P_AAC_128 << qualityPresets[Preset_480p]; + qualityPresets[Preset_1080p] << H264_1080p << VP9_1080p << qualityPresets[Preset_720p]; + qualityPresets[Preset_1440p] << H264_1440p << VP9_1440p << qualityPresets[Preset_1080p]; + qualityPresets[Preset_2160p] << H264_2160p << VP9_2160p << qualityPresets[Preset_1440p]; + + qualityPresets[Preset_720p60] << H264_720p60 << VP9_720p60; + qualityPresets[Preset_1080p60] << H264_1080p60 << VP9_1080p60 << qualityPresets[Preset_720p60]; + qualityPresets[Preset_1440p60] << H264_1440p60 << VP9_1440p60 << qualityPresets[Preset_1080p60]; + qualityPresets[Preset_2160p60] << H264_2160p60 << VP9_2160p60 << qualityPresets[Preset_1440p60]; + qualityPresets[Preset_4320p60] << VP9_4320p60 << qualityPresets[Preset_2160p60]; + } + else if (m_preferredCodec == PreferredCodec::AV1) + { + qualityPresets[Preset_480p] << AV1_480p << AV1_360p << VP9_480p << H264_480p << VP9_360p << H264_360p << H264_360P_AAC_128 << AV1_240p << VP9_240p << H264_240p << AV1_144p << VP9_144p << H264_144p; + qualityPresets[Preset_720p] << VP9_720p << H264_720p << H264_720P_AAC_128 << qualityPresets[Preset_480p]; + qualityPresets[Preset_1080p] << VP9_1080p << H264_1080p << qualityPresets[Preset_720p]; + qualityPresets[Preset_1440p] << VP9_1440p << H264_1440p << qualityPresets[Preset_1080p]; + qualityPresets[Preset_2160p] << VP9_2160p << H264_2160p << qualityPresets[Preset_1440p]; + + qualityPresets[Preset_720p60] << AV1_HFR_HIGH_720p << AV1_HFR_720p; + qualityPresets[Preset_1080p60] << AV1_HFR_HIGH_1080p << AV1_HFR_1080p; + qualityPresets[Preset_1440p60] << AV1_HFR_HIGH_1440p << AV1_HFR_1440p; + qualityPresets[Preset_2160p60] << AV1_HFR_HIGH_2160p << AV1_HFR_2160p; + qualityPresets[Preset_4320p60] << AV1_HFR_4320p_1 << AV1_HFR_4320p_2; + maybeAppendVp9Hdr(); + qualityPresets[Preset_720p60] << VP9_720p60 << H264_720p60; + qualityPresets[Preset_1080p60] << VP9_1080p60 << H264_1080p60 << qualityPresets[Preset_720p60]; + qualityPresets[Preset_1440p60] << VP9_1440p60 << H264_1440p60 << qualityPresets[Preset_1080p60]; + qualityPresets[Preset_2160p60] << VP9_2160p60 << H264_2160p60 << qualityPresets[Preset_1440p60]; + qualityPresets[Preset_4320p60] << VP9_4320p60 << qualityPresets[Preset_2160p60]; + } // Append also non-60 FPS itags to 60 FPS itags - qualityPresets[_720p60] += qualityPresets[_720p]; - qualityPresets[_1080p60] += qualityPresets[_1080p]; - qualityPresets[_1440p60] += qualityPresets[_1440p]; - qualityPresets[_2160p60] += qualityPresets[_2160p]; - qualityPresets[_4320p60] += qualityPresets[_2160p]; + qualityPresets[Preset_720p60] << qualityPresets[Preset_720p]; + qualityPresets[Preset_1080p60] << qualityPresets[Preset_1080p]; + qualityPresets[Preset_1440p60] << qualityPresets[Preset_1440p]; + qualityPresets[Preset_2160p60] << qualityPresets[Preset_2160p]; + qualityPresets[Preset_4320p60] << qualityPresets[Preset_2160p]; } - QList liveQualityPresets[QualityPresetsCount]; + QVector liveQualityPresets[PresetCount]; { - liveQualityPresets[_720p60] << 300; - liveQualityPresets[_1080p60] << 301 << liveQualityPresets[_720p60]; - liveQualityPresets[_1440p60] << liveQualityPresets[_1080p60]; - liveQualityPresets[_2160p60] << liveQualityPresets[_1440p60]; - liveQualityPresets[_4320p60] << liveQualityPresets[_2160p60]; - - liveQualityPresets[_480p] << 94 << 93 << 92 << 91; - liveQualityPresets[_720p] << 95 << liveQualityPresets[_480p]; - liveQualityPresets[_1080p] << 96 << liveQualityPresets[_720p]; - liveQualityPresets[_1440p] << 265 << liveQualityPresets[_1080p]; - liveQualityPresets[_2160p] << 267 << liveQualityPresets[_1440p]; + liveQualityPresets[Preset_480p] << H264_480p_AAC_128 << H264_360p_AAC_128 << H264_240p_AAC_48 << H264_144p_AAC_48; + liveQualityPresets[Preset_720p] << H264_720p_AAC_256 << liveQualityPresets[Preset_480p]; + liveQualityPresets[Preset_1080p] << H264_1080p_AAC_256 << liveQualityPresets[Preset_720p]; + liveQualityPresets[Preset_1440p] << liveQualityPresets[Preset_1080p]; + liveQualityPresets[Preset_2160p] << liveQualityPresets[Preset_1440p]; + + liveQualityPresets[Preset_720p60] << H264_720p60_AAC_128; + liveQualityPresets[Preset_1080p60] << H264_1080p60_AAC_128 << liveQualityPresets[Preset_720p60]; + liveQualityPresets[Preset_1440p60] << liveQualityPresets[Preset_1080p60]; + liveQualityPresets[Preset_2160p60] << liveQualityPresets[Preset_1440p60]; + liveQualityPresets[Preset_4320p60] << liveQualityPresets[Preset_2160p60]; // Append also non-60 FPS itags to 60 FPS itags - liveQualityPresets[_720p60] += liveQualityPresets[_720p]; - liveQualityPresets[_1080p60] += liveQualityPresets[_1080p]; - liveQualityPresets[_1440p60] += liveQualityPresets[_1440p]; - liveQualityPresets[_2160p60] += liveQualityPresets[_2160p]; - liveQualityPresets[_4320p60] += liveQualityPresets[_2160p]; + liveQualityPresets[Preset_720p60] += liveQualityPresets[Preset_720p]; + liveQualityPresets[Preset_1080p60] += liveQualityPresets[Preset_1080p]; + liveQualityPresets[Preset_1440p60] += liveQualityPresets[Preset_1440p]; + liveQualityPresets[Preset_2160p60] += liveQualityPresets[Preset_2160p]; + liveQualityPresets[Preset_4320p60] += liveQualityPresets[Preset_2160p]; } QMutexLocker locker(&m_itagsMutex); m_videoItags = qualityPresets[qualityIdx]; - m_audioItags = {251, 171, 140, 250, 249}; + m_audioItags = {Opus_160, AAC_128}; m_hlsItags = liveQualityPresets[qualityIdx]; - m_singleUrlItags = {43, 18}; - if (qualityIdx != _480p) - m_singleUrlItags.prepend(22); } void YouTube::deleteReplies() @@ -725,13 +851,13 @@ void YouTube::setAutocomplete(const QByteArray &data) const QJsonDocument json = QJsonDocument::fromJson(data, &jsonErr); if (jsonErr.error != QJsonParseError::NoError) { - qCWarning(youtube) << "Cannot parse autocomplete JSON:" << jsonErr.errorString(); + qWarning() << "Cannot parse autocomplete JSON:" << jsonErr.errorString(); return; } const QJsonArray mainArr = json.array(); if (mainArr.count() < 2) { - qCWarning(youtube) << "Invalid autocomplete JSON array"; + qWarning() << "Invalid autocomplete JSON array"; return; } const QJsonArray arr = mainArr.at(1).toArray(); @@ -745,143 +871,108 @@ void YouTube::setAutocomplete(const QByteArray &data) if (searchE->hasFocus()) completer->complete(); } -void YouTube::setSearchResults(QString data) + +void YouTube::setSearchResults(const QByteArray &data) { - /* Usuwanie komentarzy HTML */ - for (int commentIdx = 0 ;;) - { - if ((commentIdx = data.indexOf("", commentIdx); - if (commentEndIdx >= 0) //Jeżeli jest koniec komentarza - data.remove(commentIdx, commentEndIdx - commentIdx + 3); //Wyrzuć zakomentowany fragment - else - { - data.remove(commentIdx, data.length() - commentIdx); //Wyrzuć cały tekst do końca - break; - } - } + const auto json = getYtInitialData(data); - int i; - const QStringList splitted = data.split("yt-lockup "); - for (i = 1; i < splitted.count(); ++i) + const auto sectionListRendererContents = json.object() + ["contents"].toObject() + ["twoColumnSearchResultsRenderer"].toObject() + ["primaryContents"].toObject() + ["sectionListRenderer"].toObject() + ["contents"].toArray() + ; + + for (auto &&obj : sectionListRendererContents) { - QString title, videoInfoLink, duration, image, user; - const QString &entry = splitted[i]; - int idx; + const auto contents = obj.toObject() + ["itemSectionRenderer"].toObject() + ["contents"].toArray() + ; - if (entry.contains("yt-lockup-channel")) //Ignore channels - continue; + for (auto &&obj : contents) + { + const auto videoRenderer = obj.toObject()["videoRenderer"].toObject(); + const auto playlistRenderer = obj.toObject()["playlistRenderer"].toObject(); - const bool isPlaylist = entry.contains("yt-lockup-playlist"); + const bool isVideo = !videoRenderer.isEmpty() && playlistRenderer.isEmpty(); - if ((idx = entry.indexOf("yt-lockup-title")) > -1) - { - int urlIdx = entry.indexOf("href=\"", idx); - int titleIdx = entry.indexOf("title=\"", idx); - if (titleIdx > -1 && urlIdx > -1 && titleIdx > urlIdx) + QString title, contentId, length, user, publishTime, viewCount, thumbnail, url; + + if (isVideo) { - const int endUrlIdx = entry.indexOf("\"", urlIdx += 6); - const int endTitleIdx = entry.indexOf("\"", titleIdx += 7); - if (endTitleIdx > -1 && endUrlIdx > -1 && endTitleIdx > endUrlIdx) - { - videoInfoLink = entry.mid(urlIdx, endUrlIdx - urlIdx).replace("&", "&"); - if (!videoInfoLink.isEmpty() && videoInfoLink.startsWith('/')) - videoInfoLink.prepend(YOUTUBE_URL); - title = entry.mid(titleIdx, endTitleIdx - titleIdx); - } + title = videoRenderer["title"].toObject()["runs"].toArray().at(0).toObject()["text"].toString(); + contentId = videoRenderer["videoId"].toString(); + if (title.isEmpty() || contentId.isEmpty()) + continue; + + length = videoRenderer["lengthText"].toObject()["simpleText"].toString(); + user = videoRenderer["ownerText"].toObject()["runs"].toArray().at(0).toObject()["text"].toString(); + publishTime = videoRenderer["publishedTimeText"].toObject()["simpleText"].toString(); + viewCount = videoRenderer["shortViewCountText"].toObject()["simpleText"].toString(); + thumbnail = videoRenderer["thumbnail"].toObject()["thumbnails"].toArray().at(0).toObject()["url"].toString(); + + url = YOUTUBE_URL "/watch?v=" + contentId; } - } - if ((idx = entry.indexOf("video-thumb")) > -1) - { - int skip = 0; - int imgIdx = entry.indexOf("data-thumb=\"", idx); - if (imgIdx > -1) - skip = 12; else { - imgIdx = entry.indexOf("src=\"", idx); - skip = 5; - } - if (imgIdx > -1) - { - int imgEndIdx = entry.indexOf("\"", imgIdx += skip); - if (imgEndIdx > -1) - { - image = entry.mid(imgIdx, imgEndIdx - imgIdx); - if (image.endsWith(".gif")) //GIF nie jest miniaturką - jest to pojedynczy piksel :D (very old code, is it still relevant?) - image.clear(); - else if (image.startsWith("//")) - image.prepend("https:"); - if ((idx = image.indexOf("?")) > 0) - image.truncate(idx); - } - } - } - if (!isPlaylist && (idx = entry.indexOf("video-time")) > -1 && (idx = entry.indexOf(">", idx)) > -1) - { - int endIdx = entry.indexOf("<", idx += 1); - if (endIdx > -1) - { - duration = entry.mid(idx, endIdx - idx); - if (!duration.startsWith("0") && duration.indexOf(":") == 1 && duration.count(":") == 1) - duration.prepend("0"); + title = playlistRenderer["title"].toObject()["simpleText"].toString(); + contentId = playlistRenderer["playlistId"].toString(); + if (title.isEmpty() || contentId.isEmpty()) + continue; + + user = playlistRenderer["longBylineText"].toObject()["runs"].toArray().at(0).toObject()["text"].toString(); + thumbnail = playlistRenderer + ["thumbnailRenderer"].toObject() + ["playlistVideoThumbnailRenderer"].toObject() + ["thumbnail"].toObject() + ["thumbnails"].toArray().at(0).toObject() + ["url"].toString() + ; + + url = YOUTUBE_URL "/playlist?list=" + contentId; } - } - if ((idx = entry.indexOf("yt-lockup-byline")) > -1) - { - int endIdx = entry.indexOf("", idx); - if (endIdx > -1 && (idx = entry.lastIndexOf(">", endIdx)) > -1) - { - ++idx; - QTextDocument txtDoc; - txtDoc.setHtml(entry.mid(idx, endIdx - idx)); - user = txtDoc.toPlainText(); - } - } + auto tWI = new QTreeWidgetItem(resultsW); - if (!title.isEmpty() && !videoInfoLink.isEmpty()) - { - QTreeWidgetItem *tWI = new QTreeWidgetItem(resultsW); - - QTextDocument txtDoc; - txtDoc.setHtml(title); - - tWI->setText(0, txtDoc.toPlainText()); - tWI->setText(1, !isPlaylist ? duration : tr("Playlist")); + tWI->setText(0, title); + tWI->setText(1, isVideo ? length : tr("Playlist")); tWI->setText(2, user); - tWI->setToolTip(0, QString("%1: %2\n%3: %4\n%5: %6") - .arg(resultsW->headerItem()->text(0), tWI->text(0), - !isPlaylist ? resultsW->headerItem()->text(1) : tr("Playlist"), - !isPlaylist ? tWI->text(1) : tr("yes"), - resultsW->headerItem()->text(2), tWI->text(2)) - ); + QString tooltip; + tooltip += QString("%1: %2\n").arg(resultsW->headerItem()->text(0), tWI->text(0)); + tooltip += QString("%1: %2\n").arg(isVideo ? resultsW->headerItem()->text(1) : tr("Playlist"), isVideo ? tWI->text(1) : tr("yes")); + tooltip += QString("%1: %2").arg(resultsW->headerItem()->text(2), tWI->text(2)); + if (isVideo) + { + tooltip += QString("\n%1: %2\n").arg(tr("Publish time"), publishTime); + tooltip += QString("%1: %2").arg(tr("View count"), viewCount); + } + tWI->setToolTip(0, tooltip); - tWI->setData(0, Qt::UserRole, videoInfoLink); - tWI->setData(1, Qt::UserRole, isPlaylist); + tWI->setData(0, Qt::UserRole, url); + tWI->setData(1, Qt::UserRole, !isVideo); - if (isPlaylist) + if (!isVideo) { tWI->setDisabled(true); - NetworkReply *linkReply = net.start(videoInfoLink); - linkReply->setProperty("tWI", qVariantFromValue((void *)tWI)); + auto linkReply = net.start(url); + linkReply->setProperty("tWI", QVariant::fromValue((void *)tWI)); linkReplies += linkReply; } - NetworkReply *imageReply = net.start(image); - imageReply->setProperty("tWI", qVariantFromValue((void *)tWI)); - imageReplies += imageReply; + if (!thumbnail.isEmpty()) + { + auto imageReply = net.start(thumbnail); + imageReply->setProperty("tWI", QVariant::fromValue((void *)tWI)); + imageReplies += imageReply; + } } } - if (i == 1) - { - resultsW->clear(); - } - else + if (resultsW->topLevelItemCount() > 0) { pageSwitcher->currPageB->setValue(currPage); pageSwitcher->show(); @@ -927,7 +1018,6 @@ QStringList YouTube::getYouTubeVideo(const QString ¶m, const QString &url, I const auto videoItags = m_videoItags; const auto audioItags = m_audioItags; const auto hlsItags = m_hlsItags; - const auto singleUrlItags = m_singleUrlItags; m_itagsMutex.unlock(); QHash> itagsData; @@ -938,11 +1028,11 @@ QStringList YouTube::getYouTubeVideo(const QString ¶m, const QString &url, I if (format.isEmpty()) continue; - const auto container = format["container"].toString(); - if (container.contains("dash", Qt::CaseInsensitive)) + const auto protocol = format["protocol"].toString(); + if (protocol.contains("dash", Qt::CaseInsensitive)) { - // Skip MP4 DASH, because it doesn't work properly - continue; + if (format.contains("fragment_base_url")) + continue; // Skip DASH, because it doesn't work } const auto itag = format["format_id"].toString().toInt(); @@ -952,11 +1042,11 @@ QStringList YouTube::getYouTubeVideo(const QString ¶m, const QString &url, I itagsData[itag] = {url, "." + ext}; } - auto appendUrl = [&](const QList &itags) { + auto appendUrl = [&](const QVector &itags) { for (auto &&itag : itags) { auto it = itagsData.constFind(itag); - if (it != itagsData.cend()) + if (it != itagsData.constEnd()) { urls += it->first; exts += it->second; @@ -965,19 +1055,21 @@ QStringList YouTube::getYouTubeVideo(const QString ¶m, const QString &url, I } }; - if (!isLive) + if (isLive) { + appendUrl(hlsItags); + } + else + { + appendUrl(audioItags); if (!audioOnly) appendUrl(videoItags); - appendUrl(audioItags); } + if (urls.count() != 1 + (audioOnly ? 0 : 1)) { - if (!urls.isEmpty()) - urls.clear(); - appendUrl(hlsItags); - if (urls.isEmpty()) - appendUrl(singleUrlItags); + urls.clear(); + exts.clear(); } if (urls.isEmpty()) @@ -988,6 +1080,8 @@ QStringList YouTube::getYouTubeVideo(const QString ¶m, const QString &url, I const auto subtitles = o["subtitles"].toObject(); QString lang = QMPlay2Core.getSettings().getString("SubtitlesLanguage"); + if (lang.isEmpty()) // Default language + lang = QLocale::languageToString(QLocale::system().language()); if (!audioOnly && m_allowSubtitles && !subtitles.isEmpty() && !lang.isEmpty()) { // Try to convert full language name into short language code @@ -1007,6 +1101,8 @@ QStringList YouTube::getYouTubeVideo(const QString ¶m, const QString &url, I auto subtitlesForLang = subtitles[lang].toArray(); if (subtitlesForLang.isEmpty()) subtitlesForLang = subtitles[QMPlay2Core.getLanguage()].toArray(); + if (subtitlesForLang.isEmpty()) + subtitlesForLang = subtitles["en"].toArray(); for (auto &&subtitlesFmtVal : asConst(subtitlesForLang)) { @@ -1056,44 +1152,78 @@ QStringList YouTube::getYouTubeVideo(const QString ¶m, const QString &url, I return result; } -void YouTube::preparePlaylist(const QString &data, QTreeWidgetItem *tWI) +void YouTube::preparePlaylist(const QByteArray &data, QTreeWidgetItem *tWI) { - int idx = data.indexOf("playlist-videos-container"); - if (idx > -1) + QStringList playlist; + + const auto json = getYtInitialData(data); + + const auto contents = json.object() + ["contents"].toObject() + ["twoColumnBrowseResultsRenderer"].toObject() + ["tabs"].toArray().at(0).toObject() + ["tabRenderer"].toObject() + ["content"].toObject() + ["sectionListRenderer"].toObject() + ["contents"].toArray().at(0).toObject() + ["itemSectionRenderer"].toObject() + ["contents"].toArray().at(0).toObject() + ["playlistVideoListRenderer"].toObject() + ["contents"].toArray() + ; + + for (auto &&obj : contents) { - const QString tags[2] = {"video-id", "video-title"}; - QStringList playlist, entries = data.mid(idx).split("yt-uix-scroller-scroll-unit", QString::SkipEmptyParts); - entries.removeFirst(); - for (const QString &entry : asConst(entries)) - { - QStringList plistEntry; - for (int i = 0; i < 2; ++i) - { - idx = entry.indexOf(tags[i]); - if (idx > -1 && (idx = entry.indexOf('"', idx += tags[i].length())) > -1) - { - const int endIdx = entry.indexOf('"', idx += 1); - if (endIdx > -1) - { - const QString str = entry.mid(idx, endIdx - idx); - if (!i) - plistEntry += str; - else - { - QTextDocument txtDoc; - txtDoc.setHtml(str); - plistEntry += txtDoc.toPlainText(); - } - } - } - } - if (plistEntry.count() == 2) - playlist += plistEntry; - } - if (!playlist.isEmpty()) - { - tWI->setData(0, Qt::UserRole + 1, playlist); - tWI->setDisabled(false); - } + const auto playlistRenderer = obj.toObject()["playlistVideoRenderer"].toObject(); + + const auto title = playlistRenderer["title"].toObject()["runs"].toArray().at(0).toObject()["text"].toString(); + const auto videoId = playlistRenderer["videoId"].toString(); + if (title.isEmpty() || videoId.isEmpty()) + continue; + + playlist += { + videoId, + title, + }; + } + + if (!playlist.isEmpty()) + { + tWI->setData(0, Qt::UserRole + 1, playlist); + tWI->setDisabled(false); + } +} + +void YouTube::onShowSettingsClicked() { + emit QMPlay2Core.showSettings("Extensions"); +} + +void YouTube::onSortByChanged(int index) { + if (m_sortByIdx != index) { + m_sortByIdx = index; + sets().set("YouTube/SortBy", m_sortByIdx); + search(); } } + +QJsonDocument YouTube::getYtInitialData(const QByteArray &data) +{ + int idx = data.indexOf("ytInitialData"); + if (idx < 0) + return QJsonDocument(); + + idx = data.indexOf("{", idx); + if (idx < 0) + return QJsonDocument(); + + QJsonParseError e = {}; + auto jsonDoc = QJsonDocument::fromJson(data.mid(idx), &e); + + if (Q_UNLIKELY(e.error == QJsonParseError::NoError)) + return jsonDoc; + + if (e.error == QJsonParseError::GarbageAtEnd && e.offset > 0) + return QJsonDocument::fromJson(data.mid(idx, e.offset)); + + return QJsonDocument(); +} diff --git a/src/modules/Extensions/YouTube.hpp b/src/modules/Extensions/YouTube.hpp index 17350c2d..d853bbb1 100644 --- a/src/modules/Extensions/YouTube.hpp +++ b/src/modules/Extensions/YouTube.hpp @@ -26,6 +26,8 @@ #include #include +#include + class NetworkReply; class QProgressBar; class QActionGroup; @@ -37,18 +39,27 @@ class LineEdit; class QLabel; class QMenu; +enum class PreferredCodec +{ + VP9, + H264, + AV1, +}; + /**/ -class ResultsYoutube final : public QTreeWidget +class ResultsYoutube : public QTreeWidget { Q_OBJECT public: ResultsYoutube(); - ~ResultsYoutube(); + ~ResultsYoutube() final; private: void playOrEnqueue(const QString ¶m, QTreeWidgetItem *tWI, const QString &addrParam = QString()); + QString currentParam; + QMenu *menu; private slots: @@ -58,11 +69,14 @@ private slots: void copyPageURL(); void contextMenu(const QPoint &p); + + void playCurrentItem(); + void enqueueCurrentItem(); }; /**/ -class PageSwitcher final : public QWidget +class PageSwitcher : public QWidget { Q_OBJECT public: @@ -76,7 +90,7 @@ public: using ItagNames = QPair>; -class YouTube final : public QWidget, public QMPlay2Extensions +class YouTube : public QWidget, public QMPlay2Extensions { Q_OBJECT @@ -85,19 +99,19 @@ public: public: YouTube(Module &module); - ~YouTube(); + ~YouTube() final; - bool set() override; + bool set() override final; - DockWidget *getDockWidget() override; + DockWidget *getDockWidget() override final; - bool canConvertAddress() const override; + bool canConvertAddress() const override final; - QString matchAddress(const QString &url) const override; - QList addressPrefixList(bool) const override; - void convertAddress(const QString &, const QString &, const QString &, QString *, QString *, QIcon *, QString *, IOController<> *ioCtrl) override; + QString matchAddress(const QString &url) const override final; + QList addressPrefixList(bool) const override final; + void convertAddress(const QString &, const QString &, const QString &, QString *, QString *, QIcon *, QString *, IOController<> *ioCtrl) override final; - QVector getActions(const QString &, double, const QString &, const QString &, const QString &) override; + QVector getActions(const QString &, double, const QString &, const QString &, const QString &) override final; private slots: void next(); @@ -111,18 +125,26 @@ private slots: void searchMenu(); + void onShowSettingsClicked(); + void onQualityPresetChanged(const QString &preset); + void onQualityToggled(); + void onSortByChanged(int index); + private: void setItags(int qualityIdx); void deleteReplies(); void setAutocomplete(const QByteArray &data); - void setSearchResults(QString data); + void setSearchResults(const QByteArray &data); QStringList getYouTubeVideo(const QString ¶m, const QString &url, IOController &youTubeDL); - void preparePlaylist(const QString &data, QTreeWidgetItem *tWI); + void preparePlaylist(const QByteArray &data, QTreeWidgetItem *tWI); + QJsonDocument getYtInitialData(const QByteArray &data); + +private: DockWidget *dw; QIcon youtubeIcon, videoIcon; @@ -148,7 +170,9 @@ private: int m_sortByIdx = 0; QMutex m_itagsMutex; - QList m_videoItags, m_audioItags, m_hlsItags, m_singleUrlItags; + PreferredCodec m_preferredCodec = PreferredCodec::VP9; + bool m_allowVp9Hdr = false; + QVector m_videoItags, m_audioItags, m_hlsItags, m_singleUrlItags; }; #define YouTubeName "YouTube Browser" diff --git a/src/modules/FFmpeg/FFDemux.cpp b/src/modules/FFmpeg/FFDemux.cpp index f8fa33ee..69c5942f 100644 --- a/src/modules/FFmpeg/FFDemux.cpp +++ b/src/modules/FFmpeg/FFDemux.cpp @@ -408,15 +408,18 @@ Playlist::Entries FFDemux::fetchTracks(const QString &url, bool &ok) { Playlist::Entry &entry = entries[i]; const bool lastItem = (i == entries.count() - 1); - const double start = indexes.value(i).second; - const double end = indexes.value(i + 1, {-1.0, -1.0}).first; + const double start = indexes.contains(i) ? indexes.value(i).second : -1.0; + double end = -1.0; + if (indexes.contains(i + 1)) + end = indexes.value(i + 1).first; + if (entry.url.isEmpty() || start < 0.0 || (end <= 0.0 && !lastItem)) { - entries.removeAt(i); + entries.remove(i); continue; } const QString param = QString("CUE:%1:%2").arg(start).arg(end); - if (lastItem && end < 0.0) // Last entry doesn't have specified length in CUE file + if (lastItem && end < 0.0) { FormatContext *fmtCtx = createFmtCtx(); if (fmtCtx->open(entry.url, param)) diff --git a/src/modules/FFmpeg/FFDemux.hpp b/src/modules/FFmpeg/FFDemux.hpp index 498f4b29..389b3419 100644 --- a/src/modules/FFmpeg/FFDemux.hpp +++ b/src/modules/FFmpeg/FFDemux.hpp @@ -22,42 +22,42 @@ class FormatContext; -class FFDemux final : public Demuxer +class FFDemux : public Demuxer { Q_DECLARE_TR_FUNCTIONS(FFDemux) public: FFDemux(Module &); private: - ~FFDemux(); + ~FFDemux() final; - bool set() override; + bool set() override final; - bool metadataChanged() const override; + bool metadataChanged() const override final; - bool isStillImage() const override; + bool isStillImage() const override final; - QList getPrograms() const override; - QList getChapters() const override; + QList getPrograms() const override final; + QList getChapters() const override final; - QString name() const override; - QString title() const override; - QList tags() const override; - bool getReplayGain(bool album, float &gain_db, float &peak) const override; - qint64 size() const override; - double length() const override; - int bitrate() const override; - QByteArray image(bool forceCopy) const override; + QString name() const override final; + QString title() const override final; + QList tags() const override final; + bool getReplayGain(bool album, float &gain_db, float &peak) const override final; + qint64 size() const override final; + double length() const override final; + int bitrate() const override final; + QByteArray image(bool forceCopy) const override final; - bool localStream() const override; + bool localStream() const override final; - bool seek(double pos, bool backward) override; - bool read(Packet &encoded, int &idx) override; - void pause() override; - void abort() override; + bool seek(double pos, bool backward) override final; + bool read(Packet &encoded, int &idx) override final; + void pause() override final; + void abort() override final; - bool open(const QString &entireUrl) override; + bool open(const QString &entireUrl) override final; - Playlist::Entries fetchTracks(const QString &url, bool &ok) override; + Playlist::Entries fetchTracks(const QString &url, bool &ok) override final; /**/ diff --git a/src/modules/FFmpeg/FFReader.hpp b/src/modules/FFmpeg/FFReader.hpp index 75caec11..0cd53f11 100644 --- a/src/modules/FFmpeg/FFReader.hpp +++ b/src/modules/FFmpeg/FFReader.hpp @@ -24,25 +24,25 @@ struct AVIOContext; -class FFReader final : public Reader +class FFReader : public Reader { public: FFReader(); private: - bool readyRead() const override; - bool canSeek() const override; + bool readyRead() const override final; + bool canSeek() const override final; - bool seek(qint64) override; - QByteArray read(qint64) override; - void pause() override; - bool atEnd() const override; - void abort() override; + bool seek(qint64) override final; + QByteArray read(qint64) override final; + void pause() override final; + bool atEnd() const override final; + void abort() override final; - qint64 size() const override; - qint64 pos() const override; - QString name() const override; + qint64 size() const override final; + qint64 pos() const override final; + QString name() const override final; - bool open() override; + bool open() override final; /**/ diff --git a/src/modules/FFmpeg/FFmpeg.hpp b/src/modules/FFmpeg/FFmpeg.hpp index 70e73d4e..0e5ad238 100644 --- a/src/modules/FFmpeg/FFmpeg.hpp +++ b/src/modules/FFmpeg/FFmpeg.hpp @@ -24,19 +24,19 @@ class QComboBox; -class FFmpeg final : public Module +class FFmpeg : public Module { Q_DECLARE_TR_FUNCTIONS(FFmpeg) public: FFmpeg(); - ~FFmpeg(); + ~FFmpeg() final; private: - QList getModulesInfo(const bool) const override; - void *createInstance(const QString &) override; + QList getModulesInfo(const bool) const override final; + void *createInstance(const QString &) override final; - SettingsWidget *getSettingsWidget() override; + SettingsWidget *getSettingsWidget() override final; - void videoDeintSave() override; + void videoDeintSave() override final; /**/ @@ -65,7 +65,7 @@ class QGroupBox; class QSpinBox; class Slider; -class ModuleSettingsWidget final : public Module::SettingsWidget +class ModuleSettingsWidget : public Module::SettingsWidget { #ifdef QMPlay2_VDPAU Q_OBJECT @@ -84,7 +84,7 @@ private slots: void checkEnables(); #endif private: - void saveSettings() override; + void saveSettings() override final; QGroupBox *demuxerB; QCheckBox *reconnectStreamedB; diff --git a/src/modules/FFmpeg/FormatContext.cpp b/src/modules/FFmpeg/FormatContext.cpp index 0dd0231f..71b2a266 100644 --- a/src/modules/FFmpeg/FormatContext.cpp +++ b/src/modules/FFmpeg/FormatContext.cpp @@ -843,6 +843,10 @@ StreamInfo *FormatContext::getStreamInfo(AVStream *stream) const if (streamInfo->codec_name.isEmpty()) streamInfo->codec_name = codecDescr->name; } + else if (stream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) + { + streamInfo->must_decode = false; + } streamInfo->bitrate = stream->codecpar->bit_rate; streamInfo->bpcs = stream->codecpar->bits_per_coded_sample; diff --git a/src/modules/FFmpeg/VAAPIWriter.hpp b/src/modules/FFmpeg/VAAPIWriter.hpp index c36966db..84249e53 100644 --- a/src/modules/FFmpeg/VAAPIWriter.hpp +++ b/src/modules/FFmpeg/VAAPIWriter.hpp @@ -24,27 +24,27 @@ #include #include -class VAAPIWriter final : public QWidget, public VideoWriter +class VAAPIWriter : public QWidget, public VideoWriter { Q_OBJECT public: VAAPIWriter(Module &module, VAAPI *vaapi); - ~VAAPIWriter(); + ~VAAPIWriter() final; - bool set() override; + bool set() override final; - bool readyWrite() const override; + bool readyWrite() const override final; - bool processParams(bool *paramsCorrected) override; - void writeVideo(const VideoFrame &videoFrame) override; - void writeOSD(const QList &osd) override; - void pause() override; + bool processParams(bool *paramsCorrected) override final; + void writeVideo(const VideoFrame &videoFrame) override final; + void writeOSD(const QList &osd) override final; + void pause() override final; - bool hwAccelGetImg(const VideoFrame &videoFrame, void *dest, ImgScaler *nv12ToRGB32) const override; + bool hwAccelGetImg(const VideoFrame &videoFrame, void *dest, ImgScaler *nv12ToRGB32) const override final; - QString name() const override; + QString name() const override final; - bool open() override; + bool open() override final; /**/ @@ -56,11 +56,11 @@ public: private: void draw(VASurfaceID id = -1, int field = -1); - void resizeEvent(QResizeEvent *) override; - void paintEvent(QPaintEvent *) override; - bool event(QEvent *) override; + void resizeEvent(QResizeEvent *) override final; + void paintEvent(QPaintEvent *) override final; + bool event(QEvent *) override final; - QPaintEngine *paintEngine() const override; + QPaintEngine *paintEngine() const override final; void clearVaImage(); diff --git a/src/modules/FFmpeg/VDPAUWriter.hpp b/src/modules/FFmpeg/VDPAUWriter.hpp index 4c6dc28f..123fb86b 100644 --- a/src/modules/FFmpeg/VDPAUWriter.hpp +++ b/src/modules/FFmpeg/VDPAUWriter.hpp @@ -28,27 +28,27 @@ struct _XDisplay; -class VDPAUWriter final : public QWidget, public VideoWriter +class VDPAUWriter : public QWidget, public VideoWriter { Q_OBJECT public: VDPAUWriter(Module &module); - ~VDPAUWriter(); + ~VDPAUWriter() final; - bool set() override; + bool set() override final; - bool readyWrite() const override; + bool readyWrite() const override final; - bool processParams(bool *paramsCorrected) override; - void writeVideo(const VideoFrame &videoFrame) override; - void writeOSD(const QList &osd) override; - void pause() override; + bool processParams(bool *paramsCorrected) override final; + void writeVideo(const VideoFrame &videoFrame) override final; + void writeOSD(const QList &osd) override final; + void pause() override final; - bool hwAccelGetImg(const VideoFrame &videoFrame, void *dest, ImgScaler *nv12ToRGB32) const override; + bool hwAccelGetImg(const VideoFrame &videoFrame, void *dest, ImgScaler *nv12ToRGB32) const override final; - QString name() const override; + QString name() const override final; - bool open() override; + bool open() override final; /**/ @@ -78,11 +78,11 @@ private: Q_SLOT void draw(VdpVideoSurface surface_id = VDP_INVALID_HANDLE); void vdpau_display(); - void resizeEvent(QResizeEvent *) override; - void paintEvent(QPaintEvent *) override; - bool event(QEvent *) override; + void resizeEvent(QResizeEvent *) override final; + void paintEvent(QPaintEvent *) override final; + bool event(QEvent *) override final; - QPaintEngine *paintEngine() const override; + QPaintEngine *paintEngine() const override final; void destroyOutputSurfaces(); void clr(); diff --git a/src/modules/Inputs/Inputs.hpp b/src/modules/Inputs/Inputs.hpp index 9cdbcff3..5484fe53 100644 --- a/src/modules/Inputs/Inputs.hpp +++ b/src/modules/Inputs/Inputs.hpp @@ -20,18 +20,18 @@ #include -class Inputs final : public Module +class Inputs : public Module { Q_OBJECT public: Inputs(); private: - QList getModulesInfo(const bool) const override; - void *createInstance(const QString &) override; + QList getModulesInfo(const bool) const override final; + void *createInstance(const QString &) override final; - QList getAddActions() override; + QList getAddActions() override final; - SettingsWidget *getSettingsWidget() override; + SettingsWidget *getSettingsWidget() override final; QIcon toneIcon, pcmIcon, rayman2Icon; private slots: @@ -43,7 +43,7 @@ private slots: #include #include -class HzW final : public QWidget +class HzW : public QWidget { public: HzW(int, const QStringList &); @@ -60,7 +60,7 @@ private: class QGridLayout; -class AddD final : public QDialog +class AddD : public QDialog { Q_OBJECT public: @@ -95,7 +95,7 @@ class QGroupBox; class QCheckBox; class QLineEdit; -class ModuleSettingsWidget final : public Module::SettingsWidget +class ModuleSettingsWidget : public Module::SettingsWidget { Q_OBJECT public: @@ -103,7 +103,7 @@ public: private slots: void applyFreqs(); private: - void saveSettings() override; + void saveSettings() override final; AddD *toneGenerator; QGroupBox *pcmB; diff --git a/src/modules/Inputs/PCM.hpp b/src/modules/Inputs/PCM.hpp index eb47a911..528cb35a 100644 --- a/src/modules/Inputs/PCM.hpp +++ b/src/modules/Inputs/PCM.hpp @@ -24,25 +24,25 @@ class Reader; -class PCM final : public Demuxer +class PCM : public Demuxer { public: enum FORMAT {PCM_U8, PCM_S8, PCM_S16, PCM_S24, PCM_S32, PCM_FLT, FORMAT_COUNT}; PCM(Module &); private: - bool set() override; + bool set() override final; - QString name() const override; - QString title() const override; - double length() const override; - int bitrate() const override; + QString name() const override final; + QString title() const override final; + double length() const override final; + int bitrate() const override final; - bool seek(double, bool) override; - bool read(Packet &, int &) override; - void abort() override; + bool seek(double, bool) override final; + bool read(Packet &, int &) override final; + void abort() override final; - bool open(const QString &) override; + bool open(const QString &) override final; /**/ diff --git a/src/modules/Inputs/Rayman2.hpp b/src/modules/Inputs/Rayman2.hpp index af623d90..2326a272 100644 --- a/src/modules/Inputs/Rayman2.hpp +++ b/src/modules/Inputs/Rayman2.hpp @@ -24,23 +24,23 @@ class Reader; -class Rayman2 final : public Demuxer +class Rayman2 : public Demuxer { public: Rayman2(Module &); private: - bool set() override; + bool set() override final; - QString name() const override; - QString title() const override; - double length() const override; - int bitrate() const override; + QString name() const override final; + QString title() const override final; + double length() const override final; + int bitrate() const override final; - bool seek(double pos, bool backward) override; - bool read(Packet &, int &) override; - void abort() override; + bool seek(double pos, bool backward) override final; + bool read(Packet &, int &) override final; + void abort() override final; - bool open(const QString &) override; + bool open(const QString &) override final; /**/ diff --git a/src/modules/Inputs/ToneGenerator.cpp b/src/modules/Inputs/ToneGenerator.cpp index 62ff4436..83c1d0ea 100644 --- a/src/modules/Inputs/ToneGenerator.cpp +++ b/src/modules/Inputs/ToneGenerator.cpp @@ -21,7 +21,8 @@ #include #include -#include +#include +#define QUrlQuery(url) url #include diff --git a/src/modules/Inputs/ToneGenerator.hpp b/src/modules/Inputs/ToneGenerator.hpp index a84c7fb0..9b29313b 100644 --- a/src/modules/Inputs/ToneGenerator.hpp +++ b/src/modules/Inputs/ToneGenerator.hpp @@ -20,28 +20,28 @@ #include -class ToneGenerator final : public Demuxer +class ToneGenerator : public Demuxer { Q_DECLARE_TR_FUNCTIONS(ToneGenerator) public: ToneGenerator(Module &); - bool set() override; + bool set() override final; private: - bool metadataChanged() const override; + bool metadataChanged() const override final; - QString name() const override; - QString title() const override; - double length() const override; - int bitrate() const override; + QString name() const override final; + QString title() const override final; + double length() const override final; + int bitrate() const override final; - bool dontUseBuffer() const override; + bool dontUseBuffer() const override final; - bool seek(double, bool) override; - bool read(Packet &, int &) override; - void abort() override; + bool seek(double, bool) override final; + bool read(Packet &, int &) override final; + void abort() override final; - bool open(const QString &) override; + bool open(const QString &) override final; /**/ diff --git a/src/modules/OpenGL2/CMakeLists.txt b/src/modules/OpenGL2/CMakeLists.txt index 9a411b44..84926476 100644 --- a/src/modules/OpenGL2/CMakeLists.txt +++ b/src/modules/OpenGL2/CMakeLists.txt @@ -5,7 +5,6 @@ set(OpenGL2_HDR OpenGL2.hpp OpenGL2Writer.hpp OpenGL2Common.hpp - OpenGL2Window.hpp OpenGL2Widget.hpp Sphere.hpp Vertices.hpp @@ -15,7 +14,6 @@ set(OpenGL2_SRC OpenGL2.cpp OpenGL2Writer.cpp OpenGL2Common.cpp - OpenGL2Window.cpp OpenGL2Widget.cpp Sphere.cpp ) @@ -24,15 +22,25 @@ set(OpenGL2_RESOURCES res.qrc ) -if(NOT WIN32) - if(Qt5Gui_OPENGL_IMPLEMENTATION STREQUAL GLESv2) - set(QT_USES_OPENGLES ON) - add_definitions(-DOPENGL_ES2) - elseif(Qt5Gui_OPENGL_IMPLEMENTATION STREQUAL GLES) - message(SEND_ERROR "OpenGL|ES 1.0 is not supported!") - endif() +function(gles1Error) + message(SEND_ERROR "OpenGL|ES 1.0 is not supported!") +endfunction() + +find_package(Qt4 REQUIRED QtOpenGL) +include(${QT_USE_FILE}) +if(${QT_QCONFIG} MATCHES "opengles2") + set(QT_USES_OPENGLES ON) + add_definitions(-DOPENGL_ES2) +elseif(${QT_QCONFIG} MATCHES "opengles1") + gles1Error() endif() +if(NOT QT_USES_OPENGLES) + add_definitions(-DVSYNC_SETTINGS) +endif() + +qt4_add_resources(OpenGL2_RESOURCES_RCC ${OpenGL2_RESOURCES}) + include_directories(../../qmplay2/headers) add_library(${PROJECT_NAME} ${QMPLAY2_MODULE} @@ -41,6 +49,8 @@ add_library(${PROJECT_NAME} ${QMPLAY2_MODULE} ${OpenGL2_RESOURCES} ) +target_link_libraries(${PROJECT_NAME} Qt4::QtCore Qt4::QtGui Qt4::QtOpenGL) + target_link_libraries(${PROJECT_NAME} libqmplay2 ) @@ -48,12 +58,6 @@ target_link_libraries(${PROJECT_NAME} if(APPLE) find_package(OpenGL REQUIRED) target_link_libraries(${PROJECT_NAME} ${OPENGL_LIBRARIES}) -elseif(WIN32) - if(QT_USES_OPENGLES) - target_link_libraries(${PROJECT_NAME} GLESv2) - else() - target_link_libraries(${PROJECT_NAME} opengl32) - endif() endif() install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${MODULES_INSTALL_PATH}) diff --git a/src/modules/OpenGL2/OpenGL2.cpp b/src/modules/OpenGL2/OpenGL2.cpp index b52c76be..a98a6f51 100644 --- a/src/modules/OpenGL2/OpenGL2.cpp +++ b/src/modules/OpenGL2/OpenGL2.cpp @@ -19,8 +19,6 @@ #include #include -#include - OpenGL2::OpenGL2() : Module("OpenGL2") { @@ -29,12 +27,8 @@ OpenGL2::OpenGL2() : init("Enabled", true); init("AllowPBO", true); init("HQScaling", false); - const QString platformName = QGuiApplication::platformName(); - init("ForceRtt", (platformName == "cocoa" || platformName == "android")); +#ifdef VSYNC_SETTINGS init("VSync", true); -#ifdef Q_OS_WIN - if (QSysInfo::windowsVersion() >= QSysInfo::WV_6_0) - init("PreventFullScreen", true); #endif } @@ -45,6 +39,7 @@ QList OpenGL2::getModulesInfo(const bool showDisabled) const modulesInfo += Info(OpenGL2WriterName, WRITER, QStringList{"video"}); return modulesInfo; } + void *OpenGL2::createInstance(const QString &name) { if (name == OpenGL2WriterName && getBool("Enabled")) @@ -76,35 +71,16 @@ ModuleSettingsWidget::ModuleSettingsWidget(Module &module) : hqScalingB = new QCheckBox(tr("High quality video scaling")); hqScalingB->setToolTip(tr("Trilinear filtering for minification and bicubic filtering for magnification.")); hqScalingB->setChecked(sets().getBool("HQScaling")); - - forceRttB = new QCheckBox(tr("Force render to texture if possible (not recommended)")); - forceRttB->setToolTip(tr("Always enabled on Wayland and Android platforms.\nSet visualizations to OpenGL mode if enabled.")); - forceRttB->setChecked(sets().getBool("ForceRtt")); - +#ifdef VSYNC_SETTINGS vsyncB = new QCheckBox(tr("Vertical sync") + " (VSync)"); vsyncB->setChecked(sets().getBool("VSync")); - -#ifdef Q_OS_WIN - if (QSysInfo::windowsVersion() >= QSysInfo::WV_6_0) - { - preventFullScreenB = new QCheckBox(tr("Try to prevent exclusive full screen")); - preventFullScreenB->setChecked(sets().getBool("PreventFullScreen")); - } - else - { - preventFullScreenB = nullptr; - } #endif - QGridLayout *layout = new QGridLayout(this); layout->addWidget(enabledB); layout->addWidget(allowPboB); layout->addWidget(hqScalingB); - layout->addWidget(forceRttB); +#ifdef VSYNC_SETTINGS layout->addWidget(vsyncB); -#ifdef Q_OS_WIN - if (preventFullScreenB) - layout->addWidget(preventFullScreenB); #endif } @@ -113,10 +89,7 @@ void ModuleSettingsWidget::saveSettings() sets().set("Enabled", enabledB->isChecked()); sets().set("AllowPBO", allowPboB->isChecked()); sets().set("HQScaling", hqScalingB->isChecked()); - sets().set("ForceRtt", forceRttB->isChecked()); +#ifdef VSYNC_SETTINGS sets().set("VSync", vsyncB->isChecked()); -#ifdef Q_OS_WIN - if (preventFullScreenB) - sets().set("PreventFullScreen", preventFullScreenB->isChecked()); #endif } diff --git a/src/modules/OpenGL2/OpenGL2.hpp b/src/modules/OpenGL2/OpenGL2.hpp index 89f22d21..83aab459 100644 --- a/src/modules/OpenGL2/OpenGL2.hpp +++ b/src/modules/OpenGL2/OpenGL2.hpp @@ -20,15 +20,16 @@ #include -class OpenGL2 final : public Module +class OpenGL2 : public Module { public: OpenGL2(); + private: - QList getModulesInfo(const bool) const override; - void *createInstance(const QString &) override; + QList getModulesInfo(const bool) const override final; + void *createInstance(const QString &) override final; - SettingsWidget *getSettingsWidget() override; + SettingsWidget *getSettingsWidget() override final; }; /**/ @@ -37,18 +38,18 @@ private: class QCheckBox; -class ModuleSettingsWidget final : public Module::SettingsWidget +class ModuleSettingsWidget : public Module::SettingsWidget { Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget) + public: ModuleSettingsWidget(Module &); + private: - void saveSettings() override; + void saveSettings() override final; QCheckBox *enabledB, *allowPboB, *hqScalingB; - QCheckBox *forceRttB; +#ifdef VSYNC_SETTINGS QCheckBox *vsyncB; -#ifdef Q_OS_WIN - QCheckBox *preventFullScreenB; #endif }; diff --git a/src/modules/OpenGL2/OpenGL2Common.cpp b/src/modules/OpenGL2/OpenGL2Common.cpp index a5c451d9..4c518938 100644 --- a/src/modules/OpenGL2/OpenGL2Common.cpp +++ b/src/modules/OpenGL2/OpenGL2Common.cpp @@ -26,9 +26,8 @@ #include #include -#include -#include -#include +#include +#include #include #include #include @@ -38,6 +37,16 @@ #include +#define QOpenGLContext QGLContext +#define QOpenGLShader QGLShader + +#ifndef GL_MAJOR_VERSION + #define GL_MAJOR_VERSION 0x821B +#endif +#ifndef GL_MINOR_VERSION + #define GL_MINOR_VERSION 0x821C +#endif + /* OpenGL|ES 2.0 doesn't have those definitions */ #ifndef GL_MAP_WRITE_BIT #define GL_MAP_WRITE_BIT 0x0002 @@ -88,7 +97,9 @@ OpenGL2Common::OpenGL2Common() : supportsShaders(false), canCreateNonPowerOfTwoTextures(false), glActiveTexture(nullptr), #endif +#ifdef VSYNC_SETTINGS vSync(true), +#endif hwAccellnterface(nullptr), shaderProgramVideo(nullptr), shaderProgramOSD(nullptr), texCoordYCbCrLoc(-1), positionYCbCrLoc(-1), texCoordOSDLoc(-1), positionOSDLoc(-1), @@ -96,9 +107,6 @@ OpenGL2Common::OpenGL2Common() : target(0), Deinterlace(0), allowPBO(true), hasPbo(false), -#ifdef Q_OS_WIN - preventFullScreen(false), -#endif isPaused(false), isOK(false), hwAccelError(false), hasImage(false), doReset(true), setMatrix(true), correctLinesize(false), canUseHueSharpness(true), subsX(-1), subsY(-1), W(-1), H(-1), subsW(-1), subsH(-1), outW(-1), outH(-1), verticesIdx(0), glVer(0), @@ -118,6 +126,7 @@ OpenGL2Common::OpenGL2Common() : rotAnimation.setEasingCurve(QEasingCurve::OutQuint); rotAnimation.setDuration(1000.0); } + OpenGL2Common::~OpenGL2Common() { contextAboutToBeDestroyed(); @@ -130,24 +139,11 @@ void OpenGL2Common::deleteMe() delete this; } -bool OpenGL2Common::testGL() -{ - QOpenGLContext glCtx; - if ((isOK = glCtx.create())) - { - QOffscreenSurface offscreenSurface; - offscreenSurface.create(); - if ((isOK = glCtx.makeCurrent(&offscreenSurface))) - testGLInternal(); - } - return isOK; -} - void OpenGL2Common::newSize(const QSize &size) { const bool canUpdate = !size.isValid(); const QSize winSize = canUpdate ? widget()->size() : size; - const qreal dpr = widget()->devicePixelRatioF(); + const qreal dpr = 1.0; if (!isRotate90()) { Functions::getImageSize(aspectRatio, zoom, winSize.width(), winSize.height(), W, H, &subsX, &subsY); @@ -167,6 +163,7 @@ void OpenGL2Common::newSize(const QSize &size) updateTimer.start(40); } } + void OpenGL2Common::clearImg() { hasImage = false; @@ -319,7 +316,9 @@ void OpenGL2Common::initializeGL() glPixelStorei(GL_UNPACK_ALIGNMENT, 1); } +#ifdef VSYNC_SETTINGS setVSync(vSync); +#endif doReset = true; resetSphereVbo(); @@ -383,7 +382,7 @@ void OpenGL2Common::paintGL() if (hqScaling) { // Must be set before "HWAccelInterface::init()" and must have "m_textureSize" - maybeSetMipmaps(widget()->devicePixelRatioF()); + maybeSetMipmaps(1.0); } /* Prepare textures, register GL textures */ @@ -426,7 +425,7 @@ void OpenGL2Common::paintGL() texCoordYCbCr[2] = texCoordYCbCr[6] = (videoFrame.linesize[0] == widths[0]) ? 1.0f : (widths[0] / (videoFrame.linesize[0] + 1.0f)); if (hqScaling) - maybeSetMipmaps(widget()->devicePixelRatioF()); + maybeSetMipmaps(1.0); } resetDone = true; hasImage = false; @@ -567,7 +566,7 @@ void OpenGL2Common::paintGL() } if (hqScaling) { - const qreal dpr = widget()->devicePixelRatioF(); + const qreal dpr = 1.0; if (!resetDone) maybeSetMipmaps(dpr); const bool useBicubic = (W * dpr > m_textureSize.width() || H * dpr > m_textureSize.height()); @@ -663,7 +662,7 @@ void OpenGL2Common::paintGL() glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } - const QSizeF winSizeSubs = winSize * widget()->devicePixelRatioF(); + const QSizeF winSizeSubs = winSize * 1.0; const float left = (bounds.left() + subsX) * 2.0f / winSizeSubs.width() - osdOffset.x(); const float right = (bounds.right() + subsX + 1) * 2.0f / winSizeSubs.width() - osdOffset.x(); const float top = (bounds.top() + subsY) * 2.0f / winSizeSubs.height() - osdOffset.y(); @@ -817,36 +816,6 @@ void OpenGL2Common::testGLInternal() QWidget *w = widget(); w->grabGesture(Qt::PinchGesture); w->setMouseTracking(true); -#ifdef Q_OS_WIN - /* - * This property is read by QMPlay2 and it ensures that toolbar will be visible - * on fullscreen in Windows Vista and newer on nVidia and AMD drivers. - */ - const bool canPreventFullScreen = (qstrcmp(w->metaObject()->className(), "QOpenGLWidget") != 0); - const QSysInfo::WinVersion winVer = QSysInfo::windowsVersion(); - if (canPreventFullScreen && winVer >= QSysInfo::WV_6_0) - { - Qt::CheckState compositionEnabled; - if (!preventFullScreen) - compositionEnabled = Qt::PartiallyChecked; - else - { - compositionEnabled = Qt::Checked; - if (winVer <= QSysInfo::WV_6_1) //Windows 8 and 10 can't disable DWM composition - { - using DwmIsCompositionEnabledProc = HRESULT (WINAPI *)(BOOL *pfEnabled); - DwmIsCompositionEnabledProc DwmIsCompositionEnabled = (DwmIsCompositionEnabledProc)GetProcAddress(GetModuleHandleA("dwmapi.dll"), "DwmIsCompositionEnabled"); - if (DwmIsCompositionEnabled) - { - BOOL enabled = false; - if (DwmIsCompositionEnabled(&enabled) == S_OK && !enabled) - compositionEnabled = Qt::PartiallyChecked; - } - } - } - w->setProperty("preventFullScreen", (int)compositionEnabled); - } -#endif } bool OpenGL2Common::initGLProc() @@ -881,6 +850,7 @@ bool OpenGL2Common::initGLProc() return true; } + #ifndef OPENGL_ES2 void OpenGL2Common::showOpenGLMissingFeaturesMessage() { @@ -1006,6 +976,7 @@ void OpenGL2Common::mousePress(QMouseEvent *e) } } } + void OpenGL2Common::mouseMove(QMouseEvent *e) { if ((moveVideo || moveOSD) && (e->buttons() & Qt::LeftButton)) @@ -1027,6 +998,7 @@ void OpenGL2Common::mouseMove(QMouseEvent *e) updateGL(true); } } + void OpenGL2Common::mouseRelease(QMouseEvent *e) { if ((moveVideo || moveOSD) && e->button() == Qt::LeftButton) @@ -1051,6 +1023,7 @@ void OpenGL2Common::mousePress360(QMouseEvent *e) mousePos = e->pos(); } } + void OpenGL2Common::mouseMove360(QMouseEvent *e) { if (mouseWrapped) @@ -1072,7 +1045,6 @@ void OpenGL2Common::mouseMove360(QMouseEvent *e) mouseTime = currTime; mousePos = newMousePos; - if (e->source() == Qt::MouseEventNotSynthesized) { if (canWrapMouse) mouseWrapped = Functions::wrapMouse(widget(), mousePos, 1); @@ -1084,6 +1056,7 @@ void OpenGL2Common::mouseMove360(QMouseEvent *e) updateGL(true); } } + void OpenGL2Common::mouseRelease360(QMouseEvent *e) { if (buttonPressed && e->button() == Qt::LeftButton) @@ -1099,11 +1072,13 @@ void OpenGL2Common::mouseRelease360(QMouseEvent *e) buttonPressed = false; } } + inline void OpenGL2Common::resetSphereVbo() { memset(sphereVbo, 0, sizeof sphereVbo); nIndices = 0; } + inline void OpenGL2Common::deleteSphereVbo() { if (nIndices > 0) @@ -1112,6 +1087,7 @@ inline void OpenGL2Common::deleteSphereVbo() resetSphereVbo(); } } + void OpenGL2Common::loadSphere() { const quint32 slices = 50; diff --git a/src/modules/OpenGL2/OpenGL2Common.hpp b/src/modules/OpenGL2/OpenGL2Common.hpp index 60604799..c1b09d18 100644 --- a/src/modules/OpenGL2/OpenGL2Common.hpp +++ b/src/modules/OpenGL2/OpenGL2Common.hpp @@ -21,8 +21,7 @@ #include #include -#include - +#include #include #include #include @@ -33,7 +32,9 @@ #include #endif -#if defined OPENGL_ES2 && !defined APIENTRY +#define QOpenGLShaderProgram QGLShaderProgram + +#if !defined APIENTRY #define APIENTRY #endif @@ -48,6 +49,7 @@ public: inline RotAnimation(OpenGL2Common &glCommon) : glCommon(glCommon) {} + private: void updateCurrentValue(const QVariant &value) override; @@ -70,6 +72,7 @@ class OpenGL2Common using GLMapBufferRange = void *(APIENTRY *)(GLenum, GLintptr, GLsizeiptr, GLbitfield); using GLMapBuffer = void *(APIENTRY *)(GLenum, GLbitfield); using GLUnmapBuffer = GLboolean(APIENTRY *)(GLenum); + public: OpenGL2Common(); virtual ~OpenGL2Common(); @@ -78,7 +81,7 @@ public: virtual QWidget *widget() = 0; - bool testGL(); + virtual bool testGL() = 0; virtual bool setVSync(bool enable) = 0; virtual void updateGL(bool requestDelayed) = 0; @@ -86,6 +89,7 @@ public: void clearImg(); void setSpherical(bool spherical); + protected: void initializeGL(); void paintGL(); @@ -110,9 +114,12 @@ protected: GLMapBuffer glMapBuffer = nullptr; GLUnmapBuffer glUnmapBuffer = nullptr; +#ifdef VSYNC_SETTINGS bool vSync; +#endif void dispatchEvent(QEvent *e, QObject *p); + private: void maybeSetMipmaps(qreal dpr); @@ -133,6 +140,7 @@ private: inline void resetSphereVbo(); inline void deleteSphereVbo(); void loadSphere(); + public: HWAccelInterface *hwAccellnterface; QStringList videoAdjustmentKeys; @@ -155,10 +163,6 @@ public: quint32 pbo[4]; bool allowPBO, hasPbo, hqScaling = false; -#ifdef Q_OS_WIN - bool preventFullScreen; -#endif - bool isPaused, isOK, hwAccelError, hasImage, doReset, setMatrix, correctLinesize, canUseHueSharpness, m_useMipmaps = false; int subsX, subsY, W, H, subsW, subsH, outW, outH, verticesIdx; int glVer; diff --git a/src/modules/OpenGL2/OpenGL2Widget.cpp b/src/modules/OpenGL2/OpenGL2Widget.cpp index 11b26fcf..2c1a8172 100644 --- a/src/modules/OpenGL2/OpenGL2Widget.cpp +++ b/src/modules/OpenGL2/OpenGL2Widget.cpp @@ -1,6 +1,6 @@ /* QMPlay2 is a video and audio player. - Copyright (C) 2010-2019 Błażej Szczygieł + Copyright (C) 2010-2017 Błażej Szczygieł This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -18,12 +18,13 @@ #include "OpenGL2Widget.hpp" -#include +#include OpenGL2Widget::OpenGL2Widget() { - connect(&updateTimer, SIGNAL(timeout()), this, SLOT(update())); + connect(&updateTimer, SIGNAL(timeout()), this, SLOT(updateGL())); // updateGL() from the base class } + OpenGL2Widget::~OpenGL2Widget() { makeCurrent(); @@ -34,46 +35,66 @@ QWidget *OpenGL2Widget::widget() return this; } +bool OpenGL2Widget::testGL() +{ + makeCurrent(); + if ((isOK = isValid())) + testGLInternal(); + doneCurrent(); + return isOK; +} + bool OpenGL2Widget::setVSync(bool enable) { - QSurfaceFormat fmt = format(); - vSync = enable; - if (!isValid()) +#ifdef VSYNC_SETTINGS + bool doDoneCurrent = false; + if (QGLContext::currentContext() != context()) { - fmt.setSwapBehavior(QSurfaceFormat::DoubleBuffer); //Probably it doesn't work - fmt.setSwapInterval(enable); //Does it work on QOpenGLWidget? - setFormat(fmt); - return true; + makeCurrent(); + doDoneCurrent = true; } - return (fmt.swapInterval() == enable); + using SwapInterval = int (APIENTRY *)(int); // BOOL is just normal int in Windows, APIENTRY declares nothing on non-Windows platforms + SwapInterval swapInterval = NULL; + swapInterval = (SwapInterval)context()->getProcAddress("glXSwapIntervalMESA"); + if (!swapInterval) + swapInterval = (SwapInterval)context()->getProcAddress("glXSwapIntervalSGI"); + if (swapInterval) + swapInterval(enable); + if (doDoneCurrent) + doneCurrent(); + vSync = enable; +#else + Q_UNUSED(enable) +#endif + return true; } + void OpenGL2Widget::updateGL(bool requestDelayed) { if (requestDelayed) - QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); + QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest), Qt::LowEventPriority); else - update(); + QGLWidget::updateGL(); } void OpenGL2Widget::initializeGL() { - connect(context(), SIGNAL(aboutToBeDestroyed()), this, SLOT(aboutToBeDestroyed()), Qt::DirectConnection); OpenGL2Common::initializeGL(); } + void OpenGL2Widget::paintGL() { + glClear(GL_COLOR_BUFFER_BIT); OpenGL2Common::paintGL(); } -void OpenGL2Widget::aboutToBeDestroyed() +void OpenGL2Widget::resizeGL(int w, int h) { - makeCurrent(); - contextAboutToBeDestroyed(); - doneCurrent(); + glViewport(0, 0, w, h); } bool OpenGL2Widget::event(QEvent *e) { dispatchEvent(e, parent()); - return QOpenGLWidget::event(e); + return QGLWidget::event(e); } diff --git a/src/modules/OpenGL2/OpenGL2Widget.hpp b/src/modules/OpenGL2/OpenGL2Widget.hpp index 3e8bc14e..3a5c3729 100644 --- a/src/modules/OpenGL2/OpenGL2Widget.hpp +++ b/src/modules/OpenGL2/OpenGL2Widget.hpp @@ -1,6 +1,6 @@ /* QMPlay2 is a video and audio player. - Copyright (C) 2010-2019 Błażej Szczygieł + Copyright (C) 2010-2017 Błażej Szczygieł This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -16,29 +16,34 @@ along with this program. If not, see . */ -#pragma once +#ifndef OPENGL2WIDGET_HPP +#define OPENGL2WIDGET_HPP #include -#include -#include +#include -class OpenGL2Widget final : public QOpenGLWidget, public OpenGL2Common +class OpenGL2Widget : public QGLWidget, public OpenGL2Common { Q_OBJECT + public: OpenGL2Widget(); - ~OpenGL2Widget(); + ~OpenGL2Widget() final; + + QWidget *widget() override final; - QWidget *widget() override; + bool testGL() override final; + bool setVSync(bool enable) override final; + void updateGL(bool requestDelayed) override final; - bool setVSync(bool enable) override; - void updateGL(bool requestDelayed) override; + void initializeGL() override final; + void paintGL() override final; - void initializeGL() override; - void paintGL() override; -private slots: - void aboutToBeDestroyed(); private: - bool event(QEvent *e) override; + void resizeGL(int w, int h) override final; + + bool event(QEvent *e) override final; }; + +#endif // OPENGL2WIDGET_HPP diff --git a/src/modules/OpenGL2/OpenGL2Writer.cpp b/src/modules/OpenGL2/OpenGL2Writer.cpp index 72fe4f70..ebdeefa0 100644 --- a/src/modules/OpenGL2/OpenGL2Writer.cpp +++ b/src/modules/OpenGL2/OpenGL2Writer.cpp @@ -18,18 +18,14 @@ #include -#include #include #include #include -#include - OpenGL2Writer::OpenGL2Writer(Module &module) : drawable(nullptr) , allowPBO(true) - , forceRtt(false) { addParam("W"); addParam("H"); @@ -42,6 +38,7 @@ OpenGL2Writer::OpenGL2Writer(Module &module) SetModule(module); } + OpenGL2Writer::~OpenGL2Writer() { if (drawable) @@ -65,21 +62,10 @@ bool OpenGL2Writer::set() m_hqScaling = newHqScaling; doReset = true; } - +#ifdef VSYNC_SETTINGS vSync = sets().getBool("VSync"); if (drawable && !drawable->setVSync(vSync)) doReset = true; - - const bool newForceRtt = sets().getBool("ForceRtt"); - if (forceRtt != newForceRtt) - doReset = true; - forceRtt = newForceRtt; - -#ifdef Q_OS_WIN - bool newPreventFullScreen = sets().getBool("PreventFullScreen"); - if (preventFullScreen != newPreventFullScreen) - doReset = true; - preventFullScreen = newPreventFullScreen; #endif return !doReset && sets().getBool("Enabled"); @@ -182,6 +168,7 @@ void OpenGL2Writer::writeVideo(const VideoFrame &videoFrame) } drawable->updateGL(drawable->sphericalView); } + void OpenGL2Writer::writeOSD(const QList &osds) { QMutexLocker mL(&drawable->osdMutex); @@ -205,8 +192,6 @@ QString OpenGL2Writer::name() const QString glStr = drawable->glVer ? QString("%1.%2").arg(drawable->glVer / 10).arg(drawable->glVer % 10) : "2"; if (drawable->hwAccellnterface) glStr += " " + drawable->hwAccellnterface->name(); - if (useRtt) - glStr += " (render-to-texture)"; #ifdef OPENGL_ES2 return "OpenGL|ES " + glStr; #else @@ -216,27 +201,15 @@ QString OpenGL2Writer::name() const bool OpenGL2Writer::open() { - static const QString platformName = QGuiApplication::platformName(); - useRtt = platformName.startsWith("wayland") || platformName == "android" || forceRtt; - if (useRtt) - { - //Don't use rtt when videoDock has native window - const QWidget *videoDock = QMPlay2Core.getVideoDock(); - useRtt = !videoDock->internalWinId() || (videoDock == videoDock->window()); - } - if (useRtt) - drawable = new OpenGL2Widget; - else - drawable = new OpenGL2Window; + drawable = new OpenGL2Widget; drawable->hwAccellnterface = m_hwAccelInterface; -#ifdef Q_OS_WIN - drawable->preventFullScreen = preventFullScreen; -#endif drawable->allowPBO = allowPBO; drawable->hqScaling = m_hqScaling; if (drawable->testGL()) { +#ifdef VSYNC_SETTINGS drawable->setVSync(vSync); +#endif bool hasBrightness = false, hasContrast = false, hasSharpness = false; if (!drawable->videoAdjustmentKeys.isEmpty()) { diff --git a/src/modules/OpenGL2/OpenGL2Writer.hpp b/src/modules/OpenGL2/OpenGL2Writer.hpp index 7881da78..501ddcaa 100644 --- a/src/modules/OpenGL2/OpenGL2Writer.hpp +++ b/src/modules/OpenGL2/OpenGL2Writer.hpp @@ -27,8 +27,10 @@ class OpenGL2Common; class OpenGL2Writer final : public VideoWriter { Q_DECLARE_TR_FUNCTIONS(OpenGL2Writer) + public: OpenGL2Writer(Module &); + private: ~OpenGL2Writer(); @@ -58,10 +60,8 @@ private: OpenGL2Common *drawable; bool allowPBO; bool m_hqScaling = false; - bool forceRtt, useRtt; +#ifdef VSYNC_SETTINGS bool vSync; -#ifdef Q_OS_WIN - bool preventFullScreen; #endif }; diff --git a/src/modules/PortAudio/3rdparty/CoreAudio/AudioDevice.h b/src/modules/PortAudio/3rdparty/CoreAudio/AudioDevice.h index 8ddf0b10..515006f0 100644 --- a/src/modules/PortAudio/3rdparty/CoreAudio/AudioDevice.h +++ b/src/modules/PortAudio/3rdparty/CoreAudio/AudioDevice.h @@ -51,17 +51,12 @@ #include #include +#include #include #ifndef DEPRECATED_LISTENER_API -# if !defined(__MAC_10_11) -# define __MAC_10_11 101100 -# endif -# ifndef QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE -# define QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE -# endif -# if !QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_11) +# if MAC_OS_X_VERSION_MIN_REQUIRED < 101100 # define DEPRECATED_LISTENER_API # warning "Using the deprecated PropertyListener API; at least it works" # endif diff --git a/src/modules/PortAudio/PortAudioWriter.cpp b/src/modules/PortAudio/PortAudioWriter.cpp index aead169e..736945e7 100644 --- a/src/modules/PortAudio/PortAudioWriter.cpp +++ b/src/modules/PortAudio/PortAudioWriter.cpp @@ -23,6 +23,10 @@ #define MMSYSERR_NODRIVER 6 #endif +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) + #define QStringLiteral QString::fromUtf8 +#endif + #ifdef Q_OS_MACOS #include "3rdparty/CoreAudio/AudioDeviceList.h" #include "3rdparty/CoreAudio/AudioDevice.h" @@ -47,6 +51,7 @@ PortAudioWriter::PortAudioWriter(Module &module) : SetModule(module); } + PortAudioWriter::~PortAudioWriter() { #ifdef Q_OS_MACOS @@ -130,6 +135,7 @@ bool PortAudioWriter::processParams(bool *paramsCorrected) return readyWrite(); } + qint64 PortAudioWriter::write(const QByteArray &arr) { if (!readyWrite()) @@ -192,6 +198,7 @@ qint64 PortAudioWriter::write(const QByteArray &arr) return arr.size(); } + void PortAudioWriter::pause() { if (readyWrite()) @@ -253,6 +260,7 @@ bool PortAudioWriter::openStream() } return false; } + bool PortAudioWriter::startStream() { const PaError e = Pa_StartStream(stream); @@ -266,6 +274,7 @@ bool PortAudioWriter::startStream() } return true; } + inline bool PortAudioWriter::writeStream(const QByteArray &arr) { const PaError e = Pa_WriteStream(stream, arr.constData(), arr.size() / outputParameters.channelCount / sizeof(float)); @@ -288,6 +297,7 @@ inline bool PortAudioWriter::writeStream(const QByteArray &arr) } return (e != paUnanticipatedHostError); } + qint64 PortAudioWriter::playbackError() { QMPlay2Core.logError("PortAudio :: " + tr("Playback error")); @@ -302,6 +312,7 @@ bool PortAudioWriter::isNoDriverError() const return errorInfo && errorInfo->hostApiType == paMME && errorInfo->errorCode == MMSYSERR_NODRIVER; } #endif + bool PortAudioWriter::reopenStream() { Pa_CloseStream(stream); diff --git a/src/modules/PortAudio/PortAudioWriter.hpp b/src/modules/PortAudio/PortAudioWriter.hpp index 20ff55d8..b458e9ca 100644 --- a/src/modules/PortAudio/PortAudioWriter.hpp +++ b/src/modules/PortAudio/PortAudioWriter.hpp @@ -29,25 +29,25 @@ class AudioDevice; #endif -class PortAudioWriter final : public Writer +class PortAudioWriter : public Writer { Q_DECLARE_TR_FUNCTIONS(PortAudioWriter) public: PortAudioWriter(Module &); private: - ~PortAudioWriter(); + ~PortAudioWriter() final; - bool set() override; + bool set() override final; - bool readyWrite() const override; + bool readyWrite() const override final; - bool processParams(bool *paramsCorrected) override; - qint64 write(const QByteArray &) override; - void pause() override; + bool processParams(bool *paramsCorrected) override final; + qint64 write(const QByteArray &) override final; + void pause() override final; - QString name() const override; + QString name() const override final; - bool open() override; + bool open() override final; /**/ diff --git a/src/modules/PulseAudio/PulseAudio.hpp b/src/modules/PulseAudio/PulseAudio.hpp index 006f749d..e59f7046 100644 --- a/src/modules/PulseAudio/PulseAudio.hpp +++ b/src/modules/PulseAudio/PulseAudio.hpp @@ -20,15 +20,15 @@ #include -class PulseAudio final : public Module +class PulseAudio : public Module { public: PulseAudio(); private: - QList getModulesInfo(const bool) const override; - void *createInstance(const QString &) override; + QList getModulesInfo(const bool) const override final; + void *createInstance(const QString &) override final; - SettingsWidget *getSettingsWidget() override; + SettingsWidget *getSettingsWidget() override final; }; /**/ @@ -38,13 +38,13 @@ private: class QDoubleSpinBox; class QCheckBox; -class ModuleSettingsWidget final : public Module::SettingsWidget +class ModuleSettingsWidget : public Module::SettingsWidget { Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget) public: ModuleSettingsWidget(Module &); private: - void saveSettings() override; + void saveSettings() override final; QCheckBox *enabledB; QDoubleSpinBox *delayB; diff --git a/src/modules/PulseAudio/PulseAudioWriter.hpp b/src/modules/PulseAudio/PulseAudioWriter.hpp index 8ba6eb64..4d816b29 100644 --- a/src/modules/PulseAudio/PulseAudioWriter.hpp +++ b/src/modules/PulseAudio/PulseAudioWriter.hpp @@ -23,23 +23,23 @@ #include -class PulseAudioWriter final : public Writer +class PulseAudioWriter : public Writer { Q_DECLARE_TR_FUNCTIONS(PulseAudioWriter) public: PulseAudioWriter(Module &); - ~PulseAudioWriter(); + ~PulseAudioWriter() final; private: - bool set() override; + bool set() override final; - bool readyWrite() const override; + bool readyWrite() const override final; - bool processParams(bool *paramsCorrected) override; - qint64 write(const QByteArray &) override; + bool processParams(bool *paramsCorrected) override final; + qint64 write(const QByteArray &) override final; - QString name() const override; + QString name() const override final; - bool open() override; + bool open() override final; /**/ diff --git a/src/modules/QPainter/QPainter.hpp b/src/modules/QPainter/QPainter.hpp index 7257c675..b142be81 100644 --- a/src/modules/QPainter/QPainter.hpp +++ b/src/modules/QPainter/QPainter.hpp @@ -20,15 +20,15 @@ #include -class QPainterSW final : public Module +class QPainterSW : public Module { public: QPainterSW(); private: - QList getModulesInfo(const bool) const override; - void *createInstance(const QString &) override; + QList getModulesInfo(const bool) const override final; + void *createInstance(const QString &) override final; - SettingsWidget *getSettingsWidget() override; + SettingsWidget *getSettingsWidget() override final; }; /**/ @@ -37,13 +37,13 @@ private: class QCheckBox; -class ModuleSettingsWidget final : public Module::SettingsWidget +class ModuleSettingsWidget : public Module::SettingsWidget { Q_DECLARE_TR_FUNCTIONS(ModuleSettingsWidget) public: ModuleSettingsWidget(Module &); private: - void saveSettings() override; + void saveSettings() override final; QCheckBox *enabledB; }; diff --git a/src/modules/QPainter/QPainterWriter.cpp b/src/modules/QPainter/QPainterWriter.cpp index 92a3225f..fcb11587 100644 --- a/src/modules/QPainter/QPainterWriter.cpp +++ b/src/modules/QPainter/QPainterWriter.cpp @@ -72,9 +72,9 @@ void Drawable::draw(const VideoFrame &newVideoFrame, bool canRepaint, bool entir void Drawable::resizeEvent(QResizeEvent *e) { - const qreal dpr = devicePixelRatioF(); + const qreal scale = QMPlay2Core.getVideoDevicePixelRatio(); Functions::getImageSize(writer.aspect_ratio, writer.zoom, width(), height(), W, H, &X, &Y); - Functions::getImageSize(writer.aspect_ratio, writer.zoom, width() * dpr, height() * dpr, imgW, imgH); + Functions::getImageSize(writer.aspect_ratio, writer.zoom, width() * scale, height() * scale, imgW, imgH); imgW = Functions::aligned(imgW, 8); imgScaler.destroy(); @@ -82,6 +82,7 @@ void Drawable::resizeEvent(QResizeEvent *e) draw(VideoFrame(), e ? false : true, true); } + void Drawable::paintEvent(QPaintEvent *) { QPainter p(this); @@ -94,14 +95,15 @@ void Drawable::paintEvent(QPaintEvent *) osd_mutex.lock(); if (!osd_list.isEmpty()) { - const qreal dpr = devicePixelRatioF(); - if (!qFuzzyCompare(dpr, 1.0)) - p.scale(1.0 / dpr, 1.0 / dpr); + const qreal scale = QMPlay2Core.getVideoDevicePixelRatio(); + if (!qFuzzyCompare(scale, 1.0)) + p.scale(1.0 / scale, 1.0 / scale); p.setClipRect(0, 0, imgW, imgH); - Functions::paintOSD(true, osd_list, W * dpr / writer.outW, H * dpr / writer.outH, p); + Functions::paintOSD(true, osd_list, (qreal)W / writer.outW, (qreal)H / writer.outH, p); } osd_mutex.unlock(); } + bool Drawable::event(QEvent *e) { /* Pass gesture and touch event to the parent */ diff --git a/src/modules/QPainter/QPainterWriter.hpp b/src/modules/QPainter/QPainterWriter.hpp index f5e0a680..61991e4e 100644 --- a/src/modules/QPainter/QPainterWriter.hpp +++ b/src/modules/QPainter/QPainterWriter.hpp @@ -27,23 +27,23 @@ class QPainterWriter; class QMPlay2OSD; -class Drawable final : public QWidget +class Drawable : public QWidget { public: Drawable(class QPainterWriter &); - ~Drawable(); + ~Drawable() final; void draw(const VideoFrame &newVideoFrame, bool, bool); - void resizeEvent(QResizeEvent *) override; + void resizeEvent(QResizeEvent *) override final; VideoFrame videoFrame; QList osd_list; int Brightness, Contrast; QMutex osd_mutex; private: - void paintEvent(QPaintEvent *) override; - bool event(QEvent *) override; + void paintEvent(QPaintEvent *) override final; + bool event(QEvent *) override final; int X, Y, W, H, imgW, imgH; QPainterWriter &writer; @@ -54,28 +54,28 @@ private: /**/ -class QPainterWriter final : public VideoWriter +class QPainterWriter : public VideoWriter { friend class Drawable; public: QPainterWriter(Module &); private: - ~QPainterWriter(); + ~QPainterWriter() final; - bool set() override; + bool set() override final; - bool readyWrite() const override; + bool readyWrite() const override final; - bool processParams(bool *paramsCorrected) override; + bool processParams(bool *paramsCorrected) override final; - QMPlay2PixelFormats supportedPixelFormats() const override; + QMPlay2PixelFormats supportedPixelFormats() const override final; - void writeVideo(const VideoFrame &videoFrame) override; - void writeOSD(const QList &) override; + void writeVideo(const VideoFrame &videoFrame) override final; + void writeOSD(const QList &) override final; - QString name() const override; + QString name() const override final; - bool open() override; + bool open() override final; /**/ diff --git a/src/modules/Subtitles/Classic.cpp b/src/modules/Subtitles/Classic.cpp index d5c4d003..cb8f8bab 100644 --- a/src/modules/Subtitles/Classic.cpp +++ b/src/modules/Subtitles/Classic.cpp @@ -167,7 +167,7 @@ bool Classic::toASS(const QByteArray &txt, LibASS *ass, double fps) if (use_mDVD_FPS && (s == 0 || s == 1)) { use_mDVD_FPS = false; - const double newFPS = sub.midRef(0, 6).toDouble(); + const double newFPS = sub.mid(0, 6).toDouble(); if (newFPS > 0.0 && newFPS < 100.0) { fps = newFPS; diff --git a/src/modules/Subtitles/Classic.hpp b/src/modules/Subtitles/Classic.hpp index b4270f7f..db15b93d 100644 --- a/src/modules/Subtitles/Classic.hpp +++ b/src/modules/Subtitles/Classic.hpp @@ -20,12 +20,12 @@ #include -class Classic final : public SubsDec +class Classic : public SubsDec { public: Classic(bool, double); private: - bool toASS(const QByteArray &, class LibASS *, double) override; + bool toASS(const QByteArray &, class LibASS *, double) override final; /**/ diff --git a/src/qmplay2/CMakeLists.txt b/src/qmplay2/CMakeLists.txt index c70bd00c..d7509e9a 100644 --- a/src/qmplay2/CMakeLists.txt +++ b/src/qmplay2/CMakeLists.txt @@ -112,7 +112,7 @@ if(USE_FREEDESKTOP_NOTIFICATIONS) qt5_add_dbus_interface(QMPLAY2_SRC org.freedesktop.Notifications.xml notifications_interface) add_definitions(-DNOTIFIES_FREEDESKTOP) set(DBUS Qt5::DBus) -elseif(APPLE) +elseif(APPLE AND NOT USE_QT4) list(APPEND QMPLAY2_HDR headers/NotifiesMacOS.hpp) list(APPEND QMPLAY2_SRC NotifiesMacOS.mm) find_package(Qt5MacExtras REQUIRED) @@ -195,11 +195,18 @@ add_library(${PROJECT_NAME} SHARED set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") if(APPLE) - target_link_libraries(${PROJECT_NAME} - PRIVATE - Qt5::MacExtras - ${APPKIT_LIBRARY} + if(USE_QT4) + target_link_libraries(${PROJECT_NAME} + PRIVATE + ${APPKIT_LIBRARY} ) + else() + target_link_libraries(${PROJECT_NAME} + PRIVATE + Qt5::MacExtras + ${APPKIT_LIBRARY} + ) + endif() endif() if(WIN32) @@ -229,14 +236,30 @@ else() endif() endif() +if(USE_QT4) + add_definitions(-I@prefix@/include/QJson4) + target_link_libraries(${PROJECT_NAME} + PUBLIC + Qt4::QtCore + Qt4::QtGui + Qt4::QtSvg + QJson4 + ) +else() + target_link_libraries(${PROJECT_NAME} + PUBLIC + Qt5::Core + Qt5::Gui + Qt5::Widgets + Qt5::Svg + PRIVATE + ${QML} + ) +endif() + target_link_libraries(${PROJECT_NAME} - PUBLIC - Qt5::Core - Qt5::Gui - Qt5::Widgets PRIVATE ${DBUS} - ${QML} ${LIBQMPLAY2_LIBS} ) diff --git a/src/qmplay2/Functions.cpp b/src/qmplay2/Functions.cpp index 572952e2..554b0100 100644 --- a/src/qmplay2/Functions.cpp +++ b/src/qmplay2/Functions.cpp @@ -36,12 +36,16 @@ #include #include #include -#include +#include #include #include #include #include +#ifdef Q_WS_X11 +#include +#endif + extern "C" { #include @@ -58,14 +62,16 @@ static inline void swapArray(quint8 *a, quint8 *b, int size) memcpy(b, t, size); } -static inline QWindow *getNativeWindow(const QWidget *w) +static inline WId getNativeWindow(const QWidget *w) { if (w) { - if (QWidget *winW = w->window()) - return winW->windowHandle(); + if (const QWidget *winW = w->window()) + { + return winW->winId(); + } } - return nullptr; + return 0; } /**/ @@ -301,7 +307,8 @@ QPixmap Functions::getPixmapFromIcon(const QIcon &icon, QSize size, QWidget *w) imgSize.scale(size, size.isEmpty() ? Qt::KeepAspectRatioByExpanding : Qt::KeepAspectRatio); } - return icon.pixmap(getNativeWindow(w), imgSize); + Q_UNUSED(w) + return icon.pixmap(imgSize); } void Functions::drawPixmap(QPainter &p, const QPixmap &pixmap, const QWidget *w, Qt::TransformationMode transformationMode, Qt::AspectRatioMode aRatioMode, QSize size, qreal scale) { @@ -347,14 +354,13 @@ void Functions::drawPixmap(QPainter &p, const QPixmap &pixmap, const QWidget *w, pixmapSize = pixmap.size(); } - const qreal dpr = w->devicePixelRatioF(); + qreal devicePixelRatio = QMPlay2Core.getVideoDevicePixelRatio(); - pixmapToDraw = pixmapToDraw.scaled(pixmapSize * dpr, Qt::IgnoreAspectRatio, transformationMode); - pixmapToDraw.setDevicePixelRatio(dpr); + pixmapToDraw = pixmapToDraw.scaled(pixmapSize * devicePixelRatio, Qt::IgnoreAspectRatio, transformationMode); const QPoint pixmapPos { - size.width() / 2 - int(pixmapToDraw.width() / (dpr * 2)), - size.height() / 2 - int(pixmapToDraw.height() / (dpr * 2)) + size.width() / 2 - int(pixmapToDraw.width() / (devicePixelRatio * 2)), + size.height() / 2 - int(pixmapToDraw.height() / (devicePixelRatio * 2)) }; p.drawPixmap(pixmapPos, pixmapToDraw); @@ -405,8 +411,8 @@ void Functions::paintOSD(bool rgbSwapped, const QList &osd_l for (int j = 0; j < osd->imageCount(); j++) { const QMPlay2OSD::Image &img = osd->getImage(j); - const QImage qImg = QImage((const uchar *)img.data.constData(), img.rect.width(), img.rect.height(), rgbSwapped ? QImage::Format_RGBA8888 : QImage::Format_ARGB32); - painter.drawImage(img.rect.topLeft(), qImg); + const QImage qImg = QImage((uchar *)img.data.data(), img.rect.width(), img.rect.height(), QImage::Format_ARGB32); + painter.drawImage(img.rect.topLeft(), rgbSwapped ? qImg.rgbSwapped() : qImg); } if (osd->needsRescale()) painter.restore(); diff --git a/src/qmplay2/LineEdit.cpp b/src/qmplay2/LineEdit.cpp index 6f94853d..72176e1b 100644 --- a/src/qmplay2/LineEdit.cpp +++ b/src/qmplay2/LineEdit.cpp @@ -19,19 +19,58 @@ #include #include +#include -#include +#include + +LineEditButton::LineEditButton() +{ + const QSize iconSize(16, 16); + setToolTip(tr("Clear")); + setPixmap(Functions::getPixmapFromIcon(QMPlay2Core.getIconFromTheme("edit-clear"), iconSize, this)); + resize(iconSize); + setCursor(Qt::ArrowCursor); +} + +void LineEditButton::mousePressEvent(QMouseEvent *e) +{ + if (e->button() & Qt::LeftButton) + emit clicked(); +} + +/**/ LineEdit::LineEdit(QWidget *parent) : QLineEdit(parent) { - QAction *clearAct = addAction(QMPlay2Core.getIconFromTheme("edit-clear"), TrailingPosition); - connect(clearAct, &QAction::triggered, this, &LineEdit::clearText); - connect(this, &LineEdit::textChanged, this, [=](const QString &text) { - clearAct->setVisible(!text.isEmpty()); - }); - clearAct->setToolTip(tr("Clear")); - clearAct->setVisible(false); + connect(this, SIGNAL(textChanged(const QString &)), this, SLOT(textChangedSlot(const QString &))); + connect(&b, SIGNAL(clicked()), this, SLOT(clearText())); + setMinimumWidth(b.width() * 2.5); + setTextMargins(0, 0, b.width() * 1.5, 0); + b.setParent(this); + b.hide(); +} + +void LineEdit::resizeEvent(QResizeEvent *e) +{ + b.move(e->size().width() - b.width() * 1.5, e->size().height() / 2 - b.height() / 2); +} + +void LineEdit::mousePressEvent(QMouseEvent *e) +{ + if (!b.underMouse()) + QLineEdit::mousePressEvent(e); +} + +void LineEdit::mouseMoveEvent(QMouseEvent *e) +{ + if (!b.underMouse()) + QLineEdit::mouseMoveEvent(e); +} + +void LineEdit::textChangedSlot(const QString &str) +{ + b.setVisible(!str.isEmpty()); } void LineEdit::clearText() diff --git a/src/qmplay2/QMPlay2Core.cpp b/src/qmplay2/QMPlay2Core.cpp index 55f775da..56a04b28 100644 --- a/src/qmplay2/QMPlay2Core.cpp +++ b/src/qmplay2/QMPlay2Core.cpp @@ -28,7 +28,6 @@ #include #include -#include #include #include #include @@ -36,19 +35,19 @@ #include #include #include -#include #include #include #if defined Q_OS_WIN #include #include -#elif defined Q_OS_MACOS - #include #endif #include #include +#define QT_VERSION_MAJOR 4 +#define QT_VERSION_MINOR 8 // Qt 4.8.x is the oldest supported Qt version + extern "C" { #include @@ -58,19 +57,8 @@ extern "C" /**/ -Q_LOGGING_CATEGORY(ffmpeglog, "FFmpegLog") - static void avQMPlay2LogHandler(void *avcl, int level, const char *fmt, va_list vl) { - if (level <= AV_LOG_FATAL) - { - const QByteArray msg = QString::vasprintf(fmt, vl).trimmed().toUtf8(); - qCCritical(ffmpeglog) << msg.constData(); - } - else - { - av_log_default_callback(avcl, level, fmt, vl); - } } /**/ @@ -109,6 +97,8 @@ QMPlay2CoreClass::QMPlay2CoreClass() { qmplay2Core = this; + videoDevicePixelRatio = 1.0; + QFile f(":/Languages.txt"); if (f.open(QFile::ReadOnly)) { @@ -203,8 +193,6 @@ void QMPlay2CoreClass::init(bool loadModules, bool modulesInSubdirs, const QStri { #if defined(Q_OS_WIN) settingsDir = QFileInfo(QSettings(QSettings::IniFormat, QSettings::UserScope, QString()).fileName()).absolutePath() + "/QMPlay2/"; -#elif defined(Q_OS_MACOS) - settingsDir = Functions::cleanPath(QStandardPaths::standardLocations(QStandardPaths::DataLocation).value(0, settingsDir)); #else settingsDir = QDir::homePath() + "/.qmplay2/"; #endif @@ -414,9 +402,8 @@ QStringList QMPlay2CoreClass::getModules(const QString &type, int typeLen) const return modules + availableModules; } -qreal QMPlay2CoreClass::getVideoDevicePixelRatio() const +void QMPlay2CoreClass::setVideoDevicePixelRatio() { - return getVideoDock()->devicePixelRatioF(); } QIcon QMPlay2CoreClass::getIconFromTheme(const QString &iconName, const QIcon &fallback) diff --git a/src/qmplay2/QMPlay2OSD.cpp b/src/qmplay2/QMPlay2OSD.cpp index d77bc2ce..7c0ff030 100644 --- a/src/qmplay2/QMPlay2OSD.cpp +++ b/src/qmplay2/QMPlay2OSD.cpp @@ -17,13 +17,13 @@ */ #include +#include -#include -static QAtomicInteger g_id; +static QAtomicInt g_id; void QMPlay2OSD::genId() { - m_id = ++g_id; + m_id = g_id.fetchAndAddOrdered(1) + 1; } void QMPlay2OSD::clear(bool all) diff --git a/src/qmplay2/Writer.cpp b/src/qmplay2/Writer.cpp index 5eb05afd..a09bda50 100644 --- a/src/qmplay2/Writer.cpp +++ b/src/qmplay2/Writer.cpp @@ -20,7 +20,7 @@ #include -#include +#include #include #include @@ -52,8 +52,10 @@ class QMPlay2FileWriter : public IODeviceWriter { ~QMPlay2FileWriter() { - if (auto f = static_cast(m_io.get())) - f->commit(); + if (auto f = static_cast(m_io.get())) + { + f->close(); + } } QString name() const override final @@ -63,7 +65,7 @@ class QMPlay2FileWriter : public IODeviceWriter bool open() override final { - m_io.reset(new QSaveFile(getUrl().mid(7))); + m_io.reset(new QFile(getUrl().mid(7))); return IODeviceWriter::open(); } }; diff --git a/src/qmplay2/YouTubeDL.cpp b/src/qmplay2/YouTubeDL.cpp index f05e615b..8e3e757d 100644 --- a/src/qmplay2/YouTubeDL.cpp +++ b/src/qmplay2/YouTubeDL.cpp @@ -23,26 +23,33 @@ #include #include -#include -#include -#include -#include +#include #include #include #include +#include +#include +#include + +/* Avoid downloading yt-dlp, it fails to work correctly. */ +#ifndef BUNDLED_YTDLP +#define BUNDLED_YTDLP 0 +#endif + constexpr const char *g_name = "YouTubeDL"; static bool g_mustUpdate = true; static QMutex g_mutex(QMutex::Recursive); QString YouTubeDL::getFilePath() { - return QMPlay2Core.getSettingsDir() + "youtube-dl" + return QMPlay2Core.getSettingsDir() + "yt-dlp" #ifdef Q_OS_WIN ".exe" #endif ; } + QStringList YouTubeDL::getCommonArgs() { QStringList commonArgs { @@ -164,7 +171,7 @@ QStringList YouTubeDL::exec(const QString &url, const QStringList &args, QString startProcess(processArgs); } - if (!m_process.waitForFinished() || m_aborted) + if (!m_process.waitForFinished(-1) || m_aborted) return {}; QStringList result; @@ -181,7 +188,7 @@ QStringList YouTubeDL::exec(const QString &url, const QStringList &args, QString } else { - result = result.constFirst().split('\n', QString::SkipEmptyParts); + result = result[0].split('\n', QString::SkipEmptyParts); // Verify if URLs has printable characters, because sometimes we // can get binary garbage at output (especially on Openload). @@ -267,7 +274,7 @@ bool YouTubeDL::prepare() if (m_aborted) return false; } - +#if BUNDLED_YTDLP if (!QFileInfo(m_ytDlPath).exists()) { if (!download()) @@ -297,7 +304,7 @@ bool YouTubeDL::prepare() } ensureExecutable(); - +#endif g_mutex.unlock(); return true; } @@ -305,8 +312,8 @@ bool YouTubeDL::prepare() bool YouTubeDL::download() { // Mutex must be locked here - - const QString downloadUrl = "https://yt-dl.org/downloads/latest/youtube-dl" +#if BUNDLED_YTDLP + const QString downloadUrl = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp" #ifdef Q_OS_WIN ".exe" #endif @@ -354,11 +361,13 @@ bool YouTubeDL::download() QMPlay2Core.setWorking(false); return false; +#endif } + bool YouTubeDL::update() { // Mutex must be locked here - +#if BUNDLED_YTDLP qDebug() << "\"youtube-dl\" updates will be checked"; QMPlay2Core.setWorking(true); @@ -390,29 +399,11 @@ bool YouTubeDL::update() { qCritical() << "youtube-dl update failed:" << updateOutput; } - else if (m_process.exitCode() == 0 && !updateOutput.contains("up-to-date")) + else if (m_process.exitCode() == 0 && !updateOutput.contains(QRegExp(R"(up\Wto\Wdate)"))) { -#ifdef Q_OS_WIN - const QString updatedFile = m_ytDlPath + ".new"; - QFile::remove(Functions::filePath(m_ytDlPath) + "youtube-dl-updater.bat"); - if (QFile::exists(updatedFile)) - { - Functions::s_wait(0.2); // Wait 200 ms to be sure that file is closed - QFile::remove(m_ytDlPath); - if (QFile::rename(updatedFile, m_ytDlPath)) - { -#endif - QMPlay2Core.setWorking(false); - emit QMPlay2Core.sendMessage(tr("\"youtube-dl\" has been successfully updated!"), g_name); - return true; -#ifdef Q_OS_WIN - } - } - else - { - qDebug() << "Updated youtube-dl file:" + updatedFile + "not found!"; - } -#endif + QMPlay2Core.setWorking(false); + emit QMPlay2Core.sendMessage(tr("\"youtube-dl\" has been successfully updated!"), g_name); + return {}; } } else if (updating && m_aborted) @@ -421,6 +412,7 @@ bool YouTubeDL::update() } QMPlay2Core.setWorking(false); +#endif return true; } @@ -460,24 +452,7 @@ void YouTubeDL::startProcess(QStringList args) if (shebang.startsWith("#!") && idx > -1) { const auto pythonCmd = shebang.mid(idx); - if (!QStandardPaths::findExecutable(pythonCmd).endsWith(pythonCmd)) - { - QStringList pythonCmdsToCheck { - "python", - "python2", - "python3", - }; - pythonCmdsToCheck.removeOne(pythonCmd); - for (auto &&pythonCmd : asConst(pythonCmdsToCheck)) - { - if (QStandardPaths::findExecutable(pythonCmd).endsWith(pythonCmd)) - { - args.prepend(program); - program = pythonCmd; - break; - } - } - } + // We do not want to fetch yt-dlp anyway. } ytDlFile.close(); } diff --git a/src/qmplay2/headers/ColorButton.hpp b/src/qmplay2/headers/ColorButton.hpp index 75aa784a..a9b8c5e0 100644 --- a/src/qmplay2/headers/ColorButton.hpp +++ b/src/qmplay2/headers/ColorButton.hpp @@ -22,7 +22,7 @@ #include -class QMPLAY2SHAREDLIB_EXPORT ColorButton final : public QPushButton +class ColorButton : public QPushButton { Q_OBJECT public: @@ -34,7 +34,7 @@ public: return m_color; } protected: - void paintEvent(QPaintEvent *) override; + void paintEvent(QPaintEvent *) override final; private: QColor m_color; bool m_alphaChannel; diff --git a/src/qmplay2/headers/IPC.hpp b/src/qmplay2/headers/IPC.hpp index 4766b238..8ebd4b3c 100644 --- a/src/qmplay2/headers/IPC.hpp +++ b/src/qmplay2/headers/IPC.hpp @@ -31,18 +31,18 @@ class IPCServerPriv; class QSocketNotifier; -class QMPLAY2SHAREDLIB_EXPORT IPCSocket final : public QIODevice +class IPCSocket : public QIODevice { Q_OBJECT friend class IPCServer; public: IPCSocket(const QString &fileName, QObject *parent = nullptr); - ~IPCSocket(); + ~IPCSocket() final; bool isConnected() const; - bool open(OpenMode mode) override; - void close() override; + bool open(OpenMode mode) override final; + void close() override final; private slots: void socketReadActive(); @@ -54,20 +54,20 @@ private: IPCSocket(int socket, QObject *parent); #endif - qint64 readData(char *data, qint64 maxSize) override; - qint64 writeData(const char *data, qint64 maxSize) override; + qint64 readData(char *data, qint64 maxSize) override final; + qint64 writeData(const char *data, qint64 maxSize) override final; IPCSocketPriv *m_priv; }; /**/ -class QMPLAY2SHAREDLIB_EXPORT IPCServer final : public QObject +class IPCServer : public QObject { Q_OBJECT public: IPCServer(const QString &fileName, QObject *parent = nullptr); - ~IPCServer(); + ~IPCServer() final; bool listen(); void close(); diff --git a/src/qmplay2/headers/InDockW.hpp b/src/qmplay2/headers/InDockW.hpp index d4b51c50..67504fc5 100644 --- a/src/qmplay2/headers/InDockW.hpp +++ b/src/qmplay2/headers/InDockW.hpp @@ -23,7 +23,7 @@ #include #include -class QMPLAY2SHAREDLIB_EXPORT InDockW final : public QWidget +class InDockW : public QWidget { Q_OBJECT public: @@ -41,12 +41,12 @@ private: QPointer w; private slots: void wallpaperChanged(bool hasWallpaper, double alpha); -public: +public slots: void setWidget(QWidget *newW); protected: - void resizeEvent(QResizeEvent *) override; - void paintEvent(QPaintEvent *) override; - bool event(QEvent *) override; + void resizeEvent(QResizeEvent *) override final; + void paintEvent(QPaintEvent *) override final; + bool event(QEvent *) override final; signals: void resized(int, int); void itemDropped(const QString &); diff --git a/src/qmplay2/headers/LineEdit.hpp b/src/qmplay2/headers/LineEdit.hpp index 676be13e..b7606e40 100644 --- a/src/qmplay2/headers/LineEdit.hpp +++ b/src/qmplay2/headers/LineEdit.hpp @@ -21,16 +21,36 @@ #include #include +#include -class QMPLAY2SHAREDLIB_EXPORT LineEdit final : public QLineEdit +class LineEditButton : public QLabel { Q_OBJECT +public: + LineEditButton(); +private: + void mousePressEvent(QMouseEvent *) override final; +signals: + void clicked(); +}; + +/**/ +class LineEdit : public QLineEdit +{ + Q_OBJECT public: LineEdit(QWidget *parent = nullptr); - void clearText(); +private: + void resizeEvent(QResizeEvent *) override final; + void mousePressEvent(QMouseEvent *) override final; + void mouseMoveEvent(QMouseEvent *) override final; + LineEditButton b; +private slots: + void textChangedSlot(const QString &); + void clearText(); signals: void clearButtonClicked(); }; diff --git a/src/qmplay2/headers/MkvMuxer.hpp b/src/qmplay2/headers/MkvMuxer.hpp index f0f71a96..a49f5da0 100644 --- a/src/qmplay2/headers/MkvMuxer.hpp +++ b/src/qmplay2/headers/MkvMuxer.hpp @@ -26,7 +26,7 @@ struct AVFormatContext; class StreamInfo; struct Packet; -class QMPLAY2SHAREDLIB_EXPORT MkvMuxer +class MkvMuxer { MkvMuxer(const MkvMuxer &) = delete; MkvMuxer &operator =(const MkvMuxer &) = delete; diff --git a/src/qmplay2/headers/Module.hpp b/src/qmplay2/headers/Module.hpp index 5fee6266..442ef070 100644 --- a/src/qmplay2/headers/Module.hpp +++ b/src/qmplay2/headers/Module.hpp @@ -27,7 +27,7 @@ class ModuleCommon; -class QMPLAY2SHAREDLIB_EXPORT Module : public Settings +class Module : public Settings { friend class ModuleCommon; public: diff --git a/src/qmplay2/headers/ModuleCommon.hpp b/src/qmplay2/headers/ModuleCommon.hpp index 89ea8782..42bc0c40 100644 --- a/src/qmplay2/headers/ModuleCommon.hpp +++ b/src/qmplay2/headers/ModuleCommon.hpp @@ -20,7 +20,7 @@ #include -class QMPLAY2SHAREDLIB_EXPORT ModuleCommon +class ModuleCommon { public: virtual bool set(); diff --git a/src/qmplay2/headers/ModuleParams.hpp b/src/qmplay2/headers/ModuleParams.hpp index d351c08e..ebc69315 100644 --- a/src/qmplay2/headers/ModuleParams.hpp +++ b/src/qmplay2/headers/ModuleParams.hpp @@ -24,7 +24,7 @@ #include #include -class QMPLAY2SHAREDLIB_EXPORT ModuleParams +class ModuleParams { Q_DISABLE_COPY(ModuleParams) public: diff --git a/src/qmplay2/headers/NetworkAccess.hpp b/src/qmplay2/headers/NetworkAccess.hpp index fef728fb..1da4950c 100644 --- a/src/qmplay2/headers/NetworkAccess.hpp +++ b/src/qmplay2/headers/NetworkAccess.hpp @@ -26,7 +26,7 @@ class NetworkReplyPriv; struct NetworkAccessParams; -class QMPLAY2SHAREDLIB_EXPORT NetworkReply final : public QObject, public BasicIO +class NetworkReply : public QObject, public BasicIO { Q_OBJECT @@ -49,7 +49,6 @@ public: Download, Aborted }; - Q_ENUM(Error) enum class Wait { @@ -57,13 +56,12 @@ public: Timeout, Error }; - Q_ENUM(Wait) - ~NetworkReply(); + ~NetworkReply() final; QString url() const; - void abort() override; + void abort() override final; bool hasError() const; Error error() const; @@ -86,7 +84,7 @@ private: /**/ -class QMPLAY2SHAREDLIB_EXPORT NetworkAccess : public QObject +class NetworkAccess : public QObject { Q_OBJECT diff --git a/src/qmplay2/headers/NetworkAccessJS.hpp b/src/qmplay2/headers/NetworkAccessJS.hpp index 8cc77097..dd20c4e0 100644 --- a/src/qmplay2/headers/NetworkAccessJS.hpp +++ b/src/qmplay2/headers/NetworkAccessJS.hpp @@ -8,7 +8,7 @@ class NetworkAccess; -class QMPLAY2SHAREDLIB_EXPORT NetworkAccessJS : public QObject +class NetworkAccessJS : public QObject { Q_OBJECT diff --git a/src/qmplay2/headers/PixelFormats.hpp b/src/qmplay2/headers/PixelFormats.hpp index 52e312db..64d4fe66 100644 --- a/src/qmplay2/headers/PixelFormats.hpp +++ b/src/qmplay2/headers/PixelFormats.hpp @@ -22,22 +22,27 @@ #include +extern "C" +{ + #include +} + enum class QMPlay2PixelFormat { None = -1, - YUV420P, - YUVJ420P, - YUV422P, - YUVJ422P, - YUV444P, - YUVJ444P, + YUV420P = AV_PIX_FMT_YUV420P, + YUVJ420P = AV_PIX_FMT_YUVJ420P, + YUV422P = AV_PIX_FMT_YUV422P, + YUVJ422P = AV_PIX_FMT_YUVJ422P, + YUV444P = AV_PIX_FMT_YUV444P, + YUVJ444P = AV_PIX_FMT_YUVJ444P, - YUV410P, - YUV411P, - YUVJ411P, - YUV440P, - YUVJ440P, + YUV410P = AV_PIX_FMT_YUV410P, + YUV411P = AV_PIX_FMT_YUV411P, + YUVJ411P = AV_PIX_FMT_YUVJ411P, + YUV440P = AV_PIX_FMT_YUV440P, + YUVJ440P = AV_PIX_FMT_YUVJ440P, Count, }; @@ -53,6 +58,7 @@ enum class QMPlay2ColorSpace SMPTE240M, BT2020, }; + struct LumaCoefficients { float cR, cG, cB; diff --git a/src/qmplay2/headers/QMPlay2Core.hpp b/src/qmplay2/headers/QMPlay2Core.hpp index 7c73b00c..5cdf4815 100644 --- a/src/qmplay2/headers/QMPlay2Core.hpp +++ b/src/qmplay2/headers/QMPlay2Core.hpp @@ -94,7 +94,11 @@ public: return *settings; } - qreal getVideoDevicePixelRatio() const; + inline qreal getVideoDevicePixelRatio() const + { + return videoDevicePixelRatio; + } + void setVideoDevicePixelRatio(); QIcon getIconFromTheme(const QString &iconName, const QIcon &fallback = QIcon()); @@ -211,6 +215,7 @@ private: QStringList logs; QMap languages; QList> videoFilters; + qreal videoDevicePixelRatio; QString lang; struct diff --git a/src/qmplay2/headers/QMPlay2Lib.hpp b/src/qmplay2/headers/QMPlay2Lib.hpp index 0d1f1016..8bc2a569 100644 --- a/src/qmplay2/headers/QMPlay2Lib.hpp +++ b/src/qmplay2/headers/QMPlay2Lib.hpp @@ -20,8 +20,13 @@ #include -#if defined(QMPLAY2SHAREDLIB_LIBRARY) - #define QMPLAY2SHAREDLIB_EXPORT Q_DECL_EXPORT +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) + // Define to nothing, it breaks linking otherwise + #define QMPLAY2SHAREDLIB_EXPORT #else - #define QMPLAY2SHAREDLIB_EXPORT Q_DECL_IMPORT + #if defined(QMPLAY2SHAREDLIB_LIBRARY) + #define QMPLAY2SHAREDLIB_EXPORT Q_DECL_EXPORT + #else + #define QMPLAY2SHAREDLIB_EXPORT Q_DECL_IMPORT + #endif #endif diff --git a/src/qmplay2/headers/Slider.hpp b/src/qmplay2/headers/Slider.hpp index 52acb3b0..d8d49ee9 100644 --- a/src/qmplay2/headers/Slider.hpp +++ b/src/qmplay2/headers/Slider.hpp @@ -22,7 +22,7 @@ #include -class QMPLAY2SHAREDLIB_EXPORT Slider final : public QSlider +class Slider : public QSlider { Q_OBJECT public: @@ -45,12 +45,12 @@ public slots: } void drawRange(int first, int second); protected: - void paintEvent(QPaintEvent *) override; - void mousePressEvent(QMouseEvent *) override; - void mouseReleaseEvent(QMouseEvent *) override; - void mouseMoveEvent(QMouseEvent *) override; - void wheelEvent(QWheelEvent *) override; - void enterEvent(QEvent *) override; + void paintEvent(QPaintEvent *) override final; + void mousePressEvent(QMouseEvent *) override final; + void mouseReleaseEvent(QMouseEvent *) override final; + void mouseMoveEvent(QMouseEvent *) override final; + void wheelEvent(QWheelEvent *) override final; + void enterEvent(QEvent *) override final; private: int getMousePos(const QPoint &pos); diff --git a/src/qmplay2/headers/StreamInfo.hpp b/src/qmplay2/headers/StreamInfo.hpp index 6a8951ac..ec830b88 100644 --- a/src/qmplay2/headers/StreamInfo.hpp +++ b/src/qmplay2/headers/StreamInfo.hpp @@ -26,6 +26,8 @@ #include #include +#include // qQNaN + using QMPlay2Tag = QPair; enum QMPlay2MediaType diff --git a/src/qmplay2/headers/YouTubeDL.hpp b/src/qmplay2/headers/YouTubeDL.hpp index 76e0d2ce..beb55ced 100644 --- a/src/qmplay2/headers/YouTubeDL.hpp +++ b/src/qmplay2/headers/YouTubeDL.hpp @@ -26,7 +26,7 @@ class NetworkReply; -class QMPLAY2SHAREDLIB_EXPORT YouTubeDL final : public BasicIO +class YouTubeDL : public BasicIO { Q_DECLARE_TR_FUNCTIONS(YouTubeDL) Q_DISABLE_COPY(YouTubeDL) @@ -38,14 +38,14 @@ public: static bool fixUrl(const QString &url, QString &outUrl, IOController<> *ioCtrl, QString *name, QString *extension, QString *error); YouTubeDL(); - ~YouTubeDL(); + ~YouTubeDL() final; void addr(const QString &url, const QString ¶m, QString *streamUrl, QString *name, QString *extension, QString *err = nullptr); QStringList exec(const QString &url, const QStringList &args, QString *silentErr = nullptr, bool rawOutput = false); private: - void abort() override; + void abort() override final; private: bool prepare();