File indexing completed on 2024-04-28 04:48:38

0001 /*
0002     SPDX-FileCopyrightText: 2005 Max Howell <max.howell@methylblue.com>
0003     SPDX-FileCopyrightText: 2007 Ian Monroe <ian@monroe.nu>
0004     SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0007 */
0008 
0009 #include "mainWindow.h"
0010 
0011 #include <QActionGroup>
0012 #include <QApplication>
0013 #include <QDBusInterface>
0014 #include <QDBusReply>
0015 #include <QDBusUnixFileDescriptor>
0016 #include <QDebug>
0017 #include <QDockWidget>
0018 #include <QDragEnterEvent>
0019 #include <QDropEvent>
0020 #include <QEvent> //::stateChanged()
0021 #include <QFileDialog>
0022 #include <QInputDialog>
0023 #include <QKeyEvent>
0024 #include <QLabel>
0025 #include <QLayout> //ctor
0026 #include <QMenu>
0027 #include <QMenuBar>
0028 #include <QMimeData>
0029 #include <QMouseEvent>
0030 #include <QObject>
0031 #include <QStatusBar>
0032 #include <QTimer>
0033 
0034 #include <KAboutData>
0035 #include <KActionMenu>
0036 #include <KCursor>
0037 #include <KIO/StatJob>
0038 #include <KIO/UDSEntry>
0039 #include <KJobWidgets>
0040 #include <KLocalizedString>
0041 #include <KProtocolInfo>
0042 #include <KSharedConfig>
0043 #include <KSqueezedTextLabel>
0044 #include <KToggleFullScreenAction>
0045 #include <KToolBar>
0046 #include <KXMLGUIFactory>
0047 #include <kio_version.h>
0048 
0049 #include <phonon/BackendCapabilities>
0050 #include <phonon/VideoWidget>
0051 
0052 #include <Solid/Device>
0053 #include <Solid/OpticalDisc>
0054 
0055 #include "actions.h"
0056 #include "discSelectionDialog.h"
0057 #include "fullScreenToolBarHandler.h"
0058 #include "messageBox.h"
0059 #include "mpris2/mpris2.h"
0060 #include "playlistFile.h"
0061 #include "theStream.h"
0062 #include "ui_videoSettingsWidget.h"
0063 #include "videoWindow.h"
0064 
0065 namespace Dragon
0066 {
0067 
0068 MainWindow *MainWindow::s_instance = nullptr;
0069 /// @see codeine.h
0070 QWidget *mainWindow()
0071 {
0072     return MainWindow::s_instance;
0073 }
0074 
0075 MainWindow::MainWindow()
0076     : KXmlGuiWindow()
0077     , m_mainView(nullptr)
0078     , m_audioView(nullptr)
0079     , m_loadView(new LoadView(this))
0080     , m_currentWidget(nullptr)
0081     , m_leftDock(nullptr)
0082     , m_positionSlider(nullptr)
0083     , m_volumeSlider(nullptr)
0084     , m_timeLabel(nullptr)
0085     , m_titleLabel(new QLabel(this))
0086     , m_menuToggleAction(nullptr)
0087     , m_stopSleepCookie(-1)
0088     , m_stopScreenPowerMgmtCookie(-1)
0089     , m_profileMaxDays(30)
0090     , m_toolbarIsHidden(false)
0091     , m_statusbarIsHidden(false)
0092     , m_menuBarIsHidden(true)
0093     , m_FullScreenHandler(nullptr)
0094 {
0095     s_instance = this;
0096     setMouseTracking(true);
0097 
0098     m_mainView = new QStackedWidget(this);
0099     m_mainView->setMouseTracking(true);
0100 
0101     new VideoWindow(this);
0102     videoWindow()->setMouseTracking(true);
0103 
0104     m_positionSlider = videoWindow()->newPositionSlider();
0105 
0106     m_mainView->addWidget(m_loadView);
0107     m_audioView = new AudioView2(this);
0108     m_mainView->addWidget(m_audioView);
0109     m_mainView->addWidget(videoWindow());
0110     m_mainView->setCurrentWidget(m_loadView);
0111 
0112     setCentralWidget(m_mainView);
0113 
0114     setFocusProxy(videoWindow()); // essential! See VideoWindow::event(), QEvent::FocusOut
0115 
0116     m_titleLabel->setContentsMargins(2, 2, 2, 2);
0117     m_titleLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
0118 
0119     // FIXME work around a bug in KStatusBar
0120     // sizeHint width of statusbar seems to get stupidly large quickly
0121     statusBar()->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Maximum);
0122 
0123     setupActions();
0124 
0125     // setStandardToolBarMenuEnabled( false ); //bah to setupGUI()!
0126     // toolBar()->show(); //it's possible it would be hidden, but we don't want that as no UI way to show it!
0127 
0128     {
0129         KActionCollection *ac = actionCollection();
0130 
0131         const auto make_menu = [this, ac](const QString &name, const QString &text) {
0132             auto menuAction = new KActionMenu(text, this);
0133             menuAction->setObjectName(name);
0134             menuAction->setEnabled(false);
0135             connect(menuAction->menu(), &QMenu::aboutToShow, this, &MainWindow::aboutToShowMenu);
0136             ac->addAction(menuAction->objectName(), menuAction);
0137         };
0138         make_menu(QLatin1String("aspect_ratio_menu"), i18nc("@title:menu", "Aspect &Ratio"));
0139         make_menu(QLatin1String("audio_channels_menu"), i18nc("@title:menu", "&Audio Channels"));
0140         make_menu(QLatin1String("subtitle_channels_menu"), i18nc("@title:menu", "&Subtitles"));
0141 
0142         m_aspectRatios = new QActionGroup(this);
0143         m_aspectRatios->setExclusive(true);
0144         const auto make_ratio_action = [this, ac](const QString &text, const QString &objectName, int aspectEnum) {
0145             auto ratioAction = new QAction(this);
0146             ratioAction->setText(text);
0147             ratioAction->setCheckable(true);
0148             m_aspectRatios->addAction(ratioAction);
0149             TheStream::addRatio(aspectEnum, ratioAction);
0150             ac->addAction(objectName, ratioAction);
0151             connect(ratioAction, &QAction::triggered, this, &MainWindow::streamSettingChange);
0152         };
0153         make_ratio_action(i18nc("@option:radio aspect ratio", "Determine &Automatically"), QLatin1String("ratio_auto"), Phonon::VideoWidget::AspectRatioAuto);
0154         make_ratio_action(i18nc("@option:radio aspect ratio", "&4:3"), QLatin1String("ratio_golden"), Phonon::VideoWidget::AspectRatio4_3);
0155         make_ratio_action(i18nc("@option:radio aspect ratio", "Ana&morphic (16:9)"), QLatin1String("ratio_anamorphic"), Phonon::VideoWidget::AspectRatio16_9);
0156         make_ratio_action(i18nc("@option:radio aspect ratio", "&Window Size"), QLatin1String("ratio_window"), Phonon::VideoWidget::AspectRatioWidget);
0157 
0158         ac->action(QLatin1String("ratio_auto"))->setChecked(true);
0159         ac->action(QLatin1String("aspect_ratio_menu"))->menu()->addActions(m_aspectRatios->actions());
0160     }
0161 
0162     setupGUI(); // load xml dragonplayerui.rc file
0163     // must be done after setupGUI:
0164     {
0165         toolBar()->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea);
0166         toolBar()->setFloatable(false);
0167     }
0168     KXMLGUIClient::stateChanged(QLatin1String("empty"));
0169 
0170     auto hamburgerMenu = KStandardAction::hamburgerMenu(nullptr, nullptr, actionCollection());
0171     hamburgerMenu->setObjectName(QStringLiteral("hamburger_menu"));
0172     toolBar()->addAction(hamburgerMenu);
0173     hamburgerMenu->setMenuBar(menuBar());
0174     if (hamburgerMenu->menu()) {
0175         hamburgerMenu->menu()->clear();
0176     }
0177     connect(hamburgerMenu, &KHamburgerMenu::aboutToShowMenu, this, [this, hamburgerMenu] {
0178         if (!hamburgerMenu->menu()) {
0179             hamburgerMenu->setMenu(new QMenu);
0180         }
0181 
0182         auto ac = actionCollection();
0183 
0184         auto menu = hamburgerMenu->menu();
0185         if (!menu->isEmpty()) {
0186             return;
0187         }
0188 
0189         auto originalSettingsMenu = qobject_cast<QMenu *>(guiFactory()->container(QStringLiteral("settings"), this));
0190         auto settingsMenu = new QMenu(originalSettingsMenu->title());
0191         settingsMenu->addActions(originalSettingsMenu->actions());
0192 
0193         menu->addMenu(qobject_cast<QMenu *>(guiFactory()->container(QStringLiteral("play_media_menu"), this)));
0194         menu->addAction(ac->action(QStringLiteral("file_open_recent")));
0195         menu->addAction(ac->action(QStringLiteral("play")));
0196         menu->addAction(ac->action(QStringLiteral("stop")));
0197         menu->addAction(ac->action(QStringLiteral("prev_chapter")));
0198         menu->addAction(ac->action(QStringLiteral("next_chapter")));
0199         menu->addSeparator();
0200         menu->addAction(ac->action(QStringLiteral("fullscreen")));
0201         menu->addSeparator();
0202         // Extract certain actions out of the settingsmenu and place them in the top level. They don't deserve to be hidden
0203         for (const auto &id :
0204              {"volume", "fullscreen", "aspect_ratio_menu", "subtitle_channels_menu", "audio_channels_menu", "toggle_dvd_menu", "video_settings"}) {
0205             auto action = ac->action(QLatin1String(id));
0206             menu->addAction(action);
0207             settingsMenu->removeAction(action);
0208         }
0209 
0210         menu->addMenu(settingsMenu);
0211         hamburgerMenu->setMenuBarAdvertised(false); // hide whatever remains (i.e. the quit action)
0212     });
0213     m_menuBarIsHidden = actionCollection()->action(QStringLiteral("options_show_menubar"))->isChecked();
0214 
0215     //"faster" startup
0216     // TODO if we have a size stored for this video, do the "faster" route
0217     QTimer::singleShot(0, this, &MainWindow::init);
0218     QApplication::setOverrideCursor(Qt::WaitCursor);
0219 }
0220 
0221 void MainWindow::init()
0222 {
0223     // connect the video player
0224     connect(engine(), &VideoWindow::stateUpdated, this, &MainWindow::engineStateChanged);
0225     connect(engine(), &VideoWindow::currentSourceChanged, this, &MainWindow::engineMediaChanged);
0226     connect(engine(), &VideoWindow::seekableChanged, this, &MainWindow::engineSeekableChanged);
0227     connect(engine(), &VideoWindow::metaDataChanged, this, &MainWindow::engineMetaDataChanged);
0228     connect(engine(), &VideoWindow::hasVideoChanged, this, &MainWindow::engineHasVideoChanged);
0229 
0230     connect(engine(), &VideoWindow::subChannelsChanged, this, &MainWindow::subChannelsChanged);
0231     connect(engine(), &VideoWindow::audioChannelsChanged, this, &MainWindow::audioChannelsChanged);
0232     connect(engine(), &VideoWindow::mutedChanged, this, &MainWindow::mutedChanged);
0233 
0234     connect(engine(), &VideoWindow::finished, this, &MainWindow::toggleLoadView);
0235 
0236     if (!engine()->init()) {
0237         KMessageBox::error(this, i18n("<qt>Phonon could not be successfully initialized. Dragon Player will now exit.</qt>"));
0238         QApplication::exit(2);
0239     }
0240 
0241     // would be dangerous for these to happen before the videoWindow() is initialised
0242     setAcceptDrops(true);
0243     connect(statusBar(), &QStatusBar::messageChanged, engine(), &VideoWindow::showOSD);
0244     // statusBar()->insertPermanentItem( "hello world", 0, 0 );
0245     m_timeLabel = new TimeLabel(statusBar());
0246     connect(videoWindow(), &VideoWindow::tick, m_timeLabel, &TimeLabel::setCurrentTime);
0247     connect(videoWindow(), &VideoWindow::totalTimeChanged, m_timeLabel, &TimeLabel::setTotalTime);
0248     statusBar()->addPermanentWidget(m_titleLabel, 100);
0249     statusBar()->addPermanentWidget(m_timeLabel);
0250 
0251     new Mpris2(this);
0252 
0253     QApplication::restoreOverrideCursor();
0254     engineStateChanged(Phonon::StoppedState); // set everything as it would be in stopped state
0255     engineSeekableChanged(false);
0256 }
0257 
0258 MainWindow::~MainWindow()
0259 {
0260     hide(); // so we appear to have quit, and then sound fades out below
0261     releasePowerSave();
0262     qobject_cast<KRecentFilesAction *>(action(QStringLiteral("file_open_recent")))
0263         ->saveEntries(KConfigGroup(KSharedConfig::openConfig(), QStringLiteral("General")));
0264     delete videoWindow(); // fades out sound in dtor
0265 }
0266 
0267 void MainWindow::closeEvent(QCloseEvent *event)
0268 {
0269     mainWindow()->setWindowState(Qt::WindowNoState);
0270     // restore the following user ui settings which are changed in full screen mode
0271     if (mainWindow()->isFullScreen()) {
0272         statusBar()->setHidden(m_statusbarIsHidden);
0273         toolBar()->setHidden(m_toolbarIsHidden);
0274         menuBar()->setHidden(m_menuBarIsHidden);
0275     }
0276 
0277     KMainWindow::closeEvent(event);
0278 }
0279 
0280 void MainWindow::wheelEvent(QWheelEvent *event)
0281 {
0282     // do not allow to change volume in load view
0283     // it can be frustrating in recently played list
0284     if (m_mainView->currentWidget() == m_loadView) {
0285         return;
0286     }
0287 
0288     if (event->angleDelta().y() > 0)
0289         engine()->increaseVolume();
0290     else
0291         engine()->decreaseVolume();
0292     event->accept();
0293 }
0294 
0295 void MainWindow::setupActions()
0296 {
0297     KActionCollection *const ac = actionCollection();
0298 
0299     auto open = KStandardAction::open(this, &MainWindow::openFileDialog, ac);
0300     open->setText(i18nc("@action", "Play File…"));
0301     open->setToolTip(i18nc("@info:tooltip", "Open a media file for playback"));
0302     auto recent = KStandardAction::openRecent(this, &MainWindow::open, ac);
0303     recent->loadEntries(KConfigGroup(KSharedConfig::openConfig(), QStringLiteral("General")));
0304     KStandardAction::quit(qApp, &QApplication::closeAllWindows, ac);
0305 
0306     const auto addToAc = [ac](QAction *action) {
0307         ac->addAction(action->objectName(), action);
0308     };
0309 
0310     auto playStreamAction = new QAction(i18nc("@action", "Play Stream…"), ac);
0311     playStreamAction->setObjectName(QStringLiteral("play_stream"));
0312     playStreamAction->setIcon(QIcon::fromTheme(QStringLiteral("document-open-remote")));
0313     connect(playStreamAction, &QAction::triggered, this, &MainWindow::openStreamDialog);
0314     addToAc(playStreamAction);
0315 
0316     auto playDiscAction = new QAction(i18nc("@action", "Play Disc"), ac);
0317     playDiscAction->setObjectName(QStringLiteral("play_disc"));
0318     playDiscAction->setIcon(QIcon::fromTheme(QStringLiteral("media-optical")));
0319     connect(playDiscAction, &QAction::triggered, this, &MainWindow::playDisc);
0320     if (Solid::Device::listFromType(Solid::DeviceInterface::OpticalDrive).isEmpty()) {
0321         playDiscAction->setVisible(false);
0322     }
0323     addToAc(playDiscAction);
0324 
0325     m_loadView->setToolbarActions({open, playStreamAction, playDiscAction});
0326 
0327     KToggleFullScreenAction *toggleFullScreen = new KToggleFullScreenAction(this, ac);
0328     toggleFullScreen->setObjectName(QLatin1String("fullscreen"));
0329     ac->setDefaultShortcuts(toggleFullScreen, QList<QKeySequence>() << Qt::Key_F << KStandardShortcut::fullScreen());
0330     toggleFullScreen->setAutoRepeat(false);
0331     connect(toggleFullScreen, SIGNAL(toggled(bool)), Dragon::mainWindow(), SLOT(setFullScreen(bool)));
0332 
0333     addToAc(toggleFullScreen);
0334 
0335     new PlayAction(this, &MainWindow::play, ac);
0336     new VolumeAction(this, &MainWindow::toggleVolumeSlider, ac);
0337 
0338     m_menuToggleAction = static_cast<KToggleAction *>(ac->addAction(KStandardAction::ShowMenubar, menuBar(), SLOT(setVisible(bool))));
0339 
0340     auto action = new QAction(i18nc("@action", "Increase Volume"), ac);
0341     action->setObjectName(QLatin1String("volume_inc"));
0342     connect(action, &QAction::triggered, engine(), &VideoWindow::increaseVolume);
0343     addToAc(action);
0344 
0345     action = new QAction(i18nc("@action", "Decrease Volume"), ac);
0346     action->setObjectName(QLatin1String("volume_dec"));
0347     connect(action, &QAction::triggered, engine(), &VideoWindow::decreaseVolume);
0348     addToAc(action);
0349 
0350     auto playerStop = new QAction(QIcon::fromTheme(QLatin1String("media-playback-stop")), i18nc("@action", "Stop"), ac);
0351     playerStop->setObjectName(QLatin1String("stop"));
0352     ac->setDefaultShortcuts(playerStop, QList<QKeySequence>() << Qt::Key_S << Qt::Key_MediaStop);
0353     connect(playerStop, &QAction::triggered, this, &MainWindow::stop);
0354     addToAc(playerStop);
0355 
0356     auto mute = new KToggleAction(QIcon::fromTheme(QLatin1String("player-volume-muted")), i18nc("@action Mute the sound output", "Mute"), ac);
0357     mute->setObjectName(QLatin1String("mute"));
0358     ac->setDefaultShortcut(mute, Qt::Key_M);
0359     connect(mute, &QAction::toggled, videoWindow(), &VideoWindow::mute);
0360     addToAc(mute);
0361 
0362     auto resetZoom = new QAction(QIcon::fromTheme(QLatin1String("zoom-fit-best")), i18nc("@action", "Reset Video Scale"), ac);
0363     resetZoom->setObjectName(QLatin1String("reset_zoom"));
0364     ac->setDefaultShortcut(resetZoom, Qt::Key_Equal);
0365     connect(resetZoom, &QAction::triggered, videoWindow(), &VideoWindow::resetZoom);
0366     addToAc(resetZoom);
0367 
0368     auto dvdMenu = new QAction(QIcon::fromTheme(QLatin1String("media-optical-video")), i18nc("@action", "Menu Toggle"), ac);
0369     dvdMenu->setObjectName(QLatin1String("toggle_dvd_menu"));
0370     ac->setDefaultShortcut(dvdMenu, Qt::Key_R);
0371     connect(dvdMenu, &QAction::triggered, engine(), &VideoWindow::toggleDVDMenu);
0372     addToAc(dvdMenu);
0373 
0374     auto positionSlider = new QWidgetAction(ac);
0375     positionSlider->setObjectName(QLatin1String("position_slider"));
0376     positionSlider->setText(i18n("Position Slider"));
0377     positionSlider->setDefaultWidget(m_positionSlider);
0378     addToAc(positionSlider);
0379 
0380     auto videoSettings = new QAction(i18nc("@option:check", "Video Settings"), ac);
0381     videoSettings->setObjectName(QLatin1String("video_settings"));
0382     videoSettings->setCheckable(true);
0383     connect(videoSettings, &QAction::toggled, this, &MainWindow::toggleVideoSettings);
0384     addToAc(videoSettings);
0385 
0386     auto uniqueToggle = new QAction(i18nc("@option:check Whether only one instance of dragon can be started"
0387                                           " and will be reused when the user tries to play another file.",
0388                                           "One Instance Only"),
0389                                     ac);
0390     uniqueToggle->setObjectName(QLatin1String("unique"));
0391     uniqueToggle->setCheckable(true);
0392     uniqueToggle->setChecked(!KSharedConfig::openConfig()->group(QStringLiteral("KDE")).readEntry("MultipleInstances", QVariant(false)).toBool());
0393     connect(uniqueToggle, &QAction::toggled, this, &MainWindow::toggleUnique);
0394     addToAc(uniqueToggle);
0395 
0396     auto prev_chapter = new QAction(QIcon::fromTheme(QLatin1String("media-skip-backward")), i18nc("@action previous chapter", "Previous"), ac);
0397     prev_chapter->setObjectName(QLatin1String("prev_chapter"));
0398     ac->setDefaultShortcuts(prev_chapter, QList<QKeySequence>() << Qt::Key_Comma << Qt::Key_MediaPrevious);
0399     connect(prev_chapter, &QAction::triggered, engine(), &VideoWindow::prevChapter);
0400     addToAc(prev_chapter);
0401 
0402     auto next_chapter = new QAction(QIcon::fromTheme(QLatin1String("media-skip-forward")), i18nc("@action next chapter", "Next"), ac);
0403     next_chapter->setObjectName(QLatin1String("next_chapter"));
0404     ac->setDefaultShortcuts(next_chapter, QList<QKeySequence>() << Qt::Key_Period << Qt::Key_MediaNext);
0405     connect(next_chapter, &QAction::triggered, engine(), &VideoWindow::nextChapter);
0406     addToAc(next_chapter);
0407 
0408     // xgettext: no-c-format
0409     auto tenPercentBack = new QAction(QIcon::fromTheme(QLatin1String("media-seek-backward")), i18nc("@action", "Return 10% Back"), ac);
0410     tenPercentBack->setObjectName(QLatin1String("ten_percent_back"));
0411     ac->setDefaultShortcut(tenPercentBack, Qt::Key_PageUp);
0412     connect(tenPercentBack, &QAction::triggered, engine(), &VideoWindow::tenPercentBack);
0413     addToAc(tenPercentBack);
0414 
0415     // xgettext: no-c-format
0416     auto tenPercentForward = new QAction(QIcon::fromTheme(QLatin1String("media-seek-forward")), i18nc("@action", "Go 10% Forward"), ac);
0417     tenPercentForward->setObjectName(QLatin1String("ten_percent_forward"));
0418     ac->setDefaultShortcut(tenPercentForward, Qt::Key_PageDown);
0419     connect(tenPercentForward, &QAction::triggered, engine(), &VideoWindow::tenPercentForward);
0420     addToAc(tenPercentForward);
0421 
0422     auto tenSecondsBack = new QAction(QIcon::fromTheme(QLatin1String("media-seek-backward")), i18nc("@action", "Return 10 Seconds Back"), ac);
0423     tenSecondsBack->setObjectName(QLatin1String("ten_seconds_back"));
0424     ac->setDefaultShortcut(tenSecondsBack, Qt::Key_Minus);
0425     connect(tenSecondsBack, &QAction::triggered, engine(), &VideoWindow::tenSecondsBack);
0426     addToAc(tenSecondsBack);
0427 
0428     auto tenSecondsForward = new QAction(QIcon::fromTheme(QLatin1String("media-seek-forward")), i18nc("@action", "Go 10 Seconds Forward"), ac);
0429     tenSecondsForward->setObjectName(QLatin1String("ten_seconds_forward"));
0430     ac->setDefaultShortcut(tenSecondsForward, Qt::Key_Plus);
0431     connect(tenSecondsForward, &QAction::triggered, engine(), &VideoWindow::tenSecondsForward);
0432     addToAc(tenSecondsForward);
0433 }
0434 
0435 void MainWindow::toggleUnique(bool unique)
0436 {
0437     KSharedConfig::Ptr cfg = KSharedConfig::openConfig(); // kf5 FIXME? this might not work w/o KUniqueApplication
0438     cfg->group(QStringLiteral("KDE")).writeEntry("MultipleInstances", !unique);
0439     cfg->sync();
0440 }
0441 
0442 void MainWindow::toggleVideoSettings(bool show)
0443 {
0444     if (show) {
0445         m_leftDock = new QDockWidget(this);
0446         m_leftDock->setObjectName(QLatin1String("left_dock"));
0447         m_leftDock->setFeatures(QDockWidget::NoDockWidgetFeatures);
0448         QWidget *videoSettingsWidget = new QWidget(m_leftDock);
0449         m_leftDock->setWidget(videoSettingsWidget);
0450         Ui::VideoSettingsWidget ui;
0451         ui.setupUi(videoSettingsWidget);
0452         KGuiItem::assign(ui.defaultsButton, KStandardGuiItem::defaults());
0453         KGuiItem::assign(ui.closeButton, KStandardGuiItem::closeWindow());
0454         videoSettingsWidget->adjustSize();
0455         addDockWidget(Qt::LeftDockWidgetArea, m_leftDock);
0456         m_sliders.clear();
0457         m_sliders << ui.brightnessSlider << ui.contrastSlider << ui.hueSlider << ui.saturationSlider;
0458         updateSliders();
0459         for (QSlider *slider : std::as_const(m_sliders)) {
0460             connect(slider, &QAbstractSlider::valueChanged, engine(), &VideoWindow::settingChanged);
0461         }
0462 
0463         connect(ui.defaultsButton, &QAbstractButton::clicked, this, &MainWindow::restoreDefaultVideoSettings);
0464         connect(ui.closeButton, &QAbstractButton::clicked, action(QStringLiteral("video_settings")), &QAction::setChecked);
0465         connect(ui.closeButton, &QAbstractButton::clicked, m_leftDock, &QObject::deleteLater);
0466     } else {
0467         m_sliders.clear();
0468         delete m_leftDock;
0469     }
0470 }
0471 
0472 void MainWindow::restoreDefaultVideoSettings()
0473 {
0474     for (QSlider *slider : std::as_const(m_sliders)) {
0475         slider->setValue(0);
0476     }
0477 }
0478 
0479 void MainWindow::toggleLoadView()
0480 {
0481     if (m_mainView->currentWidget() == m_loadView) {
0482         if (m_currentWidget && engine()->state() != Phonon::StoppedState) {
0483             if (m_mainView->indexOf(m_currentWidget) == -1) {
0484                 m_mainView->addWidget(m_currentWidget);
0485             }
0486             m_mainView->setCurrentWidget(m_currentWidget);
0487         }
0488         engine()->isPreview(false);
0489     } else if (m_currentWidget != m_audioView) {
0490         m_mainView->setCurrentWidget(m_loadView);
0491         if (m_currentWidget && engine()->state() != Phonon::StoppedState) {
0492             m_mainView->removeWidget(m_currentWidget);
0493             engine()->isPreview(true);
0494         }
0495     } else {
0496         m_mainView->setCurrentWidget(m_loadView);
0497     }
0498 }
0499 
0500 void MainWindow::toggleVolumeSlider(bool show)
0501 {
0502     if (show) {
0503         m_volumeSlider = engine()->newVolumeSlider();
0504         m_volumeSlider->setDisabled(engine()->isMuted());
0505         m_volumeSlider->setFocus(Qt::PopupFocusReason);
0506 
0507         m_muteCheckBox = new QCheckBox();
0508         m_muteCheckBox->setText(i18nc("@option:check Mute the sound output", "Mute"));
0509         m_muteCheckBox->setChecked(engine()->isMuted());
0510         connect(m_muteCheckBox, &QCheckBox::toggled, videoWindow(), &VideoWindow::mute);
0511 
0512         QVBoxLayout *layout = new QVBoxLayout();
0513         layout->addWidget(m_volumeSlider);
0514         layout->addWidget(m_muteCheckBox);
0515 
0516         QWidget *dock = new QWidget;
0517         dock->setLayout(layout);
0518 
0519         m_rightDock = new QDockWidget(this);
0520         m_rightDock->setFeatures(QDockWidget::NoDockWidgetFeatures);
0521         m_rightDock->setObjectName(QStringLiteral("volume_dock"));
0522         dock->setParent(m_rightDock);
0523         m_rightDock->setWidget(dock);
0524         addDockWidget(Qt::RightDockWidgetArea, m_rightDock);
0525     } else {
0526         disconnect(m_muteCheckBox, &QCheckBox::toggled, videoWindow(), &VideoWindow::mute);
0527         delete m_rightDock; // it's a QPointer, it will 0 itself
0528     }
0529 }
0530 
0531 void MainWindow::mutedChanged(bool mute)
0532 {
0533     if (m_rightDock) {
0534         m_volumeSlider->setDisabled(mute);
0535         m_muteCheckBox->setChecked(mute);
0536     }
0537 }
0538 
0539 void MainWindow::stop()
0540 {
0541     engine()->stop();
0542     toggleLoadView();
0543 }
0544 
0545 void MainWindow::updateSliders()
0546 {
0547     for (QSlider *slider : std::as_const(m_sliders)) {
0548         slider->setValue(engine()->videoSetting(slider->objectName()));
0549     }
0550 }
0551 
0552 void MainWindow::engineMessage(const QString &message)
0553 {
0554     statusBar()->showMessage(message, 3500);
0555 }
0556 
0557 bool MainWindow::open(const QUrl &url)
0558 {
0559     qDebug() << "Opening" << url;
0560 
0561     if (load(url)) {
0562         const int offset = (TheStream::hasProfile() && isFresh())
0563             // adjust offset if we have session history for this video
0564             ? TheStream::profile().readEntry<int>("Position", 0)
0565             : 0;
0566         qDebug() << "Initial offset is " << offset;
0567         engine()->loadSettings();
0568         updateSliders();
0569         return engine()->play(offset);
0570     }
0571 
0572     return false;
0573 }
0574 
0575 bool MainWindow::load(const QUrl &url)
0576 {
0577     // FileWatch the file that is opened
0578 
0579     if (url.isEmpty()) {
0580         MessageBox::error(i18n("Dragon Player was asked to open an empty URL; it cannot."));
0581         return false;
0582     }
0583 
0584     bool ret = false;
0585 
0586     PlaylistFile playlist(url);
0587     if (playlist.isPlaylist()) {
0588         // TODO: problem is we return out of the function
0589         // statusBar()->message( i18n("Parsing playlist file...") );
0590 
0591         if (playlist.isValid())
0592             ret = engine()->load(playlist.contents());
0593         else {
0594             MessageBox::error(playlist.error());
0595             return false;
0596         }
0597     }
0598 
0599     // local protocols like nepomuksearch:/ are not supported by xine
0600     // check if an UDS_LOCAL_PATH is defined.
0601     if (!ret && KProtocolInfo::protocolClass(url.scheme()) == QLatin1String(":local")) {
0602         // #define UDS_LOCAL_PATH (7 | KIO::UDS_STRING)
0603         KIO::StatJob *job = KIO::stat(url, KIO::StatJob::SourceSide, KIO::StatBasic);
0604         KJobWidgets::setWindow(job, this);
0605         if (job->exec()) {
0606             KIO::UDSEntry e = job->statResult();
0607             const QString path = e.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
0608             if (!path.isEmpty())
0609                 ret = engine()->load(QUrl::fromLocalFile(path));
0610         }
0611         job->deleteLater();
0612     }
0613 
0614     // let xine handle invalid, etc, QUrlS
0615     // TODO it handles non-existing files with bad error message
0616     if (!ret)
0617         ret = engine()->load(url);
0618 
0619     if (ret) {
0620         m_currentWidget = nullptr;
0621     }
0622     return ret;
0623 }
0624 
0625 void MainWindow::play()
0626 {
0627     switch (engine()->state()) {
0628     case Phonon::PlayingState:
0629         engine()->pause();
0630         break;
0631     case Phonon::PausedState:
0632         engine()->resume();
0633         if (m_mainView->currentWidget() == m_loadView)
0634             toggleLoadView();
0635         break;
0636     case Phonon::StoppedState:
0637         engine()->play();
0638         m_currentWidget = nullptr;
0639         break;
0640     default:
0641         break;
0642     }
0643 }
0644 
0645 void MainWindow::openFileDialog()
0646 {
0647     QStringList mimeFilter = Phonon::BackendCapabilities::availableMimeTypes();
0648     // temporary fixes for MimeTypes that Xine does support but it doesn't return - this is a Xine bug.
0649     mimeFilter << QLatin1String("audio/x-flac");
0650     mimeFilter << QLatin1String("video/mp4");
0651     mimeFilter << QLatin1String("application/x-cd-image"); // added for *.iso images
0652     // everything. Must be here or the native Qt dialog doesn't want to default to it! https://bugs.kde.org/show_bug.cgi?id=459326
0653     mimeFilter << QLatin1String("application/octet-stream");
0654 
0655     static QUrl lastDirectory;
0656 
0657     QFileDialog dlg(this, i18nc("@title:window", "Select File to Play"));
0658     dlg.setAcceptMode(QFileDialog::AcceptOpen);
0659     dlg.setFileMode(QFileDialog::ExistingFile);
0660     dlg.setMimeTypeFilters(mimeFilter);
0661     dlg.selectMimeTypeFilter(QStringLiteral("application/octet-stream")); // by default don't restrict
0662 
0663     if (lastDirectory.isValid()) {
0664         dlg.setDirectoryUrl(lastDirectory);
0665     } else {
0666         dlg.setDirectory(QStandardPaths::writableLocation(QStandardPaths::MoviesLocation));
0667     }
0668 
0669     dlg.exec();
0670 
0671     lastDirectory = dlg.directoryUrl();
0672     const QList<QUrl> urls = dlg.selectedUrls();
0673 
0674     if (urls.isEmpty()) {
0675         qDebug() << Q_FUNC_INFO << "URL empty";
0676         return;
0677     } else {
0678         open(urls.first());
0679     }
0680 }
0681 
0682 void MainWindow::openStreamDialog()
0683 {
0684     QUrl url = QUrl::fromUserInput(QInputDialog::getText(this, i18nc("@title:window", "Stream to Play"), i18nc("@label:textbox", "Stream:")));
0685 
0686     if (url.isEmpty()) {
0687         qDebug() << "URL empty in MainWindow::openStreamDialog()";
0688         return;
0689     } else {
0690         open(url);
0691     }
0692 }
0693 
0694 void MainWindow::playDisc()
0695 {
0696     QList<Solid::Device> playableDiscs;
0697     {
0698         const QList<Solid::Device> deviceList = Solid::Device::listFromType(Solid::DeviceInterface::OpticalDisc);
0699 
0700         for (const Solid::Device &device : deviceList) {
0701             const Solid::OpticalDisc *disc = device.as<const Solid::OpticalDisc>();
0702             if (disc) {
0703                 if (disc->availableContent()
0704                     & (Solid::OpticalDisc::VideoDvd | Solid::OpticalDisc::VideoCd | Solid::OpticalDisc::SuperVideoCd | Solid::OpticalDisc::Audio))
0705                     playableDiscs << device;
0706             }
0707         }
0708     }
0709     if (!playableDiscs.isEmpty()) {
0710         if (playableDiscs.size() > 1) { // more than one disc, show user a selection box
0711             qDebug() << "> 1 possible discs, showing dialog";
0712             new DiscSelectionDialog(this, playableDiscs);
0713         } else { // only one optical disc inserted, play whatever it is
0714             bool status = engine()->playDisc(playableDiscs.first());
0715             qDebug() << "playing disc" << status;
0716         }
0717     } else {
0718         engine()->playDvd();
0719         toggleLoadView();
0720         qDebug() << "no disc in drive or Solid isn't working";
0721     }
0722 }
0723 
0724 void MainWindow::openRecentFile(const QUrl &url)
0725 {
0726     this->open(url);
0727 }
0728 
0729 void MainWindow::setFullScreen(bool isFullScreen)
0730 {
0731     qDebug() << "Setting full screen to " << isFullScreen;
0732     mainWindow()->setWindowState((isFullScreen ? Qt::WindowFullScreen : Qt::WindowNoState));
0733 
0734     if (isFullScreen) {
0735         m_statusbarIsHidden = statusBar()->isHidden();
0736         m_toolbarIsHidden = toolBar()->isHidden();
0737         m_menuBarIsHidden = menuBar()->isHidden();
0738         toolBar()->setHidden(false);
0739         statusBar()->setHidden(true);
0740         menuBar()->setHidden(true);
0741     } else {
0742         statusBar()->setHidden(m_statusbarIsHidden);
0743         toolBar()->setHidden(m_toolbarIsHidden);
0744         menuBar()->setHidden(m_menuBarIsHidden);
0745         // In case someone hit the shortcut while being in fullscreen, the action
0746         // would be out of sync.
0747         m_menuToggleAction->setChecked(!m_menuBarIsHidden);
0748     }
0749     if (m_leftDock)
0750         m_leftDock->setHidden(isFullScreen);
0751     // the right dock is handled by the tool bar handler
0752 
0753     if (isFullScreen) {
0754         if (!m_FullScreenHandler)
0755             m_FullScreenHandler = new FullScreenToolBarHandler(this);
0756     } else {
0757         action(QStringLiteral("fullscreen"))->setEnabled(videoWindow()->state() == Phonon::PlayingState || videoWindow()->state() == Phonon::PausedState);
0758         delete m_FullScreenHandler;
0759         m_FullScreenHandler = nullptr;
0760     }
0761 }
0762 
0763 void MainWindow::showVolume(bool visible)
0764 {
0765     if (m_rightDock)
0766         m_rightDock->setVisible(visible);
0767 }
0768 
0769 bool MainWindow::volumeContains(const QPoint &mousePos)
0770 {
0771     if (m_rightDock)
0772         return m_rightDock->geometry().contains(mousePos);
0773     return false;
0774 }
0775 
0776 void MainWindow::aboutToShowMenu()
0777 {
0778     TheStream::aspectRatioAction()->setChecked(true);
0779     {
0780         const int subId = TheStream::subtitleChannel();
0781         const QList<QAction *> subs = action(QStringLiteral("subtitle_channels_menu"))->menu()->actions();
0782         qDebug() << "subtitle #" << subId << " is going to be checked";
0783         for (QAction *subAction : subs) {
0784             if (subAction->property(TheStream::CHANNEL_PROPERTY).toInt() == subId) {
0785                 subAction->setChecked(true);
0786                 break;
0787             }
0788             qDebug() << subAction->property(TheStream::CHANNEL_PROPERTY).toInt() << " not checked.";
0789         }
0790     }
0791     {
0792         const int audioId = TheStream::audioChannel();
0793         const QList<QAction *> audios = action(QStringLiteral("audio_channels_menu"))->menu()->actions();
0794         qDebug() << "audio #" << audioId << " is going to be checked";
0795         for (QAction *audioAction : audios) {
0796             if (audioAction->property(TheStream::CHANNEL_PROPERTY).toInt() == audioId) {
0797                 audioAction->setChecked(true);
0798                 break;
0799             }
0800         }
0801     }
0802 }
0803 
0804 void MainWindow::dragEnterEvent(QDragEnterEvent *e)
0805 {
0806     e->setAccepted(e->mimeData()->hasUrls());
0807 }
0808 
0809 void MainWindow::dropEvent(QDropEvent *e)
0810 {
0811     if (e->mimeData()->hasUrls())
0812         this->open(e->mimeData()->urls().first());
0813     else
0814         engineMessage(i18n("Sorry, no media was found in the drop"));
0815 }
0816 
0817 void MainWindow::keyPressEvent(QKeyEvent *e)
0818 {
0819     switch (e->key()) {
0820     case Qt::Key_Left:
0821         engine()->relativeSeek(-5000);
0822         break;
0823     case Qt::Key_Right:
0824         engine()->relativeSeek(5000);
0825         break;
0826     case Qt::Key_Escape:
0827         action(QStringLiteral("fullscreen"))->setChecked(false);
0828     default:;
0829     }
0830 }
0831 
0832 void MainWindow::inhibitPowerSave()
0833 {
0834     if (m_stopSleepCookie == -1) {
0835         QDBusInterface iface(QStringLiteral("org.freedesktop.login1"),
0836                              QStringLiteral("/org/freedesktop/login1"),
0837                              QStringLiteral("org.freedesktop.login1.Manager"),
0838                              QDBusConnection::systemBus());
0839         if (iface.isValid()) {
0840             QDBusReply<QDBusUnixFileDescriptor> reply;
0841             if (TheStream::hasVideo()) {
0842                 reply = iface.call(QStringLiteral("Inhibit"),
0843                                    QStringLiteral("sleep:idle"),
0844                                    KAboutData::applicationData().componentName(),
0845                                    QStringLiteral("playing a video"),
0846                                    QStringLiteral("block"));
0847             } else {
0848                 reply = iface.call(QStringLiteral("Inhibit"),
0849                                    QStringLiteral("sleep"),
0850                                    KAboutData::applicationData().componentName(),
0851                                    QStringLiteral("playing an audio"),
0852                                    QStringLiteral("block"));
0853             }
0854             if (reply.isValid()) {
0855                 m_stopSleepCookie = reply.value().fileDescriptor();
0856             }
0857         }
0858     }
0859     // TODO: inhibit screen sleep. No viable API found.
0860     // https://git.reviewboard.kde.org/r/129651
0861     if (TheStream::hasVideo()) {
0862         QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.ScreenSaver"),
0863                                                               QStringLiteral("/ScreenSaver"),
0864                                                               QStringLiteral("org.freedesktop.ScreenSaver"),
0865                                                               QStringLiteral("Inhibit"));
0866         message << QGuiApplication::desktopFileName();
0867         message << i18nc("Notification inhibition reason", "Playing a video");
0868         QDBusReply<uint> reply = QDBusConnection::sessionBus().call(message);
0869         if (reply.isValid()) {
0870             m_screensaverDisableCookie = reply.value();
0871             return;
0872         }
0873     }
0874 }
0875 
0876 void MainWindow::releasePowerSave()
0877 {
0878     // stop suppressing sleep
0879     if (m_stopSleepCookie != -1) {
0880         ::close(m_stopSleepCookie);
0881         m_stopSleepCookie = -1;
0882     }
0883 
0884     // stop disabling screensaver
0885     if (m_screensaverDisableCookie.has_value()) {
0886         QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.ScreenSaver"),
0887                                                               QStringLiteral("/ScreenSaver"),
0888                                                               QStringLiteral("org.freedesktop.ScreenSaver"),
0889                                                               QStringLiteral("UnInhibit"));
0890         message << static_cast<uint>(m_screensaverDisableCookie.value());
0891         m_screensaverDisableCookie = {};
0892         if (QDBusConnection::sessionBus().send(message)) {
0893             return;
0894         }
0895     }
0896 }
0897 
0898 QMenu *MainWindow::menu(const char *name)
0899 {
0900     // KXMLGUI is "really good".
0901     return static_cast<QMenu *>(factory()->container(QLatin1String(name), this));
0902 }
0903 
0904 void MainWindow::streamSettingChange()
0905 {
0906     if (sender()->objectName().left(5) == QLatin1String("ratio")) {
0907         TheStream::setRatio(dynamic_cast<QAction *>(sender()));
0908     }
0909 }
0910 
0911 void MainWindow::updateTitleBarText()
0912 {
0913     if (!TheStream::hasMedia()) {
0914         setWindowTitle(i18n("No media loaded"));
0915     } else if (engine()->state() == Phonon::PausedState) {
0916         setWindowTitle(i18n("Paused"));
0917     } else {
0918         setWindowTitle(TheStream::prettyTitle());
0919     }
0920     qDebug() << "set titles ";
0921 }
0922 
0923 #define CHANNELS_CHANGED(function, actionName)                                                                                                                 \
0924     void MainWindow::function(QList<QAction *> subActions)                                                                                                     \
0925     {                                                                                                                                                          \
0926         if (subActions.size() <= 2)                                                                                                                            \
0927             action(actionName)->setEnabled(false);                                                                                                             \
0928         else {                                                                                                                                                 \
0929             action(actionName)->menu()->addActions(subActions);                                                                                                \
0930             action(actionName)->setEnabled(true);                                                                                                              \
0931         }                                                                                                                                                      \
0932     }
0933 
0934 CHANNELS_CHANGED(subChannelsChanged, QStringLiteral("subtitle_channels_menu"))
0935 CHANNELS_CHANGED(audioChannelsChanged, QStringLiteral("audio_channels_menu"))
0936 #undef CHANNELS_CHANGED
0937 
0938 /// Convenience class for other classes that need access to the actionCollection
0939 KActionCollection *actionCollection()
0940 {
0941     return static_cast<MainWindow *>(mainWindow())->actionCollection();
0942 }
0943 
0944 /// Convenience class for other classes that need access to the actions
0945 QAction *action(const char *name)
0946 {
0947     KActionCollection *actionCollection = nullptr;
0948     QAction *action = nullptr;
0949 
0950     if (mainWindow())
0951         if ((actionCollection = ((MainWindow *)mainWindow())->actionCollection()))
0952             action = actionCollection->action(QLatin1String(name));
0953     if (!action)
0954         qDebug() << name;
0955     Q_ASSERT(mainWindow());
0956     Q_ASSERT(actionCollection);
0957     Q_ASSERT(action);
0958 
0959     return action;
0960 }
0961 
0962 bool MainWindow::isFresh()
0963 {
0964     QDate date = TheStream::profile().readEntry<QDate>("Date", QDate::currentDate());
0965 
0966     return (date.daysTo(QDate::currentDate()) < m_profileMaxDays) ? true : false;
0967 }
0968 
0969 void MainWindow::contextMenuEvent(QContextMenuEvent *event)
0970 {
0971     QMenu menu;
0972     qobject_cast<KHamburgerMenu *>(action(QStringLiteral("hamburger_menu")))->addToMenu(&menu);
0973     if (menu.isEmpty()) {
0974         return;
0975     }
0976     menu.exec(event->globalPos());
0977 }
0978 } // namespace Dragon
0979 
0980 #include "moc_mainWindow.cpp"